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 }