@@ -35,14 +35,16 @@ class IP(ReassemblyBase[Packet[_AT], Datagram[_AT], BufferID, Buffer[_AT]], Gene
3535
3636 Args:
3737 strict: if :data:`True`, report a datagram that is not completely
38- reassembled as the tuple of its received runs; otherwise as one
39- contiguous payload, its holes zero-filled -- or, while the total
40- length is unknown, only the prefix up to the first hole. Receipt
41- is tracked in 8-octet blocks, so the partial last block of a
42- snaplen-truncated fragment is not counted as received: it is left
43- out of the runs and ends the prefix, but once the total length is
44- known the contiguous payload keeps its real octets, with only the
45- rest of the hole zero-filled
38+ reassembled as the tuple of its received runs, empty if none was;
39+ otherwise as one contiguous payload, its holes zero-filled -- or,
40+ while the total length is unknown, only the prefix up to the first
41+ hole. Receipt is tracked in 8-octet blocks, and only the final
42+ fragment's partial last block counts as received -- a non-final
43+ fragment's, or a snaplen-truncated or buffer-clipped fragment's,
44+ does not: it is left out of the runs and ends the prefix, but once
45+ the total length is known the contiguous payload keeps its real
46+ octets, with only the rest of the hole zero-filled. A datagram
47+ longer than the data buffer is never complete
4648 store: if store reassembled datagram in memory, i.e.,
4749 :attr:`self._dtgram <pcapkit.foundation.reassembly.reassembly.Reassembly._dtgram>`
4850 (if not, datagram will be discarded after callback)
@@ -168,12 +170,12 @@ def reassembly(self, info: 'Packet[_AT]') -> 'None':
168170 # grows to hold such a datagram rather than clipping it, ``RCVBT`` in
169171 # step with it.
170172 #
171- # With nothing declared, a snaplen cut cannot be seen in the length.
172- # A fragment with **MF** set must carry a multiple of 8 octets, so its
173- # partial last block is a cut and is left unmarked below. Any other cut
174- # -- an unfragmented frame's, or a final fragment's -- cannot be told
175- # from a whole one, since the frame's original length is not part of
176- # the input, and the datagram comes out ``COMPLETE`` but short.
173+ # With nothing declared, a snaplen cut cannot be seen in the length. A
174+ # non-final fragment's cut still shows, as a partial last block, which
175+ # is left unmarked below. Any other cut -- an unfragmented frame's, or
176+ # a final fragment's -- cannot be told from a whole one, since the
177+ # frame's original length is not part of the input, and the datagram
178+ # comes out ``COMPLETE`` but short.
177179 if TL == 0 :
178180 length = len (info .payload )
179181 extent = FO + length
@@ -206,13 +208,23 @@ def reassembly(self, info: 'Packet[_AT]') -> 'None':
206208 buf .datagram [start :stop ] = payload
207209
208210 # Set RCVBT bits (in 8 octets) for the blocks this fragment actually
209- # filled. A fragment held in full marks its last block even when it is
210- # short, as the final fragment's is; a truncated one leaves its partial
211- # last block clear, since the octets after the cut are a real hole --
212- # as does a non-final fragment with Total Length 0, whose partial last
213- # block can only be a cut.
211+ # filled. Only the final fragment, held in full, marks a partial last
212+ # block: the octets after its end are past the datagram's. Every other
213+ # partial last block is left clear, as a hole --
214+ #
215+ # * a truncated fragment's, since the octets after the cut are missing.
216+ # That includes one clipped by the data buffer's end, whose octets
217+ # past it cannot be held, so it counts as cut there (:issue:`1566`).
218+ # * a non-final fragment's. :rfc:`791#section-3.2` and
219+ # :rfc:`8200#section-4.5` make every fragment but the last carry a
220+ # multiple of 8 octets, so one that ends mid-block was cut or is
221+ # malformed, and nobody sent the rest of its block. Marking it would
222+ # complete the datagram with zeros in their place. This is the only
223+ # way a cut shows when the length is not declared: under Total
224+ # Length 0, and on IPv6, whose adapters derive it from the captured
225+ # payload (:issue:`1567`).
214226 start = FO // 8
215- if held == length and not ( TL == 0 and MF ) :
227+ if held == length and not MF :
216228 stop = (FO + held + 7 ) // 8
217229 else :
218230 stop = (FO + held ) // 8
@@ -224,16 +236,33 @@ def reassembly(self, info: 'Packet[_AT]') -> 'None':
224236 # fragment with MF clear has arrived, and ``0`` is a valid length
225237 if not MF :
226238 buf .__update__ (TDL = length + FO )
227- TDL = buf .TDL
228239
229240 # when datagram is reassembled in whole
230- start = 0
231- stop = (TDL + 7 ) // 8
232- if TDL >= 0 and all (buf .RCVBT [start :stop ]):
241+ if self ._is_complete (buf ):
233242 self ._dtgram .extend (
234243 self .submit (self ._buffer .pop (BUFID ), bufid = BUFID , checked = True )
235244 )
236245
246+ @staticmethod
247+ def _is_complete (buf : 'Buffer[_AT]' ) -> 'bool' :
248+ """Tell whether a buffer holds its whole datagram.
249+
250+ Arguments:
251+ buf: buffer to check
252+
253+ Returns:
254+ Whether the total data length is known, fits the data buffer, and
255+ every 8-octet block up to it is marked received.
256+
257+ A datagram longer than the data buffer cannot be held, so it is never
258+ complete. Checking ``RCVBT`` alone does not show that: slicing it past
259+ its end stops silently, so the blocks past the buffer would read as
260+ received (:issue:`1566`).
261+
262+ """
263+ TDL = buf .TDL
264+ return 0 <= TDL <= len (buf .datagram ) and all (buf .RCVBT [:(TDL + 7 ) // 8 ])
265+
237266 @staticmethod
238267 def _detect_conflicts (rcvbt : 'bytearray' , tdl : 'int' , datagram : 'bytearray' , payload : 'bytearray' ,
239268 start : 'int' , stop : 'int' ) -> 'list[tuple[int, int]]' :
@@ -331,9 +360,7 @@ def submit(self, buf: 'Buffer[_AT]', *, bufid: 'tuple[_AT, _AT, int, TransType]'
331360 datagram = buf .datagram
332361 conflict = tuple (buf .conflict )
333362
334- start = 0
335- stop = (TDL + 7 ) // 8
336- flag = checked or (TDL >= 0 and all (RCVBT [start :stop ]))
363+ flag = checked or self ._is_complete (buf )
337364 ret = [] # type: list[Datagram[_AT]]
338365
339366 # How completely this datagram came out, and why it stopped. Derived once,
@@ -361,23 +388,24 @@ def submit(self, buf: 'Buffer[_AT]', *, bufid: 'tuple[_AT, _AT, int, TransType]'
361388 # the last run may reach the end of the bit table
362389 if byte :
363390 data .append (bytes (byte ))
364- # strip empty packets
365- if data or header :
366- packet = Datagram (
367- completed = completion ,
368- id = DatagramID (
369- src = bufid [0 ],
370- dst = bufid [1 ],
371- id = bufid [2 ],
372- proto = bufid [3 ],
373- ),
374- index = tuple (index ),
375- header = header ,
376- payload = tuple (data ),
377- packet = None ,
378- conflict = conflict ,
379- )
380- ret .append (packet )
391+ # Report it even with no run received, its payload ``()``, as
392+ # ``strict=False`` reports it with an empty payload: ``index``
393+ # still says which frames were its fragments (:issue:`1566`).
394+ packet = Datagram (
395+ completed = completion ,
396+ id = DatagramID (
397+ src = bufid [0 ],
398+ dst = bufid [1 ],
399+ id = bufid [2 ],
400+ proto = bufid [3 ],
401+ ),
402+ index = tuple (index ),
403+ header = header ,
404+ payload = tuple (data ),
405+ packet = None ,
406+ conflict = conflict ,
407+ )
408+ ret .append (packet )
381409 # if datagram is reassembled in whole -- or if it is not, and ``strict``
382410 # asked for one contiguous payload rather than the received runs
383411 else :
0 commit comments