Skip to content

Commit 057f86f

Browse files
authored
[software] added mr.duppl cmd (#5)
1 parent b341a3d commit 057f86f

File tree

10 files changed

+320
-18
lines changed

10 files changed

+320
-18
lines changed

software/cmd/mr.duppl/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
mr.duppl
+119
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
package commands
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"io"
7+
"os"
8+
"runtime"
9+
10+
"github.com/gopacket/gopacket"
11+
"github.com/gopacket/gopacket/layers"
12+
"github.com/gopacket/gopacket/pcapgo"
13+
"github.com/spf13/cobra"
14+
15+
"github.com/buglloc/mr.duppl/software/pkg/dupplcap"
16+
"github.com/buglloc/mr.duppl/software/pkg/usbp"
17+
)
18+
19+
var dumpArgs struct {
20+
Interface string
21+
NoFolding bool
22+
Output string
23+
}
24+
25+
var dumpCmd = &cobra.Command{
26+
Use: "dump",
27+
SilenceUsage: true,
28+
SilenceErrors: true,
29+
Short: "Dump Mr.Duppl capture",
30+
RunE: func(_ *cobra.Command, _ []string) error {
31+
dev, err := dupplcap.NewDevice(dumpArgs.Interface)
32+
if err != nil {
33+
return fmt.Errorf("opening device: %w", err)
34+
}
35+
defer func() { _ = dev.Close() }()
36+
37+
if err := dev.StartCapture(!dumpArgs.NoFolding); err != nil {
38+
return fmt.Errorf("start capture: %w", err)
39+
}
40+
defer func() { _ = dev.StopCapture() }()
41+
42+
if len(dumpArgs.Output) != 0 {
43+
out, err := os.Create(dumpArgs.Output)
44+
if err != nil {
45+
return fmt.Errorf("creating output file: %w", err)
46+
}
47+
defer func() { _ = out.Close() }()
48+
49+
return dumpCapture(dev, out)
50+
}
51+
52+
return printCapture(dev)
53+
},
54+
}
55+
56+
func dumpCapture(dev *dupplcap.Device, out io.Writer) error {
57+
w, err := pcapgo.NewNgWriterInterface(
58+
out,
59+
pcapgo.NgInterface{
60+
Name: dev.Iface(),
61+
OS: runtime.GOOS,
62+
LinkType: layers.LinkType(dupplcap.LinkTypeUSBFullSpeed.Int()),
63+
SnapLength: 0, //unlimited
64+
// TimestampResolution: 9,
65+
},
66+
pcapgo.NgWriterOptions{
67+
SectionInfo: pcapgo.NgSectionInfo{
68+
Hardware: runtime.GOARCH,
69+
OS: runtime.GOOS,
70+
Application: "Mr.Duppl", //spread the word
71+
},
72+
},
73+
)
74+
if err != nil {
75+
return fmt.Errorf("open pcapng writer: %w", err)
76+
}
77+
defer func() { _ = w.Flush() }()
78+
79+
for {
80+
packet, err := dev.Packet()
81+
if err != nil {
82+
if errors.Is(err, io.EOF) {
83+
return nil
84+
}
85+
86+
return fmt.Errorf("read packet: %w", err)
87+
}
88+
89+
ci := gopacket.CaptureInfo{
90+
Length: len(packet),
91+
CaptureLength: len(packet),
92+
InterfaceIndex: 0,
93+
}
94+
err = w.WritePacket(ci, packet)
95+
if err != nil {
96+
return fmt.Errorf("write packet: %w", err)
97+
}
98+
99+
usbp.Print(packet, os.Stdout)
100+
}
101+
}
102+
103+
func printCapture(dev *dupplcap.Device) error {
104+
for {
105+
packet, err := dev.Packet()
106+
if err != nil {
107+
return fmt.Errorf("read packet: %w", err)
108+
}
109+
110+
usbp.Print(packet, os.Stdout)
111+
}
112+
}
113+
114+
func init() {
115+
flags := dumpCmd.PersistentFlags()
116+
flags.StringVarP(&dumpArgs.Interface, "iface", "i", "", "interface to use")
117+
flags.StringVarP(&dumpArgs.Output, "out", "o", "", "write PcapNG file")
118+
flags.BoolVar(&dumpArgs.NoFolding, "no-packet-folding", false, "disable packet folding")
119+
}

