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 }