Files
clipsync/desktop-daemon/src/net/framing.odin
T

99 lines
2.0 KiB
Odin
Raw Normal View History

2026-08-27 15:38:55 +02:00
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
}