]>
Commit | Line | Data |
---|---|---|
1 | -- Logic16 protocol dissector for Wireshark | |
2 | -- | |
3 | -- Copyright (C) 2015 Stefan Bruens <stefan.bruens@rwth-aachen.de> | |
4 | -- | |
5 | -- based on the LWLA dissector, which is | |
6 | -- Copyright (C) 2014 Daniel Elstner <daniel.kitta@gmail.com> | |
7 | -- | |
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. | |
12 | -- | |
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. | |
17 | -- | |
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/>. | |
20 | ||
21 | -- Usage: wireshark -X lua_script:saleae-logic16-dissector.lua | |
22 | -- | |
23 | -- Create custom protocol for the Saleae Logic16 analyzer. | |
24 | p_logic16 = Proto("Logic16", "Saleae Logic16 USB Protocol") | |
25 | ||
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", | |
40 | [0x82] = "GET_REVID" | |
41 | } | |
42 | ||
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]", | |
57 | } | |
58 | ||
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") | |
68 | ||
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") | |
71 | ||
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") | |
77 | ||
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()) | |
83 | ||
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) | |
88 | , 0x5a, iv[1]) | |
89 | dec = bit32.bxor( | |
90 | (bit32.bxor((dec + 0x39), 0x35) + 0x05) | |
91 | , 0x2b, iv[2]) | |
92 | local b = bit32.band(dec, 0xff) | |
93 | out:set_index(i, b) | |
94 | iv[1] = b | |
95 | iv[2] = s | |
96 | end | |
97 | ||
98 | return ByteArray.tvb(out, "Decrypted") | |
99 | end | |
100 | ||
101 | -- Dissect control command messages. | |
102 | local function dissect_response(range, pinfo, tree) | |
103 | pinfo.cols.info = string.format("<- response: %s", | |
104 | tostring(range)) | |
105 | local item = tree:add(p_logic16.fields.unknown, range()) | |
106 | item:add_expert_info(PI_UNDECODED, PI_WARN, "Leftover data") | |
107 | return range:len() | |
108 | end | |
109 | ||
110 | -- Dissect control command messages. | |
111 | local function dissect_command(range, pinfo, tree) | |
112 | ||
113 | local command = range(0,1):uint() | |
114 | tree:add(p_logic16.fields.command, range(0,1)) | |
115 | ||
116 | if command == 1 then -- start acquisition | |
117 | pinfo.cols.info = string.format("-> [%d]: START acquisition", | |
118 | command) | |
119 | elseif command == 2 then -- ABORT_ACQUISITION_ASYNC | |
120 | pinfo.cols.info = string.format("-> [%d]: ABORT acquisition", | |
121 | command) | |
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) | |
131 | return 3 | |
132 | end | |
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) | |
138 | ||
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") | |
142 | ||
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", | |
146 | command) | |
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) | |
158 | return 4 | |
159 | else | |
160 | local n_regs = range(1,1):uint() | |
161 | pinfo.cols.info = string.format("-> [%d]: FPGA write reg (%d x)", | |
162 | command, n_regs) | |
163 | tree:add(p_logic16.fields.reg_av_pairs, range(2)) | |
164 | return range:len() | |
165 | end | |
166 | ||
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]) | |
174 | return 3 | |
175 | else | |
176 | local n_regs = range(1,1):uint() | |
177 | pinfo.cols.info = string.format("-> [%d]: FPGA read reg (%d x)", | |
178 | command, n_regs) | |
179 | tree:add(p_logic16.fields.reg_addrs, range(2)) | |
180 | return range:len() | |
181 | end | |
182 | ||
183 | elseif command == 0x82 then -- GET_REVID" | |
184 | end | |
185 | return range:len() | |
186 | end | |
187 | ||
188 | -- Main dissector function. | |
189 | function p_logic16.dissector(tvb, pinfo, tree) | |
190 | local transfer_type = tonumber(tostring(f_transfer_type())) | |
191 | ||
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())) | |
197 | ||
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 | |
201 | then | |
202 | pinfo.cols.protocol = p_logic16.name | |
203 | ||
204 | local subtree = tree:add(p_logic16, tvb(), "Logic16") | |
205 | subtree:add(p_logic16.fields.rawdata, tvb()) | |
206 | ||
207 | local dec = decrypt_ep1_message(tvb) | |
208 | ||
209 | local dectree = subtree:add(p_logic16.fields.decrypted, dec()) | |
210 | dectree:set_generated() | |
211 | ||
212 | -- Dispatch to message-specific dissection handler. | |
213 | if (direction == 0) then | |
214 | return dissect_command(dec, pinfo, dectree) | |
215 | else | |
216 | return dissect_response(dec, pinfo, dectree) | |
217 | end | |
218 | end | |
219 | end | |
220 | return 0 | |
221 | end | |
222 | ||
223 | -- Register Logic16 protocol dissector during initialization. | |
224 | function p_logic16.init() | |
225 | local usb_product_dissectors = DissectorTable.get("usb.product") | |
226 | ||
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) | |
233 | ||
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") | |
239 | ||
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) | |
244 | end |