pnyx/client.go

134 lines
2.6 KiB
Go

package pnyx
import (
"crypto/ed25519"
"crypto/rand"
"net"
"sync"
"time"
"fmt"
)
type ClientState uint8
const (
CLIENT_SESSION_CREATE ClientState = iota
CLIENT_SESSION_CONNECT
CLIENT_SESSION_CONNECTED
)
type Client struct {
Key ed25519.PrivateKey
Session Session
ConnectionLock sync.Mutex
Connection *net.UDPConn
}
func NewClient(key ed25519.PrivateKey, remote string) (*Client, error) {
if key == nil {
var err error
_, key, err = ed25519.GenerateKey(rand.Reader)
if err != nil {
return nil, err
}
}
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")
}
address, err := net.ResolveUDPAddr("udp", remote)
if err != nil {
return nil, err
}
connection, err := net.DialUDP("udp", nil, address)
if err != nil {
return nil, err
}
session_open, ecdh_private, err := NewSessionOpen(key)
if err != nil {
connection.Close()
return nil, err
}
_, err = connection.Write(session_open)
if err != nil {
return nil, err
}
var response = [512]byte{}
read, _, err = connection.ReadFromUDP(response[:])
if err != nil {
return nil, err
}
if response[0] != byte(SESSION_OPENED) {
return nil, fmt.Errorf("Invalid SESSION_OPEN response: %x", response[0])
}
session, err := ParseSessionOpened(nil, ecdh_private, response[COMMAND_LENGTH:read])
if err != nil {
return nil, err
}
session_connect := NewSessionTimed(SESSION_CONNECT, key, &session, time.Now())
_, err = connection.Write(session_connect)
if err != nil {
return nil, err
}
read, _, err = connection.ReadFromUDP(response[:])
if err != nil {
return nil, err
}
return &Client{
Key: key,
Session: session,
Connection: connection,
}, nil
}
func(client *Client) Send(packet *Packet) error {
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")
}
client.Connection.Write(NewSessionTimed(SESSION_CLOSE, client.Key, &client.Session, time.Now()))
err := client.Connection.Close()
client.Connection = nil
return err
}