summaryrefslogtreecommitdiff
path: root/recv.go
blob: f618c7dbb1fc22743f4240aa9a6c97dc754100af (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
package binproto

import (
	"encoding/binary"
	"github.com/silvasur/kagus"
	"io"
	"sync"
)

// UnitReader is an interface with the ReadUnit function, which ist the basic reading function of the binproto.
// ReadUnit reads the next binproto unit from the reader. The second output has a different meaning for each unit type:
//
//     UTRequest, UTAnswer, UTEvent - uint16
//     UTBin                        - []byte
//     UTNumber                     - int64
//     UTUKey, UTByte               - byte
//     UTBinStream                  - *BinStreamReader
//     UTBool                       - bool
//
// A UnitReader implementation should usually wrap the SimpleUnitReader implementation.
type UnitReader interface {
	ReadUnit() (UnitType, interface{}, error)
}

// SimpleUnitReader is a UnitReader implementation that gets its data from an io.Reader.
type SimpleUnitReader struct {
	r  io.Reader
	mu *sync.Mutex
}

func NewSimpleUnitReader(r io.Reader) *SimpleUnitReader {
	return &SimpleUnitReader{
		r:  r,
		mu: new(sync.Mutex)}
}

func (sur *SimpleUnitReader) ReadUnit() (UnitType, interface{}, error) {
	r := sur.r

	sur.mu.Lock()
	doUnlock := true
	defer func() {
		if doUnlock {
			sur.mu.Unlock()
		}
	}()

	_ut, err := kagus.ReadByte(r)
	if err != nil {
		return 0, nil, err
	}

	ut := UnitType(_ut)
	switch ut {
	case UTNil:
		return ut, nil, nil
	case UTRequest, UTAnswer, UTEvent:
		var code uint16
		if err := binary.Read(r, binary.LittleEndian, &code); err != nil {
			return ut, nil, err
		}
		return ut, code, nil
	case UTBin:
		var l uint32
		if err := binary.Read(r, binary.LittleEndian, &l); err != nil {
			return ut, nil, err
		}
		buf := make([]byte, l)
		_, err := io.ReadFull(r, buf)
		if err != nil {
			return ut, nil, err
		}
		return ut, buf, nil
	case UTNumber:
		var n int64
		if err := binary.Read(r, binary.LittleEndian, &n); err != nil {
			return ut, nil, err
		}
		return ut, n, nil
	case UTList, UTTextKVMap, UTIdKVMap:
		return ut, nil, nil
	case UTUKey, UTByte:
		k, err := kagus.ReadByte(r)
		return ut, k, err
	case UTBinStream:
		doUnlock = false
		return ut, &BinstreamReader{r: r, surMu: sur.mu}, nil
	case UTTerm:
		return ut, nil, nil
	case UTBool:
		_b, err := kagus.ReadByte(r)
		b := true
		if _b == 0 {
			b = false
		}
		return ut, b, err
	}

	return ut, nil, UnknownUnit
}

// IdKVPair will be returned from ReadIdKVPair. ValueType and ValuePayload are the first two outputs of ReadUnit for the value.
type IdKVPair struct {
	Key          uint8
	ValueType    UnitType
	ValuePayload interface{}
}

// ReadIdKVPair reads a UKey + any unit pair. err will be Terminated, if this was the last KVPair.
func ReadIdKVPair(ur UnitReader) (kvp IdKVPair, err error) {
	var ut UnitType
	var data interface{}
	ut, data, err = ur.ReadUnit()
	if err != nil {
		return
	}

	switch ut {
	case UTUKey:
		kvp.Key = data.(byte)
	case UTTerm:
		err = Terminated
		return
	default:
		err = UnexpectedUnit
		return
	}

	kvp.ValueType, kvp.ValuePayload, err = ur.ReadUnit()
	return
}

// TextKVPair will be returned from ReadTextKVPair. ValueType and ValuePayload are the first two outputs of ReadUnit for the value.
type TextKVPair struct {
	Key          string
	ValueType    UnitType
	ValuePayload interface{}
}

// ReadTextKVPair reads a Bin(as string) + any unit pair. err will be Terminated, if this was the last KVPair.
func ReadTextKVPair(ur UnitReader) (kvp TextKVPair, err error) {
	var ut UnitType
	var data interface{}
	ut, data, err = ur.ReadUnit()
	if err != nil {
		return
	}

	switch ut {
	case UTBin:
		kvp.Key = string(data.([]byte))
	case UTTerm:
		err = Terminated
		return
	default:
		err = UnexpectedUnit
		return
	}

	kvp.ValueType, kvp.ValuePayload, err = ur.ReadUnit()
	return
}

const maxSkipDepth = 16

func skipUnit(ur UnitReader, ut UnitType, data interface{}, revDepth int) error {
	if revDepth == 0 {
		return TooDeeplyNested
	}

	switch ut {
	case UTNil, UTRequest, UTAnswer, UTEvent, UTBin, UTNumber, UTUKey, UTTerm, UTBool, UTByte:
		return nil
	case UTList:
		for {
			nUt, nData, nErr := ur.ReadUnit()
			if nErr != nil {
				return nErr
			}

			if nUt == UTTerm {
				return nil
			}

			if err := skipUnit(ur, nUt, nData, revDepth-1); err != nil {
				return err
			}
		}
	case UTTextKVMap:
		for {
			switch kvp, err := ReadTextKVPair(ur); err {
			case nil:
				if err := skipUnit(ur, kvp.ValueType, kvp.ValuePayload, revDepth-1); err != nil {
					return err
				}
			case Terminated:
				return nil
			default:
				return err
			}
		}
	case UTIdKVMap:
		for {
			switch kvp, err := ReadIdKVPair(ur); err {
			case nil:
				if err := skipUnit(ur, kvp.ValueType, kvp.ValuePayload, revDepth-1); err != nil {
					return err
				}
			case Terminated:
				return nil
			default:
				return err
			}
		}
	case UTBinStream:
		bsr := data.(*BinstreamReader)
		return bsr.FastForward()
	}

	return UnexpectedUnit
}

// SkipUnit skips the current unit recursively. ut and data are the first two outputs of ReadUnit.
// If the structure is nested too deeply, this function will abort with TooDeeplyNested.
func SkipUnit(ur UnitReader, ut UnitType, data interface{}) error {
	return skipUnit(ur, ut, data, maxSkipDepth)
}

// SkipNext is ReadNext + SkipUnit.
func SkipNext(ur UnitReader) error {
	ut, data, err := ur.ReadUnit()
	if err != nil {
		return err
	}
	return SkipUnit(ur, ut, data)
}

// ReadExpect reads a unit and tests, if the type is te expected type.
// If not, UnexpectedUnit is returned and the read unit will be skipped
// (if this fails, the reason for that is returned instead of UnexpectedError).
func ReadExpect(ur UnitReader, expected UnitType) (interface{}, error) {
	ut, data, err := ur.ReadUnit()
	if err != nil {
		return nil, err
	}

	if ut == expected {
		return data, nil
	}

	if err := SkipUnit(ur, ut, data); err != nil {
		return nil, err
	}

	return nil, UnexpectedUnit
}