forked from aryehlev/go-gpp
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathparse.go
157 lines (133 loc) · 4.84 KB
/
parse.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
package gpp
import (
"fmt"
"strings"
"github.com/streamrail/go-gpp/constants"
"github.com/streamrail/go-gpp/sections/uspca"
"github.com/streamrail/go-gpp/sections/uspco"
"github.com/streamrail/go-gpp/sections/uspct"
"github.com/streamrail/go-gpp/sections/uspnat"
"github.com/streamrail/go-gpp/sections/usput"
"github.com/streamrail/go-gpp/sections/uspva"
"github.com/streamrail/go-gpp/util"
)
const (
SectionGPPByte byte = 'D'
MinHeaderCharacters = 4
)
type GppContainer struct {
Version int
SectionTypes []constants.SectionID
Sections []Section
}
type Section interface {
GetID() constants.SectionID
GetValue() string // base64 encoding usually, but plaintext for ccpa
Encode(bool) []byte
}
func Parse(v string) (GppContainer, []error) {
var gpp GppContainer
sectionStrings := strings.Split(v, "~")
header := sectionStrings[0]
if err := failFastHeaderValidate(header); err != nil {
return gpp, []error{err}
}
bs, err := util.NewBitStreamFromBase64(header)
if err != nil {
return gpp, []error{fmt.Errorf("error parsing GPP header, base64 decoding: %s", err)}
}
// We checked the GPP header type above outside of the bitstream framework, so we advance the bit stream past the first 6 bits.
bs.SetPosition(6)
ver, err := bs.ReadByte6()
if err != nil {
return gpp, []error{fmt.Errorf("error parsing GPP header, unable to parse GPP version: %s", err)}
}
gpp.Version = int(ver)
intRange, err := bs.ReadFibonacciRange()
if err != nil {
return gpp, []error{fmt.Errorf("error parsing GPP header, section identifiers: %s", err)}
}
// We do not count the GPP header as a section
secCount := len(sectionStrings) - 1
secIDs := make([]constants.SectionID, 0, secCount)
for _, sec := range intRange.Range {
for i := sec.StartID; i <= sec.EndID; i++ {
secIDs = append(secIDs, constants.SectionID(i))
}
}
if len(secIDs) != secCount {
return gpp, []error{fmt.Errorf("error parsing GPP header, section IDs do not match the number of sections: found %d IDs, have %d sections", len(secIDs), secCount)}
}
gpp.SectionTypes = secIDs
sections := make([]Section, secCount)
var errs []error
for i, id := range secIDs {
switch id {
case constants.SectionUSPNAT:
sections[i], err = uspnat.NewUSPNAT(sectionStrings[i+1])
if err != nil {
errs = append(errs, fmt.Errorf("error parsing %s consent string: %s", constants.SectionNamesByID[int(id)], err))
}
case constants.SectionUSPCA:
sections[i], err = uspca.NewUSPCA(sectionStrings[i+1])
if err != nil {
errs = append(errs, fmt.Errorf("error parsing %s consent string: %s", constants.SectionNamesByID[int(id)], err))
}
case constants.SectionUSPVA:
sections[i], err = uspva.NewUSPVA(sectionStrings[i+1])
if err != nil {
errs = append(errs, fmt.Errorf("error parsing %s consent string: %s", constants.SectionNamesByID[int(id)], err))
}
case constants.SectionUSPCO:
sections[i], err = uspco.NewUSPCO(sectionStrings[i+1])
if err != nil {
errs = append(errs, fmt.Errorf("error parsing %s consent string: %s", constants.SectionNamesByID[int(id)], err))
}
case constants.SectionUSPUT:
sections[i], err = usput.NewUSPUT(sectionStrings[i+1])
if err != nil {
errs = append(errs, fmt.Errorf("error parsing %s consent string: %s", constants.SectionNamesByID[int(id)], err))
}
case constants.SectionUSPCT:
sections[i], err = uspct.NewUSPCT(sectionStrings[i+1])
if err != nil {
errs = append(errs, fmt.Errorf("error parsing %s consent string: %s", constants.SectionNamesByID[int(id)], err))
}
default:
sections[i] = GenericSection{sectionID: id, value: sectionStrings[i+1]}
if err != nil {
errs = append(errs, fmt.Errorf("error parsing unsupported (section %d) consent string: %s", int(id), err))
}
}
}
gpp.Sections = sections
return gpp, errs
}
// failFastHeaderValidate performs quick validations of the header section before decoding
// the bit stream.
func failFastHeaderValidate(h string) error {
// the GPP header must be at least 24 bits to represent the type, version, and a fibonacci sequence
// of at least 1 item. this requires at least 4 characters.
if len(h) < MinHeaderCharacters {
return fmt.Errorf("error parsing GPP header, should be at least %d bytes long", MinHeaderCharacters)
}
// base64-url encodes 6 bits into each character. the first 6 bits of GPP header must always
// evaluate to the integer '3', so we can short cut by checking the first character directly.
if h[0] != SectionGPPByte {
return fmt.Errorf("error parsing GPP header, header must have type=%d", constants.SectionGPP)
}
return nil
}
type GenericSection struct {
sectionID constants.SectionID
value string
}
func (gs GenericSection) GetID() constants.SectionID {
return gs.sectionID
}
func (gs GenericSection) GetValue() string {
return gs.value
}
func (gs GenericSection) Encode(bool) []byte {
return []byte(gs.value)
}