Chapter 33 / 51
Volume 5 · Extensions, Ops & Projects

The XTP2 extension: speaking binary over TCP

TCP gives you a byte stream; XTP2 gives you message boundaries. This chapter installs the framing library (ch. 23's foreshadowing) into EventData, yielding a protocol service immune to sticky packets and malicious frames.

Overview

Positioning: xtp2 is the "codec primitive" (see the API reference); this chapter is the "assembly craft" — where session state lives, how partials carry over, how bad frames break. The finished product is project 2.

33.1 Intro

The pain of binary protocols was never "designing the frame format" — it's implementation discipline: boundary handling (sticky/partial packets), length validation (malicious oversized frames), type safety (view lifetimes). xtp2 turned all three into a library: a 32-byte fixed header + four message types (REQUEST/RESPONSE/PUSH/EVENT) + a streaming parser; you only feed it bytes and handle the messages it produces. Prerequisite: the build.bat xtp variant.

33.2 Frames & session buffers

Assembly starts with the session structure: one processor per connection plus a partial-packet carry buffer (ch. 21's registry pattern, TCP edition):

The session structure (registry + processor)
typedef struct XtpSession {
    xtp2_processor* pRx;      /* one parser per connection */
    xbuffer*        pPending; /* partial carry (the tail feed couldn't digest) */
} XtpSession;
/* created on Open (into the registry) · removed on Close (processor and buffer destroyed) */

Why pPending exists: xtp2_feed yields at most one message per call — one TCP delivery may hold "one and a half" or several; whatever remains at XTP2_MORE stays for the next EventData.

33.3 Assembling the protocol in EventData

The assembly core (the official example, sessionized)signatures from lib/xtp2.h
static void EventData(XS_HostInfo* pHost, XS_StreamConn* pConn, xnetbuf* pBuffer)
{
    XtpSession* pS = SessionFind(pConn);      /* session from the registry */
    xbuffer* pIn = pS->pPending;
    BufferAppendNetBuf(pIn, pBuffer);          /* buffer chain → contiguous buffer */

    for ( ;; ) {
        size_t iUsed = 0;
        xtp2_message tMsg;
        xtp2_result r = xtp2_feed(pS->pRx,
            BufferData(pIn), BufferSize(pIn), &iUsed, &tMsg);
        BufferConsume(pIn, iUsed);             /* drop the consumed part */
        if ( r < 0 ) {                     /* bad frame: sticky error */
            SessionDrop(pConn);                 /* disconnect: the 33.4 decision */
            return;
        }
        if ( r == XTP2_MESSAGE ) {
            DispatchMessage(pConn, &tMsg);     /* a use-and-drop view */
            continue;                      /* the buffer may hold more whole frames */
        }
        break;  /* XTP2_MORE: keep the tail for next round */
    }
}

What just happened — all four assembly points are in there: the carry buffer bridging rounds, precise consumed-based consumption, message views used up within the round, and errors breaking immediately (a sticky error has no "try again"). The response direction packs with xtp2_measure + encode then xrtNetStreamSend — echoing the REQUEST's id into the RESPONSE gives correlation.

33.4 limits & security

  • Length defense: max_packet (default 1MiB) takes effect during header parsing — a malicious oversized frame is rejected before its body is read; memory can't be tricked by a header "declaring 4GB"
  • Structure defense: cross-checks of parameter table lengths, bounds checks on header fields, and unsigned conversions for status avoiding implementation-defined behavior
  • Disconnection policy: a sticky protocol error means the stream is untrusted — the fuzz-verified round-trip invariant guarantees "whatever decodes is a legal frame"; conversely, feeding on after a bad frame only compounds errors. Disconnect + log is the most honest handling for binary protocols
  • Time defense: TCP idle_timeout catches the slow-loris "connect and send half a header"

33.5 Contract

  • Session: one processor per connection; the carry buffer bridges rounds; Close's terminal destroys everything uniformly.
  • Messages: views from feed are used up in the round; copy before keeping.
  • Errors: a sticky error = disconnect; tighten limits to what the business actually needs.

Common mistakes

Ignoring consumed, re-feeding the whole round
Symptom
Messages processed twice, or parsing misaligned.
Cause
feed's consumed tells you how many bytes this call ate — without consuming them, the next round re-parses from the start. Buffer operations must advance strictly by consumed.
Resetting to retry after a bad frame
Symptom
After reset clears the error, feeding the same stream yields garbled messages.
Cause
reset only clears parser state — it doesn't repair the already-misaligned byte stream; the second half of a broken message gets read as a new header. reset's proper use is "reusing the processor object" (new connection/new stream).

Exercises

BasicAn echo frame

Implement a minimal PING REQUEST → PONG RESPONSE service (the client can build frames with xtp2_encode).

Acceptance: ids echoed correctly; 3 frames sent at once all answered (sticky packets).
AdvancedFragment bombardment

Have the client send one frame in 1-byte fragments, verifying the partial-packet buffer.

Acceptance: after byte-by-byte delivery of one message, the server produces it exactly once.
ChallengeMalicious-frame testing

Construct three attacks — oversized length headers, truncated frames, illegal magic — and confirm the server's memory stays flat and disconnects are clean.

Acceptance: all three disconnect at once; RSS flat throughout; normal clients unaffected.

Summary

PointConclusion
Positioninga codec primitive: frame format + parsing discipline as a library
Assemblysession = processor + carry buffer; consume precisely by consumed
Messagesfeed views used up per round; REQUEST ids echoed for correlation
Securitylimits effective at header time; bad frame = disconnect; idle as backstop
xrt further reading