├── .gitignore ├── LICENSE ├── README.md └── scripts ├── build.bat ├── decoder_audio_payload_typeI.lua ├── decoder_ethernet.lua ├── decoder_rndis.lua ├── decoder_scsi.lua ├── decoder_video_payload_mjpeg.lua ├── file_base.lua ├── file_iti1480a.lua ├── file_pcap.lua ├── file_text.lua ├── file_transaction_parser.lua ├── html.lua ├── init.lua ├── macro_defs.lua ├── upv_parser.lua ├── usb_class_audio.lua ├── usb_class_cdc_acm.lua ├── usb_class_dfu.lua ├── usb_class_hid.lua ├── usb_class_hub.lua ├── usb_class_msc_bot.lua ├── usb_class_video.lua ├── usb_control_transfer.lua ├── usb_descriptor_parser.lua ├── usb_device_aw_efex.lua ├── usb_device_ftdi.lua ├── usb_register_class.lua ├── usb_setup_parser.lua └── util.lua /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Lua sources 2 | luac.out 3 | 4 | # luarocks build files 5 | *.src.rock 6 | *.zip 7 | *.tar.gz 8 | 9 | # Object files 10 | *.o 11 | *.os 12 | *.ko 13 | *.obj 14 | *.elf 15 | 16 | # Precompiled Headers 17 | *.gch 18 | *.pch 19 | 20 | # Libraries 21 | *.lib 22 | *.a 23 | *.la 24 | *.lo 25 | *.def 26 | *.exp 27 | 28 | # Shared objects (inc. Windows DLLs) 29 | *.dll 30 | *.so 31 | *.so.* 32 | *.dylib 33 | 34 | # Executables 35 | *.exe 36 | *.out 37 | *.app 38 | *.i*86 39 | *.x86_64 40 | *.hex 41 | 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 XToolBox 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # USB Packet Viewer parser 2 | 3 | [USB Packet Viewer](http://pv.tusb.org)的插件项目,使用lua 5.3.4开发。包含文件读写,USB协议解析功能。 4 | 5 | An add-on project for [USB Packet viewer](http://pv.tusb.org), writen in lua 5.3.4. 6 | 7 | 8 | 用scripts目录替换USB Packet Viewer中的scripts.lua文件,以使用最新的解析器。 9 | 10 | Replace scripts.lua in USB Packet Viewer with scripts folder here to use the latest parser. 11 | 12 | 确保目录结构如下 Ensure the directory structure as below: 13 | 14 | ``` batch 15 | \---usbpv 16 | | usbpv.exe 17 | | 18 | \---scripts 19 | build.bat 20 | decoder_ethernet.lua 21 | decoder_rndis.lua 22 | decoder_scsi.lua 23 | file_base.lua 24 | file_iti1480a.lua 25 | file_pcap.lua 26 | html.lua 27 | init.lua 28 | macro_defs.lua 29 | upv_parser.lua 30 | usb_class_cdc_acm.lua 31 | usb_class_hid.lua 32 | usb_class_hub.lua 33 | usb_class_msc_bot.lua 34 | usb_control_transfer.lua 35 | usb_descriptor_parser.lua 36 | usb_device_ftdi.lua 37 | usb_register_class.lua 38 | usb_setup_parser.lua 39 | util.lua 40 | ``` 41 | 42 | # 脚本接口说明 43 | 44 | ``` lua 45 | -- encoding: UTF8 46 | ---------------------------------------- 47 | --- USB Packet Viewer 脚本接口说明 --- 48 | ---------------------------------------- 49 | -- 将此说明内容保存为文本文件,并重命名为scripts.lua,替换自带的脚本可以看到此示例的运行效果 50 | 51 | -------------------------------- 52 | ------ 文件相关API开始 ------- 53 | -- 文件相关操作的API一共有三个 54 | -- valid_filter 获取支持的文件名后缀 55 | -- open_file 读取文件 56 | -- write_file 写入文件 57 | -------------------------------- 58 | -- 返回Qt风格的文件Filter 59 | -- 默认支持 USB Packet Viewer File (*.upv) 60 | function valid_filter() 61 | return "Text File (*.txt);;All Files (*.*)" 62 | end 63 | 64 | -- 打开并读取文件,此函数可以阻塞,不会影响UI 65 | -- 参数说明 66 | -- name 文件名 67 | -- packet_handler 数据回调,当读取到文件中的数据后,调用packet_handler 68 | -- context 调用packet_handler时的传入的第一个参数 69 | -- 返回值(1个) 70 | -- 读取的数据包数量 71 | 72 | -- packet_handler 函数原型 packet_handler(context, ts, nano, pkt, status, pos, total) 73 | -- packet_handler 参数说明 74 | -- context 传入open_file的第三个参数 75 | -- ts 以秒为单位的时间戳 76 | -- nano 以纳秒为单位的时间戳 77 | -- pkt 数据包内容,包含前面的PID和后面的CRC 78 | -- status 状态值 79 | -- pos 当前已经读取的文件长度 80 | -- total 文件总长度 81 | -- packet_handler 返回值(1个) 82 | -- nil 表示读取到的包处理出错 83 | -- true 表示处理成功 84 | function open_file(name, packet_handler, context) 85 | for i=1,10 do 86 | packet_handler(context, i,i, "\x69\x00\x10", 0, i, 10) 87 | end 88 | return 10 89 | end 90 | 91 | -- 将数据写入文件,此函数可以阻塞,不会影响UI 92 | -- 参数说明 93 | -- name 文件名 94 | -- packet_handler 数据回调,调用packet_handler获取需要写入文件的数据包,当没有更多的数据包时,返回nil 95 | -- context 调用packet_handler时的传入的第一个参数 96 | -- 返回值(1个) 97 | -- 写入的数据包数量 98 | 99 | -- packet_handler 函数原型 packet_handler(context) 100 | -- packet_handler 参数说明 101 | -- context write_file 102 | -- packet_handler 返回值(4个) 103 | -- ts 以秒为单位的时间戳 104 | -- nano 以纳秒为单位的时间戳 105 | -- pkt 数据包内容,包含前面的PID和后面的CRC 106 | -- status 状态值 107 | 108 | function write_file(name, packet_handler, context) 109 | local f = io.open(name, "w+") 110 | local count = 0 111 | while f do 112 | local ts,nano,pkt,status = packet_handler(context) 113 | if not ts then break end 114 | f:write(string.format("ts %d, nano %d, status %d:", ts, nano, status&0xff)) 115 | for i=1,#pkt do 116 | f:write(string.format("%02x ", pkt:byte(i))) 117 | end 118 | f:write("\n") 119 | count = count + 1 120 | end 121 | f:close() 122 | return count 123 | end 124 | 125 | -------------------------------- 126 | ------ 解码相关API开始 ------- 127 | -------------------------------- 128 | -- 解码相关操作的API一共有六个 129 | -- upv_register_decoder 向USBPV注册解码器,此函数由USBPV实现,脚本中只能调用 130 | -- upv_reset_parser 重置解码器 131 | -- upv_parse_transaction 解析Transaction 132 | -- upv_add_decoder 添加解码器 133 | -- upv_remove_decoder 移除解码器 134 | -- upv_valid_parser 获取支持的解码器 135 | 136 | -- 注册解码器,此函数由USBPV实现,当脚本需要向USBPV注册解码器时,调用此函数 137 | -- 此函数内部会调用 upv_add_decoder, 用来向脚本添加端点对应的解码器 138 | -- 函数原型 upv_register_decoder(name, param) 139 | -- 参数说明 140 | -- name 解码器名称,脚本中通过名称来区分不同的解码器 141 | -- param 端点参数,格式为由地址和端点序列组成的字符串 string.char(addr,endp1,addr,endp2,...) 142 | -- 143 | -- 说明: 此函数会将解码器注册到对应的地址和端点上。 144 | -- USBPV会先检测当前数据包的地址端点是否已经注册,如果没有注册且端点号不为0,不会将数据发给脚本进行解码。 145 | -- 端点号为0的数据会直接发往脚本进行解码,不需要预先注册。 146 | local upv_register_decoder = upv_register_decoder 147 | 148 | -- 模拟一个 decoder_map 149 | local decoder_map = {} 150 | 151 | -- 重置解码器,无参数,无返回值。UI上点击清除按钮后会调用此函数 152 | function upv_reset_parser() 153 | -- 下面的代码,将地址为01的设备中的0x81和0x01端点注册到了名为MSC的解码器上 154 | upv_register_decoder("MSC", "\x01\x81\x01\x01") 155 | end 156 | 157 | -- 解码Transaction数据 158 | -- 参数说明 159 | -- param transaction 参数,包含了地址,端点,PID,和响应信息 160 | -- data transaction 数据,(不包含PID和CRC) 161 | -- needDetail 是否需要详细信息 162 | -- forceBegin 是否强制重新解码 163 | -- autoDecoder 是否为脚本自动设置的解码器 164 | -- 返回值(3个) 165 | -- state 解码状态。 0-解码失败 Transaction不能解码 166 | -- 1-数据开始 Xfer数据的第一包 167 | -- 2-数据结束 Xfer数据的最后一包 168 | -- 3-数据开始及结束 Xfer只有一包Transaction数据,例如普通的HID数据 169 | -- 4-还有更多的数据 Xfer中的数据,既不是第一包,也不是最后一包 170 | -- transaction transaction 解码结果 171 | -- transfer transfer 解码结果 172 | -- 173 | -- 解码结果数据格式为以"\x00"分隔的字符串,分别表示 标题、名字、描述、状态、html信息、数据信息 174 | function upv_parse_transaction(param, data, needDetail, forceBegin, autoDecoder) 175 | -- 从param中分离出地址,端点,PID及响应信息 176 | local addr, ep, pid, ack = param:byte(1), param:byte(2), param:byte(3), param:byte(4) 177 | if ep == 0 then 178 | -- TODO: 端点为0的为控制传输,可以在此将端点0上的Transaction合并成Control Transfer 179 | -- 然后解码设备的各种描述,根据描述符调用 upv_register_decoder 自动注册相应的解码器 180 | return 0 181 | end 182 | local title = decoder_map[string.char(addr,ep)] 183 | if title ~= "MSC" then return 0 end 184 | local state = 4 185 | -- CBW 数据 186 | if #data == 31 then state = 1 end 187 | -- CSW 数据 188 | if #data == 13 then state = 2 end 189 | 190 | if not needDetail then 191 | -- 不需要详细信息,只返回状态 192 | return state 193 | end 194 | return state, 195 | title .. "-Xact标题" 196 | .."\x00".."Xact名字" 197 | .."\x00".."Xact描述" 198 | .."\x00".."success" 199 | .."\x00".."Transaction Html Info" 200 | .."\x00".."\x01\x02\x00\x03\x00\x04", 201 | title .."-Xfer标题" 202 | .."\x00".."Xfer名字" 203 | .."\x00".."Xfer描述" 204 | .."\x00".."success" 205 | .."\x00".."Xfer Html Info" 206 | .."\x00".."\x11\x12\x00\x13\x00\x14" 207 | end 208 | 209 | -- 为端点添加添加解码器 210 | -- 参数说明 211 | -- name 解码器名称,脚本中通过名称来区分不同的解码器 212 | -- eps 端点参数,格式为由地址和端点序列组成的字符串 string.char(addr,endp1,addr,endp2,...) 213 | -- 返回值(1个) 214 | -- true-成功, false-失败 215 | function upv_add_decoder(name, eps) 216 | if name == "MSC" then 217 | for i=1,#eps,2 do 218 | decoder_map[string.char(eps:byte(i), eps:byte(i+1))] = "MSC" 219 | end 220 | return true 221 | end 222 | return false 223 | end 224 | 225 | -- 为端点移除解码器 226 | -- 参数说明 227 | -- name 解码器名称,脚本中通过名称来区分不同的解码器 228 | -- eps 端点参数,格式为由地址和端点序列组成的字符串 string.char(addr,endp1,addr,endp2,...) 229 | -- 返回值(1个) 230 | -- true成功, nil或false失败 231 | function upv_remove_decoder(name, eps) 232 | for i=1,#eps,2 do 233 | decoder_map[string.char(eps:byte(i), eps:byte(i+1))] = nil 234 | end 235 | return true 236 | end 237 | 238 | -- 获取支持的解码器 239 | -- 返回值(1个) 240 | -- 字符串形式的解码器列表,格式为以;号分隔的解码器参数: 241 | -- <解码器参数1>;<解码器参数2>;<解码器参数3> 242 | -- 解码器参数格式如下: 243 | -- <解码器名>:<端点参数1>,<端点参数2>,<端点参数3>, 244 | -- 端点参数格式如下: 245 | -- <端点描述><端点类型> 246 | -- 端点类型为012456这六种类型的一种,0-IN, 1-OUT, 2-INOUT, 4-可选IN, 5-可选OUT, 6-可选INOUT 247 | function upv_valid_parser() 248 | -- 下面的参数表示三个解码器,名称分别为MSC, CDC和Video 249 | -- MSC 有两个端点,分别为:Bulk IN,类型为IN;Bulk OUT,类型为OUT 250 | -- CDC 有三个端点,分别为:Bulk IN,类型为IN;Bulk OUT,类型为OUT。Notify,类型为IN 251 | -- Video 有两个端点,分别为:Stream,类型为INOUT;StillImage,类型为可选的INOUT 252 | return "MSC:Bulk IN1,Bulk Out0;CDC:Bulk In1,Bulk Out0,Notify1;Video:Stream2,StillImage6" 253 | end 254 | 255 | ``` 256 | -------------------------------------------------------------------------------- /scripts/build.bat: -------------------------------------------------------------------------------- 1 | call luacc init -------------------------------------------------------------------------------- /scripts/decoder_audio_payload_typeI.lua: -------------------------------------------------------------------------------- 1 | -- decoder_audio_payload_typeI.lua 2 | local html = require("html") 3 | local decoder = {} 4 | local unpack = string.unpack 5 | 6 | decoder.audio_decoder = {} 7 | 8 | 9 | local function PCM_parser(data, context) 10 | local r = "" 11 | local cnt = 0 12 | for i=1,#data,2 do 13 | local v = unpack("i2", data, i) 14 | r = r .. tostring(v) .. "," 15 | cnt = cnt + 1 16 | if cnt == 16 then 17 | r = r .. "
" 18 | cnt = 0 19 | end 20 | end 21 | return r 22 | end 23 | 24 | local function PCM8_parser(data, context) 25 | local r = "" 26 | local cnt = 0 27 | for i=1,#data do 28 | local v = unpack("I1", data, i) 29 | r = r .. tostring(v) .. "," 30 | cnt = cnt + 1 31 | if cnt == 16 then 32 | r = r .. "
" 33 | cnt = 0 34 | end 35 | end 36 | return r 37 | end 38 | 39 | local function Float_parser(data, context) 40 | local r = "" 41 | local cnt = 0 42 | for i=1,#data,4 do 43 | local v = unpack("f", data, i) 44 | r = r .. tostring(v) .. "," 45 | cnt = cnt + 1 46 | if cnt == 16 then 47 | r = r .. "
" 48 | cnt = 0 49 | end 50 | end 51 | return r 52 | end 53 | 54 | local function ALAW_parser(data, context) 55 | local r = "" 56 | local cnt = 0 57 | for i=1,#data,1 do 58 | local v = unpack("i8", data, i) 59 | r = r .. tostring(v) .. "," 60 | cnt = cnt + 1 61 | if cnt == 16 then 62 | r = r .. "
" 63 | cnt = 0 64 | end 65 | end 66 | return r 67 | end 68 | 69 | function make_decoder(id, name, parser) 70 | decoder.audio_decoder[id] = { 71 | name = name, 72 | decode = function(data, context) 73 | return "

" .. name .. " Data

" .. parser(data, context) 74 | end 75 | } 76 | end 77 | 78 | make_decoder(0x0001, "PCM", PCM_parser) 79 | make_decoder(0x0002, "PCM8", PCM8_parser) 80 | make_decoder(0x0003, "IEEE_FLOAT", Float_parser) 81 | make_decoder(0x0004, "ALAW", ALAW_parser) 82 | make_decoder(0x0005, "MULAW", ALAW_parser) 83 | 84 | package.loaded["decoder_audio_payload_typeI"] = decoder 85 | -------------------------------------------------------------------------------- /scripts/decoder_ethernet.lua: -------------------------------------------------------------------------------- 1 | -- ethernet.lua 2 | local eth = {} 3 | local html = require("html") 4 | local struct_ip_header = html.create_struct([[ 5 | struct{ 6 | uint8_t Length:4; // Length * 4 7 | uint8_t Version:4; 8 | uint8_t DSCP:6; 9 | uint8_t ECN:2; 10 | uint16_t TotalLength; 11 | uint16_t ID; 12 | uint16_t FragementOffset:13; 13 | uint16_t MoreFragement:1; 14 | uint16_t DontFragement:1; 15 | uint16_t Reserved:1; 16 | uint8_t TTL; 17 | uint8_t Protocol; 18 | uint16_t HeaderChecksum; 19 | uint8_t SourceIP[4]; 20 | uint8_t DestIP[4]; 21 | } 22 | ]], { 23 | TotalLength = {format = "dec"}, 24 | TTL = {format = "dec"}, 25 | SourceIP = {format = "ip"}, 26 | DestIP = {format = "ip"}, 27 | Protocol = { [6] = "TCP", [17] = "UDP", [1] = "ICMP", [2] = "IGMP",}, 28 | }, true) 29 | 30 | local function get_ip_html(data, context) 31 | local ipHeader = struct_ip_header:build(data, "IPv4") 32 | return ipHeader.html 33 | end 34 | 35 | local struct_arp_header = html.create_struct([[ 36 | struct{ 37 | uint16_t HardwareType; 38 | uint16_t ProtocalType; 39 | uint8_t HardwareSize; 40 | uint8_t ProtocalSize; 41 | uint16_t OpCode; 42 | uint8_t SenderMac[6]; 43 | uint8_t SenderIP[4]; 44 | uint8_t TargetMac[6]; 45 | uint8_t TargetIP[4]; 46 | } 47 | ]], { 48 | HardwareType = { [1] = "Ethernet"}, 49 | ProtocalType = {[0x0800] = "IPv4"}, 50 | SenderIP = {format = "ip"}, 51 | TargetIP = {format = "ip"}, 52 | OpCode = { [1] = "Request", [2] = "Reply" }, 53 | }, true) 54 | 55 | local function get_arp_html(data, context) 56 | local arp = struct_arp_header:build(data, "ARP") 57 | return arp.html 58 | end 59 | 60 | local struct_eth_header = html.create_struct([[ 61 | struct{ 62 | uint8_t Destination[6]; 63 | uint8_t Source[6]; 64 | uint16_t Type; 65 | } 66 | ]], { 67 | Type = {[0x0800] = "IPv4", [0x0806] = "ARP"} 68 | }, true) 69 | 70 | eth.parse_data = function(data, context) 71 | local header = struct_eth_header:build(data, "Ethernet II") 72 | local bodyHtml = "" 73 | if header.Type == 0x0800 then 74 | bodyHtml = bodyHtml .. get_ip_html(data:sub(15), context) 75 | end 76 | if header.Type == 0x0806 then 77 | bodyHtml = bodyHtml .. get_arp_html(data:sub(15), context) 78 | end 79 | return header.html .. bodyHtml 80 | end 81 | 82 | package.loaded["decoder_ethernet"] = eth 83 | -------------------------------------------------------------------------------- /scripts/decoder_rndis.lua: -------------------------------------------------------------------------------- 1 | -- rndis.lua 2 | local html = require("html") 3 | local eth = require("decoder_ethernet") 4 | local rndis = {} 5 | 6 | 7 | local msgType = { 8 | [0x00000001] = "REMOTE_NDIS_PACKET_MSG" , 9 | [0X00000002] = "REMOTE_NDIS_INITIALIZE_MSG" , 10 | [0X00000003] = "REMOTE_NDIS_HALT_MSG" , 11 | [0X00000004] = "REMOTE_NDIS_QUERY_MSG" , 12 | [0X00000005] = "REMOTE_NDIS_SET_MSG" , 13 | [0X00000006] = "REMOTE_NDIS_RESET_MSG" , 14 | [0X00000007] = "REMOTE_NDIS_INDICATE_STATUS_MSG" , 15 | [0X00000008] = "REMOTE_NDIS_KEEPALIVE_MSG" , 16 | [0X80000002] = "REMOTE_NDIS_INITIALIZE_CMPLT" , 17 | [0X80000004] = "REMOTE_NDIS_QUERY_CMPLT" , 18 | [0X80000005] = "REMOTE_NDIS_SET_CMPLT" , 19 | [0X80000006] = "REMOTE_NDIS_RESET_CMPLT" , 20 | [0X80000008] = "REMOTE_NDIS_KEEPALIVE_CMPLT" , 21 | } 22 | 23 | _G.rndis_msg_type = msgType 24 | local msgHandler 25 | 26 | local struct_message = html.create_struct([[ 27 | typedef struct{ 28 | uint32_t MessageType; 29 | uint32_t MessageLength; 30 | } 31 | ]]) 32 | 33 | local function parseMessage(data, context) 34 | data = data or "" 35 | local header = struct_message:build(data, "RNDIS Data") 36 | if not msgType[header.MessageType] or not msgHandler[header.MessageType] then 37 | return "

Unknown Message Type " .. string.format("0x%08X", header.MessageType) .."

