2024-04-03 18:52:04 -06:00
|
|
|
package pnyx
|
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto/ed25519"
|
|
|
|
"crypto/rand"
|
2024-04-06 16:38:14 -06:00
|
|
|
"net"
|
|
|
|
"sync"
|
2024-04-11 21:05:50 -06:00
|
|
|
"time"
|
2024-04-06 16:38:14 -06:00
|
|
|
"fmt"
|
|
|
|
)
|
|
|
|
|
|
|
|
type ClientState uint8
|
|
|
|
|
|
|
|
const (
|
|
|
|
CLIENT_SESSION_CREATE ClientState = iota
|
|
|
|
CLIENT_SESSION_CONNECT
|
|
|
|
CLIENT_SESSION_CONNECTED
|
2024-04-03 18:52:04 -06:00
|
|
|
)
|
|
|
|
|
|
|
|
type Client struct {
|
|
|
|
Key ed25519.PrivateKey
|
2024-04-06 17:03:31 -06:00
|
|
|
Session Session
|
2024-04-06 16:38:14 -06:00
|
|
|
ConnectionLock sync.Mutex
|
|
|
|
Connection *net.UDPConn
|
2024-04-03 18:52:04 -06:00
|
|
|
}
|
|
|
|
|
2024-04-06 17:03:31 -06:00
|
|
|
func NewClient(key ed25519.PrivateKey, remote string) (*Client, error) {
|
2024-04-03 18:52:04 -06:00
|
|
|
if key == nil {
|
2024-04-18 20:10:01 -06:00
|
|
|
return nil, fmt.Errorf("Need a key to create a client, passed nil")
|
2024-04-03 18:52:04 -06:00
|
|
|
}
|
|
|
|
|
2024-04-06 17:03:31 -06:00
|
|
|
seed_bytes := make([]byte, 8)
|
|
|
|
read, err := rand.Read(seed_bytes)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
} else if read != 8 {
|
|
|
|
return nil, fmt.Errorf("Failed to create IV seed for client")
|
|
|
|
}
|
2024-04-06 16:38:14 -06:00
|
|
|
|
|
|
|
address, err := net.ResolveUDPAddr("udp", remote)
|
|
|
|
if err != nil {
|
2024-04-06 17:03:31 -06:00
|
|
|
return nil, err
|
2024-04-06 16:38:14 -06:00
|
|
|
}
|
|
|
|
|
2024-04-06 17:03:31 -06:00
|
|
|
connection, err := net.DialUDP("udp", nil, address)
|
2024-04-06 16:38:14 -06:00
|
|
|
if err != nil {
|
2024-04-06 17:03:31 -06:00
|
|
|
return nil, err
|
2024-04-06 16:38:14 -06:00
|
|
|
}
|
|
|
|
|
2024-04-06 17:03:31 -06:00
|
|
|
session_open, ecdh_private, err := NewSessionOpen(key)
|
2024-04-06 16:38:14 -06:00
|
|
|
if err != nil {
|
2024-04-06 17:03:31 -06:00
|
|
|
connection.Close()
|
|
|
|
return nil, err
|
2024-04-06 16:38:14 -06:00
|
|
|
}
|
|
|
|
|
2024-04-06 17:03:31 -06:00
|
|
|
_, err = connection.Write(session_open)
|
2024-04-06 16:38:14 -06:00
|
|
|
if err != nil {
|
2024-04-06 17:03:31 -06:00
|
|
|
return nil, err
|
2024-04-06 16:38:14 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
var response = [512]byte{}
|
2024-04-06 17:03:31 -06:00
|
|
|
read, _, err = connection.ReadFromUDP(response[:])
|
2024-04-06 16:38:14 -06:00
|
|
|
if err != nil {
|
2024-04-06 17:03:31 -06:00
|
|
|
return nil, err
|
2024-04-06 16:38:14 -06:00
|
|
|
}
|
|
|
|
|
2024-04-11 21:05:50 -06:00
|
|
|
if response[0] != byte(SESSION_OPENED) {
|
2024-04-06 17:03:31 -06:00
|
|
|
return nil, fmt.Errorf("Invalid SESSION_OPEN response: %x", response[0])
|
2024-04-06 16:38:14 -06:00
|
|
|
}
|
|
|
|
|
2024-04-11 21:05:50 -06:00
|
|
|
session, err := ParseSessionOpened(nil, ecdh_private, response[COMMAND_LENGTH:read])
|
2024-04-06 16:38:14 -06:00
|
|
|
if err != nil {
|
2024-04-06 17:03:31 -06:00
|
|
|
return nil, err
|
2024-04-06 16:38:14 -06:00
|
|
|
}
|
|
|
|
|
2024-04-11 21:05:50 -06:00
|
|
|
session_connect := NewSessionTimed(SESSION_CONNECT, key, &session, time.Now())
|
2024-04-06 17:03:31 -06:00
|
|
|
_, err = connection.Write(session_connect)
|
2024-04-06 16:38:14 -06:00
|
|
|
if err != nil {
|
2024-04-06 17:03:31 -06:00
|
|
|
return nil, err
|
2024-04-06 16:38:14 -06:00
|
|
|
}
|
|
|
|
|
2024-04-06 17:03:31 -06:00
|
|
|
read, _, err = connection.ReadFromUDP(response[:])
|
2024-04-06 16:38:14 -06:00
|
|
|
if err != nil {
|
2024-04-06 17:03:31 -06:00
|
|
|
return nil, err
|
2024-04-06 16:38:14 -06:00
|
|
|
}
|
2024-04-11 21:05:50 -06:00
|
|
|
|
2024-04-06 17:03:31 -06:00
|
|
|
return &Client{
|
|
|
|
Key: key,
|
2024-04-07 13:27:28 -06:00
|
|
|
Session: session,
|
2024-04-06 17:03:31 -06:00
|
|
|
Connection: connection,
|
|
|
|
}, nil
|
2024-04-06 16:38:14 -06:00
|
|
|
}
|
2024-04-08 11:28:52 -06:00
|
|
|
|
2024-04-09 17:08:46 -06:00
|
|
|
func(client *Client) Send(packet *Packet) error {
|
2024-04-08 11:28:52 -06:00
|
|
|
client.ConnectionLock.Lock()
|
|
|
|
defer client.ConnectionLock.Unlock()
|
|
|
|
|
|
|
|
if client.Connection == nil {
|
|
|
|
return fmt.Errorf("Client is not connected")
|
|
|
|
}
|
|
|
|
|
|
|
|
data, err := packet.MarshalBinary()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
wrapped, err := NewSessionData(&client.Session, data)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = client.Connection.Write(wrapped)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
func(client *Client) Close() error {
|
|
|
|
client.ConnectionLock.Lock()
|
|
|
|
defer client.ConnectionLock.Unlock()
|
|
|
|
|
|
|
|
if client.Connection == nil {
|
|
|
|
return fmt.Errorf("No connection to close")
|
|
|
|
}
|
|
|
|
|
2024-04-11 21:05:50 -06:00
|
|
|
client.Connection.Write(NewSessionTimed(SESSION_CLOSE, client.Key, &client.Session, time.Now()))
|
2024-04-08 11:28:52 -06:00
|
|
|
err := client.Connection.Close()
|
|
|
|
client.Connection = nil
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|