Přemysl Eric Janouch
be50fd4b8f
All checks were successful
Alpine 3.20 Success
It is fairly dumb, but it allows to make more sense of what is going on over the wires while using EIZO software.
213 lines
5.1 KiB
Go
213 lines
5.1 KiB
Go
// Usage: tshark { -r FILE | -i INTERFACE } -l -T ek \
|
|
// | go run eizo-pcap-decode.go [ | less -R]
|
|
//
|
|
// This cannot be done through -T json, because tshark doesn't immediately
|
|
// flush the current object's trailing newline, but rather waits to decide
|
|
// if it should follow it with a comma. Even with -l, it will flush it late.
|
|
// It would be good if we could convince it not to wrap packets in a big array.
|
|
package main
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"strings"
|
|
)
|
|
|
|
type Packet struct {
|
|
Layers struct {
|
|
USB struct {
|
|
Source string `json:"usb_usb_src"`
|
|
Destination string `json:"usb_usb_dst"`
|
|
Direction string `json:"usb_usb_endpoint_address_direction"`
|
|
MacEndpointType string `json:"usb_usb_darwin_endpoint_type"`
|
|
TransferType string `json:"usb_usb_transfer_type"`
|
|
} `json:"usb"`
|
|
CapData string `json:"usb_usb_capdata"`
|
|
ControlResponse string `json:"usb_usb_control_Response"`
|
|
DataFragment string `json:"usb_usb_data_fragment"`
|
|
} `json:"layers"`
|
|
}
|
|
|
|
func (p *Packet) addr() string {
|
|
if p.Layers.USB.Source == "host" {
|
|
return p.Layers.USB.Destination
|
|
} else {
|
|
return p.Layers.USB.Source
|
|
}
|
|
}
|
|
|
|
func (p *Packet) isInterrupt() bool {
|
|
return p.Layers.USB.MacEndpointType == "3" ||
|
|
p.Layers.USB.TransferType == "0x01"
|
|
}
|
|
|
|
func (p *Packet) isControl() bool {
|
|
return p.Layers.USB.MacEndpointType == "0" ||
|
|
p.Layers.USB.TransferType == "0x02"
|
|
}
|
|
|
|
func (p *Packet) isIncoming() bool {
|
|
return p.Layers.USB.Direction == "1"
|
|
}
|
|
|
|
func hexDecode(encoded string) []byte {
|
|
decoded, err := hex.DecodeString(strings.ReplaceAll(encoded, ":", ""))
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return decoded
|
|
}
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
var (
|
|
raw *bool
|
|
le = binary.LittleEndian
|
|
|
|
fmtIn, fmtOut, fmtReset string
|
|
)
|
|
|
|
func decodeSubreport(data []byte) string {
|
|
if len(data) < 6 {
|
|
return fmt.Sprintf("%x", data)
|
|
}
|
|
usage := uint32(le.Uint16(data[:2]))<<16 | uint32(le.Uint16(data[2:4]))
|
|
filtered := make([]byte, len(data)-6)
|
|
for i, b := range data[6:] {
|
|
if b < 32 || b > 126 {
|
|
filtered[i] = '.'
|
|
} else {
|
|
filtered[i] = b
|
|
}
|
|
}
|
|
return fmt.Sprintf("<> %08x %04x %x %s", usage, le.Uint16(data[4:6]),
|
|
data[6:], string(filtered))
|
|
}
|
|
|
|
func decodeResult(data []byte) string {
|
|
if len(data) < 7 {
|
|
return fmt.Sprintf("%x", data)
|
|
}
|
|
usage := uint32(le.Uint16(data[:2]))<<16 | uint32(le.Uint16(data[2:4]))
|
|
return fmt.Sprintf(">< %08x %04x %02x", usage, le.Uint16(data[4:6]),
|
|
data[6])
|
|
}
|
|
|
|
func decodeMP(data []byte) string {
|
|
var out string
|
|
for i := 0; i+1 < len(data); {
|
|
sz := int(data[i+1])
|
|
if data[i] == 0xff || i+sz > len(data) {
|
|
break
|
|
}
|
|
if out != "" {
|
|
out += " "
|
|
}
|
|
out += fmt.Sprintf("[%02x] %x", data[i], data[i+2:i+2+sz])
|
|
i += 2 + sz
|
|
}
|
|
return out
|
|
}
|
|
|
|
func isSetSubreport(id byte) bool {
|
|
switch id {
|
|
case 2, 4, 11, 13:
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func isGetSubreport(id byte) bool {
|
|
switch id {
|
|
case 3, 5, 12, 14:
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func isSubreport(id byte) bool {
|
|
return isSetSubreport(id) || isGetSubreport(id)
|
|
}
|
|
|
|
func processInterrupt(p *Packet) {
|
|
data := hexDecode(p.Layers.CapData)
|
|
if len(data) < 1 {
|
|
return
|
|
}
|
|
if *raw {
|
|
fmt.Printf("%s INT %02x %x\n", p.addr(), data[0], data[1:])
|
|
} else if isSubreport(data[0]) {
|
|
fmt.Printf("%s INT %s\n", p.addr(), decodeSubreport(data[1:]))
|
|
}
|
|
}
|
|
|
|
func processControl(p *Packet) {
|
|
// macOS (Darwin) and Linux report Set_Feature differently.
|
|
data := hexDecode(p.Layers.ControlResponse)
|
|
if len(data) == 0 {
|
|
data = hexDecode(p.Layers.DataFragment)
|
|
}
|
|
if len(data) < 1 {
|
|
return
|
|
}
|
|
if p.isIncoming() {
|
|
if *raw {
|
|
fmt.Printf("%s IN %02x %x\n", p.addr(), data[0], data[1:])
|
|
} else if data[0] == 1 {
|
|
fmt.Printf("%s IN SR %x\n", p.addr(), data[5:])
|
|
} else if isGetSubreport(data[0]) {
|
|
fmt.Printf("%s IN %s%s%s\n", p.addr(),
|
|
fmtIn, decodeSubreport(data[1:]), fmtReset)
|
|
} else if data[0] == 6 {
|
|
fmt.Printf("%s IN PC %04x\n", p.addr(), le.Uint16(data[1:]))
|
|
} else if data[0] == 7 {
|
|
fmt.Printf("%s IN %s\n", p.addr(), decodeResult(data[1:]))
|
|
} else if data[0] == 8 {
|
|
fmt.Printf("%s IN ID %s %s\n", p.addr(), data[1:9], data[9:])
|
|
} else if data[0] == 9 {
|
|
fmt.Printf("%s IN MP %s\n", p.addr(), decodeMP(data[1:]))
|
|
} else {
|
|
fmt.Printf("%s IN %02x %x\n", p.addr(), data[0], data[1:])
|
|
}
|
|
} else {
|
|
if *raw {
|
|
fmt.Printf("%s OUT %02x %x\n", p.addr(), data[0], data[1:])
|
|
} else if isSetSubreport(data[0]) {
|
|
fmt.Printf("%s OUT %s%s%s\n", p.addr(),
|
|
fmtOut, decodeSubreport(data[1:]), fmtReset)
|
|
} else if data[0] != 1 && !isGetSubreport(data[0]) {
|
|
fmt.Printf("%s OUT %02x %x\n", p.addr(), data[0], data[1:])
|
|
}
|
|
}
|
|
}
|
|
|
|
func main() {
|
|
raw = flag.Bool("raw", false, "Do not decode EIZO packets")
|
|
flag.Parse()
|
|
|
|
if _, ok := os.LookupEnv("NO_COLOR"); !ok {
|
|
fmtIn, fmtOut, fmtReset = "\x1b[34m", "\x1b[31m", "\x1b[m"
|
|
}
|
|
|
|
decoder := json.NewDecoder(os.Stdin)
|
|
for {
|
|
var p Packet
|
|
if err := decoder.Decode(&p); err != nil {
|
|
if errors.Is(err, io.EOF) {
|
|
break
|
|
}
|
|
fmt.Fprintf(os.Stderr, "%v\n", err)
|
|
} else if p.isInterrupt() {
|
|
processInterrupt(&p)
|
|
} else if p.isControl() {
|
|
processControl(&p)
|
|
}
|
|
}
|
|
}
|