software/cmd/mr.duppl/commands/ls.go

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package commands
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/spf13/cobra"
7+
8+
"github.com/buglloc/mr.duppl/software/pkg/dupplcap"
9+
)
10+
11+
var lsArgs struct {
12+
Name bool
13+
Path bool
14+
}
15+
16+
var lsCmd = &cobra.Command{
17+
Use: "ls",
18+
SilenceUsage: true,
19+
SilenceErrors: true,
20+
Short: "List devices",
21+
RunE: func(_ *cobra.Command, _ []string) error {
22+
if !lsArgs.Name && !lsArgs.Path {
23+
lsArgs.Name = true
24+
lsArgs.Path = true
25+
}
26+
27+
ifaces, err := dupplcap.Ifaces()
28+
if err != nil {
29+
return fmt.Errorf("unable to get information about interfaces: %w", err)
30+
}
31+
32+
for _, iface := range ifaces {
33+
switch {
34+
case lsArgs.Name && lsArgs.Path:
35+
fmt.Println(iface.Name, iface.Path)
36+
case lsArgs.Name:
37+
fmt.Println(iface.Name)
38+
case lsArgs.Path:
39+
fmt.Println(iface.Path)
40+
}
41+
}
42+
43+
return nil
44+
},
45+
}
46+
47+
func init() {
48+
flags := lsCmd.PersistentFlags()
49+
flags.BoolVarP(&lsArgs.Name, "name", "n", false, "display device name")
50+
flags.BoolVarP(&lsArgs.Path, "path", "p", false, "display device path")
51+
}
+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package commands
2+
3+
import (
4+
"github.com/spf13/cobra"
5+
)
6+
7+
var rootCmd = &cobra.Command{
8+
Use: "mr.duppl",
9+
SilenceUsage: true,
10+
SilenceErrors: true,
11+
}
12+
13+
func Execute() error {
14+
return rootCmd.Execute()
15+
}
16+
17+
func init() {
18+
rootCmd.AddCommand(
19+
lsCmd,
20+
dumpCmd,
21+
)
22+
}

software/cmd/mr.duppl/main.go

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"os"
6+
7+
"github.com/buglloc/mr.duppl/software/cmd/mr.duppl/commands"
8+
)
9+
10+
func main() {
11+
if err := commands.Execute(); err != nil {
12+
_, _ = fmt.Fprintf(os.Stderr, "%v\n", err)
13+
os.Exit(1)
14+
}
15+
}

software/go.mod

+4-1
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,19 @@ go 1.23.1
55
require (
66
github.com/gopacket/gopacket v1.3.0
77
github.com/kor44/extcap v0.0.1
8+
github.com/spf13/cobra v1.8.1
89
github.com/stretchr/testify v1.8.4
910
go.bug.st/serial v1.6.2
1011
)
1112

