ForwardCompatible

@Target(allowedTargets = [AnnotationTarget.CLASS])
annotation class ForwardCompatible(val unknown: KClass<*>)

Marks a @ProtocolMessage sealed dispatch parent as forward compatible: a decoder that hits a discriminator it does not recognize skips the unknown variant's framed payload and preserves it verbatim into the unknown variant, instead of throwing DecodeException.

This lets newer protocol ops survive a round-trip through an older decoder (relay) or an on-disk frame (persistence): the bytes are read into an opaque buffer on decode and re-emitted byte-for-byte on encode.

Requirements (enforced at compile time)

  1. The annotated type must also carry FramedBy — you cannot skip a variant whose length you cannot measure. The framing prefix bounds the payload the decoder skips.

  2. The annotated type must use DispatchOn dispatch with either a single-byte discriminator (the preserved opcode is one byte, re-encoded verbatim) or a varint discriminator — a value class whose inner scalar is @UseCodec(<VariableLengthCodec>) raw: Long | ULong (QUIC varint, LEB128, …). A varint opcode is preserved as its full decoded value and re-encoded through the discriminator's own codec, so a multi-byte GREASE-style type round-trips in its canonical minimal encoding. Fixed multi-byte discriminators (UShort/UInt) are not supported.

  3. unknown must name a member of the sealed type marked UnknownVariant, whose primary constructor is shaped (opcode: Int, raw: PlatformBuffer) (a ReadBuffer-typed raw is also accepted). For a single-byte discriminator opcode is Int and carries the discriminator byte; for a varint discriminator it must be the discriminator's own inner type (Long / ULong) and carries the full decoded type value. raw carries the opaque framed payload, excluding the opcode and length prefix.

@ProtocolMessage
@DispatchOn(OpCode::class)
@FramedBy(OpLengthCodec::class, after = "header")
@ForwardCompatible(unknown = Op.Unknown::class)
sealed interface Op {
@ProtocolMessage @PacketType(value = 0x12, wire = 0x12)
data class Scroll(val header: OpCode, /* ... */) : Op

@UnknownVariant
data class Unknown(val opcode: Int, val raw: PlatformBuffer) : Op
}

Preserved bytes are allocated through ForwardCompatibleFactoryKey (default BufferFactory.managed() — GC lifetime, no manual free). A caller wanting native/pooled memory injects a pool-backed factory via that context key and owns freeing.

Parameters

unknown

The UnknownVariant-marked member of the sealed type that receives skipped-and-preserved ops.

See also

ForwardCompatibleFactoryKey

Properties

Link copied to clipboard
val unknown: KClass<*>