summaryrefslogtreecommitdiff
path: root/idkvmapscanner.go
blob: c51f041e3b4213267c924f4d5f3b14f540b2f8f5 (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
package binproto

import (
	"errors"
	"fmt"
	"io"
)

// UKeyGetter defines what to do on a UKey. Used by ScanIdKVMap.
type UKeyGetter struct {
	Type     UnitType     // Which type must the unit have?
	Optional bool         // Is this key optional?
	Action   GetterAction // Performing this action
	Captured *bool        // Pointer to variable that should be set to true, if key was found. Can be nil, if this information is not needed.
}

// Errors of ScanIdKVMap.
var (
	KeyMissing           = errors.New("Mandatory key is missing")
	UnknownKey           = errors.New("Unknown key found")
	UnexpectedTypeForKey = errors.New("Unexpected unit type for key")
)

// GetterAction specifies how an Action in UKeyGetter has to look like. Any error will be passed to the caller.
//
// If fatal is true, ScanIdKVMap will not try to cleanly skip the IdKVMap.
// If fatal is false, the action MUST handle all Units in a clear manner, so ScanIdKVMap can continue operating on a valid stream.
//
// fatal will only be checked, if err != nil.
//
// Actions for keys that are present will be executed asap, so you can e.g. NOT rely that all non-optional keys were captured.
type GetterAction func(interface{}, UnitReader) (err error, fatal bool)

// ScanIdKVMap scans a IdKVMap for keys, tests some properties of the values and will trigger a user given action.
// It also takes care of optional/mandatory keys and will generally make reading the often used IdKVMap less painful.
//
// The input stream must be positioned after the opening UTIdKVMap.
//
// If a key is missing KeyMissing is returned.
// If a key had a value with the wrong type, UnexpectedTypeForKey is returned.
// If a key is unknown AND failOnUnknown is true, UnknownKey is returned.
//
// Other errors either indicate an error in the stream OR an action returned that error.
// Since actions should only return errors when something is really wrong, you should not process the stream any further.
func ScanIdKVMap(ur UnitReader, getters map[byte]UKeyGetter, failOnUnknown bool) (outerr error) {
	seen := make(map[byte]bool)

	skipAll := false
	for {
		ut, data, err := ur.ReadUnit()
		if err != nil {
			return err
		}

		if ut == UTTerm {
			break
		}

		if skipAll {
			if err := SkipUnit(ur, ut, data); err != nil {
				return fmt.Errorf("Error while skipping: %s. Previous outerr was: %s", err, outerr)
			}
			continue
		}

		if ut != UTUKey {
			return fmt.Errorf("Found Unit of type %s (%d) in IdKVMap, expected UTUKey.", ut, ut)
		}

		key := data.(byte)

		ut, data, err = ur.ReadUnit()
		if err != nil {
			return err
		}

		getter, ok := getters[key]
		if !ok {
			if failOnUnknown {
				outerr = UnknownKey
				if err := SkipUnit(ur, ut, data); err != nil {
					return err
				}
				skipAll = true
			} else {
				if err := SkipUnit(ur, ut, data); err != nil {
					return err
				}
			}
			continue
		}

		if ut != getter.Type {
			if err := SkipUnit(ur, ut, data); err != nil {
				return err
			}
			outerr = UnexpectedTypeForKey
			skipAll = true
			continue
		}

		err, fatal := getter.Action(data, ur)
		if err != nil {
			if fatal {
				return err
			}

			outerr = err
			skipAll = true
			continue
		}

		seen[key] = true
		if getter.Captured != nil {
			*(getter.Captured) = true
		}
	}

	if skipAll {
		return
	}

	for key, getter := range getters {
		if !getter.Optional {
			if !seen[key] {
				return KeyMissing
			}
		}
	}

	return nil
}

// ActionSkip builds an action to skip this unit (useful, if you only want to know that a key exists, or for debugging).
func ActionSkip(ut UnitType) GetterAction {
	return func(data interface{}, ur UnitReader) (error, bool) {
		return SkipUnit(ur, ut, data), true // Since second return value will only be inspected, if first is != nil, this is okay.
	}
}

// ActionStoreNumber builds an action for storing a number.
func ActionStoreNumber(n *int64) GetterAction {
	return func(data interface{}, ur UnitReader) (error, bool) {
		*n = data.(int64)
		return nil, false
	}
}

// ActionStoreBin builds an action for storing binary data.
func ActionStoreBin(b *[]byte) GetterAction {
	return func(data interface{}, ur UnitReader) (error, bool) {
		*b = data.([]byte)
		return nil, false
	}
}

// ActionStoreBool builds an action for storing a boolean value.
func ActionStoreBool(b *bool) GetterAction {
	return func(data interface{}, ur UnitReader) (error, bool) {
		*b = data.(bool)
		return nil, false
	}
}

// ActionStoreByte builds an action for storing a byte.
func ActionStoreByte(b *byte) GetterAction {
	return func(data interface{}, ur UnitReader) (error, bool) {
		*b = data.(byte)
		return nil, false
	}
}

// ActionCopyBinStream builds an action that will copy the content of a BinStream to a writer.
func ActionCopyBinStream(w io.Writer) GetterAction {
	return func(data interface{}, ur UnitReader) (error, bool) {
		bsr := data.(*BinstreamReader)
		_, err := io.Copy(w, bsr)
		if err != nil {
			// Try to skip the rest of the binstream in case the error resulted from w.
			if err := bsr.FastForward(); err != nil {
				return err, true // This is a fatal error, since the stream is now corrupted.
			}
			return err, false
		}
		return nil, false
	}
}