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 }