-
Notifications
You must be signed in to change notification settings - Fork 155
/
primary.go
312 lines (278 loc) · 8.67 KB
/
primary.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
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
// Copyright 2019 Anapaya Systems
//
// 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 trc
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"github.com/scionproto/scion/go/lib/addr"
"github.com/scionproto/scion/go/lib/common"
"github.com/scionproto/scion/go/lib/scrypto"
)
// Parsing errors with context.
const (
// ErrInvalidKeyType indicates an inexistent key type.
ErrInvalidKeyType common.ErrMsg = "invalid key type"
// ErrInvalidAttribute indicates an inexistent attribute.
ErrInvalidAttribute common.ErrMsg = "invalid attribute"
// ErrInvalidAttributesSize indicates invalid number of attributes in the attributes list.
ErrInvalidAttributesSize common.ErrMsg = "invalid attributes size"
// ErrDuplicateAttributes indicates attribute duplication in the attributes list.
ErrDuplicateAttributes common.ErrMsg = "duplicate attributes"
)
// Invariant errors
const (
// ErrAuthoritativeButNotCore indicates a primary AS that is authoritative but not core.
ErrAuthoritativeButNotCore common.ErrMsg = "authoritative but not core"
// ErrUnexpectedKey indicates that a primary AS has an excess key. Voting ASes must
// have an online and offline key. Non-Voting ASes must not have an offline
// key. Issuer ASes must have an online key. Core-only ASes must not have
// any key.
ErrUnexpectedKey common.ErrMsg = "unexpected key"
// ErrMissingKey indicates that the primary AS is missing a key.
ErrMissingKey common.ErrMsg = "missing key"
// ErrInvalidPrimaryAS indicates an invalid primary AS entry.
ErrInvalidPrimaryAS common.ErrMsg = "invalid primary as entry"
)
// Parsing errors
var (
// ErrAttributesNotSet indicates the attributes in a primary AS are not set.
ErrAttributesNotSet = errors.New("attributes not set")
// ErrKeysNotSet indicates the keys in a primary AS are not set.
ErrKeysNotSet = errors.New("keys not set")
)
// PrimaryASes holds all primary ASes and maps them to their attributes and keys.
type PrimaryASes map[addr.AS]PrimaryAS
// ValidateInvariant ensures that the TRC invariant holds for the primary ASes.
func (p *PrimaryASes) ValidateInvariant() error {
for as, primary := range *p {
if err := primary.ValidateInvariant(); err != nil {
return common.NewBasicError(ErrInvalidPrimaryAS, err, "as", as)
}
}
return nil
}
// WithAttribute returns all primary ASes with the given attribute.
func (p *PrimaryASes) WithAttribute(attribute Attribute) PrimaryASes {
m := make(PrimaryASes)
for as, primary := range *p {
if primary.Is(attribute) {
m[as] = primary
}
}
return m
}
// Count counts all primary ASes with the given attribute.
func (p *PrimaryASes) Count(attribute Attribute) int {
var c int
for _, primary := range *p {
if primary.Is(attribute) {
c++
}
}
return c
}
// primaryASAlias is necessary to avoid an infinite recursion when unmarshalling.
type primaryASAlias PrimaryAS
// PrimaryAS holds the attributes and keys of a primary AS.
type PrimaryAS struct {
Attributes Attributes `json:"attributes"`
Keys map[KeyType]scrypto.KeyMeta `json:"keys"`
}
// Is returns true if the primary AS holds this property.
func (p *PrimaryAS) Is(attr Attribute) bool {
return p.Attributes.Contains(attr)
}
// ValidateInvariant ensures that the TRC invariant holds for the primary AS.
func (p *PrimaryAS) ValidateInvariant() error {
if err := p.Attributes.Validate(); err != nil {
return err
}
if err := p.checkKeyExistence(IssuingKey, p.Is(Issuing)); err != nil {
return err
}
isVoting := p.Is(Voting)
if err := p.checkKeyExistence(OnlineKey, isVoting); err != nil {
return err
}
if err := p.checkKeyExistence(OfflineKey, isVoting); err != nil {
return err
}
return nil
}
func (p *PrimaryAS) checkKeyExistence(keyType KeyType, shouldExist bool) error {
_, ok := p.Keys[keyType]
if ok && !shouldExist {
return common.NewBasicError(ErrUnexpectedKey, nil, "key_type", keyType)
}
if !ok && shouldExist {
return common.NewBasicError(ErrMissingKey, nil, "key_type", keyType)
}
return nil
}
// UnmarshalJSON checks that all fields are set.
func (p *PrimaryAS) UnmarshalJSON(b []byte) error {
dec := json.NewDecoder(bytes.NewReader(b))
dec.DisallowUnknownFields()
if err := dec.Decode((*primaryASAlias)(p)); err != nil {
return err
}
return p.checkAllSet()
}
func (p *PrimaryAS) checkAllSet() error {
switch {
case p.Attributes == nil:
return ErrAttributesNotSet
case (p.Is(Voting) || p.Is(Issuing)) && p.Keys == nil:
return ErrKeysNotSet
}
return nil
}
var _ json.Marshaler = (*Attributes)(nil)
var _ json.Unmarshaler = (*Attributes)(nil)
// Attributes holds all attributes of a primary AS.
type Attributes []Attribute
// Contains indicates whether the attribute is contained.
func (t Attributes) Contains(attr Attribute) bool {
for _, v := range t {
if v == attr {
return true
}
}
return false
}
// Validate checks that the attributes list is valid.
func (t *Attributes) Validate() error {
if len(*t) > 4 || len(*t) <= 0 {
return common.NewBasicError(ErrInvalidAttributesSize, nil, "len", len(*t))
}
var core, authoritative bool
for i := 0; i < len(*t); i++ {
core = core || (*t)[i] == Core
authoritative = authoritative || (*t)[i] == Authoritative
for j := i + 1; j < len(*t); j++ {
if (*t)[i] == (*t)[j] {
return common.NewBasicError(ErrDuplicateAttributes, nil, "attribute", (*t)[i])
}
}
}
if authoritative && !core {
return common.NewBasicError(ErrAuthoritativeButNotCore, nil)
}
return nil
}
// MarshalJSON validates the attributes list during marshaling. It has to be a
// value receiver.
func (t Attributes) MarshalJSON() ([]byte, error) {
if err := t.Validate(); err != nil {
return nil, err
}
return json.Marshal(([]Attribute)(t))
}
// UnmarshalJSON validates the attributes list during parsing.
func (t *Attributes) UnmarshalJSON(b []byte) error {
if err := json.Unmarshal(b, (*[]Attribute)(t)); err != nil {
return err
}
return t.Validate()
}
const (
// Authoritative indicates an authoritative AS.
Authoritative Attribute = "authoritative"
// Core indicates a core AS.
Core Attribute = "core"
// Issuing indicates an issuing AS.
Issuing Attribute = "issuing"
// Voting indicates a voting AS. A voting AS must also be a core AS.
Voting Attribute = "voting"
)
// Attribute indicates the capability of a primary AS.
type Attribute string
// UnmarshalText checks that the attribute is valid. It can either be
// "authoritative", "core", "issuing", or "voting".
func (t *Attribute) UnmarshalText(b []byte) error {
switch Attribute(b) {
case Authoritative:
*t = Authoritative
case Issuing:
*t = Issuing
case Voting:
*t = Voting
case Core:
*t = Core
default:
return common.NewBasicError(ErrInvalidAttribute, nil, "input", string(b))
}
return nil
}
const (
IssuingKeyJSON = "issuing"
OnlineKeyJSON = "online"
OfflineKeyJSON = "offline"
)
const (
unknownKey KeyType = iota
// IssuingKey is the issuing key type.
IssuingKey
// OnlineKey is the online key type.
OnlineKey
// OfflineKey is the offline key type.
OfflineKey
)
// KeyType indicates the type of the key authenticated by the TRC.
//
// Because KeyType is used as a map key, it cannot be a string type. (see:
// https://github.com/golang/go/issues/33298)
type KeyType int
// UnmarshalText allows KeyType to be used as a map key and do validation when parsing.
func (t *KeyType) UnmarshalText(b []byte) error {
switch string(b) {
case OnlineKeyJSON:
*t = OnlineKey
case OfflineKeyJSON:
*t = OfflineKey
case IssuingKeyJSON:
*t = IssuingKey
default:
return common.NewBasicError(ErrInvalidKeyType, nil, "input", string(b))
}
return nil
}
// MarshalText is implemented to allow KeyType to be used as JSON map key. This
// must be a value receiver in order for KeyType fields in a struct to marshal
// correctly.
func (t KeyType) MarshalText() ([]byte, error) {
if s, ok := t.string(); ok {
return []byte(s), nil
}
return nil, common.NewBasicError(ErrInvalidKeyType, nil, "key_type", int(t))
}
func (t KeyType) String() string {
if s, ok := t.string(); ok {
return s
}
return fmt.Sprintf("UNKNOWN (%d)", t)
}
func (t KeyType) string() (string, bool) {
switch t {
case OnlineKey:
return OnlineKeyJSON, true
case OfflineKey:
return OfflineKeyJSON, true
case IssuingKey:
return IssuingKeyJSON, true
}
return "", false
}