EDA.Gateway.Encoding.ETF (EDA v0.3.0)

Copy Markdown View Source

Erlang External Term Format encoding for Discord Gateway payloads.

Binary format decoded natively by the BEAM via :erlang.binary_to_term/1, significantly faster than JSON parsing. Payloads are ~15-30% smaller.

Normalization

Discord's ETF payloads differ from JSON in two ways:

  • Atom keys — Map keys arrive as atoms (e.g. :op) instead of strings.
  • Integer snowflakes — Large IDs arrive as integers instead of strings.

normalize/1 deep-converts the decoded term so the result is identical to what Jason.decode!/1 would produce. This means the rest of EDA sees no difference between ETF and JSON payloads.

Security

We use :erlang.binary_to_term/1 without the :safe option because Discord may introduce new fields (= new atoms) at any time. With :safe, unknown atoms would crash the decoder. Since normalize/1 immediately converts all atoms to strings, there is no long-term pollution of the atom table beyond the decode call.

Summary

Functions

Decodes an ETF binary and normalizes atom keys and snowflake integers to strings.

Encodes a map as an ETF binary frame.

Deep-converts an ETF-decoded term to match Jason.decode!/1 output.

Returns "etf" for the gateway URL query parameter.

Functions

decode(binary)

@spec decode(binary()) :: map()

Decodes an ETF binary and normalizes atom keys and snowflake integers to strings.

encode(map)

@spec encode(map()) :: {:binary, binary()}

Encodes a map as an ETF binary frame.

Atom keys are converted to strings before encoding because Discord's ETF parser requires string (binary) keys — atom keys cause a 4002 close code. This mirrors what Jason.encode!/1 does implicitly for JSON.

normalize(map)

@spec normalize(term()) :: term()

Deep-converts an ETF-decoded term to match Jason.decode!/1 output.

  • Atom map keys become strings
  • Atom values become strings (e.g. event type atoms)
  • Integers above 4194304 (2^22) become strings (snowflakes, permissions)
  • Booleans and nil are preserved
  • Lists and nested maps are recursively normalized

url_encoding()

@spec url_encoding() :: String.t()

Returns "etf" for the gateway URL query parameter.