# PCAP/PCAPNG Writing and Export Patterns
Guide for AI assistants on creating, filtering, and converting PCAP files with PcapFileEx.
## Quick Reference: When to Use Each API
| Task | API | Memory | Speed | Complexity |
|------|-----|--------|-------|------------|
| Filter + Export | `export_filtered/4` | Low (streaming) | Fast | Simple |
| Format convert | `copy/3` | Low (streaming) | Fast | Simple |
| Batch write | `write!/3` | High (loads all) | Fastest | Simple |
| Streaming write | `PcapWriter.open/write/close` | Low (O(1)) | Fast | Manual |
| PCAPNG multi-interface | `PcapNgWriter` | Low | Fast | Manual |
| Timestamp shift | `TimestampShift` + `write` | Medium | Fast | Simple |
## Critical Decision Trees
### 1. Format Selection
✅ **ALWAYS auto-detect when possible:**
```elixir
# Auto-detect output format from extension
PcapFileEx.write!("output.pcap", header, packets) # PCAP
PcapFileEx.write!("output.pcapng", header, packets) # PCAPNG
```
✅ **Explicit format when converting:**
```elixir
PcapFileEx.copy("input.pcap", "output.pcapng", format: :pcapng)
PcapFileEx.copy("input.pcapng", "output.pcap", format: :pcap)
```
❌ **AVOID format-specific writers unless you need manual control:**
```elixir
# DON'T: Manual writer for simple tasks
{:ok, writer} = PcapFileEx.PcapWriter.open("output.pcap", header)
# ... manual writing code
# DO: Use high-level API
PcapFileEx.write!("output.pcap", header, packets)
```
### 2. Batch vs Streaming
| Scenario | Use | Example |
|----------|-----|---------|
| < 1000 packets | Batch write (`write!/3`) | Small filtered result sets |
| 1000-10000 packets | Either | Check available memory |
| > 10GB file | Streaming (`export_filtered/4`) | Large file filtering |
| Need progress updates | Streaming (manual) | Process while writing |
### 3. Error Handling Strategy
**For export_filtered/4:**
```elixir
# :halt mode (default) - Stop on first error
PcapFileEx.export_filtered(src, dest, filter_fn)
# :skip mode - Skip corrupted packets, continue
PcapFileEx.export_filtered(src, dest, filter_fn, on_error: :skip)
# Custom error handling
case PcapFileEx.export_filtered(src, dest, filter_fn) do
{:ok, count} -> IO.puts("Exported #{count} packets")
{:error, reason} -> IO.puts("Export failed: #{reason}")
end
```
## Common Patterns
### Pattern 1: Filter and Export
**Use Case:** Extract subset of packets to new file
```elixir
# HTTP traffic only
PcapFileEx.export_filtered!(
"full_capture.pcap",
"http_only.pcap",
fn packet -> :http in packet.protocols end
)
# Specific IP address
PcapFileEx.export_filtered!(
"capture.pcap",
"host_traffic.pcap",
fn packet ->
packet.src.ip == "192.168.1.100" or
packet.dst.ip == "192.168.1.100"
end
)
# Time range
start_time = ~U[2025-11-09 10:00:00Z]
end_time = ~U[2025-11-09 11:00:00Z]
PcapFileEx.export_filtered!(
"full_day.pcapng",
"incident_window.pcapng",
fn packet ->
DateTime.compare(packet.timestamp, start_time) != :lt and
DateTime.compare(packet.timestamp, end_time) != :gt
end
)
# Packet size filter
PcapFileEx.export_filtered!(
"capture.pcap",
"large_packets.pcap",
fn packet -> byte_size(packet.data) > 1000 end
)
# Complex logic
PcapFileEx.export_filtered!(
"capture.pcap",
"suspicious.pcap",
fn packet ->
:tcp in packet.protocols and
packet.dst.port in [22, 23, 3389] and # SSH, Telnet, RDP
byte_size(packet.data) > 100
end
)
```
### Pattern 2: Format Conversion
**Use Case:** Convert between PCAP and PCAPNG formats
```elixir
# PCAP → PCAPNG (preserves all packets, adds interface metadata)
PcapFileEx.copy("legacy.pcap", "modern.pcapng", format: :pcapng)
# PCAPNG → PCAP (loses interface metadata, keeps packets)
PcapFileEx.copy("capture.pcapng", "legacy.pcap", format: :pcap)
# Auto-detect from extension
PcapFileEx.copy("input.pcap", "output.pcapng") # Detects .pcapng
# Copy without conversion (same format)
PcapFileEx.copy("original.pcap", "backup.pcap")
# Convert and verify
case PcapFileEx.copy("input.pcap", "output.pcapng", format: :pcapng) do
{:ok, count} ->
IO.puts("Converted #{count} packets to PCAPNG format")
{:error, reason} ->
IO.puts("Conversion failed: #{reason}")
end
```
### Pattern 3: Streaming Large File Writes
**Use Case:** Filter multi-GB file without loading into memory
```elixir
# Manual streaming write for progress updates
{:ok, header} = PcapFileEx.get_header("huge_50gb.pcap")
{:ok, writer} = PcapFileEx.PcapWriter.open("filtered.pcap", header)
count = 0
try do
PcapFileEx.stream!("huge_50gb.pcap")
|> Stream.filter(fn packet -> :http in packet.protocols end)
|> Enum.each(fn packet ->
:ok = PcapFileEx.PcapWriter.write_packet(writer, packet)
count = count + 1
# Progress update every 10000 packets
if rem(count, 10000) == 0 do
IO.puts("Processed #{count} packets...")
end
end)
IO.puts("Wrote #{count} packets")
after
PcapFileEx.PcapWriter.close(writer)
end
```
**Simpler alternative (no progress updates):**
```elixir
# Use export_filtered - handles everything automatically
{:ok, count} = PcapFileEx.export_filtered(
"huge_50gb.pcap",
"filtered.pcap",
fn packet -> :http in packet.protocols end
)
IO.puts("Exported #{count} packets")
```
### Pattern 4: Timestamp Manipulation
**Use Case:** Anonymize timestamps or adjust time zones
```elixir
# Normalize to Unix epoch (t=0)
{:ok, packets} = PcapFileEx.read_all("original.pcap")
normalized = PcapFileEx.TimestampShift.normalize_to_epoch(packets)
{:ok, header} = PcapFileEx.get_header("original.pcap")
PcapFileEx.write!("anonymized.pcap", header, normalized)
# Shift by specific offset (e.g., +1 hour)
one_hour_ns = 3_600_000_000_000 # 1 hour in nanoseconds
shifted = PcapFileEx.TimestampShift.shift_all(packets, one_hour_ns)
PcapFileEx.write!("time_shifted.pcap", header, shifted)
# Shift backward (e.g., -30 minutes)
minus_30_min_ns = -1_800_000_000_000
earlier = PcapFileEx.TimestampShift.shift_all(packets, minus_30_min_ns)
PcapFileEx.write!("earlier.pcap", header, earlier)
# Combined: Normalize then shift
normalized = PcapFileEx.TimestampShift.normalize_to_epoch(packets)
offset = 1_000_000_000_000 # +1000 seconds
final = PcapFileEx.TimestampShift.shift_all(normalized, offset)
PcapFileEx.write!("processed.pcap", header, final)
```
### Pattern 5: Batch Writing Small Datasets
**Use Case:** Create new PCAP from programmatically generated packets
```elixir
# Read, filter, write
{:ok, packets} = PcapFileEx.read_all("input.pcap")
filtered = Enum.filter(packets, fn p -> :tcp in p.protocols end)
{:ok, header} = PcapFileEx.get_header("input.pcap")
PcapFileEx.write!("tcp_only.pcap", header, filtered)
# Create from scratch (requires header)
header = %PcapFileEx.Header{
version_major: 2,
version_minor: 4,
snaplen: 65535,
datalink: "ethernet",
ts_resolution: "microsecond",
endianness: "little"
}
custom_packets = [
%PcapFileEx.Packet{
timestamp_precise: PcapFileEx.Timestamp.new(1000, 0),
orig_len: 100,
data: <<0x00, 0x01, 0x02, ...>>
},
# ... more packets
]
PcapFileEx.write!("custom.pcap", header, custom_packets)
```
### Pattern 6: PCAPNG Multi-Interface Writing
**Use Case:** Create PCAPNG with multiple network interfaces
```elixir
# Define interfaces
interfaces = [
%PcapFileEx.Interface{
id: 0,
linktype: "ethernet",
snaplen: 65535,
name: "eth0",
description: "Primary ethernet",
timestamp_resolution: :microsecond,
timestamp_resolution_raw: "microsecond",
timestamp_offset_secs: 0
},
%PcapFileEx.Interface{
id: 1,
linktype: "wifi",
snaplen: 65535,
name: "wlan0",
description: "Wireless interface",
timestamp_resolution: :nanosecond,
timestamp_resolution_raw: "nanosecond",
timestamp_offset_secs: 0
}
]
# Create packets with interface_id assignments
packets = [
%PcapFileEx.Packet{
timestamp_precise: PcapFileEx.Timestamp.new(1000, 100),
orig_len: 100,
data: <<...>>,
interface_id: 0, # eth0
datalink: "ethernet",
timestamp_resolution: :microsecond
},
%PcapFileEx.Packet{
timestamp_precise: PcapFileEx.Timestamp.new(1001, 200),
orig_len: 150,
data: <<...>>,
interface_id: 1, # wlan0
datalink: "wifi",
timestamp_resolution: :nanosecond
}
]
# Write all at once
{:ok, count} = PcapFileEx.PcapNgWriter.write_all(
"multi_interface.pcapng",
interfaces,
packets
)
IO.puts("Wrote #{count} packets across #{length(interfaces)} interfaces")
```
**Manual PCAPNG writer (for streaming):**
```elixir
{:ok, writer} = PcapFileEx.PcapNgWriter.open("output.pcapng")
# Register interfaces
{:ok, 0} = PcapFileEx.PcapNgWriter.write_interface(writer, eth0_interface)
{:ok, 1} = PcapFileEx.PcapNgWriter.write_interface(writer, wlan0_interface)
# Write packets one by one
:ok = PcapFileEx.PcapNgWriter.write_packet(writer, packet1)
:ok = PcapFileEx.PcapNgWriter.write_packet(writer, packet2)
:ok = PcapFileEx.PcapNgWriter.close(writer)
```
### Pattern 7: Combining Read + Filter + Write
**Use Case:** Process packets from multiple sources
```elixir
# Merge filtered results from multiple files
output_file = "combined_http.pcap"
{:ok, header} = PcapFileEx.get_header("capture1.pcap")
{:ok, writer} = PcapFileEx.PcapWriter.open(output_file, header)
try do
["capture1.pcap", "capture2.pcap", "capture3.pcap"]
|> Enum.each(fn file ->
PcapFileEx.stream!(file)
|> Stream.filter(fn p -> :http in p.protocols end)
|> Enum.each(fn packet ->
:ok = PcapFileEx.PcapWriter.write_packet(writer, packet)
end)
end)
after
PcapFileEx.PcapWriter.close(writer)
end
```
**Simpler (but loads all into memory):**
```elixir
all_http_packets =
["capture1.pcap", "capture2.pcap", "capture3.pcap"]
|> Enum.flat_map(fn file ->
{:ok, packets} = PcapFileEx.read_all(file)
Enum.filter(packets, fn p -> :http in p.protocols end)
end)
{:ok, header} = PcapFileEx.get_header("capture1.pcap")
PcapFileEx.write!("combined_http.pcap", header, all_http_packets)
```
### Pattern 8: Error Recovery
**Use Case:** Handle corrupted packets gracefully
```elixir
# Skip corrupted packets during export
{:ok, count} = PcapFileEx.export_filtered(
"possibly_corrupt.pcap",
"cleaned.pcap",
fn _packet -> true end, # Accept all valid packets
on_error: :skip # Skip corrupted ones
)
IO.puts("Exported #{count} valid packets")
# Halt on first error (default)
case PcapFileEx.export_filtered(src, dest, filter_fn) do
{:ok, count} ->
IO.puts("Success: #{count} packets")
{:error, reason} ->
IO.puts("Failed: #{reason}")
# Clean up partial file
File.rm(dest)
end
```
## Common Mistakes
### ❌ Mistake 1: Wrong Format for Conversion
```elixir
# DON'T: Use format-specific writer for conversion
{:ok, packets} = PcapFileEx.read_all("input.pcap")
# Then create interfaces, assign IDs, etc. (complex!)
PcapFileEx.PcapNgWriter.write_all(...)
# DO: Use copy/3 (handles everything)
PcapFileEx.copy("input.pcap", "output.pcapng", format: :pcapng)
```
### ❌ Mistake 2: Loading Huge Files
```elixir
# DON'T: Load 50GB file into memory
{:ok, all} = PcapFileEx.read_all("huge_50gb.pcap")
filtered = Enum.filter(all, filter_fn)
PcapFileEx.write!("filtered.pcap", header, filtered)
# DO: Use streaming export
PcapFileEx.export_filtered!("huge_50gb.pcap", "filtered.pcap", filter_fn)
```
### ❌ Mistake 3: Forgetting interface_id for PCAPNG
```elixir
# DON'T: Write PCAP packets to PCAPNG without interface_id
{:ok, packets} = PcapFileEx.read_all("input.pcap")
# packets have interface_id == nil!
PcapFileEx.PcapNgWriter.write_all("out.pcapng", interfaces, packets) # FAILS!
# DO: Use high-level API
PcapFileEx.copy("input.pcap", "out.pcapng", format: :pcapng)
# OR: Manually assign interface_id
packets_with_id = Enum.map(packets, &%{&1 | interface_id: 0})
PcapFileEx.PcapNgWriter.write_all("out.pcapng", interfaces, packets_with_id)
```
### ❌ Mistake 4: Not Closing Writers
```elixir
# DON'T: Forget to close (resource leak!)
{:ok, writer} = PcapFileEx.PcapWriter.open("output.pcap", header)
PcapFileEx.PcapWriter.write_packet(writer, packet)
# Never closed!
# DO: Use try/after
{:ok, writer} = PcapFileEx.PcapWriter.open("output.pcap", header)
try do
PcapFileEx.PcapWriter.write_packet(writer, packet)
after
PcapFileEx.PcapWriter.close(writer)
end
# BETTER: Use high-level API (handles cleanup)
PcapFileEx.write!("output.pcap", header, [packet])
```
### ❌ Mistake 5: Incorrect Header Creation
```elixir
# DON'T: Create header without required fields
header = %PcapFileEx.Header{} # Missing required fields!
PcapFileEx.write!("output.pcap", header, packets)
# DO: Copy from existing file
{:ok, header} = PcapFileEx.get_header("input.pcap")
PcapFileEx.write!("output.pcap", header, packets)
# OR: Create complete header
header = %PcapFileEx.Header{
version_major: 2,
version_minor: 4,
snaplen: 65535,
datalink: "ethernet",
ts_resolution: "microsecond",
endianness: "little"
}
```
## API Reference Summary
### High-Level API (Recommended)
**write/3, write!/3** - Create new PCAP file from packets
```elixir
PcapFileEx.write(path, header, packets)
PcapFileEx.write!(path, header, packets)
```
**copy/3, copy!/3** - Copy with optional format conversion
```elixir
PcapFileEx.copy(src, dest, format: :pcapng)
PcapFileEx.copy!(src, dest)
```
**export_filtered/4, export_filtered!/4** - Filter and export
```elixir
PcapFileEx.export_filtered(src, dest, filter_fn, on_error: :skip)
PcapFileEx.export_filtered!(src, dest, filter_fn)
```
### Low-Level API (Manual Control)
**PcapWriter** - PCAP format writing
```elixir
{:ok, writer} = PcapFileEx.PcapWriter.open(path, header, endianness: "little")
:ok = PcapFileEx.PcapWriter.write_packet(writer, packet)
{:ok, count} = PcapFileEx.PcapWriter.write_all(path, header, packets)
:ok = PcapFileEx.PcapWriter.close(writer)
{:error, reason} = PcapFileEx.PcapWriter.append(path) # Not supported
```
**PcapNgWriter** - PCAPNG format writing
```elixir
{:ok, writer} = PcapFileEx.PcapNgWriter.open(path, endianness: "little")
{:ok, interface_id} = PcapFileEx.PcapNgWriter.write_interface(writer, interface)
:ok = PcapFileEx.PcapNgWriter.write_packet(writer, packet)
{:ok, count} = PcapFileEx.PcapNgWriter.write_all(path, interfaces, packets, endianness: "little")
:ok = PcapFileEx.PcapNgWriter.close(writer)
{:error, reason} = PcapFileEx.PcapNgWriter.append(path) # Not implemented in v0.4.0
```
### Utilities
**TimestampShift** - Timestamp manipulation
```elixir
normalized = PcapFileEx.TimestampShift.normalize_to_epoch(packets)
shifted = PcapFileEx.TimestampShift.shift_all(packets, offset_ns)
```
## Performance Guidelines
### Memory Usage
| Operation | Memory | When to Use |
|-----------|--------|-------------|
| `write!/3` | O(N packets) | < 1000 packets |
| `export_filtered/4` | O(1) | Any size, filtering needed |
| `copy/3` | O(1) | Any size, format conversion |
| Manual streaming | O(1) | Need progress updates |
### Speed Comparison
For 10GB file with 10M packets:
| Method | Time | Memory |
|--------|------|--------|
| read_all + filter + write | ~180s | ~8GB |
| export_filtered (streaming) | ~120s | ~10MB |
| copy (no filter) | ~45s | ~10MB |
**Recommendation:** Use `export_filtered/4` for filtering large files, `copy/3` for format conversion.
## Append Mode Limitations (v0.4.0)
### PCAP Append
**Status:** Not supported by upstream `pcap-file` crate
```elixir
{:error, reason} = PcapFileEx.PcapWriter.append("existing.pcap")
# Returns clear error message
```
**Workaround:**
```elixir
# Read existing + new packets, write all
{:ok, existing} = PcapFileEx.read_all("existing.pcap")
all_packets = existing ++ new_packets
{:ok, header} = PcapFileEx.get_header("existing.pcap")
PcapFileEx.write!("existing.pcap", header, all_packets)
```
### PCAPNG Append
**Status:** Not implemented in MVP (v0.4.0)
```elixir
{:error, "Append mode not yet implemented"} =
PcapFileEx.PcapNgWriter.append("existing.pcapng")
```
**Planned for future release.**
## When to Use Each Module
### PcapFileEx (Main API)
✅ **Use this for 90% of writing tasks**
- Auto-detects format from extension
- Handles resource cleanup
- Simplest API
- `write/3`, `copy/3`, `export_filtered/4`
### PcapFileEx.PcapWriter
✅ **Use when:**
- Need streaming write with progress updates
- Writing very large files (>10GB)
- Need manual control over write operations
- PCAP format only
### PcapFileEx.PcapNgWriter
✅ **Use when:**
- Need multiple interface support
- Creating PCAPNG from scratch
- Need nanosecond timestamp precision
- Need interface-specific metadata
### PcapFileEx.TimestampShift
✅ **Use when:**
- Anonymizing timestamps
- Adjusting time zones
- Normalizing captures to epoch
- Testing time-based logic
## Related Documentation
- [Performance Guide](performance.md) - Optimization strategies
- [Filtering Guide](filtering.md) - Filter patterns and PreFilter
- [Merging Guide](merging.md) - Multi-file chronological merge
- [Format Guide](formats.md) - PCAP vs PCAPNG differences
- [Examples](examples.md) - Complete working examples