Created
April 30, 2025 16:05
-
-
Save Aposhian/1ddd252cb629ad54b587eecd81c5136d to your computer and use it in GitHub Desktop.
Ouster Dual Return Wireshark Packet Dissector
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- 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