Skip to content

Commit b890d72

Browse files
authored
Added Software (#2)
1 parent 65700ac commit b890d72

File tree

11 files changed

+569
-0
lines changed

11 files changed

+569
-0
lines changed

.github/workflows/firmware.yml

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
---
12
name: Firmware CI
23

34
on:

.github/workflows/software.yml

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
---
2+
name: Sofrware CI
3+
4+
on:
5+
push:
6+
branches:
7+
- main
8+
tags:
9+
- '*'
10+
paths:
11+
- 'software/**'
12+
- '.github/workflows/software.yml'
13+
pull_request:
14+
paths:
15+
- 'software/**'
16+
- '.github/workflows/software.yml'
17+
workflow_dispatch:
18+
19+
permissions:
20+
contents: read
21+
22+
jobs:
23+
staticcheck:
24+
permissions:
25+
contents: read
26+
pull-requests: read
27+
runs-on: ubuntu-latest
28+
steps:
29+
# Get the repositery's code
30+
- name: Checkout
31+
uses: actions/checkout@v3
32+
33+
- name: Set up Golang
34+
uses: actions/setup-go@v4
35+
with:
36+
go-version: '1.23.x'
37+
check-latest: true
38+
cache: true
39+
cache-dependency-path: software/go.sum
40+
41+
- name: Go test
42+
working-directory: ./software
43+
run: "go test ./..."
44+
45+
- name: Go ver
46+
working-directory: ./software
47+
run: "go vet ./..."
48+
49+
- name: StaticCheck
50+
uses: dominikh/[email protected]
51+
with:
52+
version: "2024.1.1"
53+
install-go: false
54+
working-directory: ./software
55+
56+
test:
57+
strategy:
58+
fail-fast: false
59+
matrix:
60+
platform:
61+
- ubuntu
62+
go:
63+
- 21
64+
- 22
65+
- 23
66+
name: 'tests on ${{ matrix.platform }} | 1.${{ matrix.go }}.x'
67+
runs-on: ${{ matrix.platform }}-latest
68+
steps:
69+
# Get the repositery's code
70+
- name: Checkout
71+
uses: actions/checkout@v3
72+
73+
- name: Set up Golang
74+
uses: actions/setup-go@v4
75+
with:
76+
go-version: '1.${{ matrix.go }}.x'
77+
check-latest: true
78+
cache: true
79+
cache-dependency-path: software/go.sum
80+
81+
- name: Run tests
82+
working-directory: ./software
83+
run: "go clean -testcache && go test -race -cover -covermode=atomic ./..."
File renamed without changes.

software/cmd/dupplcap/main.go

+148
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"os"
7+
"path/filepath"
8+
"runtime"
9+
10+
"github.com/gopacket/gopacket"
11+
"github.com/gopacket/gopacket/layers"
12+
"github.com/gopacket/gopacket/pcapgo"
13+
"github.com/kor44/extcap"
14+
15+
"github.com/buglloc/Mr.Duppl/software/pkg/dupplcap"
16+
)
17+
18+
const (
19+
usePacketFoldingOpt = "packet-folding"
20+
)
21+
22+
var (
23+
usePacketFolding = extcap.NewConfigBoolOpt(usePacketFoldingOpt, "Use packet folding").
24+
Default(true)
25+
)
26+
27+
func main() {
28+
app := extcap.App{
29+
Usage: "dupplcap",
30+
Version: extcap.VersionInfo{
31+
Info: "1.0.0",
32+
Help: "https://github.com/buglloc/Mr.Duppl",
33+
},
34+
HelpPage: "DupplCAP - extcap application to integrate Mr.Duppl with Wireshark or something",
35+
GetInterfaces: getAllInterfaces,
36+
GetDLT: getDLT,
37+
GetAllConfigOptions: getAllConfigOptions,
38+
GetConfigOptions: getConfigOptions,
39+
StartCapture: startCapture,
40+
}
41+
42+
app.Run(os.Args)
43+
}
44+
45+
func getAllInterfaces() ([]extcap.CaptureInterface, error) {
46+
ifaces, err := dupplcap.Ifaces()
47+
if err != nil {
48+
return nil, fmt.Errorf("unable to get information about interfaces: %w", err)
49+
}
50+
51+
extIfaces := make([]extcap.CaptureInterface, len(ifaces))
52+
for i, iface := range ifaces {
53+
// we use Name as Value to deal with Mr.Duppl replugs
54+
extIfaces[i] = extcap.CaptureInterface{
55+
Value: iface.Name,
56+
Display: iface.Path,
57+
}
58+
}
59+
60+
return extIfaces, nil
61+
}
62+
63+
func getDLT(_ string) (extcap.DLT, error) {
64+
return extcap.DLT{
65+
Number: dupplcap.LinkTypeUSBFullSpeed.Int(),
66+
Name: dupplcap.LinkTypeUSBFullSpeed.String(),
67+
}, nil
68+
}
69+
70+
func getConfigOptions(_ string) ([]extcap.ConfigOption, error) {
71+
opts := []extcap.ConfigOption{
72+
usePacketFolding,
73+
}
74+
75+
return opts, nil
76+
}
77+
78+
func getAllConfigOptions() []extcap.ConfigOption {
79+
opts := []extcap.ConfigOption{
80+
usePacketFolding,
81+
}
82+
return opts
83+
}
84+
85+
func startCapture(iface string, pipe io.WriteCloser, _ string, opts map[string]any) error {
86+
defer func() { _ = pipe.Close() }()
87+
88+
dev, err := dupplcap.NewDeviceByName(iface)
89+
if err != nil {
90+
return fmt.Errorf("open device: %w", err)
91+
}
92+
defer func() { _ = dev.Close() }()
93+
94+
packetFoldingEnabled := true
95+
if val, ok := opts[usePacketFoldingOpt]; ok {
96+
if en, ok := val.(bool); ok {
97+
packetFoldingEnabled = en
98+
}
99+
}
100+
101+
if err := dev.StartCapture(packetFoldingEnabled); err != nil {
102+
return fmt.Errorf("start capture: %w", err)
103+
}
104+
defer func() { _ = dev.StopCapture() }()
105+
106+
w, err := pcapgo.NewNgWriterInterface(
107+
pipe,
108+
pcapgo.NgInterface{
109+
Name: filepath.Base(iface),
110+
OS: runtime.GOOS,
111+
LinkType: layers.LinkType(dupplcap.LinkTypeUSBFullSpeed.Int()),
112+
SnapLength: 0, //unlimited
113+
// TimestampResolution: 9,
114+
},
115+
pcapgo.NgWriterOptions{
116+
SectionInfo: pcapgo.NgSectionInfo{
117+
Hardware: runtime.GOARCH,
118+
OS: runtime.GOOS,
119+
Application: "Mr.Duppl", //spread the word
120+
},
121+
},
122+
)
123+
if err != nil {
124+
return fmt.Errorf("open pcapng writer: %w", err)
125+
}
126+
127+
for {
128+
packet, err := dev.Packet()
129+
if err != nil {
130+
return fmt.Errorf("read packet: %w", err)
131+
}
132+
133+
ci := gopacket.CaptureInfo{
134+
Length: len(packet),
135+
CaptureLength: len(packet),
136+
InterfaceIndex: 0,
137+
}
138+
err = w.WritePacket(ci, packet)
139+
if err != nil {
140+
return fmt.Errorf("write packet: %w", err)
141+
}
142+
143+
err = w.Flush()
144+
if err != nil {
145+
return fmt.Errorf("flush packet: %w", err)
146+
}
147+
}
148+
}

