With files=True, every per-frame report lands on disk with two dots before the extension — Frame 1..json, Global Header..txt, Section Header 1..plist.
Cause
The name is built with a literal . while _fext already carries its own leading dot:
# pcapkit/foundation/engines/pcap.py:117
ofile = ext._ofile(f'{ext._ofnm}/Global Header.{ext._fext}')
# pcapkit/foundation/engines/pcap.py:156
ofile = ext._ofile(f'{ext._ofnm}/{frnum}.{ext._fext}')
# pcapkit/foundation/engines/pcapng.py:311
ofile = ext._ofile(f'{ext._ofnm}/{name}.{ext._fext}')
Three more sites share it: pcapkit/foundation/engines/dpkt.py:152, pcapkit/foundation/engines/scapy.py:127, pcapkit/foundation/engines/pyshark.py:137.
_fext comes from Extractor.__output__, whose extensions all include the dot (pcapkit/foundation/extraction.py:169-181):
'pcap': (ModuleDescriptor('pcapkit.dumpkit', 'PCAPIO'), '.pcap'),
'plist': (ModuleDescriptor('dictdumper', 'PLIST'), '.plist'),
'json': (ModuleDescriptor('dictdumper', 'JSON'), '.json'),
'tree': (ModuleDescriptor('dictdumper', 'Tree'), '.txt'),
read at extraction.py:546 (ext = cls.__output__[fmt][1]), returned as element 3 of make_name, and assigned at extraction.py:704 (self._fext = oext):
Extractor.__output__ extensions:
json -> '.json'
plist -> '.plist'
tree -> '.txt'
text -> '.txt'
pcap -> '.pcap'
make_name(...) returns oext = '.json'
make_name's own docstring states the opposite at extraction.py:510 — 3. output file extension (without .) — which is presumably where the f'….{ext._fext}' idiom came from. The non-files branch at extraction.py:557 (f'{fout}{ext}') gets it right and adds no dot.
Observed symptom
$ ls -la out
-rw-r--r-- 1 user group 2114 Sep 14 00:22 Frame 1..json
-rw-r--r-- 1 user group 1721 Sep 14 00:22 Frame 2..json
-rw-r--r-- 1 user group 423 Sep 14 00:22 Global Header..json
Both engines, every reachable format:
# PCAP engine, examples/captures/arp.pcap
json -> ['Frame 1..json', 'Frame 2..json', 'Global Header..json']
plist -> ['Frame 1..plist', 'Frame 2..plist', 'Global Header..plist']
tree -> ['Frame 1..txt', 'Frame 2..txt', 'Global Header..txt']
# PCAP-NG engine, examples/captures/test.pcapng
json -> ['Decryption Secrets 1..json', 'Frame 1..json', 'Frame 2..json', 'Frame 3..json',
'Frame 4..json', 'Frame 5..json', 'Interface Description 1..json',
'Interface Description 2..json', 'Interface Statistics 1..json',
'Name Resolution 1..json', 'Section Header 1..json', 'Section Header 2..json',
'systemd Journal Export 1..json']
tree -> ['Decryption Secrets 1..txt', 'Frame 1..txt', ...]
plist -> ['Decryption Secrets 1..plist', 'Frame 1..plist', ...]
format='pcap' / 'cap' cannot be checked, because it raises before any file is named (TypeError: PCAPIO.__init__() missing 1 required keyword-only argument: 'protocol') — that is a separate problem, not filed here.
Minimal reproduction
Sample captures are not tracked in git; generate them with python examples/generators/make_samples.py first.
import os
import pcapkit
for fmt in ('json', 'plist', 'tree'):
out = 'out-' + fmt
pcapkit.extract(fin='examples/captures/arp.pcap', fout=out,
format=fmt, files=True, store=False)
print('%-6s ->' % fmt, sorted(os.listdir(out)))
pcapkit.extract(fin='examples/captures/test.pcapng', fout='out-ng',
format='json', files=True, store=False)
print('pcapng ->', sorted(os.listdir('out-ng'))[:4], '...')
Output:
json -> ['Frame 1..json', 'Frame 2..json', 'Global Header..json']
plist -> ['Frame 1..plist', 'Frame 2..plist', 'Global Header..plist']
tree -> ['Frame 1..txt', 'Frame 2..txt', 'Global Header..txt']
pcapng -> ['Decryption Secrets 1..json', 'Frame 1..json', 'Frame 2..json', 'Frame 3..json'] ...
What a fix would need to touch
- Drop the literal
. from the f-string at pcapkit/foundation/engines/pcap.py:117 and :156, pcapkit/foundation/engines/pcapng.py:311, pcapkit/foundation/engines/dpkt.py:152, pcapkit/foundation/engines/scapy.py:127, pcapkit/foundation/engines/pyshark.py:137.
- Correct the docstring at
pcapkit/foundation/extraction.py:510, which claims the returned extension has no dot. It is wrong either way and is what the buggy call sites were written against.
- Two test sites currently depend on the broken shape and should move with the fix:
tests/integration/_helpers.py:127-139 — report_stems splits at the first dot on purpose "to stay neutral about how many dots there are, rather than pinning that defect", and cites these exact lines. Once fixed it can assert the whole filename.
tests/foundation/engines/test_runtime_engines.py:42 — the fake extractor sets '_fext': 'json' without the dot, which is why assertEqual(sink.paths[-1], 'out/Frame 1.json') at :137, :274 and :368 passes and never caught this. That fixture contradicts the real Extractor._fext ('.json') and needs correcting, or those assertions stay vacuous after the fix.
Observed vs inferred
- Observed by running code: every filename listing above, the real
ls -la, and make_name(...) returning '.json'.
- Inferred by reading code: that
dpkt.py:152, scapy.py:127 and pyshark.py:137 share the defect — the expressions are identical but those engines need optional third-party packages that were not installed — and that the test_runtime_engines.py fixture is the reason the existing unit test misses it.
With
files=True, every per-frame report lands on disk with two dots before the extension —Frame 1..json,Global Header..txt,Section Header 1..plist.Cause
The name is built with a literal
.while_fextalready carries its own leading dot:Three more sites share it:
pcapkit/foundation/engines/dpkt.py:152,pcapkit/foundation/engines/scapy.py:127,pcapkit/foundation/engines/pyshark.py:137._fextcomes fromExtractor.__output__, whose extensions all include the dot (pcapkit/foundation/extraction.py:169-181):read at
extraction.py:546(ext = cls.__output__[fmt][1]), returned as element 3 ofmake_name, and assigned atextraction.py:704(self._fext = oext):make_name's own docstring states the opposite atextraction.py:510—3. output file extension (without.)— which is presumably where thef'….{ext._fext}'idiom came from. The non-filesbranch atextraction.py:557(f'{fout}{ext}') gets it right and adds no dot.Observed symptom
Both engines, every reachable format:
format='pcap'/'cap'cannot be checked, because it raises before any file is named (TypeError: PCAPIO.__init__() missing 1 required keyword-only argument: 'protocol') — that is a separate problem, not filed here.Minimal reproduction
Sample captures are not tracked in git; generate them with
python examples/generators/make_samples.pyfirst.Output:
What a fix would need to touch
.from the f-string atpcapkit/foundation/engines/pcap.py:117and:156,pcapkit/foundation/engines/pcapng.py:311,pcapkit/foundation/engines/dpkt.py:152,pcapkit/foundation/engines/scapy.py:127,pcapkit/foundation/engines/pyshark.py:137.pcapkit/foundation/extraction.py:510, which claims the returned extension has no dot. It is wrong either way and is what the buggy call sites were written against.tests/integration/_helpers.py:127-139—report_stemssplits at the first dot on purpose "to stay neutral about how many dots there are, rather than pinning that defect", and cites these exact lines. Once fixed it can assert the whole filename.tests/foundation/engines/test_runtime_engines.py:42— the fake extractor sets'_fext': 'json'without the dot, which is whyassertEqual(sink.paths[-1], 'out/Frame 1.json')at:137,:274and:368passes and never caught this. That fixture contradicts the realExtractor._fext('.json') and needs correcting, or those assertions stay vacuous after the fix.Observed vs inferred
ls -la, andmake_name(...)returning'.json'.dpkt.py:152,scapy.py:127andpyshark.py:137share the defect — the expressions are identical but those engines need optional third-party packages that were not installed — and that thetest_runtime_engines.pyfixture is the reason the existing unit test misses it.