" 38 | end 39 | return msgHandler[header.MessageType](data, context) 40 | end 41 | 42 | rndis.parse_data = parseMessage 43 | rndis.parseResponse = parseMessage 44 | rndis.parseCommand = parseMessage 45 | 46 | local struct_REMOTE_NDIS_PACKET_MSG = html.create_struct([[ 47 | typedef struct{ 48 | uint32_t MessageType; // _G.rndis_msg_type 49 | uint32_t MessageLength; // {format = "dec"} 50 | uint32_t DataOffset; 51 | uint32_t DataLength; // {format = "dec"} 52 | uint32_t OOBDataOffset; 53 | uint32_t OOBDataLength; // {format = "dec"} 54 | uint32_t NumOOBDataElements; 55 | uint32_t PerPacketInfoOffset; 56 | uint32_t PerPacketInfoLength; // {format = "dec"} 57 | uint32_t DeviceVcHandle; 58 | uint32_t Reserved; 59 | }rndis_data_packet_t; 60 | ]]) 61 | local function REMOTE_NDIS_PACKET_MSG_Handler(data, context) 62 | local header = struct_REMOTE_NDIS_PACKET_MSG:build(data, "RNDIS Data") 63 | data = data:sub(45) 64 | local bodyHtml = eth.parse_data(data, context) 65 | return header.html .. bodyHtml 66 | end 67 | 68 | local struct_REMOTE_NDIS_INITIALIZE_MSG = html.create_struct([[ 69 | typedef struct{ 70 | uint32_t MessageType; // _G.rndis_msg_type 71 | uint32_t MessageLength; // {format = "dec"} 72 | uint32_t RequestId; 73 | uint32_t MajorVersion; 74 | uint32_t MinorVersion; 75 | uint32_t MaxTransferSize; 76 | }rndis_initialize_msg_t; 77 | ]]) 78 | 79 | local struct_REMOTE_NDIS_INITIALIZE_CMPLT = html.create_struct([[ 80 | typedef struct{ 81 | uint32_t MessageType; // _G.rndis_msg_type 82 | uint32_t MessageLength; // {format = "dec"} 83 | uint32_t RequestId; 84 | uint32_t Status; 85 | uint32_t MajorVersion; 86 | uint32_t MinorVersion; 87 | uint32_t DeviceFlags; 88 | uint32_t Medium; 89 | uint32_t MaxPacketsPerTransfer; 90 | uint32_t MaxTransferSize; 91 | uint32_t PacketAlignmentFactor; 92 | uint32_t AfListOffset; 93 | uint32_t AfListSize; 94 | } rndis_initialize_cmplt_t; 95 | ]]) 96 | 97 | local struct_REMOTE_NDIS_HALT_MSG = html.create_struct([[ 98 | typedef struct{ 99 | uint32_t MessageType; // _G.rndis_msg_type 100 | uint32_t MessageLength; // {format = "dec"} 101 | uint32_t RequestId; 102 | } rndis_halt_msg_t; 103 | ]]) 104 | 105 | local struct_REMOTE_NDIS_QUERY_MSG = html.create_struct([[ 106 | typedef struct{ 107 | uint32_t MessageType; // _G.rndis_msg_type 108 | uint32_t MessageLength; // {format = "dec"} 109 | uint32_t RequestId; 110 | uint32_t Oid; 111 | uint32_t InformationBufferLength; 112 | uint32_t InformationBufferOffset; 113 | uint32_t DeviceVcHandle; 114 | } rndis_query_msg_t; 115 | ]]) 116 | 117 | local struct_REMOTE_NDIS_QUERY_CMPLT = html.create_struct([[ 118 | typedef struct{ 119 | uint32_t MessageType; // _G.rndis_msg_type 120 | uint32_t MessageLength; // {format = "dec"} 121 | uint32_t RequestId; 122 | uint32_t Status; 123 | uint32_t InformationBufferLength; // {format = "dec"} 124 | uint32_t InformationBufferOffset; 125 | } rndis_query_cmplt_t; 126 | ]]) 127 | 128 | local struct_REMOTE_NDIS_SET_MSG = html.create_struct([[ 129 | typedef struct{ 130 | uint32_t MessageType; // _G.rndis_msg_type 131 | uint32_t MessageLength; // {format = "dec"} 132 | uint32_t RequestId; 133 | uint32_t Oid; 134 | uint32_t InformationBufferLength; 135 | uint32_t InformationBufferOffset; 136 | uint32_t DeviceVcHandle; 137 | } rndis_set_msg_t; 138 | ]]) 139 | 140 | local struct_REMOTE_NDIS_SET_CMPLT = html.create_struct([[ 141 | typedef struct{ 142 | uint32_t MessageType; // _G.rndis_msg_type 143 | uint32_t MessageLength; // {format = "dec"} 144 | uint32_t RequestId; 145 | uint32_t Status; 146 | }rndis_set_cmplt_t; 147 | ]]) 148 | 149 | local struct_REMOTE_NDIS_RESET_MSG = html.create_struct([[ 150 | typedef struct{ 151 | uint32_t MessageType; // _G.rndis_msg_type 152 | uint32_t MessageLength; // {format = "dec"} 153 | uint32_t Reserved; 154 | } rndis_reset_msg_t; 155 | ]]) 156 | 157 | local struct_REMOTE_NDIS_RESET_CMPLT = html.create_struct([[ 158 | typedef struct{ 159 | uint32_t MessageType; // _G.rndis_msg_type 160 | uint32_t MessageLength; // {format = "dec"} 161 | uint32_t Status; 162 | uint32_t AddressingReset; 163 | } rndis_reset_cmplt_t; 164 | ]]) 165 | 166 | local struct_REMOTE_NDIS_INDICATE_STATUS_MSG = html.create_struct([[ 167 | typedef struct{ 168 | uint32_t MessageType; // _G.rndis_msg_type 169 | uint32_t MessageLength; // {format = "dec"} 170 | uint32_t Status; 171 | uint32_t StatusBufferLength; 172 | uint32_t StatusBufferOffset; 173 | } rndis_indicate_status_t; 174 | ]]) 175 | 176 | local struct_REMOTE_NDIS_KEEPALIVE_MSG = html.create_struct([[ 177 | typedef struct{ 178 | uint32_t MessageType; // _G.rndis_msg_type 179 | uint32_t MessageLength; // {format = "dec"} 180 | uint32_t RequestId; 181 | }rndis_keepalive_msg_t; 182 | ]]) 183 | 184 | local struct_REMOTE_NDIS_KEEPALIVE_CMPLT = html.create_struct([[ 185 | typedef struct{ 186 | uint32_t MessageType; // _G.rndis_msg_type 187 | uint32_t MessageLength; // {format = "dec"} 188 | uint32_t RequestId; 189 | uint32_t Status; 190 | }rndis_keepalive_cmplt_t; 191 | ]]) 192 | 193 | local function make_handler(struct_builder) 194 | return function(data, context) 195 | return struct_builder:build(data, "RNDIS Data").html 196 | end 197 | end 198 | 199 | msgHandler = { 200 | [0x00000001] = REMOTE_NDIS_PACKET_MSG_Handler , 201 | [0X00000002] = make_handler(struct_REMOTE_NDIS_INITIALIZE_MSG) , 202 | [0X00000003] = make_handler(struct_REMOTE_NDIS_HALT_MSG) , 203 | [0X00000004] = make_handler(struct_REMOTE_NDIS_QUERY_MSG) , 204 | [0X00000005] = make_handler(struct_REMOTE_NDIS_SET_MSG) , 205 | [0X00000006] = make_handler(struct_REMOTE_NDIS_RESET_MSG) , 206 | [0X00000007] = make_handler(struct_REMOTE_NDIS_INDICATE_STATUS_MSG) , 207 | [0X00000008] = make_handler(struct_REMOTE_NDIS_KEEPALIVE_MSG) , 208 | [0X80000002] = make_handler(struct_REMOTE_NDIS_INITIALIZE_CMPLT) , 209 | [0X80000004] = make_handler(struct_REMOTE_NDIS_QUERY_CMPLT) , 210 | [0X80000005] = make_handler(struct_REMOTE_NDIS_SET_CMPLT) , 211 | [0X80000006] = make_handler(struct_REMOTE_NDIS_RESET_CMPLT) , 212 | [0X80000008] = make_handler(struct_REMOTE_NDIS_KEEPALIVE_CMPLT) , 213 | } 214 | 215 | package.loaded["decoder_rndis"] = rndis 216 | -------------------------------------------------------------------------------- /scripts/decoder_scsi.lua: -------------------------------------------------------------------------------- 1 | -- scsi.lua 2 | -- scsi command, data, status parser 3 | -- https://www.usb.org/sites/default/files/usbmassbulk_10.pdf 4 | 5 | local scsi = {} 6 | local fmt = string.format 7 | local unpack = string.unpack 8 | local util = require("util") 9 | local html = require("html") 10 | 11 | local SCSI_CMD_TEST_UNIT_READY = 0x00 12 | local SCSI_CMD_REQUEST_SENSE = 0x03 13 | local SCSI_CMD_INQUIRY = 0x12 14 | local SCSI_CMD_MODE_SELECT_6 = 0x15 15 | local SCSI_CMD_MODE_SENSE_6 = 0x1A 16 | local SCSI_CMD_MODE_SELECT_10 = 0x55 17 | local SCSI_CMD_MODE_SENSE_10 = 0x5A 18 | local SCSI_CMD_START_STOP_UNIT = 0x1B 19 | local SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL = 0x1E 20 | local SCSI_CMD_READ_CAPACITY_10 = 0x25 21 | local SCSI_CMD_READ_FORMAT_CAPACITY = 0x23 22 | local SCSI_CMD_READ_10 = 0x28 23 | local SCSI_CMD_WRITE_10 = 0x2A 24 | local SCSI_CMD_VERIFY_10 = 0x2F 25 | local SCSI_CMD_SYNC_CACHE_10 = 0x35 26 | 27 | local VPD_CODE = { 28 | VPD_SUPPORTED_VPDS = 0x0 , 29 | VPD_UNIT_SERIAL_NUM = 0x80, 30 | VPD_IMP_OP_DEF = 0x81, -- /* obsolete in SPC-2 */ 31 | VPD_ASCII_OP_DEF = 0x82, -- /* obsolete in SPC-2 */ 32 | VPD_DEVICE_ID = 0x83, 33 | VPD_SOFTW_INF_ID = 0x84, 34 | VPD_MAN_NET_ADDR = 0x85, 35 | VPD_EXT_INQ = 0x86, 36 | VPD_MODE_PG_POLICY = 0x87, 37 | VPD_SCSI_PORTS = 0x88, 38 | VPD_ATA_INFO = 0x89, 39 | VPD_POWER_CONDITION = 0x8a, 40 | VPD_DEVICE_CONSTITUENTS = 0x8b, 41 | VPD_CFA_PROFILE_INFO = 0x8c, 42 | VPD_POWER_CONSUMPTION = 0x8d, 43 | VPD_3PARTY_COPY = 0x8f, 44 | VPD_PROTO_LU = 0x90, 45 | VPD_PROTO_PORT = 0x91, 46 | VPD_SCSI_FEATURE_SETS = 0x92, -- /* spc5r11 */ 47 | VPD_BLOCK_LIMITS = 0xb0, -- /* SBC-3 */ 48 | VPD_SA_DEV_CAP = 0xb0, -- /* SSC-3 */ 49 | VPD_OSD_INFO = 0xb0, -- /* OSD */ 50 | VPD_BLOCK_DEV_CHARS = 0xb1, -- /* SBC-3 */ 51 | VPD_MAN_ASS_SN = 0xb1, -- /* SSC-3, ADC-2 */ 52 | VPD_SECURITY_TOKEN = 0xb1, -- /* OSD */ 53 | VPD_ES_DEV_CHARS = 0xb1, -- /* SES-4 */ 54 | VPD_TA_SUPPORTED = 0xb2, -- /* SSC-3 */ 55 | VPD_LB_PROVISIONING = 0xb2, -- /* SBC-3 */ 56 | VPD_REFERRALS = 0xb3, -- /* SBC-3 */ 57 | VPD_AUTOMATION_DEV_SN = 0xb3, -- /* SSC-3 */ 58 | VPD_SUP_BLOCK_LENS = 0xb4, -- /* sbc4r01 */ 59 | VPD_DTDE_ADDRESS = 0xb4, -- /* SSC-4 */ 60 | VPD_BLOCK_DEV_C_EXTENS = 0xb5, -- /* sbc4r02 */ 61 | VPD_LB_PROTECTION = 0xb5, -- /* SSC-5 */ 62 | VPD_ZBC_DEV_CHARS = 0xb6, -- /* zbc-r01b */ 63 | VPD_BLOCK_LIMITS_EXT = 0xb7, -- /* sbc4r08 */ 64 | } 65 | 66 | local VPD_NAME = {} 67 | for k,v in pairs(VPD_CODE) do 68 | VPD_NAME[v] = k 69 | end 70 | 71 | local DEVICE_TYPE = { 72 | [0x00] = "BLOCK_DEVICE", 73 | [0x01] = "SEQ_DEVICE", 74 | [0x02] = "PRINTER", 75 | [0x03] = "PROCESSOR", 76 | [0x04] = "WRITE_ONCE", 77 | [0x05] = "CD_DVD", 78 | } 79 | 80 | local struct_inquiry_std_data = html.create_struct( [[ 81 | typedef struct 82 | { 83 | uint8_t peripheral_device_type: 5; /**< \ref scsi_peripheral_device_type */ 84 | uint8_t peripheral_qualifier : 3; 85 | 86 | uint8_t reserved1 : 7; 87 | uint8_t removable : 1; 88 | 89 | uint8_t version; 90 | 91 | uint8_t response_data_format : 4; /**< muse be 2 */ 92 | uint8_t hierarchical_support : 1; 93 | uint8_t normal_aca : 1; 94 | uint8_t reserved2 : 2; 95 | 96 | uint8_t additional_length; 97 | 98 | uint8_t protect : 1; 99 | uint8_t reserved3 : 2; 100 | uint8_t third_party_copy : 1; 101 | uint8_t target_port_group_support : 2; 102 | uint8_t access_control_coordinator : 1; 103 | uint8_t scc_support : 1; 104 | 105 | uint8_t addr16 : 1; 106 | uint8_t reserved4 : 3; 107 | uint8_t multi_port : 1; 108 | uint8_t vendor1 : 1; // vendor specific 109 | uint8_t enclosure_service : 1; 110 | uint8_t reserved5 : 1; 111 | 112 | uint8_t vendor2 : 1; // vendor specific 113 | uint8_t cmd_queue : 1; 114 | uint8_t reserved6 : 6; 115 | 116 | uint8_t vid[8]; 117 | uint8_t pid[16]; 118 | uint8_t product_version[4]; 119 | }scsi_inquiry_std_response_t;]], 120 | { additional_length = {format="dec"}, 121 | peripheral_device_type = DEVICE_TYPE, 122 | vid = {format = "str"}, 123 | pid = {format = "str"}, 124 | product_version = {format = "str"}, 125 | }, true) 126 | 127 | local function parse_inquiry_std_data(cbw, data, context) 128 | local r = struct_inquiry_std_data:build(data, "Inquiry Std Response") 129 | return r.html 130 | end 131 | 132 | local function parse_inquiry_vpd_data(cbw, data, context) 133 | local len = unpack(">I2", data, 3) 134 | local vpd = unpack(">I1", data, 2) 135 | local data_field = "" 136 | if vpd == VPD_CODE.VPD_SUPPORTED_VPDS then 137 | for i=1,len do 138 | data_field = data_field .. "uint8_t VPD; \n" 139 | end 140 | else 141 | data_field = "uint8_t data[]; \n" 142 | end 143 | local struct_temp = html.create_struct([[ 144 | typedef struct 145 | { 146 | uint8_t peripheral_device_type: 5; /**< \ref scsi_peripheral_device_type */ 147 | uint8_t peripheral_qualifier : 3; 148 | uint8_t page_code; 149 | uint16_t length; 150 | ]]..data_field..[[ 151 | }scsi_inquiry_vpd_response_t;]], 152 | { length = {format="dec"}, 153 | peripheral_device_type = DEVICE_TYPE, 154 | VPD = VPD_NAME, 155 | page_code = VPD_NAME, 156 | }, true); 157 | local r = struct_temp:build(data, "Inquiry VPD Response") 158 | return r.html 159 | end 160 | 161 | local struct_inquiry_cmd = html.create_struct([[ 162 | typedef struct 163 | { 164 | uint8_t cmd_code; /**< SCSI OpCode for \ref SCSI_CMD_INQUIRY */ 165 | uint8_t EVPD: 1; /**< Enable Vital Product Data */ 166 | uint8_t CMDDT: 1; /**< Command Support Data */ 167 | uint8_t reserved1: 6; 168 | uint8_t page_code; /**< Page code */ 169 | uint8_t length[2]; /**< allocate length for IN data */ 170 | uint8_t control; /**< */ 171 | }scsi_inquiry_cmd_t;]], 172 | { length = {format="dec"}}, true) 173 | 174 | local function parse_inquiry_cmd(cbw, cmd, context) 175 | local r = struct_inquiry_cmd:build(cmd, "Inquiry") 176 | if (r.EVPD & 0x01) == 0 then 177 | cbw.data_parser = parse_inquiry_std_data 178 | r.name = "Inquiry Std" 179 | else 180 | cbw.data_parser = parse_inquiry_vpd_data 181 | r.name = "Inquiry VPD" 182 | end 183 | return r 184 | end 185 | 186 | local struct_request_sense_data = html.create_struct([[ 187 | typedef struct 188 | { 189 | uint8_t response_code : 7; 190 | uint8_t valid : 1; 191 | 192 | uint8_t reserved1; 193 | 194 | uint8_t sense_key : 4; 195 | uint8_t reserved2 : 1; 196 | uint8_t incorrect_length: 1; /**< Incorrect length indicator */ 197 | uint8_t end_of_medium : 1; 198 | uint8_t filemark : 1; 199 | 200 | uint8_t information[4]; 201 | uint8_t add_sense_len; 202 | uint8_t command_specific_info[4]; 203 | uint8_t add_sense_code; 204 | uint8_t add_sense_qualifier; 205 | uint8_t field_replaceable_unit_code; 206 | 207 | uint8_t sense_ks_2:7; 208 | uint8_t sense_ks_valid:1; 209 | 210 | uint8_t sense_ks_1; 211 | uint8_t sense_ks_0; 212 | 213 | } scsi_sense_fixed_resp_t;]], 214 | { 215 | add_sense_len = { 216 | format="dec" 217 | }, 218 | response_code = { 219 | [0x70] = "FIX_CURRENT", 220 | [0x71] = "FIX_DEFERRED", 221 | [0x72] = "DESC_CURRENT", 222 | [0x73] = "DESC_DEFERRED", 223 | }, 224 | sense_key = { 225 | [0x00] = "NONE", 226 | [0x01] = "RECOVERED_ERROR", 227 | [0x02] = "NOT_READY", 228 | [0x03] = "MEDIUM_ERROR", 229 | [0x04] = "HARDWARE_ERROR", 230 | [0x05] = "ILLEGAL_REQUEST", 231 | [0x06] = "UNIT_ATTENTION", 232 | [0x07] = "DATA_PROTECT", 233 | [0x08] = "FIRMWARE_ERROR", 234 | [0x0b] = "ABORTED_COMMAND", 235 | [0x0c] = "EQUAL", 236 | [0x0d] = "VOLUME_OVERFLOW", 237 | [0x0e] = "MISCOMPARE", 238 | }, 239 | add_sense_code={ 240 | [0x20] = "INVALID_CDB", 241 | [0x24] = "INVALID_FIELED_IN_COMMAND", 242 | [0x1A] = "PARAMETER_LIST_LENGTH_ERROR", 243 | [0x26] = "INVALID_FIELD_IN_PARAMETER_LIST", 244 | [0x21] = "ADDRESS_OUT_OF_RANGE", 245 | [0x3A] = "MEDIUM_NOT_PRESENT", 246 | [0x28] = "MEDIUM_HAVE_CHANGED", 247 | [0x27] = "WRITE_PROTECTED", 248 | [0x11] = "UNRECOVERED_READ_ERROR", 249 | [0x03] = "WRITE_FAULT", 250 | } 251 | }, true) 252 | 253 | local function parse_request_sense_data(cbw, data, context) 254 | local r = struct_request_sense_data:build(data, "Sense Response") 255 | return r.html 256 | end 257 | 258 | local struct_request_sense_cmd = html.create_struct( [[ 259 | typedef struct 260 | { 261 | uint8_t cmd_code; 262 | uint8_t descriptor:1; 263 | uint8_t reserved1: 7; 264 | uint8_t reserved2; 265 | uint8_t reserved3; 266 | uint8_t length; 267 | uint8_t control; 268 | }scsi_request_sense_cmd_t;]], 269 | { length = {format="dec"}}, true) 270 | 271 | local function parse_request_sense_cmd(cbw, cmd, context) 272 | local r = struct_request_sense_cmd:build(cmd, "Request Sense") 273 | cbw.data_parser = parse_request_sense_data 274 | return r 275 | end 276 | 277 | local struct_read_format_cap_data = html.create_struct([[ 278 | struct { 279 | uint8_t unknown; 280 | uint8_t unknown; 281 | uint8_t unknown; 282 | uint8_t unknown; 283 | uint32_t block_count; 284 | uint8_t unknown; 285 | uint8_t bloc_size[3]; 286 | };]], 287 | { bloc_size = {format="hex"}}, true) 288 | 289 | local function parse_read_format_cap_data(cbw, data, context) 290 | local r = struct_read_format_cap_data:build(data, "Read Format Cap Data") 291 | return r.html 292 | end 293 | 294 | local struct_read_format_cap_cmd = html.create_struct([[ 295 | struct { 296 | uint8_t cmd_code; 297 | uint8_t data[]; 298 | };]], 299 | {}, true) 300 | 301 | local function parse_read_format_cap_cmd(cbw, cmd, context) 302 | local r = struct_read_format_cap_cmd:build(cmd, "Read Format Cap") 303 | cbw.data_parser = parse_read_format_cap_data 304 | return r 305 | end 306 | 307 | local struct_read_cap10_data = html.create_struct([[ 308 | typedef struct { 309 | uint8_t last_logical_block_address[4]; 310 | uint8_t block_size[4]; 311 | } scsi_read_capacity_10_resp_t; 312 | ]], {last_logical_block_address = {format="hex"}, 313 | block_size = {format="hex"},}, true) 314 | 315 | local function parse_read_cap10_data(cbw, data, context) 316 | local r = struct_read_cap10_data:build(data, "Read Cap 10 Data") 317 | return r.html 318 | end 319 | 320 | local struct_read_cap10_cmd = html.create_struct([[ 321 | typedef struct 322 | { 323 | uint8_t cmd_code; 324 | uint8_t reserved1; 325 | uint8_t logical_block_address[4]; 326 | uint8_t reserved2[2]; 327 | uint8_t partial_medium_indicator:1; 328 | uint8_t reserved3:7; 329 | uint8_t control; 330 | } scsi_read_capacity_10_cmd_t; 331 | ]], {}, true) 332 | 333 | local function parse_read_cap10_cmd(cbw, cmd, context) 334 | local r = struct_read_cap10_cmd:build(cmd, "Read Cap 10") 335 | cbw.data_parser = parse_read_cap10_data 336 | return r 337 | end 338 | 339 | local struct_mode_sense6_data = html.create_struct([[ 340 | typedef struct 341 | { 342 | uint8_t mode_data_length; 343 | uint8_t medium_type; 344 | 345 | uint8_t reserved1:4; 346 | uint8_t DPO_FUA:1; /**< [Disable Page Out] and [Force Unit Access] in the Read10 command is valid or not */ 347 | uint8_t reserved2:2; 348 | uint8_t write_protect:1; 349 | 350 | uint8_t block_desc_length; 351 | }scsi_mode_6_resp_header_t; 352 | ]],{ mode_data_length = {format = "dec"}, 353 | block_desc_length = {format = "dec"}, 354 | }, true) 355 | local function parse_mode_sense6_data(cbw, data, context) 356 | local r = struct_mode_sense6_data:build(data, "Mode Sense 6 Data") 357 | return r.html 358 | end 359 | 360 | local struct_mode_sense6_cmd = html.create_struct([[ 361 | typedef struct 362 | { 363 | uint8_t cmd_code; 364 | 365 | uint8_t reserved1:3; 366 | uint8_t disable_block_descriptor: 1; 367 | uint8_t reserved2:4; 368 | 369 | uint8_t page_code:6; 370 | uint8_t page_control:2; 371 | 372 | uint8_t subpage_code; 373 | uint8_t length; 374 | uint8_t control; 375 | }scsi_mode_sense_6_cmd_t; 376 | ]],{ length = {format = "dec"} }, true) 377 | local function parse_mode_sense6_cmd(cbw, cmd, context) 378 | local r = struct_mode_sense6_cmd:build(cmd, "Mode Sense 6") 379 | cbw.data_parser = parse_mode_sense6_data 380 | return r 381 | end 382 | 383 | local function parse_rw_10_data(cbw, data, context) 384 | return "

Read/Write Raw Data

See data Window
" 385 | end 386 | 387 | local struct_rw_10_cmd = html.create_struct([[ 388 | typedef struct 389 | { 390 | uint8_t cmd_code; 391 | uint8_t reserved1:2; 392 | uint8_t RARC:1; /**< rebuild assist recovery control */ 393 | uint8_t FUA:1; /**< Force Unit Access */ 394 | uint8_t DPO:1; /**< Disable Page Out */ 395 | uint8_t protect_info:3; 396 | uint8_t logical_block_addr[4]; 397 | uint8_t group_number:5; 398 | uint8_t reserved2:3; 399 | uint8_t transfer_length[2]; 400 | uint8_t control; 401 | }scsi_read_10_cmd_t, scsi_write_10_cmd_t; 402 | ]], { logical_block_addr = {format="hex"}, 403 | transfer_length = {format="hex"} 404 | }, true) 405 | 406 | local function parse_rw_10_cmd(cbw, cmd, context, isRead) 407 | return struct_rw_10_cmd:build(cmd, isRead and "Read 10" or "Write 10") 408 | end 409 | local function parse_read_10_cmd(cbw, cmd, context) 410 | local r = parse_rw_10_cmd(cbw, cmd, context, true) 411 | cbw.data_parser = parse_rw_10_data 412 | return r 413 | end 414 | 415 | local function parse_write_10_cmd(cbw, cmd, context) 416 | local r = parse_rw_10_cmd(cbw, cmd, context, false) 417 | cbw.data_parser = parse_rw_10_data 418 | return r 419 | end 420 | 421 | local struct_test_unit_ready = html.create_struct([[ 422 | typedef struct 423 | { 424 | uint8_t cmd_code; 425 | };]], {}, true) 426 | 427 | local function parse_test_unit_ready_cmd(cbw, cmd, context) 428 | return struct_test_unit_ready:build(cmd, "Test Unit Ready") 429 | end 430 | 431 | local struct_start_stop_unit = html.create_struct([[ 432 | typedef struct 433 | { 434 | uint8_t cmd_code; 435 | 436 | uint8_t immediate:1; 437 | uint8_t reserved1:7; 438 | 439 | uint8_t reserved2; 440 | 441 | uint8_t power_cond_modifier:4; 442 | uint8_t reserved3:4; 443 | 444 | uint8_t start:1; 445 | uint8_t load_eject:1; 446 | uint8_t no_flush:1; 447 | uint8_t reserved4:1; 448 | uint8_t power_cond:4; /** Power Condition */ 449 | 450 | uint8_t control; 451 | }scsi_start_stop_cmd_t;]], { 452 | power_cond = { 453 | [0x00] = "START_VALID", 454 | [0x01] = "ACTIVE", 455 | [0x02] = "IDLE", 456 | [0x03] = "STANDBY", 457 | [0x07] = "LU_CONTROL", 458 | [0x0a] = "FORCE_IDLE_0", 459 | [0x0b] = "FORCE_STANDBY_0", 460 | } 461 | }, true) 462 | 463 | local function parse_start_stop_unit_cmd(cbw, cmd, context) 464 | return struct_start_stop_unit:build(cmd, "Start/Stop Unit") 465 | end 466 | 467 | local scsi_cmd = { 468 | [SCSI_CMD_INQUIRY] = parse_inquiry_cmd, 469 | [SCSI_CMD_REQUEST_SENSE] = parse_request_sense_cmd, 470 | [SCSI_CMD_READ_FORMAT_CAPACITY] = parse_read_format_cap_cmd, 471 | [SCSI_CMD_READ_CAPACITY_10] = parse_read_cap10_cmd, 472 | [SCSI_CMD_MODE_SENSE_6] = parse_mode_sense6_cmd, 473 | [SCSI_CMD_READ_10] = parse_read_10_cmd, 474 | [SCSI_CMD_WRITE_10] = parse_write_10_cmd, 475 | [SCSI_CMD_TEST_UNIT_READY] = parse_test_unit_ready_cmd, 476 | [SCSI_CMD_START_STOP_UNIT] = parse_start_stop_unit_cmd, 477 | } 478 | 479 | local function parse_scsi_cmd(cbw, cmd, context) 480 | if #cmd < 1 then 481 | return { 482 | name = "SCSI Wrong", 483 | html = "

SCSI command data length wrong

" 484 | } 485 | end 486 | local cmd_code = unpack("I1", cmd) 487 | local scsi_cmd_parser = scsi_cmd[cmd_code] 488 | if not scsi_cmd_parser then 489 | return { 490 | name = "SCSI Unknown", 491 | html = "

Unknown SCSI command: "..fmt("0x%02X", cmd_code).."

" 492 | } 493 | end 494 | return scsi_cmd_parser(cbw, cmd, context) 495 | end 496 | 497 | local struct_cbw = html.create_struct([[ 498 | struct { 499 | uint32_t dCBWSignature; 500 | uint32_t dCBWTag; 501 | uint32_t dCBWDataTransferLength; 502 | // bmCBWFlags 503 | uint8_t Reserved:6; 504 | uint8_t Obsolete:1; 505 | uint8_t Direction:1; // {[0] = "Host to Device", [1] = "Device to Host"} 506 | uint8_t bCBWLUN; 507 | uint8_t bCBWCBLength; 508 | uint8_t CBWCB[16]; 509 | } 510 | ]]) 511 | 512 | local struct_csw = html.create_struct([[ 513 | struct { 514 | uint32_t dCSWSignature; 515 | uint32_t dCSWTag; 516 | uint32_t dCSWDataResidue; 517 | uint8_t bCSWStatus; 518 | }]], { 519 | bCSWStatus = { 520 | [0] = "Passed", 521 | [1] = "Failed", 522 | [2] = " Phase Error", 523 | format = "dec", 524 | } 525 | }) 526 | 527 | 528 | local function parse_csw(cbw, data, context) 529 | local csw = {} 530 | csw.status = "error" 531 | csw.name = "CSW" 532 | if #data ~= 13 then 533 | csw.html = "

Wrong CSW length

" 534 | return csw 535 | end 536 | local r = struct_csw:build(data, "Command Status Wrapper (CSW)") 537 | csw.html = r.html 538 | if r.bCSWStatus == 0 then 539 | csw.status = "success" 540 | end 541 | return csw 542 | end 543 | 544 | local function parse_cbw(data, context) 545 | local tb = {} 546 | local cbw = {} 547 | cbw.name = "CBW" 548 | if #data ~= 31 then 549 | cbw.html = "

Wrong CBW length

" 550 | return cbw 551 | end 552 | cbw = struct_cbw:build(data, "Command Block Wrapper (CBW)") 553 | local cmd = data:sub(16) 554 | cbw.scsi = parse_scsi_cmd(cbw, cmd, context) 555 | cbw.name = "CBW" 556 | if cbw.scsi and cbw.scsi.html then 557 | cbw.name = cbw.scsi.name 558 | cbw.html = cbw.html .. cbw.scsi.html 559 | end 560 | return cbw 561 | end 562 | 563 | 564 | function scsi.parse_cmd(data, context) 565 | return parse_cbw(data, context) 566 | end 567 | 568 | function scsi.parse_data(cbw, data, context) 569 | if cbw.data_parser then 570 | return cbw.data_parser(cbw, data, context) 571 | end 572 | return "

Unknown SCSI DATA

" 573 | end 574 | 575 | function scsi.parse_status(cbw, data, context) 576 | return parse_csw(cbw, data, context) 577 | end 578 | 579 | 580 | 581 | 582 | package.loaded["decoder_scsi"] = scsi 583 | -------------------------------------------------------------------------------- /scripts/decoder_video_payload_mjpeg.lua: -------------------------------------------------------------------------------- 1 | -- decoder_video_payload_mjpeg.lua 2 | -- Video class definition https://www.usb.org/sites/default/files/USB_Video_Class_1_5.zip 3 | -- MJPEG payload definition in USB_Video_Payload_MJPEG_1.5.pdf 4 | 5 | local html = require("html") 6 | local decoder = {} 7 | 8 | local struct_VS_FORMAT_MJPEG = html.create_struct([[ 9 | uint8_t bLength; 10 | uint8_t bDescriptorType; // CS_INTERFACE 11 | uint8_t bDescriptorSubtype; // VS_FORMAT_MJPEG 12 | uint8_t bFormatIndex; 13 | uint8_t bNumFrameDescriptors; 14 | // bmFlags 15 | uint8_t FixedSizeSamples:1; 16 | uint8_t reserved:7; 17 | uint8_t bDefaultFrameIndex; 18 | uint8_t bAspectRatioX; 19 | uint8_t bAspectRatioY; 20 | // bmInterlaceFlags 21 | uint8_t Interlaced_stream_or_variable:1; // {[0] = "No", [1] = "Yes"} 22 | uint8_t Fields_per_frame:1; // {[0] = "2 fields", [1] = "1 fields"} 23 | uint8_t Field_1_first:1; // {[0] = "No", [1] = "Yes"} 24 | uint8_t Reserved1:1; 25 | uint8_t Field_pattern:2; // {[0] = "Field 1 only", [1] = "Field 2 only", [2] = "Regular pattern of fields 1 and 2", [3] = "Random pattern of fields 1 and 2"} 26 | uint8_t Reserved2:2; 27 | uint8_t bCopyProtect; // {[0] = "No restrictions", [1] = "Restrict duplication"} 28 | ]]) 29 | 30 | local struct_VS_FRAME_MJPEG = html.create_struct([[ 31 | uint8_t bLength; 32 | uint8_t bDescriptorType; // CS_INTERFACE 33 | uint8_t bDescriptorSubtype; // VS_FRAME_MJPEG 34 | uint8_t bFrameIndex; 35 | // bmCapabilities 36 | uint8_t Still_image_supported:1; 37 | uint8_t Fixed_frame_rate:1; 38 | uint8_t reserved:6; 39 | uint16_t wWidth; // {format = "dec"} 40 | uint16_t wHeight; // {format = "dec"} 41 | uint32_t dwMinBitRate; // {format = "dec"} 42 | uint32_t dwMaxBitRate; // {format = "dec"} 43 | uint32_t dwMaxVideoFrameBufferSize; // {format = "dec"} 44 | uint32_t dwDefaultFrameInterval; // {format = "dec"} 45 | uint8_t bFrameIntervalType; // { [0] = "Continuous 1:min,2:max,3:step" } 46 | { 47 | uint32_t dwFrameInterval; 48 | }[ (bFrameIntervalType == 0) and 3 or bFrameIntervalType ]; 49 | 50 | ]]) 51 | 52 | decoder.video_as = {} 53 | decoder.video_decoder = {} 54 | 55 | decoder.video_as[0x06] = function(data, offset, context) 56 | return struct_VS_FORMAT_MJPEG:build(data:sub(offset), "VS Interface MJPEG format Descriptor") 57 | end 58 | decoder.video_as[0x07] = function(data, offset, context) 59 | return struct_VS_FRAME_MJPEG:build(data:sub(offset), "VS Interface MJPEG frame Descriptor") 60 | end 61 | 62 | local function fix_length(len) 63 | return function() 64 | return len 65 | end 66 | end 67 | 68 | local function get_tag(data, offset) 69 | if offset+1 <= #data then 70 | return data:byte(offset)*256 + data:byte(offset+1) 71 | end 72 | return 0x0000 73 | end 74 | 75 | local function get_length(data, offset) 76 | return data:byte(offset) * 256 + data:byte(offset+1) 77 | end 78 | 79 | local jpeg_tag = { 80 | [0xffc0] = {"SOF Start of Frame", get_length }, 81 | [0xffc4] = {"DHT Define Huffman Table", get_length }, 82 | [0xffd0] = {"RST Restart count", fix_length(0) }, 83 | [0xffd8] = {"SOI Start of image", fix_length(0) }, 84 | [0xffd9] = {"EOI End of Image", fix_length(0) }, 85 | [0xffda] = {"SOS Start of Scan", get_length }, 86 | [0xffdb] = {"DQT Define Quantization Table", get_length }, 87 | [0xffdd] = {"DRI Define Restart Interval", fix_length(4) }, 88 | [0xffe0] = {"APP Application Marker", get_length }, 89 | [0xfffe] = {"COM Comment", get_length }, 90 | } 91 | 92 | for i=1,0x0f do 93 | jpeg_tag[0xffe0+i] = jpeg_tag[0xffe0] 94 | end 95 | for i=1,0x07 do 96 | jpeg_tag[0xffd0+i] = jpeg_tag[0xffd0] 97 | end 98 | 99 | 100 | decoder.video_decoder[0x06] = { 101 | name = "MJPEG", 102 | decode = function(data, context) 103 | local tb = { 104 | title = "MJPEG Frame - JPEG data", 105 | header = {"Seg", "Offset", "Len", "Description"}, 106 | } 107 | local i = 1 108 | while i < #data do 109 | local tag_v = get_tag(data, i) 110 | local tag = jpeg_tag[tag_v] 111 | i = i + 2 112 | if tag then 113 | local data_len = tag[2](data, i) 114 | tb[#tb+1] = { tag[1]:sub(1,3), string.format("0x%x", i - 3), data_len, string.format("(%04x)", tag_v) .. tag[1]:sub(4) } 115 | i = i + data_len 116 | if tag_v == 0xffda then 117 | local tt = data:find("\xff\xd9", i) 118 | tb[#tb+1] = {"Image Data", string.format("0x%x", i - 1), tt-i+1, "Entropy coded image data"} 119 | i = tt 120 | end 121 | end 122 | end 123 | return html.make_table(tb) .. "

