Skip to content

Wire format

The byte-level encoding, ported from RemotingUtils. Implemented in src/proto/{frame,flex,writer,reader}.ts.

Every post-handshake message is a frame:

[cmd byte] [flexInt requestId — only if cmd has 0x40] [flexInt bodyLength] [body]
  • cmd low 6 bits = the command (see overview).
  • 0x80 set → this frame is a response. On a response, 0x40 means isFinal.
  • 0x40 set on a request → the call expects a response, and a requestId (flexInt) immediately follows. Fire-and-forget calls omit both the bit and the id.

src/proto/frame.ts provides encodeRequest, encodeResponse, and a streaming FrameParser.

Integers are big-endian base-128 with a continuation bit (0x80) on every byte except the last. Two widths:

HelperWidthUsed for
flexInt32-bitlengths, method ids, enum values, type ids
flexLong64-bitobject ids

WriteInteger in the original is flexInt, so a length of -1 (used to mean “null”) is encoded as the uint32 0xFFFFFFFF.

import { writeFlexInt, readFlexInt, writeFlexLong, readFlexLong } from 'roon-internal-api';

Ported in writer.ts / reader.ts:

TypeEncoding
stringflexInt(byteLen) + utf8 bytes; nullflexInt(-1)
SooidflexInt(len) + raw bytes
bool1 byte (0/1)
guid16 raw .NET-order bytes
double / floatlittle-endian IEEE-754
byte[]flexInt(len) + raw bytes
enumflexInt(value)
FormEncoding
int? long? double? float? char? guid?bool present then the value if present
bool?one byte: 0 = false, 1 = true, 2 = null
Sooid?flexInt(len), with -1 meaning null

The single call that broke the project open, fully decoded:

43 <msgid> 1a2e 8420 12<18-byte profile Sooid> <TrackBase handle> <state byte>
│ │ │ │ │ │ └ 0 = un-favorite, 1 = favorite
│ │ │ │ │ └ arg 2: the track's *ephemeral* object handle
│ │ │ │ └ arg 1: profile/context System.Sooid (constant per user)
│ │ │ └ method handle
│ │ └ callback handle
│ └ request id
└ CALL (cmd 3, with response bit)

The two lessons baked into this one packet: the long constant blob is the profile Sooid (not the track), and the track is referenced by an ephemeral session handle you must obtain live first. See the journey for why that mattered so much.