forked from netsec-ethz/scion
/
json.go
208 lines (192 loc) · 5.48 KB
/
json.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
// Copyright 2017 ETH Zurich
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package pktcls
import (
"encoding/json"
"strconv"
"github.com/scionproto/scion/go/lib/common"
"github.com/scionproto/scion/go/lib/serrors"
)
type Typer interface {
Type() string
}
// This package makes extensive use of serialized interfaces. This requires
// special handling during marshaling and unmarshaling to take concrete types
// into account. During marshaling, an object of type T that implements some
// interface I is encoded as {"T": JSON(I)}, where JSON(I) is the normal
// encoding of type T.
//
// Marshaling uses a custom map with a single entry with key a string "T" and
// value interface{} containing the object iself.
//
// Unmarshaling uses a custom map of type map[string]*json.RawMessage which
// delays the unmarshaling of the object itself. After unmarshaling to this
// map, it contains a single entry with key "T". Depending on T, the correct
// concrete type is unmarshaled.
const (
TypeCondAllOf = "CondAllOf"
TypeCondAnyOf = "CondAnyOf"
TypeCondNot = "CondNot"
TypeCondBool = "CondBool"
TypeCondIPv4 = "CondIPv4"
TypeIPv4MatchSource = "MatchSource"
TypeIPv4MatchDestination = "MatchDestination"
TypeIPv4MatchToS = "MatchToS"
TypeIPv4MatchDSCP = "MatchDSCP"
)
// generic container for marshaling custom data
type jsonContainer map[string]interface{}
func marshalInterface(t Typer) ([]byte, error) {
return json.Marshal(jsonContainer{t.Type(): t})
}
// unmarshalInterface receives a JSON encoded object with a single field whose
// key is a type and value is the JSON encoding of an object of that type, and
// returns an interface containing that concrete type.
func unmarshalInterface(b []byte) (Typer, error) {
var container map[string]*json.RawMessage
err := json.Unmarshal(b, &container)
if err != nil {
return nil, err
}
for k, v := range container {
switch k {
case TypeCondAllOf:
var c CondAllOf
if v == nil {
return c, nil
}
err := json.Unmarshal(*v, &c)
return c, err
case TypeCondAnyOf:
var c CondAnyOf
if v == nil {
return c, nil
}
err := json.Unmarshal(*v, &c)
return c, err
case TypeCondNot:
var c CondNot
err := json.Unmarshal(*v, &c)
return c, err
case TypeCondBool:
var c CondBool
err := json.Unmarshal(*v, &c)
return c, err
case TypeCondIPv4:
var c CondIPv4
err := json.Unmarshal(*v, &c)
return &c, err
case TypeIPv4MatchSource:
var p IPv4MatchSource
err := json.Unmarshal(*v, &p)
return &p, err
case TypeIPv4MatchDestination:
var p IPv4MatchDestination
err := json.Unmarshal(*v, &p)
return &p, err
case TypeIPv4MatchToS:
var p IPv4MatchToS
err := json.Unmarshal(*v, &p)
return &p, err
case TypeIPv4MatchDSCP:
var p IPv4MatchDSCP
err := json.Unmarshal(*v, &p)
return &p, err
default:
return nil, common.NewBasicError("Unknown type", nil, "type", k)
}
}
return nil, nil
}
// unmarshalCond extracts a Cond from a JSON encoding
func unmarshalCond(b []byte) (Cond, error) {
t, err := unmarshalInterface(b)
if err != nil {
return nil, err
}
c, ok := t.(Cond)
if !ok {
return nil, serrors.New("Unable to extract Cond from interface")
}
return c, nil
}
// unmarshal extracts an IPv4Predicate from a JSON encoding
func unmarshalPredicate(b []byte) (IPv4Predicate, error) {
t, err := unmarshalInterface(b)
if err != nil {
return nil, err
}
p, ok := t.(IPv4Predicate)
if !ok {
return nil, serrors.New("Unable to extract Cond from interface")
}
return p, nil
}
// Special case slices because we only need them for Conds
func marshalCondSlice(conds []Cond) ([]byte, error) {
var jsons []*json.RawMessage
for _, cond := range conds {
b, err := marshalInterface(cond)
if err != nil {
return nil, err
}
jsons = append(jsons, (*json.RawMessage)(&b))
}
return json.Marshal(jsons)
}
func unmarshalCondSlice(b []byte) ([]Cond, error) {
var jsons []*json.RawMessage
err := json.Unmarshal(b, &jsons)
if err != nil {
return nil, err
}
var conds []Cond
for _, v := range jsons {
cond, err := unmarshalCond(*v)
if err != nil {
return nil, err
}
conds = append(conds, cond)
}
return conds, nil
}
func unmarshalStringField(b []byte, name, field string) (string, error) {
var jc jsonContainer
err := json.Unmarshal(b, &jc)
if err != nil {
return "", err
}
v, ok := jc[field]
if !ok {
return "", common.NewBasicError("String field missing", nil, "name", name, "field", field)
}
s, ok := v.(string)
if !ok {
return "", common.NewBasicError("Field is non-string", nil,
"name", name, "field", field, "type", common.TypeOf(v))
}
return s, nil
}
func unmarshalUintField(b []byte, name, field string, width int) (uint64, error) {
s, err := unmarshalStringField(b, name, field)
if err != nil {
return 0, err
}
i, err := strconv.ParseUint(s, 0, width)
if err != nil {
return 0, common.NewBasicError("Unable to parse uint field", err,
"name", name, "field", field)
}
return i, nil
}