99 lines
2.0 KiB
Odin
99 lines
2.0 KiB
Odin
package clipsync_net
|
|||
|
|
|
||
|
|
import cnet "core:net"
|
||
|
|
|
||
|
|
// TCP framing for protocol v1:
|
||
|
|
// [4-byte big-endian length][UTF-8 JSON payload]
|
||
|
|
|
||
|
|
MAX_FRAME_BYTES :: 1024 * 1024 // 1 MiB
|
||
|
|
|
||
|
|
Framing_Error :: enum {
|
||
|
|
None,
|
||
|
|
Frame_Too_Large,
|
||
|
|
Connection_Closed,
|
||
|
|
Socket_Recv_Failed,
|
||
|
|
Socket_Send_Failed,
|
||
|
|
}
|
||
|
|
|
||
|
|
u32_to_be_bytes :: proc(v: u32) -> [4]byte {
|
||
|
|
return [4]byte{
|
||
|
|
byte((v >> 24) & 0xff),
|
||
|
|
byte((v >> 16) & 0xff),
|
||
|
|
byte((v >> 8) & 0xff),
|
||
|
|
byte(v & 0xff),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
be_bytes_to_u32 :: proc(prefix: [4]byte) -> u32 {
|
||
|
|
return (u32(prefix[0]) << 24) |
|
||
|
|
(u32(prefix[1]) << 16) |
|
||
|
|
(u32(prefix[2]) << 8) |
|
||
|
|
u32(prefix[3])
|
||
|
|
}
|
||
|
|
|
||
|
|
recv_exact :: proc(socket: cnet.TCP_Socket, out: []byte) -> Framing_Error {
|
||
|
|
offset := 0
|
||
|
|
for offset < len(out) {
|
||
|
|
read, err := cnet.recv_tcp(socket, out[offset:])
|
||
|
|
if err != nil {
|
||
|
|
return .Socket_Recv_Failed
|
||
|
|
}
|
||
|
|
if read == 0 {
|
||
|
|
return .Connection_Closed
|
||
|
|
}
|
||
|
|
offset += read
|
||
|
|
}
|
||
|
|
return .None
|
||
|
|
}
|
||
|
|
|
||
|
|
send_all :: proc(socket: cnet.TCP_Socket, data: []byte) -> Framing_Error {
|
||
|
|
offset := 0
|
||
|
|
for offset < len(data) {
|
||
|
|
written, err := cnet.send_tcp(socket, data[offset:])
|
||
|
|
if err != nil {
|
||
|
|
return .Socket_Send_Failed
|
||
|
|
}
|
||
|
|
if written == 0 {
|
||
|
|
return .Connection_Closed
|
||
|
|
}
|
||
|
|
offset += written
|
||
|
|
}
|
||
|
|
return .None
|
||
|
|
}
|
||
|
|
|
||
|
|
send_frame :: proc(socket: cnet.TCP_Socket, payload: []byte) -> Framing_Error {
|
||
|
|
if len(payload) > MAX_FRAME_BYTES {
|
||
|
|
return .Frame_Too_Large
|
||
|
|
}
|
||
|
|
|
||
|
|
prefix := u32_to_be_bytes(u32(len(payload)))
|
||
|
|
if err := send_all(socket, prefix[:]); err != .None {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
return send_all(socket, payload)
|
||
|
|
}
|
||
|
|
|
||
|
|
recv_frame :: proc(socket: cnet.TCP_Socket, allocator := context.allocator) -> ([]byte, Framing_Error) {
|
||
|
|
len_prefix: [4]byte
|
||
|
|
if err := recv_exact(socket, len_prefix[:]); err != .None {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
|
||
|
|
size := int(be_bytes_to_u32(len_prefix))
|
||
|
|
if size > MAX_FRAME_BYTES {
|
||
|
|
return nil, .Frame_Too_Large
|
||
|
|
}
|
||
|
|
|
||
|
|
buf := make([]byte, size, allocator)
|
||
|
|
if size == 0 {
|
||
|
|
return buf, .None
|
||
|
|
}
|
||
|
|
|
||
|
|
if err := recv_exact(socket, buf); err != .None {
|
||
|
|
delete(buf)
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
|
||
|
|
return buf, .None
|
||
|
|
}
|