Skip to content

blackbull.fault_injection.scenario_h2

blackbull.fault_injection.scenario_h2

Programmable HTTP/2 wire-level scenario model.

A :class:ScenarioH2 is a sequence of typed steps that the :class:blackbull.fault_injection.h2_server.H2FaultServer executor walks in order against a connected HTTP/2 client. This is the server-side half of the :mod:blackbull.fault_injection toolkit: a programmable server that emits deliberate misbehaviour toward a client — half-closed streams, exhausted flow-control windows, illegal SETTINGS, weird frame sequences — expressed as data, not procedural test code.

The symmetric client-side half (programmable HTTP/1.1 client driving deliberate misbehaviour toward a server) lives in :mod:blackbull.fault_injection.scenario_h1.

Use cases:

  • HTTP/2 client-library authors testing their client's resilience against a misbehaving server.
  • Proxy / load-balancer authors testing what their transit code does when an upstream emits illegal frame sequences.
  • Security researchers reproducing CVE-class patterns (CONTINUATION-flood, RST-flood) from a deterministic harness.

Steps

  • :class:SendFrame — emit one parsed :class:FrameBase instance. The executor handles serialisation through the existing :class:~blackbull.protocol.frame.FrameFactory.
  • :class:SendRawBytes — escape hatch for bytes the framework's FrameFactory cannot construct (e.g. illegal frame types, oversized frames, malformed length fields).
  • :class:WaitForClientFrame — pause until an inbound frame from the client matches the declarative match dict. Fields supported:

=============== ========================================= type 'HEADERS', 'SETTINGS', etc. stream_id Integer; None = any. flags_set List of flag names (uppercase) that must be set. flags_unset List of flag names that must be unset. =============== =========================================

  • :class:Sleep — idle without sending or reading.
  • :class:Abort — hard-close the underlying transport (RST on Linux).
  • :class:CloseGracefully — send a GOAWAY frame, then close cleanly.

Serialisation

:meth:ScenarioH2.to_json / :meth:ScenarioH2.from_json round-trip through JSON Lines. Because :class:SendFrame carries a frame object, round-tripping requires the frame to be reconstructable from its serialised form — currently SETTINGS, WINDOW_UPDATE, RST_STREAM, GOAWAY, PING, and a typed-payload form of HEADERS / DATA. For ad-hoc frames the caller passes through :class:SendRawBytes, which is always round-trippable.

Abort dataclass

Hard-close the connection (transport.abort → RST on Linux).

CloseGracefully dataclass

Send a GOAWAY then close cleanly.

Subsequent scenario steps short-circuit (this is a terminator just like :class:Abort). error_code is one of the :class:~blackbull.protocol.frame_types.ErrorCodes values; last_stream_id advertises the last stream the server is willing to process — pass 0 to refuse all client streams, or the highest accepted stream ID otherwise.

ScenarioH2 dataclass

Sequence of steps a programmable H2 server walks per connection.

Two control knobs sit outside the step list because they apply to the whole connection, not to one step:

  • send_preface: whether the server sends the standard SERVER_PREFACE_BYTES + initial SETTINGS at handshake time. Most catalogue scenarios want this (real H2 clients require it before proceeding); set False to exercise client behaviour against a server that skips the handshake.
  • initial_settings: tuple of (setting_id, value) pairs the server advertises in its initial SETTINGS frame. Used by the "exhausted window" and "illegal SETTINGS" catalogue entries to inject a hostile starting state before the first step runs.

ScenarioH2Result dataclass

Outcome of one :class:ScenarioH2 run.

Mirrors :class:~blackbull.fault_injection.scenario_h1.ScenarioResult's shape so callers can write uniform pytest assertions across protocols.

SendFrame dataclass

Emit one parsed frame onto the connection.

Routed through :class:~blackbull.protocol.frame.FrameFactory so the on-wire serialisation matches the framework's normal output. Use :class:SendRawBytes for frames the factory cannot construct.

SendRawBytes dataclass

Push arbitrary bytes onto the connection.

Escape hatch for malformed frames (illegal type byte, length exceeding SETTINGS_MAX_FRAME_SIZE, etc.) that the typed :class:SendFrame path will not produce.

byte_interval > 0 transmits one byte at a time with that delay — useful for stalled-handshake patterns where the client is expected to enforce a preface-completion timeout.

Sleep dataclass

Idle for duration seconds without reading or writing.

StepOpH2

Bases: str, Enum

Tag used by the JSON serialiser.

WaitForClientFrame dataclass

Block until an inbound frame matches match.

Declarative grammar — see module docstring for the supported keys. Frames the client sends that do not match are still consumed (the executor remains responsive to the wire) but do not advance this step.

On timeout expiry the executor records the miss on :class:ScenarioH2Result and proceeds to the next step.

frame_matches(frame, match)

Return True iff frame satisfies every key in match.

Recognised keys: type, stream_id, flags_set, flags_unset. Unknown keys fail closed — an unrecognised match key is almost certainly a typo in a catalogue entry, and silently matching on a missing key would hide the bug.

scenario_from_json(src)

Parse JSON Lines back to a :class:ScenarioH2.

scenario_to_json(scenario)

Serialise scenario to JSON Lines (one step per line).

Header lines (send_preface flag, initial_settings) sit on the first line under the op HEADER so the file is one line-oriented stream with no out-of-band metadata.