Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/source/pcapkit/foundation/traceflow/tcp.rst
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,8 @@ Terminology

(tuple) index
|--> (Info) data
| |--> 'fpout' : (Optional[str]) output filename if exists
| |--> 'fpout' : (Optional[str]) output filename, or None when
| | the format writes no file
| |--> 'index': (tuple) tuple of frame index, both directions,
| | in capture order
| | |--> (int) frame index
Expand Down
3 changes: 2 additions & 1 deletion pcapkit/foundation/traceflow/data/tcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,8 @@ class Index(DeferredPacket, Info):
#: :class:`~pcapkit.corekit.packet.DeferredPacket` to resolve.
__deferred__ = Deferred

#: Output filename if exists.
#: Path of the flow's output file, or :data:`None` when the output format
#: writes no file. Open and finalised flows report the same path.
fpout: 'Optional[str]'
#: Tuple of frame index, **both directions**, in capture order.
index: 'tuple[int, ...]'
Expand Down
23 changes: 21 additions & 2 deletions pcapkit/foundation/traceflow/tcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,25 @@ def _make_segment(self, packet: 'Packet[_AT]') -> 'Any':
timestamp=packet.timestamp, # capture timestamp
)

def _output_path(self, label: 'str') -> 'Optional[str]':
"""Path of a flow's output file, as an index entry reports it.

Arguments:
label: the flow's label

Returns:
The file :meth:`trace` writes the flow to, or :data:`None` when the
output format writes no file at all. An *empty* extension still
writes one, named after the label alone.

Both :meth:`submit` and :meth:`_finalise` report through this, so an
open flow and a finalised one name the same file (:issue:`2237`).

"""
if self._fdpext is None:
return None
return f'{self._fproot}/{label}{self._fdpext}'

@staticmethod
def _ended(buffer: 'Buffer[_AT]') -> 'bool':
"""Whether a flow's connection has been seen to end.
Expand Down Expand Up @@ -478,7 +497,7 @@ def _finalise(self, bufid: 'BufferID') -> 'Index':
logger.debug('TCP flow %s finalised after %d frame(s) (%d forward, %d reverse)',
label, len(buf.index), len(buf.forward), len(buf.reverse))
index = Index(
fpout=f'{self._fproot}/{label}{self._fdpext}' if self._fdpext is not None else None,
fpout=self._output_path(label),
index=tuple(buf.index),
label=label,
forward=tuple(buf.forward),
Expand Down Expand Up @@ -550,7 +569,7 @@ def submit(self) -> 'tuple[Index, ...]':

ret = [] # type: list[Index]
for buf in self._buffer.values():
ret.append(Index(fpout=f"{self._fproot}/{buf.label}{self._fdpext}" if self._fdpext else None,
ret.append(Index(fpout=self._output_path(buf.label),
index=tuple(buf.index),
label=buf.label,
forward=tuple(buf.forward),
Expand Down
29 changes: 29 additions & 0 deletions tests/foundation/traceflow/test_tcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,35 @@ def test_tcp_trace_ipv6_label_and_no_extension_output_path(self) -> None:
self.assertEqual(final_index.index, (1, 2))
self.assertFalse(pathlib.Path(tempdir, f'{label}.None').exists())

def test_an_empty_extension_reports_the_written_file_open_or_finalised(self) -> None:
"""An empty extension still writes a file, and every report names it (#2237).

The flow file is named after the label alone. :meth:`TCP.submit` reported
:data:`None` for a flow still open while :meth:`TCP._finalise` reported the
path, so one flow's ``fpout`` depended on when it was read.

"""
from dictdumper import JSON

from pcapkit.foundation.traceflow.tcp import TCP

TCP.register_dumper('unit-noext', JSON, '')

with tempfile.TemporaryDirectory() as tempdir:
trace = TCP(tempdir, 'unit-noext')
label = trace.trace(self._packet(index=1, syn=True))
expected = f'{tempdir}/{label}'
self.assertTrue(pathlib.Path(expected).is_file())

# mid-capture: the flow is open, reported by ``submit``
open_index, = trace.index
self.assertEqual(open_index.fpout, expected)

# end of capture: the same flow, reported by ``_finalise``
trace.finish()
final_index, = trace.index
self.assertEqual(final_index.fpout, expected)


if __name__ == '__main__':
unittest.main()
Loading