forked from TheThingsArchive/ttn
/
convert_fields_custom.go
263 lines (218 loc) · 6.76 KB
/
convert_fields_custom.go
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
257
258
259
260
261
262
263
// Copyright © 2017 The Things Network
// Use of this source code is governed by the MIT license that can be found in the LICENSE file.
package handler
import (
"fmt"
"reflect"
"time"
pb_handler "github.com/TheThingsNetwork/ttn/api/handler"
"github.com/TheThingsNetwork/ttn/core/handler/functions"
"github.com/TheThingsNetwork/ttn/utils/errors"
)
// CustomUplinkFunctions decodes, converts and validates payload using JavaScript functions
type CustomUplinkFunctions struct {
// Decoder is a JavaScript function that accepts the payload as byte array and
// returns an object containing the decoded values
Decoder string
// Converter is a JavaScript function that accepts the data as decoded by
// Decoder and returns an object containing the converted values
Converter string
// Validator is a JavaScript function that validates the data is converted by
// Converter and returns a boolean value indicating the validity of the data
Validator string
// Logger is the logger that will be used to store logs
Logger functions.Logger
}
// timeOut is the maximum allowed time a payload function is allowed to run
var timeOut = 100 * time.Millisecond
// decode decodes the payload using the Decoder function into a map
func (f *CustomUplinkFunctions) decode(payload []byte, port uint8) (map[string]interface{}, error) {
if f.Decoder == "" {
return nil, nil
}
env := map[string]interface{}{
"payload": payload,
"port": port,
}
code := fmt.Sprintf(`
%s;
Decoder(payload.slice(0), port);
`, f.Decoder)
value, err := functions.RunCode("Decoder", code, env, timeOut, f.Logger)
if err != nil {
return nil, err
}
if !value.IsObject() {
return nil, errors.NewErrInvalidArgument("Decoder", "does not return an object")
}
v, _ := value.Export()
m, ok := v.(map[string]interface{})
if !ok {
return nil, errors.NewErrInvalidArgument("Decoder", "does not return an object")
}
return m, nil
}
// convert converts the values in the specified map to a another map using the
// Converter function. If the Converter function is not set, this function
// returns the data as-is
func (f *CustomUplinkFunctions) convert(fields map[string]interface{}, port uint8) (map[string]interface{}, error) {
if f.Converter == "" {
return fields, nil
}
env := map[string]interface{}{
"fields": fields,
"port": port,
}
code := fmt.Sprintf(`
%s;
Converter(fields, port)
`, f.Converter)
value, err := functions.RunCode("Converter", code, env, timeOut, f.Logger)
if err != nil {
return nil, err
}
if !value.IsObject() {
return nil, errors.NewErrInvalidArgument("Converter", "does not return an object")
}
v, _ := value.Export()
m, ok := v.(map[string]interface{})
if !ok {
return nil, errors.NewErrInvalidArgument("Converter", "does not return an object")
}
return m, nil
}
// validate validates the values in the specified map using the Validator
// function. If the Validator function is not set, this function returns true
func (f *CustomUplinkFunctions) validate(fields map[string]interface{}, port uint8) (bool, error) {
if f.Validator == "" {
return true, nil
}
env := map[string]interface{}{
"fields": fields,
"port": port,
}
code := fmt.Sprintf(`
%s;
Validator(fields, port)
`, f.Validator)
value, err := functions.RunCode("Validator", code, env, timeOut, f.Logger)
if err != nil {
return false, err
}
if !value.IsBoolean() {
return false, errors.NewErrInvalidArgument("Validator", "does not return a boolean")
}
return value.ToBoolean()
}
// Decode decodes the specified payload, converts it and tests the validity
func (f *CustomUplinkFunctions) Decode(payload []byte, port uint8) (map[string]interface{}, bool, error) {
decoded, err := f.decode(payload, port)
if err != nil {
return nil, false, err
}
converted, err := f.convert(decoded, port)
if err != nil {
return nil, false, err
}
valid, err := f.validate(converted, port)
return converted, valid, err
}
// Log returns the log
func (f *CustomUplinkFunctions) Log() []*pb_handler.LogEntry {
return f.Logger.Entries()
}
// CustomDownlinkFunctions encodes payload using JavaScript functions
type CustomDownlinkFunctions struct {
// Encoder is a JavaScript function that accepts the payload as JSON and
// returns an array of bytes
Encoder string
// Logger is the logger that will be used to store logs
Logger functions.Logger
}
// encode encodes the map into a byte slice using the encoder payload function
// If no encoder function is set, this function returns an array.
func (f *CustomDownlinkFunctions) encode(payload map[string]interface{}, port uint8) ([]byte, error) {
if f.Encoder == "" {
return nil, errors.NewErrInvalidArgument("Downlink Payload", "fields supplied, but no Encoder function set")
}
env := map[string]interface{}{
"payload": payload,
"port": port,
}
code := fmt.Sprintf(`
%s;
Encoder(payload, port)
`, f.Encoder)
value, err := functions.RunCode("Encoder", code, env, timeOut, f.Logger)
if err != nil {
return nil, err
}
if !value.IsObject() {
return nil, errors.NewErrInvalidArgument("Encoder", "does not return an object")
}
v, err := value.Export()
if err != nil {
return nil, err
}
if reflect.TypeOf(v).Kind() != reflect.Slice {
return nil, errors.NewErrInvalidArgument("Encoder", "does not return an Array")
}
s := reflect.ValueOf(v)
l := s.Len()
res := make([]byte, l)
var n int64
for i := 0; i < l; i++ {
el := s.Index(i).Interface()
// type switch does not have fallthrough so we need
// to check every element individually
switch t := el.(type) {
case byte:
n = int64(t)
case int:
n = int64(t)
case int8:
n = int64(t)
case int16:
n = int64(t)
case uint16:
n = int64(t)
case int32:
n = int64(t)
case uint32:
n = int64(t)
case int64:
n = int64(t)
case uint64:
n = int64(t)
case float32:
n = int64(t)
if float32(n) != t {
return nil, errors.NewErrInvalidArgument("Encoder", "should return an Array of integer numbers")
}
case float64:
n = int64(t)
if float64(n) != t {
return nil, errors.NewErrInvalidArgument("Encoder", "should return an Array of integer numbers")
}
default:
return nil, errors.NewErrInvalidArgument("Encoder", "should return an Array of integer numbers")
}
if n < 0 || n > 255 {
return nil, errors.NewErrInvalidArgument("Encoder Output", "Numbers in Array should be between 0 and 255")
}
res[i] = byte(n)
}
return res, nil
}
// Encode encodes the specified field, converts it into a valid payload
func (f *CustomDownlinkFunctions) Encode(payload map[string]interface{}, port uint8) ([]byte, bool, error) {
encoded, err := f.encode(payload, port)
if err != nil {
return nil, false, err
}
return encoded, true, nil
}
// Log returns the log
func (f *CustomDownlinkFunctions) Log() []*pb_handler.LogEntry {
return f.Logger.Entries()
}