From cb6a9a967dce3377326be75fd0e18d14f0bc8c5f Mon Sep 17 00:00:00 2001 From: Bryan Bennett Date: Thu, 22 Sep 2022 13:10:05 -0400 Subject: [PATCH 1/4] Add support for skipping sections during iteration closes #47 --- pcapng/scanner.py | 33 +++++++++++- test_data/explicit_shb_sec_length.ntar | Bin 0 -> 464 bytes test_data/implicit_shb_sec_length.ntar | Bin 0 -> 464 bytes tests/test_parse_wireshark_capture_files.py | 56 ++++++++++++++++++++ 4 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 test_data/explicit_shb_sec_length.ntar create mode 100644 test_data/implicit_shb_sec_length.ntar diff --git a/pcapng/scanner.py b/pcapng/scanner.py index 4284823..749e0e7 100644 --- a/pcapng/scanner.py +++ b/pcapng/scanner.py @@ -33,20 +33,50 @@ class FileScanner(object): just wrap it in a :py:class:`io.BytesIO` object. """ - __slots__ = ["stream", "current_section", "endianness"] + __slots__ = [ + "stream", + "current_section", + "endianness", + "_stashed_header", + "_last_header_offset", + ] def __init__(self, stream): self.stream = stream self.current_section = None self.endianness = "=" + self._last_header_offset = 0 + self._stashed_header = None + def __iter__(self): while True: try: + if self._stashed_header: + yield self._stashed_header + self._stashed_header = None yield self._read_next_block() except StreamEmpty: return + def skip_section(self): + """ + Skip the current section. + """ + if ( + self.stream.seekable() + and self.current_section + and self.current_section.length != -1 + ): + # seek + self.stream.seek(self._last_header_offset + self.current_section.length) + else: + # iterate till next section + for block in self: + if isinstance(block, blocks.SectionHeader): + self._stashed_header = block + break + def _read_next_block(self): block_type = self._read_int(32, False) @@ -54,6 +84,7 @@ def _read_next_block(self): block = self._read_section_header() self.current_section = block self.endianness = block.endianness + self._last_header_offset = self.stream.tell() return block if self.current_section is None: diff --git a/test_data/explicit_shb_sec_length.ntar b/test_data/explicit_shb_sec_length.ntar new file mode 100644 index 0000000000000000000000000000000000000000..f70fd4b7eec147d7ecd86abadd0681b4d5040037 GIT binary patch literal 464 zcmd<$<>iWCU|{gI(UxKa(i4Cf1Xvi18C+8`OOg`{6g0v!i&Bd-5{t4m6^!-F^o$iW z+%roQlJj$Ofs|Q_v57^hiJ75cs*xsGFUUNQSt3vxWEzOg2E;W$3<9T4u`(!UZ1ju* zvO(C`7)Xe*$+p*h|JZgn0mxxcVPO2%0Ac|3%eMdj2bB5G1mQzTF)IdEpa>&FKf^a} bpco?ykU&zy_#dbTVkZ}xKmS9)aPubsD(G94 literal 0 HcmV?d00001 diff --git a/test_data/implicit_shb_sec_length.ntar b/test_data/implicit_shb_sec_length.ntar new file mode 100644 index 0000000000000000000000000000000000000000..1d56e65640c4a40d87ba309b883ecba078299b99 GIT binary patch literal 464 zcmd<$<>iWCU|{gI(UxKa(*L1=g~6D?H6^noIk7-NBRsPxwKyZOC|gs(SkFw)SV6-* zvqT{|KQ|XhnWY$;SfrYm85*VB${8Cy zqkwD>HZ}$lVr;VQb>Ba>-Aw>;7*rS-|22RZK>f1q|NjAH{xd=NP*Ti_ffXpi$k5O5 djTVeqFh33x*P$Pz$KLO!_Ta^F+ literal 0 HcmV?d00001 diff --git a/tests/test_parse_wireshark_capture_files.py b/tests/test_parse_wireshark_capture_files.py index 97a0d3e..5a07be2 100644 --- a/tests/test_parse_wireshark_capture_files.py +++ b/tests/test_parse_wireshark_capture_files.py @@ -261,3 +261,59 @@ def test_sample_test010_ntar(): scanner = FileScanner(fp) for entry in scanner: pass + + +def test_skip_section_explicit_length(): + """ + Test section skipping when the section is explicitly sized. + + The pcapng used here is laid out as follows: + SectionHeader with explicit length of 144 + InterfaceDescription + EnhancedPacket + SectionHeader with implicit length + InterfaceDescription + EnhancedPacket + + Note that the only difference between the two sections + is the explicit sizing in the first section. + """ + with open("test_data/explicit_shb_sec_length.ntar", "rb") as fp: + scanner = FileScanner(fp) + iter_scanner = iter(scanner) + first_shb = next(iter_scanner) + assert first_shb.length == 144 + # Read another block so we're in the middle of the section + next(iter_scanner) + scanner.skip_section() + block = next(iter_scanner) + assert isinstance(block, SectionHeader) + assert block.length == -1 + + +def test_skip_section_implicit_length(): + """ + Test section skipping when the section is implicitly sized. + + The pcapng used here is laid out as follows: + SectionHeader with implicit length + InterfaceDescription + EnhancedPacket + SectionHeader with explicit length of 144 + InterfaceDescription + EnhancedPacket + + Note that the only difference between the two sections + is the implicit sizing in the first section. + """ + with open("test_data/implicit_shb_sec_length.ntar", "rb") as fp: + scanner = FileScanner(fp) + iter_scanner = iter(scanner) + first_shb = next(iter_scanner) + assert first_shb.length == -1 + # Read another block so we're in the middle of the section + next(iter_scanner) + scanner.skip_section() + block = next(iter_scanner) + assert isinstance(block, SectionHeader) + assert block.length == 144 From 8434456e4b5aec5494d43089eaa17461bfe91a7c Mon Sep 17 00:00:00 2001 From: aib Date: Fri, 15 Jul 2022 09:29:47 +0300 Subject: [PATCH 2/4] Fix FileScanner calling tell on unseekable streams --- pcapng/structs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pcapng/structs.py b/pcapng/structs.py index 65e5969..cdcf194 100644 --- a/pcapng/structs.py +++ b/pcapng/structs.py @@ -255,7 +255,7 @@ def read_bytes_padded(stream, size, pad_block_size=4): were read """ - if stream.tell() % pad_block_size != 0: + if stream.seekable() and (stream.tell() % pad_block_size != 0): raise RuntimeError("Stream is misaligned!") data = read_bytes(stream, size) From 5a451ddfebbbb35c6fdd1aa15b77eac91e4e4dc5 Mon Sep 17 00:00:00 2001 From: Bryan Bennett Date: Tue, 27 Sep 2022 10:03:10 -0400 Subject: [PATCH 3/4] Don't call tell if stream is unseekable --- pcapng/scanner.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pcapng/scanner.py b/pcapng/scanner.py index 749e0e7..0b99cf7 100644 --- a/pcapng/scanner.py +++ b/pcapng/scanner.py @@ -84,7 +84,8 @@ def _read_next_block(self): block = self._read_section_header() self.current_section = block self.endianness = block.endianness - self._last_header_offset = self.stream.tell() + if self.stream.seekable(): + self._last_header_offset = self.stream.tell() return block if self.current_section is None: From 95782a44764aec22fc2c7b2ee92577f2875c3840 Mon Sep 17 00:00:00 2001 From: Bryan Bennett Date: Tue, 27 Sep 2022 10:03:30 -0400 Subject: [PATCH 4/4] Add test for skip_section on unseekable stream --- tests/test_parse_wireshark_capture_files.py | 25 +++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/test_parse_wireshark_capture_files.py b/tests/test_parse_wireshark_capture_files.py index 5a07be2..77f287a 100644 --- a/tests/test_parse_wireshark_capture_files.py +++ b/tests/test_parse_wireshark_capture_files.py @@ -317,3 +317,28 @@ def test_skip_section_implicit_length(): block = next(iter_scanner) assert isinstance(block, SectionHeader) assert block.length == 144 + + +def test_skip_section_non_seekable(): + """ + Test section skipping with non-seekable streams + """ + + def throw_err(): + raise NotImplementedError + + with open("test_data/explicit_shb_sec_length.ntar", "rb") as fp: + fp.seekable = lambda: False + fp.tell = throw_err + fp.seek = throw_err + + scanner = FileScanner(fp) + iter_scanner = iter(scanner) + first_shb = next(iter_scanner) + assert first_shb.length == 144 + # Read another block so we're in the middle of the section + next(iter_scanner) + scanner.skip_section() + block = next(iter_scanner) + assert isinstance(block, SectionHeader) + assert block.length == -1