The acknowledgment by the HC-sender is
up to and including
ack_ackno.
Both Ack Vector cells cover down to
E0, which is given as
E0 = ack_ackno - ack_runlen = ptr_ackno - ptr_runlen. (NB: all addition/subtraction here is modulo-48).
We need to find the new lower end
E1
and the corresponding new run
length:
ptr_runlen' = ptr_ackno - E1
Since
E1 = ack_ackno + 1,
we have
ptr_runlen' = ptr_ackno -
ack_ackno - 1, i.e.
ptr_runlen' = (E0 + ptr_runlen) - (E0 + ack_runlen) - 1
= ptr_runlen - (ack_runlen + 1),
which gives the new run length to be entered in the buffer cell at
ack_ptr. Since (
ptr_runlen' + 1) packets are
still unacknowledged by the sender, we advance
buf_tail to the cell preceding
ack_ptr (thus clearing state
for all packets with sequence number older than E0).
3. Handling overflow
The circular buffer is statically
allocated to match
reasonable operational conditions, so that overflow appears as
exception case. The following
section discusses the chosen recovery strategy
for overflow.
3.1 Existing alternatives
In RFC
4340, section A.1.1 the following
alternatives
are suggested:
- compress the buffer (by
increasing run lengths when
possible),
- drop all received
packets, without processing them at all, until the buffer shrinks again.
The existing Linux implementation uses the second alternative
(drop-on-overflow).
3.2 Discussion and choice of other alternative
There is a
third alternative
- treating overflow as special case. In this case the
head/tail pointers move in
lock-step, as described
here.
The difference between the second and the third scheme is:
- drop-on-overflow
means
that the connection will suddenly `hang', since no new packets are
admitted;
- continue-on-overflow
means that the connection can continue, but old Acks from the sender
need to be ignored.
From the user perspective,
continue-on-overflow
is less
disturbing: the HC-receiver continues to pass on data to the user and
continues to send Ack Vectors. This strategy is useful when for
instance an outstanding Ack from the HC-sender is just delayed or
re-ordered. The
drop-on-overflow
strategy, in contrast, applies the ostrich principle and will disrupt
user data flow.
Both have in common that ultimately packets will be
considered lost by
the HC-sender:
- In the case of drop-on-overflow,
no new Ack Vectors will be generated until an Ack for the older data
(which may long have been lost) arrives; in the worst case this
leads
to complete stand-still.
- Continue-on-overflow,
on the other hand, will in the worst case
let the sender consider some old packets as lost, while newer packets
continue to be acknowledged. In the best
case, continue-on-overflow
`gets away
with' a transient overflow where all data from the sender is
acknowledged.
Due to the above stated advantages,
continue-on-overflow
has been
chosen as strategy for dealing with overflow.
The
first alternative (buffer
compression) is not considered, as it has
two main disadvantages.
First,
it is expensive to perform and brings no
guarantee that the compression will free enough space. The
second
disadvantage is that it complicates the process of matching Ack
Vector
records with buffer cells - when buffer compression is
used,
ack_ptr becomes
useless and positions need to be recomputed from scratch,
whereas otherwise the value of
ack_ptr
can be used directly.
3.3 Short description of overflow handling
The receiver tracks the overflow condition by setting an
overflow bit.
As long as overflow prevails, the
head
and
tail pointers move in
lock-step, thereby erasing older data. To avoid ambiguities
(caused by
older acknowledgments from the sender), the receiver
only keeps the most
recent Ack Vector record in its list. If this is acknowledged,
the
entire buffer is acked and so
reverts to the empty state. Otherwise,
the
overflow condition continues - visible via repeated warnings in the
system log.
4. Computing ECN nonces
When using a buffer larger than the room consumed by a single Ack
Vector (253 bytes), keeping track of the ECN nonce is a problem, since
the Ack Vector nonce is
relative
to the length of the Ack Vector,
but not relative to the length of the buffer.
The
solution is
straightforward: the buffer is allocated in
N units of 253 bytes and there
is an array
buf_nonce[N]
of
nonce sums, where
buf_nonce[i]
is the nonce sum of buffer cells (i-1) * 253 up to i * 253, for i >
1. The
ack_nonce then
contains the one-bit parity sum of
all elements in
buf_nonce
that cover live parts of the buffer.