-
Notifications
You must be signed in to change notification settings - Fork 6
/
value.go
674 lines (555 loc) · 16.1 KB
/
value.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
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
/* Go IPP - IPP core protocol implementation in pure Go
*
* Copyright (C) 2020 and up by Alexander Pevzner ([email protected])
* See LICENSE for license terms and conditions
*
* Values for message attributes
*/
package goipp
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"math"
"time"
)
// Values represents a sequence of values with tags.
// Usually Values used as a "payload" of Attribute
type Values []struct {
T Tag // The tag
V Value // The value
}
// Add Value to Values
func (values *Values) Add(t Tag, v Value) {
*values = append(*values, struct {
T Tag
V Value
}{t, v})
}
// String converts Values to string
func (values Values) String() string {
if len(values) == 1 {
return values[0].V.String()
}
var buf bytes.Buffer
buf.Write([]byte("["))
for i, v := range values {
if i != 0 {
buf.Write([]byte(","))
}
buf.Write([]byte(v.V.String()))
}
buf.Write([]byte("]"))
return buf.String()
}
// Equal performs deep check of equality of two Values
func (values Values) Equal(values2 Values) bool {
if len(values) != len(values2) {
return false
}
for i, v := range values {
v2 := values2[i]
if v.T != v2.T || !ValueEqual(v.V, v2.V) {
return false
}
}
return true
}
// Similar performs deep check of **logical** equality of two Values
func (values Values) Similar(values2 Values) bool {
if len(values) != len(values2) {
return false
}
for i, v := range values {
v2 := values2[i]
if v.T != v2.T || !ValueSimilar(v.V, v2.V) {
return false
}
}
return true
}
// Value represents an attribute value
//
// IPP uses typed values, and type of each value is unambiguously
// defined by the attribute tag
type Value interface {
String() string
Type() Type
encode() ([]byte, error)
decode([]byte) (Value, error)
}
var (
_ = Value(Binary(nil))
_ = Value(Boolean(false))
_ = Value(Collection(nil))
_ = Value(Integer(0))
_ = Value(Range{})
_ = Value(Resolution{})
_ = Value(String(""))
_ = Value(TextWithLang{})
_ = Value(Time{time.Time{}})
_ = Value(Void{})
)
// IntegerOrRange is a Value of type Integer or Range
type IntegerOrRange interface {
Value
// Within checks that x fits within the range:
//
// for Integer: x == Integer's value
// for Range: Lower <= x && x <= Upper
Within(x int) bool
}
var (
_ = IntegerOrRange(Integer(0))
_ = IntegerOrRange(Range{})
)
// ValueEqual checks if two values are equal
//
// Equality means that types and values are equal. For structured
// values, like Collection, deep comparison is performed
func ValueEqual(v1, v2 Value) bool {
if v1.Type() != v2.Type() {
return false
}
switch v1.Type() {
case TypeDateTime:
return v1.(Time).Equal(v2.(Time).Time)
case TypeBinary:
return bytes.Equal(v1.(Binary), v2.(Binary))
case TypeCollection:
c1 := Attributes(v1.(Collection))
c2 := Attributes(v2.(Collection))
return c1.Equal(c2)
}
return v1 == v2
}
// ValueSimilar checks if two values are **logically** equal,
// which means the following:
// - If values are equal (i.e., ValueEqual() returns true),
// they are similar.
// - Binary and String values are similar, if they represent
// the same sequence of bytes.
// - Two collections are similar, if they contain the same
// set of attributes (but may be differently ordered) and
// values of these attributes are similar.
func ValueSimilar(v1, v2 Value) bool {
if ValueEqual(v1, v2) {
return true
}
t1 := v1.Type()
t2 := v2.Type()
switch {
case t1 == TypeBinary && t2 == TypeString:
return bytes.Equal(v1.(Binary), []byte(v2.(String)))
case t1 == TypeString && t2 == TypeBinary:
return bytes.Equal([]byte(v1.(String)), v2.(Binary))
case t1 == TypeCollection && t2 == TypeCollection:
return Attributes(v1.(Collection)).Similar(
Attributes(v2.(Collection)))
}
return false
}
// Void is the Value that represents "no value"
//
// Use with: TagUnsupportedValue, TagDefault, TagUnknown,
// TagNotSettable, TagDeleteAttr, TagAdminDefine
type Void struct{}
// String converts Void Value to string
func (Void) String() string { return "" }
// Type returns type of Value (TypeVoid for Void)
func (Void) Type() Type { return TypeVoid }
// Encode Void Value into wire format
func (v Void) encode() ([]byte, error) {
return []byte{}, nil
}
// Decode Void Value from wire format
func (Void) decode([]byte) (Value, error) {
return Void{}, nil
}
// Integer is the Value that represents 32-bit signed int
//
// Use with: TagInteger, TagEnum
type Integer int32
// String converts Integer value to string
func (v Integer) String() string { return fmt.Sprintf("%d", int32(v)) }
// Type returns type of Value (TypeInteger for Integer)
func (Integer) Type() Type { return TypeInteger }
// Within checks that x fits within the range
//
// It implements IntegerOrRange interface
func (v Integer) Within(x int) bool {
return x == int(v)
}
// Encode Integer Value into wire format
func (v Integer) encode() ([]byte, error) {
return []byte{byte(v >> 24), byte(v >> 16), byte(v >> 8), byte(v)}, nil
}
// Decode Integer Value from wire format
func (Integer) decode(data []byte) (Value, error) {
if len(data) != 4 {
return nil, errors.New("value must be 4 bytes")
}
return Integer(binary.BigEndian.Uint32(data)), nil
}
// Boolean is the Value that contains true of false
//
// Use with: TagBoolean
type Boolean bool
// String converts Boolean value to string
func (v Boolean) String() string { return fmt.Sprintf("%t", bool(v)) }
// Type returns type of Value (TypeBoolean for Boolean)
func (Boolean) Type() Type { return TypeBoolean }
// Encode Boolean Value into wire format
func (v Boolean) encode() ([]byte, error) {
if v {
return []byte{1}, nil
}
return []byte{0}, nil
}
// Decode Boolean Value from wire format
func (Boolean) decode(data []byte) (Value, error) {
if len(data) != 1 {
return nil, errors.New("value must be 1 byte")
}
return Boolean(data[0] != 0), nil
}
// String is the Value that represents string of text
//
// Use with: TagText, TagName, TagReservedString, TagKeyword, TagURI,
// TagURIScheme, TagCharset, TagLanguage, TagMimeType, TagMemberName
type String string
// String converts String value to string
func (v String) String() string { return string(v) }
// Type returns type of Value (TypeString for String)
func (String) Type() Type { return TypeString }
// Encode String Value into wire format
func (v String) encode() ([]byte, error) {
return []byte(v), nil
}
// Decode String Value from wire format
func (String) decode(data []byte) (Value, error) {
return String(data), nil
}
// Time is the Value that represents DataTime
//
// Use with: TagTime
type Time struct{ time.Time }
// String converts Time value to string
func (v Time) String() string { return v.Time.Format(time.RFC3339) }
// Type returns type of Value (TypeDateTime for Time)
func (Time) Type() Type { return TypeDateTime }
// Encode Time Value into wire format
func (v Time) encode() ([]byte, error) {
// From RFC2579:
//
// field octets contents range
// ----- ------ -------- -----
// 1 1-2 year* 0..65536
// 2 3 month 1..12
// 3 4 day 1..31
// 4 5 hour 0..23
// 5 6 minutes 0..59
// 6 7 seconds 0..60
// (use 60 for leap-second)
// 7 8 deci-seconds 0..9
// 8 9 direction from UTC '+' / '-'
// 9 10 hours from UTC* 0..13
// 10 11 minutes from UTC 0..59
//
// * Notes:
// - the value of year is in network-byte order
// - daylight saving time in New Zealand is +13
year := v.Year()
_, zone := v.Zone()
dir := byte('+')
if zone < 0 {
zone = -zone
dir = '-'
}
return []byte{
byte(year >> 8), byte(year),
byte(v.Month()),
byte(v.Day()),
byte(v.Hour()),
byte(v.Minute()),
byte(v.Second()),
byte(v.Nanosecond() / 100000000),
dir,
byte(zone / 3600),
byte((zone / 60) % 60),
}, nil
}
// Decode Time Value from wire format
func (Time) decode(data []byte) (Value, error) {
// Check size
if len(data) != 11 {
return nil, errors.New("value must be 11 bytes")
}
// Validate ranges
var err error
switch {
case data[2] < 1 || data[2] > 12:
err = fmt.Errorf("bad month %d", data[2])
case data[3] < 1 || data[3] > 31:
err = fmt.Errorf("bad day %d", data[3])
case data[4] > 23:
err = fmt.Errorf("bad hours %d", data[4])
case data[5] > 59:
err = fmt.Errorf("bad minutes %d", data[5])
case data[6] > 60:
err = fmt.Errorf("bad seconds %d", data[6])
case data[7] > 9:
err = fmt.Errorf("bad deciseconds %d", data[7])
case data[8] != '+' && data[8] != '-':
return nil, errors.New("bad UTC sign")
case data[9] > 11:
err = fmt.Errorf("bad UTC hours %d", data[9])
case data[10] > 59:
err = fmt.Errorf("bad UTC minutes %d", data[10])
}
if err != nil {
return Time{}, err
}
// Decode time zone
tzName := fmt.Sprintf("UTC%c%d", data[8], data[9])
if data[10] != 0 {
tzName += fmt.Sprintf(":%d", data[10])
}
tzOff := 3600*int(data[9]) + 60*int(data[10])
if data[8] == '-' {
tzOff = -tzOff
}
tz := time.FixedZone(tzName, tzOff)
// Decode time
t := time.Date(
int(binary.BigEndian.Uint16(data[0:2])), // year
time.Month(data[2]), // month
int(data[3]), // day
int(data[4]), // hour
int(data[5]), // min
int(data[6]), // sec
int(data[7])*100000000, // nsec
tz, // time zone
)
return Time{t}, nil
}
// Resolution is the Value that represents image resolution.
//
// Use with: TagResolution
type Resolution struct {
Xres, Yres int // X/Y resolutions
Units Units // Resolution units
}
// String converts Resolution value to string
func (v Resolution) String() string {
return fmt.Sprintf("%dx%d%s", v.Xres, v.Yres, v.Units)
}
// Type returns type of Value (TypeResolution for Resolution)
func (Resolution) Type() Type { return TypeResolution }
// Encode Resolution Value into wire format
func (v Resolution) encode() ([]byte, error) {
// Wire format
// 4 bytes: Xres
// 4 bytes: Yres
// 1 byte: Units
x, y := v.Xres, v.Yres
return []byte{
byte(x >> 24), byte(x >> 16), byte(x >> 8), byte(x),
byte(y >> 24), byte(y >> 16), byte(y >> 8), byte(y),
byte(v.Units),
}, nil
}
// Decode Resolution Value from wire format
func (Resolution) decode(data []byte) (Value, error) {
if len(data) != 9 {
return nil, errors.New("value must be 9 bytes")
}
return Resolution{
Xres: int(binary.BigEndian.Uint32(data[0:4])),
Yres: int(binary.BigEndian.Uint32(data[4:8])),
Units: Units(data[8]),
}, nil
}
// Units represents resolution units
type Units uint8
// Resolution units codes
const (
UnitsDpi Units = 3 // Dots per inch
UnitsDpcm Units = 4 // Dots per cm
)
// String converts Units to string
func (u Units) String() string {
switch u {
case UnitsDpi:
return "dpi"
case UnitsDpcm:
return "dpcm"
default:
return fmt.Sprintf("0x%2.2x", uint8(u))
}
}
// Range is the Value that represents a range of 32-bit signed integers
//
// Use with: TagRange
type Range struct {
Lower, Upper int // Lower/upper bounds
}
// String converts Range value to string
func (v Range) String() string {
return fmt.Sprintf("%d-%d", v.Lower, v.Upper)
}
// Type returns type of Value (TypeRange for Range)
func (Range) Type() Type { return TypeRange }
// Encode Range Value into wire format
func (v Range) encode() ([]byte, error) {
// Wire format
// 4 bytes: Lower
// 4 bytes: Upper
l, u := v.Lower, v.Upper
return []byte{
byte(l >> 24), byte(l >> 16), byte(l >> 8), byte(l),
byte(u >> 24), byte(u >> 16), byte(u >> 8), byte(u),
}, nil
}
// Within checks that x fits within the range
//
// It implements IntegerOrRange interface
func (v Range) Within(x int) bool {
return v.Lower <= x && x <= v.Upper
}
// Decode Range Value from wire format
func (Range) decode(data []byte) (Value, error) {
if len(data) != 8 {
return nil, errors.New("value must be 8 bytes")
}
return Range{
Lower: int(binary.BigEndian.Uint32(data[0:4])),
Upper: int(binary.BigEndian.Uint32(data[4:8])),
}, nil
}
// TextWithLang is the Value that represents a combination
// of two strings:
// * text on some natural language (i.e., "hello")
// * name of that language (i.e., "en")
//
// Use with: TagTextLang, TagNameLang
type TextWithLang struct {
Lang, Text string // Language and text
}
// String converts TextWithLang value to string
func (v TextWithLang) String() string { return v.Text + " [" + v.Lang + "]" }
// Type returns type of Value (TypeTextWithLang for TextWithLang)
func (TextWithLang) Type() Type { return TypeTextWithLang }
// Encode TextWithLang Value into wire format
func (v TextWithLang) encode() ([]byte, error) {
// Wire format
// 2 bytes: len(Lang)
// variable: Lang
// 2 bytes: len(Text)
// variable: Text
lang := []byte(v.Lang)
text := []byte(v.Text)
if len(lang) > math.MaxUint16 {
return nil, fmt.Errorf("Lang exceeds %d bytes", math.MaxUint16)
}
if len(text) > math.MaxUint16 {
return nil, fmt.Errorf("Text exceeds %d bytes", math.MaxUint16)
}
data := make([]byte, 2+2+len(lang)+len(text))
binary.BigEndian.PutUint16(data, uint16(len(lang)))
copy(data[2:], []byte(lang))
data2 := data[2+len(lang):]
binary.BigEndian.PutUint16(data2, uint16(len(text)))
copy(data2[2:], []byte(text))
return data, nil
}
// Decode TextWithLang Value from wire format
func (TextWithLang) decode(data []byte) (Value, error) {
var langLen, textLen int
var lang, text string
// Unpack language length
if len(data) < 2 {
return nil, errors.New("truncated language length")
}
langLen = int(binary.BigEndian.Uint16(data[0:2]))
data = data[2:]
// Unpack language value
if len(data) < langLen {
return nil, errors.New("truncated language name")
}
lang = string(data[:langLen])
data = data[langLen:]
// Unpack text length
if len(data) < 2 {
return nil, errors.New("truncated text length")
}
textLen = int(binary.BigEndian.Uint16(data[0:2]))
data = data[2:]
// Unpack text value
if len(data) < textLen {
return nil, errors.New("truncated text string")
}
text = string(data[:textLen])
data = data[textLen:]
// We must have consumed all bytes at this point
if len(data) != 0 {
return nil, fmt.Errorf("extra %d bytes at the end of value",
len(data))
}
// Return a value
return TextWithLang{Lang: lang, Text: text}, nil
}
// Binary is the Value that represents a raw binary data
type Binary []byte
// String converts Binary value to string
func (v Binary) String() string {
return fmt.Sprintf("%x", []byte(v))
}
// Type returns type of Value (TypeBinary for Binary)
func (Binary) Type() Type { return TypeBinary }
// Encode TextWithLang Value into wire format
func (v Binary) encode() ([]byte, error) {
return []byte(v), nil
}
// Decode Binary Value from wire format
func (Binary) decode(data []byte) (Value, error) {
return Binary(data), nil
}
// Collection is the Value that represents collection of attributes
//
// Use with: TagBeginCollection
type Collection Attributes
// Add Attribute to Attributes
func (v *Collection) Add(attr Attribute) {
*v = append(*v, attr)
}
// Equal checks that two collections are equal
func (v Collection) Equal(v2 Attributes) bool {
return Attributes(v).Equal(Attributes(v2))
}
// String converts Collection to string
func (v Collection) String() string {
var buf bytes.Buffer
buf.Write([]byte("{"))
for i, attr := range v {
if i > 0 {
buf.Write([]byte(" "))
}
fmt.Fprintf(&buf, "%s=%s", attr.Name, attr.Values)
}
buf.Write([]byte("}"))
return buf.String()
}
// Type returns type of Value (TypeCollection for Collection)
func (Collection) Type() Type { return TypeCollection }
// Encode Collection Value into wire format
func (Collection) encode() ([]byte, error) {
// Note, TagBeginCollection attribute contains
// no data, collection itself handled the different way
return []byte{}, nil
}
// Decode Collection Value from wire format
func (Collection) decode(data []byte) (Value, error) {
panic("internal error")
}