Skip to content

Commit 7ab8e2d

Browse files
authored
feat: point accessors (#15)
Comprehensive re-write of the `Point` interface implementations to simplify the numerous different implementations to only a few based on their data types, and then add Get/Set functions for each of the fields that a Point _may_ have.
1 parent e3e751b commit 7ab8e2d

File tree

14 files changed

+2046
-1609
lines changed

14 files changed

+2046
-1609
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ jobs:
2525
run: sudo apt-get update && sudo apt-get install -y libpcap-dev
2626
- uses: golangci/golangci-lint-action@v9
2727
with:
28-
version: v2.5.0
28+
version: v2.10.1
2929

3030
codespell:
3131
runs-on: ubuntu-latest

Makefile

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
export PATH := $(HOME)/go/bin:$(PATH)
44

55
setup:
6-
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b $(go env GOPATH)/bin v2.7.2
6+
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b $(go env GOPATH)/bin v2.10.1
77
go install golang.org/x/tools/cmd/stringer@latest
88
sudo apt-get install -y libpcap-dev
99
git config core.hooksPath .githooks
@@ -16,6 +16,7 @@ generate: .generated-canary
1616
@touch $@
1717

1818
fix: generate
19+
codespell . -w
1920
golangci-lint run ./... --fix
2021

2122
lint: generate
@@ -27,7 +28,7 @@ spell:
2728
check: lint spell
2829

2930
test: generate
30-
CGO_ENABLED=1 go test ./dnp3 -v -args -pcaps=opendnp3_test1.pcap
31+
go test ./dnp3 -v -args -pcaps=opendnp3_test1.pcap
3132

3233
clean:
33-
rm -f *_string.go .generated-canary
34+
rm -f **/*_string.go .generated-canary

dnp3/applicationData.go

Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
package dnp3
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"strings"
7+
)
8+
9+
// ApplicationData holds an array of Data objects.
10+
type ApplicationData struct {
11+
Objects []DataObject `json:"objects"`
12+
// in case we get in to trouble unrolling the objects just store the rest
13+
// of the data in here. Or can use this to set all data like "raw"
14+
extra []byte
15+
}
16+
17+
func (ad *ApplicationData) FromBytes(data []byte) error {
18+
ad.Objects = nil // in case there was already stuff here
19+
20+
for readOffset := 0; readOffset < len(data); {
21+
var object DataObject
22+
23+
err := object.FromBytes(data[readOffset:])
24+
if err != nil {
25+
ad.extra = data[readOffset:]
26+
27+
return fmt.Errorf("could not decode object: 0x % X, err: %w",
28+
data[readOffset:], err)
29+
}
30+
31+
ad.Objects = append(ad.Objects, object)
32+
readOffset += object.SizeOf()
33+
}
34+
35+
return nil
36+
}
37+
38+
func (ad *ApplicationData) ToBytes() ([]byte, error) {
39+
var encoded []byte
40+
41+
for _, object := range ad.Objects {
42+
bytesOut, err := object.ToBytes()
43+
if err != nil {
44+
return encoded, fmt.Errorf("could not encode object: %w", err)
45+
}
46+
47+
encoded = append(encoded, bytesOut...)
48+
}
49+
50+
encoded = append(encoded, ad.extra...)
51+
52+
return encoded, nil
53+
}
54+
55+
func (ad *ApplicationData) String() string {
56+
output := ""
57+
header := "Data Objects:"
58+
headerAdded := false
59+
60+
if len(ad.Objects) > 0 {
61+
output += header
62+
headerAdded = true
63+
64+
var stringBuilder strings.Builder
65+
for _, obj := range ad.Objects {
66+
stringBuilder.WriteString("\n" + indent("- "+obj.String(), "\t"))
67+
}
68+
69+
output += stringBuilder.String()
70+
}
71+
72+
if len(ad.extra) > 0 {
73+
if !headerAdded {
74+
output += header
75+
}
76+
77+
output += fmt.Sprintf("\n\t- Extra: 0x % X", ad.extra)
78+
}
79+
80+
return output
81+
}
82+
83+
func (ad *ApplicationData) HasExtra() bool {
84+
return len(ad.extra) > 0
85+
}
86+
87+
type DataObject struct {
88+
Header ObjectHeader `json:"header"`
89+
Points []Point `json:"points"`
90+
Extra []byte `json:"extra,omitempty"`
91+
totalSize int
92+
indexes []int
93+
}
94+
95+
func (do *DataObject) FromBytes(data []byte) error {
96+
err := do.Header.FromBytes(data)
97+
if err != nil {
98+
return fmt.Errorf("can't create Data Object Header: %w", err)
99+
}
100+
101+
headSize := do.Header.SizeOf()
102+
do.totalSize = headSize
103+
104+
if do.Header.objectType == nil || do.Header.objectType.Constructor == nil {
105+
do.Extra = data[headSize:]
106+
do.totalSize += len(do.Extra)
107+
108+
return fmt.Errorf("unsupported group/variation: %d/%d",
109+
do.Header.Group, do.Header.Variation)
110+
}
111+
112+
numPoints := do.Header.RangeField.NumObjects()
113+
if numPoints == 0 {
114+
return nil
115+
}
116+
117+
var size int
118+
119+
do.Points, size, err = do.Header.objectType.Constructor(
120+
data[headSize:],
121+
numPoints,
122+
do.Header.PointPrefixCode.GetPointPrefixSize(),
123+
do.Header.PointPrefixCode,
124+
)
125+
if err != nil {
126+
return fmt.Errorf("can't create points: %w", err)
127+
}
128+
129+
do.totalSize += size
130+
131+
err = do.updateIndexes()
132+
if err != nil {
133+
return fmt.Errorf("failed to update indexes: %w", err)
134+
}
135+
136+
return nil
137+
}
138+
139+
func (do *DataObject) ToBytes() ([]byte, error) {
140+
// TODO get this to be more elegant.
141+
var encoded []byte
142+
143+
headerBytes, err := do.Header.ToBytes()
144+
if err != nil {
145+
return nil, fmt.Errorf("failed to encode object header: %w", err)
146+
}
147+
148+
encoded = append(encoded, headerBytes...)
149+
150+
if len(do.Points) > 0 {
151+
var packer PointsPacker
152+
if do.Header.objectType != nil {
153+
packer = do.Header.objectType.Packer
154+
} else if def, ok := objectTypes[groupVariation{do.Header.Group, do.Header.Variation}]; ok {
155+
// Try to look it up if it wasn't set (e.g. manual construction)
156+
do.Header.objectType = def
157+
packer = def.Packer
158+
}
159+
160+
if packer == nil {
161+
encoded = append(encoded, do.Extra...)
162+
163+
return encoded, fmt.Errorf("no packer for Group %d, Var %d",
164+
do.Header.Group, do.Header.Variation)
165+
}
166+
167+
packedPoints, err := packer(do.Points)
168+
if err != nil {
169+
encoded = append(encoded, do.Extra...)
170+
171+
return encoded, fmt.Errorf("could not pack points: %w", err)
172+
}
173+
174+
encoded = append(encoded, packedPoints...)
175+
}
176+
177+
encoded = append(encoded, do.Extra...)
178+
179+
return encoded, nil
180+
}
181+
182+
func (do *DataObject) String() string {
183+
output := do.Header.String()
184+
185+
if len(do.Points) == 0 {
186+
return output
187+
}
188+
189+
output += "\n Objects:"
190+
191+
var stringBuilder strings.Builder
192+
193+
for _, point := range do.Points {
194+
lines := strings.Split(point.String(), "\n")
195+
if len(lines) > 0 {
196+
lines[0] = "- " + lines[0]
197+
for i := 1; i < len(lines); i++ {
198+
lines[i] = " " + lines[i]
199+
}
200+
201+
stringBuilder.WriteString("\n" + indent(strings.Join(lines, "\n"), "\t"))
202+
}
203+
}
204+
205+
output += stringBuilder.String()
206+
207+
return output
208+
}
209+
210+
func (do *DataObject) SizeOf() int {
211+
return do.totalSize
212+
}
213+
214+
func (do *DataObject) Indexes() []int {
215+
return do.indexes
216+
}
217+
218+
func (do *DataObject) updateIndexes() error {
219+
switch rangeField := do.Header.RangeField.(type) {
220+
case *StartStopRangeField:
221+
for i := rangeField.Start; i <= rangeField.Stop; i++ {
222+
do.indexes = append(do.indexes, int(i))
223+
}
224+
225+
return nil
226+
case *AllRangeField:
227+
for i := range len(do.Points) {
228+
do.indexes = append(do.indexes, i)
229+
}
230+
231+
return nil
232+
case *CountRangeField:
233+
return do.updateIndexesFromPrefix()
234+
default:
235+
return fmt.Errorf("unexpected range field type %T", do.Header.RangeField)
236+
}
237+
}
238+
239+
// updateIndexesFromPrefix resolves point indexes for count-based range
240+
// fields by inspecting the object header's PointPrefixCode.
241+
func (do *DataObject) updateIndexesFromPrefix() error {
242+
switch do.Header.PointPrefixCode {
243+
case Index1Octet, Index2Octet, Index4Octet:
244+
for _, point := range do.Points {
245+
index, err := point.GetIndex()
246+
if err != nil {
247+
return fmt.Errorf("failed to get index from point: %w", err)
248+
}
249+
250+
do.indexes = append(do.indexes, index)
251+
}
252+
253+
return nil
254+
case NoPrefix:
255+
for i := range do.Points {
256+
do.indexes = append(do.indexes, i)
257+
}
258+
259+
return nil
260+
case Size1Octet, Size2Octet, Size4Octet:
261+
return fmt.Errorf(
262+
"point prefix code %s does not determine indexes",
263+
do.Header.PointPrefixCode,
264+
)
265+
case Reserved:
266+
return errors.New("reserved point prefix code cannot be used to determine indexes")
267+
default:
268+
return fmt.Errorf("unexpected point prefix code %d", do.Header.PointPrefixCode)
269+
}
270+
}

0 commit comments

Comments
 (0)