forked from google/martian
/
utils.go
203 lines (179 loc) · 6.27 KB
/
utils.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
package trafficshape
import (
"errors"
"fmt"
"regexp"
"sort"
"strconv"
"strings"
"time"
)
// Converts a sorted slice of Throttles to their ChangeBandwidth actions. In adddition, checks for
// overlapping throttle ranges. Returns a slice of actions and an error specifying if the throttles
// passed the non-overlapping verification.
// Idea: For every throttle, add two ChangeBandwidth actions (one for start and one for end), unless
// the ending byte of one throttle is the same as the starting byte of the next throttle, in which
// case we do not add the end ChangeBandwidth for the first throttle, or if the end of a throttle
// is -1 (representing till the end of file), in which case we do not add the end ChangeBandwidth
// action for the throttle. Note, we only allow the last throttle in the sorted list to have an end
// of -1, since otherwise there would be an overlap.
func getActionsFromThrottles(throttles []*Throttle, defaultBandwidth int64) ([]Action, error) {
lenThr := len(throttles)
var actions []Action
for index, throttle := range throttles {
start := throttle.ByteStart
end := throttle.ByteEnd
if index == lenThr-1 {
if end == -1 {
actions = append(actions,
Action(&ChangeBandwidth{
Byte: start,
Bandwidth: throttle.Bandwidth,
}))
} else {
actions = append(actions,
Action(&ChangeBandwidth{
Byte: start,
Bandwidth: throttle.Bandwidth,
}),
Action(&ChangeBandwidth{
Byte: end,
Bandwidth: defaultBandwidth,
}))
}
break
}
if end > throttles[index+1].ByteStart || end == -1 {
return actions, errors.New("overlapping throttle intervals found")
}
if end == throttles[index+1].ByteStart {
actions = append(actions,
Action(&ChangeBandwidth{
Byte: start,
Bandwidth: throttle.Bandwidth,
}))
} else {
actions = append(actions,
Action(&ChangeBandwidth{
Byte: start,
Bandwidth: throttle.Bandwidth,
}),
Action(&ChangeBandwidth{
Byte: end,
Bandwidth: defaultBandwidth,
}))
}
}
return actions, nil
}
// Parses the Trafficshape object and populates/updates Traffficshape.Shapes,
// while performing verifications. Returns an error in case a verification check fails.
func parseShapes(ts *Trafficshape) error {
var err error
for shapeIndex, shape := range ts.Shapes {
if shape == nil {
return fmt.Errorf("nil shape at index: %d", shapeIndex)
}
if shape.URLRegex == "" {
return fmt.Errorf("no url_regex for shape at index: %d", shapeIndex)
}
if _, err = regexp.Compile(shape.URLRegex); err != nil {
return fmt.Errorf("url_regex for shape at index doesn't compile: %d", shapeIndex)
}
if shape.MaxBandwidth < 0 {
return fmt.Errorf("max_bandwidth cannot be negative for shape at index: %d", shapeIndex)
}
if shape.MaxBandwidth == 0 {
shape.MaxBandwidth = DefaultBitrate / 8
}
shape.WriteBucket = NewBucket(shape.MaxBandwidth, time.Second)
// Verify and process the throttles, filling in their ByteStart and ByteEnd.
for throttleIndex, throttle := range shape.Throttles {
if throttle == nil {
return fmt.Errorf("nil throttle at index %d in shape index %d", throttleIndex, shapeIndex)
}
if throttle.Bandwidth <= 0 {
return fmt.Errorf("invalid bandwidth: %d at throttle index %d in shape index %d",
throttle.Bandwidth, throttleIndex, shapeIndex)
}
sl := strings.Split(throttle.Bytes, "-")
if len(sl) != 2 {
return fmt.Errorf("invalid bytes: %s at throttle index %d in shape index %d",
throttle.Bytes, throttleIndex, shapeIndex)
}
start := sl[0]
end := sl[1]
if start == "" {
throttle.ByteStart = 0
} else {
throttle.ByteStart, err = strconv.ParseInt(start, 10, 64)
if err != nil {
return fmt.Errorf("invalid bytes: %s at throttle index %d in shape index %d",
throttle.Bytes, throttleIndex, shapeIndex)
}
}
if end == "" {
throttle.ByteEnd = -1
} else {
throttle.ByteEnd, err = strconv.ParseInt(end, 10, 64)
if err != nil {
return fmt.Errorf("invalid bytes: %s at throttle index %d in shape index %d",
throttle.Bytes, throttleIndex, shapeIndex)
}
if throttle.ByteEnd < throttle.ByteStart {
return fmt.Errorf("invalid bytes: %s at throttle index %d in shape index %d",
throttle.Bytes, throttleIndex, shapeIndex)
}
}
if throttle.ByteStart == throttle.ByteEnd {
return fmt.Errorf("invalid bytes: %s at throttle index %d in shape index %d",
throttle.Bytes, throttleIndex, shapeIndex)
}
}
// Fill in the actions, while performing verification.
shape.Actions = make([]Action, len(shape.Halts)+len(shape.CloseConnections))
for index, value := range shape.Halts {
if value == nil {
return fmt.Errorf("nil halt at index %d in shape index %d", index, shapeIndex)
}
if value.Duration < 0 || value.Byte < 0 {
return fmt.Errorf("invalid halt at index %d in shape index %d", index, shapeIndex)
}
if value.Count == 0 {
return fmt.Errorf(" 0 count for halt at index %d in shape index %d", index, shapeIndex)
}
shape.Actions[index] = Action(value)
}
offset := len(shape.Halts)
for index, value := range shape.CloseConnections {
if value == nil {
return fmt.Errorf("nil close_connection at index %d in shape index %d",
index, shapeIndex)
}
if value.Byte < 0 {
return fmt.Errorf("invalid close_connection at index %d in shape index %d",
index, shapeIndex)
}
if value.Count == 0 {
return fmt.Errorf("0 count for close_connection at index %d in shape index %d",
index, shapeIndex)
}
shape.Actions[offset+index] = Action(value)
}
sort.SliceStable(shape.Throttles, func(i, j int) bool {
return shape.Throttles[i].ByteStart < shape.Throttles[j].ByteStart
})
defaultBandwidth := DefaultBitrate / 8
if shape.MaxBandwidth > 0 {
defaultBandwidth = shape.MaxBandwidth
}
throttleActions, err := getActionsFromThrottles(shape.Throttles, defaultBandwidth)
if err != nil {
return fmt.Errorf("err: %s in shape index %d", err.Error(), shapeIndex)
}
shape.Actions = append(shape.Actions, throttleActions...)
// Sort the actions according to their byte offset.
sort.SliceStable(shape.Actions, func(i, j int) bool { return shape.Actions[i].getByte() < shape.Actions[j].getByte() })
}
return nil
}