pnyx/client.go

129 lines
2.6 KiB
Go

2024-04-03 18:52:04 -06:00
package pnyx
import (
"crypto/ed25519"
"crypto/rand"
2024-04-06 17:03:31 -06:00
mrand "math/rand"
"encoding/binary"
2024-04-03 18:52:04 -06:00
"crypto/sha512"
2024-04-06 17:03:31 -06:00
"crypto/aes"
"net"
"sync"
"fmt"
"github.com/google/uuid"
)
type ClientID uuid.UUID
func(id ClientID) String() string {
return uuid.UUID(id).String()
}
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
ConnectionLock sync.Mutex
Connection *net.UDPConn
2024-04-03 18:52:04 -06:00
}
func ID[T ~[16]byte, V ~[]byte](data V) T {
hash := sha512.Sum512(data)
return (T)(hash[0:16])
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 {
var err error
_, key, err = ed25519.GenerateKey(rand.Reader)
if err != nil {
2024-04-06 17:03:31 -06:00
return nil, err
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")
}
address, err := net.ResolveUDPAddr("udp", remote)
if err != nil {
2024-04-06 17:03:31 -06:00
return nil, err
}
2024-04-06 17:03:31 -06:00
connection, err := net.DialUDP("udp", nil, address)
if err != nil {
2024-04-06 17:03:31 -06:00
return nil, err
}
2024-04-06 17:03:31 -06:00
session_open, ecdh_private, err := NewSessionOpen(key)
if err != nil {
2024-04-06 17:03:31 -06:00
connection.Close()
return nil, err
}
2024-04-06 17:03:31 -06:00
_, err = connection.Write(session_open)
if err != nil {
2024-04-06 17:03:31 -06:00
return nil, err
}
var response = [512]byte{}
2024-04-06 17:03:31 -06:00
read, _, err = connection.ReadFromUDP(response[:])
if err != nil {
2024-04-06 17:03:31 -06:00
return nil, err
}
if response[0] != byte(SESSION_OPEN) {
2024-04-06 17:03:31 -06:00
return nil, fmt.Errorf("Invalid SESSION_OPEN response: %x", response[0])
}
2024-04-06 17:03:31 -06:00
server_pubkey, ecdh_public, err := ParseSessionOpen(response[COMMAND_LENGTH:read])
if err != nil {
2024-04-06 17:03:31 -06:00
return nil, err
}
secret, err := ECDH(ecdh_public, ecdh_private)
if err != nil {
2024-04-06 17:03:31 -06:00
return nil, err
}
2024-04-06 17:03:31 -06:00
session_cipher, err := aes.NewCipher(secret)
if err != nil {
return nil, err
}
2024-04-06 17:03:31 -06:00
session_connect := NewSessionConnect(connection.LocalAddr().(*net.UDPAddr), secret)
_, err = connection.Write(session_connect)
if err != nil {
2024-04-06 17:03:31 -06:00
return nil, err
}
2024-04-06 17:03:31 -06:00
read, _, err = connection.ReadFromUDP(response[:])
if err != nil {
2024-04-06 17:03:31 -06:00
return nil, err
}
2024-04-06 17:03:31 -06:00
return &Client{
Key: key,
Session: Session{
ID: ID[SessionID](secret),
remote: address,
Peer: ID[ClientID](server_pubkey),
secret: secret,
cipher: session_cipher,
iv_generator: mrand.NewSource(int64(binary.BigEndian.Uint64(seed_bytes))).(mrand.Source64),
},
Connection: connection,
}, nil
}