defmodule GrpcClient.Encoding do # This module is derived from https://github.com/NFIBrokerage/spear/blob/9177196721d943fda2ee1a70698580b036f220ee/lib/spear/grpc.ex # and https://github.com/NFIBrokerage/spear/blob/main/lib/spear/request.ex @spec to_binary_data(tuple()) :: {iodata(), pos_integer()} def to_binary_data(%module{} = message) do {:ok, encoded} = module.encode(message) iodata = :binary.list_to_bin(encoded) length = byte_size(iodata) compressed = 0 {[<>, iodata], 1 + 4 + length} end # Parse DATA frame(s) # See https://github.com/grpc/grpc/blob/fd27fb09b028e5823f3f246e90bad2d02dfd90d3/doc/PROTOCOL-HTTP2.md # # notes: # - "1 byte unsigned integer" is 8 bits, hence `unsigned-integer-8` # - `unit(4)` captures 4 bytes for the message_length # - endianness does not matter when the binary in question is 1 byte # hence big/little is not specified for `compressed_flag` (however the # default according to the Elixir documentation is big endian). # - it _does_ matter for message_length however which is 4 bytes # - this is why the specification says "big endian" for the message_length # - the signature of this function makes it very easy to use with # Stream.unfold/2 @spec from_binary_data(binary(), module()) :: nil | {tuple(), binary()} def from_binary_data( <<0::unsigned-integer-8, message_length::unsigned-big-integer-8-unit(4), encoded_message::binary-size(message_length), rest::binary>>, module ) do decoded = Protox.decode!(encoded_message, module) {decoded, rest} end def from_binary_data(_empty_or_malformed, _module), do: nil end