Skip to content

Instantly share code, notes, and snippets.

@Aposhian
Created April 30, 2025 16:05
Show Gist options
  • Save Aposhian/1ddd252cb629ad54b587eecd81c5136d to your computer and use it in GitHub Desktop.
Save Aposhian/1ddd252cb629ad54b587eecd81c5136d to your computer and use it in GitHub Desktop.
Ouster Dual Return Wireshark Packet Dissector
-- ouster_lidar_dual.lua
local ouster = Proto("ouster", "Ouster LiDAR Dual Return")
-- Define enums
local packet_type_enum = {
[0x1] = "Lidar Data",
[0x2] = "IMU Data",
[0x3] = "Time Sync",
[0x4] = "Calibration",
[0x5] = "Config",
[0x6] = "Status",
[0x7] = "Error"
}
local status_enum = {
[0x0] = "Invalid",
[0x1] = "Valid"
}
local shot_limiting_status_enum = {
[0x00] = "NORMAL",
[0x01] = "SHOT_LIMITING_IMMINENT",
[0x02] = "SHOT_LIMITING",
[0x03] = "SHOT_LIMITING",
[0x04] = "SHOT_LIMITING",
[0x05] = "SHOT_LIMITING",
[0x06] = "SHOT_LIMITING",
[0x07] = "SHOT_LIMITING",
[0x08] = "SHOT_LIMITING",
[0x09] = "SHOT_LIMITING",
[0x0A] = "SHOT_LIMITING",
[0x0B] = "SHOT_LIMITING",
[0x0C] = "SHOT_LIMITING",
[0x0D] = "SHOT_LIMITING",
[0x0E] = "SHOT_LIMITING",
[0x0F] = "SHOT_LIMITING"
}
-- Define header fields
local f_packet_type = ProtoField.uint8("ouster.packet_type", "Packet Type", base.HEX, packet_type_enum)
local f_init_id = ProtoField.uint24("ouster.init_id", "Init ID", base.DEC)
local f_frame_id = ProtoField.uint32("ouster.frame_id", "Frame ID", base.DEC)
local f_serial_no = ProtoField.uint64("ouster.serial_no", "Serial No", base.DEC)
local f_alert_flags = ProtoField.uint8("ouster.alert_flags", "Alert Flags", base.HEX)
local f_alert_cursors = ProtoField.uint8("ouster.alert_cursors", "Alert Cursors", base.DEC, nil, 0x3F)
local f_cursor_overflow = ProtoField.bool("ouster.cursor_overflow", "Cursor Overflow", 8, nil, 0x40)
local f_alerts_active = ProtoField.bool("ouster.alerts_active", "Alerts Active", 8, nil, 0x80)
local f_shutdown_countdown = ProtoField.uint8("ouster.shutdown_countdown", "Shutdown Countdown", base.DEC)
local f_shot_limiting_countdown = ProtoField.uint8("ouster.shot_limiting_countdown", "Shot Limiting Countdown", base.DEC)
local f_shutdown_status = ProtoField.uint8("ouster.shutdown_status", "Shutdown Status", base.DEC, nil, 0x0F)
local f_shot_limiting_status = ProtoField.uint8("ouster.shot_limiting_status", "Shot Limiting Status", base.DEC, shot_limiting_status_enum, 0xF0)
-- Define fields
local f_range1 = ProtoField.uint32("ouster.range1", "Range Return 1 (mm)", base.DEC, nil, 0x7FFFF)
local f_range2 = ProtoField.uint32("ouster.range2", "Range Return 2 (mm)", base.DEC, nil, 0x7FFFF)
local f_reflectivity1 = ProtoField.uint8("ouster.reflectivity1", "Reflectivity Return 1", base.DEC)
local f_reflectivity2 = ProtoField.uint8("ouster.reflectivity2", "Reflectivity Return 2", base.DEC)
local f_signal1 = ProtoField.uint16("ouster.signal1", "Signal Photons Return 1", base.DEC)
local f_signal2 = ProtoField.uint16("ouster.signal2", "Signal Photons Return 2", base.DEC)
local f_nir1 = ProtoField.uint16("ouster.nir1", "Near Infrared Photons Return 1", base.DEC)
local f_nir2 = ProtoField.uint16("ouster.nir2", "Near Infrared Photons Return 2", base.DEC)
-- Define measurement header fields
local f_timestamp = ProtoField.uint64("ouster.timestamp", "Timestamp (ns)", base.DEC)
local f_measurement_id = ProtoField.uint16("ouster.measurement_id", "Measurement ID", base.DEC)
local f_status = ProtoField.uint8("ouster.status", "Status", base.HEX, nil, 0x01)
-- Define footer fields
local f_reserved = ProtoField.bytes("ouster.reserved", "Reserved")
local f_e2e_crc = ProtoField.uint64("ouster.e2e_crc", "E2E CRC", base.HEX)
ouster.fields = {
-- Header fields
f_packet_type, f_init_id, f_frame_id, f_serial_no,
f_alert_flags, f_alert_cursors, f_cursor_overflow, f_alerts_active,
f_shutdown_countdown, f_shot_limiting_countdown,
f_shutdown_status, f_shot_limiting_status,
-- Measurement header fields
f_timestamp, f_measurement_id, f_status,
-- Footer fields
f_reserved, f_e2e_crc,
-- Channel data fields
f_range1, f_range2, f_reflectivity1, f_reflectivity2,
f_signal1, f_signal2, f_nir1, f_nir2
}
function ouster.dissector(buffer, pinfo, tree)
-- Check if this is a Lidar Data packet (type 1)
local packet_type = buffer(0, 1):le_uint()
if packet_type ~= 0x1 then
return
end
pinfo.cols.protocol = "OUSTER"
local subtree = tree:add(ouster, buffer(), "Ouster LiDAR Dual Return Packet")
local offset = 0
local packet_len = buffer:len()
-- Parse header fields
local header_tree = subtree:add(ouster, buffer(0, 32), "Packet Header")
header_tree:add_le(f_packet_type, buffer(0, 1))
header_tree:add_le(f_init_id, buffer(1, 3))
header_tree:add_le(f_frame_id, buffer(4, 4))
header_tree:add_le(f_serial_no, buffer(8, 5))
local alert_flags = buffer(13, 1):le_uint()
local alert_flags_tree = header_tree:add_le(f_alert_flags, alert_flags)
alert_flags_tree:add_le(f_alert_cursors, alert_flags)
alert_flags_tree:add_le(f_cursor_overflow, alert_flags)
alert_flags_tree:add_le(f_alerts_active, alert_flags)
header_tree:add_le(f_shutdown_countdown, buffer(14, 1))
header_tree:add_le(f_shot_limiting_countdown, buffer(15, 1))
local status_byte = buffer(16, 1):le_uint()
header_tree:add_le(f_shutdown_status, status_byte)
header_tree:add_le(f_shot_limiting_status, status_byte)
-- Skip to measurement data
offset = 32
-- Process 16 measurement blocks
for blk = 0, 15 do
if offset + 12 > packet_len then break end
local mb_header = buffer(offset, 12)
local mb_tree = subtree:add(ouster, mb_header, "Measurement Block " .. blk)
-- Parse Column Header Block
local timestamp = buffer(offset, 8):le_uint64()
local measurement_id = buffer(offset + 8, 2):le_uint()
local status = buffer(offset + 10, 1):le_uint()
local col_header_tree = mb_tree:add(ouster, buffer(offset, 12), "Column Header Block")
col_header_tree:add_le(f_timestamp, timestamp)
col_header_tree:add_le(f_measurement_id, measurement_id)
col_header_tree:add_le(f_status, status, status_enum[status] or "Unknown")
offset = offset + 12
-- Number of channels (e.g., 64)
local num_channels = 64
-- Add parent node for channel data blocks
local channel_data_tree = mb_tree:add(ouster, buffer(offset, num_channels * 16), "Channel Data Blocks")
-- Each channel data block is 16 bytes (128 bits)
for ch = 0, num_channels - 1 do
if offset + 16 > packet_len then break end
local ch_data = buffer(offset, 16)
local ch_tree = channel_data_tree:add(ouster, ch_data, "Channel " .. ch)
-- Extract fields for Return 1
local word0 = ch_data(0, 4):le_uint()
local word1 = ch_data(4, 4):le_uint()
local range1 = bit32.band(word0, 0x7FFFF)
local reflectivity1 = bit32.band(word1, 0xFF)
local signal1 = bit32.band(word1, 0xFFFF)
local nir1 = bit32.rshift(word1, 16)
ch_tree:add_le(f_range1, range1)
ch_tree:add_le(f_reflectivity1, reflectivity1)
ch_tree:add_le(f_signal1, signal1)
ch_tree:add_le(f_nir1, nir1)
-- Extract fields for Return 2
local word2 = ch_data(8, 4):le_uint()
local word3 = ch_data(12, 4):le_uint()
local range2 = bit32.band(word2, 0x7FFFF)
local reflectivity2 = bit32.band(word3, 0xFF)
local signal2 = bit32.band(word3, 0xFFFF)
local nir2 = bit32.rshift(word3, 16)
ch_tree:add_le(f_range2, range2)
ch_tree:add_le(f_reflectivity2, reflectivity2)
ch_tree:add_le(f_signal2, signal2)
ch_tree:add_le(f_nir2, nir2)
offset = offset + 16
end
end
-- Parse Packet Footer
if offset + 32 <= packet_len then
local footer_tree = subtree:add(ouster, buffer(offset, 32), "Packet Footer")
footer_tree:add_le(f_reserved, buffer(offset, 24))
footer_tree:add_le(f_e2e_crc, buffer(offset + 24, 8))
end
end
-- Register dissector to UDP port 7502
local udp_port = DissectorTable.get("udp.port")
udp_port:add(7502, ouster)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment