RemainingBytes

annotation class RemainingBytes

Marks a field that consumes the remaining bytes of the bounded buffer.

Accepted on:

  • String — UTF-8 body consuming the rest of the buffer.

  • List<T> where T is a @ProtocolMessage data class — loop reads nested-message bodies until the bound is reached.

  • Typed binary payload via @RemainingBytes @UseCodec(C::class) val: P — the user codec consumes the bounded region in one call. Use this instead of a raw scalar list when the bytes are opaque (an image, a compressed blob, an encrypted payload). See UseCodec. P must extend Payload (a self-contained, consumer-owned value) — unless C implements com.ditchoom.buffer.codec.ViewCodec, the explicit opt-in for zero-copy borrowed views (then P may be any type, including ReadBuffer; the view's lifetime is tied to the source buffer per the ViewCodec contract).

For protocols that genuinely need a typed list of single bytes the scalar-list shape (List<UByte> / List<Byte>) is also accepted, but the typed-payload pattern above is the preferred way to model a binary blob.

Bounding the body

The decoder reads against buffer.limit(). Callers (or an outer codec) are responsible for narrowing buffer.limit() to the body's extent before invoking decode — typical for protocols whose framing carries the body byte count outside the codec's view (MQTT's fixed-header remaining-length variable-byte-integer, parsed by an outer dispatcher; HTTP/2 payload length, parsed by the frame header).

Simplest case (terminal)

@ProtocolMessage
data class LogEntry(
val level: UByte,
@RemainingBytes val message: String, // reads everything after level byte
)

Trailing FixedSize fields

@RemainingBytes may appear before the last constructor parameter, provided every trailing field is fixed-size on the wire (a plain scalar or a value-class scalar). The decoder subtracts the trailers' summed wire bytes from buffer.limit() before the body read so the trailers survive intact:

@ProtocolMessage
data class TextWithChecksum(
val tag: UByte,
@RemainingBytes val text: String, // bounded to limit() - 4 (the crc)
val crc: UInt, // 4-byte FixedSize trailer
)

Variable-size trailers (@LengthPrefixed, @LengthFrom, another @RemainingBytes, @When, @UseCodec) are rejected by the validator with a focused error: the body decode would have no way to know its end without re-encoding. Move @RemainingBytes to the end of the constructor parameter list, or remove the trailer.