github.com/simonmittag/ws@v1.1.0-rc.5.0.20210419231947-82b846128245/wsflate/parameters.go (about)

     1  package wsflate
     2  
     3  import (
     4  	"fmt"
     5  	"strconv"
     6  
     7  	"github.com/gobwas/httphead"
     8  )
     9  
    10  const (
    11  	ExtensionName = "permessage-deflate"
    12  
    13  	serverNoContextTakeover = "server_no_context_takeover"
    14  	clientNoContextTakeover = "client_no_context_takeover"
    15  	serverMaxWindowBits     = "server_max_window_bits"
    16  	clientMaxWindowBits     = "client_max_window_bits"
    17  )
    18  
    19  var (
    20  	ExtensionNameBytes = []byte(ExtensionName)
    21  
    22  	serverNoContextTakeoverBytes = []byte(serverNoContextTakeover)
    23  	clientNoContextTakeoverBytes = []byte(clientNoContextTakeover)
    24  	serverMaxWindowBitsBytes     = []byte(serverMaxWindowBits)
    25  	clientMaxWindowBitsBytes     = []byte(clientMaxWindowBits)
    26  )
    27  
    28  var windowBits [8][]byte
    29  
    30  func init() {
    31  	for i := range windowBits {
    32  		windowBits[i] = []byte(strconv.Itoa(i + 8))
    33  	}
    34  }
    35  
    36  // Parameters contains compression extension options.
    37  type Parameters struct {
    38  	ServerNoContextTakeover bool
    39  	ClientNoContextTakeover bool
    40  	ServerMaxWindowBits     WindowBits
    41  	ClientMaxWindowBits     WindowBits
    42  }
    43  
    44  // WindowBits specifies window size accordingly to RFC.
    45  // Use its Bytes() method to obtain actual size of window in bytes.
    46  type WindowBits byte
    47  
    48  // Defined reports whether window bits were specified.
    49  func (b WindowBits) Defined() bool {
    50  	return b > 0
    51  }
    52  
    53  // Bytes returns window size in number of bytes.
    54  func (b WindowBits) Bytes() int {
    55  	return 1 << uint(b)
    56  }
    57  
    58  const (
    59  	MaxLZ77WindowSize = 32768 // 2^15
    60  )
    61  
    62  // Parse reads parameters from given HTTP header option accordingly to RFC.
    63  //
    64  // It returns non-nil error at least in these cases:
    65  //   - The negotiation offer contains an extension parameter not defined for
    66  //   use in an offer/response.
    67  //   - The negotiation offer/response contains an extension parameter with an
    68  //   invalid value.
    69  //   - The negotiation offer/response contains multiple extension parameters
    70  //   with
    71  // the same name.
    72  func (p *Parameters) Parse(opt httphead.Option) (err error) {
    73  	const (
    74  		clientMaxWindowBitsSeen = 1 << iota
    75  		serverMaxWindowBitsSeen
    76  		clientNoContextTakeoverSeen
    77  		serverNoContextTakeoverSeen
    78  	)
    79  
    80  	// Reset to not mix parsed data from previous Parse() calls.
    81  	*p = Parameters{}
    82  
    83  	var seen byte
    84  	opt.Parameters.ForEach(func(key, val []byte) (ok bool) {
    85  		switch string(key) {
    86  		case clientMaxWindowBits:
    87  			if len(val) == 0 {
    88  				p.ClientMaxWindowBits = 1
    89  				return true
    90  			}
    91  			if seen&clientMaxWindowBitsSeen != 0 {
    92  				err = paramError("duplicate", key, val)
    93  				return false
    94  			}
    95  			seen |= clientMaxWindowBitsSeen
    96  			if p.ClientMaxWindowBits, ok = bitsFromASCII(val); !ok {
    97  				err = paramError("invalid", key, val)
    98  				return false
    99  			}
   100  
   101  		case serverMaxWindowBits:
   102  			if len(val) == 0 {
   103  				err = paramError("invalid", key, val)
   104  				return false
   105  			}
   106  			if seen&serverMaxWindowBitsSeen != 0 {
   107  				err = paramError("duplicate", key, val)
   108  				return false
   109  			}
   110  			seen |= serverMaxWindowBitsSeen
   111  			if p.ServerMaxWindowBits, ok = bitsFromASCII(val); !ok {
   112  				err = paramError("invalid", key, val)
   113  				return false
   114  			}
   115  
   116  		case clientNoContextTakeover:
   117  			if len(val) > 0 {
   118  				err = paramError("invalid", key, val)
   119  				return false
   120  			}
   121  			if seen&clientNoContextTakeoverSeen != 0 {
   122  				err = paramError("duplicate", key, val)
   123  				return false
   124  			}
   125  			seen |= clientNoContextTakeoverSeen
   126  			p.ClientNoContextTakeover = true
   127  
   128  		case serverNoContextTakeover:
   129  			if len(val) > 0 {
   130  				err = paramError("invalid", key, val)
   131  				return false
   132  			}
   133  			if seen&serverNoContextTakeoverSeen != 0 {
   134  				err = paramError("duplicate", key, val)
   135  				return false
   136  			}
   137  			seen |= serverNoContextTakeoverSeen
   138  			p.ServerNoContextTakeover = true
   139  
   140  		default:
   141  			err = paramError("unexpected", key, val)
   142  			return false
   143  		}
   144  		return true
   145  	})
   146  	return
   147  }
   148  
   149  // Option encodes parameters into HTTP header option.
   150  func (p Parameters) Option() httphead.Option {
   151  	opt := httphead.Option{
   152  		Name: ExtensionNameBytes,
   153  	}
   154  	setBool(&opt, serverNoContextTakeoverBytes, p.ServerNoContextTakeover)
   155  	setBool(&opt, clientNoContextTakeoverBytes, p.ClientNoContextTakeover)
   156  	setBits(&opt, serverMaxWindowBitsBytes, p.ServerMaxWindowBits)
   157  	setBits(&opt, clientMaxWindowBitsBytes, p.ClientMaxWindowBits)
   158  	return opt
   159  }
   160  
   161  func isValidBits(x int) bool {
   162  	return 8 <= x && x <= 15
   163  }
   164  
   165  func bitsFromASCII(p []byte) (WindowBits, bool) {
   166  	n, ok := httphead.IntFromASCII(p)
   167  	if !ok || !isValidBits(n) {
   168  		return 0, false
   169  	}
   170  	return WindowBits(n), true
   171  }
   172  
   173  func setBits(opt *httphead.Option, name []byte, bits WindowBits) {
   174  	if bits == 0 {
   175  		return
   176  	}
   177  	if bits == 1 {
   178  		opt.Parameters.Set(name, nil)
   179  		return
   180  	}
   181  	if !isValidBits(int(bits)) {
   182  		panic(fmt.Sprintf("wsflate: invalid bits value: %d", bits))
   183  	}
   184  	opt.Parameters.Set(name, windowBits[bits-8])
   185  }
   186  
   187  func setBool(opt *httphead.Option, name []byte, flag bool) {
   188  	if flag {
   189  		opt.Parameters.Set(name, nil)
   190  	}
   191  }
   192  
   193  func paramError(reason string, key, val []byte) error {
   194  	return fmt.Errorf(
   195  		"wsflate: %s extension parameter %q: %q",
   196  		reason, key, val,
   197  	)
   198  }