Wireshark dissector for Pro DJ Link protocol
3
fork

Configure Feed

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

at main 1253 lines 51 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 6local TRACK_SORT_MODES = { 7 [0x00] = "Default (By Title)", 8 [0x01] = "By Title With Artist", 9 [0x02] = "Artist", 10 [0x03] = "Album", 11 [0x04] = "Genre", 12 [0x05] = "BPM", 13 [0x06] = "Key", 14 [0x07] = "Rating", 15 [0x08] = "Duration", 16 [0x09] = "Color", 17 [0x0a] = "Date Added", 18 [0x0b] = "Label", 19 [0x0c] = "Bit Rate", 20 [0x0d] = "Year", 21 [0x0e] = "Original Artist", 22 [0x0f] = "Remixer", 23 [0x10] = "Comment", 24 [0x11] = "DJ Play Count", 25 [0x12] = "History", 26 [0x13] = "Matching", 27} 28 29local PACKET_TYPES = { 30 [0x00] = "Channel Claim 1", 31 [0x02] = "Channel Claim 2", 32 [0x04] = "Channel Claim Final", 33 [0x06] = "Keep-Alive", 34 [0x0a] = "CDJ Status / Announcement", 35 [0x0b] = "Absolute Position", 36 [0x28] = "Beat", 37 [0x2a] = "Sync Control", 38 [0x29] = "Mixer Status" 39} 40 41local DB_MSG_TYPES = { 42 [0x0000] = "GetQueryContext", 43 [0x0001] = "Termination / End Transaction", 44 [0x0100] = "Disconnect", 45 [0x1000] = "Root Menu", 46 [0x4000] = "Success/Ack", 47 [0x1001] = "Disconnect Acknowledgment", 48 [0x1002] = "Artist Menu", 49 [0x1003] = "Album Menu", 50 [0x1004] = "Track Menu", 51 [0x1006] = "BPM Menu", 52 [0x1007] = "Rating Menu", 53 [0x1008] = "Year Menu", 54 [0x100a] = "Label Menu", 55 [0x100d] = "Color Menu", 56 [0x1010] = "Time Menu", 57 [0x1011] = "Bitrate Menu", 58 [0x1012] = "History Menu", 59 [0x1013] = "Filename Menu", 60 [0x1014] = "Key Menu", 61 [0x2002] = "GetMetadata", 62 [0x3000] = "RenderMenu", 63 [0x4001] = "MenuHeader", 64 [0x4101] = "MenuItem", 65 [0x4201] = "MenuFooter", 66 [0x2003] = "GetArtwork", 67 [0x4002] = "ArtworkResponse", 68 [0x2204] = "GetBeatGrid", 69 [0x4602] = "BeatGridResponse", 70 [0x2104] = "GetCuePoints", 71 [0x4702] = "CuePointsResponse", 72 [0x2b04] = "GetExtCuePoints", 73 [0x4e02] = "ExtCuePointsResponse", 74 [0x2004] = "GetWaveformPreview", 75 [0x4402] = "WaveformPreviewResponse", 76 [0x2102] = "GetTrackStreamingReq", 77 [0x2904] = "GetWaveformDetail", 78 [0x4a02] = "WaveformDetailResponse", 79 [0x2c04] = "GetAnalysisTag", 80 [0x4f02] = "AnalysisTagResponse", 81 [0x3100] = "TrackLoadNotification", 82 [0x3e03] = "GetCapabilities", 83 [0x4b02] = "CapabilitiesResponse", 84 [0x3007] = "SetupBrowseContext", 85 [0x1101] = "ArtistsForGenre", 86 [0x1102] = "AlbumsForArtist", 87 [0x1103] = "GetTrackList", 88 [0x1114] = "DistancesForKey", 89 [0x1214] = "TracksNearKey", 90 [0x1300] = "Search", 91 [0x1301] = "TracksForGenreArtistAlbum", 92 [0x2006] = "Folder", 93 [0x1105] = "GetTrackCount", 94 [0x1106] = "BPM Distances", 95 [0x1206] = "TracksForBpmRange", 96 [0x5105] = "TrackCountResponse", 97 [0x5103] = "TrackListResponse", 98} 99 100local PLAY_MODES = { 101 [0x00] = "No Track", 102 [0x02] = "Loading", 103 [0x03] = "Playing", 104 [0x04] = "Looping", 105 [0x05] = "Paused", 106 [0x06] = "Paused at Cue", 107 [0x07] = "Cue Play", 108 [0x08] = "Cue Scratch", 109 [0x09] = "Searching", 110 [0x0e] = "CD Spun Down", 111 [0x11] = "Ended", 112} 113 114local TRACK_TYPES = { 115 [0x00] = "None", 116 [0x01] = "Rekordbox", 117 [0x02] = "Unanalyzed", 118 [0x05] = "Audio CD", 119} 120 121local SYNC_COMMANDS = { 122 [0x10] = "Turn On Sync", 123 [0x20] = "Turn Off Sync", 124 [0x01] = "Become Master", 125} 126 127local DEV_TYPES = { 128 [0x01] = "Player (CDJ)", 129 [0x02] = "Player (XDJ)", 130 [0x03] = "Mixer", 131 [0x04] = "PC/Rekordbox", 132} 133 134local INTERFACE_COLORS = { 135 [0] = "Default", 136 [1] = "Pink", 137 [2] = "Red", 138 [3] = "Orange", 139 [4] = "Yellow", 140 [5] = "Green", 141 [6] = "Aqua", 142 [7] = "Blue", 143 [8] = "Purple", 144} 145 146local DMST_MENUS = { 147 [0x01] = "Left/Main", 148 [0x02] = "Right/Split", 149 [0x03] = "Metadata Preview", 150 [0x08] = "Waveform/Art/Grid", 151} 152 153local DMST_SLOTS = { 154 [0x00] = "No Track", 155 [0x01] = "CD", 156 [0x02] = "SD", 157 [0x03] = "USB", 158 [0x04] = "Rekordbox", 159 [0x06] = "Streaming (Direct)", 160 [0x07] = "USB 2", 161 [0x09] = "Beatport LINK", 162} 163 164local DMST_TYPES = { 165 [0x01] = "Rekordbox", 166 [0x02] = "Unanalyzed", 167 [0x05] = "Audio CD", 168 [0x06] = "Streaming", 169} 170 171local ANALYSIS_TAGS = { 172 [0x49414d50] = "PMAI (Header)", 173 [0x5a545150] = "PQTZ (Beat Grid)", 174 [0x424f4350] = "PCOB (Cue List)", 175 [0x54504350] = "PCPT (Cue List Entry)", 176 [0x324f4350] = "PCO2 (Extended Cue List)", 177 [0x32504350] = "PCP2 (Extended Cue List Entry)", 178 [0x48545050] = "PPTH (Path)", 179 [0x52425650] = "PVBR (VBR Index)", 180 [0x56415750] = "PWAV (Waveform Preview)", 181 [0x32565750] = "PWV2 (Tiny Waveform Preview)", 182 [0x33565750] = "PWV3 (Waveform Detail)", 183 [0x34565750] = "PWV4 (Color Waveform Preview)", 184 [0x35565750] = "PWV5 (Color Waveform Detail)", 185 [0x36565750] = "PWV6 (3-Band Waveform Preview)", 186 [0x37565750] = "PWV7 (3-Band Waveform Detail)", 187 [0x49535350] = "PSSI (Song Structure)", 188} 189 190local ARG_TAGS = { 191 [0x00] = "none", 192 [0x02] = "str", 193 [0x03] = "blob", 194 [0x06] = "u32", 195} 196 197-- Table to track TCP streams that have been identified as PDJL 198local pdjl_streams = {} 199 200-- Table to track Analysis Tag requests by Transaction ID for showing in responses 201local analysis_requests = {} -- key: stream_id .. ":" .. tx_id, value: tag_name 202 203-- Helper to get a unique key for a bidirectional TCP stream 204local function get_stream_key(pkt) 205 local ip1 = tostring(pkt.net_src) 206 local ip2 = tostring(pkt.net_dst) 207 local p1 = pkt.src_port 208 local p2 = pkt.dst_port 209 if ip1 < ip2 or (ip1 == ip2 and p1 < p2) then 210 return ip1 .. ":" .. p1 .. "-" .. ip2 .. ":" .. p2 211 else 212 return ip2 .. ":" .. p2 .. "-" .. ip1 .. ":" .. p1 213 end 214end 215 216local MENU_ITEM_TYPES = { 217 [0x0001] = "Folder", 218 [0x0002] = "Album Title", 219 [0x0003] = "Disc", 220 [0x0004] = "Track Title", 221 [0x0006] = "Genre", 222 [0x0007] = "Artist", 223 [0x0008] = "Playlist", 224 [0x000a] = "Rating", 225 [0x000b] = "Duration", 226 [0x000d] = "Tempo", 227 [0x000e] = "Label", 228 [0x000f] = "Key", 229 [0x0010] = "Bit Rate", 230 [0x0011] = "Year", 231 [0x0013] = "Color None", 232 [0x0014] = "Color Pink", 233 [0x0015] = "Color Red", 234 [0x0016] = "Color Orange", 235 [0x0017] = "Color Yellow", 236 [0x0018] = "Color Green", 237 [0x0019] = "Color Aqua", 238 [0x001a] = "Color Blue", 239 [0x001b] = "Color Purple", 240 [0x0023] = "Comment", 241 [0x0024] = "History Playlist", 242 [0x0028] = "Original Artist", 243 [0x0029] = "Remixer", 244 [0x002e] = "Date Added", 245 [0x0080] = "Genre menu", 246 [0x0081] = "Artist menu", 247 [0x0082] = "Album menu", 248 [0x0083] = "Track menu", 249 [0x0084] = "Playlist menu", 250 [0x0085] = "Bpm menu", 251 [0x0086] = "Rating menu", 252 [0x0087] = "Year menu", 253 [0x0088] = "Remixer menu", 254 [0x0089] = "Label menu", 255 [0x008a] = "Original Artist menu", 256 [0x008b] = "Key menu", 257 [0x008c] = "Date Added menu", 258 [0x008e] = "Color menu", 259 [0x0090] = "Folder menu", 260 [0x0091] = "Search \"menu\"", 261 [0x0092] = "Time menu", 262 [0x0093] = "Bit Rate menu", 263 [0x0094] = "Filename menu", 264 [0x0095] = "History menu", 265 [0x0098] = "Hot cue bank menu", 266 [0x00a0] = "All", 267 [0x00aa] = "Matching", 268 [0x0204] = "Track Title and Album", 269 [0x0604] = "Track Title and Genre", 270 [0x0704] = "Track Title and Artist", 271 [0x0a04] = "Track Title and Rating", 272 [0x0b04] = "Track Title and Time", 273 [0x0d04] = "Track Title and BPM", 274 [0x0e04] = "Track Title and Label", 275 [0x0f04] = "Track Title and Key", 276 [0x1004] = "Track Title and Bit Rate", 277 [0x1a04] = "Track Title and Color", 278 [0x2304] = "Track Title and Comment", 279 [0x2804] = "Track Title and Original Artist", 280 [0x2904] = "Track Title and Remixer", 281 [0x2a04] = "Track Title and DJ Play Count", 282 [0x2e04] = "Track Title and Date Added", 283} 284 285 286-- ============================================================================ 287-- COMMON FIELDS 288-- ============================================================================ 289local f_header = ProtoField.string("pdjl.header", "Header") 290local f_type = ProtoField.uint8("pdjl.type", "Packet Type", base.HEX) 291local f_device_name = ProtoField.string("pdjl.device_name", "Device Name") 292local f_dev_num = ProtoField.uint8("pdjl.device_number", "Device Number", base.DEC) 293 294-- Port 50000 (Management) 295local f_mac = ProtoField.ether("pdjl.mac", "MAC Address") 296local f_ip = ProtoField.ipv4("pdjl.ip", "IP Address") 297local f_sequence = ProtoField.uint8("pdjl.sequence", "Sequence", base.DEC) 298local f_dev_type = ProtoField.uint8("pdjl.device_type", "Device Type", base.HEX) 299local f_peer_count = ProtoField.uint8("pdjl.peer_count", "Peer Count", base.DEC) 300 301-- Port 50001 (Beat Sync) 302local f_bpm_raw = ProtoField.uint16("pdjl.bpm_raw", "BPM", base.DEC) 303local f_pitch = ProtoField.uint32("pdjl.pitch", "Pitch", base.DEC) 304local f_beat_in_bar = ProtoField.uint8("pdjl.beat_in_bar", "Beat in Bar", base.DEC) 305local f_next_beat = ProtoField.uint32("pdjl.next_beat", "Next Beat (ms)", base.DEC) 306local f_second_beat = ProtoField.uint32("pdjl.second_beat", "Second Beat (ms)", base.DEC) 307local f_next_bar = ProtoField.uint32("pdjl.next_bar", "Next Bar (ms)", base.DEC) 308local f_fourth_beat = ProtoField.uint32("pdjl.fourth_beat", "Fourth Beat (ms)", base.DEC) 309local f_second_bar = ProtoField.uint32("pdjl.second_bar", "Second Bar (ms)", base.DEC) 310local f_eighth_beat = ProtoField.uint32("pdjl.eighth_beat", "Eighth Beat (ms)", base.DEC) 311local f_track_len = ProtoField.uint32("pdjl.track_length", "Track Length (s)", base.DEC) 312local f_playhead = ProtoField.uint32("pdjl.playhead", "Playhead (ms)", base.DEC) 313local f_pitch_abs = ProtoField.int32("pdjl.pitch_abs", "Position Pitch", base.DEC) 314local f_bpm_abs = ProtoField.uint32("pdjl.bpm_abs", "Position BPM", base.DEC) 315local f_sync_cmd = ProtoField.uint8("pdjl.sync_cmd", "Sync Command", base.HEX) 316 317-- Port 50002 (Status) 318local f_rb_id = ProtoField.uint32("pdjl.rekordbox_id", "Rekordbox ID", base.DEC) 319local f_track_num = ProtoField.uint16("pdjl.track_number", "Track Number", base.DEC) 320local f_track_sort = ProtoField.uint8("pdjl.track_sort", "Track Sort State", base.HEX, TRACK_SORT_MODES) 321local f_track_dev = ProtoField.uint8("pdjl.track_device", "Track Host Device", base.DEC) 322local f_slot = ProtoField.uint8("pdjl.slot", "Slot", base.HEX) 323local f_track_type = ProtoField.uint8("pdjl.track_type", "Track Type", base.HEX) 324local f_activity = ProtoField.uint8("pdjl.activity", "Activity", base.HEX) 325local f_firmware = ProtoField.string("pdjl.firmware", "Firmware") 326local f_beat_count = ProtoField.uint32("pdjl.beat_count", "Beat Count", base.DEC) 327local f_play_mode_1 = ProtoField.uint8("pdjl.play_mode_1", "Play Mode 1", base.HEX) 328local f_play_mode_2 = ProtoField.uint8("pdjl.play_mode_2", "Play Mode 2", base.HEX) 329local f_pitch_1 = ProtoField.uint32("pdjl.pitch_1", "Effective Pitch", base.DEC) 330local f_pitch_2 = ProtoField.uint32("pdjl.pitch_2", "Local Pitch", base.DEC) 331local f_master_info = ProtoField.uint8("pdjl.master_info", "Master Info", base.HEX) 332local f_handoff = ProtoField.uint8("pdjl.handoff", "Master Handoff", base.DEC) 333local f_countdown = ProtoField.uint16("pdjl.cue_countdown", "Cue Countdown", base.DEC) 334local f_pitch_3 = ProtoField.uint32("pdjl.pitch_3", "Pitch 3", base.DEC) 335 336local f_querying_ip = ProtoField.ipv4("pdjl.queried_ip", "Querying IP") 337local f_queried_dev = ProtoField.uint8("pdjl.queried_device", "Target Device", base.DEC) 338local f_queried_slot = ProtoField.uint8("pdjl.queried_slot", "Target Slot", base.HEX) 339local f_slot_owner = ProtoField.uint8("pdjl.slot_owner", "Slot Owner Device", base.DEC) 340local f_playlist_count = ProtoField.uint16("pdjl.playlist_count", "Playlist Count", base.DEC) 341local f_total_media_space = ProtoField.uint64("pdjl.total_media_space", "Total Space", base.DEC) 342local f_avail_media_space = ProtoField.uint64("pdjl.avail_media_space", "Available Space", base.DEC) 343local f_interface_color = ProtoField.uint8("pdjl.interface_color", "Interface Color", base.DEC, INTERFACE_COLORS) 344local f_settings_present = ProtoField.bool("pdjl.settings_present", "MYSETTINGS.DAT present", 1, nil, 0x01) 345 346 347local f_media_type = ProtoField.uint8("pdjl.media_type", "Media Type", base.HEX) 348local f_media_status = ProtoField.uint8("pdjl.media_status", "Media Status", base.HEX) 349local f_media_tracks = ProtoField.uint16("pdjl.media_tracks", "Media Tracks", base.DEC) 350local f_media_name = ProtoField.string("pdjl.media_name", "Media Name") 351local f_media_db_date= ProtoField.string("pdjl.media_db_date", "DB Creation Date") 352 353local f_flags = ProtoField.uint8("pdjl.flags", "Status Flags", base.HEX) 354local f_flag_playing= ProtoField.bool("pdjl.flags.playing", "Is Playing", 8, nil, 0x40) 355local f_flag_master = ProtoField.bool("pdjl.flags.master", "Is Master", 8, nil, 0x20) 356local f_flag_sync = ProtoField.bool("pdjl.flags.sync", "Is Syncing", 8, nil, 0x10) 357local f_flag_onair = ProtoField.bool("pdjl.flags.onair", "Is On Air", 8, nil, 0x08) 358local f_flag_bpm = ProtoField.bool("pdjl.flags.bpm", "BPM Sync Active", 8, nil, 0x02) 359 360-- TCP DB Server 361local f_db_magic = ProtoField.uint32("pdjl.db.magic", "DB Magic", base.HEX) 362local f_db_tx_id = ProtoField.uint32("pdjl.db.tx_id", "Transaction ID", base.HEX) 363local f_db_msg_type = ProtoField.uint16("pdjl.db.msg_type", "DB Message Type", base.HEX) 364local f_db_arg_count= ProtoField.uint8("pdjl.db.arg_count", "Argument Count", base.DEC) 365local f_db_tag_blob = ProtoField.bytes("pdjl.db.tags", "Argument Tags") 366 367local f_db_field_u8 = ProtoField.uint8("pdjl.db.field.u8", "Field (U8)", base.DEC) 368local f_db_field_u16= ProtoField.uint16("pdjl.db.field.u16", "Field (U16)", base.DEC) 369local f_db_field_u32= ProtoField.uint32("pdjl.db.field.u32", "Field (U32)", base.HEX) 370local f_db_field_u64= ProtoField.uint64("pdjl.db.field.u64", "Field (U64)", base.DEC) 371local f_db_field_str= ProtoField.string("pdjl.db.field.str", "Field (String)") 372local f_db_field_bin= ProtoField.bytes("pdjl.db.field.bin", "Field (Blob)") 373 374-- DMST decoding 375local f_dmst = ProtoField.uint32("pdjl.dmst", "DMST Parameter", base.HEX) 376local f_dmst_dev = ProtoField.uint8("pdjl.dmst.device", "Device", base.DEC) 377local f_dmst_menu = ProtoField.uint8("pdjl.dmst.menu", "Menu", base.HEX, DMST_MENUS) 378local f_dmst_slot = ProtoField.uint8("pdjl.dmst.slot", "Slot", base.HEX, DMST_SLOTS) 379local f_dmst_type = ProtoField.uint8("pdjl.dmst.type", "Type", base.HEX, DMST_TYPES) 380 381local f_db_port = ProtoField.uint16("pdjl.db_port", "DB Server Port", base.DEC) 382local f_raw_payload = ProtoField.bytes("pdjl.raw_payload", "Raw Payload") 383 384p_djl.fields = { 385 f_header, f_type, f_device_name, f_dev_num, 386 f_mac, f_ip, f_sequence, f_dev_type, f_peer_count, 387 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, 388 f_track_len, f_playhead, f_pitch_abs, f_bpm_abs, f_sync_cmd, 389 f_rb_id, f_track_num, f_track_sort, f_track_dev, f_slot, f_track_type, f_activity, f_firmware, f_beat_count, 390 f_play_mode_1, f_play_mode_2, f_pitch_1, f_pitch_2, f_master_info, f_handoff, f_countdown, f_pitch_3, 391 f_querying_ip, f_queried_dev, f_queried_slot, f_slot_owner, f_playlist_count, f_total_media_space, f_avail_media_space, 392 f_interface_color, f_settings_present, f_media_type, f_media_status, f_media_tracks, f_media_name, f_media_db_date, 393 f_flags, f_flag_playing, f_flag_master, f_flag_sync, f_flag_onair, f_flag_bpm, 394 f_db_magic, f_db_tx_id, f_db_msg_type, f_db_arg_count, f_db_tag_blob, 395 f_db_field_u8, f_db_field_u16, f_db_field_u32, f_db_field_u64, f_db_field_str, f_db_field_bin, 396 f_dmst, f_dmst_dev, f_dmst_menu, f_dmst_slot, f_dmst_type, 397 f_db_port, f_raw_payload 398} 399 400-- ============================================================================ 401-- HELPERS 402-- ============================================================================ 403 404-- Decodes a 4-byte buffer as a reverse-ASCII string 405local function get_reverse_ascii(buf) 406 local b = buf:bytes() 407 local s = "" 408 for j = 3, 0, -1 do 409 local c = b:get_index(j) 410 if c ~= 0 then s = s .. string.char(c) end 411 end 412 return s 413end 414 415-- Label for specific arguments based on message type 416function get_arg_label(msg_type, arg_idx, val) 417 -- Many request types have DMST as the first argument 418 if arg_idx == 1 and ( 419 msg_type == 0x1000 or -- Root Menu 420 msg_type == 0x1001 or -- Genre Menu 421 msg_type == 0x1002 or -- Artist Menu 422 msg_type == 0x1003 or -- Album Menu 423 msg_type == 0x1004 or -- Track Menu 424 msg_type == 0x1006 or -- BPM Menu 425 msg_type == 0x1007 or -- Rating Menu 426 msg_type == 0x1008 or -- Year Menu 427 msg_type == 0x100a or -- Label Menu 428 msg_type == 0x100d or -- Color Menu 429 msg_type == 0x1010 or -- Time Menu 430 msg_type == 0x1011 or -- Bitrate Menu 431 msg_type == 0x1012 or -- History Menu 432 msg_type == 0x1013 or -- Filename Menu 433 msg_type == 0x1014 or -- Key Menu 434 msg_type == 0x1101 or -- ArtistsForGenre 435 msg_type == 0x1102 or -- AlbumsForArtist 436 msg_type == 0x1103 or -- GetTrackList 437 msg_type == 0x1105 or -- GetTrackCount 438 msg_type == 0x1106 or -- BPM Distances 439 msg_type == 0x1114 or -- DistancesForKey 440 msg_type == 0x1206 or -- TracksForBpmRange 441 msg_type == 0x1214 or -- TracksNearKey 442 msg_type == 0x1300 or -- Search 443 msg_type == 0x1301 or -- TracksForGenreArtistAlbum 444 msg_type == 0x2002 or -- GetMetadata 445 msg_type == 0x2003 or -- GetArtwork 446 msg_type == 0x2004 or -- GetWaveformPreview 447 msg_type == 0x2102 or -- GetTrackStreamingReq 448 msg_type == 0x2104 or -- GetCuePoints 449 msg_type == 0x2204 or -- GetBeatGrid 450 msg_type == 0x2904 or -- GetWaveformDetail 451 msg_type == 0x2b04 or -- GetExtCuePoints 452 msg_type == 0x2c04 or -- GetAnalysisTag 453 msg_type == 0x3000 or -- RenderMenu 454 msg_type == 0x3007 or -- SetupBrowseContext 455 msg_type == 0x3100 or -- TrackLoadNotification 456 msg_type == 0x3e03 -- GetCapabilities 457 ) then 458 return "DMST" 459 end 460 461 if msg_type == 0x1000 then -- Root Menu 462 local labels = { 463 [1] = "DMST", 464 [2] = "Sort", 465 [3] = "Unknown (0x00ffffff)" 466 } 467 return labels[arg_idx] 468 elseif msg_type == 0x3100 then -- TrackLoadNotification 469 local labels = { 470 [1] = "DMST", 471 [2] = "Rekordbox ID", 472 [3] = "Unknown 1", 473 [4] = "Unknown 2" 474 } 475 return labels[arg_idx] 476 elseif msg_type == 0x2c04 then -- GetAnalysisTag 477 local labels = { 478 [1] = "DMST", 479 [2] = "Rekordbox ID", 480 [3] = "Tag", 481 [4] = "Extension" 482 } 483 return labels[arg_idx] 484 elseif msg_type == 0x2102 then -- GetTrackStreamingReq 485 local labels = { 486 [1] = "DMST", 487 [2] = "Rekordbox ID" 488 } 489 return labels[arg_idx] 490 elseif msg_type == 0x4101 then -- MenuItem 491 local labels = { 492 [1] = "Parent ID", 493 [2] = "Main ID", 494 [3] = "Label 1 Len", 495 [4] = "Label 1", 496 [5] = "Label 2 Len", 497 [6] = "Label 2", 498 [7] = "Item Type", 499 [8] = "Flags", 500 [9] = "Artwork ID", 501 [10] = "Position", 502 [11] = "Unknown", 503 [12] = "Unknown" 504 } 505 return labels[arg_idx] 506 elseif msg_type == 0x4201 then -- MenuFooter 507 return arg_idx == 1 and "Status" or (arg_idx == 2 and "Total Items" or nil) 508 elseif msg_type == 0x4001 then -- MenuHeader 509 return arg_idx == 1 and "Item Count" or nil 510 elseif msg_type == 0x4000 then -- Success/Ack 511 return arg_idx == 1 and "Request Type Echo" or (arg_idx == 2 and "Items Found" or nil) 512 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 513 if arg_idx == 1 then return "Request Type Echo" end 514 if arg_idx == 2 then return "Status/Echo" end 515 if arg_idx == 3 then return "Length" end 516 if arg_idx == 4 then 517 local labels = { 518 [0x4002] = "Blob (image)", 519 [0x4602] = "Beat Grid Data", 520 [0x4702] = "Cue Points Data", 521 [0x4e02] = "Extended Cue Points Data", 522 [0x4402] = "Waveform Preview Data", 523 [0x4a02] = "Waveform Detail Data", 524 [0x4f02] = "Analysis Tag Data", 525 } 526 return labels[msg_type] or "Blob Data" 527 end 528 return nil 529 elseif msg_type == 0x3000 then -- RenderMenu 530 local labels = { 531 [1] = "DMST", 532 [2] = "Offset", 533 [3] = "Limit", 534 [4] = "Unknown", 535 [5] = "Total Items", 536 [6] = "Unknown" 537 } 538 return labels[arg_idx] 539 elseif msg_type == 0x0000 then -- Setup 540 return arg_idx == 1 and "Our Device Number" or nil 541 elseif msg_type == 0x4b02 then -- CapabilitiesResp 542 local labels = { 543 [1] = "Request Type Echo", 544 [2] = "Reserved", 545 [3] = "Capability Level", 546 [4] = "Placeholder (Empty String)" 547 } 548 return labels[arg_idx] 549 end 550 return nil 551end 552 553-- ============================================================================ 554-- DB TAGGED FIELD DISSECTOR 555-- ============================================================================ 556function dissect_db_field(buf, offset, tree, msg_type, arg_idx) 557 local len = buf:len() 558 if len - offset < 1 then return offset, nil, nil end 559 local tag = buf(offset, 1):uint() 560 local bytes_consumed = 1 561 local val = nil 562 local item = nil 563 564 local prefix 565 local label 566 if type(arg_idx) == "string" then 567 prefix = arg_idx 568 else 569 label = get_arg_label(msg_type, arg_idx) 570 prefix = arg_idx and string.format("Arg %d", arg_idx) or "Field" 571 if label then prefix = prefix .. " (" .. label .. ")" end 572 end 573 574 if tag == 0x0f then -- U8 575 if len - offset >= 2 then 576 val = buf(offset+1, 1):uint() 577 item = tree:add(f_db_field_u8, buf(offset+1, 1)) 578 item:set_text(prefix .. ": " .. val) 579 bytes_consumed = 2 580 end 581 elseif tag == 0x10 then -- U16 582 if len - offset >= 3 then 583 val = buf(offset+1, 2):uint() 584 item = tree:add(f_db_field_u16, buf(offset+1, 2)) 585 if prefix == "Message Type" then 586 local m_type_str = DB_MSG_TYPES[val] or string.format("Unknown(0x%04x)", val) 587 item:set_text(prefix .. ": " .. m_type_str .. " (0x" .. string.format("%04x", val) .. ")") 588 else 589 item:set_text(prefix .. ": " .. val .. " (0x" .. string.format("%04x", val) .. ")") 590 end 591 bytes_consumed = 3 592 end 593 elseif tag == 0x11 then -- U32 594 if len - offset >= 5 then 595 val = buf(offset+1, 4):uint() 596 597 -- Special handling for DMST to keep it as one expandable layer 598 if label == "DMST" then 599 local dmst_tree = tree:add(f_dmst, buf(offset+1, 4)) 600 dmst_tree:set_text(prefix .. ": " .. string.format("0x%08x", val)) 601 dmst_tree:add(f_dmst_dev, buf(offset+1, 1)) 602 dmst_tree:add(f_dmst_menu, buf(offset+2, 1)) 603 dmst_tree:add(f_dmst_slot, buf(offset+3, 1)) 604 dmst_tree:add(f_dmst_type, buf(offset+4, 1)) 605 item = dmst_tree 606 else 607 item = tree:add(f_db_field_u32, buf(offset+1, 4)) 608 if label == "Transaction ID" and val == 0xfffffffe then 609 item:set_text(prefix .. ": Setup/Teardown (0xfffffffe)") 610 elseif label == "Item Type" and MENU_ITEM_TYPES[val] then 611 item:set_text(prefix .. ": " .. MENU_ITEM_TYPES[val] .. " (0x" .. string.format("%04x", val) .. ")") 612 elseif label == "Tag" then 613 local tag_str = ANALYSIS_TAGS[val] or get_reverse_ascii(buf(offset+1, 4)) 614 item:set_text(prefix .. ": " .. tag_str .. " (0x" .. string.format("%08x", val) .. ")") 615 elseif label == "Extension" then 616 item:set_text(prefix .. ": \"" .. get_reverse_ascii(buf(offset+1, 4)) .. "\" (0x" .. string.format("%08x", val) .. ")") 617 else 618 item:set_text(prefix .. ": " .. val .. " (0x" .. string.format("%08x", val) .. ")") 619 end 620 end 621 bytes_consumed = 5 622 end 623 elseif tag == 0x14 then -- Blob 624 if len - offset >= 5 then 625 local blob_len = buf(offset+1, 4):uint() 626 local available = len - offset - 5 627 local to_read = math.min(blob_len, available) 628 item = tree:add(f_db_field_bin, buf(offset+5, to_read)) 629 if to_read < blob_len then 630 item:set_text(prefix .. ": [Blob Truncated " .. to_read .. "/" .. blob_len .. " bytes]") 631 else 632 item:set_text(prefix .. ": [Blob " .. blob_len .. " bytes]") 633 end 634 bytes_consumed = 5 + to_read 635 val = buf(offset+5, to_read):bytes() 636 else 637 bytes_consumed = len - offset 638 end 639 elseif tag == 0x26 then -- String (UTF-16BE) 640 if len - offset >= 5 then 641 local chars = buf(offset+1, 4):uint() 642 local byte_len = chars * 2 643 if len - offset >= 5 + byte_len then 644 -- Use Wireshark's native UTF-16BE decoding 645 local enc = (ENC_UTF_16 or 2) + (ENC_BIG_ENDIAN or 0) 646 local str = buf(offset+5, byte_len):string(enc) 647 -- Strip trailing nulls to clean up UI display 648 str = str:gsub("%z", "") 649 650 -- Use f_db_field_bin for the highlight range to avoid "trailing stray chars" warning 651 -- caused by Wireshark's internal string dissector seeing high-byte nulls. 652 item = tree:add(f_db_field_bin, buf(offset+5, byte_len)) 653 item:set_text(prefix .. ": \"" .. str .. "\"") 654 bytes_consumed = 5 + byte_len 655 val = str 656 else 657 bytes_consumed = len - offset 658 end 659 end 660 end 661 662 return offset + bytes_consumed, val, item 663end 664 665-- ============================================================================ 666-- DB SERVER DISSECTION LOGIC 667-- ============================================================================ 668 669-- Helper to calculate the full byte length of a DB message starting at `offset`. 670-- Returns: 671-- > 0: The total byte length of the complete message. 672-- < 0: The number of missing bytes needed to complete the message (returns -missing). 673-- 0: Not a valid DB message or parse error. 674local function get_db_msg_len(buf, offset) 675 local len = buf:len() 676 local available = len - offset 677 678 if available < 5 then return -(5 - available) end 679 680 local tag = buf(offset, 1):uint() 681 local magic = buf(offset+1, 4):uint() 682 683 if tag == 0x11 and magic == 0x00000001 then 684 return 5 685 end 686 687 if tag ~= 0x11 or magic ~= 0x872349ae then 688 return 0 689 end 690 691 local cur = offset + 5 692 693 local function read_field() 694 local a = len - cur 695 if a < 1 then return false, (1 - a) end 696 local ftag = buf(cur, 1):uint() 697 if ftag == 0x0f then 698 if a < 2 then return false, (2 - a) end 699 local val = buf(cur+1, 1):uint() 700 cur = cur + 2 701 return true, val 702 elseif ftag == 0x10 then 703 if a < 3 then return false, (3 - a) end 704 local val = buf(cur+1, 2):uint() 705 cur = cur + 3 706 return true, val 707 elseif ftag == 0x11 then 708 if a < 5 then return false, (5 - a) end 709 local val = buf(cur+1, 4):uint() 710 cur = cur + 5 711 return true, val 712 elseif ftag == 0x14 then 713 if a < 5 then return false, (5 - a) end 714 local blen = buf(cur+1, 4):uint() 715 if a < 5 + blen then return false, (5 + blen - a) end 716 cur = cur + 5 + blen 717 return true, nil 718 elseif ftag == 0x26 then 719 if a < 5 then return false, (5 - a) end 720 local chars = buf(cur+1, 4):uint() 721 local blen = chars * 2 722 if a < 5 + blen then return false, (5 + blen - a) end 723 cur = cur + 5 + blen 724 return true, nil 725 else 726 return false, nil 727 end 728 end 729 730 local ok, val = read_field() -- Tx ID 731 if not ok then return val and -val or 0 end 732 733 ok, val = read_field() -- Msg Type 734 if not ok then return val and -val or 0 end 735 736 ok, val = read_field() -- Arg Count 737 if not ok then return val and -val or 0 end 738 local arg_count = val 739 740 local a = len - cur 741 if a < 1 then return -(1 - a) end 742 if buf(cur, 1):uint() == 0x14 then 743 ok, val = read_field() 744 if not ok then return val and -val or 0 end 745 end 746 747 for i = 1, arg_count do 748 ok, val = read_field() 749 if not ok then return val and -val or 0 end 750 end 751 752 return cur - offset 753end 754 755local function dissect_db_tcp(buf, pkt, root, length) 756 local off = 0 757 local found_db = false 758 local db_types = {} 759 local tx_ids = {} 760 761 local src_port = pkt.src_port 762 local dst_port = pkt.dst_port 763 local stream_id = get_stream_key(pkt) 764 765 while off < length do 766 local available = length - off 767 if available < 5 then 768 pkt.desegment_offset = off 769 pkt.desegment_len = 5 - available 770 return true 771 end 772 773 local tag = buf(off, 1):uint() 774 local magic_or_val = buf(off+1, 4):uint() 775 776 if tag == 0x11 and (magic_or_val == 0x872349ae or magic_or_val == 0x00000001) then 777 pdjl_streams[stream_id] = true 778 779 local msg_len = get_db_msg_len(buf, off) 780 if msg_len < 0 then 781 pkt.desegment_offset = off 782 pkt.desegment_len = -msg_len 783 return true 784 elseif msg_len == 0 then 785 break -- parse error, stop processing 786 end 787 788 if magic_or_val == 0x872349ae then 789 if not found_db then 790 pkt.cols.protocol = "Pro DJ Link (DB)" 791 found_db = true 792 end 793 794 local msg_tree = root:add(p_djl, buf(off, msg_len)) 795 msg_tree:add(f_db_magic, buf(off+1, 4)) 796 797 local current = off + 5 798 799 -- Transaction ID 800 local tx_id = 0 801 if msg_len - (current - off) >= 5 then 802 local bytes_read, val = dissect_db_field(buf, current, msg_tree, nil, "Transaction ID") 803 tx_id = val or 0 804 local tx_str = (tx_id == 0xfffffffe) and "Setup/Teardown" or string.format("0x%08x", tx_id) 805 806 local already_added = false 807 for _, v in ipairs(tx_ids) do 808 if v == tx_str then already_added = true break end 809 end 810 if not already_added then 811 table.insert(tx_ids, tx_str) 812 end 813 814 current = bytes_read 815 end 816 817 -- Message Type 818 local m_type = 0 819 if msg_len - (current - off) >= 3 then 820 local bytes_read, val, m_type_item = dissect_db_field(buf, current, msg_tree, nil, "Message Type") 821 m_type = val or 0 822 local m_type_str = DB_MSG_TYPES[m_type] or string.format("Unknown(0x%04x)", m_type) 823 824 if m_type == 0x0100 and tx_id == 0xfffffffe then 825 m_type_str = "Device Disconnect" 826 if m_type_item then 827 m_type_item:set_text("Message Type: " .. m_type_str .. " (0x" .. string.format("%04x", m_type) .. ")") 828 end 829 end 830 831 table.insert(db_types, m_type_str) 832 833 current = bytes_read 834 msg_tree:append_text(": " .. m_type_str) 835 end 836 837 -- Arg Count 838 local arg_count = 0 839 if msg_len - (current - off) >= 2 then 840 local bytes_read, val = dissect_db_field(buf, current, msg_tree, nil, "Arg Count") 841 arg_count = val or 0 842 current = bytes_read 843 end 844 845 -- Arg Tag Blob (usually 12 bytes) 846 if msg_len - (current - off) >= 5 and buf(current, 1):uint() == 0x14 then 847 local tag_len = buf(current+1, 4):uint() 848 if msg_len - (current - off) >= 5 + tag_len then 849 local tag_item = msg_tree:add(f_db_tag_blob, buf(current+5, tag_len)) 850 local arg_types = {} 851 for i = 0, tag_len - 1 do 852 local t = buf(current+5+i, 1):uint() 853 if t ~= 0 then 854 table.insert(arg_types, ARG_TAGS[t] or string.format("0x%02x", t)) 855 end 856 end 857 if #arg_types > 0 then 858 tag_item:set_text("Arg Types: " .. table.concat(arg_types, ", ")) 859 end 860 current = current + 5 + tag_len 861 end 862 end 863 864 -- Arguments 865 for i = 1, arg_count do 866 if current >= off + msg_len then break end 867 local prev_current = current 868 local bytes_read, val = dissect_db_field(buf, current, msg_tree, m_type, i) 869 870 -- If it's a Success/Ack, show what it's acking in the summary 871 if m_type == 0x4000 then 872 if i == 1 and val then 873 local ack_type_str = DB_MSG_TYPES[val] or string.format("0x%04x", val) 874 msg_tree:append_text(" - " .. ack_type_str) 875 db_types[#db_types] = "Ack " .. ack_type_str 876 elseif i == 2 and val then 877 msg_tree:append_text(": " .. val .. " Items") 878 db_types[#db_types] = db_types[#db_types] .. ": " .. val .. " Items" 879 end 880 end 881 882 -- If it's a GetAnalysisTag request, record the tag for the response 883 if m_type == 0x2c04 and i == 3 and val then 884 local tag_name = ANALYSIS_TAGS[val] or get_reverse_ascii(buf(prev_current+1, 4)) 885 analysis_requests[stream_id .. ":" .. tx_id] = tag_name 886 msg_tree:append_text(" - " .. tag_name) 887 -- Update the last entry in db_types to include the tag 888 db_types[#db_types] = db_types[#db_types] .. " - " .. tag_name 889 end 890 891 -- If it's a TrackLoadNotification, show the Rekordbox ID in the summary 892 if m_type == 0x3100 and i == 2 and val then 893 msg_tree:append_text(" - RB ID: " .. val) 894 db_types[#db_types] = db_types[#db_types] .. " - RB ID: " .. val 895 end 896 897 -- If it's an AnalysisTagResponse, try to find the tag from the request 898 if m_type == 0x4f02 and i == 1 then 899 local tag_name = analysis_requests[stream_id .. ":" .. tx_id] 900 if tag_name then 901 msg_tree:append_text(" - " .. tag_name) 902 db_types[#db_types] = db_types[#db_types] .. " - " .. tag_name 903 end 904 end 905 906 current = bytes_read 907 if current <= prev_current then break end 908 end 909 910 -- Special handling for trailing binary data in blob responses 911 if (m_type == 0x4002 or m_type == 0x4602 or m_type == 0x4702 or m_type == 0x4e02 or m_type == 0x4a02 or m_type == 0x4402 or m_type == 0x4f02) and current < off + msg_len then 912 local label = get_arg_label(m_type, 4) or "Blob Data" 913 local remaining = (off + msg_len) - current 914 msg_tree:add(f_db_field_bin, buf(current, remaining)):set_text(label .. ": [" .. remaining .. " bytes]") 915 current = current + remaining 916 end 917 918 msg_tree:set_len(msg_len) 919 off = off + msg_len 920 elseif magic_or_val == 0x00000001 then 921 if not found_db then 922 pkt.cols.protocol = "Pro DJ Link (DB)" 923 found_db = true 924 end 925 table.insert(db_types, "Greeting") 926 root:add(p_djl, buf(off, 5)):set_text("Greeting (0x00000001)") 927 off = off + 5 928 end 929 else 930 break 931 end 932 end 933 934 if found_db then 935 -- Group consecutive message types for a concise summary (e.g., "MenuItem x 10") 936 local summarized_types = {} 937 local current_type = nil 938 local current_count = 0 939 940 for _, t in ipairs(db_types) do 941 if t == current_type then 942 current_count = current_count + 1 943 else 944 if current_type then 945 local entry = (current_count > 1) and (current_type .. " x " .. current_count) or current_type 946 table.insert(summarized_types, entry) 947 end 948 current_type = t 949 current_count = 1 950 end 951 end 952 -- Final entry 953 if current_type then 954 local entry = (current_count > 1) and (current_type .. " x " .. current_count) or current_type 955 table.insert(summarized_types, entry) 956 end 957 958 local info_str = table.concat(summarized_types, ", ") 959 -- Only append the first transaction ID found in the packet 960 if #tx_ids > 0 then 961 info_str = info_str .. " [tx: " .. tx_ids[1] .. "]" 962 end 963 pkt.cols.info:set(info_str) 964 pkt.cols.info:fence() 965 return true 966 end 967 return false 968end 969 970-- ============================================================================ 971-- HELPERS - PORT 50000/50001/50002 972-- ============================================================================ 973 974local function dissect_management_port(buf, pkt, tree, p_type) 975 tree:add(f_device_name, buf(0x0C, 20)) 976 if p_type == 0x06 then -- Keep-Alive 977 tree:add(f_dev_num, buf(0x24, 1)) 978 local dtype = buf(0x25, 1):uint() 979 tree:add(f_dev_type, buf(0x25, 1)):append_text(" (" .. (DEV_TYPES[dtype] or "Unknown") .. ")") 980 tree:add(f_mac, buf(0x26, 6)) 981 tree:add(f_ip, buf(0x2C, 4)) 982 tree:add(f_peer_count, buf(0x30, 1)) 983 elseif p_type == 0x0a then -- Announcement 984 local dtype = buf(0x24, 1):uint() 985 tree:add(f_dev_type, buf(0x24, 1)):append_text(" (" .. (DEV_TYPES[dtype] or "Unknown") .. ")") 986 elseif p_type == 0x00 then -- Claim Stage 1 987 tree:add(f_sequence, buf(0x24, 1)) 988 local dtype = buf(0x25, 1):uint() 989 tree:add(f_dev_type, buf(0x25, 1)):append_text(" (" .. (DEV_TYPES[dtype] or "Unknown") .. ")") 990 tree:add(f_mac, buf(0x26, 6)) 991 elseif p_type == 0x02 then -- Claim Stage 2 992 tree:add(f_ip, buf(0x24, 4)) 993 tree:add(f_mac, buf(0x28, 6)) 994 tree:add(f_dev_num, buf(0x2E, 1)) 995 tree:add(f_sequence, buf(0x2F, 1)) 996 elseif p_type == 0x04 then -- Claim Final 997 tree:add(f_dev_num, buf(0x24, 1)) 998 tree:add(f_sequence, buf(0x25, 1)) 999 end 1000end 1001 1002local function dissect_beat_sync_port(buf, pkt, tree, p_type) 1003 if p_type == 0x28 then -- Beat 1004 tree:add(f_device_name, buf(0x0B, 20)) 1005 tree:add(f_dev_num, buf(0x21, 1)) 1006 tree:add(f_next_beat, buf(0x24, 4)) 1007 tree:add(f_second_beat, buf(0x28, 4)) 1008 tree:add(f_next_bar, buf(0x2C, 4)) 1009 tree:add(f_fourth_beat, buf(0x30, 4)) 1010 tree:add(f_second_bar, buf(0x34, 4)) 1011 tree:add(f_eighth_beat, buf(0x38, 4)) 1012 1013 local raw_pitch = buf(0x54, 4):uint() 1014 local pitch_pct = (raw_pitch / 0x100000 - 1.0) * 100 1015 tree:add(f_pitch, buf(0x54, 4)):set_text(string.format("Pitch: %+.2f%%", pitch_pct)) 1016 1017 local bpm_val = buf(0x5a, 2):uint() 1018 tree:add(f_bpm_raw, buf(0x5a, 2)):set_text(string.format("BPM: %.2f", bpm_val / 100)) 1019 tree:add(f_beat_in_bar, buf(0x5c, 1)) 1020 elseif p_type == 0x0b then -- Absolute Position 1021 tree:add(f_dev_num, buf(0x21, 1)) 1022 tree:add(f_track_len, buf(0x24, 4)) 1023 tree:add(f_playhead, buf(0x28, 4)) 1024 tree:add(f_pitch_abs, buf(0x2C, 4)) 1025 tree:add(f_bpm_abs, buf(0x38, 4)) 1026 elseif p_type == 0x2a then -- Sync Control 1027 tree:add(f_dev_num, buf(0x21, 1)) 1028 local scmd = buf(0x2b, 1):uint() 1029 tree:add(f_sync_cmd, buf(0x2b, 1)):append_text(" (" .. (SYNC_COMMANDS[scmd] or "Unknown") .. ")") 1030 end 1031end 1032 1033local function dissect_status_media_port(buf, pkt, tree, p_type, length) 1034 if p_type == 0x05 then -- Media Query 1035 tree:add(f_device_name, buf(0x0B, 20)) 1036 tree:add(f_dev_num, buf(0x21, 1)) 1037 if length >= 0x30 then 1038 tree:add(f_querying_ip, buf(0x24, 4)) 1039 tree:add(f_queried_dev, buf(0x2B, 1)) -- Target Device: 1 byte at 0x2B 1040 tree:add(f_queried_slot, buf(0x2F, 1)) -- Target Slot: 1 byte at 0x2F 1041 end 1042 elseif p_type == 0x06 then -- Media Response 1043 tree:add(f_device_name, buf(0x0B, 20)) 1044 tree:add(f_dev_num, buf(0x21, 1)) 1045 1046 -- The real Pioneer response is 192 bytes (0xC0): 36 bytes header + 156 bytes payload 1047 if length >= 0xC0 then 1048 local enc = (ENC_UTF_16 or 2) + (ENC_BIG_ENDIAN or 0) 1049 tree:add(f_slot_owner, buf(0x27, 1)) -- Target Device ($D_r$) 1050 tree:add(f_queried_slot, buf(0x2B, 1)) -- Target Slot ($S_r$): 1 byte at 0x2B 1051 1052 -- Media Name starts at 0x2C (UTF-16, 64 bytes) 1053 local name_str = buf(0x2C, 64):string(enc):gsub("%z", "") 1054 tree:add(f_media_name, buf(0x2C, 64), name_str) 1055 1056 -- DB Creation Date starts at 0x6C (UTF-16, 40 bytes) 1057 local date_str = buf(0x6C, 40):string(enc):gsub("%z", "") 1058 tree:add(f_media_db_date, buf(0x6C, 40), date_str) 1059 1060 -- Unknown Text at 0x94–0xA5 (18 bytes) 1061 local unknown_str = buf(0x94, 18):string(enc):gsub("%z", "") 1062 tree:add(f_raw_payload, buf(0x94, 18)):set_text("Unknown Text: \"" .. unknown_str .. "\"") 1063 1064 tree:add(f_media_tracks, buf(0xa6, 2)) 1065 local c_val = buf(0xa8, 1):uint() 1066 local c_str = INTERFACE_COLORS[c_val] or "Unknown" 1067 tree:add(f_interface_color, buf(0xa8, 1)):set_text("Interface Color: " .. c_str .. " (" .. c_val .. ")") 1068 1069 tree:add(f_media_type, buf(0xaa, 1)) -- Media Type $T_r$ at correct offset 1070 tree:add(f_settings_present, buf(0xab, 1)) -- Settings presence bit 1071 1072 tree:add(f_playlist_count, buf(0xae, 2)) 1073 tree:add(f_total_media_space, buf(0xb0, 8)) 1074 tree:add(f_avail_media_space, buf(0xb8, 8)) 1075 elseif length >= 0x2C + 32 then 1076 -- Fallback: 80-byte synthetic format from our own NFS server (djlink/src/server.rs). 1077 -- This does NOT match real Pioneer hardware; offsets differ from the spec above. 1078 local enc = (ENC_ASCII or 0) 1079 local name_str = buf(0x2C, 32):string(enc):gsub("%z", "") 1080 tree:add(f_media_type, buf(0x26, 1)) 1081 tree:add(f_media_status, buf(0x27, 1)) 1082 tree:add(f_media_tracks, buf(0x28, 4)) 1083 tree:add(f_media_name, buf(0x2C, 32), name_str) 1084 end 1085 elseif p_type == 0x0a then -- CDJ Status 1086 tree:add(f_device_name, buf(0x0B, 20)) 1087 tree:add(f_dev_num, buf(0x21, 1)) 1088 1089 tree:add(f_activity, buf(0x27, 1)) 1090 tree:add(f_track_dev, buf(0x28, 1)) 1091 tree:add(f_slot, buf(0x29, 1)) 1092 local ttype = buf(0x2a, 1):uint() 1093 tree:add(f_track_type, buf(0x2a, 1)):append_text(" (" .. (TRACK_TYPES[ttype] or "Unknown") .. ")") 1094 tree:add(f_rb_id, buf(0x2c, 4)) 1095 tree:add(f_track_num, buf(0x32, 2)) 1096 local tsort = buf(0x35, 1):uint() 1097 tree:add(f_track_sort, buf(0x35, 1)) 1098 1099 local pmode1 = buf(0x7b, 1):uint() 1100 tree:add(f_play_mode_1, buf(0x7b, 1)):append_text(" (" .. (PLAY_MODES[pmode1] or "Unknown") .. ")") 1101 tree:add(f_firmware, buf(0x7c, 4)) 1102 1103 local flag_tree = tree:add(f_flags, buf(0x89, 1)) 1104 flag_tree:add(f_flag_playing, buf(0x89, 1)) 1105 flag_tree:add(f_flag_master, buf(0x89, 1)) 1106 flag_tree:add(f_flag_sync, buf(0x89, 1)) 1107 flag_tree:add(f_flag_onair, buf(0x89, 1)) 1108 flag_tree:add(f_flag_bpm, buf(0x89, 1)) 1109 1110 local pmode2 = buf(0x8b, 1):uint() 1111 tree:add(f_play_mode_2, buf(0x8b, 1)):append_text(" (" .. (PLAY_MODES[pmode2] or "Unknown") .. ")") 1112 1113 local p_1 = buf(0x8c, 4):uint() 1114 local p_1_pct = (p_1 / 0x100000 - 1.0) * 100 1115 tree:add(f_pitch_1, buf(0x8c, 4)):set_text(string.format("Effective Pitch: %+.2f%%", p_1_pct)) 1116 1117 local bpm_val = buf(0x92, 2):uint() 1118 if bpm_val == 0xffff then 1119 tree:add(f_bpm_raw, buf(0x92, 2)):set_text("BPM: None") 1120 else 1121 tree:add(f_bpm_raw, buf(0x92, 2)):set_text(string.format("BPM: %.2f", bpm_val / 100)) 1122 end 1123 1124 local p_2 = buf(0x98, 4):uint() 1125 local p_2_pct = (p_2 / 0x100000 - 1.0) * 100 1126 tree:add(f_pitch_2, buf(0x98, 4)):set_text(string.format("Local Pitch: %+.2f%%", p_2_pct)) 1127 1128 tree:add(f_master_info, buf(0x9e, 1)) 1129 tree:add(f_handoff, buf(0x9f, 1)) 1130 1131 local beat_count = buf(0xa0, 4):uint() 1132 if beat_count == 0xffffffff then 1133 tree:add(f_beat_count, buf(0xa0, 4)):set_text("Beat Count: None") 1134 else 1135 tree:add(f_beat_count, buf(0xa0, 4)):set_text("Beat Count: " .. beat_count) 1136 end 1137 1138 tree:add(f_countdown, buf(0xa4, 2)) 1139 tree:add(f_beat_in_bar, buf(0xa6, 1)) 1140 1141 local p_3 = buf(0xc0, 4):uint() 1142 local p_3_pct = (p_3 / 0x100000 - 1.0) * 100 1143 tree:add(f_pitch_3, buf(0xc0, 4)):set_text(string.format("Pitch 3: %+.2f%%", p_3_pct)) 1144 end 1145end 1146 1147-- ============================================================================ 1148-- MAIN DISSECTOR ENTRY 1149-- ============================================================================ 1150 1151local function dissect_pdjl_main(buf, pkt, root, length) 1152 local src_port = pkt.src_port 1153 local dst_port = pkt.dst_port 1154 1155 -- TCP DB Port Discovery 1156 if (src_port == 12523 or dst_port == 12523) then 1157 if length == 2 then 1158 local tree = root:add(p_djl, buf(0, 2)) 1159 pkt.cols.protocol = "Pro DJ Link (DB Port)" 1160 pkt.cols.info:set("Port Discovery Response") 1161 tree:add(f_db_port, buf(0, 2)) 1162 return true 1163 elseif (length == 15 and buf(0, 1):uint() == 0x52) or (length == 19 and buf(0, 4):uint() == 0x0000000f) then 1164 local tree = root:add(p_djl, buf(0, length)) 1165 pkt.cols.protocol = "Pro DJ Link (DB Port)" 1166 pkt.cols.info:set("Port Discovery Request") 1167 tree:add(f_raw_payload, buf(0, length)):set_text("Discovery Request: RemoteDBServer") 1168 return true 1169 end 1170 end 1171 1172 if length < 5 then return false end 1173 1174 -- Check for "Qspt1WmJOL" header (UDP) 1175 if length >= 10 and buf(0, 10):string() == "Qspt1WmJOL" then 1176 -- UDP Logic 1177 pkt.cols.protocol = "Pro DJ Link" 1178 local tree = root:add(p_djl, buf(0, length)) 1179 tree:add(f_header, buf(0, 10)) 1180 1181 local p_type = buf(10, 1):uint() 1182 local p_type_str = PACKET_TYPES[p_type] 1183 1184 if dst_port == 50002 or src_port == 50002 then 1185 if p_type == 0x05 then p_type_str = "Media Query" end 1186 if p_type == 0x06 then p_type_str = "Media Response" end 1187 end 1188 1189 if p_type_str then 1190 tree:add(f_type, buf(10, 1)):append_text(" (" .. p_type_str .. ")") 1191 pkt.cols.info:set(p_type_str) 1192 else 1193 local unknown_label = string.format("Unknown(0x%02x)", p_type) 1194 tree:add(f_type, buf(10, 1)):append_text(" (" .. unknown_label .. ")") 1195 1196 if length > 11 then 1197 local raw_hex = buf(11):bytes():tohex() 1198 tree:add(f_raw_payload, buf(11)) 1199 pkt.cols.info:set(unknown_label .. ": " .. raw_hex) 1200 else 1201 pkt.cols.info:set(unknown_label) 1202 end 1203 end 1204 1205 pkt.cols.info:fence() 1206 1207 if dst_port == 50000 or src_port == 50000 then 1208 dissect_management_port(buf, pkt, tree, p_type) 1209 elseif dst_port == 50001 or src_port == 50001 then 1210 dissect_beat_sync_port(buf, pkt, tree, p_type) 1211 elseif dst_port == 50002 or src_port == 50002 then 1212 dissect_status_media_port(buf, pkt, tree, p_type, length) 1213 end 1214 return true 1215 else 1216 -- Fallback to DB TCP parser 1217 return dissect_db_tcp(buf, pkt, root, length) 1218 end 1219end 1220 1221function p_djl.dissector(buf, pkt, root) 1222 dissect_pdjl_main(buf, pkt, root, buf:len()) 1223end 1224 1225-- Heuristic TCP dissector to pick up DB traffic on dynamic ports 1226p_djl:register_heuristic("tcp", function(buf, pkt, root) 1227 local stream_id = get_stream_key(pkt) 1228 if buf:len() < 5 then 1229 -- If we already know this stream, claim even small packets 1230 if pdjl_streams[stream_id] then 1231 return dissect_pdjl_main(buf, pkt, root, buf:len()) 1232 end 1233 return false 1234 end 1235 1236 local tag = buf(0, 1):uint() 1237 local val = buf(1, 4):uint() 1238 -- Match DB Magic, DB Greeting, or a previously identified PDJL stream 1239 if (tag == 0x11 and (val == 0x872349ae or val == 0x00000001)) or pdjl_streams[stream_id] then 1240 return dissect_pdjl_main(buf, pkt, root, buf:len()) 1241 end 1242 return false 1243end) 1244 1245local udp_table = DissectorTable.get("udp.port") 1246udp_table:add(50000, p_djl) 1247udp_table:add(50001, p_djl) 1248udp_table:add(50002, p_djl) 1249 1250local tcp_table = DissectorTable.get("tcp.port") 1251tcp_table:add(12523, p_djl) 1252tcp_table:add(1051, p_djl) 1253tcp_table:add(1052, p_djl)