BaileysEx.Syncd.Codec (baileys_ex v0.1.0-alpha.9)

Copy Markdown View Source

Syncd protocol codec — encode/decode snapshots, patches, and mutations with MAC generation and verification.

Pure functions that implement the Baileys Syncd data transformation pipeline. All crypto operations use Erlang :crypto. Non-deterministic inputs (random IV) are injectable via options for testing.

Ports chat-utils.ts:45-489generateMac, generateSnapshotMac, generatePatchMac, makeLtHashGenerator, decodeSyncdMutations, decodeSyncdPatch, decodeSyncdSnapshot, decodePatches, encodeSyncdPatch, extractSyncdPatches.

Summary

Functions

Decode a list of Syncd mutations, verifying MACs and decrypting values.

Decode a single Syncd patch, verifying the patch MAC and decoding all mutations.

Decode a full Syncd snapshot, verifying the snapshot MAC and extracting all mutations.

Extract Syncd patches and snapshots from a server response binary node.

Finalize the LTHash generator — apply accumulated adds/subs to the hash.

Generate a value MAC for a mutation record.

Generate a patch MAC that chains to the previous snapshot.

Generate a snapshot MAC from the LTHash state.

Create an LTHash generator that accumulates mix operations.

Mix a single mutation into the LTHash generator state.

Create a fresh LTHash state with version 0 and a 128-byte zero hash.

Valid Syncd collection names.

Types

chat_mutation()

@type chat_mutation() :: %{sync_action: map(), index: [String.t()]}

chat_mutation_map()

@type chat_mutation_map() :: %{required(String.t()) => chat_mutation()}

chat_mutation_order()

@type chat_mutation_order() :: [chat_mutation()]

lt_hash_state()

@type lt_hash_state() :: %{
  version: non_neg_integer(),
  hash: binary(),
  index_value_map: %{required(String.t()) => %{value_mac: binary()}}
}

mutation_operation()

@type mutation_operation() :: :set | :remove

patch_name()

@type patch_name() ::
  :critical_block
  | :critical_unblock_low
  | :regular_high
  | :regular_low
  | :regular

Functions

decode_patches(name, patches, initial, get_app_state_sync_key, minimum_version_number \\ nil, validate_macs \\ true, opts \\ [])

@spec decode_patches(
  patch_name(),
  [map()],
  lt_hash_state(),
  (String.t() -> {:ok, map()} | {:error, term()}),
  non_neg_integer() | nil,
  boolean(),
  keyword()
) ::
  {:ok,
   %{
     state: lt_hash_state(),
     mutation_map: chat_mutation_map(),
     mutation_order: chat_mutation_order()
   }}
  | {:error, term()}

Decode a sequence of Syncd patches, maintaining state across them.

Ports decodePatches from chat-utils.ts:422-489.

decode_syncd_mutations(msg_mutations, initial_state, get_app_state_sync_key, on_mutation, validate_macs)

@spec decode_syncd_mutations(
  [map()],
  lt_hash_state(),
  (String.t() -> {:ok, map()} | {:error, term()}),
  (chat_mutation() -> any()),
  boolean()
) :: {:ok, %{hash: binary(), index_value_map: map()}} | {:error, term()}

Decode a list of Syncd mutations, verifying MACs and decrypting values.

For each mutation:

  1. Extract operation and record
  2. Fetch decryption key via get_app_state_sync_key callback
  3. Verify value MAC (if validate_macs)
  4. AES-256-CBC decrypt the value
  5. Decode SyncActionData protobuf
  6. Verify index MAC (if validate_macs)
  7. Call on_mutation callback
  8. Mix into LTHash generator

Returns the updated LTHash state (hash + index_value_map).

Ports decodeSyncdMutations from chat-utils.ts:197-270.

decode_syncd_patch(msg, name, initial_state, get_app_state_sync_key, on_mutation, validate_macs)

@spec decode_syncd_patch(
  map(),
  patch_name(),
  lt_hash_state(),
  (String.t() -> {:ok, map()} | {:error, term()}),
  (chat_mutation() -> any()),
  boolean()
) :: {:ok, %{hash: binary(), index_value_map: map()}} | {:error, term()}

Decode a single Syncd patch, verifying the patch MAC and decoding all mutations.

Ports decodeSyncdPatch from chat-utils.ts:272-304.

decode_syncd_snapshot(name, snapshot, get_app_state_sync_key, minimum_version_number \\ nil, validate_macs \\ true)

