Wireshark dissector for Pro DJ Link protocol
3
fork

Configure Feed

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

use lookup tables to simplify many functions

Stella 29fda766 1af06021

+180 -248
+180 -248
pro_dj_link.lua
··· 398 398 } 399 399 400 400 -- ============================================================================ 401 + -- LOOKUP TABLES FOR ARGUMENT LABELS 402 + -- ============================================================================ 403 + 404 + local HAS_DMST_ARG_1 = { 405 + [0x0000] = true, [0x1000] = true, [0x1001] = true, [0x1002] = true, 406 + [0x1003] = true, [0x1004] = true, [0x1006] = true, [0x1007] = true, 407 + [0x1008] = true, [0x100a] = true, [0x100d] = true, [0x1010] = true, 408 + [0x1011] = true, [0x1012] = true, [0x1013] = true, [0x1014] = true, 409 + [0x1101] = true, [0x1102] = true, [0x1103] = true, [0x1105] = true, 410 + [0x1106] = true, [0x1114] = true, [0x1206] = true, [0x1214] = true, 411 + [0x1300] = true, [0x1301] = true, [0x2002] = true, [0x2003] = true, 412 + [0x2004] = true, [0x2102] = true, [0x2104] = true, [0x2204] = true, 413 + [0x2904] = true, [0x2b04] = true, [0x2c04] = true, [0x3000] = true, 414 + [0x3007] = true, [0x3100] = true, [0x3e03] = true 415 + } 416 + 417 + local MSG_ARG_LABELS = { 418 + [0x0000] = { [1] = "Our Device Number" }, 419 + [0x1000] = { [1] = "DMST", [2] = "Sort", [3] = "Unknown (0x00ffffff)" }, 420 + [0x3100] = { [1] = "DMST", [2] = "Rekordbox ID", [3] = "Unknown 1", [4] = "Unknown 2" }, 421 + [0x2c04] = { [1] = "DMST", [2] = "Rekordbox ID", [3] = "Tag", [4] = "Extension" }, 422 + [0x2102] = { [1] = "DMST", [2] = "Rekordbox ID" }, 423 + [0x4101] = { 424 + [1] = "Parent ID", [2] = "Main ID", [3] = "Label 1 Len", [4] = "Label 1", 425 + [5] = "Label 2 Len", [6] = "Label 2", [7] = "Item Type", [8] = "Flags", 426 + [9] = "Artwork ID", [10] = "Position", [11] = "Unknown", [12] = "Unknown" 427 + }, 428 + [0x4201] = { [1] = "Status", [2] = "Total Items" }, 429 + [0x4001] = { [1] = "Item Count" }, 430 + [0x4000] = { [1] = "Request Type Echo", [2] = "Items Found" }, 431 + [0x3000] = { [1] = "DMST", [2] = "Offset", [3] = "Limit", [4] = "Unknown", [5] = "Total Items", [6] = "Unknown" }, 432 + [0x4b02] = { [1] = "Request Type Echo", [2] = "Reserved", [3] = "Capability Level", [4] = "Placeholder (Empty String)" } 433 + } 434 + 435 + -- ============================================================================ 401 436 -- HELPERS 402 437 -- ============================================================================ 403 438 404 439 -- Decodes a 4-byte buffer as a reverse-ASCII string 405 440 local function get_reverse_ascii(buf) 406 441 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 442 + return string.char( 443 + b:get_index(3), 444 + b:get_index(2), 445 + b:get_index(1), 446 + b:get_index(0) 447 + ):gsub("%z", "") 413 448 end 414 449 415 450 -- Label for specific arguments based on message type 416 451 function 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 452 + if arg_idx == 1 and HAS_DMST_ARG_1[msg_type] then 458 453 return "DMST" 459 454 end 460 455 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 - } 456 + local labels = MSG_ARG_LABELS[msg_type] 457 + if labels and labels[arg_idx] then 505 458 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 459 + end 460 + 461 + -- Special case for Blob Responses 462 + if arg_idx <= 4 and (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 513 463 if arg_idx == 1 then return "Request Type Echo" end 514 464 if arg_idx == 2 then return "Status/Echo" end 515 465 if arg_idx == 3 then return "Length" end 516 466 if arg_idx == 4 then 517 - local labels = { 467 + local blob_labels = { 518 468 [0x4002] = "Blob (image)", 519 469 [0x4602] = "Beat Grid Data", 520 470 [0x4702] = "Cue Points Data", ··· 523 473 [0x4a02] = "Waveform Detail Data", 524 474 [0x4f02] = "Analysis Tag Data", 525 475 } 526 - return labels[msg_type] or "Blob Data" 476 + return blob_labels[msg_type] or "Blob Data" 527 477 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 478 end 479 + 550 480 return nil 551 481 end 552 482 ··· 752 682 return cur - offset 753 683 end 754 684 685 + local function dissect_single_db_message(buf, off, msg_len, root, pkt, stream_id, db_types, tx_ids) 686 + local msg_tree = root:add(p_djl, buf(off, msg_len)) 687 + msg_tree:add(f_db_magic, buf(off+1, 4)) 688 + 689 + local current = off + 5 690 + 691 + -- Transaction ID 692 + local tx_id = 0 693 + if msg_len - (current - off) >= 5 then 694 + local bytes_read, val = dissect_db_field(buf, current, msg_tree, nil, "Transaction ID") 695 + tx_id = val or 0 696 + local tx_str = (tx_id == 0xfffffffe) and "Setup/Teardown" or string.format("0x%08x", tx_id) 697 + 698 + local already_added = false 699 + for _, v in ipairs(tx_ids) do 700 + if v == tx_str then already_added = true break end 701 + end 702 + if not already_added then 703 + table.insert(tx_ids, tx_str) 704 + end 705 + 706 + current = bytes_read 707 + end 708 + 709 + -- Message Type 710 + local m_type = 0 711 + if msg_len - (current - off) >= 3 then 712 + local bytes_read, val, m_type_item = dissect_db_field(buf, current, msg_tree, nil, "Message Type") 713 + m_type = val or 0 714 + local m_type_str = DB_MSG_TYPES[m_type] or string.format("Unknown(0x%04x)", m_type) 715 + 716 + if m_type == 0x0100 and tx_id == 0xfffffffe then 717 + m_type_str = "Device Disconnect" 718 + if m_type_item then 719 + m_type_item:set_text("Message Type: " .. m_type_str .. " (0x" .. string.format("%04x", m_type) .. ")") 720 + end 721 + end 722 + 723 + table.insert(db_types, m_type_str) 724 + 725 + current = bytes_read 726 + msg_tree:append_text(": " .. m_type_str) 727 + end 728 + 729 + -- Arg Count 730 + local arg_count = 0 731 + if msg_len - (current - off) >= 2 then 732 + local bytes_read, val = dissect_db_field(buf, current, msg_tree, nil, "Arg Count") 733 + arg_count = val or 0 734 + current = bytes_read 735 + end 736 + 737 + -- Arg Tag Blob (usually 12 bytes) 738 + if msg_len - (current - off) >= 5 and buf(current, 1):uint() == 0x14 then 739 + local tag_len = buf(current+1, 4):uint() 740 + if msg_len - (current - off) >= 5 + tag_len then 741 + local tag_item = msg_tree:add(f_db_tag_blob, buf(current+5, tag_len)) 742 + local arg_types = {} 743 + for i = 0, tag_len - 1 do 744 + local t = buf(current+5+i, 1):uint() 745 + if t ~= 0 then 746 + table.insert(arg_types, ARG_TAGS[t] or string.format("0x%02x", t)) 747 + end 748 + end 749 + if #arg_types > 0 then 750 + tag_item:set_text("Arg Types: " .. table.concat(arg_types, ", ")) 751 + end 752 + current = current + 5 + tag_len 753 + end 754 + end 755 + 756 + -- Arguments 757 + for i = 1, arg_count do 758 + if current >= off + msg_len then break end 759 + local prev_current = current 760 + local bytes_read, val = dissect_db_field(buf, current, msg_tree, m_type, i) 761 + 762 + -- If it's a Success/Ack, show what it's acking in the summary 763 + if m_type == 0x4000 then 764 + if i == 1 and val then 765 + local ack_type_str = DB_MSG_TYPES[val] or string.format("0x%04x", val) 766 + msg_tree:append_text(" - " .. ack_type_str) 767 + db_types[#db_types] = "Ack " .. ack_type_str 768 + elseif i == 2 and val then 769 + msg_tree:append_text(": " .. val .. " Items") 770 + db_types[#db_types] = db_types[#db_types] .. ": " .. val .. " Items" 771 + end 772 + end 773 + 774 + -- If it's a GetAnalysisTag request, record the tag for the response 775 + if m_type == 0x2c04 and i == 3 and val then 776 + local tag_name = ANALYSIS_TAGS[val] or get_reverse_ascii(buf(prev_current+1, 4)) 777 + analysis_requests[stream_id .. ":" .. tx_id] = tag_name 778 + msg_tree:append_text(" - " .. tag_name) 779 + -- Update the last entry in db_types to include the tag 780 + db_types[#db_types] = db_types[#db_types] .. " - " .. tag_name 781 + end 782 + 783 + -- If it's a TrackLoadNotification, show the Rekordbox ID in the summary 784 + if m_type == 0x3100 and i == 2 and val then 785 + msg_tree:append_text(" - RB ID: " .. val) 786 + db_types[#db_types] = db_types[#db_types] .. " - RB ID: " .. val 787 + end 788 + 789 + -- If it's an AnalysisTagResponse, try to find the tag from the request 790 + if m_type == 0x4f02 and i == 1 then 791 + local tag_name = analysis_requests[stream_id .. ":" .. tx_id] 792 + if tag_name then 793 + msg_tree:append_text(" - " .. tag_name) 794 + db_types[#db_types] = db_types[#db_types] .. " - " .. tag_name 795 + end 796 + end 797 + 798 + current = bytes_read 799 + if current <= prev_current then break end 800 + end 801 + 802 + -- Special handling for trailing binary data in blob responses 803 + 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 804 + local label = get_arg_label(m_type, 4) or "Blob Data" 805 + local remaining = (off + msg_len) - current 806 + msg_tree:add(f_db_field_bin, buf(current, remaining)):set_text(label .. ": [" .. remaining .. " bytes]") 807 + current = current + remaining 808 + end 809 + 810 + msg_tree:set_len(msg_len) 811 + end 812 + 755 813 local function dissect_db_tcp(buf, pkt, root, length) 756 814 local off = 0 757 815 local found_db = false 758 816 local db_types = {} 759 817 local tx_ids = {} 760 818 761 - local src_port = pkt.src_port 762 - local dst_port = pkt.dst_port 763 819 local stream_id = get_stream_key(pkt) 764 820 765 821 while off < length do ··· 791 847 found_db = true 792 848 end 793 849 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) 850 + dissect_single_db_message(buf, off, msg_len, root, pkt, stream_id, db_types, tx_ids) 919 851 off = off + msg_len 920 852 elseif magic_or_val == 0x00000001 then 921 853 if not found_db then