2024-04-03 18:52:04 -06:00
|
|
|
package pnyx
|
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto/ed25519"
|
|
|
|
"crypto/rand"
|
2024-04-23 16:38:08 -06:00
|
|
|
"fmt"
|
2024-04-06 16:38:14 -06:00
|
|
|
"net"
|
2024-04-23 16:38:08 -06:00
|
|
|
"sync/atomic"
|
|
|
|
"time"
|
2024-04-06 16:38:14 -06:00
|
|
|
)
|
|
|
|
|
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-23 16:38:08 -06:00
|
|
|
remote string
|
|
|
|
|
|
|
|
connection *net.UDPConn
|
|
|
|
|
|
|
|
data_fn func(Payload)error
|
2024-04-23 16:57:04 -06:00
|
|
|
|
2024-04-23 16:38:08 -06:00
|
|
|
active atomic.Bool
|
2024-04-23 16:57:04 -06:00
|
|
|
connected atomic.Bool
|
2024-04-23 16:38:08 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
func(client *Client) Remote() string {
|
|
|
|
return client.remote
|
|
|
|
}
|
|
|
|
|
|
|
|
func(client *Client) Active() bool {
|
|
|
|
return client.active.Load()
|
2024-04-03 18:52:04 -06:00
|
|
|
}
|
|
|
|
|
2024-04-23 16:38:08 -06:00
|
|
|
const CLIENT_UDP_BUFFER = 2048
|
|
|
|
const CLIENT_MAX_CONNECT_ATTEMPTS = 5
|
|
|
|
|
|
|
|
func(client *Client) Log(format string, vals ...any) {
|
|
|
|
fmt.Printf("%s\n", fmt.Sprintf(format, vals...))
|
|
|
|
}
|
|
|
|
|
|
|
|
func(client *Client) listen_udp() {
|
|
|
|
buffer := [CLIENT_UDP_BUFFER]byte{}
|
|
|
|
for client.active.Load() {
|
|
|
|
read, err := client.connection.Read(buffer[:])
|
|
|
|
if err != nil {
|
|
|
|
client.Log("Client listen error - %s", err)
|
|
|
|
} else if read == 0 {
|
|
|
|
client.Log("Client listen error - no data in packet")
|
|
|
|
} else {
|
|
|
|
packet_type := SessionPacketType(buffer[0])
|
2024-04-23 16:57:04 -06:00
|
|
|
if client.connected.Load() {
|
2024-04-23 16:38:08 -06:00
|
|
|
switch packet_type {
|
|
|
|
case SESSION_DATA:
|
|
|
|
if len(buffer) < SESSION_ID_LENGTH + 1 {
|
|
|
|
client.Log("Not enough data to decode SESSION_DATA packet %d/%d", len(buffer), SESSION_ID_LENGTH + 1)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
session_id := SessionID(buffer[1:1+SESSION_ID_LENGTH])
|
|
|
|
if session_id != client.Session.ID {
|
|
|
|
client.Log("Session ID of data packet does not match client.Session %s =/= %s", session_id, client.Session.ID)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
data, err := ParseSessionData(&client.Session, buffer[1+SESSION_ID_LENGTH:read])
|
|
|
|
if err != nil {
|
|
|
|
client.Log("Error parsing session data: %s", err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
payload, err := ParsePacket(data)
|
|
|
|
if err != nil {
|
|
|
|
client.Log("Error parsing packet from session data: %s", err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
switch payload.(type) {
|
|
|
|
case PingPacket:
|
|
|
|
err = client.Send(NewPingPacket())
|
|
|
|
if err != nil {
|
|
|
|
client.Log("Error sending ping packet: %s", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
err = client.data_fn(payload)
|
|
|
|
if err != nil {
|
|
|
|
client.Log("Error running data_fn: %s", err)
|
|
|
|
}
|
2024-04-23 16:57:04 -06:00
|
|
|
case SESSION_CLOSED:
|
|
|
|
client.Log("Server sent SESSION_CLOSED")
|
|
|
|
client.active.Store(false)
|
2024-04-23 16:38:08 -06:00
|
|
|
default:
|
2024-04-23 16:57:04 -06:00
|
|
|
client.Log("Bad session packet type %s for connected session", packet_type)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
switch packet_type {
|
|
|
|
case SESSION_CONNECTED:
|
|
|
|
client.connected.Store(true)
|
|
|
|
case SESSION_CLOSED:
|
|
|
|
client.Log("Server repsonded to session connect with SESSION_CLOSED")
|
|
|
|
client.active.Store(false)
|
|
|
|
default:
|
|
|
|
client.Log("Bad session packet type %s for disconnected session", packet_type)
|
2024-04-23 16:38:08 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewClient(key ed25519.PrivateKey, remote string, data_fn func(Payload)error) (*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-23 16:38:08 -06:00
|
|
|
} else if data_fn == nil {
|
|
|
|
return nil, fmt.Errorf("Need a function to run with session data")
|
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
|
|
|
}
|
|
|
|
|
|
|
|
var response = [512]byte{}
|
2024-04-23 16:38:08 -06:00
|
|
|
var session Session
|
|
|
|
for attempts := 0; attempts <= CLIENT_MAX_CONNECT_ATTEMPTS; attempts++ {
|
|
|
|
_, err = connection.Write(session_open)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: handle timeout
|
|
|
|
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 {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
net_error, ok := err.(net.Error)
|
|
|
|
if ok == false {
|
|
|
|
return nil, err
|
|
|
|
} else if net_error.Timeout() {
|
|
|
|
if attempts == CLIENT_MAX_CONNECT_ATTEMPTS {
|
|
|
|
return nil, fmt.Errorf("Failed to connect to server at %s", remote)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
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-23 16:38:08 -06:00
|
|
|
|
|
|
|
client := &Client{
|
2024-04-06 17:03:31 -06:00
|
|
|
Key: key,
|
2024-04-07 13:27:28 -06:00
|
|
|
Session: session,
|
2024-04-23 16:38:08 -06:00
|
|
|
remote: remote,
|
2024-04-08 11:28:52 -06:00
|
|
|
|
2024-04-23 16:38:08 -06:00
|
|
|
connection: connection,
|
|
|
|
data_fn: data_fn,
|
|
|
|
}
|
|
|
|
|
|
|
|
client.active.Store(true)
|
2024-04-23 16:57:04 -06:00
|
|
|
client.connected.Store(false)
|
2024-04-08 11:28:52 -06:00
|
|
|
|
2024-04-23 16:38:08 -06:00
|
|
|
go client.listen_udp()
|
|
|
|
|
|
|
|
return client, nil
|
|
|
|
}
|
|
|
|
|
2024-04-23 16:57:04 -06:00
|
|
|
func(client *Client) Send(packet *Packet) error {
|
|
|
|
if client.active.Load() == false {
|
|
|
|
} else if client.connected.Load() == false {
|
2024-04-08 11:28:52 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
data, err := packet.MarshalBinary()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
wrapped, err := NewSessionData(&client.Session, data)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2024-04-23 16:57:04 -06:00
|
|
|
_, err = client.connection.Write(wrapped)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2024-04-23 16:38:08 -06:00
|
|
|
}
|
2024-04-23 16:57:04 -06:00
|
|
|
|
|
|
|
return nil
|
2024-04-08 11:28:52 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
func(client *Client) Close() error {
|
2024-04-23 16:38:08 -06:00
|
|
|
var err error = fmt.Errorf("Client not active")
|
|
|
|
if client.active.CompareAndSwap(true, false) {
|
|
|
|
client.connection.Write(NewSessionTimed(SESSION_CLOSE, client.Key, &client.Session, time.Now()))
|
|
|
|
err = client.connection.Close()
|
2024-04-08 11:28:52 -06:00
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|