]> sigrok.org Git - sigrok-util.git/blame - debug/saleae-logicpro16/saleae-logicpro16-dissector.lua
sigrok-fwextract-dreamsourcelab-dslogic: Revert to v0.97 firmware.
[sigrok-util.git] / debug / saleae-logicpro16 / saleae-logicpro16-dissector.lua
CommitLineData
00b7a9ee
JL
1-- Logic Pro 16 protocol dissector for Wireshark
2--
3-- Copyright (C) 2016-2017 Jan Luebbe <jluebbe@lasnet.de>
4--
5-- based on the LWLA dissector, which is
6-- Copyright (C) 2015 Stefan Bruens <stefan.bruens@rwth-aachen.de>
7--
8-- based on the LWLA dissector, which is
9-- Copyright (C) 2014 Daniel Elstner <daniel.kitta@gmail.com>
10--
11-- This program is free software; you can redistribute it and/or modify
12-- it under the terms of the GNU General Public License as published by
13-- the Free Software Foundation; either version 3 of the License, or
14-- (at your option) any later version.
15--
16-- This program is distributed in the hope that it will be useful,
17-- but WITHOUT ANY WARRANTY; without even the implied warranty of
18-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19-- GNU General Public License for more details.
20--
21-- You should have received a copy of the GNU General Public License
22-- along with this program; if not, see <http://www.gnu.org/licenses/>.
23
24-- Usage: wireshark -X lua_script:saleae-logicpro16-dissector.lua
25--
26-- Create custom protocol for the Saleae Logic Pro 16 analyzer.
27p_logicpro16 = Proto("LogicPro16", "Saleae Logic Pro 16 USB Protocol")
28
29local ctrl_enum = {
30 [0x00] = "Normal",
31 [0x20] = "Reseed",
32}
33
34local cmd_enum = {
35 [0x01] = "Start Capture",
36 [0x02] = "Stop Capture",
37 [0x07] = "Read EEPROM",
38 [0x7e] = "Prepare Firmware Upload",
39 [0x7f] = "Upload Firmware",
40 [0x80] = "Write Register",
41 [0x81] = "Read Register",
42 [0x87] = "Write I2C",
43 [0x88] = "Read I2C",
44 [0x89] = "Wake I2C",
45 [0x8b] = "Read Firmware Version",
24f4548b 46 [0x86] = "Read Temperature",
00b7a9ee
JL
47}
48
49local reg_enum = {
24f4548b
JL
50 -- see https://www.hittite.com/content/documents/data_sheet/hmcad1100.pdf
51 -- for ADC details
52 [0x03] = "ADC Index",
53 [0x04] = "ADC Value (LSB)",
54 [0x05] = "ADC Value (MSB)",
00b7a9ee
JL
55 [0x08] = "Analog Channels (LSB)",
56 [0x09] = "Analog Channels (MSB)",
57 [0x06] = "Digital Channels (LSB)",
58 [0x07] = "Digital Channels (MSB)",
59 [0x0f] = "LED Red",
60 [0x10] = "LED Green",
61 [0x11] = "LED Blue",
24f4548b
JL
62 [0x12] = "Voltage",
63 [0x15] = "Bank Power?",
64 [0x17] = "Magic EEPROM Value?",
65 [0x40] = "Capture Status",
00b7a9ee
JL
66}
67
68local i2c_result_enum = {
69 [0x01] = "Error",
70 [0x02] = "OK",
71}
72
73local crypt_cmd_enum = {
74 [0x00] = "Reset",
75 [0x01] = "Sleep",
76 [0x02] = "Idle",
77 [0x03] = "Normal",
78}
79
80local crypt_op_enum = {
81 [0x16] = "Nonce",
82 [0x1b] = "Random",
83 [0x41] = "Sign",
84}
85
86-- Create the fields exhibited by the protocol.
87p_logicpro16.fields.req = ProtoField.new("Request Frame", "logicpro16.req", ftypes.FRAMENUM)
88p_logicpro16.fields.rsp = ProtoField.new("Response Frame", "logicpro16.rsp", ftypes.FRAMENUM)
89
90p_logicpro16.fields.ctrl = ProtoField.uint8("logicpro16.ctrl", "Control Byte", base.HEX, ctrl_enum)
91p_logicpro16.fields.cmd = ProtoField.uint8("logicpro16.cmd", "Command Byte", base.HEX, cmd_enum)
92p_logicpro16.fields.size = ProtoField.uint16("logicpro16.size", "Payload Size")
93p_logicpro16.fields.unknown = ProtoField.bytes("logicpro16.unknown", "Unidentified message data")
94
95p_logicpro16.fields.eepromaddr = ProtoField.uint16("logicpro16.eepromaddr", "EEPROM Address", base.HEX_DEC)
96p_logicpro16.fields.eepromsize = ProtoField.uint16("logicpro16.eepromsize", "EEPROM Size", base.HEX_DEC)
97
98p_logicpro16.fields.regaddr = ProtoField.uint8("logicpro16.regaddr", "Register Address", base.HEX_DEC, reg_enum)
99p_logicpro16.fields.regval = ProtoField.uint8("logicpro16.regval", "Register Value", base.HEX_DEC)
100
101p_logicpro16.fields.i2caddr = ProtoField.uint8("logicpro16.i2caddr", "I2C Address", base.HEX_DEC)
102p_logicpro16.fields.i2csize = ProtoField.uint16("logicpro16.i2csize", "I2C Size", base.HEX_DEC)
103p_logicpro16.fields.i2cdata = ProtoField.bytes("logicpro16.i2cdata", "I2C Data")
104p_logicpro16.fields.i2cresult = ProtoField.uint8("logicpro16.i2cresult", "I2C Result", base.HEX_DEC, i2c_result_enum)
105
106p_logicpro16.fields.cryptcmd = ProtoField.uint8("logicpro16.cryptcmd", "Crypt Command", base.HEX_DEC, crypt_cmd_enum)
107p_logicpro16.fields.cryptcount = ProtoField.uint8("logicpro16.cryptcount", "Crypt Count", base.HEX_DEC)
108p_logicpro16.fields.cryptop = ProtoField.uint8("logicpro16.cryptop", "Crypt Op", base.HEX_DEC, crypt_op_enum)
109p_logicpro16.fields.cryptp1 = ProtoField.uint8("logicpro16.cryptp1", "Crypt Param 1", base.HEX_DEC)
110p_logicpro16.fields.cryptp2 = ProtoField.uint16("logicpro16.cryptp2", "Crypt Param 2", base.HEX_DEC)
111p_logicpro16.fields.cryptdata = ProtoField.bytes("logicpro16.cryptdata", "Crypt Data")
112p_logicpro16.fields.cryptcrc = ProtoField.uint16("logicpro16.cryptcrc", "Crypt CRC", base.HEX_DEC)
113
114p_logicpro16.fields.rawdata = ProtoField.bytes("logicpro16.rawdata", "Raw Message Data")
115p_logicpro16.fields.decrypted = ProtoField.bytes("logicpro16.decrypted", "Decrypted message data")
116
117-- Referenced USB URB dissector fields.
118local f_urb_type = Field.new("usb.urb_type")
119local f_transfer_type = Field.new("usb.transfer_type")
120local f_endpoint = Field.new("usb.endpoint_number.endpoint")
121local f_direction = Field.new("usb.endpoint_number.direction")
122
123local iv = 0x354B248E
124local state = iv
125local states = {}
126
127local request_frame
128local response_requests = {}
129local request_responses = {}
130
131local request_cmd
132local response_cmds = {}
133
134local crypt_op
135local crypt_nonce_in
136local crypt_nonce_out
137local crypt_sign_out_crc
138
139local function iterate_state()
140 local max = bit32.band(state, 0x1f) + 34
141 --print(string.format("shift -/%i state 0x%x", max, state))
142 for i = 0, max, 1 do
143 state = bit32.bor(
144 bit32.rshift(state, 1),
145 bit32.lshift(
146 bit32.bxor(
147 state,
148 bit32.rshift(state, 1),
149 bit32.rshift(state, 21),
150 bit32.rshift(state, 31)
151 ),
152 31
153 )
154 )
155 --print(string.format("shift %i/%i state 0x%x", i, max, state))
156 end
157end
158
159local function reinit_state()
160 print("in", crypt_nonce_in)
161 print("out", crypt_nonce_out)
162 print("crc", crypt_sign_out_crc)
163 local input = crypt_nonce_in(4, 16) .. crypt_nonce_out(0, 28) .. crypt_sign_out_crc .. ByteArray.new("00000000") -- add padding
164 print("input", input)
165 local result = 0
166 for i = 0, input:len()-4, 4 do
167 result = bit32.bxor(
168 result,
169 input:get_index(i),
170 bit32.lshift(input:get_index(i+1), 8),
171 bit32.lshift(input:get_index(i+2), 16),
172 bit32.lshift(input:get_index(i+3), 24)
173 )
174 print(string.format("iterate 0x%x 0x%x 0x%x 0x%x 0x%x", result, input:get_index(i), input:get_index(i+1), input:get_index(i+2), input:get_index(i+3)))
175 end
176 state = result
177end
178
179-- Decrypt EP1 OUT message
180local function decrypt_ep1_out_message(pinfo, range)
181 local out = ByteArray.new()
182 out:set_size(range:len())
183
184 local ctrl = range(0, 1):uint()
185 local reseed = bit32.btest(ctrl, 0x20)
186 if not pinfo.visited then
187 if reseed then
188 state = iv
189 end
190 states[pinfo.number] = state
191 end
192
193 local secret = states[pinfo.number]
194 print("out visited", pinfo.visited, string.format("secret 0x%x", secret))
195
196 for i = 0, range:len() - 1, 1 do
197 local value = range(i,1):uint()
198 local mask = bit32.extract(secret, 8*(i%4), 8)
199 local dec
200 if i == 0 then
201 -- only 0x20 and 0x08 are relevant here
202 dec = bit32.band(value, 0x28)
203 else
204 dec = bit32.bxor(value, mask)
205 end
206 out:set_index(i, dec)
207 end
208
209 tvb = ByteArray.tvb(out, "Decrypted")
210
211 if not pinfo.visited then
212 if reseed then
213 state = tvb:range(1,4):le_uint()
214 print("reseed", string.format("state 0x%x", secret))
215 else
216 iterate_state()
217 end
218 end
219
220 return tvb
221end
222
223-- Decrypt EP1 IN message
224local function decrypt_ep1_in_message(pinfo, range)
225 local out = ByteArray.new()
226 out:set_size(range:len())
227
228 if not pinfo.visited then
229 states[pinfo.number] = state
230 end
231
232 local secret = states[pinfo.number]
233 print("in visited", pinfo.visited, string.format("secret 0x%x", secret))
234
235 for i = 0, range:len() - 1, 1 do
236 local value = range(i,1):uint()
237 local mask = bit32.extract(secret, 8*(i%4), 8)
238 local dec = bit32.bxor(value, mask)
239 out:set_index(i, dec)
240 end
241
242 if not pinfo.visited then
243 iterate_state()
244 end
245
246 return ByteArray.tvb(out, "Decrypted")
247end
248
249-- Dissect control command messages.
250local function dissect_command(range, pinfo, tree)
251
252 tree:add(p_logicpro16.fields.ctrl, range(0, 1))
253 tree:add(p_logicpro16.fields.cmd, range(1, 1))
254 local cmd = range(1, 1):uint()
255 request_cmd = cmd
256 request_frame = pinfo.number
257 response_frame = request_responses[pinfo.number]
258
259 pinfo.cols.info = string.format("command: 0x%02x %s",
260 cmd, tostring(range))
261
262 if not (response_frame == nil) then
263 tree:add(p_logicpro16.fields.rsp, response_frame):set_generated()
264 end
265
266 if cmd == 0x7 then -- eeprom read
267 tree:add(p_logicpro16.fields.unknown, range(2, 2))
268 tree:add_le(p_logicpro16.fields.eepromaddr, range(4, 2))
269 tree:add_le(p_logicpro16.fields.eepromsize, range(6, 2))
270 elseif cmd == 0x7f then -- firmware upload
271 tree:add_le(p_logicpro16.fields.size, range(2, 2))
272 tree:add(p_logicpro16.fields.rawdata, range(4))
273 elseif cmd == 0x80 then -- register write
274 tree:add(p_logicpro16.fields.size, range(2, 1))
275 local t = tree:add(p_logicpro16.fields.rawdata, range(3))
276 for i = 0, range(2, 1):uint() - 1, 1 do
277 t:add(p_logicpro16.fields.regaddr, range(3+i*2, 1))
278 t:add(p_logicpro16.fields.regval, range(3+i*2+1, 1))
279 end
280 elseif cmd == 0x81 then -- register read
281 tree:add(p_logicpro16.fields.size, range(2, 1))
282 local t = tree:add(p_logicpro16.fields.rawdata, range(3))
283 for i = 0, range(2, 1):uint() - 1, 1 do
284 t:add(p_logicpro16.fields.regaddr, range(3+i, 1))
285 end
286 elseif cmd == 0x87 then -- i2c write
287 tree:add(p_logicpro16.fields.i2caddr, range(2, 1))
288 tree:add_le(p_logicpro16.fields.i2csize, range(3, 2))
289 local t = tree:add(p_logicpro16.fields.i2cdata, range(5))
290 t:add(p_logicpro16.fields.cryptcmd, range(5, 1))
291 local cryptcmd = range(5, 1):uint()
292 if cryptcmd == 0x03 then
293 t:add(p_logicpro16.fields.cryptcount, range(6, 1))
294 local cryptdatalen = range(6, 1):uint() - 7
295 t:add(p_logicpro16.fields.cryptop, range(7, 1))
296 crypt_op = range(7, 1):uint()
297 t:add(p_logicpro16.fields.cryptp1, range(8, 1))
298 t:add(p_logicpro16.fields.cryptp2, range(9, 2))
299 if cryptdatalen > 0 then
300 t:add(p_logicpro16.fields.cryptdata, range(11, cryptdatalen))
301 end
302 t:add(p_logicpro16.fields.cryptcrc, range(11+cryptdatalen, 2))
303 if crypt_op == 0x16 then -- nonce
304 crypt_nonce_in = range(11, cryptdatalen):bytes()
305 end
306 end
307 elseif cmd == 0x88 then -- i2c read
308 tree:add(p_logicpro16.fields.i2caddr, range(2, 1))
309 tree:add_le(p_logicpro16.fields.i2csize, range(3, 2))
310 elseif cmd == 0x89 then -- i2c wake
311 else
312 local item = tree:add(p_logicpro16.fields.unknown, range(2))
313 item:add_expert_info(PI_UNDECODED, PI_WARN, "Leftover data")
314 end
315 return range:len()
316end
317
318-- Dissect control response messages.
319local function dissect_response(range, pinfo, tree)
320 local cmd
321 if not pinfo.visited then
322 response_requests[pinfo.number] = request_frame
323 response_cmds[pinfo.number] = request_cmd
324 request_responses[request_frame] = pinfo.number
325 else
326 request_frame = response_requests[pinfo.number]
327 request_cmd = response_cmds[pinfo.number]
328 end
329
330 print("visited", pinfo.visited, string.format("request_cmd 0x%x", request_cmd))
331 pinfo.cols.info = string.format("response: 0x%02x %s",
332 request_cmd, tostring(range))
333
334 tree:add(p_logicpro16.fields.req, request_frame):set_generated()
335 tree:add(p_logicpro16.fields.cmd, request_cmd):set_generated()
336
337 if request_cmd == 0x81 then -- register read
24f4548b 338 local t = tree:add(p_logicpro16.fields.rawdata, range(0))
00b7a9ee
JL
339 elseif request_cmd == 0x87 then -- i2c write
340 tree:add(p_logicpro16.fields.i2cresult, range(0, 1))
341 local t = tree:add(p_logicpro16.fields.i2cdata, range(1))
342 elseif request_cmd == 0x88 then -- i2c read
343 tree:add(p_logicpro16.fields.i2cresult, range(0, 1))
344 local i2cresult = range(0, 1):uint()
345 local t = tree:add(p_logicpro16.fields.i2cdata, range(1))
346 if i2cresult == 0x02 then
347 t:add(p_logicpro16.fields.cryptcount, range(1, 1))
348 local cryptdatalen = range(1, 1):uint() - 3
349 if cryptdatalen > 0 then
350 t:add(p_logicpro16.fields.cryptdata, range(2, cryptdatalen))
351 end
352 t:add(p_logicpro16.fields.cryptcrc, range(2+cryptdatalen, 2))
353 if crypt_op == 0x16 then -- nonce
354 crypt_nonce_out = range(2, cryptdatalen):bytes()
355 elseif crypt_op == 0x41 then -- sign
356 crypt_sign_out_crc = range(2+cryptdatalen, 2):bytes()
357 if not pinfo.visited then
358 reinit_state()
359 end
360 end
361 crypt_op = nil
362 end
363 else
364 local item = tree:add(p_logicpro16.fields.unknown, range())
365 item:add_expert_info(PI_UNDECODED, PI_WARN, "Leftover data")
366 end
367 return range:len()
368end
369
370-- Main dissector function.
371function p_logicpro16.dissector(tvb, pinfo, tree)
372 local transfer_type = tonumber(tostring(f_transfer_type()))
373
374 -- Bulk transfers only.
375 if transfer_type == 3 then
376 local urb_type = tonumber(tostring(f_urb_type()))
377 local endpoint = tonumber(tostring(f_endpoint()))
378 local direction = tonumber(tostring(f_direction()))
379
380 -- Payload-carrying packets only.
381 if (urb_type == 83 and endpoint == 1) -- 'S' - Submit
382 or (urb_type == 67 and endpoint == 1) -- 'C' - Complete
383 then
384 pinfo.cols.protocol = p_logicpro16.name
385
386 local subtree = tree:add(p_logicpro16, tvb(), "Logic Pro 16")
387 subtree:add(p_logicpro16.fields.rawdata, tvb())
388
389 local dec
390 if (direction == 0) then
391 dec = decrypt_ep1_out_message(pinfo, tvb)
392 else
393 dec = decrypt_ep1_in_message(pinfo, tvb)
394 end
395
396 local dectree = subtree:add(p_logicpro16.fields.decrypted, dec())
397 dectree:set_generated()
398
399 -- Dispatch to message-specific dissection handler.
400 if (direction == 0) then
401 return dissect_command(dec, pinfo, dectree)
402 else
403 return dissect_response(dec, pinfo, dectree)
404 end
405 end
406 end
407 return 0
408end
409
410-- Register Logic Pro 16 protocol dissector during initialization.
411function p_logicpro16.init()
412 local usb_product_dissectors = DissectorTable.get("usb.product")
413
414 -- Dissection by vendor+product ID requires that Wireshark can get the
415 -- the device descriptor. Making a USB device available inside a VM
416 -- will make it inaccessible from Linux, so Wireshark cannot fetch the
417 -- descriptor by itself. However, it is sufficient if the guest requests
418 -- the descriptor once while Wireshark is capturing.
419 usb_product_dissectors:add(0x21a91006, p_logicpro16)
420
421 -- Addendum: Protocol registration based on product ID does not always
422 -- work as desired. Register the protocol on the interface class instead.
423 -- The downside is that it would be a bad idea to put this into the global
424 -- configuration, so one has to make do with -X lua_script: for now.
425 -- local usb_bulk_dissectors = DissectorTable.get("usb.bulk")
426
427 -- For some reason the "unknown" class ID is sometimes 0xFF and sometimes
428 -- 0xFFFF. Register both to make it work all the time.
429 -- usb_bulk_dissectors:add(0xFF, p_logicpro16)
430 -- usb_bulk_dissectors:add(0xFFFF, p_logicpro16)
431end