diff options
author | Lassi Pulkkinen <lassi@pulk.fi> | 2024-10-31 03:11:21 +0200 |
---|---|---|
committer | Lassi Pulkkinen <lassi@pulk.fi> | 2024-10-31 03:51:35 +0200 |
commit | ae44478b30d890fe0fb04022f44d474dcdcc3f9d (patch) | |
tree | 5f462459ae4b47d22114eed717d1382d08cf4dfe /recv.ha |
Diffstat (limited to 'recv.ha')
-rw-r--r-- | recv.ha | 105 |
1 files changed, 105 insertions, 0 deletions
@@ -0,0 +1,105 @@ +use errors; +use io; +use mcproto; +use net; + +type Receiver = struct { + buf: []u8, + start: size, + avail: size, + packet_length: (size | void), +}; + +fn newrecv(bufsize: size) Receiver = { + assert(bufsize & (bufsize - 1) == 0, "bufsize must be a power of 2"); + + return Receiver { + buf = alloc([0...], bufsize), + start = 0, + avail = 0, + packet_length = void, + }; +}; + +fn recv_finish(recv: *Receiver) void = { + free(recv.buf); +}; + +fn recv_poll(recv: *Receiver, s: io::handle, out: []u8) (size | void) = { + for (true) { + for (true) match (recv.packet_length) { + case void => + match (recv_read_varint(recv)) { + case void => + break; + case let length: i32 => + if (length: u32 > len(recv.buf)) { + die("packet too long: {}", length); + }; + recv.packet_length = length: u32; + }; + case let length: size => + if (recv.avail < length) { + break; + }; + recv.packet_length = void; + + assert(len(out) >= length, + "not enough space in output buffer"); + + let payload = out[..0]; + const pl_end = recv.start + length & len(recv.buf) - 1; + if (recv.start < pl_end || length == 0) { + static append(payload, + recv.buf[recv.start..pl_end]...); + } else { + static append(payload, + recv.buf[recv.start..]...); + static append(payload, + recv.buf[..pl_end]...); + }; + assert(len(payload) == length); + recv_advance(recv, length); + + return length; + }; + + assert(recv.avail < len(recv.buf)); + const end = recv.start + recv.avail & len(recv.buf) - 1; + const limit = if (recv.start <= end) len(recv.buf) + else recv.start; + match (io::read(s, recv.buf[end..limit])) { + case let nread: size => + recv.avail += nread; + case io::EOF => + die("disconnected"); + case errors::again => + return; + case let err: io::error => + die("read: {}", io::strerror(err)); + }; + }; +}; + +fn recv_read_varint(recv: *Receiver) (i32 | void) = { + let res = 0u32; + + for (let i = 0u32; i < 3; i += 1) { + if (i >= recv.avail) { + return; + }; + const b = recv.buf[recv.start + i & len(recv.buf) - 1]; + res |= (b & 0x7f): u32 << (7 * i); + if (b & 0x80 == 0) { + recv_advance(recv, i + 1); + return res: i32; + }; + }; + + die("varint should have ended by now"); +}; + +fn recv_advance(recv: *Receiver, n: size) void = { + recv.start = recv.start + n & len(recv.buf) - 1; + recv.avail -= n; +}; |