Wireshark dissector for Pro DJ Link protocol
3
fork

Configure Feed

Select the types of activity you want to include in your feed.

at fixes 635 lines 29 kB view raw
1-- Pro DJ Link Full Wireshark Dissector (Lua) 2-- Supports UDP Ports 50000, 50001, 50002 and TCP DB Server traffic 3 4local p_djl = Proto("pdjl", "Pro DJ Link") 5 6-- ============================================================================ 7-- COMMON FIELDS 8-- ============================================================================ 9local f_header = ProtoField.string("pdjl.header", "Header") 10local f_type = ProtoField.uint8("pdjl.type", "Packet Type", base.HEX) 11local f_device_name = ProtoField.string("pdjl.device_name", "Device Name") 12local f_dev_num = ProtoField.uint8("pdjl.device_number", "Device Number", base.DEC) 13 14-- Port 50000 (Management) 15local f_mac = ProtoField.ether("pdjl.mac", "MAC Address") 16local f_ip = ProtoField.ipv4("pdjl.ip", "IP Address") 17local f_sequence = ProtoField.uint8("pdjl.sequence", "Sequence", base.DEC) 18local f_dev_type = ProtoField.uint8("pdjl.device_type", "Device Type", base.HEX) 19local f_peer_count = ProtoField.uint8("pdjl.peer_count", "Peer Count", base.DEC) 20 21-- Port 50001 (Beat Sync) 22local f_bpm_raw = ProtoField.uint16("pdjl.bpm_raw", "BPM (Raw x100)", base.DEC) 23local f_pitch = ProtoField.uint32("pdjl.pitch", "Pitch (Raw)", base.HEX) 24local f_beat_in_bar = ProtoField.uint8("pdjl.beat_in_bar", "Beat in Bar", base.DEC) 25local f_next_beat = ProtoField.uint32("pdjl.next_beat", "Next Beat (ms)", base.DEC) 26local f_second_beat = ProtoField.uint32("pdjl.second_beat", "Second Beat (ms)", base.DEC) 27local f_next_bar = ProtoField.uint32("pdjl.next_bar", "Next Bar (ms)", base.DEC) 28local f_fourth_beat = ProtoField.uint32("pdjl.fourth_beat", "Fourth Beat (ms)", base.DEC) 29local f_second_bar = ProtoField.uint32("pdjl.second_bar", "Second Bar (ms)", base.DEC) 30local f_eighth_beat = ProtoField.uint32("pdjl.eighth_beat", "Eighth Beat (ms)", base.DEC) 31local f_track_len = ProtoField.uint32("pdjl.track_length", "Track Length (s)", base.DEC) 32local f_playhead = ProtoField.uint32("pdjl.playhead", "Playhead (ms)", base.DEC) 33local f_pitch_abs = ProtoField.int32("pdjl.pitch_abs", "Position Pitch", base.DEC) 34local f_bpm_abs = ProtoField.uint32("pdjl.bpm_abs", "Position BPM", base.DEC) 35local f_sync_cmd = ProtoField.uint8("pdjl.sync_cmd", "Sync Command", base.HEX) 36 37-- Port 50002 (Status) 38local f_rb_id = ProtoField.uint32("pdjl.rekordbox_id", "Rekordbox ID", base.DEC) 39local f_track_num = ProtoField.uint16("pdjl.track_number", "Track Number", base.DEC) 40local f_track_dev = ProtoField.uint8("pdjl.track_device", "Track Host Device", base.DEC) 41local f_slot = ProtoField.uint8("pdjl.slot", "Slot", base.HEX) 42local f_track_type = ProtoField.uint8("pdjl.track_type", "Track Type", base.HEX) 43local f_activity = ProtoField.uint8("pdjl.activity", "Activity", base.HEX) 44local f_firmware = ProtoField.string("pdjl.firmware", "Firmware") 45local f_beat_count = ProtoField.uint32("pdjl.beat_count", "Beat Count", base.DEC) 46local f_play_mode_1 = ProtoField.uint8("pdjl.play_mode_1", "Play Mode 1", base.HEX) 47local f_play_mode_2 = ProtoField.uint8("pdjl.play_mode_2", "Play Mode 2", base.HEX) 48local f_pitch_1 = ProtoField.uint32("pdjl.pitch_1", "Effective Pitch", base.HEX) 49local f_pitch_2 = ProtoField.uint32("pdjl.pitch_2", "Local Pitch", base.HEX) 50local f_master_info = ProtoField.uint8("pdjl.master_info", "Master Info", base.HEX) 51local f_handoff = ProtoField.uint8("pdjl.handoff", "Master Handoff", base.DEC) 52local f_countdown = ProtoField.uint16("pdjl.cue_countdown", "Cue Countdown", base.DEC) 53local f_pitch_3 = ProtoField.uint32("pdjl.pitch_3", "Pitch 3", base.HEX) 54 55local f_queried_ip = ProtoField.ipv4("pdjl.queried_ip", "Queried IP") 56local f_queried_dev = ProtoField.uint8("pdjl.queried_device", "Target Device", base.DEC) 57local f_queried_slot = ProtoField.uint8("pdjl.queried_slot", "Target Slot", base.HEX) 58local f_slot_owner = ProtoField.uint8("pdjl.slot_owner", "Slot Owner Device", base.DEC) 59local f_playlist_count = ProtoField.uint16("pdjl.playlist_count", "Playlist Count", base.DEC) 60local f_total_media_space = ProtoField.uint64("pdjl.total_media_space", "Total Space", base.DEC) 61local f_avail_media_space = ProtoField.uint64("pdjl.avail_media_space", "Available Space", base.DEC) 62local f_interface_color = ProtoField.uint8("pdjl.interface_color", "Interface Color", base.DEC, INTERFACE_COLORS) 63local f_settings_present = ProtoField.bool("pdjl.settings_present", "MYSETTINGS.DAT present", 8, nil, 0x01) 64 65 66local f_media_type = ProtoField.uint8("pdjl.media_type", "Media Type", base.HEX) 67local f_media_status = ProtoField.uint8("pdjl.media_status", "Media Status", base.HEX) 68local f_media_tracks = ProtoField.uint16("pdjl.media_tracks", "Media Tracks", base.DEC) 69local f_media_name = ProtoField.string("pdjl.media_name", "Media Name") 70local f_media_db_date= ProtoField.string("pdjl.media_db_date", "DB Creation Date") 71 72local f_flags = ProtoField.uint8("pdjl.flags", "Status Flags", base.HEX) 73local f_flag_playing= ProtoField.bool("pdjl.flags.playing", "Is Playing", 8, nil, 0x40) 74local f_flag_master = ProtoField.bool("pdjl.flags.master", "Is Master", 8, nil, 0x20) 75local f_flag_sync = ProtoField.bool("pdjl.flags.sync", "Is Syncing", 8, nil, 0x10) 76local f_flag_onair = ProtoField.bool("pdjl.flags.onair", "Is On Air", 8, nil, 0x08) 77local f_flag_bpm = ProtoField.bool("pdjl.flags.bpm", "BPM Sync Active", 8, nil, 0x02) 78 79-- TCP DB Server 80local f_db_magic = ProtoField.uint32("pdjl.db.magic", "DB Magic", base.HEX) 81local f_db_tx_id = ProtoField.uint32("pdjl.db.tx_id", "Transaction ID", base.HEX) 82local f_db_msg_type = ProtoField.uint16("pdjl.db.msg_type", "DB Message Type", base.HEX) 83local f_db_arg_count= ProtoField.uint8("pdjl.db.arg_count", "Argument Count", base.DEC) 84local f_db_tag_blob = ProtoField.bytes("pdjl.db.tags", "Argument Tags") 85 86local f_db_field_u8 = ProtoField.uint8("pdjl.db.field.u8", "Field (U8)", base.DEC) 87local f_db_field_u16= ProtoField.uint16("pdjl.db.field.u16", "Field (U16)", base.DEC) 88local f_db_field_u32= ProtoField.uint32("pdjl.db.field.u32", "Field (U32)", base.HEX) 89local f_db_field_u64= ProtoField.uint64("pdjl.db.field.u64", "Field (U64)", base.DEC) 90local f_db_field_str= ProtoField.string("pdjl.db.field.str", "Field (String)") 91local f_db_field_bin= ProtoField.bytes("pdjl.db.field.bin", "Field (Blob)") 92 93-- DMST decoding 94local f_dmst = ProtoField.uint32("pdjl.dmst", "DMST Parameter", base.HEX) 95local f_dmst_dev = ProtoField.uint8("pdjl.dmst.device", "Device", base.DEC) 96local f_dmst_menu = ProtoField.uint8("pdjl.dmst.menu", "Menu", base.HEX) 97local f_dmst_slot = ProtoField.uint8("pdjl.dmst.slot", "Slot", base.HEX) 98local f_dmst_type = ProtoField.uint8("pdjl.dmst.type", "Type", base.HEX) 99 100local f_db_port = ProtoField.uint16("pdjl.db_port", "DB Server Port", base.DEC) 101local f_raw_payload = ProtoField.bytes("pdjl.raw_payload", "Raw Payload") 102 103p_djl.fields = { 104 f_header, f_type, f_device_name, f_dev_num, 105 f_mac, f_ip, f_sequence, f_dev_type, f_peer_count, 106 f_bpm_raw, f_pitch, f_beat_in_bar, f_next_beat, f_second_beat, f_next_bar, f_fourth_beat, f_second_bar, f_eighth_beat, 107 f_track_len, f_playhead, f_pitch_abs, f_bpm_abs, f_sync_cmd, 108 f_rb_id, f_track_num, f_track_dev, f_slot, f_track_type, f_activity, f_firmware, f_beat_count, 109 f_play_mode_1, f_play_mode_2, f_pitch_1, f_pitch_2, f_master_info, f_handoff, f_countdown, f_pitch_3, 110 f_queried_ip, f_queried_dev, f_queried_slot, f_slot_owner, f_playlist_count, f_total_media_space, f_avail_media_space, 111 f_interface_color, f_settings_present, f_media_type, f_media_status, f_media_tracks, f_media_name, f_media_db_date, 112 f_flags, f_flag_playing, f_flag_master, f_flag_sync, f_flag_onair, f_flag_bpm, 113 f_db_magic, f_db_tx_id, f_db_msg_type, f_db_arg_count, f_db_tag_blob, 114 f_db_field_u8, f_db_field_u16, f_db_field_u32, f_db_field_u64, f_db_field_str, f_db_field_bin, 115 f_dmst, f_dmst_dev, f_dmst_menu, f_dmst_slot, f_dmst_type, 116 f_db_port, f_raw_payload 117} 118 119-- ============================================================================ 120-- HELPERS 121-- ============================================================================ 122local PACKET_TYPES = { 123 [0x00] = "Channel Claim 1", 124 [0x02] = "Channel Claim 2", 125 [0x04] = "Channel Claim Final", 126 [0x06] = "Keep-Alive", 127 [0x0a] = "CDJ Status / Announcement", 128 [0x0b] = "Absolute Position", 129 [0x28] = "Beat", 130 [0x2a] = "Sync Control", 131 [0x29] = "Mixer Status" 132} 133 134local DB_MSG_TYPES = { 135 [0x0000] = "GetQueryContext", 136 [0x4000] = "Success/Ack", 137 [0x1001] = "Disconnect", 138 [0x2002] = "GetMetadata", 139 [0x3000] = "RenderMenu", 140 [0x4001] = "MenuHeader", 141 [0x4101] = "MenuItem", 142 [0x4201] = "MenuFooter", 143 [0x2003] = "GetArtwork", 144 [0x4002] = "ArtworkResp", 145 [0x2204] = "GetBeatGrid", 146 [0x4602] = "BeatGridResp", 147 [0x2104] = "GetCuePoints", 148 [0x4702] = "CuePointsResp", 149 [0x2b04] = "GetExtCuePoints", 150 [0x4e02] = "ExtCuePointsResp", 151 [0x2004] = "GetWaveformPreview", 152 [0x4402] = "WaveformPreviewResp", 153 [0x2904] = "GetWaveformDetail", 154 [0x4a02] = "WaveformDetailResp", 155 [0x2c04] = "GetAnalysisTag", 156 [0x4f02] = "AnalysisTagResp", 157 [0x1105] = "GetTrackCount", 158 [0x5105] = "TrackCountResp", 159 [0x1103] = "GetTrackList", 160 [0x5103] = "TrackListResp", 161} 162 163local PLAY_MODES = { 164 [0x00] = "No Track", 165 [0x02] = "Loading", 166 [0x03] = "Playing", 167 [0x04] = "Looping", 168 [0x05] = "Paused", 169 [0x06] = "Paused at Cue", 170 [0x07] = "Cue Play", 171 [0x08] = "Cue Scratch", 172 [0x09] = "Searching", 173 [0x0e] = "CD Spun Down", 174 [0x11] = "Ended", 175} 176 177local TRACK_TYPES = { 178 [0x00] = "None", 179 [0x01] = "Rekordbox", 180 [0x02] = "Unanalyzed", 181 [0x05] = "Audio CD", 182} 183 184local SYNC_COMMANDS = { 185 [0x10] = "Turn On Sync", 186 [0x20] = "Turn Off Sync", 187 [0x01] = "Become Master", 188} 189 190local DEV_TYPES = { 191 [0x01] = "Player (CDJ)", 192 [0x02] = "Player (XDJ)", 193 [0x03] = "Mixer", 194 [0x04] = "PC/Rekordbox", 195} 196 197local INTERFACE_COLORS = { 198 [0] = "Default", 199 [1] = "Pink", 200 [2] = "Red", 201 [3] = "Orange", 202 [4] = "Yellow", 203 [5] = "Green", 204 [6] = "Aqua", 205 [7] = "Blue", 206 [8] = "Purple", 207} 208 209local MENU_ITEM_TYPES = { 210 [0x0002] = "Album", 211 [0x0004] = "TrackTitle", 212 [0x0006] = "Genre", 213 [0x0007] = "Artist", 214 [0x000a] = "Rating", 215 [0x000b] = "Duration", 216 [0x000d] = "Tempo", 217 [0x000f] = "Key", 218 [0x0023] = "Comment", 219 [0x002e] = "DateAdded", 220} 221 222-- Label for specific arguments based on message type 223function get_arg_label(msg_type, arg_idx, val) 224 -- Many request types have DMST as the first argument 225 if arg_idx == 1 and (msg_type == 0x2002 or msg_type == 0x3000 or msg_type == 0x1105 or msg_type == 0x1103 or msg_type == 0x2003 or msg_type == 0x2204 or msg_type == 0x2104 or msg_type == 0x2b04 or msg_type == 0x2004 or msg_type == 0x2904 or msg_type == 0x2c04) then 226 return "DMST" 227 end 228 229 if msg_type == 0x4101 then -- MenuItem 230 local labels = { 231 [1] = "Parent ID", 232 [2] = "Main ID", 233 [3] = "Label 1 Len", 234 [4] = "Label 1", 235 [5] = "Label 2 Len", 236 [6] = "Label 2", 237 [7] = "Item Type", 238 [8] = "Flags", 239 [9] = "Artwork ID", 240 [10] = "Position", 241 [11] = "Unknown", 242 [12] = "Unknown" 243 } 244 return labels[arg_idx] 245 elseif msg_type == 0x4201 then -- MenuFooter 246 return arg_idx == 1 and "Status" or (arg_idx == 2 and "Total Items" or nil) 247 elseif msg_type == 0x4001 then -- MenuHeader 248 return arg_idx == 1 and "Item Count" or nil 249 elseif msg_type == 0x4000 then -- Success/Ack 250 return arg_idx == 1 and "Request Type Echo" or (arg_idx == 2 and "Items Found" or nil) 251 elseif msg_type == 0x4602 or msg_type == 0x4702 or msg_type == 0x4e02 or msg_type == 0x4a02 or msg_type == 0x4402 or msg_type == 0x4002 or msg_type == 0x4f02 then -- Blob Responses 252 return arg_idx == 1 and "Request Type Echo" or (arg_idx == 3 and "Blob Length" or (arg_idx == 4 and "Blob Data" or nil)) 253 elseif msg_type == 0x3000 then -- RenderMenu 254 local labels = { 255 [1] = "DMST", 256 [2] = "Offset", 257 [3] = "Limit", 258 [4] = "Unknown", 259 [5] = "Total Items", 260 [6] = "Unknown" 261 } 262 return labels[arg_idx] 263 elseif msg_type == 0x0000 then -- Setup 264 return arg_idx == 1 and "Our Device Number" or nil 265 end 266 return nil 267end 268 269-- ============================================================================ 270-- DB TAGGED FIELD DISSECTOR 271-- ============================================================================ 272function dissect_db_field(buf, offset, tree, msg_type, arg_idx) 273 local len = buf:len() 274 if len - offset < 1 then return offset end 275 local tag = buf(offset, 1):uint() 276 local bytes_consumed = 1 277 278 local label = get_arg_label(msg_type, arg_idx) 279 local prefix = arg_idx and string.format("Arg %d", arg_idx) or "Field" 280 if label then prefix = prefix .. " (" .. label .. ")" end 281 282 if tag == 0x0f then -- U8 283 if len - offset >= 2 then 284 tree:add(f_db_field_u8, buf(offset+1, 1)):set_text(prefix .. ": " .. buf(offset+1, 1):uint()) 285 bytes_consumed = 2 286 end 287 elseif tag == 0x10 then -- U16 288 if len - offset >= 3 then 289 tree:add(f_db_field_u16, buf(offset+1, 2)):set_text(prefix .. ": " .. buf(offset+1, 2):uint()) 290 bytes_consumed = 3 291 end 292 elseif tag == 0x11 then -- U32 293 if len - offset >= 5 then 294 local val = buf(offset+1, 4):uint() 295 local item = tree:add(f_db_field_u32, buf(offset+1, 4)) 296 297 -- Special handling for DMST 298 if label == "DMST" then 299 item:set_text(prefix .. ": " .. string.format("0x%08x", val)) 300 local dmst_tree = item:add(f_dmst, buf(offset+1, 4)) 301 dmst_tree:add(f_dmst_dev, buf(offset+1, 1)) 302 dmst_tree:add(f_dmst_menu, buf(offset+2, 1)) 303 dmst_tree:add(f_dmst_slot, buf(offset+3, 1)) 304 dmst_tree:add(f_dmst_type, buf(offset+4, 1)) 305 elseif label == "Item Type" and MENU_ITEM_TYPES[val] then 306 item:set_text(prefix .. ": " .. MENU_ITEM_TYPES[val] .. " (0x" .. string.format("%04x", val) .. ")") 307 else 308 item:set_text(prefix .. ": " .. val .. " (0x" .. string.format("%08x", val) .. ")") 309 end 310 bytes_consumed = 5 311 end 312 elseif tag == 0x14 then -- Blob 313 if len - offset >= 5 then 314 local blob_len = buf(offset+1, 4):uint() 315 if len - offset >= 5 + blob_len then 316 tree:add(f_db_field_bin, buf(offset+5, blob_len)):set_text(prefix .. ": [Blob " .. blob_len .. " bytes]") 317 bytes_consumed = 5 + blob_len 318 else 319 bytes_consumed = len - offset 320 end 321 end 322 elseif tag == 0x26 then -- String (UTF-16BE) 323 if len - offset >= 5 then 324 local chars = buf(offset+1, 4):uint() 325 local byte_len = chars * 2 326 if len - offset >= 5 + byte_len then 327 -- Use Wireshark's native UTF-16BE decoding 328 local enc = (ENC_UTF_16 or 2) + (ENC_BIG_ENDIAN or 0) 329 local str = buf(offset+5, byte_len):string(enc) 330 -- Strip trailing nulls to clean up UI display 331 str = str:gsub("%z", "") 332 333 -- Use f_db_field_bin for the highlight range to avoid "trailing stray chars" warning 334 -- caused by Wireshark's internal string dissector seeing high-byte nulls. 335 tree:add(f_db_field_bin, buf(offset+5, byte_len)):set_text(prefix .. ": \"" .. str .. "\"") 336 bytes_consumed = 5 + byte_len 337 else 338 bytes_consumed = len - offset 339 end 340 end 341 end 342 343 return offset + bytes_consumed 344end 345 346-- ============================================================================ 347-- MAIN DISSECTOR 348-- ============================================================================ 349function p_djl.dissector(buf, pkt, root) 350 local length = buf:len() 351 local src_port = pkt.src_port 352 local dst_port = pkt.dst_port 353 354 -- TCP DB Port Discovery Response 355 if (src_port == 12523 or dst_port == 12523) and length == 2 then 356 local tree = root:add(p_djl, buf(0, 2)) 357 pkt.cols.protocol = "PDJL (DB Port)" 358 tree:add(f_db_port, buf(0, 2)) 359 return 360 end 361 362 363 364 if length < 5 then return end 365 366 -- Check for "Qspt1WmJOL" header (UDP) 367 local header_buf = buf(0, length >= 10 and 10 or length) 368 local header_str = header_buf:string() 369 370 if header_str ~= "Qspt1WmJOL" then 371 local off = 0 372 local found_db = false 373 local db_types = {} 374 375 while off + 5 <= length do 376 if buf(off, 1):uint() == 0x11 and buf(off+1, 4):uint() == 0x872349ae then 377 if not found_db then 378 pkt.cols.protocol = "Pro DJ Link (DB)" 379 found_db = true 380 end 381 382 local msg_tree = root:add(p_djl, buf(off, length - off)) 383 msg_tree:add(f_db_magic, buf(off+1, 4)) 384 385 local current = off + 5 386 387 -- Transaction ID 388 if length - current >= 5 then 389 current = dissect_db_field(buf, current, msg_tree) 390 end 391 392 -- Message Type 393 local m_type = 0 394 if length - current >= 3 then 395 local type_off = current 396 m_type = buf(type_off+1, 2):uint() 397 local m_type_str = DB_MSG_TYPES[m_type] or string.format("Unknown(0x%04x)", m_type) 398 table.insert(db_types, m_type_str) 399 400 current = dissect_db_field(buf, current, msg_tree) 401 msg_tree:append_text(": " .. m_type_str) 402 end 403 404 -- Arg Count 405 local arg_count = 0 406 if length - current >= 2 then 407 if buf(current, 1):uint() == 0x0f then 408 arg_count = buf(current + 1, 1):uint() 409 end 410 current = dissect_db_field(buf, current, msg_tree) 411 end 412 413 -- Arg Tag Blob 414 if current < length and buf(current, 1):uint() == 0x14 then 415 if length - current >= 5 then 416 local tag_len = buf(current+1, 4):uint() 417 if length - current >= 5 + tag_len then 418 msg_tree:add(f_db_tag_blob, buf(current+5, tag_len)) 419 current = current + 5 + tag_len 420 else 421 current = length 422 end 423 end 424 end 425 426 -- Arguments 427 for i = 1, arg_count do 428 if current >= length then break end 429 local prev_current = current 430 current = dissect_db_field(buf, current, msg_tree, m_type, i) 431 if current <= prev_current then break end 432 end 433 434 msg_tree:set_len(current - off) 435 off = current 436 else 437 if off == 0 and length == 5 and buf(0, 1):uint() == 0x11 and buf(1, 4):uint() == 0x00000001 then 438 pkt.cols.protocol = "Pro DJ Link (DB)" 439 pkt.cols.info:set("DB: Greeting") 440 root:add(p_djl, buf(0, 5)):append_text(": Greeting (0x00000001)") 441 return 442 end 443 break 444 end 445 end 446 447 if found_db then 448 pkt.cols.info:set("DB: " .. table.concat(db_types, ", ")) 449 pkt.cols.info:fence() 450 return 451 end 452 return 453 end 454 455 -- UDP Logic 456 pkt.cols.protocol = "Pro DJ Link" 457 local tree = root:add(p_djl, buf(0, length)) 458 tree:add(f_header, header_buf) 459 460 local p_type = buf(10, 1):uint() 461 local p_type_str = PACKET_TYPES[p_type] 462 463 if dst_port == 50002 or src_port == 50002 then 464 if p_type == 0x05 then p_type_str = "Media Query" end 465 if p_type == 0x06 then p_type_str = "Media Response" end 466 end 467 468 if p_type_str then 469 tree:add(f_type, buf(10, 1)):append_text(" (" .. p_type_str .. ")") 470 pkt.cols.info:set(p_type_str) 471 else 472 local unknown_label = string.format("Unknown(0x%02x)", p_type) 473 tree:add(f_type, buf(10, 1)):append_text(" (" .. unknown_label .. ")") 474 475 if length > 11 then 476 local raw_hex = buf(11):bytes():tohex() 477 tree:add(f_raw_payload, buf(11)) 478 pkt.cols.info:set(unknown_label .. ": " .. raw_hex) 479 else 480 pkt.cols.info:set(unknown_label) 481 end 482 end 483 484 pkt.cols.info:fence() 485 486 if dst_port == 50000 or src_port == 50000 then 487 tree:add(f_device_name, buf(0x0C, 20)) 488 if p_type == 0x06 then -- Keep-Alive 489 tree:add(f_dev_num, buf(0x24, 1)) 490 local dtype = buf(0x25, 1):uint() 491 tree:add(f_dev_type, buf(0x25, 1)):append_text(" (" .. (DEV_TYPES[dtype] or "Unknown") .. ")") 492 tree:add(f_mac, buf(0x26, 6)) 493 tree:add(f_ip, buf(0x2C, 4)) 494 tree:add(f_peer_count, buf(0x30, 1)) 495 elseif p_type == 0x0a then -- Announcement 496 local dtype = buf(0x24, 1):uint() 497 tree:add(f_dev_type, buf(0x24, 1)):append_text(" (" .. (DEV_TYPES[dtype] or "Unknown") .. ")") 498 elseif p_type == 0x00 then -- Claim Stage 1 499 tree:add(f_sequence, buf(0x24, 1)) 500 local dtype = buf(0x25, 1):uint() 501 tree:add(f_dev_type, buf(0x25, 1)):append_text(" (" .. (DEV_TYPES[dtype] or "Unknown") .. ")") 502 tree:add(f_mac, buf(0x26, 6)) 503 elseif p_type == 0x02 then -- Claim Stage 2 504 tree:add(f_ip, buf(0x24, 4)) 505 tree:add(f_mac, buf(0x28, 6)) 506 tree:add(f_dev_num, buf(0x2E, 1)) 507 tree:add(f_sequence, buf(0x2F, 1)) 508 elseif p_type == 0x04 then -- Claim Final 509 tree:add(f_dev_num, buf(0x24, 1)) 510 tree:add(f_sequence, buf(0x25, 1)) 511 end 512 elseif dst_port == 50001 or src_port == 50001 then 513 if p_type == 0x28 then -- Beat 514 tree:add(f_device_name, buf(0x0B, 20)) 515 tree:add(f_dev_num, buf(0x21, 1)) 516 tree:add(f_next_beat, buf(0x24, 4)) 517 tree:add(f_second_beat, buf(0x28, 4)) 518 tree:add(f_next_bar, buf(0x2C, 4)) 519 tree:add(f_fourth_beat, buf(0x30, 4)) 520 tree:add(f_second_bar, buf(0x34, 4)) 521 tree:add(f_eighth_beat, buf(0x38, 4)) 522 tree:add(f_pitch, buf(0x54, 4)) 523 tree:add(f_bpm_raw, buf(0x5a, 2)) 524 tree:add(f_beat_in_bar, buf(0x5c, 1)) 525 elseif p_type == 0x0b then -- Absolute Position 526 tree:add(f_dev_num, buf(0x21, 1)) 527 tree:add(f_track_len, buf(0x24, 4)) 528 tree:add(f_playhead, buf(0x28, 4)) 529 tree:add(f_pitch_abs, buf(0x2C, 4)) 530 tree:add(f_bpm_abs, buf(0x38, 4)) 531 elseif p_type == 0x2a then -- Sync Control 532 tree:add(f_dev_num, buf(0x21, 1)) 533 local scmd = buf(0x2b, 1):uint() 534 tree:add(f_sync_cmd, buf(0x2b, 1)):append_text(" (" .. (SYNC_COMMANDS[scmd] or "Unknown") .. ")") 535 end 536 elseif dst_port == 50002 or src_port == 50002 then 537 if p_type == 0x05 then -- Media Query 538 tree:add(f_device_name, buf(0x0B, 20)) 539 tree:add(f_dev_num, buf(0x21, 1)) 540 if length >= 0x30 then 541 tree:add(f_queried_ip, buf(0x24, 4)) 542 tree:add(f_queried_dev, buf(0x2B, 1)) -- Target Device: 1 byte at 0x2B 543 tree:add(f_queried_slot, buf(0x2F, 1)) -- Target Slot: 1 byte at 0x2F 544 end 545 elseif p_type == 0x06 then -- Media Response 546 tree:add(f_device_name, buf(0x0B, 20)) 547 tree:add(f_dev_num, buf(0x21, 1)) 548 549 -- The real Pioneer response is 192 bytes (0xC0): 36 bytes header + 156 bytes payload 550 if length >= 0xC0 then 551 local enc = (ENC_UTF_16 or 2) + (ENC_BIG_ENDIAN or 0) 552 tree:add(f_slot_owner, buf(0x27, 1)) -- Target Device ($D_r$) 553 tree:add(f_queried_slot, buf(0x2B, 1)) -- Target Slot ($S_r$): 1 byte at 0x2B 554 555 -- Media Name starts at 0x2C (UTF-16, 64 bytes) 556 local name_str = buf(0x2C, 64):string(enc):gsub("%z", "") 557 tree:add(f_media_name, buf(0x2C, 64), name_str) 558 559 -- DB Creation Date starts at 0x6C (UTF-16, 40 bytes) 560 local date_str = buf(0x6C, 40):string(enc):gsub("%z", "") 561 tree:add(f_media_db_date, buf(0x6C, 40), date_str) 562 563 -- Unknown Text at 0x94–0xA5 (18 bytes) 564 local unknown_str = buf(0x94, 18):string(enc):gsub("%z", "") 565 tree:add(f_raw_payload, buf(0x94, 18)):set_text("Unknown Text: \"" .. unknown_str .. "\"") 566 567 tree:add(f_media_tracks, buf(0xa6, 2)) 568 local c_val = buf(0xa8, 1):uint() 569 local c_str = INTERFACE_COLORS[c_val] or "Unknown" 570 tree:add(f_interface_color, buf(0xa8, 1)):set_text("Interface Color: " .. c_str .. " (" .. c_val .. ")") 571 572 tree:add(f_media_type, buf(0xaa, 1)) -- Media Type $T_r$ at correct offset 573 tree:add(f_settings_present, buf(0xab, 1)) -- Settings presence bit 574 575 tree:add(f_playlist_count, buf(0xae, 2)) 576 tree:add(f_total_media_space, buf(0xb0, 8)) 577 tree:add(f_avail_media_space, buf(0xb8, 8)) 578 elseif length >= 0x2C + 32 then 579 -- Fallback: 80-byte synthetic format from our own NFS server (djlink/src/server.rs). 580 -- This does NOT match real Pioneer hardware; offsets differ from the spec above. 581 local enc = (ENC_ASCII or 0) 582 local name_str = buf(0x2C, 32):string(enc):gsub("%z", "") 583 tree:add(f_media_type, buf(0x26, 1)) 584 tree:add(f_media_status, buf(0x27, 1)) 585 tree:add(f_media_tracks, buf(0x28, 4)) 586 tree:add(f_media_name, buf(0x2C, 32), name_str) 587 end 588 elseif p_type == 0x0a then -- CDJ Status 589 tree:add(f_device_name, buf(0x0B, 20)) 590 tree:add(f_dev_num, buf(0x21, 1)) 591 592 tree:add(f_activity, buf(0x27, 1)) 593 tree:add(f_track_dev, buf(0x28, 1)) 594 tree:add(f_slot, buf(0x29, 1)) 595 local ttype = buf(0x2a, 1):uint() 596 tree:add(f_track_type, buf(0x2a, 1)):append_text(" (" .. (TRACK_TYPES[ttype] or "Unknown") .. ")") 597 tree:add(f_rb_id, buf(0x2c, 4)) 598 tree:add(f_track_num, buf(0x32, 2)) 599 600 local pmode1 = buf(0x7b, 1):uint() 601 tree:add(f_play_mode_1, buf(0x7b, 1)):append_text(" (" .. (PLAY_MODES[pmode1] or "Unknown") .. ")") 602 tree:add(f_firmware, buf(0x7c, 4)) 603 604 local flag_tree = tree:add(f_flags, buf(0x89, 1)) 605 flag_tree:add(f_flag_playing, buf(0x89, 1)) 606 flag_tree:add(f_flag_master, buf(0x89, 1)) 607 flag_tree:add(f_flag_sync, buf(0x89, 1)) 608 flag_tree:add(f_flag_onair, buf(0x89, 1)) 609 flag_tree:add(f_flag_bpm, buf(0x89, 1)) 610 611 local pmode2 = buf(0x8b, 1):uint() 612 tree:add(f_play_mode_2, buf(0x8b, 1)):append_text(" (" .. (PLAY_MODES[pmode2] or "Unknown") .. ")") 613 614 tree:add(f_pitch_1, buf(0x8c, 4)) 615 tree:add(f_bpm_raw, buf(0x92, 2)) 616 tree:add(f_pitch_2, buf(0x98, 4)) 617 tree:add(f_master_info, buf(0x9e, 1)) 618 tree:add(f_handoff, buf(0x9f, 1)) 619 tree:add(f_beat_count, buf(0xa0, 4)) 620 tree:add(f_countdown, buf(0xa4, 2)) 621 tree:add(f_beat_in_bar, buf(0xa6, 1)) 622 tree:add(f_pitch_3, buf(0xc0, 4)) 623 end 624 end 625end 626 627local udp_table = DissectorTable.get("udp.port") 628udp_table:add(50000, p_djl) 629udp_table:add(50001, p_djl) 630udp_table:add(50002, p_djl) 631 632local tcp_table = DissectorTable.get("tcp.port") 633tcp_table:add(12523, p_djl) 634tcp_table:add(1051, p_djl) 635tcp_table:add(1052, p_djl)