towerops/src/towerops_proto_wire_ffi.erl
Graham McIntire e54ee75513 Rewrite protobuf encoding/decoding in pure Gleam with integrated validation (#104)
Replace the protobuf hex package (agent.pb.ex) and hand-written validator
(validator.ex) with a pure Gleam implementation: wire format primitives,
all 23 message types, encode/decode functions, and validation merged into
decode. Thin Elixir wrappers in agent.ex preserve the existing struct API.

- Wire format: varint, length-delimited, double (IEEE 754 via FFI), tag encode/decode
- Types: all proto3 message types as Gleam custom types with enums and oneof
- Decode: field-level parsing with integrated validation (UUID, IP, hostname, limits)
- Encode: proto3 default omission, bytes_tree concatenation, map/repeated fields
- Wrappers: Elixir structs with encode/decode delegating to Gleam, enum atom conversion
- Remove {:protobuf, "~> 0.12"} dependency

Reviewed-on: graham/towerops-web#104
2026-03-21 16:08:40 -05:00

54 lines
1.6 KiB
Erlang

-module(towerops_proto_wire_ffi).
-export([encode_double/1, decode_double/1, split_bytes/2,
bits_append/2, string_to_bits/1,
int_and/2, int_or/2, shift_left/2, shift_right/2]).
%% Encode a float as 8-byte little-endian IEEE 754 double.
-spec encode_double(float()) -> binary().
encode_double(Value) ->
<<Value:64/float-little>>.
%% Decode 8 bytes as a little-endian IEEE 754 double.
-spec decode_double(binary()) -> {ok, float()} | {error, nil}.
decode_double(<<Value:64/float-little>>) ->
{ok, Value};
decode_double(_) ->
{error, nil}.
%% Split a binary into two parts at position Len.
-spec split_bytes(binary(), non_neg_integer()) -> {ok, {binary(), binary()}} | {error, nil}.
split_bytes(Data, Len) when byte_size(Data) >= Len ->
<<Part:Len/binary, Rest/binary>> = Data,
{ok, {Part, Rest}};
split_bytes(_, _) ->
{error, nil}.
%% Append two binaries.
-spec bits_append(binary(), binary()) -> binary().
bits_append(A, B) ->
<<A/binary, B/binary>>.
%% Convert a string (binary) to bits (identity for BEAM).
-spec string_to_bits(binary()) -> binary().
string_to_bits(S) ->
S.
%% Bitwise AND.
-spec int_and(integer(), integer()) -> integer().
int_and(A, B) ->
A band B.
%% Bitwise OR.
-spec int_or(integer(), integer()) -> integer().
int_or(A, B) ->
A bor B.
%% Bitwise shift left.
-spec shift_left(integer(), non_neg_integer()) -> integer().
shift_left(Value, Bits) ->
Value bsl Bits.
%% Bitwise shift right (arithmetic).
-spec shift_right(integer(), non_neg_integer()) -> integer().
shift_right(Value, Bits) ->
Value bsr Bits.