Save the data as xxx.jpg to view it

" 124 | end 125 | } 126 | 127 | package.loaded["decoder_video_payload_mjpeg"] = decoder 128 | -------------------------------------------------------------------------------- /scripts/file_base.lua: -------------------------------------------------------------------------------- 1 | -- file_base.lua 2 | -- encoding: utf-8 3 | --[[ 4 | there are 3 API for file read/write operation 5 | the read/write operation running in a separator with different lua context 6 | 7 | 8 | valid_filter() 9 | get the valid filter, return a filter string in Qt file filter format, e.g. "Wireshark Capture file (*.pcap);;All files (*.*)" 10 | 11 | 12 | -- read the packet from a file 13 | -- name file name 14 | -- open_handler called when got a new packet 15 | -- context internal use, just passed to open_handler 16 | -- return value total read packet count 17 | open_file(name, open_handler, context) 18 | 19 | -- when got a new packet call this function 20 | -- context passed from open_file 21 | -- ts timestamp in second 22 | -- nano timestamp in nano second 23 | -- pkt raw pkt data 24 | -- cur current read position 25 | -- total total file size 26 | open_handler(context, ts, nano, pkt, cur, total) 27 | 28 | -- write the packet into a file 29 | -- name file name 30 | -- write_handler call this function to get a packet, if return nil means no more packets 31 | -- context internal use, just passed to write_handler 32 | -- return value total write packet count 33 | write_file(name, write_handler, context) 34 | 35 | -- when got a new packet call this function 36 | -- context passed from open_file 37 | -- return value(ts,nano,pkt) 38 | -- ts timestamp in second 39 | -- nano timestamp in nano second 40 | -- pkt raw pkt data 41 | ts, nano, pkt = write_handler(context) 42 | ]] 43 | 44 | local handlers = {} 45 | 46 | function toHex(data) 47 | local res = "" 48 | if not data then return "" end 49 | for i=1,#data do 50 | res = res .. string.format( "%x", data:byte(i)) 51 | end 52 | return res 53 | end 54 | 55 | function register_file_handler(fh) 56 | assert(fh and fh.name, "file handler must have a name") 57 | if not handlers[fh.name] then 58 | handlers[#handlers+1] = fh 59 | handlers[fh.name] = fh 60 | else 61 | log(fh.name, "already exsit") 62 | end 63 | end 64 | 65 | function open_file(name, packet_handler, context) 66 | local file, e = io.open(name, "rb") 67 | assert(file ,e) 68 | local fh = nil 69 | local totalLen = file:seek("end") 70 | 71 | for i,v in ipairs(handlers) do 72 | file:seek("set", 0) 73 | if v.init_read and v:init_read(file) then 74 | fh = v 75 | break 76 | end 77 | end 78 | local pkt_count = 0 79 | while fh do 80 | local ts, nano, pkt, status = fh:read_packet(file) 81 | if ts and nano and pkt then 82 | packet_handler(context, ts, nano, pkt, status or 0, file:seek(), totalLen) 83 | pkt_count = pkt_count + 1 84 | else 85 | break 86 | end 87 | end 88 | file:close() 89 | assert(fh, "Unknown file format") 90 | return pkt_count 91 | end 92 | 93 | function write_file(name, packet_handler, context) 94 | local p1, p2 = string.find(name, "%.%w+$") 95 | local fh = nil 96 | if p1 and p2 then 97 | local ext = name:sub(p1,p2) 98 | for i,v in ipairs(handlers) do 99 | if string.find(v.extension, ext) then 100 | fh = v 101 | end 102 | end 103 | end 104 | if not fh then return error("Unknown file format to write " .. name) end 105 | local file, e = io.open(name, "w+b") 106 | assert(file ,e) 107 | local pkt_count = 0 108 | if fh.init_write and fh:init_write(file) then 109 | while true do 110 | local ts, nano, pkt, status = packet_handler(context) 111 | local res = nil 112 | if ts and nano and pkt then 113 | res = fh:write_packet(file, ts, nano, pkt, status or 0) 114 | end 115 | if not res then 116 | break 117 | end 118 | pkt_count = pkt_count + 1 119 | end 120 | end 121 | 122 | file:close() 123 | return pkt_count 124 | end 125 | 126 | function valid_filter() 127 | local res = "" 128 | local sep = "" 129 | for i,v in ipairs(handlers) do 130 | if v.extension then 131 | local n = v.description or v.name 132 | res = res .. sep .. n.." (" .. v.extension .. ")" 133 | sep = ";;" 134 | end 135 | end 136 | res = res .. sep .. "All files (*.*)" 137 | return res 138 | end 139 | 140 | package.loaded["file_base"] = "file_base" 141 | -------------------------------------------------------------------------------- /scripts/file_iti1480a.lua: -------------------------------------------------------------------------------- 1 | -- file_iti1480a.lua 2 | -- encoding: utf-8 3 | require("file_base") 4 | require("file_pcap") -- not real used, just make sure pcap_file is the first format 5 | local unpack = string.unpack 6 | local pack = string.pack 7 | local fmt = string.format 8 | 9 | local ITI1480A_handler = { 10 | name = "ITI1480A", 11 | description = "ITI1480A file", 12 | extension = "*.usb", 13 | init_read = function(self, file) 14 | self.ts_utc = 0 15 | self.ts_offset = 0 16 | local ts = file:read(4) 17 | if unpack("I2", file:read(2)) == 0xc040 then 18 | file:seek("set", 0) 19 | return true 20 | end 21 | return false 22 | end, 23 | read_packet = function(self, file) 24 | local ts = file:read(4) 25 | if ts then 26 | local a,b,c,d = unpack("I1I1I1I1", ts) 27 | ts = (a<<4) | (b &0x0f) | (c<<20) | (d<<12) 28 | ts = ts * 1000 / 60 29 | else 30 | return nil 31 | end 32 | 33 | local data = file:read(2) 34 | if data then data = unpack("I2", data) end 35 | local v = "" 36 | while true do 37 | data = file:read(2) 38 | if data then data = unpack("I2", data) end 39 | if data and data ~= 0xc000 then 40 | v = v .. string.char(data & 0xff) 41 | else 42 | break 43 | end 44 | end 45 | if ts and v then 46 | self.ts_offset = self.ts_offset + ts 47 | while self.ts_offset > 1000000000 do 48 | self.ts_utc = self.ts_utc + 1 49 | self.ts_offset = self.ts_offset - 1000000000 50 | end 51 | return self.ts_utc, math.floor(self.ts_offset + 0.5), v, 0 52 | end 53 | return nil 54 | end, 55 | init_write = function(self, file) 56 | return true 57 | end, 58 | write_packet = function(self, file, ts, nano, pkt, status) 59 | self.last_ts = self.last_ts or (ts*1e9 + nano) 60 | local cur_ts = ts*1e9 + nano 61 | local d_ts = math.floor( (cur_ts - self.last_ts)*60/1000 + 0.5) 62 | local a = (d_ts >> 4) & 0xff 63 | local b = (d_ts & 0x0f) | 0x30 64 | local c = (d_ts >> 20) & 0xff 65 | local d = (d_ts >> 12) & 0xff 66 | file:write(pack("I1I1I1I1",a,b,c,d)) 67 | file:write("\x40\xc0") 68 | for i=1,#pkt do 69 | file:write(pkt:sub(i,i), "\x80") 70 | end 71 | file:write("\x00\xc0") 72 | self.last_ts = cur_ts 73 | return true 74 | end 75 | } 76 | 77 | register_file_handler(ITI1480A_handler) 78 | 79 | package.loaded["file_iti1480a"] = "file_iti1480a" 80 | -------------------------------------------------------------------------------- /scripts/file_pcap.lua: -------------------------------------------------------------------------------- 1 | -- file_pcap.lua 2 | -- encoding: utf-8 3 | require("file_base") 4 | local unpack = string.unpack 5 | local pack = string.pack 6 | local fmt = string.format 7 | local DLT_USBLL = 288 8 | local PCAP_MS_MAGIC = 0xa1b2c3d4 9 | local PCAP_NANO_MAGIC = 0xa1b23c4d 10 | local pcap_handler = { 11 | name = "pcap", 12 | description = "wireshark pcap file", 13 | extension = "*.pcap", 14 | init_read = function(self, file) 15 | local header = file:read(24) 16 | if not header then return false end 17 | if #header ~= 24 then return false end 18 | local magic = unpack("I4", header) 19 | local dlt = unpack("I4", header, 21) 20 | if magic == PCAP_MS_MAGIC and dlt == DLT_USBLL then 21 | self.mul = 1000 22 | return true 23 | elseif magic == PCAP_NANO_MAGIC and dlt == DLT_USBLL then 24 | self.mul = 1 25 | return true 26 | end 27 | return false 28 | end, 29 | read_packet = function(self, file) 30 | local t = file:read(16) 31 | if not t then return nil end 32 | if #t < 16 then return nil end 33 | local ts, nano, act_len, org_len = unpack("I4I4I4I4", t) 34 | local pkt = file:read(act_len) 35 | if not pkt then return nil end 36 | if #pkt ~= act_len then return nil end 37 | return ts, nano*self.mul, pkt, 0 38 | end, 39 | init_write = function(self, file) 40 | file:write(pack("I4", PCAP_NANO_MAGIC)) 41 | file:write("\x02\x00\x04\x00") 42 | file:write(pack("I4I4I4I4", 0,0,65535, DLT_USBLL)) 43 | return true 44 | end, 45 | write_packet = function(self, file, ts, nano, pkt, status) 46 | file:write(pack("I4I4I4I4",ts,nano,#pkt,#pkt), pkt) 47 | return true 48 | end 49 | } 50 | 51 | register_file_handler(pcap_handler) 52 | 53 | package.loaded["file_pcap"] = "file_pcap" 54 | -------------------------------------------------------------------------------- /scripts/file_text.lua: -------------------------------------------------------------------------------- 1 | require("file_base") 2 | local col_w = { 5,5,4,24,14,8 } 3 | local col_name = {"Index","PID","Len","Data(Hex)","Description","Delta"} 4 | 5 | function toCol(s, colWidth, pad) 6 | pad = pad or ' ' 7 | s = tostring(s) 8 | if #s> 7) & 0xf 32 | r[4] = "ADDR: " .. addr .. '.' .. ep 33 | end 34 | return r 35 | end 36 | end 37 | local function parse_handshake(name) 38 | return function(pkt) 39 | return {name} 40 | end 41 | end 42 | 43 | local function toHex(data) 44 | local res = "" 45 | local sep = "" 46 | if #data<=8 then 47 | for i=1,#data do 48 | res = res .. sep .. string.format("%02X", data:byte(i)) 49 | if i == 4 then res = res .. ' ' end 50 | sep = ' ' 51 | end 52 | return res 53 | end 54 | 55 | local rt = {} 56 | while #data>0 do 57 | local t = data:sub(1,8) 58 | rt[#rt+1] = toHex(t) 59 | if #t < 8 then break end 60 | data = data:sub(9) 61 | end 62 | return rt 63 | end 64 | 65 | local function parse_data(name) 66 | return function(pkt) 67 | return {name, #pkt-3, toHex(pkt:sub(2,#pkt-2))} 68 | end 69 | end 70 | 71 | local function parse_split(name) 72 | return function(pkt) 73 | pkt = pkt .. "\x00\x00\x00\x00" 74 | local res = {name,"",""} 75 | local hub, port, crc = unpack("I1I1I1", pkt, 2) 76 | local addr = hub & 0x7f 77 | local port = port & 0x7f 78 | res[4] = "Hub " .. addr..":"..port 79 | return res 80 | end 81 | end 82 | 83 | local pid_map = { 84 | [0xe1] = parse_token("OUT"), -- OUT 85 | [0x69] = parse_token("IN"), -- IN 86 | [0xa5] = parse_token("SOF"), -- SOF 87 | [0x2d] = parse_token("SETUP"), -- SETUP 88 | [0xb4] = parse_token("PING"), -- PING 89 | 90 | [0xd2] = parse_handshake("ACK"), -- ACK 91 | [0x5a] = parse_handshake("NAK"), -- NAK 92 | [0x1e] = parse_handshake("STALL"), -- STALL 93 | [0x96] = parse_handshake("NYET"), -- NYET 94 | 95 | [0xc3] = parse_data("DATA0"), -- DATA0 96 | [0x4b] = parse_data("DATA1"), -- DATA1 97 | [0x87] = parse_data("DATA2"), -- DATA2 98 | [0x0f] = parse_data("MDATA"), -- MDATA 99 | 100 | [0x3c] = parse_handshake("PRE"), -- PRE_ERR 101 | [0x78] = parse_split("SPLIT"), -- SPLIT 102 | } 103 | 104 | local function dt2str(dt) 105 | if dt>1000000000*60 then 106 | return string.format("%.2f m", dt/(1000000000*60)) 107 | elseif dt>1000000000 then 108 | return string.format("%.2f s", dt/1000000000) 109 | elseif dt>1000000 then 110 | return string.format("%.2f ms", dt/1000000) 111 | elseif dt>1000 then 112 | return string.format("%.2f us", dt/1000) 113 | end 114 | return string.format("%.2f ns", dt) 115 | end 116 | 117 | local text_handler = { 118 | name = "text", 119 | description = "export as text file", 120 | extension = "*.txt", 121 | init_write = function(self, file) 122 | file:write("USB Packet Viewer text file\n"); 123 | file:write("Date: ", os.date(),"\n\n") 124 | self.writeRow = function(rowData, pad) 125 | writeRow(file, rowData, pad) 126 | end 127 | self.writeRow(col_name); 128 | self.writeRow({},'-'); 129 | file:write("\n") 130 | self.index = 0 131 | self.lastTime = nil 132 | return true 133 | end, 134 | write_packet = function(self, file, ts, nano, pkt, status) 135 | if #pkt > 0 and pid_map[pkt:byte(1)] then 136 | self.index = self.index + 1 137 | local dt = '0' 138 | local curT = ts*1000000000 + nano 139 | if self.lastTime then 140 | local delta = curT - self.lastTime 141 | dt = dt2str(delta) 142 | self.lastTime = curT 143 | else 144 | self.lastTime = curT 145 | end 146 | local r = pid_map[pkt:byte(1)](pkt) 147 | if type(r[3]) == 'table' then 148 | for i=1,#r[3] do 149 | self.writeRow({ 150 | self.index, 151 | r[1],r[2],r[3][i],r[4], 152 | dt 153 | }) 154 | r[1]='' 155 | r[2]='' 156 | r[4]='' 157 | end 158 | else 159 | self.writeRow({ 160 | self.index, 161 | r[1],r[2],r[3],r[4], 162 | dt 163 | }) 164 | end 165 | end 166 | return true 167 | end 168 | } 169 | 170 | register_file_handler(text_handler) 171 | package.loaded["file_text"] = "file_text" 172 | -------------------------------------------------------------------------------- /scripts/file_transaction_parser.lua: -------------------------------------------------------------------------------- 1 | -- transaction parser in lua 2 | local VALID_ADDR = {14} 3 | local VALID_EP = {3} 4 | local VALID_TOKEN = {"IN"} 5 | local VALID_ACK = {"ACK"} 6 | local SHOW_DATA_DESC = false 7 | 8 | require("file_base") 9 | 10 | 11 | local VALID_ADDR_map = {} 12 | local VALID_EP_map = {} 13 | local VALID_TOKEN_map = {} 14 | local VALID_ACK_map = {} 15 | 16 | for k,v in ipairs(VALID_ADDR) do VALID_ADDR_map[v]=true end 17 | for k,v in ipairs(VALID_EP) do VALID_EP_map[v]=true end 18 | for k,v in ipairs(VALID_TOKEN) do VALID_TOKEN_map[v]=true end 19 | for k,v in ipairs(VALID_ACK) do VALID_ACK_map[v]=true end 20 | 21 | local function filter_data(self) 22 | if #VALID_ADDR > 0 then 23 | if not VALID_ADDR_map[self.addr] then 24 | return false 25 | end 26 | end 27 | 28 | if #VALID_EP > 0 then 29 | if not VALID_EP_map[self.ep] then 30 | return false 31 | end 32 | end 33 | 34 | if #VALID_TOKEN > 0 then 35 | if not VALID_TOKEN_map[self.token] then 36 | return false 37 | end 38 | end 39 | 40 | if #VALID_ACK > 0 then 41 | if not VALID_ACK_map[self.ack] then 42 | return false 43 | end 44 | end 45 | 46 | return true 47 | end 48 | 49 | 50 | local col_w = { 5,5,4,24,14,8 } 51 | local col_name = {"Index","PID","Len","Data(Hex)","Description","Delta"} 52 | 53 | 54 | function toCol(s, colWidth, pad) 55 | pad = pad or ' ' 56 | s = tostring(s) 57 | if #s> 7) & 0xf 81 | r[4] = "ADDR: " .. addr .. '.' .. ep 82 | end 83 | return r 84 | end 85 | end 86 | 87 | local S_IDLE = 0 88 | local S_SETUP = 1 89 | local S_DATA = 2 90 | local S_ACK = 3 91 | local function get_token(name) 92 | return function (pkt) 93 | pkt = pkt .. "\x00\x00\x00" 94 | local v = unpack("I2", pkt, 2) 95 | local addr = v & 0x7f 96 | local ep = (v >> 7) & 0xf 97 | return S_SETUP, name, addr, ep 98 | end 99 | end 100 | 101 | local function get_data(name) 102 | return function (pkt) 103 | return S_DATA, name, pkt:sub(2,#pkt-2) 104 | end 105 | end 106 | 107 | local function get_ack(name) 108 | return function (pkt) 109 | return S_ACK, name 110 | end 111 | end 112 | 113 | local function parse_handshake(name) 114 | return function(pkt) 115 | return {name} 116 | end 117 | end 118 | 119 | local function toHex(data, max) 120 | local res = "" 121 | local sep = "" 122 | if not max then 123 | max = 8 124 | end 125 | if #data<=max then 126 | for i=1,#data do 127 | res = res .. sep .. string.format("%02X", data:byte(i)) 128 | --if i == 4 then res = res .. ' ' end 129 | sep = ' ' 130 | end 131 | return res 132 | end 133 | 134 | local rt = {} 135 | while #data>0 do 136 | local t = data:sub(1,8) 137 | rt[#rt+1] = toHex(t) 138 | if #t < 8 then break end 139 | data = data:sub(9) 140 | end 141 | return rt 142 | end 143 | 144 | local function parse_data(name) 145 | return function(pkt) 146 | return {name, #pkt-3, toHex(pkt:sub(2,#pkt-2))} 147 | end 148 | end 149 | 150 | local function parse_split(name) 151 | return function(pkt) 152 | pkt = pkt .. "\x00\x00\x00\x00" 153 | local res = {name,"",""} 154 | local hub, port, crc = unpack("I1I1I1", pkt, 2) 155 | local addr = hub & 0x7f 156 | local port = port & 0x7f 157 | res[4] = "Hub " .. addr..":"..port 158 | return res 159 | end 160 | end 161 | 162 | -- local pid_map = { 163 | -- [0xe1] = parse_token("OUT"), -- OUT 164 | -- [0x69] = parse_token("IN"), -- IN 165 | -- [0xa5] = parse_token("SOF"), -- SOF 166 | -- [0x2d] = parse_token("SETUP"), -- SETUP 167 | -- [0xb4] = parse_token("PING"), -- PING 168 | 169 | -- [0xd2] = parse_handshake("ACK"), -- ACK 170 | -- [0x5a] = parse_handshake("NAK"), -- NAK 171 | -- [0x1e] = parse_handshake("STALL"), -- STALL 172 | -- [0x96] = parse_handshake("NYET"), -- NYET 173 | 174 | -- [0xc3] = parse_data("DATA0"), -- DATA0 175 | -- [0x4b] = parse_data("DATA1"), -- DATA1 176 | -- [0x87] = parse_data("DATA2"), -- DATA2 177 | -- [0x0f] = parse_data("MDATA"), -- MDATA 178 | 179 | -- [0x3c] = parse_handshake("PRE"), -- PRE_ERR 180 | -- [0x78] = parse_split("SPLIT"), -- SPLIT 181 | -- } 182 | 183 | local pid_map = { 184 | [0xe1] = get_token("OUT"), -- OUT 185 | [0x69] = get_token("IN"), -- IN 186 | --[0xa5] = parse_token("SOF"), -- SOF 187 | [0x2d] = get_token("SETUP"), -- SETUP 188 | --[0xb4] = parse_token("PING"), -- PING 189 | 190 | [0xd2] = get_ack("ACK"), -- ACK 191 | [0x5a] = get_ack("NAK"), -- NAK 192 | [0x1e] = get_ack("STALL"), -- STALL 193 | [0x96] = get_ack("NYET"), -- NYET 194 | 195 | [0xc3] = get_data("DATA0"), -- DATA0 196 | [0x4b] = get_data("DATA1"), -- DATA1 197 | [0x87] = get_data("DATA2"), -- DATA2 198 | [0x0f] = get_data("MDATA"), -- MDATA 199 | 200 | -- [0x3c] = parse_handshake("PRE"), -- PRE_ERR 201 | -- [0x78] = parse_split("SPLIT"), -- SPLIT 202 | } 203 | 204 | local function dt2str(dt) 205 | if dt>1000000000*60 then 206 | return string.format("%.2f m", dt/(1000000000*60)) 207 | elseif dt>1000000000 then 208 | return string.format("%.2f s", dt/1000000000) 209 | elseif dt>1000000 then 210 | return string.format("%.2f ms", dt/1000000) 211 | elseif dt>1000 then 212 | return string.format("%.2f us", dt/1000) 213 | end 214 | return string.format("%.2f ns", dt) 215 | end 216 | 217 | local text_handler = { 218 | name = "transaction parse", 219 | description = "export transaction data", 220 | extension = "*.tra", 221 | init_write = function(self, file) 222 | file:write("USB Packet Viewer transaction data\n"); 223 | file:write("Date: ", os.date(),"\n\n") 224 | -- self.writeRow = function(rowData, pad) 225 | -- writeRow(file, rowData, pad) 226 | -- end 227 | -- self.writeRow(col_name); 228 | -- self.writeRow({},'-'); 229 | -- file:write("\n") 230 | self.index = 0 231 | self.lastTime = nil 232 | self.state = S_IDLE 233 | self.data = "" 234 | self.addr = 0 235 | self.ep = 0 236 | self.token = "" 237 | return true 238 | end, 239 | write_packet2 = function(self, file, ts, nano, pkt, status) 240 | if #pkt > 0 and pid_map[pkt:byte(1)] then 241 | self.index = self.index + 1 242 | local dt = '0' 243 | local curT = ts*1000000000 + nano 244 | if self.lastTime then 245 | local delta = curT - self.lastTime 246 | dt = dt2str(delta) 247 | self.lastTime = curT 248 | else 249 | self.lastTime = curT 250 | end 251 | local r = pid_map[pkt:byte(1)](pkt) 252 | if type(r[3]) == 'table' then 253 | for i=1,#r[3] do 254 | self.writeRow({ 255 | self.index, 256 | r[1],r[2],r[3][i],r[4], 257 | dt 258 | }) 259 | r[1]='' 260 | r[2]='' 261 | r[4]='' 262 | end 263 | else 264 | self.writeRow({ 265 | self.index, 266 | r[1],r[2],r[3],r[4], 267 | dt 268 | }) 269 | end 270 | end 271 | return true 272 | end, 273 | write_packet = function(self,file,ts,nano,pkt, status) 274 | if #pkt<1 then 275 | return true 276 | end 277 | local act = pid_map[pkt:byte(1)] 278 | if not act then 279 | return true 280 | end 281 | local s, n, r1, r2 = act(pkt) 282 | if s == S_SETUP then 283 | if self.state == S_DATA then 284 | self.ack = "ISO" 285 | if filter_data(self) then 286 | if SHOW_DATA_DESC then 287 | file:write(self.token, " ", tostring(self.addr),".", tostring(self.ep), " ", self.ack, " len =", #self.data, "\n") 288 | end 289 | file:write(toHex(self.data, 2048), "\n") 290 | end 291 | --file:write(self.token, tostring(self.addr),".", tostring(self.ep)," ISO ", toHex(self.data, 2048), "\n") 292 | end 293 | self.token = n 294 | self.state = S_SETUP 295 | self.addr = r1 296 | self.ep = r2 297 | self.data = "" 298 | elseif s == S_DATA then 299 | if self.state == S_SETUP then 300 | self.data = r1 301 | self.state = S_DATA 302 | else 303 | self.state = S_IDLE 304 | end 305 | elseif s == S_ACK then 306 | self.ack = n 307 | if self.state == S_DATA then 308 | if filter_data(self) then 309 | if SHOW_DATA_DESC then 310 | file:write(self.token, " ", tostring(self.addr),".", tostring(self.ep), " ", self.ack, " len =", #self.data, "\n") 311 | end 312 | file:write(toHex(self.data, 2048), "\n") 313 | end 314 | --file:write(self.token, tostring(self.addr),".", tostring(self.ep)," "..n.." ", toHex(self.data, 2048), "\n") 315 | end 316 | self.state = S_IDLE 317 | end 318 | return true 319 | end 320 | } 321 | 322 | register_file_handler(text_handler) 323 | package.loaded["file_tran_parser"] = "file_tran_parser" 324 | -------------------------------------------------------------------------------- /scripts/html.lua: -------------------------------------------------------------------------------- 1 | 2 | local unpack = string.unpack 3 | local fmt = string.format 4 | local util = require("util") 5 | 6 | local r = {} 7 | 8 | r.make_table = function(t) 9 | local tw = nil 10 | if t.width then 11 | tw = 0 12 | for i,v in ipairs(t.width) do 13 | tw = tw + v 14 | end 15 | end 16 | local r = "" 17 | if t.title then 18 | r = r .. "

"..tostring(t.title) .. "

" 19 | end 20 | r = r .. '

' 21 | r = r .. '' 22 | local colCount = nil 23 | if t.header then 24 | for i,v in ipairs(t.header) do 25 | r = r .. '" 26 | end 27 | colCount = #t.header 28 | end 29 | colCount = colCount or (t[1] and #t[1] or 0) 30 | r = r .. "" 31 | for i,v in ipairs(t) do 32 | local trColor = (i&1)==1 and "odd" or "even" 33 | local trRowCount = 1 34 | local trRow = 1 35 | for j=1, colCount do 36 | if type(v[j]) == "table" then 37 | trRowCount = #v[j] 38 | end 39 | end 40 | while trRow <= trRowCount do 41 | r = r .. '' 42 | for j=1, colCount do 43 | local w = "" 44 | if t.width and t.width[j] then 45 | w = " width=" .. t.width[j] 46 | end 47 | local rowSpan = "" 48 | local cell = tostring(v[j]) 49 | if type(v[j]) ~= "table" then 50 | if trRowCount > 1 then 51 | if trRow == 1 then 52 | rowSpan = ' rowspan= "'..trRowCount..'" ' 53 | r = r .. '' .. cell .. "" 54 | end 55 | else 56 | r = r .. '' .. cell .. "" 57 | end 58 | else 59 | cell = tostring(v[j][trRow]) 60 | r = r .. '' .. cell .. "" 61 | end 62 | end 63 | r = r .. "" 64 | trRow = trRow + 1 65 | end 66 | end 67 | return r .. "

