Length From
Marks a field whose byte length is determined by a numeric sibling elsewhere in the message — the length is carried as a separate constructor parameter because the consumer cares about it as a number (flow control, routing) or because the on-wire prefix shape is one @LengthPrefixed cannot express (e.g. TLS uint24).
Accepted on:
Stringfields — body is a single UTF-8 string sized by the sibling.List<T>whereTis a@ProtocolMessagedata class — body is a sequence of nested-message bodies, byte-bounded by the sibling.TwhereTis a@ProtocolMessagedata class or sealed parent — body is a single nested message; the sibling-derived length covers the whole nested wire form. Decode narrowsbuffer.limit()to the bounded extent and delegates to<TCodec>.decode; encode delegates to<TCodec>.encodeand the user is responsible for sizing the sibling to the body's wire byte count.
When to prefer @LengthPrefixed
For adjacent length carriers, prefer @LengthPrefixed when the prefix shape matches one of LengthPrefix.Byte / LengthPrefix.Short / LengthPrefix.Int. A field whose only purpose is to bound the immediately following sibling is a redundant length carrier — modeling it as an independent constructor parameter encodes the same quantity twice (prefix vs. value's wireSize). The validator rejects the redundant shape for String and List<T> bodies.
When @LengthFrom is the only option
Adjacent siblings remain valid for nested @ProtocolMessage bodies because @LengthPrefixed only supports 1 / 2 / 4-byte prefixes — protocols with non-standard prefix widths cannot express their wire shape via @LengthPrefixed. Example: TLS 1.3 handshake header (RFC 8446 §4) uses a 3-byte big-endian length:
@ProtocolMessage(wireOrder = Endianness.Big)
data class TlsHandshake(
val msgType: UByte,
@WireBytes(3) val length: UInt, // uint24
@LengthFrom("length") val body: TlsHandshakeBody,
)Genuine remote-prefix: length carried in a non-adjacent field, often parsed by a different codec or sitting several positions away (MQTT-style header-bounded payloads, parent-passed bounds via @DispatchOn, etc.):
@ProtocolMessage
data class RemoteHeader(
val payloadLength: UShort, // consumer-visible — flow control, routing
val flags: UByte,
val correlationId: UInt,
@LengthFrom("payloadLength") val payload: String,
)Parameters
The name of the sibling field that holds the byte length. Must exist, come before this field, and resolve to either a non-nullable numeric scalar (Byte/Short/Int/Long/UByte/ UShort/UInt/ULong) for the simple form "siblingName", or a value class with a val property returning non-nullable Int for the dotted form "siblingName.property".