Compare commits

...

3 Commits

Author SHA1 Message Date
02da76e958 eizo-pcap-decode.go: figure out USBHID
All checks were successful
Alpine 3.20 Success
2024-11-27 10:00:08 +01:00
be50fd4b8f Add an EIZO packet decoding tool
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.
2024-11-26 11:10:15 +01:00
781fd392fe README.adoc: fix a typo
All checks were successful
Alpine 3.20 Success
2024-11-26 03:27:09 +01:00
2 changed files with 213 additions and 1 deletions

View File

@@ -20,7 +20,7 @@ and may not run at the same time, as it would contend for device access.
eizoctltray eizoctltray
~~~~~~~~~~~ ~~~~~~~~~~~
_eizoctltray_ is a derived Windows utility that can stay in the systray. _eizoctltray_ is a derived Windows utility that can stay in the systray.
When holding the Shift or Control keys while switching singnal inputs, When holding the Shift or Control keys while switching signal inputs,
it will also suspend or power off the system, respectively. it will also suspend or power off the system, respectively.
image::eizoctltray.png["eizoctltray with expanded context menu", 343, 229] image::eizoctltray.png["eizoctltray with expanded context menu", 343, 229]

212
eizo-pcap-decode.go Normal file
View File

@@ -0,0 +1,212 @@
// Usage: tshark { -r FILE | -i INTERFACE } -l -T ek --disable-protocol usbhid \
// | 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)
}
}
}