software/go.mod

+4
Original file line numberDiff line numberDiff line change
@@ -5,15 +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/stretchr/testify v1.8.4
89
go.bug.st/serial v1.6.2
910
)
1011

1112
require (
1213
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
1314
github.com/creack/goselect v0.1.2 // indirect
15+
github.com/davecgh/go-spew v1.1.1 // indirect
16+
github.com/pmezard/go-difflib v1.0.0 // indirect
1417
github.com/russross/blackfriday/v2 v2.1.0 // indirect
1518
github.com/urfave/cli/v2 v2.25.7 // indirect
1619
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
1720
golang.org/x/net v0.28.0 // indirect
1821
golang.org/x/sys v0.24.0 // indirect
22+
gopkg.in/yaml.v3 v3.0.1 // indirect
1923
)

software/go.sum

+2
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,7 @@ golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
2828
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
2929
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
3030
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
31+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
32+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
3133
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
3234
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

software/pkg/dupplcap/device.go

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package dupplcap
2+
3+
import (
4+
"fmt"
5+
6+
"go.bug.st/serial"
7+
)
8+
9+
const (
10+
cmdStartCapture = 0x01
11+
cmdStopCapture = 0x02
12+
)
13+
14+
type Device struct {
15+
port serial.Port
16+
}
17+
18+
func NewDeviceByIface(iface string) (*Device, error) {
19+
serialPort, err := serial.Open(iface, &serial.Mode{
20+
BaudRate: 115200,
21+
})
22+
if err != nil {
23+
return nil, fmt.Errorf("open serial port %s: %w", iface, err)
24+
}
25+
26+
return &Device{
27+
port: serialPort,
28+
}, nil
29+
}
30+
31+
func NewDeviceByName(name string) (*Device, error) {
32+
ifaces, err := Ifaces()
33+
if err != nil {
34+
return nil, fmt.Errorf("list ifaces: %w", err)
35+
}
36+
37+
for _, iface := range ifaces {
38+
if iface.Name != name {
39+
continue
40+
}
41+
42+
return NewDeviceByIface(iface.Path)
43+
}
44+
45+
return nil, fmt.Errorf("device with name %s not found", name)
46+
}
47+
48+
func (r *Device) Packet() ([]byte, error) {
49+
return readSlipPacket(r.port)
50+
}
51+
52+
func (r *Device) StartCapture(withPacketFolding bool) error {
53+
cmd := []byte{cmdStartCapture, 0x00}
54+
if withPacketFolding {
55+
cmd[1] = 0x01
56+
}
57+
58+
return writeSlipPacket(r.port, cmd)
59+
}
60+
61+
func (r *Device) StopCapture() error {
62+
return writeSlipPacket(r.port, []byte{cmdStopCapture})
63+
}
64+
65+
func (r *Device) Close() error {
66+
return r.port.Close()
67+
}

software/pkg/dupplcap/ifaces.go

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package dupplcap
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
7+
"go.bug.st/serial/enumerator"
8+
)
9+
10+
const (
11+
targetVID = "2E8A"
12+
targetPID = "5052"
13+
)
14+
15+
type Iface struct {
16+
Path string
17+
Name string
18+
}
19+
20+
func Ifaces() ([]Iface, error) {
21+
ports, err := enumerator.GetDetailedPortsList()
22+
if err != nil {
23+
return nil, fmt.Errorf("list detailed ports: %w", err)
24+
}
25+
26+
var out []Iface
27+
for _, port := range ports {
28+
if !port.IsUSB {
29+
continue
30+
}
31+
32+
if !strings.EqualFold(port.VID, targetVID) {
33+
continue
34+
}
35+
36+
if !strings.EqualFold(port.PID, targetPID) {
37+
continue
38+
}
39+
40+
out = append(out, Iface{
41+
Path: port.Name,
42+
Name: fmt.Sprintf("Mr.Duppl:%s", port.SerialNumber),
43+
})
44+
}
45+
46+
return out, nil
47+
}

0 commit comments

Comments
 (0)