" 68 | end 69 | 70 | 71 | local function bfvalue(v, bits, mask, noMask) 72 | bits = bits or 8 73 | mask = mask or 0xffffffff 74 | local vStr = "" 75 | local bitMask = 1 76 | local rShift = 0 77 | local lsb = nil 78 | for i=1,bits do 79 | local t = (v & bitMask) == 0 and '0' or '1' 80 | if (mask & bitMask) == 0 then 81 | t = '.' 82 | rShift = i 83 | else 84 | lsb = lsb or rShift 85 | end 86 | vStr = t .. vStr 87 | if i~=bits and i % 8 == 0 then vStr = " " .. vStr end 88 | bitMask = bitMask << 1 89 | end 90 | lsb = lsb or rShift 91 | v = (v & mask) >> lsb 92 | if noMask then 93 | return string.format( "0x%0" .. math.floor(bits/4) .. "X", v), v 94 | end 95 | return vStr .. string.format( " (0x%0" .. math.floor(bits/4) .. "X)", v), v 96 | end 97 | 98 | local function fix(v) 99 | return "" .. v .. "" 100 | end 101 | 102 | r.expand_bit_field = function(value, bf, noMask) 103 | value = value or 0 104 | local bits = bf.bits or 8 105 | local name = bf.name or "" 106 | if bf.name == "" then 107 | name = "[bit fields]" 108 | end 109 | local nameCol = { name } 110 | local bs, bv = bfvalue(value,bf.bits, nil, noMask) 111 | local valueCol = { fix(bs) } 112 | local descCol = { "" } 113 | 114 | for i,v in ipairs(bf) do 115 | local bs, bv = bfvalue(value, bits, v.mask, noMask) 116 | local desc = v[bv] 117 | if not desc then desc = v.convertor and v.convertor(bv) end 118 | if not desc then desc = v.comment or "" end 119 | if v.name then 120 | nameCol[#nameCol+1] = " " .. (v.name or "") 121 | end 122 | valueCol[#valueCol+1] = fix(bs) 123 | descCol[#descCol+1] = desc 124 | end 125 | if #nameCol == 1 then nameCol = nameCol[1] end 126 | return {nameCol, valueCol, descCol} 127 | end 128 | 129 | local type2size = { 130 | uint8_t = 1, 131 | uint16_t = 2, 132 | uint24_t = 3, 133 | uint32_t = 4, 134 | 135 | int8_t = 1, 136 | int16_t = 2, 137 | int24_t = 3, 138 | int32_t = 4, 139 | } 140 | 141 | local function hexFmt(v, len, isBig) 142 | isBig = isBig and ">" or "<" 143 | if type(v) == "string" then 144 | local ss = len <= 4 and len or 4 145 | v = unpack(isBig .. "I" .. ss, v) 146 | end 147 | return fmt("0x%0" .. (len*2) .. "X", v) 148 | end 149 | 150 | local function decFmt(v, len, isBig) 151 | isBig = isBig and ">" or "<" 152 | if type(v) == "string" then 153 | v = unpack(isBig .. "I" .. len, v) 154 | end 155 | return fmt("%d", v) 156 | end 157 | 158 | local function signedFmt(v, len, isBig) 159 | isBig = isBig and ">" or "<" 160 | if type(v) == "string" then 161 | v = unpack(isBig .. "i" .. len, v) 162 | end 163 | return fmt("%d", v) 164 | end 165 | 166 | local function dataFmt(v, len) 167 | return util.toHex(v, " ", 8, "
") 168 | end 169 | 170 | local function strFmt(v, len) 171 | return tostring(v) 172 | end 173 | local function unicodeFmt(v, len) 174 | local r = "" 175 | for i=1, #v, 2 do 176 | local l,h = unpack("I1I1", v, i) 177 | if h == 0 then 178 | r = r .. string.char(l) 179 | else 180 | r = r .. "." 181 | end 182 | end 183 | return r 184 | end 185 | 186 | local function ipFmt(v, len) 187 | local res = "" 188 | local del = "" 189 | for i=1,#v do 190 | res = res .. del .. tonumber(v:byte(i)) 191 | del = "." 192 | end 193 | return res 194 | end 195 | 196 | local fmt2str = { 197 | [hexFmt] = "hex", 198 | [decFmt] = "dec", 199 | [dataFmt] = "data", 200 | [strFmt] = "str", 201 | [ipFmt] = "ip" 202 | } 203 | 204 | local function format_comment(x) 205 | x = string.gsub(x, "^%s+", "") 206 | x = string.gsub(x, "^/[/%*<]+", "") 207 | x = string.gsub(x, "^%s+", "") 208 | 209 | x = string.gsub(x, "%s+$", "") 210 | x = string.gsub(x, "[%*/]+$", "") 211 | x = string.gsub(x, "%s+$", "") 212 | return x 213 | end 214 | 215 | local function parse_comment(c) 216 | local res = load("return " .. c .. "\n\n") 217 | if type(res) == "function" then 218 | local r1, r2 = pcall(res) 219 | if r1 then return r2 end 220 | end 221 | return nil 222 | end 223 | 224 | local function format_name(r1) 225 | r1 = string.gsub(r1, "^%s*","") 226 | r1 = string.gsub(r1, "^[//%*]+","") 227 | r1 = string.gsub(r1, "[//%*%s]+$","") 228 | r1 = string.gsub(r1, "^struct","") 229 | r1 = string.gsub(r1, "^union","") 230 | r1 = string.gsub(r1, "^%s*","") 231 | r1 = string.gsub(r1, "[%s{}]*$","") 232 | return r1 233 | end 234 | 235 | local function push_rep(stack, r) 236 | local rep = { 237 | start = #r + 1; 238 | } 239 | stack[#stack + 1] = rep 240 | end 241 | 242 | local function dynamic_get_size(n, type_size) 243 | return function(res, data, offset) 244 | local def_res = #data 245 | if offset > #data then 246 | def_res = 0 247 | end 248 | 249 | if #n < 1 then return def_res end 250 | res.math = math 251 | local func, t = load("return (" .. n .. ")\n", "calc size: \"" .. n .. "\"", "tb", res) 252 | if type(func) == "function" then 253 | local err, r = pcall(func) 254 | if err and type(r) == "number" then 255 | return math.floor(r) * (type_size or 1) 256 | end 257 | print("dynamic_get_size error exec",r) 258 | else 259 | print("dynamic_get_size error parse",t) 260 | end 261 | return def_res 262 | end 263 | end 264 | 265 | local function pop_rep(stack, r, n) 266 | local rep = stack[#stack] 267 | stack[#stack] = nil 268 | assert(rep, "{} pair mismatch") 269 | rep.stop = #r 270 | if not n then 271 | return 272 | end 273 | if tonumber(n) then 274 | rep.size = function(res, data, offset) 275 | return tonumber(n) 276 | end 277 | else 278 | rep.size = dynamic_get_size(n) 279 | end 280 | for i=rep.start, rep.stop do 281 | r[i].rep = rep 282 | end 283 | end 284 | 285 | local function build_struct(info, data, name) 286 | local tb = {} 287 | local res = {} 288 | local offset = 1 289 | tb.title = name or info.name 290 | res.name = name or info.name 291 | tb.header = {"Filed", "Value", "Description"} 292 | local rep_info = {} 293 | local isBig = info.isBig 294 | local i = 0 295 | while i < #info do 296 | i = i + 1 297 | local v = info[i] 298 | if v.name then 299 | local bits = math.floor((v.bits or 8)/8) 300 | local unpack_fmt = "I"..bits 301 | if isBig then unpack_fmt = ">" .. unpack_fmt end 302 | local t = 0 303 | local truncated = #data + 1 < offset + bits 304 | if not truncated then 305 | t = unpack(unpack_fmt, data, offset) 306 | end 307 | tb[#tb+1] = r.expand_bit_field(t, v) 308 | if truncated then 309 | local vals = tb[#tb][2] 310 | for j=1,#vals do vals[j] = "<Truncated>" end 311 | end 312 | offset = offset + bits 313 | res[v.name] = t 314 | for j,fv in ipairs(v) do 315 | res[fv.name] = (t & fv.mask) >> fv.pos 316 | end 317 | else 318 | local n_suffix = "" 319 | if v.rep then 320 | if v.rep.start == i then 321 | rep_info[v.rep] = rep_info[v.rep] or { 322 | count = 1 323 | } 324 | end 325 | n_suffix = tostring(rep_info[v.rep].count) 326 | end 327 | local n, formater, size, big, fields, type_size = v[1], v[2], v[3], v[4], v[5], v[6] 328 | local name_suffix = "" 329 | if type(size) == 'function' then 330 | size = size(res, data, offset) 331 | name_suffix = "[" .. tostring(size) .. "]" 332 | end 333 | local data_size = size * (type_size or 1) 334 | name_suffix = name_suffix .. n_suffix 335 | local t = data:sub(offset, offset+data_size-1)-- unpack(unpack_fmt, data, offset) 336 | local prefix = big and ">" or "<" 337 | local truncated = #data + 1 < offset + data_size 338 | local desc = "" 339 | local oldTV = t 340 | t = 0 341 | if not truncated then 342 | local ss = data_size <= 4 and data_size or 4 343 | if formater == hexFmt or formater == decFmt then 344 | t = unpack(prefix .."I"..ss, data, offset) 345 | elseif formater == signedFmt then 346 | t = unpack(prefix .."i"..ss, data, offset) 347 | end 348 | end 349 | if type(fields) == "table" then 350 | desc = fields[t] or fields.comment or "" 351 | elseif type(fields) == "string" then 352 | desc = fields 353 | elseif type(fields) == "function" then 354 | desc = fields(t) 355 | end 356 | local val_str = truncated and "<Truncated>" or formater(oldTV, data_size, big) 357 | tb[#tb+1] = {n .. name_suffix, val_str, desc} 358 | res[n] = t 359 | offset = offset + data_size 360 | if v.rep and v.rep.stop == i then 361 | if rep_info[v.rep].count >= v.rep.size(res, data, offset) then 362 | rep_info[v.rep] = nil 363 | else 364 | if not truncated then 365 | rep_info[v.rep].count = rep_info[v.rep].count + 1 366 | i = v.rep.start - 1 367 | else 368 | rep_info[v.rep] = nil 369 | end 370 | end 371 | end 372 | end 373 | end 374 | res.html = r.make_table(tb) 375 | return res 376 | end 377 | 378 | local function create_struct(desc, field, isBig) 379 | assert(desc, debug.traceback()) 380 | local r = {} 381 | r.isBig = isBig 382 | local stk = {} 383 | field = field or {} 384 | local bf = nil 385 | local fieldNames = {} 386 | local lastBfName = nil 387 | string.gsub(desc, "([^\n]*)\n", function(l) 388 | local mayName = format_name(l) 389 | local bfName = nil 390 | if l:find("{") then 391 | push_rep(stk,r) 392 | end 393 | if l:find("}") then 394 | local rep_name = nil 395 | string.gsub(l, "}%s*%[([^%[]*)%]",function(tn) 396 | rep_name = tn 397 | end) 398 | pop_rep(stk, r, rep_name) 399 | end 400 | local st_name = l:find("struct") or l:find("union") 401 | if st_name then 402 | if mayName and #mayName > 1 then 403 | r.name = mayName 404 | end 405 | else 406 | if mayName and #mayName > 1 then 407 | bfName = mayName 408 | end 409 | end 410 | string.gsub(l, "^%s*([%w_]+)%s+([%w_]+)([^;]*);(.*)", function(t, n, q, comment) 411 | bfName = nil 412 | comment = format_comment(comment) 413 | local table_comment = parse_comment(comment) 414 | local p1 = q:find(":") 415 | local p2, p3 = q:find("%[[^%[]*%]") 416 | local bitfield = nil 417 | local array = nil 418 | if p1 then 419 | bitfield = tonumber(q:sub(p1+1)) 420 | elseif p2 then 421 | array = tonumber(q:sub(p2+1, p3-1)) 422 | if p2+1 == p3 then 423 | array = 0 424 | end 425 | if not array then 426 | array = q:sub(p2+1, p3-1) 427 | end 428 | end 429 | assert(type2size[t], "unkown type " .. t) 430 | fieldNames[n] = 1 431 | if bitfield then 432 | if not bf then 433 | bf = { 434 | bits = type2size[t]*8, 435 | bit_pos = 0, 436 | name = lastBfName, 437 | } 438 | lastBfName = nil 439 | end 440 | local f = { 441 | name = n, 442 | mask = ((1<= bf.bits then 460 | bf.name = bf.name or "" 461 | r[#r+1] = bf 462 | bf = nil 463 | end 464 | else 465 | local f = field[n] or table_comment 466 | local formater = dataFmt 467 | local size = 0 468 | local type_size = type2size[t] or 1 469 | if array then 470 | if type(array) == "string" then 471 | size = dynamic_get_size(array) 472 | end 473 | if type(array) == "number" then 474 | if array < 1 then 475 | size = function(res, data, offset) 476 | if #data >= offset then 477 | return #data - offset + 1 478 | end 479 | return 0 480 | end 481 | else 482 | size = type2size[t]*array 483 | end 484 | end 485 | formater = dataFmt 486 | if f and f.format then 487 | if f.format == "string" or f.format == "str" then 488 | formater = strFmt 489 | elseif f.format == "data" then 490 | formater = dataFmt 491 | elseif f.format == "hex" then 492 | formater = hexFmt 493 | elseif f.format == "dec" then 494 | formater = t:sub(1,1) == 'i' and signedFmt or decFmt 495 | elseif f.format == "ip" then 496 | formater = ipFmt 497 | elseif f.format == "uni" or f.format == "unicode" then 498 | formater = unicodeFmt 499 | end 500 | end 501 | else 502 | formater = t:sub(1,1) == 'i' and signedFmt or hexFmt 503 | size = 1 504 | if f and type(f) == "table" and f.format then 505 | if f.format == "string" or f.format == "str" then 506 | formater = strFmt 507 | elseif f.format == "data" then 508 | formater = hexFmt 509 | elseif f.format == "hex" then 510 | formater = hexFmt 511 | elseif f.format == "dec" then 512 | formater = t:sub(1,1) == 'i' and signedFmt or decFmt 513 | elseif f.format == "ip" then 514 | formater = ipFmt 515 | elseif f.format == "uni" or f.format == "unicode" then 516 | formater = unicodeFmt 517 | end 518 | end 519 | end 520 | local fieldBig = isBig 521 | if f and type(f) == "table" and f.endian then fieldBig = f.endian == ">" end 522 | r[#r+1] = {n, formater, size, fieldBig, f or comment, type_size} 523 | end 524 | end) 525 | lastBfName = bfName or lastBfName 526 | end) 527 | r.build = build_struct 528 | return r 529 | end 530 | 531 | r.create_struct = create_struct 532 | r.create_field = function(desc, field, isBig) 533 | local res = create_struct(desc, field, isBig)[1] 534 | assert(res and res.name, "wrong field description") 535 | return res 536 | end 537 | 538 | package.loaded["html"] = r 539 | -------------------------------------------------------------------------------- /scripts/init.lua: -------------------------------------------------------------------------------- 1 | -- init.lua 2 | -- encoding: utf-8 3 | require("file_base") 4 | require("file_pcap") 5 | require("file_iti1480a") 6 | 7 | require("usb_class_hid") 8 | require("usb_class_msc_bot") 9 | require("usb_class_cdc_acm") 10 | require("usb_class_hub") 11 | require("usb_class_audio") 12 | require("usb_class_video") 13 | require("usb_class_dfu") 14 | 15 | require("usb_device_ftdi") 16 | require("usb_device_aw_efex") 17 | 18 | 19 | require("upv_parser") 20 | -------------------------------------------------------------------------------- /scripts/macro_defs.lua: -------------------------------------------------------------------------------- 1 | -- macro_defs.lua 2 | -- constant value used as macros 3 | local macro_defs = { 4 | -- the 16 USB PIDs 5 | PID_RESERVED = 0, 6 | PID_OUT = 1, 7 | PID_ACK = 2, 8 | PID_DATA0 = 3, 9 | PID_PING = 4, 10 | PID_SOF = 5, 11 | PID_NYET = 6, 12 | PID_DATA2 = 7, 13 | PID_SPLIT = 8, 14 | PID_IN = 9, 15 | PID_NAK = 10, 16 | PID_DATA1 = 11, 17 | PID_ERR_PRE = 12, 18 | PID_SETUP = 13, 19 | PID_STALL = 14, 20 | PID_MDATA = 15, 21 | 22 | RES_NONE = 0x00, 23 | RES_BEGIN = 0x01, 24 | RES_END = 0x02, 25 | RES_BEGIN_END = 0x03, 26 | RES_MORE = 0x04, 27 | 28 | -- the standard request codes 29 | GET_STATUS = 0, 30 | CLEAR_FEATURE = 1, 31 | --RESERVED_2 = 2, 32 | SET_FEATURE = 3, 33 | --RESERVED_4 = 4, 34 | SET_ADDRESS = 5, 35 | GET_DESCRIPTOR = 6, 36 | SET_DESCRIPTOR = 7, 37 | GET_CONFIG = 8, 38 | SET_CONFIG = 9, 39 | GET_INTERFACE = 10, 40 | SET_INTERFACE = 11, 41 | SYNC_FRAME = 12, 42 | 43 | -- the descriptor type codes 44 | DEVICE_DESC = 1, 45 | CFG_DESC = 2, 46 | STRING_DESC = 3, 47 | INTERFACE_DESC = 4, 48 | ENDPOINT_DESC = 5, 49 | DEV_QUAL_DESC = 6, 50 | OTHER_DESC = 7, 51 | UNUSED_8_DESC = 8, 52 | OTG_DESC = 9, 53 | IAD_DESC = 0xB, 54 | BOS_DESC = 0xF, 55 | MAX_STD_DESC = 0x9, 56 | 57 | HID_DESC = 0x21, 58 | REPORT_DESC = 0x22, 59 | FUNC_DESC = 0x24, 60 | HUB_DESC = 0x29, 61 | 62 | -- Functional Descriptor Type 63 | CS_INTERFACE = 0x24, 64 | CS_ENDPOINT = 0x25, 65 | 66 | MAX_KNOWN_DESCRIPTOR = 0x29, 67 | 68 | 69 | ST_CBW = 0x01, -- $macro 70 | ST_DATA = 0x02, -- $macro 71 | ST_CSW = 0x03, 72 | 73 | ST_SETUP = 0x01, -- $macro 74 | ST_DATA_IN = 0x02, -- $macro 75 | ST_DATA_OUT = 0x03, -- $macro 76 | ST_STATUS_IN = 0x04, -- $macro 77 | ST_STATUS_OUT = 0x05, -- $macro 78 | 79 | } 80 | 81 | package.loaded["macro_defs"] = macro_defs 82 | -------------------------------------------------------------------------------- /scripts/upv_parser.lua: -------------------------------------------------------------------------------- 1 | 2 | -- USB Packet Viewer parser 3 | require("usb_register_class") 4 | local make_control_xfer_decoder = require("usb_control_transfer") 5 | 6 | local upv = { 7 | decoder = {}, 8 | autoDecoder = {}, 9 | mps_map = {}, 10 | mps_guess = {} 11 | } 12 | 13 | function upv_reset_parser() 14 | upv.decoder = {} 15 | upv.autoDecoder = {} 16 | upv.mps_map = {} 17 | upv.mps_guess = {} 18 | collectgarbage() 19 | end 20 | 21 | local function get_decoder(addr, ep, autoDecoder) 22 | local decoder = upv.decoder[string.char(addr, ep)] 23 | if not decoder and autoDecoder then 24 | decoder = upv.autoDecoder[string.char(addr, ep)] 25 | end 26 | if (not decoder) and ( (ep & 0x7f) == 0) then 27 | -- create default decoder for control transfer 28 | local ctrl_xfer_decoder = make_control_xfer_decoder(upv) 29 | upv.decoder[string.char(addr, 0)] = ctrl_xfer_decoder 30 | upv.decoder[string.char(addr, 0x80)] = ctrl_xfer_decoder 31 | return ctrl_xfer_decoder 32 | end 33 | return decoder 34 | end 35 | 36 | local upv_register_decoder = upv_register_decoder 37 | upv.setup_decoder = function(self, addr, eps, decoder, mpss) 38 | if mpss then 39 | for i,v in ipairs(mpss) do 40 | upv.mps_map[string.char(addr, eps[i])] = v 41 | end 42 | end 43 | 44 | local ep_res = decoder:check_ep_require(eps) 45 | local param = "" 46 | for i,v in ipairs(ep_res) do 47 | param = param .. string.char(addr, v) 48 | end 49 | local temp = upv.decoder 50 | upv.decoder = upv.autoDecoder 51 | upv_register_decoder(decoder.name, param) 52 | upv.decoder = temp 53 | end 54 | 55 | upv.is_short_packet = function(self, addr, ep, data) 56 | local key = string.char(addr, ep) 57 | local mps = upv.mps_map[key] 58 | if mps and #data < mps then return true end 59 | local l = #data 60 | if l < 8 then return true end 61 | if ((l-1) & l) ~= 0 then return true end 62 | mps = upv.mps_guess[key] 63 | if mps and (l 8 then 74 | self.is_cdc_data = true 75 | local setup = setup_parser.parse_setup(data:sub(1,8), self) 76 | self.is_cdc_data = false 77 | if setup.bRequest == CDC_SERIAL_STATE then 78 | setup.html = setup.html .. struct_serial_state:build(data:sub(9), "Serial State").html 79 | return setup 80 | end 81 | return nil 82 | end 83 | end 84 | 85 | function cls.parse_setup(setup, context) 86 | if setup.recip ~= "Interface" or setup.type ~= "Class" then 87 | return 88 | end 89 | local bRequest_desc = req2str[setup.bRequest] or "CDC Unknown Req" 90 | 91 | local wValueField = nil 92 | if context.is_cdc_data then 93 | if setup.bRequest == CDC_SERIAL_STATE then 94 | bRequest_desc = "SERIAL_STATE" 95 | end 96 | else 97 | if bRequest_desc == "SET_CONTROL_LINE_STATE"then 98 | wValueField = field_cdc_control_line_state 99 | end 100 | end 101 | setup.name = shortName[bRequest_desc] or "CDC Unknown" 102 | setup.title = "CDC Request" 103 | setup.render.bRequest = bRequest_desc 104 | setup.render.wValue = wValueField 105 | setup.render.wIndex = "Interface" 106 | setup.render.title = "CDC Request " .. bRequest_desc 107 | end 108 | 109 | local struct_line_coding = html.create_struct([[ 110 | struct{ 111 | uint32_t baudrate; 112 | uint8_t stopbits; 113 | uint8_t parity; 114 | uint8_t databits; 115 | }; 116 | ]], { 117 | baudrate = {format = "dec"}, 118 | databits = {format = "dec"}, 119 | stopbits = {[0] = "1 Stop", [1] = "1.5 Stop", [2] = "2 Stop"}, 120 | parity = {[0] = "None", [1] = "ODD", [2] = "EVEN", [3] = "MARK", [4] = "SPACE"}, 121 | }) 122 | 123 | function cls.parse_setup_data(setup, data, context) 124 | local s = req2str[setup.bRequest] 125 | if s then 126 | if s == "SET_LINE_CODING" or s == "GET_LINE_CODING" then 127 | local r = struct_line_coding:build(data, "Line Coding") 128 | return r.html 129 | elseif s == "SEND_ENCAPSULATED_COMMAND" then 130 | return rndis.parseCommand(data, context) 131 | elseif s == "GET_ENCAPSULATED_RESPONSE" then 132 | return rndis.parseResponse(data, context) 133 | end 134 | end 135 | return nil 136 | end 137 | 138 | local function rndis_on_transaction(self, param, data, needDetail, forceBegin) 139 | local addr, ep, pid, ack = param:byte(1), param:byte(2), param:byte(3), param:byte(4) 140 | if ack ~= macro_defs.PID_ACK then 141 | return macro_defs.RES_NONE 142 | end 143 | local context = self:get_context(needDetail, pid) 144 | self.addr = addr 145 | context.data = context.data or "" 146 | if forceBegin then 147 | context.data = "" 148 | end 149 | local endMark = self.upv:is_short_packet(addr, ep, data) and macro_defs.RES_END or macro_defs.RES_NONE 150 | local begindMark = #context.data == 0 and macro_defs.RES_BEGIN or macro_defs.RES_NONE 151 | context.data = context.data .. data 152 | if #context.data >= 4096 then 153 | endMark = macro_defs.RES_END 154 | end 155 | 156 | local res = endMark | begindMark 157 | if res == macro_defs.RES_NONE then res = macro_defs.RES_MORE end 158 | 159 | if needDetail then 160 | context.status = "incomp" 161 | context.title = "CDC Rndis DATA" 162 | context.name = "CDC DATA" 163 | context.desc = "Rndis DATA" 164 | context.infoHtml = "" 165 | if endMark == macro_defs.RES_END then 166 | context.infoHtml = rndis.parse_data(context.data, context) 167 | context.status = "success" 168 | end 169 | local xfer_res = self.upv.make_xfer_res(context) 170 | if endMark == macro_defs.RES_END then 171 | context.data = "" 172 | end 173 | return res, self.upv.make_xact_res("CDC Rndis Data", "", data), xfer_res 174 | end 175 | if endMark ~= 0 then 176 | context.data = "" 177 | end 178 | return res 179 | end 180 | 181 | local function data_on_transaction(self, param, data, needDetail, forceBegin) 182 | local addr, ep, pid, ack = param:byte(1), param:byte(2), param:byte(3), param:byte(4) 183 | if ack ~= macro_defs.PID_ACK then 184 | return macro_defs.RES_NONE 185 | end 186 | self.addr = addr 187 | if needDetail then 188 | local context = {} 189 | context.data = data 190 | context.status = "success" 191 | context.title = "CDC RAW DATA" 192 | context.name = "CDC DATA" 193 | context.desc = "CDC DATA" 194 | context.infoHtml = "Raw Data" 195 | return macro_defs.RES_BEGIN_END, self.upv.make_xact_res("CDC Raw Data", "Raw Data", data), self.upv.make_xfer_res(context) 196 | end 197 | return macro_defs.RES_BEGIN_END 198 | end 199 | 200 | function cls.on_transaction(self, param, data, needDetail, forceBegin) 201 | local addr, ep, pid, ack = param:byte(1), param:byte(2), param:byte(3), param:byte(4) 202 | if pid ~= macro_defs.PID_IN then 203 | return macro_defs.RES_NONE 204 | end 205 | if ack ~= macro_defs.PID_ACK then 206 | return macro_defs.RES_NONE 207 | end 208 | if needDetail then 209 | local status = "success" 210 | local info = cdc_parse_notify_data(self, data) 211 | local html = "" 212 | if not info then 213 | status = "error" 214 | html = "Wrong line status data" 215 | else 216 | html = info.html 217 | end 218 | return macro_defs.RES_BEGIN_END, self.upv.make_xact_res("Hub Notify", html, data), self.upv.make_xfer_res({ 219 | title = "CDC Line status", 220 | name = "CDC Notify", 221 | desc = "Line Sts", 222 | status = status, 223 | infoHtml = html, 224 | data = data, 225 | }) 226 | end 227 | return macro_defs.RES_BEGIN_END 228 | end 229 | 230 | -- 0x00 -- Header Functional Descriptor, which marks the beginning of the concatenated set of functional descriptors for the interface. 231 | -- 0x01 -- Call Management Functional Descriptor. 232 | -- 0x02 -- Abstract Control Management Functional Descriptor. 233 | -- 0x03 -- Direct Line Management Functional Descriptor. 234 | -- 0x04 -- Telephone Ringer Functional Descriptor. 235 | -- 0x05 -- Telephone Call and Line State Reporting Capabilities Functional Descriptor. 236 | -- 0x06 -- Union Functional Descriptor 237 | 238 | _G.cdc_interface_desc_type = { 239 | [0x00] = "Header", 240 | [0x01] = "Call Management", 241 | [0x02] = "Abstract Control Management", 242 | [0x03] = "Direct Line Management", 243 | [0x04] = "Telephone Ringer", 244 | [0x05] = "Telephone Call and Line State Reporting Capabilities", 245 | [0x06] = "Union", 246 | } 247 | 248 | local function build_desc(name, info) 249 | local builder = html.create_struct(info) 250 | return function(data, offset, context) 251 | return builder:build(data:sub(offset), name) 252 | end 253 | end 254 | 255 | local cdc_desc = { 256 | [0x00] = build_desc("Header Functional Descriptor", [[ 257 | struct{ 258 | uint8_t bLength; 259 | uint8_t bDescriptorType; // CS_INTERFACE 260 | uint8_t bDescriptorSubtype; // _G.cdc_interface_desc_type 261 | uint16_t bcdCDC; 262 | } 263 | ]]), 264 | [0x01] = build_desc("Call Management Functional Descriptor", [[ 265 | struct{ 266 | uint8_t bLength; 267 | uint8_t bDescriptorType; // CS_INTERFACE 268 | uint8_t bDescriptorSubtype; // _G.cdc_interface_desc_type 269 | uint8_t bmCapabilities; 270 | uint8_t bDataInterface; 271 | } 272 | ]]), 273 | [0x02] = build_desc("Abstract Control Management Functional Descriptor", [[ 274 | struct{ 275 | uint8_t bLength; 276 | uint8_t bDescriptorType; // CS_INTERFACE 277 | uint8_t bDescriptorSubtype; // _G.cdc_interface_desc_type 278 | // bmCapabilities 279 | uint8_t COMM:1; 280 | uint8_t LINE:1; 281 | uint8_t BREAK:1; 282 | uint8_t NETWORK:1; 283 | uint8_t reserved:4; 284 | } 285 | ]]), 286 | [0x06] = build_desc("Union Functional Descriptor", [[ 287 | struct{ 288 | uint8_t bLength; 289 | uint8_t bDescriptorType; // CS_INTERFACE 290 | uint8_t bDescriptorSubtype; // _G.cdc_interface_desc_type 291 | uint8_t bMasterInterface; 292 | uint8_t bSlaveInterface0; 293 | } 294 | ]]), 295 | } 296 | 297 | function cls.descriptor_parser(data, offset, context) 298 | if #data < offset + 1 then return nil end 299 | local len = data:byte(offset+1) 300 | if #data < offset + len then return nil end 301 | if unpack("I1", data, offset + 1) ~= macro_defs.CS_INTERFACE then 302 | return nil 303 | end 304 | local subType = unpack("I1", data, offset + 2) 305 | return cdc_desc[subType] and cdc_desc[subType](data, offset, context) 306 | end 307 | 308 | cls.bInterfaceClass = 2 309 | cls.bInterfaceSubClass = 2 310 | cls.bInterfaceProtocol = nil 311 | cls.endpoints = { EP_IN("Line status") } 312 | cls.iad = { 313 | bInterfaceClass = 2, 314 | bInterfaceSubClass = 2, 315 | bInterfaceProtocol = nil, 316 | } 317 | 318 | local protoName = { 319 | [0x00] ="No class specific" , -- USB specification No class specific protocol required 320 | [0x01] ="ITU-T V.250" , -- AT Commands: V.250 etc 321 | [0x02] ="PCCA-101" , -- AT Commands defined by PCCA-101 322 | [0x03] ="PCCA-101" , -- AT Commands defined by PCCA-101 & Annex O 323 | [0x04] ="GSM 7.07" , -- AT Commands defined by GSM 07.07 324 | [0x05] ="3GPP 27.07" , -- AT Commands defined by 3GPP 27.007 325 | [0x06] ="C-S0017-0" , -- AT Commands defined by TIA for CDMA 326 | [0x07] ="USB EEM" , -- Ethernet Emulation Model 327 | [0xFE] ="External Protocol" , -- : Commands defined by Command Set Functional Descriptor 328 | [0xFF] ="Vendor-specific" , -- USB Specification Vendor-specific 329 | --08-FDh RESERVED (future use) 330 | } 331 | function cls.get_name(desc, context) 332 | local name = protoName[desc.bInterfaceProtocol] or "RESERVED" 333 | return { 334 | bInterfaceClass = "CDC", 335 | bInterfaceSubClass = "ACM", 336 | bInterfaceProtocol = name, 337 | } 338 | end 339 | 340 | register_class_handler(cls) 341 | 342 | local rndis_data_cls = {} 343 | for k,v in pairs(cls) do 344 | rndis_data_cls[k] = v 345 | end 346 | rndis_data_cls.name = "CDC Rndis Data" 347 | rndis_data_cls.bInterfaceClass = 10 348 | rndis_data_cls.bInterfaceSubClass = nil 349 | rndis_data_cls.bInterfaceProtocol = nil 350 | rndis_data_cls.iad = { 351 | bInterfaceClass = 2, 352 | bInterfaceSubClass = 2, 353 | bInterfaceProtocol = 0xff, 354 | } 355 | rndis_data_cls.endpoints = { EP_IN("Incoming Data"), EP_OUT("Outgoning Data") } 356 | rndis_data_cls.on_transaction = rndis_on_transaction 357 | 358 | function rndis_data_cls.get_name(desc, context) 359 | return { 360 | bInterfaceClass = "CDC Rndis Data", 361 | } 362 | end 363 | register_class_handler(rndis_data_cls) 364 | 365 | local raw_data_cls = {} 366 | for k,v in pairs(cls) do 367 | raw_data_cls[k] = v 368 | end 369 | raw_data_cls.name = "CDC Raw Data" 370 | raw_data_cls.bInterfaceClass = 10 371 | raw_data_cls.bInterfaceSubClass = nil 372 | raw_data_cls.bInterfaceProtocol = nil 373 | raw_data_cls.iad = { 374 | bInterfaceClass = 2, 375 | bInterfaceSubClass = 2, 376 | bInterfaceProtocol = nil, 377 | } 378 | raw_data_cls.endpoints = { EP_IN("Incoming Data"), EP_OUT("Outgoning Data") } 379 | raw_data_cls.on_transaction = data_on_transaction 380 | 381 | function raw_data_cls.get_name(desc, context) 382 | return { 383 | bInterfaceClass = "CDC Raw Data", 384 | } 385 | end 386 | register_class_handler(raw_data_cls) 387 | 388 | package.loaded["usb_class_cdc_acm"] = cls 389 | -------------------------------------------------------------------------------- /scripts/usb_class_dfu.lua: -------------------------------------------------------------------------------- 1 | -- usb_class_dfu.lua 2 | -- protocol are defined at: https://usb.org/sites/default/files/DFU_1.1.pdf 3 | -- qianfan Zhao 4 | 5 | local html = require("html") 6 | require("usb_setup_parser") 7 | require("usb_register_class") 8 | 9 | local cls = {} 10 | cls.name = "DFU" 11 | 12 | local DFU_FUNCTIONAL_DESCRIPTOR = 0x21 13 | 14 | local struct_dfu_functional_descriptor = html.create_struct([[ 15 | struct { 16 | uint8_t bLength; // {format = "dec"} 17 | uint8_t bDescriptorType; 18 | //bmAttributes; 19 | uint8_t bitCanDnload:1; 20 | uint8_t bitCanUpload:1; 21 | uint8_t bitMainfestationTolerant:1; 22 | uint8_t bitWillDetach:1; 23 | uint8_t reserved:4; 24 | uint16_t wDetachtimeOut; // {format = "dec"} 25 | uint16_t wTransferSize; // {format = "dec"} 26 | uint16_t bcdDFUVersion; 27 | }]], { 28 | bDescriptorType = { 29 | [DFU_FUNCTIONAL_DESCRIPTOR] = "DFU FUNCTIONAL", 30 | } 31 | } 32 | ) 33 | 34 | function cls.descriptor_parser(data, offset, context) 35 | local t = data:byte(offset+1) 36 | 37 | if t == DFU_FUNCTIONAL_DESCRIPTOR then 38 | local rawData = data:sub(offset) 39 | local res = struct_dfu_functional_descriptor:build(rawData, "DFU Functionnal Descriptor") 40 | res.rawData = rawData 41 | return res 42 | end 43 | end 44 | 45 | local DFU_DETACH = 0 46 | local DFU_DNLOAD = 1 47 | local DFU_UPLOAD = 2 48 | local DFU_GETSTATUS = 3 49 | local DFU_CLRSTATUS = 4 50 | local DFU_GETSTATE = 5 51 | local DFU_ABORT = 6 52 | 53 | local dfu_request_names = { 54 | [DFU_DETACH] = "DFU_DETACH", 55 | [DFU_DNLOAD] = "DFU_DNLOAD", 56 | [DFU_UPLOAD] = "DFU_UPLOAD", 57 | [DFU_GETSTATUS] = "DFU_GETSTATUS", 58 | [DFU_CLRSTATUS] = "DFU_CLRSTATUS", 59 | [DFU_GETSTATE] = "DFU_GETSTATE", 60 | [DFU_ABORT] = "DFU_ABORT", 61 | } 62 | 63 | local dfu_request_wValue_render = { 64 | [DFU_DETACH] = html.create_field([[ 65 | struct { 66 | // wValue 67 | uint16_t wTimeout:16; // {format = "dec"} 68 | } 69 | ]])[1], 70 | [DFU_DNLOAD] = html.create_field([[ 71 | struct { 72 | // wValue 73 | uint16_t wBlockNum:16; // {format = "dec"} 74 | } 75 | ]])[1], 76 | [DFU_UPLOAD] = html.create_field([[ 77 | struct { 78 | // wValue 79 | uint16_t wBlockNum:16; // {format = "dec"} 80 | } 81 | ]])[1], 82 | } 83 | 84 | function cls.parse_setup(setup, context) 85 | local cmd = setup.bRequest 86 | local name = dfu_request_names[cmd] 87 | 88 | if setup.recip ~= "Interface" or setup.type ~= "Class" or name == nil then 89 | return 90 | end 91 | 92 | setup.title = name 93 | setup.render.title = name 94 | setup.render.bRequest = name 95 | 96 | if cmd == DFU_DNLOAD or cmd == DFU_UPLOAD then 97 | setup.name = string.format("%d", setup.wValue) 98 | else 99 | setup.name = name 100 | end 101 | 102 | local wValue_render = dfu_request_wValue_render[setup.bRequest] 103 | if wValue_render then 104 | setup.render.wValue = wValue_render 105 | end 106 | end 107 | 108 | local struct_dfu_getstatus = html.create_struct([[ 109 | struct { 110 | uint8_t bStatus; 111 | uint24_t bwPollTimeout; // {format = "dec"} 112 | uint8_t bState; 113 | uint8_t iString; 114 | }]], { 115 | bStatus = { 116 | [0x00] = "OK", 117 | [0x01] = "errTARGET", 118 | [0x02] = "errFILE", 119 | [0x03] = "errWRITE", 120 | [0x04] = "errERASE", 121 | [0x05] = "errCHECK_ERASED", 122 | [0x06] = "errPROG", 123 | [0x07] = "errVERIFY", 124 | [0x08] = "errADDRESS", 125 | [0x09] = "errNOTDONE", 126 | [0x0a] = "errFIRMWARE", 127 | [0x0b] = "errVENDOR", 128 | [0x0c] = "errUSBR", 129 | [0x0d] = "errPOR", 130 | [0x0e] = "errUNKNOWN", 131 | [0x0f] = "errSTALLEDPKT", 132 | }, 133 | bState = { 134 | [0x00] = "appIDLE", 135 | [0x01] = "appDETACH", 136 | [0x02] = "dfuIDLE", 137 | [0x03] = "dfuDNLOAD-SYNC", 138 | [0x04] = "dfuDNBUSY", 139 | [0x05] = "dfuDNLOAD-IDLE", 140 | [0x06] = "dfuMANIFEST-SYNC", 141 | [0x07] = "dfuMANIFEST", 142 | [0x08] = "dfuMANIFEST-WAIT-RESET", 143 | [0x09] = "dfuUPLOAD-IDLE", 144 | [0x0a] = "dfuERROR", 145 | }, 146 | } 147 | ) 148 | 149 | function dfu_dnload_parser(setup, data, context) 150 | return string.format("

DFU Download Data

\ 151 | Display in data window, size = %d", 152 | setup.wLength) 153 | end 154 | 155 | function dfu_getstatus_parser(setup, data, context) 156 | return struct_dfu_getstatus:build(data, "DFU STATUS").html 157 | end 158 | 159 | local dfu_request_parsers = { 160 | [DFU_DNLOAD] = dfu_dnload_parser, 161 | [DFU_GETSTATUS] = dfu_getstatus_parser, 162 | } 163 | 164 | function cls.parse_setup_data(setup, data, context) 165 | local parser = dfu_request_parsers[setup.bRequest] 166 | 167 | if parser then 168 | return parser(setup, data, context) 169 | end 170 | end 171 | 172 | cls.bInterfaceClass = 0xFE 173 | cls.bInterfaceSubClass = 0x01 174 | cls.bInterfaceProtocol = 0x02 175 | 176 | function cls.get_name(desc, context) 177 | return { 178 | bInterfaceClass = "DFU", 179 | bInterfaceSubClass = "DFU", 180 | bInterfaceProtocol = "DFU", 181 | } 182 | end 183 | 184 | register_class_handler(cls) 185 | package.loaded["usb_class_dfu"] = cls 186 | -------------------------------------------------------------------------------- /scripts/usb_class_hid.lua: -------------------------------------------------------------------------------- 1 | -- usb_class_hid.lua 2 | 3 | -- a typical class has these functions 4 | -- cls.parse_setup(setup, context), update setup.html, setup.name field in setup, and return it 5 | -- cls.parse_setup_data(setup, data, context) return a html to describe the data 6 | -- cls.on_transaction(self, param, data, needDetail, forceBegin) return macro_defs.RES_xxx 7 | -- cls.descriptor_parser(data, offset, context) return a parsed descriptor 8 | -- cls.get_name(descriptor, context) return a field name table 9 | -- HID class definition https://www.usb.org/sites/default/files/documents/hid1_11.pdf 10 | 11 | local html = require("html") 12 | local macro_defs = require("macro_defs") 13 | require("usb_setup_parser") 14 | require("usb_register_class") 15 | 16 | local fmt = string.format 17 | local unpack = string.unpack 18 | local cls = {} 19 | cls.name = "HID" 20 | 21 | local HID_DESCRIPTOR = 0x21 22 | 23 | local struct_hid_descriptor = html.create_struct([[ 24 | struct{ 25 | uint8_t bLength; 26 | uint8_t bDescriptorType; // HID descriptor 27 | uint16_t bcdHID; 28 | uint8_t bCountryCode; 29 | uint8_t bNumDescriptors; 30 | { 31 | uint8_t bDescriptorType; // {[0x22] = "Report Descriptor", [0x33] = "Physical Descriptor"} 32 | uint16_t wDescriptorLength; // {format="dec"} 33 | }[bNumDescriptors]; 34 | } 35 | ]]) 36 | 37 | function cls.descriptor_parser(data, offset, context) 38 | local t = data:byte(offset+1) 39 | 40 | if t == HID_DESCRIPTOR then 41 | local rawData = data:sub(offset) 42 | local res = struct_hid_descriptor:build(rawData, "HID Descriptor") 43 | res.rawData = rawData 44 | return res 45 | end 46 | end 47 | 48 | local KEY_A = 0x04 49 | local KEY_Z = 0x1d 50 | local KEY_1 = 0x1e 51 | local KEY_0 = 0x27 52 | local KEY_F1 = 0x3a 53 | local KEY_F12 = 0x45 54 | local KEY_ENTER = 0x28 55 | local num = "1234567890" 56 | local function key2str(key) 57 | if key == 0 then return '[None]' 58 | elseif key == 1 then return '[ERR_OVF]' 59 | elseif key >= KEY_A and key<= KEY_Z then 60 | return string.char( string.byte("A",1) + key - 4 ) 61 | elseif key >= KEY_1 and key <= KEY_0 then 62 | local p = key-0x1e+1 63 | return num:sub(p,p) 64 | elseif key >= KEY_F1 and key <= KEY_F12 then 65 | return "F" .. (key - KEY_F1 + 1) 66 | elseif key == KEY_ENTER then 67 | return "Enter" 68 | end 69 | return "[unknown]" 70 | end 71 | 72 | _G.hid_key_name = key2str 73 | 74 | local field_wValue_hid_report = html.create_field([[ 75 | struct{ 76 | // wValue 77 | uint16_t ReportID:8; 78 | uint16_t ReportType:8; // {[1] = "Input", [2] = "Output", [3] = "Feature"} 79 | } 80 | ]]) 81 | 82 | local field_wValue_hid_idle = html.create_field([[ 83 | struct{ 84 | // wValue 85 | uint16_t ReportID:8; 86 | uint16_t IdleValue:8; 87 | } 88 | ]]) 89 | 90 | local struct_boot_mouse_data = html.create_struct([[ 91 | struct{ 92 | uint8_t button1:1; // {[0]="Released", [1]= "pressed"} 93 | uint8_t button2:1; // {[0]="Released", [1]= "pressed"} 94 | uint8_t button3:1; // {[0]="Released", [1]= "pressed"} 95 | uint8_t reserved:5; 96 | int8_t x; // {format = "dec"} 97 | int8_t y; // {format = "dec"} 98 | uint8_t reserved; 99 | } 100 | ]]) 101 | 102 | local struct_boot_key_data = html.create_struct([[ 103 | struct{ 104 | // Modifier 105 | uint8_t LeftCtrl:1; // {[0]="Released", [1]= "pressed"} 106 | uint8_t LeftShift:1; // {[0]="Released", [1]= "pressed"} 107 | uint8_t LeftAlt:1; // {[0]="Released", [1]= "pressed"} 108 | uint8_t LeftMeta:1; // {[0]="Released", [1]= "pressed"} 109 | uint8_t RightCtrl:1; // {[0]="Released", [1]= "pressed"} 110 | uint8_t RightShift:1; // {[0]="Released", [1]= "pressed"} 111 | uint8_t RightAlt:1; // {[0]="Released", [1]= "pressed"} 112 | uint8_t RightMeta:1; // {[0]="Released", [1]= "pressed"} 113 | uint8_t reserved; 114 | { 115 | uint8_t key; // _G.hid_key_name 116 | }[6]; 117 | } 118 | ]]) 119 | 120 | local req2str = { 121 | [1] = "GET_REPORT", 122 | [2] = "GET_IDLE", 123 | [3] = "GET_PROTOCOL", 124 | [9] = "SET_REPORT", 125 | [10] = "SET_IDLE", 126 | [11] = "SET_PROTOCOL", 127 | } 128 | 129 | function cls.parse_setup(setup, context) 130 | if setup.recip ~= "Interface" or setup.type ~= "Class" then 131 | return 132 | end 133 | local bRequest_desc = req2str[setup.bRequest] or "HID Unknown Req" 134 | local wValueField = nil 135 | if bRequest_desc == "GET_REPORT" or bRequest_desc == "SET_REPORT" then 136 | wValueField = field_wValue_hid_report 137 | elseif bRequest_desc == "GET_IDLE" or bRequest_desc == "SET_IDLE" then 138 | wValueField = field_wValue_hid_idle 139 | elseif bRequest_desc == "GET_PROTOCOL" or bRequest_desc == "SET_PROTOCOL" then 140 | end 141 | setup.name = bRequest_desc 142 | setup.title = "HID Request" 143 | setup.render.title = "HID Req " .. bRequest_desc 144 | setup.render.bRequest = bRequest_desc 145 | setup.render.wValue = wValueField 146 | setup.render.wIndex = "Interface" 147 | end 148 | 149 | function cls.parse_setup_data(setup, data, context) 150 | local s = req2str[setup.bRequest] 151 | if s then 152 | return "

" .. s .." data

" 153 | end 154 | if (setup.bRequest == macro_defs.GET_DESCRIPTOR) and ((setup.wValue>>8) == macro_defs.REPORT_DESC) then 155 | return "

Report descriptor


Todo: parse it to analyze endpoint data" 156 | end 157 | return nil 158 | end 159 | 160 | local function key_on_transaction(self, param, data, needDetail, forceBegin) 161 | local addr, ep, pid, ack = param:byte(1), param:byte(2), param:byte(3), param:byte(4) 162 | if pid ~= macro_defs.PID_IN then 163 | return macro_defs.RES_NONE 164 | end 165 | if ack ~= macro_defs.PID_ACK then 166 | return macro_defs.RES_NONE 167 | end 168 | 169 | if needDetail then 170 | local infoHtml = struct_boot_key_data:build(data, "Maybe Boot Keyboard").html 171 | return macro_defs.RES_BEGIN_END, self.upv.make_xact_res("HID Key", infoHtml, data), self.upv.make_xfer_res({ 172 | title = "HID Key Data", 173 | name = "HID Data", 174 | desc = "Boot Keyboard", 175 | status = "success", 176 | infoHtml = infoHtml, 177 | data = data, 178 | }) 179 | end 180 | return macro_defs.RES_BEGIN_END 181 | end 182 | 183 | local function mouse_on_transaction(self, param, data, needDetail, forceBegin) 184 | local addr, ep, pid, ack = param:byte(1), param:byte(2), param:byte(3), param:byte(4) 185 | if pid ~= macro_defs.PID_IN then 186 | return macro_defs.RES_NONE 187 | end 188 | if ack ~= macro_defs.PID_ACK then 189 | return macro_defs.RES_NONE 190 | end 191 | if needDetail then 192 | local infoHtml = struct_boot_mouse_data:build(data, "Maybe Boot Mouse").html 193 | return macro_defs.RES_BEGIN_END, self.upv.make_xact_res("HID Mouse",infoHtml,data), self.upv.make_xfer_res({ 194 | title = "HID Mouse Data", 195 | name = "HID Data", 196 | desc = "Boot Mouse", 197 | status = "success", 198 | infoHtml = infoHtml, 199 | data = data, 200 | }) 201 | end 202 | return macro_defs.RES_BEGIN_END 203 | end 204 | 205 | local function hid_on_transaction(self, param, data, needDetail, forceBegin) 206 | local addr, ep, pid, ack = param:byte(1), param:byte(2), param:byte(3), param:byte(4) 207 | if pid ~= macro_defs.PID_IN and pid ~= macro_defs.PID_OUT then 208 | return macro_defs.RES_NONE 209 | end 210 | if ack ~= macro_defs.PID_ACK then 211 | return macro_defs.RES_NONE 212 | end 213 | if needDetail then 214 | local infoHtml = "

HID data

Need decode with report descriptor" 215 | return macro_defs.RES_BEGIN_END, self.upv.make_xact_res("Hub Notify", infoHtml, data), self.upv.make_xfer_res({ 216 | title = "HID Data", 217 | name = "HID Data", 218 | desc = "HID Data", 219 | status = "success", 220 | infoHtml = infoHtml, 221 | data = data, 222 | }) 223 | end 224 | return macro_defs.RES_BEGIN_END 225 | end 226 | 227 | local function xunpack(fmt, data, index, length) 228 | index = index or 1 229 | length = length or 1 230 | if #data + 1 < index + length then 231 | return 0 232 | end 233 | return unpack(fmt, data, index) 234 | end 235 | 236 | --function cls.descriptor_parser = nil 237 | 238 | cls.bInterfaceClass = 3 239 | cls.bInterfaceSubClass = nil 240 | cls.bInterfaceProtocol = nil 241 | cls.endpoints = { EP_IN("HID Data") } 242 | 243 | function cls.get_name(desc, context) 244 | local subName = "Reserved" 245 | if desc.bInterfaceSubClass == 0 then 246 | subName = "No SubClass" 247 | elseif desc.bInterfaceSubClass == 1 then 248 | subName = "Boot Interface" 249 | end 250 | local bootName = "Reserved" 251 | if desc.bInterfaceProtocol == 0 then 252 | bootName = "None" 253 | elseif desc.bInterfaceProtocol == 1 then 254 | bootName = "Keyboard" 255 | elseif desc.bInterfaceProtocol == 2 then 256 | bootName = "Mouse" 257 | end 258 | return { 259 | bInterfaceClass = "HID", 260 | bInterfaceSubClass = subName, 261 | bInterfaceProtocol = bootName, 262 | } 263 | end 264 | 265 | local function build_hid_class(name, handler, subClass, protool) 266 | local r = {} 267 | for k,v in pairs(cls) do 268 | r[k] = v 269 | end 270 | r.name = name 271 | r.on_transaction = handler 272 | r.bInterfaceSubClass = subClass 273 | r.bInterfaceProtocol = protool 274 | return r 275 | end 276 | 277 | register_class_handler(build_hid_class("HID Boot Key", key_on_transaction, 1, 1)) 278 | register_class_handler(build_hid_class("HID Boot Mouse", mouse_on_transaction, 1, 2)) 279 | cls.endpoints = { EP_IN("Incoming Data", true), EP_OUT("Outgoing Data", true) } 280 | register_class_handler(build_hid_class("HID User", hid_on_transaction, nil, nil)) 281 | 282 | package.loaded["usb_class_hid"] = cls 283 | -------------------------------------------------------------------------------- /scripts/usb_class_hub.lua: -------------------------------------------------------------------------------- 1 | -- usb_class_hub.lua 2 | 3 | -- a typical class has these functions 4 | -- cls.parse_setup(setup, context), update setup.html, setup.name field in setup, and return it 5 | -- cls.parse_setup_data(setup, data, context) return a html to describe the data 6 | -- cls.on_transaction(self, param, data, needDetail, forceBegin) return macro_defs.RES_xxx 7 | -- cls.descriptor_parser(data, offset, context) return a parsed descriptor 8 | -- cls.get_name(descriptor, context) return a field name table 9 | -- HUB class definition usb_20.pdf 11.23, 11.24 10 | 11 | local html = require("html") 12 | local macro_defs = require("macro_defs") 13 | require("usb_register_class") 14 | local fmt = string.format 15 | local unpack = string.unpack 16 | local cls = {} 17 | cls.name = "HUB Notify Data" 18 | 19 | local CLEAR_TT_BUFFER = 8 20 | local RESET_TT = 9 21 | local GET_TT_STATE = 10 22 | local STOP_TT = 11 23 | 24 | local function hub_feat_sel(wValue) 25 | if wValue == 0 then return "C_HUB_LOCAL_POWER" 26 | elseif wValue == 1 then return "C_HUB_OVER_CURRENT" 27 | end 28 | return "Unknown Feature selector" 29 | end 30 | 31 | local port_feature_list = { 32 | [0 ] = "PORT_CONNECTION" , 33 | [1 ] = "PORT_ENABLE" , 34 | [2 ] = "PORT_SUSPEND" , 35 | [3 ] = "PORT_OVER_CURRENT" , 36 | [4 ] = "PORT_RESET" , 37 | [8 ] = "PORT_POWER" , 38 | [9 ] = "PORT_LOW_SPEED" , 39 | [16] = "C_PORT_CONNECTION" , 40 | [17] = "C_PORT_ENABLE" , 41 | [18] = "C_PORT_SUSPEND" , 42 | [19] = "C_PORT_OVER_CURRENT" , 43 | [20] = "C_PORT_RESET" , 44 | [21] = "PORT_TEST" , 45 | [22] = "PORT_INDICATOR" , 46 | } 47 | 48 | local function port_feat_sel(value) 49 | return port_feature_list[value] or "Unknwon selector" 50 | end 51 | 52 | _G.hub_port_feature_selection = port_feature_list 53 | 54 | local field_wIndex_port_feature = html.create_field([[ 55 | strcut{ 56 | // wIndex 57 | uint16_t port:8; 58 | uint16_t selection:8; // _G.hub_port_feature_selection 59 | } 60 | ]]) 61 | 62 | local struct_hub_status = html.create_struct([[ 63 | struct{ 64 | // wHubStatus 65 | uint16_t LocalPowerSource:1; // {[0] = "good", [1] = "lost" } 66 | uint16_t OverCurrent:1; // {[0] = "No Over-current", [1] = "over-current condition exists" } 67 | uint16_t Reserved:14; 68 | // wHubChange 69 | uint16_t LocalPowerSourceChg:1; // {[0] = "no change", [1] = "changed"} 70 | uint16_t OverCurrentChg:1; // {[0] = "no change", [1] = "changed"} 71 | uint16_t Reserved:14; 72 | } 73 | ]]) 74 | 75 | local function hubStatus(data, context) 76 | data = data .. "\x00\x00\x00\x00" 77 | local s,cs = unpack("I2I2", data) 78 | return struct_hub_status:build(data, "Hub Status & Change Status").html 79 | end 80 | 81 | local struct_port_status = html.create_struct([[ 82 | struct{ 83 | // wPortStatus 84 | uint16_t Connect:1; // {[0] = "disconnected", [1] = "connected" } 85 | uint16_t Enabled:1; // {[0] = "Disabled", [1] = "Enabled", } 86 | uint16_t Suspend:1; // {[0] = "Not Suspend", [1] = "Suspend/Resuming", } 87 | uint16_t OverCurrent:1; // {[0] = "No", [1] = "Over-current occur", } 88 | uint16_t Reset:1; // {[0] = "not asserted", [1] = "Asserted", } 89 | uint16_t Reserved:3; 90 | uint16_t Power:1; // {[0] = "Power off", [1] = "Power on", } 91 | uint16_t LowSpeed:1; // {[0] = "Full/High Speed", [1] = "Low Speed", } 92 | uint16_t HighSpeed:1; // {[0] = "Full Speed", [1] = "High Speed", } 93 | uint16_t TestMode:1; // {[0] = "Not Test Mode", [1] = "Test Mode", } 94 | uint16_t IndicatorCtrl:1; // {[0] = "default", [1] = "Software control", } 95 | uint16_t Reserved:3; 96 | // wPortChange 97 | uint16_t ConnectChg:1; // {[0] = "No Change", [1] = "Changed", } 98 | uint16_t EnabledChg:1; // {[0] = "No Change", [1] = "Disable by Port_Error condition", } 99 | uint16_t SuspendChg:1; // {[0] = "No Change", [1] = "Resuming Complete", } 100 | uint16_t OverCurrentChg:1; // {[0] = "No Change", [1] = "Over-current changed", } 101 | uint16_t ResetChg:1; // {[0] = "No Change", [1] = "Reset complete", } 102 | uint16_t Reserved:13; 103 | } 104 | ]]) 105 | 106 | local function portStatus(data, context) 107 | data = data .. "\x00\x00\x00\x00" 108 | local s,cs = unpack("I2I2", data) 109 | return struct_port_status:build(data, "Port Status & Change Status").html 110 | end 111 | 112 | local field_clear_tt = html.create_field([[ 113 | struct{ 114 | // wIndex 115 | uint16_t EndpointNumber:4; 116 | uint16_t DeviceAddr:7; 117 | uint16_t EndpointType:2; // {[0] = "Control", [1] = "ISO", [2] = "Bulk", [3] = "Interrupt" } 118 | uint16_t Reserved:2; 119 | uint16_t Direction:1; // {[0] = "OUT", [1] = "IN" } 120 | } 121 | ]]) 122 | 123 | local hub_render = { 124 | [macro_defs.GET_DESCRIPTOR] = {"Hub Get Desc" }, 125 | [macro_defs.SET_DESCRIPTOR] = {"Hub Set Desc" }, 126 | [macro_defs.CLEAR_FEATURE] = {"Hub Clear Feat", hub_feat_sel }, 127 | [macro_defs.SET_FEATURE] = {"Hub Set Feat", hub_feat_sel }, 128 | [macro_defs.GET_STATUS] = {"Hub Get Status"}, 129 | } 130 | 131 | local port_render = { 132 | [macro_defs.CLEAR_FEATURE] = {"Port Clear Feat", port_feat_sel, field_wIndex_port_feature}, 133 | [macro_defs.SET_FEATURE] = {"Port Set Feat", port_feat_sel, field_wIndex_port_feature}, 134 | [macro_defs.GET_STATUS] = {"Port Get Status", nil, "port" }, 135 | [CLEAR_TT_BUFFER] = {"Hub Clear TT", field_clear_tt}, 136 | [RESET_TT] = {"Hub Reset TT", nil, "Port"}, 137 | [GET_TT_STATE] = {"Hub Get TT State", "TT Flags", "Port"}, 138 | [STOP_TT] = {"Hub Stop TT", nil, "Port"}, 139 | } 140 | 141 | function cls.parse_setup(setup, context) 142 | if setup.recip == "Device" and setup.type == "Class" then 143 | setup.name = hub_render[setup.bRequest] and hub_render[setup.bRequest][1] 144 | setup.render.wValue = hub_render[setup.bRequest] and hub_render[setup.bRequest][2] 145 | setup.render.wIndex = hub_render[setup.bRequest] and hub_render[setup.bRequest][3] 146 | elseif setup.recip == "Other" and setup.type == "Class" then 147 | setup.name = port_render[setup.bRequest] and port_render[setup.bRequest][1] 148 | setup.render.wValue = port_render[setup.bRequest] and port_render[setup.bRequest][2] 149 | setup.render.wIndex = port_render[setup.bRequest] and port_render[setup.bRequest][3] 150 | else 151 | return 152 | end 153 | setup.render.bRequest = setup.name 154 | setup.render.title = "HUB Requset" .. (setup.name or "") 155 | setup.title = "HUB Requset" 156 | end 157 | 158 | function cls.parse_setup_data(setup, data, context) 159 | if setup.recip == "Device" and setup.type == "Class" then 160 | if setup.bRequest == macro_defs.GET_DESCRIPTOR or setup.bRequest == macro_defs.SET_DESCRIPTOR then 161 | local r = cls.descriptor_parser(data, 1, context) 162 | if r then return r.html end 163 | elseif setup.bRequest == macro_defs.GET_STATUS then 164 | return hubStatus(data, context) 165 | end 166 | elseif setup.recip == "Other" and setup.type == "Class" then 167 | if setup.bRequest == macro_defs.GET_STATUS then 168 | return portStatus(data, context) 169 | end 170 | end 171 | return nil 172 | end 173 | 174 | local struct_hub_desc = html.create_struct([[ 175 | struct{ 176 | uint8_t bLength; 177 | uint8_t bDescriptorType; // _G.get_descriptor_name 178 | uint8_t bNbrPorts; 179 | // wHubCharacteristics 180 | uint16_t LogicalPowerSwitchingMode:2; // {[0] = "Global", [1] = "Individual", [2] = "Reserved.", [3] = "Reserved."} 181 | uint16_t Compound Device:1; // {[0] = "not part of a compound device", [1] = "part of a compound device"} 182 | uint16_t OverCurrentProtMode:2; // {[0] = "Global", [1] = "Individual", [2] = "No Protection", [2] = "No Protection"} 183 | uint16_t TT_ThinkTime:2; // {[0] = "8 FS bit", [1] = "16 FS bit", [2] = "24 FS bit", [3] = "32 FS bit"} 184 | uint16_t PortIndicators:1; // {[0] = "not supported", [1] = "supported"} 185 | uint16_t reserved:8; 186 | uint8_t bPwrOn2PwrGood; // unit 2ms 187 | uint8_t bHubContrCurrent; // unit 1mA 188 | uint8_t DeviceRemovable; 189 | uint8_t PortPwrCtrlMask; 190 | } 191 | ]]) 192 | 193 | function cls.descriptor_parser(data, offset, context) 194 | if unpack("I1", data, offset + 1) ~= macro_defs.HUB_DESC then 195 | return nil 196 | end 197 | return struct_hub_desc:build(data:sub(offset), "Hub Descriptor") 198 | end 199 | 200 | cls.bInterfaceClass = 0x09 201 | cls.bInterfaceSubClass = 0x00 202 | cls.bInterfaceProtocol = nil 203 | cls.endpoints = { EP_IN("Hub Notify") } 204 | 205 | function cls.on_transaction(self, param, data, needDetail, forceBegin) 206 | local addr, ep, pid, ack = param:byte(1), param:byte(2), param:byte(3), param:byte(4) 207 | if pid ~= macro_defs.PID_IN then 208 | return macro_defs.RES_NONE 209 | end 210 | if needDetail then 211 | return macro_defs.RES_BEGIN_END, self.upv.make_xact_res("Hub Notify"), self.upv.make_xfer_res({ 212 | title = "Hub Notify Data", 213 | name = "Notify Data", 214 | desc = "Notify Data", 215 | status = "success", 216 | infoHtml = "

Hub Notify data

", 217 | data = data, 218 | }) 219 | end 220 | return macro_defs.RES_BEGIN_END 221 | end 222 | 223 | function cls.get_name(desc, context) 224 | local proto = "Hub Protocol" 225 | if desc.bInterfaceProtocol == 1 then 226 | proto = "Single TT Protocol" 227 | elseif desc.bInterfaceProtocol == 2 then 228 | proto = "Multiple TT Protocol" 229 | end 230 | return { 231 | bInterfaceClass = "Hub Class", 232 | bInterfaceSubClass = "Hub Sub Class", 233 | bInterfaceProtocol = proto, 234 | } 235 | end 236 | register_class_handler(cls) 237 | package.loaded["usb_class_hub"] = cls 238 | -------------------------------------------------------------------------------- /scripts/usb_class_msc_bot.lua: -------------------------------------------------------------------------------- 1 | -- usb_class_msc_bot.lua 2 | 3 | -- a typical class has these functions 4 | -- cls.parse_setup(setup, context), update setup.html, setup.name field in setup, and return it 5 | -- cls.parse_setup_data(setup, data, context) return a html to describe the data 6 | -- cls.on_transaction(self, param, data, needDetail, forceBegin) return macro_defs.RES_xxx 7 | -- cls.descriptor_parser(data, offset, context) return a parsed descriptor 8 | -- cls.get_name(descriptor, context) return a field name table 9 | -- MSC class definition https://www.usb.org/sites/default/files/usbmassbulk_10.pdf 10 | 11 | local html = require("html") 12 | local macro_defs = require("macro_defs") 13 | require("usb_setup_parser") 14 | require("usb_register_class") 15 | 16 | local scsi = require("decoder_scsi") 17 | 18 | local fmt = string.format 19 | local unpack = string.unpack 20 | local cls = {} 21 | cls.name = "MSC BOT" 22 | 23 | local BOT_GET_MAX_LUN = 0xfe 24 | local BOT_RESET = 0xff 25 | 26 | function cls.parse_setup(setup, context) 27 | if setup.recip ~= "Interface" or setup.type ~= "Class" then 28 | return 29 | end 30 | local bRequest_desc = "MSC Unknown Request" 31 | if setup.bRequest == BOT_GET_MAX_LUN then 32 | bRequest_desc = "Get Max LUN" 33 | elseif setup.bRequest == BOT_RESET then 34 | bRequest_desc = "BOT Reset" 35 | end 36 | setup.name = bRequest_desc 37 | setup.title = "MSC Request" 38 | setup.render.title = "MSC Request " .. bRequest_desc 39 | setup.render.bRequest = bRequest_desc 40 | end 41 | 42 | function cls.parse_setup_data(setup, data, context) 43 | if setup.bRequest == BOT_GET_MAX_LUN then 44 | local lun = unpack("I1", data) 45 | return "

Max Logic Unit Number " .. lun .."

" 46 | end 47 | return nil 48 | end 49 | 50 | function cls.on_transaction(self, param, data, needDetail, forceBegin) 51 | local addr, ep, pid, ack = param:byte(1), param:byte(2), param:byte(3), param:byte(4) 52 | local context = self:get_context(needDetail) 53 | self.addr = addr 54 | context.state = context.state or macro_defs.ST_CBW 55 | if forceBegin then 56 | context.state = macro_defs.ST_CBW 57 | end 58 | if #data == 31 and data:sub(1,4) == "USBC" then 59 | context.state = macro_defs.ST_CBW 60 | end 61 | if #data == 13 and data:sub(1,4) == "USBS" then 62 | context.state = macro_defs.ST_CSW 63 | end 64 | 65 | if context.state == macro_defs.ST_CBW then 66 | if #data ~= 31 then 67 | return macro_defs.RES_NONE 68 | end 69 | if ack ~= macro_defs.PID_ACK then 70 | return macro_defs.RES_NONE 71 | end 72 | local xfer_len = unpack("I4", data, 9) 73 | if xfer_len > 0 then 74 | context.state = macro_defs.ST_DATA 75 | else 76 | context.state = macro_defs.ST_CSW 77 | end 78 | context.data = "" 79 | context.xfer_len = xfer_len 80 | if needDetail then 81 | context.cbw = scsi.parse_cmd(data, self) 82 | context.infoHtml = context.cbw.html 83 | context.title = "BOT SCSI CMD" 84 | context.name = "SCSI CMD" 85 | context.desc = context.cbw.name 86 | context.status = "incomp" 87 | return macro_defs.RES_BEGIN, self.upv.make_xact_res("CBW", context.cbw.html, data), self.upv.make_xfer_res(context) 88 | end 89 | return macro_defs.RES_BEGIN 90 | elseif context.state == macro_defs.ST_DATA then 91 | if ack == macro_defs.PID_STALL then 92 | context.state = macro_defs.ST_CSW 93 | if needDetail then 94 | context.status = "stall" 95 | return macro_defs.RES_MORE, self.upv.make_xact_res("SCSI Stall", "", data), self.upv.make_xfer_res(context) 96 | end 97 | return macro_defs.RES_MORE 98 | end 99 | context.data = context.data .. data 100 | if self.upv:is_short_packet(addr, ep, data) then 101 | context.state = macro_defs.ST_CSW 102 | elseif #context.data == context.xfer_len then 103 | context.state = macro_defs.ST_CSW 104 | end 105 | if needDetail then 106 | if context.state == macro_defs.ST_CSW then 107 | context.infoHtml = (context.infoHtml or "") .. scsi.parse_data(context.cbw, context.data, self) 108 | end 109 | return macro_defs.RES_MORE, self.upv.make_xact_res("SCSI DATA", "", data), self.upv.make_xfer_res(context) 110 | end 111 | return macro_defs.RES_MORE 112 | elseif context.state == macro_defs.ST_CSW then 113 | if ack == macro_defs.PID_STALL then 114 | return macro_defs.RES_MORE 115 | end 116 | if ack ~= macro_defs.PID_ACK then 117 | return macro_defs.RES_END 118 | end 119 | if #data ~= 13 then 120 | return macro_defs.RES_END 121 | end 122 | if needDetail then 123 | local status = scsi.parse_status(context.cbw, data, self) 124 | context.infoHtml = (context.infoHtml or "") .. status.html 125 | context.data = context.data or "" 126 | context.title = context.title or "" 127 | context.name = context.name or "" 128 | context.desc = context.desc or "" 129 | context.status = status.status 130 | return macro_defs.RES_END, self.upv.make_xact_res("CSW", status.html, data), self.upv.make_xfer_res(context) 131 | end 132 | return macro_defs.RES_END 133 | else 134 | context.state = macro_defs.ST_CBW 135 | return macro_defs.RES_NONE 136 | end 137 | end 138 | 139 | 140 | cls.bInterfaceClass = 0x08 141 | cls.bInterfaceSubClass = 0x06 142 | cls.bInterfaceProtocol = 0x50 143 | cls.endpoints = { EP_IN("Incoming Data"), EP_OUT("Outgoing Data") } 144 | 145 | function cls.get_name(desc, context) 146 | return { 147 | bInterfaceClass = "Mass Storage Class", 148 | bInterfaceSubClass = "SCSI transparent command set", 149 | bInterfaceProtocol = "Bulk Only Transport", 150 | } 151 | end 152 | 153 | register_class_handler(cls) 154 | package.loaded["usb_class_msc_bot"] = cls 155 | -------------------------------------------------------------------------------- /scripts/usb_control_transfer.lua: -------------------------------------------------------------------------------- 1 | -- usb_control_transfer.lua 2 | -- control transfer parser 3 | 4 | local html = require("html") 5 | local fmt = string.format 6 | local unpack = string.unpack 7 | local setup_parser = require("usb_setup_parser") 8 | local macro_defs = require("macro_defs") 9 | 10 | local function on_transaction(self, param, data, needDetail, forceBegin) 11 | local addr, ep, pid, ack = param:byte(1), param:byte(2), param:byte(3), param:byte(4) 12 | local context 13 | if needDetail then 14 | self.detail = self.detail or {} 15 | context = self.detail 16 | else 17 | self.simple = self.simple or {} 18 | context = self.simple 19 | end 20 | self.addr = addr 21 | self.interfaces = self.interfaces or {} 22 | self.interface_data = self.interface_data or {} 23 | context.state = context.state or macro_defs.ST_SETUP 24 | if forceBegin or (pid == macro_defs.PID_SETUP) then 25 | context.state = macro_defs.ST_SETUP 26 | end 27 | 28 | if (context.state ~= macro_defs.ST_SETUP) and (ack ~= macro_defs.PID_ACK) then 29 | if ack == macro_defs.PID_STALL then 30 | context.status = "stall" 31 | if needDetail then 32 | return macro_defs.RES_END, self.upv.make_xact_res("Stall Ack", "", data), self.upv.make_xfer_res(context) 33 | else 34 | return macro_defs.RES_END 35 | end 36 | end 37 | context.status = "error" 38 | if needDetail then 39 | return macro_defs.RES_END, self.upv.make_xact_res("Error Ack", "", data), self.upv.make_xfer_res(context) 40 | else 41 | return macro_defs.RES_END 42 | end 43 | end 44 | 45 | if context.state == macro_defs.ST_SETUP then 46 | -- setup stage 47 | if (pid == macro_defs.PID_SETUP) and (#data == 8) then 48 | -- got setup packet 49 | local len = unpack("I2", data, 7) 50 | if len == 0 then 51 | if data:byte(1) > 127 then 52 | -- wrong condition 53 | context.state = macro_defs.ST_STATUS_OUT 54 | else 55 | context.state = macro_defs.ST_STATUS_IN 56 | end 57 | else 58 | if data:byte(1) > 127 then 59 | context.state = macro_defs.ST_DATA_IN 60 | else 61 | context.state = macro_defs.ST_DATA_OUT 62 | end 63 | end 64 | context.setup = { 65 | data = data 66 | } 67 | context.data = "" 68 | if needDetail then 69 | context.setup = setup_parser.parse_setup(context.setup.data, self) 70 | context.infoHtml = context.setup.html 71 | context.title = "Control Out" 72 | if data:byte(1) > 127 then 73 | context.title = "Control In" 74 | end 75 | context.name = context.setup.title or "Request" 76 | context.desc = context.setup.name 77 | context.status = "incomp" 78 | return macro_defs.RES_BEGIN, self.upv.make_xact_res("Setup", context.infoHtml, data), self.upv.make_xfer_res(context) 79 | end 80 | return macro_defs.RES_BEGIN 81 | else 82 | return macro_defs.RES_NONE 83 | end 84 | elseif context.state == macro_defs.ST_DATA_IN then 85 | -- data in stage 86 | if (pid == macro_defs.PID_IN) then 87 | context.data = context.data .. data 88 | if needDetail then 89 | local t = setup_parser.parse_data(context.setup, context.data, self) 90 | return macro_defs.RES_MORE, self.upv.make_xact_res("Data In", t, context.data), self.upv.make_xfer_res(context) 91 | end 92 | return macro_defs.RES_MORE 93 | elseif pid == macro_defs.PID_OUT then 94 | -- status out stage 95 | local setupType = context.setup.data:sub(1,4) 96 | context.state = macro_defs.ST_SETUP 97 | if needDetail then 98 | local t = setup_parser.parse_data(context.setup, context.data, self) 99 | context.infoHtml = context.infoHtml .. (t or "") 100 | context.status = "success" 101 | return macro_defs.RES_END, self.upv.make_xact_res("Status Out", "", context.data), self.upv.make_xfer_res(context) 102 | else 103 | if addr ~= 0 then 104 | if setupType == "\x80\x06\x00\x01" then 105 | self:setup_device_handler(context.data) 106 | elseif setupType:sub(1,2) == "\x80\x06" and setupType:sub(4) == "\x02"then 107 | self:setup_interface_handler(context.data) 108 | end 109 | end 110 | end 111 | return macro_defs.RES_END 112 | else 113 | return macro_defs.RES_NONE 114 | end 115 | elseif context.state == macro_defs.ST_DATA_OUT then 116 | -- data out stage 117 | if pid == macro_defs.PID_OUT then 118 | context.data = context.data .. data 119 | if needDetail then 120 | local t = setup_parser.parse_data(context.setup, context.data, self) 121 | return macro_defs.RES_MORE, self.upv.make_xact_res("Data Out", t, context.data), self.upv.make_xfer_res(context) 122 | end 123 | return macro_defs.RES_MORE 124 | elseif pid == macro_defs.PID_IN then 125 | -- status in stage 126 | context.state = macro_defs.ST_SETUP 127 | if needDetail then 128 | local t = setup_parser.parse_data(context.setup, context.data, self) 129 | context.infoHtml = context.infoHtml .. t 130 | context.status = "success" 131 | return macro_defs.RES_END, self.upv.make_xact_res("Status In", "", context.data), self.upv.make_xfer_res(context) 132 | end 133 | return macro_defs.RES_END 134 | else 135 | return macro_defs.RES_NONE 136 | end 137 | elseif context.state == macro_defs.ST_STATUS_IN then 138 | -- status in stage 139 | if pid == macro_defs.PID_IN then 140 | context.state = macro_defs.ST_SETUP 141 | if needDetail then 142 | context.status = "success" 143 | return macro_defs.RES_END, self.upv.make_xact_res("Status In", "", context.data), self.upv.make_xfer_res(context) 144 | end 145 | return macro_defs.RES_END 146 | else 147 | return macro_defs.RES_NONE 148 | end 149 | elseif context.state == macro_defs.ST_STATUS_OUT then 150 | -- status out stage 151 | if pid == macro_defs.PID_OUT then 152 | context.state = macro_defs.ST_SETUP 153 | if needDetail then 154 | context.status = "success" 155 | return macro_defs.RES_END, self.upv.make_xact_res("Status Out", "", context.data), self.upv.make_xfer_res(context) 156 | end 157 | return macro_defs.RES_END 158 | else 159 | return macro_defs.RES_NONE 160 | end 161 | else 162 | context.state = macro_defs.ST_SETUP 163 | return macro_defs.RES_NONE 164 | end 165 | end 166 | 167 | local function get_endpoint_interface(self, endpoint) 168 | return self.endpoint_itf[endpoint], self.endpoint_alt[endpoint] 169 | end 170 | 171 | local function get_interface_class(self, index) 172 | return self.interfaces[index] 173 | end 174 | 175 | local function get_interface_data(self, itf, alt) 176 | local res = self.interface_data[itf] 177 | if not res then 178 | self.interface_data[itf] = {} 179 | end 180 | return self.interface_data[itf] 181 | end 182 | 183 | local function current_interface_data(self) 184 | return get_interface_data(self, self.current_itf_n, self.current_itf_alt) 185 | end 186 | 187 | local function set_current_interface(self, itf_desc) 188 | self.current_itf_n = itf_desc:byte(3) 189 | self.current_itf_alt = itf_desc:byte(4) 190 | end 191 | 192 | local function current_device(self) 193 | return self.device_parser or {} 194 | end 195 | -- get class by full descriptor info 196 | local function find_class(self, itf_desc, iad_desc) 197 | local iad = nil 198 | if iad_desc then 199 | iad = iad_desc.rawData 200 | end 201 | return self.upv:find_parser_by_interface(itf_desc.rawData, iad) 202 | end 203 | 204 | -- setup device handler by device descriptor 205 | local function setup_device_handler(self, device_desc) 206 | if #device_desc == 18 then 207 | local mps, vid, pid = unpack("I1I2I2", device_desc, 8) 208 | self.upv:set_device_mps(self.addr, mps) 209 | self.device_parser = self.upv:find_parser_by_vid_pid(vid, pid) 210 | local devClass = self.upv:find_parser_by_interface("1" .. device_desc) 211 | if devClass then 212 | self.device_parser = self.device_parser or {} 213 | self.device_parser.deviceClass = self.device_parser.deviceClass or devClass 214 | end 215 | end 216 | end 217 | 218 | -- setup interface handler by config descriptor 219 | local function setup_interface_handler(self, config_desc) 220 | -- fast parse the config descriptor 221 | local i = 1 222 | local itf_desc = nil 223 | local iad_desc = nil 224 | local iad_cnt = 0 225 | local total = #config_desc 226 | local cur_itf_n = 0 227 | local cur_alt_n = 0 228 | local decoder = nil 229 | local eps = {} 230 | local mpss = {} 231 | while (i + 1) < total do 232 | local len = config_desc:byte(i) 233 | local t = config_desc:byte(i+1) 234 | if len < 2 then break end 235 | if len + i > #config_desc + 1 then 236 | break 237 | end 238 | if t == macro_defs.IAD_DESC then 239 | if #eps > 0 then 240 | if decoder then 241 | self.upv:setup_decoder(self.addr, eps, decoder, mpss) 242 | end 243 | eps = {} 244 | mpss = {} 245 | end 246 | iad_desc = config_desc:sub(i, i + len - 1) 247 | iad_cnt = iad_desc:byte(4) 248 | elseif t == macro_defs.INTERFACE_DESC then 249 | if #eps > 0 then 250 | if decoder then 251 | self.upv:setup_decoder(self.addr, eps, decoder, mpss) 252 | end 253 | eps = {} 254 | mpss = {} 255 | end 256 | itf_desc = config_desc:sub(i, i + len - 1) 257 | cur_itf_n = itf_desc:byte(3) 258 | cur_alt_n = itf_desc:byte(4) 259 | self.interface_data[cur_itf_n] = self.interface_data[cur_itf_n] or {} 260 | local handler = nil 261 | decoder = nil 262 | if self.device_parser and self.device_parser.get_interface_handler then 263 | handler = self.device_parser.get_interface_handler(self, cur_itf_n) 264 | end 265 | if not handler then 266 | handler = self.upv:find_parser_by_interface(itf_desc, iad_desc) 267 | end 268 | if handler then 269 | self.interfaces[cur_itf_n] = handler 270 | if handler.make_decoder then 271 | decoder = handler.make_decoder(self.upv) 272 | end 273 | end 274 | if iad_cnt > 0 then 275 | iad_cnt = iad_cnt - 1 276 | end 277 | if iad_cnt == 0 then 278 | iad_desc = nil 279 | end 280 | elseif t == macro_defs.ENDPOINT_DESC then 281 | local ep = config_desc:byte(i+2) 282 | self.endpoint_itf = self.endpoint_itf or {} 283 | self.endpoint_itf[ep] = cur_itf_n 284 | self.endpoint_alt = self.endpoint_alt or {} 285 | self.endpoint_alt[ep] = cur_alt_n 286 | local mps = config_desc:byte(i+4) + (config_desc:byte(i+5) * 256) 287 | eps[#eps+1] = ep 288 | mpss[#mpss+1] = mps 289 | end 290 | i = i + len 291 | end 292 | 293 | if #eps > 0 then 294 | if decoder then 295 | self.upv:setup_decoder(self.addr, eps, decoder, mpss) 296 | end 297 | eps = {} 298 | mpss = {} 299 | end 300 | 301 | end 302 | 303 | local function make_control_xfer_decoder(upv) 304 | local res = {} 305 | res.on_transaction = on_transaction 306 | res.setup_device_handler = setup_device_handler 307 | res.setup_interface_handler = setup_interface_handler 308 | res.get_interface_class = get_interface_class 309 | res.get_endpoint_interface = get_endpoint_interface 310 | res.current_interface_data = current_interface_data 311 | res.set_current_interface = set_current_interface 312 | res.get_interface_data = get_interface_data 313 | res.current_device = current_device 314 | res.find_class = find_class 315 | res.upv = upv 316 | return res 317 | end 318 | 319 | package.loaded["usb_control_transfer"] = make_control_xfer_decoder 320 | -------------------------------------------------------------------------------- /scripts/usb_descriptor_parser.lua: -------------------------------------------------------------------------------- 1 | -- usb_descriptor_parser.lua 2 | local macro_defs = require("macro_defs") 3 | local html = require("html") 4 | local parser = {} 5 | local descTable = {} 6 | local fmt = string.format 7 | local unpack = string.unpack 8 | 9 | local last_vid = 0 10 | _G.render_vid = function(vid) 11 | last_vid = vid 12 | return 'Who\'s that?' 13 | end 14 | _G.render_pid = function(pid) 15 | return 'What\'s it?' 16 | end 17 | 18 | local function make_desc_parser(name, info) 19 | local builder = html.create_struct(info) 20 | return function(data, offset, context) 21 | local rawData = data:sub(offset) 22 | local res = builder:build(rawData, name .. " Descriptor") 23 | res.rawData = rawData 24 | return res 25 | end 26 | end 27 | 28 | local parse_unknown_desc = make_desc_parser("Unknown", [[ 29 | struct { 30 | uint8_t bLength; // {format = "dec"} 31 | uint8_t bDescriptorType; // _G.get_descriptor_name 32 | uint8_t data[bLength-2]; 33 | } 34 | ]]) 35 | 36 | descTable[macro_defs.DEVICE_DESC] = make_desc_parser("Device", [[ 37 | uint8_t bLength; // {format = "dec"} 38 | uint8_t bDescriptorType; // _G.get_descriptor_name 39 | uint16_t bcdUSB; 40 | uint8_t bDeviceClass; 41 | uint8_t bDeviceSubClass; 42 | uint8_t bDeviceProtocol; 43 | uint8_t bMaxPacketSize; 44 | uint16_t idVendor; // _G.render_vid 45 | uint16_t idProduct; // _G.render_pid 46 | uint16_t bcdDevice; 47 | uint8_t iManufacturer; 48 | uint8_t iProduct; 49 | uint8_t iSerial; 50 | uint8_t bNumConfigurations; 51 | ]]) 52 | 53 | 54 | descTable[macro_defs.DEV_QUAL_DESC] = make_desc_parser("Device Qualifier", [[ 55 | uint8_t bLength; // {format = "dec"} 56 | uint8_t bDescriptorType; // _G.get_descriptor_name 57 | uint16_t bcdUSB; 58 | uint8_t bDeviceClass; 59 | uint8_t bDeviceSubClass; 60 | uint8_t bDeviceProtocol; 61 | uint8_t bMaxPacketSize; 62 | uint8_t bNumConfigurations; 63 | uint8_t bReserved; 64 | ]]) 65 | 66 | descTable[macro_defs.CFG_DESC] = make_desc_parser("Config", [[ 67 | uint8_t bLength; // {format = "dec"} 68 | uint8_t bDescriptorType; // _G.get_descriptor_name 69 | uint16_t wTotalLength; 70 | uint8_t bNumInterfaces; 71 | uint8_t bConfigurationValue; 72 | uint8_t iConfiguration; 73 | // bmAttributes 74 | uint8_t Reserved:5; 75 | uint8_t RemoteWakeup:1; { [0]="No", [1]="Yes" } 76 | uint8_t SelfPowered:1; { [0]="No", [1]="Yes" } 77 | uint8_t Reserved:1; 78 | uint8_t bMaxPower; // {format="dec", comment = "x 2mA"} 79 | ]]) 80 | 81 | descTable[macro_defs.STRING_DESC] = make_desc_parser("String",[[ 82 | uint8_t bLength; // {format = "dec"} 83 | uint8_t bDescriptorType; // _G.get_descriptor_name 84 | uint16_t wString[bLength/2-1]; // {format = "unicode"} 85 | ]]) 86 | 87 | local currentInterface = {} 88 | _G.get_InterfaceClass = function() 89 | return currentInterface.bInterfaceClass or "" 90 | end 91 | _G.get_InterfaceSubClass = function() 92 | return currentInterface.bInterfaceSubClass or "" 93 | end 94 | _G.get_InterfaceProtocol = function() 95 | return currentInterface.bInterfaceProtocol or "" 96 | end 97 | 98 | descTable[macro_defs.INTERFACE_DESC] = make_desc_parser("Interface", [[ 99 | uint8_t bLength; // {format = "dec"} 100 | uint8_t bDescriptorType; // _G.get_descriptor_name 101 | uint8_t bInterfaceNumber; 102 | uint8_t bAlternateSetting; 103 | uint8_t bNumEndpoints; 104 | uint8_t bInterfaceClass; // _G.get_InterfaceClass 105 | uint8_t bInterfaceSubClass; // _G.get_InterfaceSubClass 106 | uint8_t bInterfaceProtocol; // _G.get_InterfaceProtocol 107 | uint8_t iInterface; 108 | ]]) 109 | 110 | descTable[macro_defs.ENDPOINT_DESC] = make_desc_parser("Endpoint", [[ 111 | uint8_t bLength; // {format = "dec"} 112 | uint8_t bDescriptorType; // _G.get_descriptor_name 113 | // bEndpointAddress 114 | uint8_t EndpointAddress:4; 115 | uint8_t Reserved:3; 116 | uint8_t Direction:1; // {[0] ="OUT", [1]="IN"} 117 | // bmAttributes 118 | uint8_t Type:2; // {[0]="Control", [1]="Isochronous", [2]="Bulk", [3]="Interrupt"} 119 | uint8_t SyncType:2; // {[0]="No Synchonisation", [1]="Asynchronous", [2]="Adaptive", [3]="Synchronous"} 120 | uint8_t UsageType:2; // {[0]="Data Endpoint", [1]="Feedback Endpoint", [2]="Explicit Feedback Data Endpoint", [3]="Reserved"} 121 | uint8_t PacketPerFrame:2;// {[0]="1", [1]="2", [2]="3", [3]="Reserved"} 122 | uint16_t wMaxPacketSize; // {format = "dec"} 123 | uint8_t bInterval; 124 | ]]) 125 | 126 | descTable[macro_defs.IAD_DESC] = make_desc_parser("IAD", [[ 127 | uint8_t bLength; // {format = "dec"} 128 | uint8_t bDescriptorType; // _G.get_descriptor_name 129 | uint8_t bFirstInterface; 130 | uint8_t bInterfaceCount; 131 | uint8_t bFunctionClass; 132 | uint8_t bFunctionSubClass; 133 | uint8_t bFunctionProtocol; 134 | uint8_t iFunction; 135 | ]]) 136 | 137 | function parser.parse(data, context) 138 | local info = {} 139 | local offset = 1 140 | local lastInterface = nil 141 | local lastIad = nil 142 | local lastIadCount = 0 143 | while offset < #data do 144 | local t = data:byte(offset+1) 145 | local parseFunc = descTable[t] 146 | local desc = nil 147 | 148 | if parseFunc then --standard descriptor 149 | desc = parseFunc(data, offset, context) 150 | if desc.bDescriptorType == macro_defs.INTERFACE_DESC then 151 | context:set_current_interface(desc.rawData) 152 | local cls = context:find_class(desc, lastIad) 153 | if cls and cls.get_name then 154 | currentInterface = cls.get_name(desc, context) 155 | desc = parseFunc(data, offset, context) 156 | currentInterface = {} 157 | end 158 | lastInterface = desc 159 | 160 | if lastIadCount > 0 then 161 | lastIadCount = lastIadCount - 1 162 | end 163 | if lastIadCount == 0 then 164 | lastIad = nil 165 | end 166 | elseif desc.bDescriptorType == macro_defs.IAD_DESC then 167 | lastIad = desc 168 | lastIadCount = desc.bInterfaceCount 169 | end 170 | elseif lastInterface then 171 | local cls = context:find_class(lastInterface, lastIad) 172 | if cls and cls.descriptor_parser then 173 | desc = cls.descriptor_parser(data, offset, context) 174 | end 175 | end 176 | 177 | -- use the default parse function. 178 | desc = desc or parse_unknown_desc(data, offset, context) 179 | 180 | offset = offset + desc.bLength 181 | info.html = info.html or "" 182 | info.html = info.html .. desc.html 183 | info[#info+1] = desc 184 | if desc.bLength < 2 then 185 | break 186 | end 187 | end 188 | --gotDescriptor(info, context) 189 | return info 190 | end 191 | 192 | package.loaded["usb_descriptor_parser"] = parser 193 | 194 | -------------------------------------------------------------------------------- /scripts/usb_device_aw_efex.lua: -------------------------------------------------------------------------------- 1 | -- allwinner sunxi FEL and FES decode 2 | 3 | -- protocol are defined in linux-sunxi.org, about more informations: 4 | -- FEL: https://linux-sunxi.org/FEL/Protocol 5 | -- FES: https://linux-sunxi.org/FES 6 | 7 | -- qianfan Zhao 8 | 9 | local html = require("html") 10 | local macro_defs = require("macro_defs") 11 | require("usb_register_class") 12 | 13 | local fmt = string.format 14 | local unpack = string.unpack 15 | 16 | local DIRECTION_DEVICE_TO_HOST = 0x11 17 | local DIRECTION_HOST_TO_DEVICE = 0x12 18 | 19 | local struct_fel_awuc = html.create_struct([[ 20 | struct { 21 | uint32_t magic; 22 | uint32_t tag; 23 | uint32_t data_transfer_len; 24 | uint16_t unused1; 25 | uint8_t unused2; 26 | uint8_t cmd_len; 27 | uint8_t direction; 28 | uint8_t unused3; 29 | uint32_t data_len; 30 | uint8_t unused4[10]; 31 | }]], { 32 | direction = { 33 | [0x00] = "RESERVED", 34 | [DIRECTION_DEVICE_TO_HOST] = "-> Host", 35 | [DIRECTION_HOST_TO_DEVICE] = "-> Device", 36 | format = "hex", 37 | } 38 | } 39 | ) 40 | 41 | -- AWUC command used in FEL, started with AWUC, 32 bytes. 42 | local function parse_fel_awuc(data, self) 43 | local cbw = {} 44 | 45 | if self.is_new_cmd == false and #data ~= 32 then 46 | cbw.html = "

Wrong FEL AWUC length

" 47 | return cbw 48 | end 49 | 50 | self.cbw_direction = unpack("I1", data, 17) 51 | cbw = struct_fel_awuc:build(data, "FEL AWUC") 52 | cbw.name = "FEL" 53 | 54 | return cbw 55 | end 56 | 57 | local struct_awus = html.create_struct([[ 58 | struct { 59 | uint32_t magic; 60 | uint32_t tag; 61 | uint32_t residue; 62 | uint8_t status; 63 | }]], { 64 | status = { 65 | [0] = "Passed", 66 | [1] = "Failed", 67 | } 68 | } 69 | ) 70 | 71 | local function parse_awus(cbw, data, self) 72 | local csw = {} 73 | 74 | csw.status = "error" 75 | csw.name = "AWUS" 76 | 77 | if #data ~= 13 then 78 | csw.html = "

Wrong AWUS length

" 79 | return csw 80 | end 81 | 82 | local r = struct_awus:build(data, "AWUS") 83 | csw.html = r.html 84 | 85 | if r.status == 0 then 86 | csw.status = "success" 87 | end 88 | 89 | return csw 90 | end 91 | 92 | -- FEL command sets: 93 | local FEL_CMD_VERIFY = 0x0001 94 | local FEL_CMD_SWITCH_ROLE = 0x0002 95 | local FEL_CMD_IS_READY = 0x0003 96 | local FEL_CMD_GET_CMD_SET_VER = 0x0004 97 | local FEL_CMD_DISCONNECT = 0x0010 98 | local FEL_CMD_DOWNLOAD = 0x0101 99 | local FEL_CMD_EXEC = 0x0102 100 | local FEL_CMD_READ = 0x0103 101 | 102 | -- FES command sets: 103 | local FES_CMD_TRANSFER = 0x0201 104 | local FES_CMD_DOWNLOAD = 0x0206 105 | local FES_CMD_UPLOAD = 0x0207 106 | local FES_CMD_QUERY_STORAGE = 0x0209 107 | local FES_CMD_FLASH_SET_ON = 0x020a 108 | local FES_CMD_VERIFY_VALUE = 0x020c 109 | local FES_CMD_VERIFY_STATUS = 0x020d 110 | 111 | local command_names = { 112 | [FEL_CMD_VERIFY] = "Verify", 113 | [FEL_CMD_SWITCH_ROLE] = "Switch", 114 | [FEL_CMD_IS_READY] = "Ready?", 115 | [FEL_CMD_GET_CMD_SET_VER] = "Version", 116 | [FEL_CMD_DISCONNECT] = "Disconnect", 117 | [FEL_CMD_DOWNLOAD] = "Download", 118 | [FEL_CMD_EXEC] = "Exec", 119 | [FEL_CMD_READ] = "Read", 120 | [FES_CMD_TRANSFER] = "Transfer", 121 | [FES_CMD_DOWNLOAD] = "Download", 122 | [FES_CMD_UPLOAD] = "Upload", 123 | [FES_CMD_QUERY_STORAGE] = "Storage?", 124 | [FES_CMD_FLASH_SET_ON] = "FlashON", 125 | [FES_CMD_VERIFY_VALUE] = "VerifyVal", 126 | [FES_CMD_VERIFY_STATUS] = "VerifySt", 127 | 128 | -- not a command, it is reported by device. 129 | -- create it to make the parser happy. 130 | [0xffff] = "Status", 131 | } 132 | 133 | local function default_cmd_parser(data, self) 134 | local cmd = unpack("I2", data) 135 | 136 | return string.format("

%s

", command_names[cmd] or "???") 137 | end 138 | 139 | local function cmd_build_html(cmd, data, b) 140 | local name = command_names[cmd] or "???" 141 | 142 | return b:build(data, name) 143 | end 144 | 145 | local function parse_fel_cmd_verify(data, self) 146 | -- AWUC + VERIFY + AWUS 147 | -- AWUC + VERIFY RESULT(to host) 32B + AWUS 148 | self.trans_len = 32 149 | return default_cmd_parser(data, self) 150 | end 151 | 152 | local struct_fel_cmd_download = html.create_struct([[ 153 | struct { 154 | uint32_t cmd; 155 | uint32_t addr; 156 | uint32_t len; 157 | uint32_t unused; 158 | }]] 159 | ) 160 | 161 | local function parse_fel_cmd_download(data, self) 162 | local b = cmd_build_html(FEL_CMD_DOWNLOAD, data, struct_fel_cmd_download) 163 | self.trans_len = b.len 164 | 165 | return b.html 166 | end 167 | 168 | local function parse_fel_cmd_exec(data, self) 169 | self.trans_len = 0 170 | -- FEL_CMD_EXEC has the same struct with FEL_CMD_DOWNLOAD 171 | return cmd_build_html(FEL_CMD_EXEC, data, struct_fel_cmd_download).html 172 | end 173 | 174 | local function parse_fel_cmd_read(data, self) 175 | -- FEL_CMD_READ has the same struct with FEL_CMD_DOWNLOAD 176 | local b = cmd_build_html(FEL_CMD_READ, data, struct_fel_cmd_download) 177 | 178 | self.trans_len = b.len 179 | return b.html 180 | end 181 | 182 | local struct_fes_cmd_transfer = html.create_struct([[ 183 | struct { 184 | uint16_t cmd; 185 | uint16_t tag; 186 | uint32_t addr; 187 | uint32_t len; 188 | uint8_t logic_unit:4; 189 | uint8_t media_index:4; 190 | uint8_t res:4; 191 | uint8_t dir:2; 192 | uint8_t ooc:2; 193 | uint16_t unused; 194 | }]], { 195 | dir = { 196 | [0] = "download", 197 | [1] = "download", 198 | [2] = "upload", 199 | } 200 | } 201 | ) 202 | 203 | local function parse_fes_cmd_transfer(data, self) 204 | self.trans_len = unpack("I4", data, 9) 205 | 206 | return cmd_build_html(FES_CMD_TRANSFER, data, struct_fes_cmd_transfer).html 207 | end 208 | 209 | -- parse the command host send to device 210 | local fel_command_parses = { 211 | [FEL_CMD_VERIFY] = parse_fel_cmd_verify, 212 | [FEL_CMD_SWITCH_ROLE] = default_cmd_parser, 213 | [FEL_CMD_IS_READY] = default_cmd_parser, 214 | [FEL_CMD_GET_CMD_SET_VER] = default_cmd_parser, 215 | [FEL_CMD_DISCONNECT] = default_cmd_parser, 216 | [FEL_CMD_DOWNLOAD] = parse_fel_cmd_download, 217 | [FEL_CMD_EXEC] = parse_fel_cmd_exec, 218 | [FEL_CMD_READ] = parse_fel_cmd_read, 219 | [FES_CMD_TRANSFER] = parse_fes_cmd_transfer, 220 | } 221 | 222 | local struct_fes_trans_param = html.create_struct([[ 223 | struct { 224 | uint16_t cmd; 225 | uint16_t tag; 226 | uint32_t addr; 227 | uint32_t len; 228 | uint32_t data_type; 229 | }]], { 230 | data_type = { 231 | [0x7f00] = "DRAM", 232 | [0x7f01] = "MBR", 233 | [0x7f02] = "BOOT1", 234 | [0x7f03] = "BOOT0", 235 | [0x7f04] = "ERASE", 236 | [0x7f05] = "PMU", 237 | [0x7f06] = "UNSEQ_READ", 238 | [0x7f07] = "UNSEQ_WRITE", 239 | [0x7f10] = "FULLIMG_SIZE", 240 | [0x17f00] = "(finish)DRAM", 241 | [0x17f01] = "(finish)MBR", 242 | [0x17f02] = "(finish)BOOT1", 243 | [0x17f03] = "(finish)BOOT0", 244 | [0x17f04] = "(finish)ERASE", 245 | [0x17f05] = "(finish)PMU", 246 | [0x17f06] = "(finish)UNSEQ_READ", 247 | [0x17f07] = "(finish)UNSEQ_WRITE", 248 | [0x17f10] = "(finish)FULLIMG_SIZE", 249 | } 250 | } 251 | ) 252 | 253 | local function parse_fes_cmd_download(data, self) 254 | self.trans_len = unpack("I4", data, 9) 255 | 256 | return cmd_build_html(FES_CMD_DOWNLOAD, data, struct_fes_trans_param).html 257 | end 258 | 259 | local struct_fes_cmd_verify_status = html.create_struct([[ 260 | struct { 261 | uint16_t cmd; 262 | uint16_t tag; 263 | uint32_t start; 264 | uint32_t size; 265 | uint32_t data_tag; 266 | }]] 267 | ) 268 | 269 | local function parse_fes_cmd_verify_status(data, self) 270 | self.trans_len = 12 271 | self.cbw_direction = DIRECTION_DEVICE_TO_HOST 272 | return cmd_build_html(FES_CMD_VERIFY_STATUS, data, struct_fes_cmd_verify_status).html 273 | end 274 | 275 | local function parse_fes_cmd_query_storage(data, self) 276 | self.trans_len = 4 277 | self.cbw_direction = DIRECTION_DEVICE_TO_HOST 278 | 279 | return default_cmd_parser(data, self) 280 | end 281 | 282 | local function parse_fes_cmd_flash_set_on(data, self) 283 | self.trans_len = 0 284 | 285 | return default_cmd_parser(data, self) 286 | end 287 | 288 | local struct_fes_cmd_verify_value = html.create_struct([[ 289 | struct { 290 | uint16_t cmd; 291 | uint16_t tag; 292 | uint32_t start; 293 | uint32_t size; 294 | uint32_t unused; 295 | }]] 296 | ) 297 | 298 | local function parse_fes_cmd_verify_value(data, self) 299 | self.trans_len = 12 300 | self.cbw_direction = DIRECTION_DEVICE_TO_HOST 301 | 302 | return cmd_build_html(FES_CMD_VERIFY_VALUE, data, struct_fes_cmd_verify_value).html 303 | end 304 | 305 | local fes_command_parses = { 306 | [FES_CMD_DOWNLOAD] = parse_fes_cmd_download, 307 | [FES_CMD_VERIFY_STATUS] = parse_fes_cmd_verify_status, 308 | [FES_CMD_QUERY_STORAGE] = parse_fes_cmd_query_storage, 309 | [FES_CMD_FLASH_SET_ON] = parse_fes_cmd_flash_set_on, 310 | [FES_CMD_VERIFY_VALUE] = parse_fes_cmd_verify_value, 311 | } 312 | 313 | -- AWUC command used in FES, 20 bytes and ending with AWUC 314 | local function parse_fes_awuc(data, self) 315 | local cbw = { } 316 | local cmd = unpack("I2", data) 317 | local f = fes_command_parses[cmd] 318 | 319 | self.last_cmd = cmd 320 | cbw.name = command_names[cmd] or "???" 321 | 322 | -- set the default direction HOST_TO_DEVICE, the parse function 323 | -- should change the direction if it is DEVICE_TO_HOST. 324 | self.cbw_direction = DIRECTION_HOST_TO_DEVICE 325 | 326 | if f then 327 | cbw.html = f(data, self) 328 | else 329 | cbw.html = "

doesn't support now

" 330 | end 331 | 332 | return cbw 333 | end 334 | 335 | local struct_fel_cmd_data_verify = html.create_struct([[ 336 | struct { 337 | uint8_t magic[8]; 338 | uint32_t platform_id_hw; 339 | uint32_t platform_id_fw; 340 | uint16_t mode; 341 | uint8_t phoenix_data_flag; 342 | uint8_t phoenix_data_len; 343 | uint32_t phoenix_data_start_addr; 344 | uint8_t unused[2]; 345 | }]] 346 | ) 347 | 348 | local function parse_fes_cmd_data_verify(data, self) 349 | return cmd_build_html(FEL_CMD_VERIFY, data, struct_fel_cmd_data_verify).html 350 | end 351 | 352 | local function parse_fes_cmd_data_switch_role(data, self) 353 | return default_cmd_parser(data, self) 354 | end 355 | 356 | local struct_fel_cmd_data_is_ready = html.create_struct([[ 357 | struct { 358 | uint16_t state; 359 | uint16_t interval_ms; 360 | uint8_t unused[12]; 361 | }]], { 362 | state = { 363 | [0x00] = "NULL", 364 | [0x01] = "BUSY", 365 | [0x02] = "READY", 366 | [0x03] = "FAIL", 367 | format = "dec", 368 | } 369 | } 370 | ) 371 | 372 | local function parse_fes_cmd_data_is_ready(data, self) 373 | return cmd_build_html(FEL_CMD_IS_READY, data, struct_fel_cmd_data_is_ready).html 374 | end 375 | 376 | local function parse_fes_cmd_data_disconnect(data, self) 377 | return default_cmd_parser(data, self) 378 | end 379 | 380 | local struct_fes_cmd_verify_status = html.create_struct([[ 381 | struct { 382 | uint32_t tag; 383 | uint32_t fes_crc; 384 | uint32_t media_crc; 385 | }]] 386 | ) 387 | 388 | local function parse_fes_cmd_verify_status(data, self) 389 | return cmd_build_html(FES_CMD_VERIFY_STATUS, data, struct_fes_cmd_verify_status).html 390 | end 391 | 392 | local struct_fes_cmd_data_query_storage = html.create_struct([[ 393 | struct { 394 | uint32_t storage; 395 | }]], { 396 | storage = { 397 | [0] = "NAND", 398 | [1] = "SD", 399 | [2] = "EMMC", 400 | [3] = "NOR", 401 | } 402 | } 403 | ) 404 | 405 | local function parse_fes_cmd_data_query_storage(data, self) 406 | return cmd_build_html(FES_CMD_QUERY_STORAGE, data, struct_fes_cmd_data_query_storage).html 407 | end 408 | 409 | -- parse the command device response to host 410 | local command_data_parses = { 411 | [FEL_CMD_VERIFY] = parse_fes_cmd_data_verify, 412 | [FEL_CMD_SWITCH_ROLE] = parse_fes_cmd_data_is_ready, 413 | [FEL_CMD_IS_READY] = parse_fel_cmd_is_ready, 414 | [FEL_CMD_DISCONNECT] = parse_fel_cmd_disconnect, 415 | 416 | [FES_CMD_VERIFY_STATUS] = parse_fes_cmd_verify_status, 417 | [FES_CMD_QUERY_STORAGE] = parse_fes_cmd_data_query_storage, 418 | 419 | -- VERIFY_VALUE has the same response with VERIFY_STATUS 420 | [FES_CMD_VERIFY_VALUE] = parse_fes_cmd_verify_status, 421 | } 422 | 423 | local struct_app_report_status = html.create_struct([[ 424 | struct { 425 | uint16_t mark; 426 | uint16_t tag; 427 | uint8_t status; 428 | uint8_t unused[3]; 429 | }]] 430 | ) 431 | 432 | local function parse_app_report_status(data, self) 433 | self.last_cmd = 0xffff 434 | return cmd_build_html(self.last_cmd, data, struct_app_report_status).html 435 | end 436 | 437 | local function parse_data(cbw, data, self) 438 | local default_debug_info = string.format("Debug informations
\ 439 | direction: 0x%x
\ 440 | last_cmd: 0x%x
\ 441 | trans_len: %d
\ 442 | data_size: %d", 443 | self.cbw_direction or 0xFFFFFFFF, 444 | self.last_cmd or 0xFFFFFFFF, 445 | self.trans_len or -1, 446 | #data 447 | ) 448 | 449 | if self.cbw_direction == DIRECTION_HOST_TO_DEVICE then 450 | if self.last_cmd == FES_CMD_TRANSFER or 451 | self.last_cmd == FES_CMD_DOWNLOAD or 452 | self.last_cmd == FEL_CMD_DOWNLOAD then 453 | if self.trans_len == #data then 454 | local length = self.trans_len 455 | 456 | self.trans_len = 0 457 | 458 | return string.format("

TRANSFER DATA(to device)


\ 459 | See data Window
\ 460 | length = %d
", length) 461 | else 462 | -- the command and transfer data doesn't match, 463 | -- clear state machine and try again 464 | self.last_cmd = 0 465 | self.trans_len = 0 466 | parse_data(cbw, data, self) 467 | end 468 | elseif self.is_new_cmd == false then -- FEL formater 469 | if #data == 16 then -- commnad 470 | local op = unpack("I2", data) 471 | local f = fel_command_parses[op] 472 | 473 | if f then 474 | self.last_cmd = op 475 | return f(data, self) 476 | end 477 | end 478 | end 479 | elseif self.cbw_direction == DIRECTION_DEVICE_TO_HOST then 480 | if self.last_cmd ~= 0 then 481 | if self.last_cmd == FES_CMD_TRANSFER or 482 | self.last_cmd == FEL_CMD_READ then 483 | if self.trans_len == #data then 484 | local length = self.trans_len 485 | 486 | self.trans_len = 0 487 | 488 | return string.format("

TRANSFER DATA(to host)


\ 489 | See data Window
\ 490 | length = %d
", length) 491 | else 492 | -- clear state machine and try again 493 | self.last_cmd = 0 494 | self.trans_len = 0 495 | parse_data(cbw, data, self) 496 | end 497 | else 498 | local f = command_data_parses[self.last_cmd] 499 | 500 | -- clear self.trans_len so that on_transaction could clear last_cmd 501 | -- don't clear self.last_cmd due to we display the name of last_cmd 502 | -- in on_transaction. 503 | self.trans_len = 0 504 | 505 | if f then 506 | local html = f(data, self) 507 | if html then 508 | return html 509 | end 510 | end 511 | end 512 | end 513 | 514 | -- if the topper level doesn't match, try parser as report status 515 | -- CBW/CBS done, the device report status 516 | if #data == 8 then 517 | return parse_app_report_status(data, self) 518 | end 519 | end 520 | 521 | return "

Unknow DATA

" .. default_debug_info 522 | end 523 | 524 | local device = {} 525 | device.name = "allwinner" 526 | 527 | local cls = {} 528 | cls.name = "FEL/FES" 529 | cls.endpoints = { EP_IN("Incoming Data"), EP_OUT("Outgoing Data") } 530 | 531 | function cls.on_transaction(self, param, data, needDetail, forceBegin) 532 | local addr, ep, pid, ack = param:byte(1), param:byte(2), param:byte(3), param:byte(4) 533 | local context = self:get_context(needDetail) 534 | 535 | self.addr = addr 536 | self.cbw_direction = self.cbw_direction or DIRECTION_HOST_TO_DEVICE 537 | self.last_cmd = self.last_cmd or 0 538 | self.trans_len = self.trans_len or -1 539 | 540 | context.state = context.state or macro_defs.ST_CBW 541 | if forceBegin then 542 | context.state = macro_defs.ST_CBW 543 | end 544 | if #data == 32 and data:sub(1,4) == "AWUC" then 545 | context.state = macro_defs.ST_CBW 546 | self.is_new_cmd = false 547 | end 548 | if #data == 20 and data:sub(-4) == "AWUC" then 549 | -- allwinner fes new command 550 | context.state = macro_defs.ST_CBW 551 | self.is_new_cmd = true 552 | end 553 | if #data == 13 and data:sub(1,4) == "AWUS" then 554 | context.state = macro_defs.ST_CSW 555 | end 556 | 557 | if context.state == macro_defs.ST_CBW then 558 | if self.is_new_cmd == true and #data ~= 20 then 559 | return macro_defs.RES_NONE 560 | elseif self.is_new_cmd == false and #data ~= 32 then 561 | return macro_defs.RES_NONE 562 | end 563 | if ack ~= macro_defs.PID_ACK then 564 | return macro_defs.RES_NONE 565 | end 566 | 567 | local xfer_len = 0 568 | local parse_result 569 | local title 570 | 571 | if self.is_new_cmd == false then 572 | xfer_len = unpack("I4", data, 9) 573 | parse_result = parse_fel_awuc(data, self) 574 | title = "FEL" 575 | else 576 | parse_result = parse_fes_awuc(data, self) 577 | xfer_len = self.trans_len 578 | title = "FES" 579 | end 580 | 581 | if xfer_len > 0 then 582 | context.state = macro_defs.ST_DATA 583 | else 584 | context.state = macro_defs.ST_CSW 585 | end 586 | 587 | context.data = "" 588 | context.xfer_len = xfer_len 589 | if needDetail then 590 | context.cbw = parse_result 591 | context.infoHtml = context.cbw.html 592 | context.title = title 593 | context.name = title 594 | context.desc = context.cbw.name 595 | context.status = "incomp" 596 | return macro_defs.RES_BEGIN, self.upv.make_xact_res("AWUC", context.cbw.html, data), self.upv.make_xfer_res(context) 597 | end 598 | return macro_defs.RES_BEGIN 599 | elseif context.state == macro_defs.ST_DATA then 600 | if ack == macro_defs.PID_STALL then 601 | context.state = macro_defs.ST_CSW 602 | if needDetail then 603 | context.status = "stall" 604 | return macro_defs.RES_MORE, self.upv.make_xact_res("Stall", "", data), self.upv.make_xfer_res(context) 605 | end 606 | return macro_defs.RES_MORE 607 | end 608 | context.data = context.data .. data 609 | if self.upv:is_short_packet(addr, ep, data) then 610 | context.state = macro_defs.ST_CSW 611 | elseif #context.data == context.xfer_len then 612 | context.state = macro_defs.ST_CSW 613 | end 614 | if needDetail then 615 | if context.state == macro_defs.ST_CSW then 616 | context.infoHtml = (context.infoHtml or "") .. parse_data(context.cbw, context.data, self) 617 | -- try update context.desc based on self.last_cmd if it is FEL command. 618 | -- because command defined in DATA sections. 619 | if self.is_new_cmd == false and self.last_cmd ~= 0 then 620 | context.desc = command_names[self.last_cmd] or "???" 621 | end 622 | 623 | if self.trans_len <= 0 then 624 | -- transfer is done 625 | self.last_cmd = 0 626 | end 627 | end 628 | return macro_defs.RES_MORE, self.upv.make_xact_res("DATA", "", data), self.upv.make_xfer_res(context) 629 | end 630 | return macro_defs.RES_MORE 631 | elseif context.state == macro_defs.ST_CSW then 632 | if ack == macro_defs.PID_STALL then 633 | return macro_defs.RES_MORE 634 | end 635 | if ack ~= macro_defs.PID_ACK then 636 | return macro_defs.RES_END 637 | end 638 | if #data ~= 13 then 639 | return macro_defs.RES_END 640 | end 641 | if needDetail then 642 | local status = parse_awus(context.cbw, data, self) 643 | context.infoHtml = (context.infoHtml or "") .. status.html 644 | context.data = context.data or "" 645 | context.title = context.title or "" 646 | context.name = context.name or "" 647 | context.desc = context.desc or "" 648 | context.status = status.status 649 | return macro_defs.RES_END, self.upv.make_xact_res("AWUS", status.html, data), self.upv.make_xfer_res(context) 650 | end 651 | return macro_defs.RES_END 652 | else 653 | context.state = macro_defs.ST_CBW 654 | return macro_defs.RES_NONE 655 | end 656 | end 657 | 658 | function device.get_interface_handler(self, itf_number) 659 | return cls 660 | end 661 | 662 | register_device_handler(device, 0x1f3a, 0xefe8) 663 | register_class_handler(cls) 664 | package.loaded["usb_device_aw_efex"] = cls 665 | -------------------------------------------------------------------------------- /scripts/usb_device_ftdi.lua: -------------------------------------------------------------------------------- 1 | -- usb_device_ftdi.lua 2 | 3 | local html = require("html") 4 | local macro_defs = require("macro_defs") 5 | require("usb_setup_parser") 6 | require("usb_register_class") 7 | 8 | local fmt = string.format 9 | local unpack = string.unpack 10 | local device = {} 11 | device.name = "FTDI FT232" 12 | 13 | local FTDI_RESET = 0x00 14 | local FTDI_MODEM_CTRL = 0x01 15 | local FTDI_SET_FLOW_CTRL = 0x02 16 | local FTDI_SET_BAUDRATE = 0x03 17 | local FTDI_SET_LN_CODE = 0x04 18 | local FTDI_POLL_STATUS = 0x05 19 | local FTDI_SET_EVENT_CHAR = 0x06 20 | local FTDI_SET_ERROR_CHAR = 0x07 21 | local FTDI_SET_LATENCY_TIMER = 0x09 22 | local FTDI_GET_LATENCY_TIMER = 0x0A 23 | local FTDI_SET_BITMODE = 0x0B 24 | local FTDI_READ_PINS = 0x0C 25 | local FTDI_READ_EEPROM = 0x90 26 | local FTDI_WRITE_EEPROM = 0x91 27 | local FTDI_ERASE_EEPROM = 0x92 28 | 29 | local ftdi_action = { 30 | [FTDI_RESET] = {"Reset"}, 31 | [FTDI_MODEM_CTRL] = {"Modem Ctrl", "", ""}, 32 | [FTDI_SET_FLOW_CTRL] = {"Set Flow Ctrl", "", ""}, 33 | [FTDI_SET_BAUDRATE] = {"Set Baudrate", "low part", ""}, 34 | [FTDI_SET_LN_CODE] = {"Set Ln Code", "", ""}, 35 | [FTDI_POLL_STATUS] = {"Poll status", "", ""}, 36 | [FTDI_SET_EVENT_CHAR] = {"Set evt char"}, 37 | [FTDI_SET_ERROR_CHAR] = {"Set error char"}, 38 | [FTDI_SET_LATENCY_TIMER] = {"Set Latence", "Latence"}, 39 | [FTDI_GET_LATENCY_TIMER] = {"Get Latence", "", "", "Latence"}, 40 | [FTDI_SET_BITMODE] = {"Set bit mode"}, 41 | [FTDI_READ_PINS] = {"Read Pins", "", "", "Pin Data"}, 42 | 43 | [FTDI_READ_EEPROM] = {"Read EEPROM", "", "EE Location", "EEPROM DATA" }, 44 | [FTDI_WRITE_EEPROM] = {"Write EEPROM", "", "EE Location", "EEPROM DATA" }, 45 | [FTDI_ERASE_EEPROM] = {"Erase EEPROM", "", "" }, 46 | } 47 | 48 | local wValue_render = { 49 | [FTDI_SET_LN_CODE] = html.create_field([[ 50 | struct { 51 | // wValue 52 | uint16_t data_bits:8; // data bits 53 | uint16_t parity:3; // {[0] = "None", [1] = "Odd", [2] = "Even", [3] = "Mark" , [4] = "Space"} 54 | uint16_t stop_bits:3; // {[0] = "1 bits", [1] = "1.5 bits", [2] = "2 bits"} 55 | uint16_t break:1; // {[0] = "break off", [1] = "break on"} 56 | uint16_t reserved:1; 57 | } 58 | ]]), 59 | [FTDI_MODEM_CTRL] = html.create_field([[ 60 | struct { 61 | // wValue 62 | uint16_t DTR:1; // {[0] = "low", [1] = "high"} 63 | uint16_t RTS:1; // {[0] = "low", [1] = "high"} 64 | uint16_t reserved:6; 65 | uint16_t DTR_MASK:1; 66 | uint16_t RTS_MASK:1; 67 | uint16_t reserved:6; 68 | } 69 | ]]), 70 | [FTDI_SET_FLOW_CTRL] = html.create_field([[ 71 | struct { 72 | // wValue 73 | uint16_t reserved:8; 74 | uint16_t RTS_CTS:1; // {[0] = "off", [1] = "on"} 75 | uint16_t DTR_DST:1; // {[0] = "off", [1] = "on"} 76 | uint16_t xOn_xOff:1; // {[0] = "off", [1] = "on"} 77 | uint16_t reserved:5; 78 | } 79 | ]]), 80 | [FTDI_SET_EVENT_CHAR] = html.create_field([[ 81 | struct { 82 | // wValue 83 | uint16_t event_char:8; 84 | uint16_t enable:1; // {[0] = "disable", [1] = "enable"} 85 | uint16_t reserved:7; 86 | } 87 | ]]), 88 | [FTDI_SET_ERROR_CHAR] = html.create_field([[ 89 | struct { 90 | // wValue 91 | uint16_t error_char:8; 92 | uint16_t enable:1; // {[0] = "disable", [1] = "enable"} 93 | uint16_t reserved:7; 94 | } 95 | ]]), 96 | [FTDI_SET_BITMODE] = html.create_field([[ 97 | struct { 98 | // wValue 99 | uint16_t mask:8; 100 | uint16_t RESET:1; 101 | uint16_t BITBANG:1; 102 | uint16_t MPSSE:1; 103 | uint16_t MCU:1; 104 | uint16_t OPTO:1; 105 | uint16_t CBUS:1; 106 | uint16_t SYNCFF:1; 107 | uint16_t FT1284:1; 108 | } 109 | ]]) 110 | } 111 | 112 | local wIndex_render = { 113 | [FTDI_SET_BAUDRATE] = html.create_field([[ 114 | struct { 115 | // wValue 116 | uint16_t index:8; 117 | uint16_t baudrate:8; // high part 118 | } 119 | ]]) 120 | } 121 | 122 | local reset_value_desc = { 123 | [0] = "Reset", 124 | [1] = "Purge Rx", 125 | [1] = "Purge Tx", 126 | } 127 | 128 | local struct_ftdi_status_header = html.create_struct([[ 129 | struct { 130 | // wValue 131 | uint16_t reserved:4; 132 | uint16_t CTS:1; 133 | uint16_t DTS:1; 134 | uint16_t RI:1; 135 | uint16_t RLSD:1; 136 | uint16_t DataReady:1; 137 | uint16_t Overrun:1; 138 | uint16_t ParityError:1; 139 | uint16_t FrameError:1; 140 | uint16_t BreakInt:1; 141 | uint16_t TransmitHolding:1; 142 | uint16_t TransmitEmpty:1; 143 | uint16_t RxFifoError:1; 144 | } 145 | ]]) 146 | 147 | function device.parse_setup(setup, context) 148 | if setup.type ~= "Vendor" then 149 | return 150 | end 151 | local action = ftdi_action[setup.bRequest] 152 | local bRequest_desc = "FTDI Unknown" 153 | local wValue_desc = nil 154 | local wIndex_desc = nil 155 | if action then 156 | bRequest_desc = action[1] or bRequest_desc 157 | wValue_desc = action[2] or wValue_desc 158 | wIndex_desc = action[3] or wIndex_desc 159 | end 160 | if setup.bRequest == FTDI_READ_EEPROM or setup.bRequest == FTDI_WRITE_EEPROM then 161 | bRequest_desc = bRequest_desc .. ":".. setup.wIndex 162 | end 163 | setup.title = "FTDI Request" 164 | setup.name = bRequest_desc 165 | setup.render.title = "FTDI " .. bRequest_desc 166 | setup.render.bRequest = bRequest_desc 167 | setup.render.wValue = wValue_render[setup.bRequest] or wValue_desc 168 | setup.render.wIndex = wIndex_render[setup.bRequest] or wIndex_desc 169 | end 170 | 171 | local function make_ftdi_status(data) 172 | local v = 0 173 | if #data > 1 then 174 | v = unpack("I2", data) 175 | end 176 | return struct_ftdi_status_header:build(data, "FTDI Status").html 177 | end 178 | 179 | function device.parse_setup_data(setup, data, context) 180 | if setup.type ~= "Vendor" then 181 | return nil 182 | end 183 | if setup.bRequest == FTDI_POLL_STATUS then 184 | return make_ftdi_status(data) 185 | end 186 | local action = ftdi_action[setup.bRequest] 187 | local desc = "Unknown Data" 188 | if action then 189 | desc = action[4] or desc 190 | end 191 | return "

"..desc.."

" 192 | end 193 | 194 | local cls = {} 195 | cls.name = "FTDI Data" 196 | cls.endpoints = { EP_IN("Incoming Data"), EP_OUT("Outgoing Data") } 197 | 198 | function cls.on_transaction(self, param, data, needDetail, forceBegin) 199 | local addr, ep, pid, ack = param:byte(1), param:byte(2), param:byte(3), param:byte(4) 200 | self.addr = addr 201 | if ack ~= macro_defs.PID_ACK then 202 | return macro_defs.RES_NONE 203 | end 204 | local context = self:get_context(needDetail, pid) 205 | context.data = context.data or "" 206 | if forceBegin then 207 | context.data = "" 208 | end 209 | local endMark = self.upv:is_short_packet(addr, ep, data) and macro_defs.RES_END or macro_defs.RES_NONE 210 | local begindMark = #context.data == 0 and macro_defs.RES_BEGIN or macro_defs.RES_NONE 211 | context.data = context.data .. data 212 | if #context.data >= 4096 then 213 | endMark = macro_defs.RES_END 214 | end 215 | local res = endMark | begindMark 216 | if res == macro_defs.RES_NONE then res = macro_defs.RES_MORE end 217 | 218 | if needDetail then 219 | context.status = "incomp" 220 | context.title = "FTDI Data Xfer" 221 | context.name = "FTDI Data" 222 | context.desc = "FTDI Data" 223 | context.infoHtml = "" 224 | if pid == macro_defs.PID_IN and #context.data > 1 then 225 | context.infoHtml = make_ftdi_status(context.data) 226 | end 227 | if endMark ~= macro_defs.RES_NONE then 228 | context.status = "success" 229 | end 230 | local xfer_res = self.upv.make_xfer_res(context) 231 | if endMark ~= macro_defs.RES_NONE then 232 | context.data = "" 233 | end 234 | return res, self.upv.make_xact_res("FTDI Data", context.infoHtml, data), xfer_res 235 | end 236 | if endMark ~= macro_defs.RES_NONE then 237 | context.data = "" 238 | end 239 | return res 240 | end 241 | 242 | function device.get_interface_handler(self, itf_number) 243 | return cls 244 | end 245 | 246 | register_device_handler(device, 0x0403, 0x6001) 247 | 248 | register_class_handler(cls) 249 | 250 | package.loaded["usb_device_ftdi"] = device 251 | -------------------------------------------------------------------------------- /scripts/usb_register_class.lua: -------------------------------------------------------------------------------- 1 | -- usb_register_class.lua 2 | --[[ 3 | local hid = require("usb_class_hid") 4 | local bot = require("usb_class_msc_bot") 5 | local cdc_acm = require("usb_class_cdc_acm") 6 | local data = require("usb_class_data") 7 | 8 | local function register(context) 9 | context:regClass(hid) 10 | context:regClass(bot) 11 | context:regClass(cdc_acm) 12 | context:regClass(data) 13 | end 14 | --]] 15 | 16 | local class_handlers = {} 17 | local class_map = {} 18 | local iad_map = {} 19 | 20 | local function cls2key(cls) 21 | local res = "" 22 | if cls.bInterfaceClass then 23 | res = res .. string.char(cls.bInterfaceClass) 24 | if cls.bInterfaceSubClass then 25 | res = res .. string.char(cls.bInterfaceSubClass) 26 | if cls.bInterfaceProtocol then 27 | res = res .. string.char(cls.bInterfaceProtocol) 28 | end 29 | end 30 | end 31 | return res 32 | end 33 | 34 | local function parse_ep_require(eps) 35 | local res = {} 36 | for i,v in ipairs(eps) do 37 | res[#res+1] = tonumber(v:sub(#v,#v)) 38 | end 39 | return res 40 | end 41 | 42 | local function check_ep_require(self, eps) 43 | local ep_cnt = #self.ep_require 44 | local ep_res = {} 45 | local ep_opt_cnt = 0 46 | for j,v in ipairs(self.ep_require) do 47 | if v >= 4 then 48 | ep_res[j] = 255 49 | ep_opt_cnt = ep_opt_cnt + 1 50 | end 51 | end 52 | 53 | for i=1,#eps do 54 | local ep = eps[i] 55 | for j,v in ipairs(self.ep_require) do 56 | if v == 1 or v == 5 then 57 | if (ep & 0x80) == 0x80 then 58 | ep_res[j] = ep 59 | ep_cnt = ep_cnt - 1 60 | break 61 | end 62 | elseif v == 0 or v == 4 then 63 | if (ep & 0x80) == 0x00 then 64 | ep_res[j] = ep 65 | ep_cnt = ep_cnt - 1 66 | break 67 | end 68 | else 69 | ep_res[j] = ep 70 | ep_cnt = ep_cnt - 1 71 | break 72 | end 73 | end 74 | end 75 | if ep_cnt > ep_opt_cnt then 76 | -- error("fail to parse endpoint requirement\n" .. debug.traceback()) 77 | end 78 | return ep_res 79 | end 80 | -- context related on needDetail or pid 81 | local function get_context(self, needDetail, pid) 82 | local context 83 | if needDetail then 84 | self.detail = self.detail or {} 85 | context = self.detail 86 | else 87 | self.simple = self.simple or {} 88 | context = self.simple 89 | end 90 | if pid then 91 | context.pid_map = context.pid_map or {} 92 | context.pid_map[pid] = context.pid_map[pid] or {} 93 | context = context.pid_map[pid] 94 | end 95 | return context 96 | end 97 | 98 | 99 | local function get_endpoint_interface_data(self, addr, ep) 100 | local dev = self.upv.get_decoder(addr, 0) 101 | if not dev then return {} end 102 | local itf, alt = dev:get_endpoint_interface(ep) 103 | return dev:get_interface_data(itf, alt) 104 | end 105 | 106 | function register_class_handler(cls) 107 | local old = class_handlers[cls.name] 108 | cls.ep_require = parse_ep_require(cls.endpoints or {}) 109 | cls.make_decoder = function(upv) 110 | local res = {} 111 | res.on_transaction = cls.on_transaction 112 | res.upv = upv 113 | res.name = cls.name 114 | res.ep_require = cls.ep_require 115 | res.check_ep_require = check_ep_require 116 | res.get_context = get_context 117 | res.class_handler = cls 118 | res.get_endpoint_interface_data = get_endpoint_interface_data 119 | return res 120 | end 121 | class_handlers[cls.name] = cls 122 | if cls.iad then 123 | local key = cls2key(cls) 124 | local iadKey= cls2key(cls.iad) 125 | iad_map[iadKey] = iad_map[iadKey] or {} 126 | iad_map[iadKey][key] = cls 127 | else 128 | local key = cls2key(cls) 129 | class_map[key] = cls 130 | end 131 | return old 132 | end 133 | 134 | local function find_something(map, key) 135 | while #key > 1 do 136 | local t = map[key] 137 | if t then return t end 138 | key = string.sub(key, 1, #key-1) 139 | end 140 | return map[key] 141 | end 142 | 143 | function find_class_handler(itf_desc, iad_desc) 144 | local key = nil 145 | if type(itf_desc) == "table" then 146 | key = cls2key(itf_desc) 147 | elseif type(itf_desc) == "string" then 148 | key = string.sub(itf_desc,6,8) 149 | else 150 | error("wrong itf desc" .. type(itf_desc)) 151 | return nil 152 | end 153 | if iad_desc then 154 | local iad_key = nil 155 | if type(itf_desc) == "table" then 156 | iad_key = cls2key(iad_desc) 157 | elseif type(itf_desc) == "string" then 158 | iad_key = string.sub(iad_desc,5,7) 159 | else 160 | error("wrong iad desc") 161 | return nil 162 | end 163 | local map = find_something(iad_map, iad_key) 164 | if map then 165 | return find_something(map, key) 166 | end 167 | return nil 168 | end 169 | return find_something(class_map, key) 170 | end 171 | 172 | local device_handlers = {} 173 | local device_handlers_map = {} 174 | local pack = string.pack 175 | function register_device_handler(dev, vid, pid) 176 | local t = pack("I2I2",vid,pid) 177 | local old = device_handlers[t] 178 | device_handlers[t] = dev 179 | device_handlers_map[dev.name] = dev 180 | return old 181 | end 182 | 183 | function find_device_handler(vid, pid) 184 | local t = pack("I2I2",vid,pid) 185 | return device_handlers[t] 186 | end 187 | 188 | function get_register_handler() 189 | local res = "" 190 | local sep = "" 191 | local n1 = {} 192 | 193 | for k,v in pairs(class_handlers) do 194 | n1[#n1+1] = k 195 | end 196 | 197 | table.sort(n1) 198 | 199 | for i,k in ipairs(n1) do 200 | local v = class_handlers[k] 201 | if v.endpoints then 202 | res = res .. sep .. k .. ":".. table.concat(v.endpoints, ",") 203 | sep = ";" 204 | end 205 | end 206 | 207 | --[[ 208 | local n2 = {} 209 | for k,v in pairs(device_handlers_map) do 210 | n2[#n2+1] = k 211 | end 212 | table.sort(n2) 213 | 214 | for i,k in ipairs(n2) do 215 | local v = device_handlers_map[k] 216 | if v.endpoints then 217 | res = res .. sep .. k .. ":".. table.concat(v.endpoints, ",") 218 | sep = ";" 219 | end 220 | end 221 | ]] 222 | return res 223 | end 224 | 225 | function find_handler_by_name(name) 226 | return class_handlers[name] -- or device_handlers_map[name] 227 | end 228 | 229 | local function register(context) 230 | end 231 | 232 | package.loaded["usb_register_class"] = register 233 | -------------------------------------------------------------------------------- /scripts/usb_setup_parser.lua: -------------------------------------------------------------------------------- 1 | -- usb_setup_parser.lua 2 | -- encoding: utf-8 3 | 4 | local parser = {} 5 | local fmt = string.format 6 | local unpack = string.unpack 7 | local html = require("html") 8 | local macro_defs = require("macro_defs") 9 | local desc_parser = require("usb_descriptor_parser") 10 | 11 | local field_bmRequest = html.create_field([[ 12 | struct{ 13 | // bmRequest 14 | uint8_t Recipient:5; // {[0] = "Device", [1] = "Interface", [2] = "Endpoint" ,[3]="Other"} 15 | uint8_t Type:2; // {[0] = "Standard", [1]="Class",[2]="Vendor",[3]="Reserved"} 16 | uint8_t Direction:1; // {[0] = "Host to Device", [1]="Device to Host"} 17 | } 18 | ]]) 19 | 20 | local field_wValue_get_desc = html.create_field([[ 21 | struct{ 22 | // wValue 23 | uint16_t Index:8; 24 | uint16_t Type:8; // _G.get_descriptor_name 25 | } 26 | ]]) 27 | 28 | local struct_device_status = html.create_struct([[ 29 | struct{ 30 | // wStatus 31 | uint16_t SelfPowered:1; // {[0] = "Bus Powered", [1] = "Self Powered"} 32 | uint16_t RemoteWakeUp:1; // {[0] = "Disabled", [1] = "Enabled"} 33 | uint16_t reserved:14; 34 | } 35 | ]]) 36 | 37 | local function render_field(setup, field, default) 38 | local render = setup.render[field] or default 39 | if render then 40 | if type(render) == "string" then 41 | return {field, fmt("%d", setup[field]), render } 42 | elseif type(render) == "table" then 43 | return html.expand_bit_field(setup[field], render) 44 | elseif type(render) == "function" then 45 | return {field, fmt("%d", setup[field]), render(setup[field])} 46 | end 47 | end 48 | return nil 49 | end 50 | 51 | function parser.parse_setup(data, context) 52 | local setup = {} 53 | setup.data = data 54 | local bmRequest, bRequest, wValue, wIndex, wLength = unpack("I1I1I2I2I2", setup.data .. "\xff\xff\xff\xff\xff\xff\xff\xff") 55 | setup.bmRequest = bmRequest 56 | setup.bRequest = bRequest 57 | setup.wValue = wValue 58 | setup.wIndex = wIndex 59 | setup.wLength = wLength 60 | setup.render = {} 61 | 62 | local typStr 63 | local reqTitle = "Unknwon Req" 64 | local typ = (bmRequest >> 5) & 3 65 | if typ == 0 then 66 | typStr = "Standard" 67 | reqTitle = "Standard Req" 68 | elseif typ == 1 then 69 | typStr = "Class" 70 | reqTitle = "Class Req" 71 | elseif typ == 2 then 72 | typStr = "Vendor" 73 | reqTitle = "Vendor Req" 74 | else typStr = "Reserved" 75 | end 76 | setup.type = typStr 77 | 78 | local recipStr 79 | local recip = bmRequest & 0x1f 80 | if recip == 0 then recipStr = "Device" 81 | elseif recip == 1 then recipStr = "Interface" 82 | elseif recip == 2 then recipStr = "Endpoint" 83 | elseif recip == 3 then recipStr = "Other" 84 | else recipStr = "Reserved" 85 | end 86 | setup.recip = recipStr 87 | if typStr == "Class" and recipStr == "Endpoint" then 88 | local itf = context:get_endpoint_interface(wIndex & 0xff) 89 | local cls = context.get_interface_class and context:get_interface_class(itf) 90 | cls = cls or context.class_handler 91 | if cls and cls.parse_setup then 92 | local r = cls.parse_setup(setup, context) 93 | if r then return r end 94 | end 95 | end 96 | if recipStr == "Interface" then 97 | local cls = context.get_interface_class and context:get_interface_class(wIndex & 0xff) 98 | cls = cls or context.class_handler 99 | if cls and cls.parse_setup then 100 | local r = cls.parse_setup(setup, context) 101 | if r then return r end 102 | end 103 | elseif recipStr == "Device" or recipStr == "Other" then 104 | if typStr == "Class" and context.current_device then 105 | local cls = context:current_device().deviceClass 106 | if cls and cls.parse_setup then 107 | local r = cls.parse_setup(setup, context) 108 | if r then return r end 109 | end 110 | end 111 | end 112 | 113 | local dev = context.current_device and context:current_device() 114 | if dev and dev.parse_setup then 115 | local r = dev.parse_setup(setup, context) 116 | if r then return r end 117 | end 118 | 119 | local bRequest_desc = "" 120 | if typStr == "Standard" then 121 | bRequest_desc = get_std_request_name(bRequest) 122 | elseif typStr == "Class" then 123 | bRequest_desc = " Class Req " .. bRequest 124 | elseif typStr == "Vendor" then 125 | bRequest_desc = " Vendor Req " .. bRequest 126 | end 127 | 128 | local wValue_field = "" 129 | if typStr == "Standard" then 130 | if (bRequest == macro_defs.CLEAR_FEATURE) or (bRequest == macro_defs.SET_FEATURE) then 131 | wValue_field = fmt("Feature: %d", wValue) 132 | elseif bRequest == macro_defs.SET_ADDRESS then 133 | wValue_field = fmt("Address: %d", wValue) 134 | elseif (bRequest == macro_defs.GET_DESCRIPTOR) or (bRequest == macro_defs.SET_DESCRIPTOR) then 135 | wValue_field = field_wValue_get_desc 136 | elseif bRequest == macro_defs.SET_CONFIG then 137 | wValue_field = fmt("Config: %d", wValue) 138 | end 139 | end 140 | 141 | local wIndex_desc = "" 142 | if recipStr == "Device" then 143 | if (wValue > 0) and ((bRequest == macro_defs.GET_DESCRIPTOR) or (bRequest == macro_defs.SET_DESCRIPTOR)) then 144 | wIndex_desc = fmt("Language ID: 0x%04x", wIndex) 145 | else 146 | end 147 | elseif recipStr == "Interface" then 148 | wIndex_desc = fmt("Interface: %d", wIndex) 149 | elseif recipStr == "Endpoint" then 150 | wIndex_desc = fmt("Endpoint: 0x%02X", wIndex & 0xff) 151 | end 152 | setup.html = html.make_table{ 153 | title = setup.render.title or (typStr .. " Request"), 154 | header = {"Field", "Value", "Description"}, 155 | render_field(setup, "bmRequest", field_bmRequest), 156 | render_field(setup, "bRequest", bRequest_desc), 157 | render_field(setup, "wValue", wValue_field), 158 | render_field(setup, "wIndex", wIndex_desc), 159 | render_field(setup, "wLength", ""), 160 | } 161 | setup.title = setup.title or reqTitle 162 | setup.name = setup.name or (typStr == "Standard" and get_std_request_name(bRequest, wValue, wIndex) or bRequest_desc) 163 | return setup 164 | end 165 | 166 | function parser.parse_data(setup, data, context) 167 | if setup.type == "Class" and setup.recip == "Endpoint" then 168 | local itf = context:get_endpoint_interface(setup.wIndex & 0xff) 169 | local cls = context.get_interface_class and context:get_interface_class(itf) 170 | cls = cls or context.class_handler 171 | if cls and cls.parse_setup_data then 172 | local r = cls.parse_setup_data(setup, data, context) 173 | if r then return r end 174 | end 175 | end 176 | if setup.recip == "Interface" then 177 | local cls = context.get_interface_class and context:get_interface_class(setup.wIndex & 0xff) 178 | cls = cls or context.class_handler 179 | if cls and cls.parse_setup_data then 180 | local r = cls.parse_setup_data(setup, data, context) 181 | if r then return r end 182 | end 183 | elseif setup.recip == "Device" or setup.recip == "Other" then 184 | if setup.type == "Class" then 185 | local cls = context.current_device and context:current_device().deviceClass 186 | if cls and cls.parse_setup_data then 187 | local r = cls.parse_setup_data(setup, data, context) 188 | if r then return r end 189 | end 190 | end 191 | end 192 | 193 | local dev = context.current_device and context:current_device() 194 | if dev and dev.parse_setup_data then 195 | local r = dev.parse_setup_data(setup, data, context) 196 | if r then return r end 197 | end 198 | 199 | if setup.type == "Standard" then 200 | if (setup.bRequest == macro_defs.GET_DESCRIPTOR or bRequest == macro_defs.SET_DESCRIPTOR) then 201 | if (setup.wValue >> 8) <= macro_defs.MAX_STD_DESC then 202 | local descInfo = desc_parser.parse(data, context) 203 | return descInfo.html 204 | end 205 | elseif setup.bRequest == macro_defs.GET_STATUS and #data >= 2 then 206 | return struct_device_status:build(data, "Device Status").html 207 | end 208 | end 209 | return "

Get " .. #data .. " bytes data

Display in data window" 210 | end 211 | 212 | package.loaded["usb_setup_parser"] = parser 213 | -------------------------------------------------------------------------------- /scripts/util.lua: -------------------------------------------------------------------------------- 1 | -- util.lua 2 | local macro_defs = require("macro_defs") 3 | local util = {} 4 | util.toHex = function(data, sep, record_per_line, line_break) 5 | local res = "" 6 | sep = sep or "" 7 | record_per_line = record_per_line or 8 8 | line_break = line_break or "\n" 9 | local sssep = "" 10 | if not data then return "" end 11 | local data_in_line = 0 12 | for i=1,#data do 13 | res = res .. sssep .. string.format( "%02X", data:byte(i)) 14 | sssep = sep 15 | data_in_line = data_in_line + 1 16 | if data_in_line == record_per_line then 17 | sssep = "" 18 | res = res .. line_break 19 | data_in_line = 0 20 | end 21 | end 22 | return res 23 | end 24 | 25 | local STD_REQ_NAME ={ 26 | "Get Status" , 27 | "Clear Feature" , 28 | "Reserved" , 29 | "Set Feature" , 30 | "Reserved" , 31 | "Set Address" , 32 | "Get Descriptor" , 33 | "Set Descriptor" , 34 | "Get Config" , 35 | "Set Config" , 36 | "Get Interface" , 37 | "Set Interface" , 38 | "Sync Frame" , 39 | } 40 | 41 | local STD_DESCRIPTOR_NAME = { 42 | "Undefined" , 43 | "Device" , 44 | "Configuration" , 45 | "String" , 46 | "Interface" , 47 | "Endpoint" , 48 | "Device Qualifier" , 49 | "Other Speed" , 50 | "Interface Power" , 51 | "OTG" , 52 | } 53 | 54 | local DESCRIPTOR_NAME = { 55 | [macro_defs.DEVICE_DESC ] = "DevDesc", 56 | [macro_defs.CFG_DESC ] = "CfgDesc", 57 | [macro_defs.STRING_DESC ] = "StrDesc", 58 | [macro_defs.DEV_QUAL_DESC ] = "QualDesc", 59 | [macro_defs.OTHER_DESC ] = "OtherSpeed", 60 | [macro_defs.BOS_DESC ] = "BOS Desc", 61 | } 62 | 63 | _G.EP_IN = function(name, optional) 64 | if optional then 65 | return name .. "5" 66 | end 67 | return name .. "1" 68 | end 69 | 70 | _G.EP_OUT = function(name, optional) 71 | if optional then 72 | return name .. "4" 73 | end 74 | return name .. "0" 75 | end 76 | 77 | _G.EP_INOUT = function(name, optional) 78 | if optional then 79 | return name .. "6" 80 | end 81 | return name .. "2" 82 | end 83 | 84 | _G.get_std_request_name = function(v, wValue, wIndex) 85 | if v < #STD_REQ_NAME then 86 | if (v == macro_defs.GET_DESCRIPTOR or v == macro_defs.GET_DESCRIPTOR) and wValue then 87 | local idx = wValue & 0xff 88 | local t = wValue >> 8 89 | local prefix = "Get " 90 | if v == macro_defs.SET_DESCRIPTOR then 91 | prefix = "Set " 92 | end 93 | local postfix = "" 94 | if t == macro_defs.CFG_DESC or t == macro_defs.STRING_DESC then 95 | postfix = ":"..idx 96 | end 97 | if DESCRIPTOR_NAME[t] then 98 | return prefix .. DESCRIPTOR_NAME[t] .. postfix 99 | end 100 | end 101 | local postfix = "" 102 | if v == macro_defs.SET_CONFIG and wValue then 103 | postfix = " :" .. wValue 104 | end 105 | if v == macro_defs.SET_INTERFACE and wIndex then 106 | return "Set Itf :" .. wIndex 107 | end 108 | if v == macro_defs.GET_INTERFACE and wIndex then 109 | return "Get Itf :" .. wIndex 110 | end 111 | return STD_REQ_NAME[v+1] .. postfix 112 | else 113 | return "Unknown Request" 114 | end 115 | end 116 | 117 | _G.get_descriptor_name = function(v) 118 | if (type(v) == "string") then 119 | error(debug.traceback()) 120 | end 121 | if (v>=0) and (v < #STD_DESCRIPTOR_NAME) then 122 | return STD_DESCRIPTOR_NAME[v+1] .. " Descriptor" 123 | elseif v == macro_defs.REPORT_DESC then 124 | return "Report Descritpor" 125 | elseif v == macro_defs.FUNC_DESC then 126 | return "Function Descritpor" 127 | elseif v == macro_defs.HUB_DESC then 128 | return "HUB Descritpor" 129 | else 130 | return "Unknown Descritpor" 131 | end 132 | end 133 | 134 | package.loaded["util"] = util 135 | 136 | --------------------------------------------------------------------------------