1213
require (
13-
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
14+
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
1415
github.com/creack/goselect v0.1.2 // indirect
1516
github.com/davecgh/go-spew v1.1.1 // indirect
17+
github.com/inconshreveable/mousetrap v1.1.0 // indirect
1618
github.com/pmezard/go-difflib v1.0.0 // indirect
1719
github.com/russross/blackfriday/v2 v2.1.0 // indirect
20+
github.com/spf13/pflag v1.0.5 // indirect
1821
github.com/urfave/cli/v2 v2.25.7 // indirect
1922
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
2023
golang.org/x/net v0.28.0 // indirect

software/go.sum

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,23 @@
1-
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
2-
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
1+
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
2+
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
33
github.com/creack/goselect v0.1.2 h1:2DNy14+JPjRBgPzAd1thbQp4BSIihxcBf0IXhQXDRa0=
44
github.com/creack/goselect v0.1.2/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY=
55
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
66
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
77
github.com/gopacket/gopacket v1.3.0 h1:MouZCc+ej0vnqzB0WeiaO/6+tGvb+KU7UczxoQ+X0Yc=
88
github.com/gopacket/gopacket v1.3.0/go.mod h1:WnFrU1Xkf5lWKV38uKNR9+yYtppn+ZYzOyNqMeH4oNE=
9+
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
10+
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
911
github.com/kor44/extcap v0.0.1 h1:fcNWKzd25nN9sD+mY9z/GWsTfNXNnuIVlJRKpSkeHpQ=
1012
github.com/kor44/extcap v0.0.1/go.mod h1:+Pl5vUaEZv3VWQ7EC9eksa+XsLGU0ZUVXGYEjAuC+9k=
1113
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
1214
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
1315
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
1416
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
17+
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
18+
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
19+
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
20+
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
1521
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
1622
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
1723
github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs=

software/pkg/dupplcap/device.go

+50-10
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
package dupplcap
22

33
import (
4+
"errors"
45
"fmt"
6+
"io"
7+
"strings"
58

69
"go.bug.st/serial"
710
)
@@ -12,20 +15,29 @@ const (
1215
)
1316

1417
type Device struct {
15-
port serial.Port
18+
iface string
19+
port serial.Port
1620
}
1721

18-
func NewDeviceByIface(iface string) (*Device, error) {
19-
serialPort, err := serial.Open(iface, &serial.Mode{
20-
BaudRate: 115200,
21-
})
22+
func NewDevice(nameOrIface string) (*Device, error) {
23+
if len(nameOrIface) != 0 {
24+
if strings.HasPrefix(nameOrIface, Name) {
25+
return NewDeviceByName(nameOrIface)
26+
}
27+
28+
return NewDeviceByIface(nameOrIface)
29+
}
30+
31+
ifaces, err := Ifaces()
2232
if err != nil {
23-
return nil, fmt.Errorf("open serial port %s: %w", iface, err)
33+
return nil, fmt.Errorf("list ifaces: %w", err)
2434
}
2535

26-
return &Device{
27-
port: serialPort,
28-
}, nil
36+
if len(ifaces) == 0 {
37+
return nil, errors.New("device was not found")
38+
}
39+
40+
return NewDeviceByIface(ifaces[0].Path)
2941
}
3042

3143
func NewDeviceByName(name string) (*Device, error) {
@@ -45,8 +57,36 @@ func NewDeviceByName(name string) (*Device, error) {
4557
return nil, fmt.Errorf("device with name %s not found", name)
4658
}
4759

60+
func NewDeviceByIface(iface string) (*Device, error) {
61+
serialPort, err := serial.Open(iface, &serial.Mode{
62+
BaudRate: 115200,
63+
})
64+
if err != nil {
65+
return nil, fmt.Errorf("open serial port %s: %w", iface, err)
66+
}
67+
68+
return &Device{
69+
iface: iface,
70+
port: serialPort,
71+
}, nil
72+
}
73+
74+
func (r *Device) Iface() string {
75+
return r.iface
76+
}
77+
4878
func (r *Device) Packet() ([]byte, error) {
49-
return readSlipPacket(r.port)
79+
packet, err := readSlipPacket(r.port)
80+
if err != nil {
81+
var portErr *serial.PortError
82+
if errors.As(err, &portErr) && portErr.Code() == serial.PortClosed {
83+
return nil, io.EOF
84+
}
85+
86+
return nil, fmt.Errorf("read packet: %w", err)
87+
}
88+
89+
return packet, nil
5090
}
5191

5292
func (r *Device) StartCapture(withPacketFolding bool) error {

software/pkg/dupplcap/ifaces.go

+6-5
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ import (
88
)
99

1010
const (
11-
targetVID = "2E8A"
12-
targetPID = "5052"
11+
VID = "2E8A"
12+
PID = "5052"
13+
Name = "Mr.Duppl"
1314
)
1415

1516
type Iface struct {
@@ -29,17 +30,17 @@ func Ifaces() ([]Iface, error) {
2930
continue
3031
}
3132

32-
if !strings.EqualFold(port.VID, targetVID) {
33+
if !strings.EqualFold(port.VID, VID) {
3334
continue
3435
}
3536

36-
if !strings.EqualFold(port.PID, targetPID) {
37+
if !strings.EqualFold(port.PID, PID) {
3738
continue
3839
}
3940

4041
out = append(out, Iface{
4142
Path: port.Name,
42-
Name: fmt.Sprintf("Mr.Duppl:%s", port.SerialNumber),
43+
Name: fmt.Sprintf("%s:%s", Name, port.SerialNumber),
4344
})
4445
}
4546

0 commit comments

Comments
 (0)