---
title: "WebSocket, REST, TLS and JSON at Speed"
book: "Low-Latency Software"
subject: quant
language: en
chapter: 17
exercises: 8
source: https://one-course.com/books/quant/13/en/chapter/17-websocket-rest-tls-and-json-at-speed
---

# Chapter 17 — WebSocket, REST, TLS and JSON at Speed

Crypto venues and many brokers do not speak FIX or binary protocols to most of their clients: market data arrives as JSON text inside [WebSocket frames](#def-ll-websocket-rest-tls-and-json-at-speed-ws) inside TLS records, and orders leave as signed HTTP requests or as JSON over the same kind of connection. A trading server placed in the venue’s cloud region (One Quant Book 3, chapter 26) sits a fraction of a millisecond from its matching engine. On this book’s laptop, the Python reference of this chapter’s build spends almost $50\,\text{µ}\mathrm{s}$ of processor time to receive one depth update and send one order (opening and sealing TLS records, unmasking and masking frames, decoding the JSON, signing); the C++ components spend about one. The first is a tenth of a half-millisecond round trip; the second disappears in it. This chapter takes the web stack apart layer by layer: HTTP and the cost of opening connections, TLS and what its handshake and records cost, [WebSocket](#def-ll-websocket-rest-tls-and-json-at-speed-ws) framing, JSON decoding with a structural index, and request signatures computed without rehashing the key.

## 17.1 HTTP, REST and connection reuse

**Definition 17.1 (REST interface, persistent connection, connection pool).**

A *REST interface* exposes a service as resources addressed by URLs and manipulated by HTTP requests (GET to read, POST to create, DELETE to remove), each request carrying everything the server needs to process it. A *persistent connection* carries many HTTP requests and responses over one TCP (and TLS) connection instead of opening one per request. A *connection pool* keeps a set of open persistent connections to a server and lends an idle one to each request.

A crypto venue’s order entry is typically a [REST interface](#def-ll-websocket-rest-tls-and-json-at-speed-rest): an order is a POST with the order’s parameters, the client’s API key and a signature (One Quant Book 3, chapter 15), and the answer is JSON. The expensive part is not the request but the connection under it. Opening one costs a TCP handshake, one round trip, then a [TLS handshake](#def-ll-websocket-rest-tls-and-json-at-speed-tls), another round trip plus the cryptography of key exchange and certificate verification; a [WebSocket](#def-ll-websocket-rest-tls-and-json-at-speed-ws) adds an HTTP upgrade, a third. HTTP/1.1 “defaults to the use of [persistent connections](#def-ll-websocket-rest-tls-and-json-at-speed-rest)” (RFC 9112), and a client that uses them pays these costs once per connection instead of once per order. The build’s pool keeps a fixed number of connections open and passes every request through Book 3’s rate-limit governor first, since a venue’s limits count requests and weights (Book 3, chapter 15), not connections.

![A WebSocket connection over TLS 1.3: one round trip for TCP, one for TLS, one for the HTTP upgrade; then each message is a WebSocket frame inside a TLS record. A REST order on a new connection pays the first two round trips; on a persistent connection, none.](https://one-course.com/images/onecourse/chapters/quant-13/ll-websocket-rest-tls-and-json-at-speed/fig-838cf15008e2.svg)

***Figure 17.1.** A [WebSocket](#def-ll-websocket-rest-tls-and-json-at-speed-ws) connection over TLS 1.3: one round trip for TCP, one for TLS, one for the HTTP upgrade; then each message is a [WebSocket frame](#def-ll-websocket-rest-tls-and-json-at-speed-ws) inside a TLS record. A REST order on a new connection pays the first two round trips; on a [persistent connection](#def-ll-websocket-rest-tls-and-json-at-speed-rest), none.*

## 17.2 TLS: the handshake and the record layer

**Definition 17.2 (TLS handshake, session resumption).**

The *TLS handshake* is the exchange that opens a TLS connection: the two sides agree on a protocol version and cipher, exchange key shares, the server proves its identity with its certificate, and both derive the keys that protect the records that follow; in TLS 1.3 it takes one round trip. *Session resumption* opens a new connection with a key derived from an earlier one (a pre-shared key sent to the client as a ticket), skipping the certificate and part of the key exchange.

TLS 1.3 (RFC 8446) cut the full handshake to one round trip and replaced the older session identifiers and tickets by resumption with a pre-shared key: “the key derived from the initial handshake is used to bootstrap the cryptographic state instead of a full handshake”. A zero-round-trip mode lets a resuming client send data with its first message, “at the cost of certain security properties” (the data can be replayed), which is why an order must never travel in it. After the handshake, the record layer cuts the stream into records of at most $2^{14}$ bytes and protects each with an authenticated cipher.

[Table 17.1](#tab-ll-websocket-rest-tls-and-json-at-speed-tls) gives the costs on the laptop’s loopback, where the network costs almost nothing and what remains is processing. A full TLS 1.3 handshake with an elliptic-curve certificate takes about $2.1\,\mathrm{m}\mathrm{s}$ with Python’s `ssl` module (both ends on the same machine), a resumed one about $1.6\,\mathrm{m}\mathrm{s}$. A round trip of 300 bytes on an open connection takes about $45\,\text{µ}\mathrm{s}$ over TLS and $33\,\text{µ}\mathrm{s}$ over plain TCP, both in Python; the protection itself, one AES-128-GCM seal or open of a 300-byte record with OpenSSL called from C++, is under $0.2\,\text{µ}\mathrm{s}$. The handshake is ten thousand times dearer than a record: the reason to keep connections open.

| Operation (loopback, laptop) | Cost |
| --- | --- |
| TLS 1.3 full handshake (Python `ssl`, both ends) | $2.1\,\mathrm{m}\mathrm{s}$ |
| TLS 1.3 resumed handshake (Python `ssl`, both ends) | $1.6\,\mathrm{m}\mathrm{s}$ |
| Round trip of 300 bytes over TLS (Python) | $45\,\text{µ}\mathrm{s}$ |
| Round trip of 300 bytes over plain TCP (Python) | $33\,\text{µ}\mathrm{s}$ |
| AES-128-GCM seal of a 300-byte record (OpenSSL, C++) | $0.16\,\text{µ}\mathrm{s}$ |
| AES-128-GCM open of a 300-byte record (OpenSSL, C++) | $0.15\,\text{µ}\mathrm{s}$ |

***Table 17.1.** TLS on the laptop’s loopback: medians of 300 handshakes each way and of 5 000 round trips, and of 21 passes of 1 000 records. Measured on a laptop (Intel Core Ultra 7 155H) under WSL2, no isolated cores; OpenSSL 3.0.2, Python 3.10. Data: `bench_web.py`, `measured_tls.csv` and `measured_web.csv`.*

## 17.3 WebSocket framing

**Definition 17.3 (WebSocket, WebSocket frame).**

A *WebSocket* is a bidirectional message channel over one TCP connection (usually inside TLS), opened by an HTTP request that asks to upgrade the connection (RFC 6455). A *WebSocket frame* is its unit on the wire: a two-byte header (a final-fragment bit, an opcode for text, binary, continuation, close, ping or pong, a mask bit and a 7-bit length), an extended length of 2 or 8 bytes when the payload exceeds 125 bytes, a 4-byte masking key on frames from the client, and the payload.

Three rules of RFC 6455 shape a client. “A client MUST mask all frames that it sends to the server”, with a new random key per frame, even over TLS: the payload is XORed with the key repeated, a defence of intercepting proxies, not of the data. A message may be split into fragments (a first frame with the opcode, continuation frames, the last with its final bit), and control frames (ping, pong, close) may arrive between fragments, carry at most 125 bytes and are never fragmented. And the venue checks the connection with pings that the client must answer: one large venue sends a ping every 20 seconds and drops a connection that has not answered within a minute. The build’s decoder unmasks in place eight bytes at a time.

```cpp

// XOR with the 4-byte key, eight bytes at a time (the key repeated twice in a 64-bit word), then the tail.
inline void unmask(std::uint8_t* p, std::size_t n, const std::uint8_t key[4]) {
    std::uint64_t k8;
    std::uint8_t kk[8] = {key[0], key[1], key[2], key[3], key[0], key[1], key[2], key[3]};
    std::memcpy(&k8, kk, 8);
    std::size_t i = 0;
    for (; i + 8 <= n; i += 8) {
        std::uint64_t w;
        std::memcpy(&w, p + i, 8);
        w ^= k8;
        std::memcpy(p + i, &w, 8);
    }
    for (; i < n; ++i) p[i] ^= key[i % 4];
```

***Listing 17.1.** Unmasking a payload with the 4-byte key repeated in a 64-bit word. code/firm/wsclient/cpp/firm_wsclient.hpp*

Framing costs little in C++: about $24\,\mathrm{n}\mathrm{s}$ to decode and unmask a 300-byte frame, $16\,\mathrm{n}\mathrm{s}$ to encode and mask one. The build’s Python reference, which XORs the payload one byte at a time, takes about $16\,\text{µ}\mathrm{s}$ for the same frame: several hundred times more, and the largest item of its budget.

## 17.4 JSON at speed

**Definition 17.4 (Structural index).**

A *structural index* of a JSON text is the array of the positions of its structural characters (braces, brackets, colons, commas) and quotes, computed in one vectorised pass; a decoder then walks the index instead of the bytes, reading each value directly between two known positions.

The parser of Langdale and Lemire (chapter 14) works in two stages: the first writes “the location of all structural characters … as integer indexes in a separate array”, the second walks that index. The build applies the idea to one message shape, the depth update a large venue documents for its diff-depth stream: an event time, a symbol, the first and last update identifiers (`U`, `u`) that Book 3’s book builder checks for gaps, and arrays of bids and asks as `[price, quantity]` strings. The first stage is chapter 14’s `positions_any` over the seven characters `{}[]:,` and the double quote; the second walks the positions with the shape known in advance, reads each number where it stands, and parses prices into integers of $10^{-8}$ without floating point. Anything unexpected (an escape, a missing key) makes it return false, and a general parser takes over.

```cpp
    while (k < m) {
        std::string_view key;
        if (!str(key) || k >= m || s[ix[k++]] != ':') return false;
        if (key == "b" || key == "a") {
            if (!(key == "b" ? levels(d.bids, d.nb) : levels(d.asks, d.na))) return false;
        } else if (s[ix[k]] == '"') {
            std::string_view v;
            if (!str(v)) return false;
            if (key == "s") {
                const std::size_t n = v.size() < sizeof d.symbol - 1 ? v.size() : sizeof d.symbol - 1;
                std::memcpy(d.symbol, v.data(), n);
                d.symbol[n] = 0;
            }
        } else {   // a number: from after ':' to the next ',' or '}'
            const std::size_t a = ix[k - 1] + 1, b = ix[k];
            std::uint64_t v = 0;
            std::size_t used = 0;
            if (!simdscan::parse_uint(s + a, b - a, v, used) || used != b - a) return false;
            if (key == "E") d.event_time = v;
            else if (key == "U") d.first = v;
            else if (key == "u") d.last = v;
        }
        const char c = s[ix[k++]];
        if (c == '}') return k == m;
        if (c != ',') return false;
    }
    return false;
}
```

***Listing 17.2.** The second stage: walking the structural index of a depth update. code/firm/wsclient/cpp/firm_wsclient.hpp*

On the fixture’s 1 000 depth updates (about 286 bytes each, up to ten levels a side), the indexed decoder takes about $0.39\,\text{µ}\mathrm{s}$ per update; a general parser that builds a tree of maps, vectors and strings and converts prices with `strtod` about $2.6\,\text{µ}\mathrm{s}$, seven times more; Python’s `json` module and the conversion to integers about $10\,\text{µ}\mathrm{s}$. The decoded levels, divided by the instrument’s tick and lot, are what Book 3’s `firm.wsbook` applies.

## 17.5 Signing requests off the hot path

**Definition 17.5 (Request signature).**

A *request signature* is a message authentication code computed by the client over a request’s parameters with a secret shared with the venue (typically HMAC-SHA-256 of the query string and body, sent in hexadecimal with the API key), which lets the venue check that the request comes from the key’s owner and was not altered; a timestamp and a validity window inside the signed payload stop old requests from being replayed.

HMAC (RFC 2104) computes $H\big((K \oplus \mathit{opad}) \,\|\, H((K \oplus \mathit{ipad}) \,\|\, m)\big)$. Both padded keys are one 64-byte block, so the hash’s state after absorbing each of them depends only on the key; the RFC notes that these intermediate results “can be precomputed only once at the time of generation of the key”. A signer that does so absorbs the key once, copies the two states for each request and hashes only the message. The build’s signer does this in C++, Rust and Python, and is checked against RFC 4231’s vectors and against the worked example of a large venue’s documentation, whose published key and order payload give a published signature.

```cpp
class HmacSha256 {
public:
    explicit HmacSha256(std::string_view key) {
        std::uint8_t k[64] = {}, pad[64];
        if (key.size() > 64) {
            Sha256 h;
            h.update(reinterpret_cast<const std::uint8_t*>(key.data()), key.size());
            h.final(k);
        } else {
            std::memcpy(k, key.data(), key.size());
        }
        for (int i = 0; i < 64; ++i) pad[i] = k[i] ^ 0x36;
        inner_.update(pad, 64);
        for (int i = 0; i < 64; ++i) pad[i] = k[i] ^ 0x5c;
        outer_.update(pad, 64);
    }
    void sign(std::string_view msg, std::uint8_t out[32]) const {
        Sha256 in = inner_, o = outer_;
        in.update(reinterpret_cast<const std::uint8_t*>(msg.data()), msg.size());
        std::uint8_t ih[32];
        in.final(ih);
        o.update(ih, 32);
        o.final(out);
```

***Listing 17.3.** The key’s pads hashed once, at construction; each signature copies two states. code/firm/wsclient/cpp/firm_wsclient.hpp*

The costs teach two lessons. Precomputing the pads saves about two-fifths with the build’s portable SHA-256 (about $0.64\,\text{µ}\mathrm{s}$ against $1.1\,\text{µ}\mathrm{s}$ for a 119-byte order), and four-fifths with OpenSSL (about $0.19\,\text{µ}\mathrm{s}$ against $0.92\,\text{µ}\mathrm{s}$ for its one-shot `HMAC()`). And the library is several times faster than a portable implementation because it uses the processor’s SHA instructions: cryptography is the one place where the fast path is someone else’s code, called well.

**As of September 2026 — A venue’s signing and streams.**

Binance’s spot API documentation (GitHub, consulted September 2026) signs each request with HMAC-SHA-256 of the query string and body, keyed by the API key’s secret, and bounds its validity with a timestamp and a window, `recvWindow`, of 5 000 milliseconds by default and 60 000 at most. Its [WebSocket](#def-ll-websocket-rest-tls-and-json-at-speed-ws) streams send a ping every 20 seconds, drop a connection that has not answered within a minute, and end any connection after 24 hours; its diff-depth stream publishes updates every 100 or 1 000 milliseconds.

[Figure 17.2](#fig-ll-websocket-rest-tls-and-json-at-speed-budget) puts the pieces together: the processor time of receiving one depth update and sending one order, step by step, for the build’s C++ components and for its Python reference.

![The processor time of one websocket order: receiving a depth update (TLS record, frame, JSON) and sending an order (signature, masked frame, TLS record). C++: the build’s decoders and OpenSSL; Python: the build’s reference and ssl (its record cost estimated as a quarter of the extra round-trip time over TLS). Measured on a laptop (Intel Core Ultra 7 155H) under WSL2, no isolated cores. Data: bench_web.py.](https://one-course.com/images/onecourse/chapters/quant-13/ll-websocket-rest-tls-and-json-at-speed/fig-bfd4bde3343c.svg)

***Figure 17.2.** The processor time of one websocket order: receiving a depth update (TLS record, frame, JSON) and sending an order (signature, masked frame, TLS record). C++: the build’s decoders and OpenSSL; Python: the build’s reference and `ssl` (its record cost estimated as a quarter of the extra round-trip time over TLS). Measured on a laptop (Intel Core Ultra 7 155H) under WSL2, no isolated cores. Data: `bench_web.py`.*

## 17.6 Tutorial: the web stack, measured

**Goal.** Build and check each layer, then measure the cost of one websocket order. **End state:** [Table 17.1](#tab-ll-websocket-rest-tls-and-json-at-speed-tls), [Figure 17.2](#fig-ll-websocket-rest-tls-and-json-at-speed-budget), and green tests in Python, C++ and Rust.

1. **Frames.** The tests encode RFC 6455’s own examples (an unmasked and a masked “Hello”, lengths needing 16 and 64 bits) and decode the fixture stream written by `make_ws_fixtures.py` : fifty depth updates, some fragmented in three with a ping between the fragments, and a close. Python, C++ and Rust deliver the same messages in the same order.
2. **Depth updates.** The C++ indexed decoder reproduces, level for level, the Python reference’s decoding of the fixture’s 1 000 updates.
3. **Signatures.** All three signers reproduce RFC 4231’s test cases and the venue’s published example.
4. **TLS.** `bench_web.py` makes a self-signed certificate with the `openssl` command, runs a TLS 1.3 server in a thread, and times full and resumed handshakes (it checks that every resumption was accepted) and round trips.

**What to change next.** Unmask with 32-byte vector XORs (chapter 14) and measure a 16-kilobyte frame; replace the Python reference’s masking with a bytes-level XOR over integers and measure again.

## 17.7 Build: the web client

**Purpose.** The firm’s connection to venues that speak JSON over [WebSocket](#def-ll-websocket-rest-tls-and-json-at-speed-ws) and REST: depth updates decoded for Book 3’s `firm.wsbook`, orders signed and sent through a pool of [persistent connections](#def-ll-websocket-rest-tls-and-json-at-speed-rest) that respects the venue’s rate limits.

**Interface.** Python reference `firm_wsclient`: `encode_frame`, `decode_frame`, `Reassembler`, `decode_depth`, `to_book_levels`, `Signer`, `Pool`. C++20 `firm::ws`: `encode_frame`, `decode_frame` (unmasking in place), `Reassembler`, `decode_depth` into a fixed `Depth`, `Sha256`, `HmacSha256`. Rust `firm_wsclient`: `encode_frame`, `decode_frame`, `Sha256`, `HmacSha256`.

**Rules.** Client frames are always masked; control frames are never fragmented and carry at most 125 bytes; lengths are minimally encoded; prices and quantities are integers of $10^{-8}$, never floats; the key’s pads are hashed once; the pool never opens more than its size and asks the governor before every request.

**Acceptance tests.** `code/firm/wsclient/`: RFC 6455’s frame examples; the fixture stream reassembled identically in the three languages; the 1 000 fixture updates decoded identically in Python and C++; RFC 4231 test cases 1, 2 and 6 and the venue’s example signature in the three languages; the pool’s reuse, capacity and throttling.

**Stretch.** Vectorised unmasking; a general structural-index JSON reader; per-message deflate; the order path through the simulator’s crypto preset of One Quant Book 10.

Sources and further reading

- IETF RFC 6455 (WebSocket), RFC 8446 (TLS 1.3), RFC 2104 (HMAC), RFC 4231 (HMAC-SHA-256 test vectors), RFC 9112 (HTTP/1.1).
- G. Langdale and D. Lemire, “Parsing gigabytes of JSON per second”, *The VLDB Journal* 28(6), 2019.
- Binance spot API documentation (GitHub): REST request security, WebSocket streams.

## 17.8 Exercises

**Exercise 17.1 ★.**

How many bytes does a client frame carrying 300 bytes of payload occupy? And 70 000 bytes? And a server frame of 100 bytes?

**Solution of Exercise 17.1.**

$2 + 2 + 4 + 300 = 308$ bytes (header, 16-bit length, masking key, payload); $2 + 8 + 4 + 70\,000 = 70\,014$ bytes; a server frame is not masked, so $2 + 100 = 102$ bytes.

**Exercise 17.2 ★.**

A REST order on a new connection pays TCP and TLS setup. With a round trip of $0.5\,\mathrm{m}\mathrm{s}$ to the venue and the measured handshake, how much later does it arrive than on a [persistent connection](#def-ll-websocket-rest-tls-and-json-at-speed-rest)?

**Solution of Exercise 17.2.**

Two round trips (TCP, then TLS) and the handshake’s computation: $2 \times 0.5 + 2.06 \approx 3.1\,\mathrm{m}\mathrm{s}$ with a full handshake, about $2.6\,\mathrm{m}\mathrm{s}$ with a resumed one. On the measured laptop the handshake’s computation, which both ends share, costs more than the round trips.

**Exercise 17.3 ★.**

Why must a client never send an order in TLS 1.3’s zero-round-trip data?

**Solution of Exercise 17.3.**

Zero-round-trip data can be replayed: RFC 8446 buys the saved round trip “at the cost of certain security properties”. An order replayed by an attacker or a retransmitting middlebox would be executed twice; orders must travel only after the handshake completes.

**Exercise 17.4 ★★.**

Why can the two padded keys of HMAC be hashed in advance, and how many SHA-256 compressions does signing a 119-byte payload take with and without that precomputation?

**Solution of Exercise 17.4.**

Each padded key is exactly one 64-byte block that depends only on the key, so the hash state after it is the same for every message. Without precomputation: the inner hash takes the ipad block and the 119-byte message padded to two blocks (three compressions), the outer the opad block and the 32-byte inner digest padded to one (two): five. With it: three.

**Exercise 17.5 ★★.**

A depth update’s price is the string `64123.2`. What does the indexed decoder return at $10^{-8}$, and what can go wrong if a decoder converts it with `strtod` and multiplies by $10^8$?

**Solution of Exercise 17.5.**

6 412 320 000 000 exactly. In binary floating point 64123.2 is not representable; multiplied by $10^8$ it lands a hair below or above the integer, so truncation can give 6 412 319 999 999, and prices that differ in the last digit can compare equal or unequal by accident. Parsing the decimal text into an integer avoids the question.

**Exercise 17.6 ★★.**

A ping arrives between the first and second fragments of a depth update. What must the client do, and in what order?

**Solution of Exercise 17.6.**

Answer the ping at once with a pong carrying the same payload, keep the first fragment, and deliver the depth update only when its final fragment arrives; control frames may interleave with fragments but never split them.

**Exercise 17.7 ★★★.**

*Coding.* Replace the Python reference’s byte-by-byte masking with one XOR of two large integers built with `int.from_bytes`, check the fixture tests, and measure the frame costs again.

**Solution of Exercise 17.7.**

Repeat the key to the payload’s length and turn both byte strings into integers (`int.from_bytes`); XOR the two integers and convert the result back to bytes (`to_bytes`). The fixture tests are unchanged; the cost per 300-byte frame falls from tens of microseconds to a few.

**Exercise 17.8 ★★★.**

*Find the flaw.* “Each strategy thread opens its own HTTPS connection for each order, so that no two threads ever contend for a connection.”

**Solution of Exercise 17.8.**

Every order pays two round trips and a handshake, about $3.1\,\mathrm{m}\mathrm{s}$ here, the venue sees a new connection per order (and may limit or ban them), and the rate-limit governor has no single place to count requests. A pool of [persistent connections](#def-ll-websocket-rest-tls-and-json-at-speed-rest) shared behind the governor avoids all three; contention for a connection costs nanoseconds, a handshake milliseconds.

## 17.9 Problem: Cloud to Venue

**Problem 17.1.**

Weekend problem — where the time of a websocket order goes

A strategy runs in the same cloud region as a crypto venue. Assume a one-way network hop of $250\,\text{µ}\mathrm{s}$ and a decision of negligible cost; take the measured costs of [Figure 17.2](#fig-ll-websocket-rest-tls-and-json-at-speed-budget) and [Table 17.1](#tab-ll-websocket-rest-tls-and-json-at-speed-tls) (`measured_budget.csv`, `measured_tls.csv`).

**Part I — The budget.**

1. What is the total processor time of one order with the C++ components, and with the Python reference?
2. Which step dominates each?
3. What share of the path venue, host, venue is the host’s processing, in each case?
4. What would the Python share be with the C++ framing alone (the rest unchanged)?

**Part II — Connections.**

5. What does opening a new connection for each REST order add, with a full handshake, and with a resumed one?
6. What does a new [WebSocket](#def-ll-websocket-rest-tls-and-json-at-speed-ws) add before its first message?
7. How many orders a second could one connection carry if each took a new connection?
8. What does the venue’s 24-hour connection limit imply for a strategy that runs for days?

**Part III — Signatures and data.**

9. Why can the signature not be computed before the decision, and what can be?
10. At 100 depth updates a second per instrument over 50 instruments, how much of a core does decoding take with each decoder?
11. What does the update identifier pair `(U, u)` let the client detect, and what does it do then?
12. Why is the timestamp inside the signed payload?

**Part IV — The verdict.**

13. State the *named result* : the share of the tick-to-trade spent in decryption, framing, JSON and signing against the network hop, and what connection reuse saves per order.
14. When does the Python reference suffice?
15. Where would you put the TLS work if the budget were tighter still?
16. What would you measure on the real server before trusting these laptop numbers?
17. Why does the network hop not appear in the processor budget, and why does it still matter?
18. What part of this stack does a venue’s binary protocol remove?
19. How would you test the pool’s behaviour when the venue answers 429?
20. In one sentence: what is fast about a web stack that is fast?

**Solution of Problem 17.1.**

1. On the committed measurement, about $0.9\,\text{µ}\mathrm{s}$ with the C++ components and $48\,\text{µ}\mathrm{s}$ with the Python reference.
2. C++: the JSON decoding ( $0.39\,\text{µ}\mathrm{s}$ ). Python: the byte-by-byte framing, $16\,\text{µ}\mathrm{s}$ to unmask and $15\,\text{µ}\mathrm{s}$ to mask.
3. $0.93/(0.93 + 500) \approx 0.2\%$ and $47.6/(47.6 + 500) \approx 8.7\%$ .
4. About $16.9\,\text{µ}\mathrm{s}$ , $3.3\%$ of the path: framing was two-thirds of the Python cost.
5. $2 \times 0.5\,\mathrm{m}\mathrm{s}$ of round trips plus the handshake: about $3.1\,\mathrm{m}\mathrm{s}$ (full) or $2.6\,\mathrm{m}\mathrm{s}$ (resumed) per order.
6. Three round trips and the handshake, about $3.6\,\mathrm{m}\mathrm{s}$ .
7. Opened one after the other, about 330 a second; on a [persistent connection](#def-ll-websocket-rest-tls-and-json-at-speed-rest) the limit is the venue’s rate limit.
8. Plan a reconnection before the limit: open the new connection, subscribe and resynchronise the book, then close the old one, so that no update is missed.
9. It covers the order’s price and quantity, known only after the decision. The keyed hash state, the fixed parts of the payload and the formatting of the timestamp can be prepared in advance.
10. 5 000 updates a second: about 0.2% of a core with the indexed decoder and 4.9% with the Python reference.
11. Missed updates: a first identifier above the last applied one plus one. The book is discarded and rebuilt from a new snapshot (Book 3’s `firm.wsbook` ).
12. So that a captured request cannot be replayed later: the venue refuses one older than its window.
13. **Named result.** With a $250\,\text{µ}\mathrm{s}$ hop each way, decryption, framing, JSON and signing take 0.2% of the venue–host–venue path with the C++ components and 8.7% with the Python reference; reusing a connection saves about $3.1\,\mathrm{m}\mathrm{s}$ per REST order against opening one ( $2.6\,\mathrm{m}\mathrm{s}$ with resumption), several times the network path itself.
14. When the strategy’s decisions are worth milliseconds, not microseconds, and messages are few: the network hop and the venue’s own latency dominate anyway.
15. In a separate thread or process that terminates TLS for several connections, or in the operating system’s or the network card’s TLS support where available, so that the strategy’s thread sees plain frames.
16. The same costs on the server’s processor (its AES and SHA instructions), under the real message rates and sizes, and the distribution of the network hop, not just its median.
17. It is not processor time on this host, but it is most of the tick-to-trade and its variance; a closer placement changes the result more than any decoder.
18. JSON and decimal text, and the masking; the parsing becomes fixed-offset reads (chapter 16).
19. Feed the pool a scripted sequence of venue answers with 429 and a retry time, as Book 3’s governor accepts through `on_status` , and check that it stops sending until the time has passed.
20. It keeps connections open, decodes without building anything and signs without rehashing the key.

## 17.10 Interview questions

**Interview question 17.1 ★ developer.**

Why reuse HTTP connections for order entry? What does a new connection cost?

**Solution of Interview question 17.1.**

A new connection costs a TCP round trip, a TLS round trip and the handshake’s cryptography (milliseconds), and may count against the venue’s connection limits; a [persistent connection](#def-ll-websocket-rest-tls-and-json-at-speed-rest) pays this once. Use a pool behind the rate-limit governor.

*What the interviewer is looking for: round trips plus handshake computation, and limits.*

**Interview question 17.2 ★★ developer.**

Describe a [WebSocket frame](#def-ll-websocket-rest-tls-and-json-at-speed-ws). Why are client frames masked, and what does masking cost?

**Solution of Interview question 17.2.**

Two header bytes (final bit, opcode, mask bit, 7-bit length), an extended 16- or 64-bit length, a 4-byte masking key on client frames, the payload. Masking protects intercepting proxies from crafted payloads; it costs an XOR per byte, nanoseconds when done eight or thirty-two bytes at a time.

*What the interviewer is looking for: layout, the reason, and the cost done well.*

**Interview question 17.3 ★★ developer.**

How would you parse a venue’s JSON depth updates as fast as possible?

**Solution of Interview question 17.3.**

Find all structural characters in one vectorised pass, then walk that index with the message’s expected shape, reading numbers in place and parsing prices into integers; fall back to a general parser if the shape differs. No tree, no allocation, no floating point.

*What the interviewer is looking for: a structural index and a shape-specific walk.*

**Interview question 17.4 ★★ developer.**

How is an HMAC request signature computed, and how would you make signing cheap?

**Solution of Interview question 17.4.**

HMAC-SHA-256 of the query string and body with the secret key, sent in hex with the API key, a timestamp and a validity window in the payload. Hash the key’s two padded blocks once and copy those states per request; use a library SHA-256 that uses the processor’s instructions; prepare the fixed parts of the payload.

*What the interviewer is looking for: the RFC 2104 precomputation, and replay protection.*

**Interview question 17.5 ★★ developer.**

What does TLS 1.3 [session resumption](#def-ll-websocket-rest-tls-and-json-at-speed-tls) save, and what does zero-round-trip data risk?

**Solution of Interview question 17.5.**

Resumption skips the certificate and part of the key exchange by using a key from an earlier connection: less computation, still one round trip. Zero-round-trip data is sent with the first message and can be replayed, so it must be idempotent, which an order is not.

*What the interviewer is looking for: computation saved versus round trips, and replay.*

**Interview question 17.6 ★★★ developer, researcher.**

Your crypto strategy’s orders arrive 3 ms after the market data that triggered them, although the venue is 0.3 ms away. Where do you look?

**Solution of Interview question 17.6.**

Connection setup on the order path (a new connection or a reconnection per order), queueing behind a rate limiter or a busy connection, garbage collection or slow decoding in the client, TLS or signing done inefficiently, and the venue’s own latency; measure each step with timestamps at every boundary.

*What the interviewer is looking for: measure the steps; handshakes first.*
