1 -- Logic16 protocol dissector for Wireshark
3 -- Copyright (C) 2015 Stefan Bruens <stefan.bruens@rwth-aachen.de>
5 -- based on the LWLA dissector, which is
6 -- Copyright (C) 2014 Daniel Elstner <daniel.kitta@gmail.com>
8 -- This program is free software; you can redistribute it and/or modify
9 -- it under the terms of the GNU General Public License as published by
10 -- the Free Software Foundation; either version 3 of the License, or
11 -- (at your option) any later version.
13 -- This program is distributed in the hope that it will be useful,
14 -- but WITHOUT ANY WARRANTY; without even the implied warranty of
15 -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 -- GNU General Public License for more details.
18 -- You should have received a copy of the GNU General Public License
19 -- along with this program; if not, see <http://www.gnu.org/licenses/>.
21 -- Usage: wireshark -X lua_script:saleae-logic16-dissector.lua
23 -- Create custom protocol for the Saleae Logic16 analyzer.
24 p_logic16 = Proto("Logic16", "Saleae Logic16 USB Protocol")
26 -- Known IDs of Logic16 control commands.
27 local control_commands = {
28 [0x01] = "START_ACQUISITION",
29 [0x02] = "ABORT_ACQUISITION_ASYNC",
30 [0x06] = "WRITE_EEPROM",
31 [0x07] = "READ_EEPROM",
32 [0x7a] = "WRITE_LED_TABLE",
33 [0x7b] = "SET_LED_MODE",
34 [0x7c] = "RETURN_TO_BOOTLOADER",
35 [0x7d] = "ABORT_ACQUISITION_SYNC",
36 [0x7e] = "FPGA_UPLOAD_INIT",
37 [0x7f] = "FPGA_UPLOAD_DATA",
38 [0x80] = "FPGA_WRITE_REGISTER",
39 [0x81] = "FPGA_READ_REGISTER",
43 -- Known Logic16 FPGA registers
44 local fpga_registers = {
45 [0x00] = "Bitstream version",
46 [0x01] = "Status and control",
47 [0x02] = "Channel select low",
48 [0x03] = "Channel select high",
49 [0x04] = "Sample rate divisor",
50 [0x05] = "LED brightness",
51 [0x06] = "??? [0x06]",
52 [0x07] = "??? [0x07]",
53 [0x08] = "??? [0x08]",
54 [0x09] = "??? [0x09]",
55 [0x0a] = "Base clock",
56 [0x0c] = "??? [0x0c]",
59 -- Create the fields exhibited by the protocol.
60 p_logic16.fields.command = ProtoField.uint8("logic16.cmd", "Command ID", base.HEX_DEC, control_commands)
61 p_logic16.fields.regaddr = ProtoField.uint8("logic16.regaddr", "Register Address", base.HEX, fpga_registers)
62 p_logic16.fields.regdata = ProtoField.uint8("logic16.regdata", "Register Value", base.HEX_DEC)
63 p_logic16.fields.regcount = ProtoField.uint8("logic16.regcount", "Register Count", base.HEX_DEC)
64 p_logic16.fields.EE_len = ProtoField.uint8("logic16.eeprom_len", "Read len", base.HEX_DEC)
65 p_logic16.fields.unknown = ProtoField.bytes("logic16.unknown", "Unidentified message data")
66 p_logic16.fields.reg_addrs = ProtoField.bytes("logic16.reg_addrs", "Register addresses")
67 p_logic16.fields.reg_av_pairs = ProtoField.bytes("logic16.reg_av_pairs", "Register address/value pairs")
69 p_logic16.fields.rawdata = ProtoField.bytes("logic16.rawdata", "Raw Message Data")
70 p_logic16.fields.decrypted = ProtoField.bytes("logic16.ep1_decrypted", "Decrypted message data")
72 -- Referenced USB URB dissector fields.
73 local f_urb_type = Field.new("usb.urb_type")
74 local f_transfer_type = Field.new("usb.transfer_type")
75 local f_endpoint = Field.new("usb.endpoint_number.endpoint")
76 local f_direction = Field.new("usb.endpoint_number.direction")
78 -- Decrypt EP1 message
79 local function decrypt_ep1_message(range)
80 local iv = {0x9b, 0x54}
81 local out = ByteArray.new()
82 out:set_size(range:len())
84 for i = 0, range:len() - 1, 1 do
85 local s = range(i,1):uint()
86 local dec = bit32.bxor(
87 (bit32.bxor((s + 0x45), 0x38) + 0xb0)
90 (bit32.bxor((dec + 0x39), 0x35) + 0x05)
92 local b = bit32.band(dec, 0xff)
98 return ByteArray.tvb(out, "Decrypted")
101 -- Dissect control command messages.
102 local function dissect_response(range, pinfo, tree)
103 pinfo.cols.info = string.format("<- response: %s",
105 local item = tree:add(p_logic16.fields.unknown, range())
106 item:add_expert_info(PI_UNDECODED, PI_WARN, "Leftover data")
110 -- Dissect control command messages.
111 local function dissect_command(range, pinfo, tree)
113 local command = range(0,1):uint()
114 tree:add(p_logic16.fields.command, range(0,1))
116 if command == 1 then -- start acquisition
117 pinfo.cols.info = string.format("-> [%d]: START acquisition",
119 elseif command == 2 then -- ABORT_ACQUISITION_ASYNC
120 pinfo.cols.info = string.format("-> [%d]: ABORT acquisition",
122 elseif command == 6 then -- WRITE_EEPROM
123 elseif command == 7 then -- READ_EEPROM
124 if range:len() == 5 then
125 local regaddr = range(3,1):uint()
126 local len = range(4,1):uint()
127 tree:add(p_logic16.fields.regaddr, range(3,1))
128 tree:add(p_logic16.fields.EE_len, range(4,1))
129 pinfo.cols.info = string.format("-> [%d]: read EEPROM 0x%02X len=%d",
130 command, regaddr, len)
133 elseif command == 0x7a then -- WRITE_LED_TABLE
134 local offset = range(1,1):uint()
135 local len = range(2,1):uint()
136 pinfo.cols.info = string.format("-> [%d]: write LED table offset=%d len=%d",
137 command, offset, len)
139 elseif command == 0x7b then -- SET_LED_MODE
140 pinfo.cols.info = string.format("-> [%d]: set LED mode flashing=%s",
141 command, range(1,1):uint() and "on" or "off")
143 elseif command == 0x7c then -- RETURN_TO_BOOTLOADER
144 elseif command == 0x7d then -- ABORT_ACQUISITION_SYNC
145 pinfo.cols.info = string.format("[%d]: ABORT acquisition SYNC",
147 elseif command == 0x7e then -- FPGA_UPLOAD_INIT
148 elseif command == 0x7f then -- FPGA_UPLOAD_DATA
149 elseif command == 0x80 then -- FPGA_WRITE_REGISTER
150 tree:add(p_logic16.fields.regcount, range(1,1))
151 if range:len() == 4 then
152 local regaddr = range(2,1):uint()
153 local regval = range(3,1):uint()
154 tree:add(p_logic16.fields.regaddr, range(2,1))
155 tree:add(p_logic16.fields.regdata, range(3,1))
156 pinfo.cols.info = string.format("-> [%d]: FPGA write reg 0x%02X [%s] value 0x%02X",
157 command, regaddr, fpga_registers[regaddr], regval)
160 local n_regs = range(1,1):uint()
161 pinfo.cols.info = string.format("-> [%d]: FPGA write reg (%d x)",
163 tree:add(p_logic16.fields.reg_av_pairs, range(2))
167 elseif command == 0x81 then -- FPGA_READ_REGISTER
168 tree:add(p_logic16.fields.regcount, range(1,1))
169 if range:len() == 3 then
170 local regaddr = range(2,1):uint()
171 tree:add(p_logic16.fields.regaddr, range(2,1))
172 pinfo.cols.info = string.format("-> [%d]: FPGA read reg 0x%02X [%s]",
173 command, regaddr, fpga_registers[regaddr])
176 local n_regs = range(1,1):uint()
177 pinfo.cols.info = string.format("-> [%d]: FPGA read reg (%d x)",
179 tree:add(p_logic16.fields.reg_addrs, range(2))
183 elseif command == 0x82 then -- GET_REVID"
188 -- Main dissector function.
189 function p_logic16.dissector(tvb, pinfo, tree)
190 local transfer_type = tonumber(tostring(f_transfer_type()))
192 -- Bulk transfers only.
193 if transfer_type == 3 then
194 local urb_type = tonumber(tostring(f_urb_type()))
195 local endpoint = tonumber(tostring(f_endpoint()))
196 local direction = tonumber(tostring(f_direction()))
198 -- Payload-carrying packets only.
199 if (urb_type == 83 and endpoint == 1) -- 'S' - Submit
200 or (urb_type == 67 and endpoint == 1) -- 'C' - Complete
202 pinfo.cols.protocol = p_logic16.name
204 local subtree = tree:add(p_logic16, tvb(), "Logic16")
205 subtree:add(p_logic16.fields.rawdata, tvb())
207 local dec = decrypt_ep1_message(tvb)
209 local dectree = subtree:add(p_logic16.fields.decrypted, dec())
210 dectree:set_generated()
212 -- Dispatch to message-specific dissection handler.
213 if (direction == 0) then
214 return dissect_command(dec, pinfo, dectree)
216 return dissect_response(dec, pinfo, dectree)
223 -- Register Logic16 protocol dissector during initialization.
224 function p_logic16.init()
225 local usb_product_dissectors = DissectorTable.get("usb.product")
227 -- Dissection by vendor+product ID requires that Wireshark can get the
228 -- the device descriptor. Making a USB device available inside a VM
229 -- will make it inaccessible from Linux, so Wireshark cannot fetch the
230 -- descriptor by itself. However, it is sufficient if the guest requests
231 -- the descriptor once while Wireshark is capturing.
232 usb_product_dissectors:add(0x21a91001, p_logic16)
234 -- Addendum: Protocol registration based on product ID does not always
235 -- work as desired. Register the protocol on the interface class instead.
236 -- The downside is that it would be a bad idea to put this into the global
237 -- configuration, so one has to make do with -X lua_script: for now.
238 -- local usb_bulk_dissectors = DissectorTable.get("usb.bulk")
240 -- For some reason the "unknown" class ID is sometimes 0xFF and sometimes
241 -- 0xFFFF. Register both to make it work all the time.
242 -- usb_bulk_dissectors:add(0xFF, p_logic16)
243 -- usb_bulk_dissectors:add(0xFFFF, p_logic16)