httpNormalizer_test.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592
  1. /*
  2. * Copyright (c) 2023, Psiphon Inc.
  3. * All rights reserved.
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation, either version 3 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. *
  18. */
  19. package transforms
  20. import (
  21. "bytes"
  22. stderrors "errors"
  23. "io"
  24. "net"
  25. "strings"
  26. "testing"
  27. "time"
  28. "github.com/Psiphon-Labs/psiphon-tunnel-core/psiphon/common/errors"
  29. )
  30. type httpNormalizerTest struct {
  31. name string
  32. input string
  33. maxHeaderSize int
  34. prohibitedHeaders []string
  35. headerOrder []string
  36. chunkSize int
  37. connReadErrs []error
  38. validateMeekCookie func([]byte) ([]byte, error)
  39. wantOutput string
  40. wantError error
  41. }
  42. func runHTTPNormalizerTest(tt *httpNormalizerTest, useNormalizer bool) error {
  43. conn := testConn{
  44. readErrs: tt.connReadErrs,
  45. readBuffer: []byte(tt.input),
  46. }
  47. passthroughMessage := "passthrough"
  48. passthroughConn := testConn{
  49. readBuffer: []byte(passthroughMessage),
  50. }
  51. var normalizer net.Conn
  52. if useNormalizer {
  53. n := NewHTTPNormalizer(&conn)
  54. n.maxReqLineAndHeadersSize = tt.maxHeaderSize
  55. n.headerWriteOrder = tt.headerOrder
  56. n.prohibitedHeaders = tt.prohibitedHeaders
  57. n.validateMeekCookie = tt.validateMeekCookie
  58. if n.validateMeekCookie != nil {
  59. n.passthroughAddress = "127.0.0.1:0"
  60. n.passthroughDialer = func(network, address string) (net.Conn, error) {
  61. if network != "tcp" {
  62. return nil, errors.Tracef("expected network tcp but got \"%s\"", network)
  63. }
  64. if address != n.passthroughAddress {
  65. return nil, errors.Tracef("expected address \"%s\" but got \"%s\"", n.passthroughAddress, address)
  66. }
  67. return &passthroughConn, nil // return underlying conn
  68. }
  69. }
  70. normalizer = n
  71. } else {
  72. normalizer = &conn
  73. }
  74. defer normalizer.Close()
  75. remain := len(tt.wantOutput)
  76. var acc []byte
  77. var err error
  78. // Write input bytes to normalizer in chunks and then check
  79. // output.
  80. for {
  81. if remain <= 0 {
  82. break
  83. }
  84. b := make([]byte, tt.chunkSize)
  85. expectedErr := len(conn.readErrs) > 0
  86. var n int
  87. n, err = normalizer.Read(b)
  88. if err != nil && !expectedErr {
  89. // err checked outside loop
  90. break
  91. }
  92. if n > 0 {
  93. remain -= n
  94. acc = append(acc, b[:n]...)
  95. }
  96. }
  97. if tt.validateMeekCookie != nil && err == io.EOF {
  98. // wait for passthrough to complete
  99. timeout := time.After(time.Second)
  100. for len(passthroughConn.readBuffer) != 0 || len(conn.readBuffer) != 0 {
  101. select {
  102. case <-timeout:
  103. return errors.TraceNew("timed out waiting for passthrough to complete")
  104. case <-time.After(10 * time.Millisecond):
  105. }
  106. }
  107. // Subsequent reads should return EOF
  108. b := make([]byte, 1)
  109. _, err := normalizer.Read(b)
  110. if err != io.EOF {
  111. return errors.TraceNew("expected EOF")
  112. }
  113. // Subsequent writes should not impact conn or passthroughConn
  114. _, err = normalizer.Write([]byte("ignored"))
  115. if !stderrors.Is(err, ErrPassthroughActive) {
  116. return errors.Tracef("expected error io.EOF but got %v", err)
  117. }
  118. if string(acc) != "" {
  119. return errors.TraceNew("expected to read no bytes")
  120. }
  121. if string(passthroughConn.readBuffer) != "" {
  122. return errors.TraceNew("expected read buffer to be emptied")
  123. }
  124. if string(passthroughConn.writeBuffer) != tt.wantOutput {
  125. return errors.Tracef("expected \"%s\" of len %d but got \"%s\" of len %d", escapeNewlines(tt.wantOutput), len(tt.wantOutput), escapeNewlines(string(passthroughConn.writeBuffer)), len(passthroughConn.writeBuffer))
  126. }
  127. if string(conn.readBuffer) != "" {
  128. return errors.TraceNew("expected read buffer to be emptied")
  129. }
  130. if string(conn.writeBuffer) != passthroughMessage {
  131. return errors.Tracef("expected \"%s\" of len %d but got \"%s\" of len %d", escapeNewlines(passthroughMessage), len(passthroughMessage), escapeNewlines(string(conn.writeBuffer)), len(conn.writeBuffer))
  132. }
  133. }
  134. if tt.wantError == nil {
  135. if err != nil {
  136. return errors.TraceMsg(err, "unexpected error")
  137. }
  138. } else {
  139. // tt.wantError != nil
  140. if err == nil {
  141. return errors.Tracef("expected error %v", tt.wantError)
  142. } else if !strings.Contains(err.Error(), tt.wantError.Error()) {
  143. return errors.Tracef("expected error %v got %v", tt.wantError, err)
  144. }
  145. }
  146. if tt.wantError == nil && string(acc) != tt.wantOutput {
  147. return errors.Tracef("expected \"%s\" of len %d but got \"%s\" of len %d", escapeNewlines(tt.wantOutput), len(tt.wantOutput), escapeNewlines(string(acc)), len(acc))
  148. }
  149. return nil
  150. }
  151. func TestHTTPNormalizerHTTPRequest(t *testing.T) {
  152. tests := []httpNormalizerTest{
  153. {
  154. name: "no cookie in chunks",
  155. input: "POST / HTTP/1.1\r\nContent-Length: 4\r\n\r\nabcd",
  156. wantOutput: "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 4\r\n\r\nabcd",
  157. chunkSize: 1,
  158. },
  159. {
  160. name: "no cookie in single read",
  161. input: "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 4\r\n\r\nabcd",
  162. headerOrder: []string{"Host", "Content-Length"},
  163. wantOutput: "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 4\r\n\r\nabcd",
  164. chunkSize: 999,
  165. },
  166. {
  167. name: "no cookie, first read lands in body",
  168. input: "POST / HTTP/1.1\r\nContent-Length: 4\r\n\r\nabcd",
  169. headerOrder: []string{"Host", "Content-Length"},
  170. wantOutput: "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 4\r\n\r\nabcd",
  171. chunkSize: 40, // first read goes up to and including "b"
  172. },
  173. {
  174. name: "no cookie with spaces",
  175. input: "POST / HTTP/1.1\r\n Content-Length: 4 \r\n\r\nabcd",
  176. headerOrder: []string{"Host", "Content-Length"},
  177. wantOutput: "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 4\r\n\r\nabcd",
  178. chunkSize: 1,
  179. },
  180. {
  181. name: "cookie and range",
  182. input: "POST / HTTP/1.1\r\nContent-Length: 4\r\n Cookie: X\r\nRange: 1234 \r\n\r\nabcd",
  183. headerOrder: []string{"Host", "Content-Length", "Cookie", "Range"},
  184. wantOutput: "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 4\r\nCookie: X\r\nRange: 1234\r\n\r\nabcd",
  185. chunkSize: 1,
  186. },
  187. {
  188. name: "partial write and errors",
  189. input: "POST / HTTP/1.1\r\nContent-Length: 4\r\n\r\nabcd",
  190. headerOrder: []string{"Host", "Content-Length"},
  191. wantOutput: "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 4\r\n\r\nabcd",
  192. chunkSize: 1,
  193. connReadErrs: []error{stderrors.New("err1"), stderrors.New("err2")},
  194. },
  195. {
  196. name: "Content-Length missing",
  197. input: "POST / HTTP/1.1\r\n\r\nabcd",
  198. wantOutput: "POST / HTTP/1.1\r\n\r\nabcd", // set to ensure all bytes are read
  199. wantError: stderrors.New("Content-Length missing"),
  200. chunkSize: 1,
  201. },
  202. {
  203. name: "invalid Content-Length header value",
  204. input: "POST / HTTP/1.1\r\nContent-Length: X\r\n\r\nabcd",
  205. wantOutput: "POST / HTTP/1.1\r\nContent-Length: X\r\nHost: example.com\r\n\r\nabcd", // set to ensure all bytes are read
  206. wantError: stderrors.New("strconv.ParseUint: parsing \"X\": invalid syntax"),
  207. chunkSize: 1,
  208. },
  209. {
  210. name: "incorrect Content-Length header value",
  211. input: "POST / HTTP/1.1\r\nContent-Length: 3\r\n\r\nabcd",
  212. wantOutput: "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 3\r\n\r\nabc",
  213. chunkSize: 1,
  214. },
  215. {
  216. name: "single HTTP request written in a single write",
  217. input: "POST / HTTP/1.1\r\nRemoved: removed\r\nContent-Length: 4\r\n\r\nabcd",
  218. headerOrder: []string{"Host", "Content-Length"},
  219. wantOutput: "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 4\r\n\r\nabcd",
  220. chunkSize: 999,
  221. },
  222. {
  223. name: "multiple HTTP requests written in a single write",
  224. input: "POST / HTTP/1.1\r\nRemoved: removed\r\nContent-Length: 4\r\n\r\nabcdPOST / HTTP/1.1\r\nContent-Length: 2\r\n\r\n12",
  225. headerOrder: []string{"Host", "Content-Length"},
  226. wantOutput: "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 4\r\n\r\nabcdPOST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 2\r\n\r\n12",
  227. chunkSize: 999,
  228. },
  229. {
  230. name: "multiple HTTP requests written in chunks",
  231. input: "POST / HTTP/1.1\r\nRemoved: removed\r\nContent-Length: 4\r\n\r\nabcdPOST / HTTP/1.1\r\nContent-Length: 2\r\n\r\n12",
  232. headerOrder: []string{"Host", "Content-Length"},
  233. wantOutput: "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 4\r\n\r\nabcdPOST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 2\r\n\r\n12",
  234. chunkSize: 3,
  235. },
  236. {
  237. name: "multiple HTTP requests first read lands in middle of last request",
  238. input: "POST / HTTP/1.1\r\nRemoved: removed\r\nContent-Length: 4\r\n\r\nabcdPOST / HTTP/1.1\r\nContent-Length: 2\r\n\r\n12POST / HTTP/1.1\r\nContent-Length: 2\r\n\r\nxyx",
  239. headerOrder: []string{"Host", "Content-Length"},
  240. wantOutput: "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 4\r\n\r\nabcdPOST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 2\r\n\r\n12",
  241. chunkSize: 109,
  242. },
  243. {
  244. name: "longer",
  245. input: "POST / HTTP/1.1\r\nContent-Length: 4\r\n\r\nabcd",
  246. headerOrder: []string{"Host", "Content-Length"},
  247. wantOutput: "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 4\r\n\r\nabcd",
  248. chunkSize: 1,
  249. },
  250. {
  251. name: "shorter",
  252. input: "POST / HTTP/1.1111111111111111111\r\nContent-Length: 4\r\n\r\nabcd",
  253. headerOrder: []string{"Host", "Content-Length"},
  254. wantOutput: "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 4\r\n\r\nabcd",
  255. chunkSize: 1,
  256. },
  257. {
  258. name: "missing cookie",
  259. input: "POST / HTTP/1.1\r\nContent-Length: 4\r\n\r\nabcd",
  260. validateMeekCookie: func([]byte) ([]byte, error) {
  261. return nil, errors.TraceNew("invalid cookie")
  262. },
  263. wantOutput: "POST / HTTP/1.1\r\nContent-Length: 4\r\n\r\nabcd",
  264. chunkSize: 1,
  265. wantError: io.EOF,
  266. },
  267. {
  268. name: "invalid cookie",
  269. input: "POST / HTTP/1.1\r\nCookie: invalid\r\nContent-Length: 4\r\n\r\nabcd",
  270. validateMeekCookie: func([]byte) ([]byte, error) {
  271. return nil, errors.TraceNew("invalid cookie")
  272. },
  273. wantOutput: "POST / HTTP/1.1\r\nCookie: invalid\r\nContent-Length: 4\r\n\r\nabcd",
  274. chunkSize: 1,
  275. wantError: io.EOF,
  276. },
  277. {
  278. name: "valid cookie",
  279. input: "POST / HTTP/1.1\r\nHost: example.com\r\nCookie: valid\r\nContent-Length: 4\r\nRange: unused\r\nSkipped: skipped\r\n\r\nabcd",
  280. headerOrder: []string{"Host", "Cookie", "Content-Length", "Range"},
  281. validateMeekCookie: func([]byte) ([]byte, error) {
  282. return nil, nil
  283. },
  284. wantOutput: "POST / HTTP/1.1\r\nHost: example.com\r\nCookie: valid\r\nContent-Length: 4\r\nRange: unused\r\n\r\nabcd",
  285. chunkSize: 1,
  286. },
  287. {
  288. name: "exceeds max Request-Line, and headers, size",
  289. input: "POST / HTTP/1.1\r\nContent-Length: 4\r\nCookie: X\r\nRange: 1234 \r\n\r\nabcd",
  290. maxHeaderSize: 47, // up to end of Cookie header
  291. wantOutput: "POST / HTTP/1.1\r\nContent-Length: 4\r\nCookie: X\r\nRange: 1234 \r\n\r\nabcd",
  292. chunkSize: 1,
  293. wantError: stderrors.New("exceeds maxReqLineAndHeadersSize"),
  294. },
  295. }
  296. for _, tt := range tests {
  297. t.Run(tt.name, func(t *testing.T) {
  298. err := runHTTPNormalizerTest(&tt, true)
  299. if err != nil {
  300. t.Fatalf("runHTTPNormalizerTest failed: %v", err)
  301. }
  302. })
  303. }
  304. }
  305. // Caveats:
  306. // - Does not test or handle mutiple requests in a single connection
  307. // - Does not test the scenario where the first request in a connection
  308. // passes validation and then a subsequent request fails which triggers
  309. // a passthrough - in this scenario both the listener and passthrough
  310. // listener will receive bytes.
  311. func TestHTTPNormalizerHTTPServer(t *testing.T) {
  312. type test struct {
  313. name string
  314. request string
  315. maxHeaderSize int
  316. prohibitedHeaders []string
  317. wantPassthrough bool
  318. wantRecv string
  319. }
  320. tests := []test{
  321. {
  322. name: "valid cookie",
  323. request: "POST / HTTP/1.1\r\nCookie: valid\r\nContent-Length: 4\r\n\r\nabcd",
  324. wantRecv: "POST / HTTP/1.1\r\nHost: example.com\r\nCookie: valid\r\nContent-Length: 4\r\n\r\nabcd",
  325. },
  326. {
  327. name: "missing cookie",
  328. request: "POST HTTP/1.1\r\nContent-Length: 4\r\n\r\nabcd",
  329. wantPassthrough: true,
  330. wantRecv: "POST HTTP/1.1\r\nContent-Length: 4\r\n\r\nabcd",
  331. },
  332. {
  333. name: "invalid cookie",
  334. request: "POST HTTP/1.1\r\nCookie: invalid\r\nContent-Length: 4\r\n\r\nabcd",
  335. wantPassthrough: true,
  336. wantRecv: "POST HTTP/1.1\r\nCookie: invalid\r\nContent-Length: 4\r\n\r\nabcd",
  337. },
  338. {
  339. name: "valid cookie with prohibited headers",
  340. request: "POST / HTTP/1.1\r\nCookie: valid\r\nProhibited: prohibited\r\nContent-Length: 4\r\n\r\nabcd",
  341. prohibitedHeaders: []string{"Prohibited"},
  342. wantPassthrough: true,
  343. wantRecv: "POST / HTTP/1.1\r\nCookie: valid\r\nProhibited: prohibited\r\nContent-Length: 4\r\n\r\nabcd",
  344. },
  345. {
  346. name: "valid cookie but exceeds max header size",
  347. request: "POST / HTTP/1.1\r\nCookie: valid\r\nContent-Length: 4\r\n\r\nabcd",
  348. wantPassthrough: true,
  349. maxHeaderSize: 32, // up to end of Cookie header
  350. wantRecv: "POST / HTTP/1.1\r\nCookie: valid\r\nContent-Length: 4\r\n\r\nabcd",
  351. },
  352. }
  353. for _, tt := range tests {
  354. t.Run(tt.name, func(t *testing.T) {
  355. listener, err := net.Listen("tcp", "127.0.0.1:0")
  356. if err != nil {
  357. t.Fatalf("net.Listen failed %v", err)
  358. }
  359. defer listener.Close()
  360. passthrough, err := net.Listen("tcp", "127.0.0.1:0")
  361. if err != nil {
  362. t.Fatalf("net.Listen failed %v", err)
  363. }
  364. defer passthrough.Close()
  365. listener = WrapListenerWithHTTPNormalizer(listener)
  366. normalizer := listener.(*HTTPNormalizerListener)
  367. normalizer.PassthroughAddress = passthrough.Addr().String()
  368. normalizer.PassthroughDialer = net.Dial
  369. normalizer.MaxReqLineAndHeadersSize = tt.maxHeaderSize
  370. normalizer.ProhibitedHeaders = tt.prohibitedHeaders
  371. normalizer.PassthroughLogPassthrough = func(clientIP string, tunnelError error, logFields map[string]interface{}) {}
  372. validateMeekCookieResult := "payload"
  373. normalizer.ValidateMeekCookie = func(clientIP string, cookie []byte) ([]byte, error) {
  374. if string(cookie) == "valid" {
  375. return []byte(validateMeekCookieResult), nil
  376. }
  377. return nil, stderrors.New("invalid cookie")
  378. }
  379. normalizer.HeaderWriteOrder = []string{"Host", "Cookie", "Content-Length"}
  380. type listenerState struct {
  381. lType string // listener type, "listener" or "passthrough"
  382. err error
  383. recv []byte
  384. validateMeekCookieResult []byte // set if listener is "passthrough"
  385. }
  386. runListener := func(listener net.Listener, listenerType string, recv chan *listenerState) {
  387. conn, err := listener.Accept()
  388. if err != nil {
  389. recv <- &listenerState{
  390. lType: listenerType,
  391. err: errors.TraceMsg(err, "listener.Accept failed"),
  392. }
  393. return
  394. }
  395. defer conn.Close()
  396. b := make([]byte, len(tt.wantRecv))
  397. // A single Read should be sufficient because multiple requests
  398. // in a single connection are not supported by this test.
  399. n, err := conn.Read(b)
  400. if err != nil {
  401. recv <- &listenerState{
  402. lType: listenerType,
  403. err: errors.TraceMsg(err, "conn.Read failed"),
  404. }
  405. return
  406. }
  407. b = b[:n]
  408. var validateMeekCookieResult []byte
  409. if n, ok := conn.(*HTTPNormalizer); ok {
  410. validateMeekCookieResult = n.ValidateMeekCookieResult
  411. }
  412. _, err = conn.Write([]byte(listenerType))
  413. if err != nil {
  414. if stderrors.Is(err, ErrPassthroughActive) {
  415. return
  416. }
  417. recv <- &listenerState{
  418. lType: listenerType,
  419. err: errors.TraceMsg(err, "conn.Write failed"),
  420. validateMeekCookieResult: validateMeekCookieResult,
  421. }
  422. return
  423. }
  424. recv <- &listenerState{
  425. lType: listenerType,
  426. recv: b,
  427. err: nil,
  428. validateMeekCookieResult: validateMeekCookieResult,
  429. }
  430. }
  431. recv := make(chan *listenerState)
  432. listenerType := "listener"
  433. passthroughType := "passthrough"
  434. go runListener(listener, listenerType, recv)
  435. go runListener(passthrough, passthroughType, recv)
  436. conn, err := net.Dial("tcp", listener.Addr().String())
  437. if err != nil {
  438. t.Fatalf("net.Dial failed %v", err)
  439. }
  440. defer conn.Close()
  441. n, err := conn.Write([]byte(tt.request))
  442. if err != nil {
  443. t.Fatalf("conn.Write failed %v", err)
  444. }
  445. if n != len(tt.request) {
  446. t.Fatalf("expected to write %d bytes but wrote %d", len(tt.request), n)
  447. }
  448. // read response
  449. b := make([]byte, 512)
  450. n, err = conn.Read(b)
  451. if err != nil {
  452. t.Fatalf("conn.Read failed %v", err)
  453. }
  454. b = b[:n]
  455. if tt.wantPassthrough && string(b) != passthroughType {
  456. t.Fatalf("expected passthrough but got response from listener")
  457. } else if !tt.wantPassthrough && string(b) != listenerType {
  458. t.Fatalf("expected no passthrough but got response from passthrough")
  459. }
  460. r := <-recv
  461. if r.err != nil {
  462. t.Fatalf("listener failed %v", r.err)
  463. }
  464. if !bytes.Equal(r.recv, []byte(tt.wantRecv)) {
  465. t.Fatalf("expected \"%s\" of len %d but got \"%s\" of len %d", escapeNewlines(string(tt.wantRecv)), len(tt.wantRecv), escapeNewlines(string(r.recv)), len(r.recv))
  466. }
  467. if r.lType != "passthrough" && string(r.validateMeekCookieResult) != validateMeekCookieResult {
  468. t.Fatalf("expected validateMeekCookieResult value \"%s\" but got \"%s\"", validateMeekCookieResult, string(r.validateMeekCookieResult))
  469. }
  470. // Check that other listener did not get a connection
  471. n, err = conn.Read(b)
  472. if err != nil && err != io.EOF {
  473. t.Fatalf("conn.Read failed %v", err)
  474. }
  475. if n != 0 {
  476. t.Fatalf("expected to read 0 bytes")
  477. }
  478. select {
  479. case r := <-recv:
  480. t.Fatalf("unexpected response from %s: %v \"%s\"", r.lType, r.err, string(r.recv))
  481. case <-time.After(10 * time.Millisecond):
  482. }
  483. })
  484. }
  485. }
  486. func BenchmarkHTTPNormalizer(b *testing.B) {
  487. inReq := "POST / HTTP/1.1\r\nContent-Length: 400\r\n\r\nxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
  488. outReq := "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 400\r\n\r\nxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
  489. input := ""
  490. output := ""
  491. // Concatenate many requests to simulate a single connection running over
  492. // the normalizer.
  493. for i := 0; i < 100; i++ {
  494. input += inReq
  495. output += outReq
  496. }
  497. // TODO: test different chunk sizes
  498. test := &httpNormalizerTest{
  499. name: "no cookie in chunks",
  500. input: input,
  501. wantOutput: output,
  502. chunkSize: 1,
  503. }
  504. for n := 0; n < b.N; n++ {
  505. // TODO: does test setup and teardown in runHTTPNormalizerTest skew
  506. // the benchmark
  507. err := runHTTPNormalizerTest(test, true)
  508. if err != nil {
  509. b.Fatalf("runHTTPNormalizerTest failed: %v", err)
  510. }
  511. }
  512. }