@spec decode_syncd_snapshot(
  patch_name(),
  map(),
  (String.t() -> {:ok, map()} | {:error, term()}),
  non_neg_integer() | nil,
  boolean()
) ::
  {:ok,
   %{
     state: lt_hash_state(),
     mutation_map: chat_mutation_map(),
     mutation_order: chat_mutation_order()
   }}
  | {:error, term()}

Decode a full Syncd snapshot, verifying the snapshot MAC and extracting all mutations.

Ports decodeSyncdSnapshot from chat-utils.ts:374-420.

encode_syncd_patch(patch_create, my_app_state_key_id, state, get_app_state_sync_key, opts \\ [])

@spec encode_syncd_patch(
  map(),
  String.t(),
  lt_hash_state(),
  (String.t() -> {:ok, map()} | {:error, term()}),
  keyword()
) :: {:ok, %{patch: map(), state: lt_hash_state()}} | {:error, term()}

Encode a single outbound Syncd patch.

Steps:

  1. Fetch encryption key via get_app_state_sync_key
  2. Encode SyncActionData protobuf
  3. AES-256-CBC encrypt with random IV (injectable via opts[:iv])
  4. Generate value MAC, index MAC
  5. Update LTHash state
  6. Generate snapshot MAC, patch MAC
  7. Build SyncdPatch protobuf

Returns {:ok, %{patch: SyncdPatch.t(), state: lt_hash_state()}}.

Ports encodeSyncdPatch from chat-utils.ts:132-195.

extract_syncd_patches(response, opts \\ [])

@spec extract_syncd_patches(
  map(),
  keyword()
) ::
  {:ok,
   %{
     required(atom()) => %{
       patches: [map()],
       has_more_patches: boolean(),
       snapshot: map() | nil
     }
   }}
  | {:error, term()}

Extract Syncd patches and snapshots from a server response binary node.

Ports extractSyncdPatches from chat-utils.ts:306-356.

finish_lt_hash_generator(gen)

@spec finish_lt_hash_generator(map()) :: %{hash: binary(), index_value_map: map()}

Finalize the LTHash generator — apply accumulated adds/subs to the hash.

Ports the finish() closure from makeLtHashGenerator in chat-utils.ts:103-111.

generate_mac(operation, data, key_id, key)

@spec generate_mac(mutation_operation(), binary(), binary(), binary()) :: binary()

Generate a value MAC for a mutation record.

HMAC-SHA512 over (op_byte || key_id) || encrypted_value || (8-byte length), truncated to the first 32 bytes.

Ports generateMac from chat-utils.ts:45-67.

generate_patch_mac(snapshot_mac, value_macs, version, type, key)

@spec generate_patch_mac(
  binary(),
  [binary()],
  non_neg_integer(),
  patch_name(),
  binary()
) :: binary()

Generate a patch MAC that chains to the previous snapshot.

HMAC-SHA256 over snapshot_mac || value_macs... || version_64bit_be || name_utf8.

Ports generatePatchMac from chat-utils.ts:119-128.

generate_snapshot_mac(lt_hash, version, name, key)

@spec generate_snapshot_mac(binary(), non_neg_integer(), patch_name(), binary()) ::
  binary()

Generate a snapshot MAC from the LTHash state.

HMAC-SHA256 over lt_hash || version_64bit_be || name_utf8.

Ports generateSnapshotMac from chat-utils.ts:114-117.

init_lt_hash_generator(map)

@spec init_lt_hash_generator(lt_hash_state()) :: %{
  index_value_map: map(),
  add_buffs: [binary()],
  sub_buffs: [binary()],
  hash: binary()
}

Create an LTHash generator that accumulates mix operations.

Returns {state, mix_fn, finish_fn} where:

  • mix_fn.(state, %{index_mac:, value_mac:, operation:}) → updated state
  • finish_fn.(state)%{hash:, index_value_map:}

This is a functional equivalent of Baileys' closure-based makeLtHashGenerator. Use with Enum.reduce/3 over mutations.

mix_mutation(gen, map)

@spec mix_mutation(map(), %{
  index_mac: binary(),
  value_mac: binary(),
  operation: mutation_operation()
}) ::
  map()

Mix a single mutation into the LTHash generator state.

Ports the mix() closure from makeLtHashGenerator in chat-utils.ts:83-101.

new_lt_hash_state()

@spec new_lt_hash_state() :: lt_hash_state()

Create a fresh LTHash state with version 0 and a 128-byte zero hash.

Ports newLTHashState() from chat-utils.ts:130.

patch_names()

@spec patch_names() :: [patch_name()]

Valid Syncd collection names.