github.com/google/martian/v3@v3.3.3/trafficshape/utils.go (about)

     1  package trafficshape
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"regexp"
     7  	"sort"
     8  	"strconv"
     9  	"strings"
    10  	"time"
    11  )
    12  
    13  // Converts a sorted slice of Throttles to their ChangeBandwidth actions. In adddition, checks for
    14  // overlapping throttle ranges. Returns a slice of actions and an error specifying if the throttles
    15  // passed the non-overlapping verification.
    16  //
    17  // Idea: For every throttle, add two ChangeBandwidth actions (one for start and one for end), unless
    18  // the ending byte of one throttle is the same as the starting byte of the next throttle, in which
    19  // case we do not add the end ChangeBandwidth for the first throttle, or if the end of a throttle
    20  // is -1 (representing EOF), in which case we do not add the end ChangeBandwidth action for the throttle.
    21  //
    22  // Note, we only allow the last throttle in the sorted list to have an end of -1 in order to avoid an overlap.
    23  func getActionsFromThrottles(throttles []*Throttle, defaultBandwidth int64) ([]Action, error) {
    24  
    25  	lenThr := len(throttles)
    26  	var actions []Action
    27  	for index, throttle := range throttles {
    28  		start := throttle.ByteStart
    29  		end := throttle.ByteEnd
    30  
    31  		if index == lenThr-1 {
    32  			if end == -1 {
    33  				actions = append(actions,
    34  					Action(&ChangeBandwidth{
    35  						Byte:      start,
    36  						Bandwidth: throttle.Bandwidth,
    37  					}))
    38  			} else {
    39  				actions = append(actions,
    40  					Action(&ChangeBandwidth{
    41  						Byte:      start,
    42  						Bandwidth: throttle.Bandwidth,
    43  					}),
    44  					Action(&ChangeBandwidth{
    45  						Byte:      end,
    46  						Bandwidth: defaultBandwidth,
    47  					}))
    48  			}
    49  			break
    50  		}
    51  
    52  		if end > throttles[index+1].ByteStart || end == -1 {
    53  			return actions, errors.New("overlapping throttle intervals found")
    54  		}
    55  
    56  		if end == throttles[index+1].ByteStart {
    57  			actions = append(actions,
    58  				Action(&ChangeBandwidth{
    59  					Byte:      start,
    60  					Bandwidth: throttle.Bandwidth,
    61  				}))
    62  		} else {
    63  			actions = append(actions,
    64  				Action(&ChangeBandwidth{
    65  					Byte:      start,
    66  					Bandwidth: throttle.Bandwidth,
    67  				}),
    68  				Action(&ChangeBandwidth{
    69  					Byte:      end,
    70  					Bandwidth: defaultBandwidth,
    71  				}))
    72  		}
    73  	}
    74  	return actions, nil
    75  }
    76  
    77  // Parses a Trafficshape and updates Traffficshape.Shapes while performing verifications.
    78  //
    79  // Returns an error in case a verification check fails.
    80  func parseShapes(ts *Trafficshape) error {
    81  	var err error
    82  	for shapeIndex, shape := range ts.Shapes {
    83  		if shape == nil {
    84  			return fmt.Errorf("nil shape at index: %d", shapeIndex)
    85  		}
    86  		if shape.URLRegex == "" {
    87  			return fmt.Errorf("no url_regex for shape at index: %d", shapeIndex)
    88  		}
    89  
    90  		if _, err = regexp.Compile(shape.URLRegex); err != nil {
    91  			return fmt.Errorf("url_regex for shape at index doesn't compile: %d", shapeIndex)
    92  		}
    93  
    94  		if shape.MaxBandwidth < 0 {
    95  			return fmt.Errorf("max_bandwidth cannot be negative for shape at index: %d", shapeIndex)
    96  		}
    97  
    98  		if shape.MaxBandwidth == 0 {
    99  			shape.MaxBandwidth = DefaultBitrate / 8
   100  		}
   101  
   102  		shape.WriteBucket = NewBucket(shape.MaxBandwidth, time.Second)
   103  
   104  		// Verify and process the throttles, filling in their ByteStart and ByteEnd.
   105  		for throttleIndex, throttle := range shape.Throttles {
   106  			if throttle == nil {
   107  				return fmt.Errorf("nil throttle at index %d in shape index %d", throttleIndex, shapeIndex)
   108  			}
   109  
   110  			if throttle.Bandwidth <= 0 {
   111  				return fmt.Errorf("invalid bandwidth: %d at throttle index %d in shape index %d",
   112  					throttle.Bandwidth, throttleIndex, shapeIndex)
   113  			}
   114  			sl := strings.Split(throttle.Bytes, "-")
   115  
   116  			if len(sl) != 2 {
   117  				return fmt.Errorf("invalid bytes: %s at throttle index %d in shape index %d",
   118  					throttle.Bytes, throttleIndex, shapeIndex)
   119  			}
   120  
   121  			start := sl[0]
   122  			end := sl[1]
   123  
   124  			if start == "" {
   125  				throttle.ByteStart = 0
   126  			} else {
   127  				throttle.ByteStart, err = strconv.ParseInt(start, 10, 64)
   128  				if err != nil {
   129  					return fmt.Errorf("invalid bytes: %s at throttle index %d in shape index %d",
   130  						throttle.Bytes, throttleIndex, shapeIndex)
   131  				}
   132  			}
   133  
   134  			if end == "" {
   135  				throttle.ByteEnd = -1
   136  			} else {
   137  				throttle.ByteEnd, err = strconv.ParseInt(end, 10, 64)
   138  				if err != nil {
   139  					return fmt.Errorf("invalid bytes: %s at throttle index %d in shape index %d",
   140  						throttle.Bytes, throttleIndex, shapeIndex)
   141  				}
   142  				if throttle.ByteEnd < throttle.ByteStart {
   143  					return fmt.Errorf("invalid bytes: %s at throttle index %d in shape index %d",
   144  						throttle.Bytes, throttleIndex, shapeIndex)
   145  				}
   146  			}
   147  
   148  			if throttle.ByteStart == throttle.ByteEnd {
   149  				return fmt.Errorf("invalid bytes: %s at throttle index %d in shape index %d",
   150  					throttle.Bytes, throttleIndex, shapeIndex)
   151  			}
   152  		}
   153  		// Fill in the actions, while performing verification.
   154  		shape.Actions = make([]Action, len(shape.Halts)+len(shape.CloseConnections))
   155  
   156  		for index, value := range shape.Halts {
   157  			if value == nil {
   158  				return fmt.Errorf("nil halt at index %d in shape index %d", index, shapeIndex)
   159  			}
   160  			if value.Duration < 0 || value.Byte < 0 {
   161  				return fmt.Errorf("invalid halt at index %d in shape index %d", index, shapeIndex)
   162  			}
   163  			if value.Count == 0 {
   164  				return fmt.Errorf(" 0 count for halt at index %d in shape index %d", index, shapeIndex)
   165  			}
   166  			shape.Actions[index] = Action(value)
   167  		}
   168  		offset := len(shape.Halts)
   169  		for index, value := range shape.CloseConnections {
   170  			if value == nil {
   171  				return fmt.Errorf("nil close_connection at index %d in shape index %d",
   172  					index, shapeIndex)
   173  			}
   174  			if value.Byte < 0 {
   175  				return fmt.Errorf("invalid close_connection at index %d in shape index %d",
   176  					index, shapeIndex)
   177  			}
   178  			if value.Count == 0 {
   179  				return fmt.Errorf("0 count for close_connection at index %d in shape index %d",
   180  					index, shapeIndex)
   181  			}
   182  			shape.Actions[offset+index] = Action(value)
   183  		}
   184  
   185  		sort.SliceStable(shape.Throttles, func(i, j int) bool {
   186  			return shape.Throttles[i].ByteStart < shape.Throttles[j].ByteStart
   187  		})
   188  
   189  		defaultBandwidth := DefaultBitrate / 8
   190  		if shape.MaxBandwidth > 0 {
   191  			defaultBandwidth = shape.MaxBandwidth
   192  		}
   193  
   194  		throttleActions, err := getActionsFromThrottles(shape.Throttles, defaultBandwidth)
   195  		if err != nil {
   196  			return fmt.Errorf("err: %s in shape index %d", err.Error(), shapeIndex)
   197  		}
   198  		shape.Actions = append(shape.Actions, throttleActions...)
   199  
   200  		// Sort the actions according to their byte offset.
   201  		sort.SliceStable(shape.Actions, func(i, j int) bool { return shape.Actions[i].getByte() < shape.Actions[j].getByte() })
   202  	}
   203  	return nil
   204  }