blackbull.fault_injection.scenario_h2_client¶
blackbull.fault_injection.scenario_h2_client
¶
Programmable HTTP/2 client-side scenario model.
A :class:ScenarioH2Client is a sequence of typed steps that
:meth:blackbull.client.http2.HTTP2Client.execute_scenario walks in order
against a live connection. This is the client-side half of the HTTP/2
toolkit: a programmable client that drives a target server through
deliberate misbehaviour — a preface that never arrives, a header block
opened and abandoned, a Rapid Reset burst, a window never opened.
Its twin is :mod:blackbull.fault_injection.scenario_h1, the client-side
vocabulary one protocol over, and this module takes that twin's names
wherever the two mean the same thing: :class:SendRawBytes,
:class:ReadResponse, :class:Sleep, :class:Abort, and the fields of
:class:ScenarioH2ClientResult. That matters more than it looks: a
vocabulary written from the protocol rather than from its twin drifts
away from it, and the drift is found by someone asking rather than by
reading the code.
Two steps have no HTTP/1.1 counterpart, and both earn it:
- :class:
SendPreface— HTTP/1.1 has no connection preface. - :class:
SendFrame— HTTP/2 is framed where HTTP/1.1 is a byte stream, so the typed step builds a frame rather than a blob.
As on the server side, the bytes are assembled here rather than by the production send path: a breaker that shares the production serialiser cannot emit a fault that serialiser has.
Abort
dataclass
¶
Hard-close the connection (transport.abort → RST on Linux).
Terminal: later steps short-circuit, exactly as on the HTTP/1.1 side.
ExpectServerFrame
dataclass
¶
Read one frame and record whether it matched.
A guard, not a filter: nothing is skipped and the executor moves
on either way. Twin of
:class:~blackbull.fault_injection.scenario_h2.ExpectClientFrame.
HalfClose
dataclass
¶
Shut down the sending direction only (FIN), keep reading.
Neither :class:Abort nor a full close says this. Abort sends RST,
which discards whatever is buffered and leaves nothing to read; a full
close ends both directions at once. A half-close is the ordinary end of
a non-keep-alive exchange — "I have finished sending, I am still waiting
for your answer" — and it is a distinct code path on the peer.
Not terminal: later steps still run, because continuing to read is the whole point.
ReadResponse
dataclass
¶
Read one frame from the server, or record a timeout.
ScenarioH2Client
dataclass
¶
An ordered sequence of steps, plus a name for parametrisation.
ScenarioH2ClientResult
dataclass
¶
Outcome of one :meth:HTTP2Client.execute_scenario call.
Field names come from
:class:~blackbull.fault_injection.scenario_h1.ScenarioResult, its
twin, so a harness reporting on one does not need a second spelling for
the other.
SendFrame
dataclass
¶
Emit one frame, built here rather than by the production sender.
payload is the frame payload; the 9-byte header is assembled from
the other fields. Deliberately low-level: a fault scenario wants to
set a length that disagrees with the payload, or a flag combination the
typed frame classes refuse, and a step that went through
FrameFactory could not.
declared_length overrides the header's length field without
changing the bytes actually written — the direct way to express "the
peer lied about how much is coming".
frame_type is the raw type byte: a :class:~blackbull.protocol.frame_types.FrameTypes
member (which is a one-byte bytes), or an int for a type the
enum does not name — an unregistered type being itself a fault worth
staging.
SendHeaders
dataclass
¶
Emit a HEADERS frame, with the header block built for you.
Added by the 107+108 consistency sweep, which found that every header-field fault — a malformed HPACK block, a missing or duplicated pseudo-header, a connection-specific header HTTP/2 forbids — could only be written as hand-assembled hex. Three of the sweep's rows moved from raw-bytes-only to typed with this step.
pseudo and headers are encoded with HPACK in the order given, so
a scenario can put :path after a regular field (RFC 9113 §8.3
forbids it) simply by saying so. Nothing here validates: the whole
point is to send what a conforming client would not.
raw_block replaces the encoded block outright, for faults HPACK
itself cannot produce — a truncated block, an invalid table index, a
Huffman string that does not decode. When set, pseudo and
headers are ignored.
SendPreface
dataclass
¶
Write the client connection preface (RFC 9113 §3.4).
A step rather than a scenario flag, unlike ScenarioH2.send_preface
on the server side: a client scenario's whole point may be to delay
the preface, split it, or never send it, and a boolean cannot say
"after 30 seconds".
SendRawBytes
dataclass
¶
Push arbitrary bytes at the server.
The escape hatch, and the same name and fields the HTTP/1.1 client-side
vocabulary uses. byte_interval > 0 transmits one byte at a time
with that delay.
Sleep
dataclass
¶
Idle for duration seconds without reading or writing.
StepOpH2Client
¶
Bases: str, Enum
Tag used by the JSON serialiser.
WaitForServerFrame
dataclass
¶
Read frames until one satisfies match, or the timeout wins.
A filter: non-matching frames are read, counted in
wait_skipped, and passed over. Twin of
:class:~blackbull.fault_injection.scenario_h2.WaitForClientFrame.
This is what makes an HTTP/2 client scenario able to observe a
verdict. A single ReadResponse cannot: the first frame any
correct server sends is its handshake SETTINGS, so a GOAWAY or
RST_STREAM is always further down the stream, at a depth that varies
by peer. A scenario that had to guess that depth was a scenario
written against one server.
__getattr__(name)
¶
PEP 562 — warn when the deprecated spelling is actually used.
A module-level assignment would alias silently; going through
__getattr__ means a reader who never touches SendRawBytes never
sees a warning, and one who does gets it at their own call site.
encode_frame(step)
¶
Assemble one frame's wire bytes from step.
Here rather than in FrameBase.save() on purpose — the same rule the
fault servers follow. It is also what makes declared_length
possible: a header whose length disagrees with the payload is exactly
the fault, and a serialiser that computed the length could not say it.
encode_headers(step)
¶
Assemble one HEADERS frame from step.
HPACK encoding goes through the hpack package the server also uses,
because a correct block is the baseline every header fault is a
deviation from — hand-rolling it would make even the well-formed case a
guess. raw_block is the escape hatch for blocks HPACK will not
produce.
scenario_from_json(src)
¶
Parse JSON Lines back to a :class:ScenarioH2Client.
scenario_to_json(scenario)
¶
Serialise to JSON Lines, name on the first HEADER line.