package pnyx import ( "crypto/ed25519" "crypto/rand" "crypto/sha512" "net" "sync" "fmt" "github.com/google/uuid" ) type PeerID uuid.UUID func(id PeerID) String() string { return uuid.UUID(id).String() } 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 ID[T ~[16]byte, V ~[]byte](data V) T { hash := sha512.Sum512(data) return (T)(hash[0:16]) } 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_OPEN) { return nil, fmt.Errorf("Invalid SESSION_OPEN response: %x", response[0]) } session, err := ParseSessionOpen(ecdh_private, response[COMMAND_LENGTH:read]) if err != nil { return nil, err } session_connect := NewSessionConnect(connection.LocalAddr().(*net.UDPAddr), session.secret) _, 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(NewSessionClose(&client.Session)) err := client.Connection.Close() client.Connection = nil return err }