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
|
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
}
|