| Commit message (Collapse) | Author | Age | Files | Lines |
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
A message from another node was taken only in its input's exact shape
(the link's STREAM shape, fields and order alike; over HTTP, every
field). So a board whose nodes are upgraded one after the other stopped
both ways until both were, and an output and its input whose fields
differ only in order were refused for good (docs/design/014).
An input now takes a message when it has every field the sender sends,
by name, of the same type or one that holds its values (a u32 in a
number, text in bytes); a field only the input has is 0 or empty. One
that sends a field the input lacks is refused: taking it would drop the
value, and with its number heard the value would never come again (two
boards apart for good). So add fields to receivers first and take them
from senders first, and nothing stops or is lost.
The link carries a sender's fields in a new frame, FIELDS (0x0d: names,
shape, u8 count, each {type, name length, name}): after a REFUSED for
another shape the sender puts it in its packets (after OFFER, before
the records, so a receiver that knows no FIELDS reads what it read
before) until the stream is taken, the first time at once. A receiver
seeing another shape waits for the packet's FIELDS before refusing, and
asks its host with them (PureLinkHost.input takes them); REFUSED says 4
when the input lacks a field. Deliveries carry the fields and the host
lays each record out anew (pure_module_takes, pure_module_recast,
pure_module_fields, over pure_send_recast's rule). A stream whose sender
starts again in another shape lets go what it held; the sender sends it
again laid out anew. A refusal already queued for a stream now says the
last reason judged, not the first. Over HTTP a delivery (X-Pure-Source)
may lack fields; an unknown member is still refused.
test-link named: a stream of another shape naming its fields is taken,
200 stored in 47 ms with every delivery naming them; an input that does
not take them refuses (4) and the sender backs off. check.py
server_fields: five outputs, three linked and two over HTTP, fields in
another order taken, an input with a field more taking messages without
it, one lacking a field refusing until it starts again with it, then
taking all. server_upgrade's new receiver declares its fields in another
order. 012 names the frame and the rules.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
104 measures a node's memory under a receiver that is down and under a
snapshot held for long (021's run.py: resident memory against what was
posted, 30 s a case). A held snapshot grew the node without end (6.4 to
678 MiB, fixed in d1e8f38); a slow receiver grows it by what was sent,
221 B a send, which is state until delivered, so the budget bounds state
and refuses past it.
013 is that budget's design: what it counts (state bytes, deterministic),
what an event over it does, why an answer takes its send's room, how
links and HTTP wait, what it does not count and those bounds, and the
checks. With --memory 4000000 the same load fills it in about 4 s and
the node stays at 9.6-9.8 MiB (45.9 MiB and growing without).
012 records the hole the budget found in the link (the full flag), and
103 that its clock fix fell short (385754c). CHECKPOINT gains the row.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
A link's delivery the app had no room for was handed over again at the
next turn: a receiver over its memory budget would refuse the same
records as fast as the owner could apply them. And its sender, seeing
records received but not stored for a second, sent the whole window
again every second.
Now a host says a delivery found it full (PureLinkDone.full). The
receiver keeps the records and hands them over again after a pause, 100
ms doubling to 500 ms, and meanwhile its STORED frames carry flag 2: the
app is full and what came is kept. A sender hearing it starts its
one-second wait for the save again, and sends none of it again. The
spec (012) says both sides; the pause is a local choice that must stay
under the sender's wait.
pure_link_done takes the PureLinkDone itself. test-link's new case keeps
the app full for 5 s: 12 hand-overs meanwhile, taken within 0.5 s of
having room, and no record sent again (21,640 without the flag).
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
102: the spec (012) and a receiver written from it alone; the four holes
writing it found (REFUSED, the record's size, a peer's run places, the
memo's places beside) and the credit M6 showed fixed at 1,024 records;
M6 with ngtcp2 as a third, two rounds. Under reordering Pure carries the
most (45 Mbit/s; ngtcp2 29-47, quinn 1-2), under random loss the three
are the same, on a clean bottleneck Pure carries 3-5 % less (its framing,
some 112 B a packet to QUIC's 41), and where loss and reordering meet
ngtcp2 carries 1.3-3.6 times as much: undoing a loss it took for
reordering, it restores the window a real loss had cut too (its log: 22
of 34 undos past the window before the last cut; none without
reordering). Shares beside TCP stay within 25 %.
011 and the status table say so; 012 also says that a receiver answers
within 25 ms (a sender's PTO counts on it), that a shape and a record's
bytes are the app's, and why a packet of the past does no harm.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
A receiver gave every stream a credit of 1,024 records past its last
stored, however small the records: a link carried at most 1,024 records
a round trip. On a path of 100 ms that is 10,000 records a second of
61 B, a ninth of what 50 Mbit/s carries (the path simulator: 9,953 a
second, 90,814 at 10 ms); and records of 1 KiB, a queue's worth of them
in flight, went a record or two a packet as each STORED let a few more
through (M6: 107,968 packets where 20 ms took 84,106).
A receiver bounds what it holds of a stream (16 MiB) apart, so its
credit is now what fits there at the size its last delivery's records
had: 1 to 65,536 (what a sender takes), 1,024 before any delivery. It
takes records that far past its last stored, as its memory lets it.
Simulator at 100 ms: 56,296 records a second of 61 B. M6 at 100 ms:
45.4 Mbit/s of 1 KiB records in 83,566 packets (44.9 in 107,968). The
window, not the credit, now fills the bottleneck's queue: losses there
where the credit hid them (61 B at 10 ms: 87,130 a second, 1,371 lost).
012 says so.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
A receiver's memo kept one run's epoch a peer. A node whose outputs
send to this one sends in a run each, so after the receiver started
again (or let the peer rest) all runs but the last accepted were told
LOST and began their epochs again: records under the static keys for a
round trip, and an X25519 each. Now a run whose peer's place holds
another run still sending keeps its epoch beside, in one of two of
1,024 places its peer and run find (160 KiB at the file's head: the
memo is PLM3, and a PLM2 one is begun again). A run's place beside goes
once its peer's place has it, so an epoch's secrets stay only until the
next one's are.
test-link's three outputs of one node, each in epoch 1, go on after the
receiver starts again: no LOST, no X25519 (before: two LOSTs, an
accept). 012 says so.
|
|
|
012 says what goes on the wire, enough for another implementation: the
header, the key id, the version and why it is not negotiated on the
wire, the static secret, HKDF's salt and infos, the nonce, epochs'
chain, the thirteen frames byte by byte, what each side must do, the
values that are the spec's and those each side picks, the two words a
receiver says (LOST, REFUSED), what an attacker gets, and what is known
not to hold. A receiver written from it alone, in Python, links with a
pure-serve node through epoch 1 (check.py).
011's last row of its stop table is met: H1-H4 held, every check passes,
CPU within 10 % of QUIC at 61 B and 25 % under it from 1 KiB, the share
with TCP within 25 %, 3,097 lines of C. Links without connections are
now the default between Pure nodes; HTTP stays the boundary to others,
and relayed sealed sends stay as they are. 006, 009 and the README say
so.
|