///|
/// Minimal pcapng bridge for trace artifact exchange.
///|
pub fn pcapng_custom_block_type() -> Int {
0x00000BAD
}
///|
pub fn pcapng_linktype_ethernet() -> Int {
1
}
///|
pub fn TraceLog::to_pcapng(self : TraceLog, seed~ : Int) -> Bytes {
let artifact = TraceArtifact::from_log(self, seed~)
let out : Array[Byte] = []
append_section_header_block(out)
append_interface_description_block(out)
for event in self.events {
append_enhanced_packet_block(out, event)
}
append_trace_artifact_custom_block(out, artifact)
Bytes::from_array(out)
}
///|
pub fn TraceArtifact::to_pcapng(
self : TraceArtifact,
) -> Bytes raise TraceArtifactError {
let out : Array[Byte] = []
append_section_header_block(out)
append_interface_description_block(out)
let log = self.import_log()
for event in log.events {
append_enhanced_packet_block(out, event)
}
append_trace_artifact_custom_block(out, self)
Bytes::from_array(out)
}
///|
pub fn parse_trace_pcapng(
bytes : Bytes,
) -> TraceArtifact raise TraceArtifactError {
let mut offset = 0
let mut saw_section = false
let mut saw_interface = false
let mut artifact : TraceArtifact? = None
while offset < bytes.length() {
if offset + 12 > bytes.length() {
raise TraceArtifactError::InvalidArtifact("truncated pcapng block header")
}
let block_type = read_u32_le(bytes, offset)
let block_len = read_u32_le(bytes, offset + 4)
if block_len < 12 || offset + block_len > bytes.length() {
raise TraceArtifactError::InvalidArtifact("invalid pcapng block length")
}
let trailing_len = read_u32_le(bytes, offset + block_len - 4)
if trailing_len != block_len {
raise TraceArtifactError::InvalidArtifact(
"pcapng trailing length mismatch",
)
}
if block_type == pcapng_section_header_block_type() {
validate_section_header(bytes, offset, block_len)
saw_section = true
} else if block_type == pcapng_interface_description_block_type() {
if !saw_section {
raise TraceArtifactError::InvalidArtifact(
"pcapng interface before section",
)
}
validate_interface_description(bytes, offset, block_len)
saw_interface = true
} else if block_type == pcapng_custom_block_type() {
artifact = Some(
parse_trace_artifact_custom_block(bytes, offset, block_len),
)
}
offset += block_len
}
if !saw_section {
raise TraceArtifactError::InvalidArtifact("missing pcapng section header")
}
if !saw_interface {
raise TraceArtifactError::InvalidArtifact("missing pcapng interface block")
}
match artifact {
Some(value) => value
None =>
raise TraceArtifactError::InvalidArtifact(
"missing trace artifact custom block",
)
}
}
///|
fn append_section_header_block(out : Array[Byte]) -> Unit {
let body : Array[Byte] = []
append_u32_le(body, 0x1A2B3C4D)
append_u16_le(body, 1)
append_u16_le(body, 0)
for _ in 0..<8 {
body.push(b'\xFF')
}
append_block(out, pcapng_section_header_block_type(), body)
}
///|
fn append_interface_description_block(out : Array[Byte]) -> Unit {
let body : Array[Byte] = []
append_u16_le(body, pcapng_linktype_ethernet())
append_u16_le(body, 0)
append_u32_le(body, 65535)
append_block(out, pcapng_interface_description_block_type(), body)
}
///|
fn append_enhanced_packet_block(out : Array[Byte], event : TraceEvent) -> Unit {
let packet = synthetic_ethernet_frame(event)
let timestamp_us = normalize_timestamp_us(event.raw_ns)
let body : Array[Byte] = []
append_u32_le(body, 0)
append_u32_le(body, int64_high_u32(timestamp_us))
append_u32_le(body, int64_low_u32(timestamp_us))
append_u32_le(body, packet.length())
append_u32_le(body, packet.length())
append_bytes(body, packet)
append_padding(body, packet.length())
append_block(out, pcapng_enhanced_packet_block_type(), body)
}
///|
fn append_trace_artifact_custom_block(
out : Array[Byte],
artifact : TraceArtifact,
) -> Unit {
let payload = @utf8.encode(artifact.to_text())
let body : Array[Byte] = []
append_u32_le(body, pcapng_private_enterprise_number())
append_u32_le(body, payload.length())
append_bytes(body, payload)
append_padding(body, payload.length())
append_block(out, pcapng_custom_block_type(), body)
}
///|
fn append_block(
out : Array[Byte],
block_type : Int,
body : Array[Byte],
) -> Unit {
let block_len = 12 + body.length()
append_u32_le(out, block_type)
append_u32_le(out, block_len)
for byte in body {
out.push(byte)
}
append_u32_le(out, block_len)
}
///|
fn synthetic_ethernet_frame(event : TraceEvent) -> Bytes {
let packet : Array[Byte] = []
packet.push(b'\x02')
packet.push(b'\x00')
packet.push(b'\x00')
packet.push(b'\x00')
packet.push(event.direction.code().to_byte())
packet.push(normalize_u8(event.event_id))
packet.push(b'\x02')
packet.push(b'\x00')
packet.push(b'\x00')
packet.push(b'\x01')
packet.push(normalize_u8(event.rng_step))
packet.push(normalize_u8(event.seed))
packet.push(b'\x88')
packet.push(b'\xB5')
let line = @utf8.encode(event.to_golden_line())
append_bytes(packet, line)
Bytes::from_array(packet)
}
///|
fn append_bytes(out : Array[Byte], bytes : Bytes) -> Unit {
for i in 0.. Unit {
let pad = padded_len(payload_len) - payload_len
for _ in 0.. Unit {
out.push((value & 0xFF).to_byte())
out.push(((value >> 8) & 0xFF).to_byte())
}
///|
fn append_u32_le(out : Array[Byte], value : Int) -> Unit {
out.push((value & 0xFF).to_byte())
out.push(((value >> 8) & 0xFF).to_byte())
out.push(((value >> 16) & 0xFF).to_byte())
out.push(((value >> 24) & 0xFF).to_byte())
}
///|
fn read_u32_le(bytes : Bytes, offset : Int) -> Int raise TraceArtifactError {
if offset < 0 || offset + 4 > bytes.length() {
raise TraceArtifactError::InvalidArtifact("truncated pcapng u32")
}
bytes[offset].to_int() |
(bytes[offset + 1].to_int() << 8) |
(bytes[offset + 2].to_int() << 16) |
(bytes[offset + 3].to_int() << 24)
}
///|
fn validate_section_header(
bytes : Bytes,
offset : Int,
block_len : Int,
) -> Unit raise TraceArtifactError {
if block_len < 28 {
raise TraceArtifactError::InvalidArtifact("short pcapng section header")
}
if read_u32_le(bytes, offset + 8) != 0x1A2B3C4D {
raise TraceArtifactError::InvalidArtifact("unsupported pcapng byte order")
}
if read_u16_le(bytes, offset + 12) != 1 {
raise TraceArtifactError::InvalidArtifact(
"unsupported pcapng major version",
)
}
}
///|
fn validate_interface_description(
bytes : Bytes,
offset : Int,
block_len : Int,
) -> Unit raise TraceArtifactError {
if block_len < 20 {
raise TraceArtifactError::InvalidArtifact("short pcapng interface block")
}
if read_u16_le(bytes, offset + 8) != pcapng_linktype_ethernet() {
raise TraceArtifactError::InvalidArtifact("unsupported pcapng linktype")
}
}
///|
fn read_u16_le(bytes : Bytes, offset : Int) -> Int raise TraceArtifactError {
if offset < 0 || offset + 2 > bytes.length() {
raise TraceArtifactError::InvalidArtifact("truncated pcapng u16")
}
bytes[offset].to_int() | (bytes[offset + 1].to_int() << 8)
}
///|
fn parse_trace_artifact_custom_block(
bytes : Bytes,
offset : Int,
block_len : Int,
) -> TraceArtifact raise TraceArtifactError {
if block_len < 20 {
raise TraceArtifactError::InvalidArtifact(
"short trace artifact custom block",
)
}
let pen = read_u32_le(bytes, offset + 8)
if pen != pcapng_private_enterprise_number() {
raise TraceArtifactError::InvalidArtifact("unexpected trace artifact PEN")
}
let payload_len = read_u32_le(bytes, offset + 12)
let payload_offset = offset + 16
if payload_offset + payload_len > offset + block_len - 4 {
raise TraceArtifactError::InvalidArtifact(
"truncated trace artifact payload",
)
}
let payload : Array[Byte] = []
for i in 0..
raise TraceArtifactError::InvalidArtifact("malformed trace artifact utf8")
}
parse_trace_artifact_text(text)
}
///|
fn normalize_timestamp_us(raw_ns : Int64) -> Int64 {
if raw_ns <= 0L {
0L
} else {
raw_ns / 1000L
}
}
///|
fn int64_high_u32(value : Int64) -> Int {
(value / 4294967296L % 4294967296L).to_int()
}
///|
fn int64_low_u32(value : Int64) -> Int {
(value % 4294967296L).to_int()
}
///|
fn normalize_u8(value : Int) -> Byte {
let mut out = value % 256
if out < 0 {
out += 256
}
out.to_byte()
}
///|
fn padded_len(length : Int) -> Int {
let rem = length % 4
if rem == 0 {
length
} else {
length + 4 - rem
}
}
///|
fn pcapng_section_header_block_type() -> Int {
0x0A0D0D0A
}
///|
fn pcapng_interface_description_block_type() -> Int {
0x00000001
}
///|
fn pcapng_enhanced_packet_block_type() -> Int {
0x00000006
}
///|
fn pcapng_private_enterprise_number() -> Int {
0
}