| // Copyright 2011 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // Tests for transport.go |
| |
| package http_test |
| |
| import ( |
| "bytes" |
| "compress/gzip" |
| "crypto/rand" |
| "fmt" |
| "io" |
| "io/ioutil" |
| . "net/http" |
| "net/http/httptest" |
| "net/url" |
| "strconv" |
| "strings" |
| "testing" |
| "time" |
| ) |
| |
| // TODO: test 5 pipelined requests with responses: 1) OK, 2) OK, Connection: Close |
| // and then verify that the final 2 responses get errors back. |
| |
| // hostPortHandler writes back the client's "host:port". |
| var hostPortHandler = HandlerFunc(func(w ResponseWriter, r *Request) { |
| if r.FormValue("close") == "true" { |
| w.Header().Set("Connection", "close") |
| } |
| w.Write([]byte(r.RemoteAddr)) |
| }) |
| |
| // Two subsequent requests and verify their response is the same. |
| // The response from the server is our own IP:port |
| func TestTransportKeepAlives(t *testing.T) { |
| ts := httptest.NewServer(hostPortHandler) |
| defer ts.Close() |
| |
| for _, disableKeepAlive := range []bool{false, true} { |
| tr := &Transport{DisableKeepAlives: disableKeepAlive} |
| c := &Client{Transport: tr} |
| |
| fetch := func(n int) string { |
| res, err := c.Get(ts.URL) |
| if err != nil { |
| t.Fatalf("error in disableKeepAlive=%v, req #%d, GET: %v", disableKeepAlive, n, err) |
| } |
| body, err := ioutil.ReadAll(res.Body) |
| if err != nil { |
| t.Fatalf("error in disableKeepAlive=%v, req #%d, ReadAll: %v", disableKeepAlive, n, err) |
| } |
| return string(body) |
| } |
| |
| body1 := fetch(1) |
| body2 := fetch(2) |
| |
| bodiesDiffer := body1 != body2 |
| if bodiesDiffer != disableKeepAlive { |
| t.Errorf("error in disableKeepAlive=%v. unexpected bodiesDiffer=%v; body1=%q; body2=%q", |
| disableKeepAlive, bodiesDiffer, body1, body2) |
| } |
| } |
| } |
| |
| func TestTransportConnectionCloseOnResponse(t *testing.T) { |
| ts := httptest.NewServer(hostPortHandler) |
| defer ts.Close() |
| |
| for _, connectionClose := range []bool{false, true} { |
| tr := &Transport{} |
| c := &Client{Transport: tr} |
| |
| fetch := func(n int) string { |
| req := new(Request) |
| var err error |
| req.URL, err = url.Parse(ts.URL + fmt.Sprintf("/?close=%v", connectionClose)) |
| if err != nil { |
| t.Fatalf("URL parse error: %v", err) |
| } |
| req.Method = "GET" |
| req.Proto = "HTTP/1.1" |
| req.ProtoMajor = 1 |
| req.ProtoMinor = 1 |
| |
| res, err := c.Do(req) |
| if err != nil { |
| t.Fatalf("error in connectionClose=%v, req #%d, Do: %v", connectionClose, n, err) |
| } |
| body, err := ioutil.ReadAll(res.Body) |
| defer res.Body.Close() |
| if err != nil { |
| t.Fatalf("error in connectionClose=%v, req #%d, ReadAll: %v", connectionClose, n, err) |
| } |
| return string(body) |
| } |
| |
| body1 := fetch(1) |
| body2 := fetch(2) |
| bodiesDiffer := body1 != body2 |
| if bodiesDiffer != connectionClose { |
| t.Errorf("error in connectionClose=%v. unexpected bodiesDiffer=%v; body1=%q; body2=%q", |
| connectionClose, bodiesDiffer, body1, body2) |
| } |
| } |
| } |
| |
| func TestTransportConnectionCloseOnRequest(t *testing.T) { |
| ts := httptest.NewServer(hostPortHandler) |
| defer ts.Close() |
| |
| for _, connectionClose := range []bool{false, true} { |
| tr := &Transport{} |
| c := &Client{Transport: tr} |
| |
| fetch := func(n int) string { |
| req := new(Request) |
| var err error |
| req.URL, err = url.Parse(ts.URL) |
| if err != nil { |
| t.Fatalf("URL parse error: %v", err) |
| } |
| req.Method = "GET" |
| req.Proto = "HTTP/1.1" |
| req.ProtoMajor = 1 |
| req.ProtoMinor = 1 |
| req.Close = connectionClose |
| |
| res, err := c.Do(req) |
| if err != nil { |
| t.Fatalf("error in connectionClose=%v, req #%d, Do: %v", connectionClose, n, err) |
| } |
| body, err := ioutil.ReadAll(res.Body) |
| if err != nil { |
| t.Fatalf("error in connectionClose=%v, req #%d, ReadAll: %v", connectionClose, n, err) |
| } |
| return string(body) |
| } |
| |
| body1 := fetch(1) |
| body2 := fetch(2) |
| bodiesDiffer := body1 != body2 |
| if bodiesDiffer != connectionClose { |
| t.Errorf("error in connectionClose=%v. unexpected bodiesDiffer=%v; body1=%q; body2=%q", |
| connectionClose, bodiesDiffer, body1, body2) |
| } |
| } |
| } |
| |
| func TestTransportIdleCacheKeys(t *testing.T) { |
| ts := httptest.NewServer(hostPortHandler) |
| defer ts.Close() |
| |
| tr := &Transport{DisableKeepAlives: false} |
| c := &Client{Transport: tr} |
| |
| if e, g := 0, len(tr.IdleConnKeysForTesting()); e != g { |
| t.Errorf("After CloseIdleConnections expected %d idle conn cache keys; got %d", e, g) |
| } |
| |
| resp, err := c.Get(ts.URL) |
| if err != nil { |
| t.Error(err) |
| } |
| ioutil.ReadAll(resp.Body) |
| |
| keys := tr.IdleConnKeysForTesting() |
| if e, g := 1, len(keys); e != g { |
| t.Fatalf("After Get expected %d idle conn cache keys; got %d", e, g) |
| } |
| |
| if e := "|http|" + ts.Listener.Addr().String(); keys[0] != e { |
| t.Errorf("Expected idle cache key %q; got %q", e, keys[0]) |
| } |
| |
| tr.CloseIdleConnections() |
| if e, g := 0, len(tr.IdleConnKeysForTesting()); e != g { |
| t.Errorf("After CloseIdleConnections expected %d idle conn cache keys; got %d", e, g) |
| } |
| } |
| |
| func TestTransportMaxPerHostIdleConns(t *testing.T) { |
| resch := make(chan string) |
| gotReq := make(chan bool) |
| ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { |
| gotReq <- true |
| msg := <-resch |
| _, err := w.Write([]byte(msg)) |
| if err != nil { |
| t.Fatalf("Write: %v", err) |
| } |
| })) |
| defer ts.Close() |
| maxIdleConns := 2 |
| tr := &Transport{DisableKeepAlives: false, MaxIdleConnsPerHost: maxIdleConns} |
| c := &Client{Transport: tr} |
| |
| // Start 3 outstanding requests and wait for the server to get them. |
| // Their responses will hang until we we write to resch, though. |
| donech := make(chan bool) |
| doReq := func() { |
| resp, err := c.Get(ts.URL) |
| if err != nil { |
| t.Error(err) |
| } |
| _, err = ioutil.ReadAll(resp.Body) |
| if err != nil { |
| t.Fatalf("ReadAll: %v", err) |
| } |
| donech <- true |
| } |
| go doReq() |
| <-gotReq |
| go doReq() |
| <-gotReq |
| go doReq() |
| <-gotReq |
| |
| if e, g := 0, len(tr.IdleConnKeysForTesting()); e != g { |
| t.Fatalf("Before writes, expected %d idle conn cache keys; got %d", e, g) |
| } |
| |
| resch <- "res1" |
| <-donech |
| keys := tr.IdleConnKeysForTesting() |
| if e, g := 1, len(keys); e != g { |
| t.Fatalf("after first response, expected %d idle conn cache keys; got %d", e, g) |
| } |
| cacheKey := "|http|" + ts.Listener.Addr().String() |
| if keys[0] != cacheKey { |
| t.Fatalf("Expected idle cache key %q; got %q", cacheKey, keys[0]) |
| } |
| if e, g := 1, tr.IdleConnCountForTesting(cacheKey); e != g { |
| t.Errorf("after first response, expected %d idle conns; got %d", e, g) |
| } |
| |
| resch <- "res2" |
| <-donech |
| if e, g := 2, tr.IdleConnCountForTesting(cacheKey); e != g { |
| t.Errorf("after second response, expected %d idle conns; got %d", e, g) |
| } |
| |
| resch <- "res3" |
| <-donech |
| if e, g := maxIdleConns, tr.IdleConnCountForTesting(cacheKey); e != g { |
| t.Errorf("after third response, still expected %d idle conns; got %d", e, g) |
| } |
| } |
| |
| func TestTransportServerClosingUnexpectedly(t *testing.T) { |
| ts := httptest.NewServer(hostPortHandler) |
| defer ts.Close() |
| |
| tr := &Transport{} |
| c := &Client{Transport: tr} |
| |
| fetch := func(n, retries int) string { |
| condFatalf := func(format string, arg ...interface{}) { |
| if retries <= 0 { |
| t.Fatalf(format, arg...) |
| } |
| t.Logf("retrying shortly after expected error: "+format, arg...) |
| time.Sleep(time.Second / time.Duration(retries)) |
| } |
| for retries >= 0 { |
| retries-- |
| res, err := c.Get(ts.URL) |
| if err != nil { |
| condFatalf("error in req #%d, GET: %v", n, err) |
| continue |
| } |
| body, err := ioutil.ReadAll(res.Body) |
| if err != nil { |
| condFatalf("error in req #%d, ReadAll: %v", n, err) |
| continue |
| } |
| res.Body.Close() |
| return string(body) |
| } |
| panic("unreachable") |
| } |
| |
| body1 := fetch(1, 0) |
| body2 := fetch(2, 0) |
| |
| ts.CloseClientConnections() // surprise! |
| |
| // This test has an expected race. Sleeping for 25 ms prevents |
| // it on most fast machines, causing the next fetch() call to |
| // succeed quickly. But if we do get errors, fetch() will retry 5 |
| // times with some delays between. |
| time.Sleep(25 * time.Millisecond) |
| |
| body3 := fetch(3, 5) |
| |
| if body1 != body2 { |
| t.Errorf("expected body1 and body2 to be equal") |
| } |
| if body2 == body3 { |
| t.Errorf("expected body2 and body3 to be different") |
| } |
| } |
| |
| // Test for http://golang.org/issue/2616 (appropriate issue number) |
| // This fails pretty reliably with GOMAXPROCS=100 or something high. |
| func TestStressSurpriseServerCloses(t *testing.T) { |
| if testing.Short() { |
| t.Logf("skipping test in short mode") |
| return |
| } |
| ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { |
| w.Header().Set("Content-Length", "5") |
| w.Header().Set("Content-Type", "text/plain") |
| w.Write([]byte("Hello")) |
| w.(Flusher).Flush() |
| conn, buf, _ := w.(Hijacker).Hijack() |
| buf.Flush() |
| conn.Close() |
| })) |
| defer ts.Close() |
| |
| tr := &Transport{DisableKeepAlives: false} |
| c := &Client{Transport: tr} |
| |
| // Do a bunch of traffic from different goroutines. Send to activityc |
| // after each request completes, regardless of whether it failed. |
| const ( |
| numClients = 50 |
| reqsPerClient = 250 |
| ) |
| activityc := make(chan bool) |
| for i := 0; i < numClients; i++ { |
| go func() { |
| for i := 0; i < reqsPerClient; i++ { |
| res, err := c.Get(ts.URL) |
| if err == nil { |
| // We expect errors since the server is |
| // hanging up on us after telling us to |
| // send more requests, so we don't |
| // actually care what the error is. |
| // But we want to close the body in cases |
| // where we won the race. |
| res.Body.Close() |
| } |
| activityc <- true |
| } |
| }() |
| } |
| |
| // Make sure all the request come back, one way or another. |
| for i := 0; i < numClients*reqsPerClient; i++ { |
| select { |
| case <-activityc: |
| case <-time.After(5 * time.Second): |
| t.Fatalf("presumed deadlock; no HTTP client activity seen in awhile") |
| } |
| } |
| } |
| |
| // TestTransportHeadResponses verifies that we deal with Content-Lengths |
| // with no bodies properly |
| func TestTransportHeadResponses(t *testing.T) { |
| ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { |
| if r.Method != "HEAD" { |
| panic("expected HEAD; got " + r.Method) |
| } |
| w.Header().Set("Content-Length", "123") |
| w.WriteHeader(200) |
| })) |
| defer ts.Close() |
| |
| tr := &Transport{DisableKeepAlives: false} |
| c := &Client{Transport: tr} |
| for i := 0; i < 2; i++ { |
| res, err := c.Head(ts.URL) |
| if err != nil { |
| t.Errorf("error on loop %d: %v", i, err) |
| } |
| if e, g := "123", res.Header.Get("Content-Length"); e != g { |
| t.Errorf("loop %d: expected Content-Length header of %q, got %q", i, e, g) |
| } |
| if e, g := int64(0), res.ContentLength; e != g { |
| t.Errorf("loop %d: expected res.ContentLength of %v, got %v", i, e, g) |
| } |
| } |
| } |
| |
| // TestTransportHeadChunkedResponse verifies that we ignore chunked transfer-encoding |
| // on responses to HEAD requests. |
| func TestTransportHeadChunkedResponse(t *testing.T) { |
| ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { |
| if r.Method != "HEAD" { |
| panic("expected HEAD; got " + r.Method) |
| } |
| w.Header().Set("Transfer-Encoding", "chunked") // client should ignore |
| w.Header().Set("x-client-ipport", r.RemoteAddr) |
| w.WriteHeader(200) |
| })) |
| defer ts.Close() |
| |
| tr := &Transport{DisableKeepAlives: false} |
| c := &Client{Transport: tr} |
| |
| res1, err := c.Head(ts.URL) |
| if err != nil { |
| t.Fatalf("request 1 error: %v", err) |
| } |
| res2, err := c.Head(ts.URL) |
| if err != nil { |
| t.Fatalf("request 2 error: %v", err) |
| } |
| if v1, v2 := res1.Header.Get("x-client-ipport"), res2.Header.Get("x-client-ipport"); v1 != v2 { |
| t.Errorf("ip/ports differed between head requests: %q vs %q", v1, v2) |
| } |
| } |
| |
| var roundTripTests = []struct { |
| accept string |
| expectAccept string |
| compressed bool |
| }{ |
| // Requests with no accept-encoding header use transparent compression |
| {"", "gzip", false}, |
| // Requests with other accept-encoding should pass through unmodified |
| {"foo", "foo", false}, |
| // Requests with accept-encoding == gzip should be passed through |
| {"gzip", "gzip", true}, |
| } |
| |
| // Test that the modification made to the Request by the RoundTripper is cleaned up |
| func TestRoundTripGzip(t *testing.T) { |
| const responseBody = "test response body" |
| ts := httptest.NewServer(HandlerFunc(func(rw ResponseWriter, req *Request) { |
| accept := req.Header.Get("Accept-Encoding") |
| if expect := req.FormValue("expect_accept"); accept != expect { |
| t.Errorf("in handler, test %v: Accept-Encoding = %q, want %q", |
| req.FormValue("testnum"), accept, expect) |
| } |
| if accept == "gzip" { |
| rw.Header().Set("Content-Encoding", "gzip") |
| gz, _ := gzip.NewWriter(rw) |
| gz.Write([]byte(responseBody)) |
| gz.Close() |
| } else { |
| rw.Header().Set("Content-Encoding", accept) |
| rw.Write([]byte(responseBody)) |
| } |
| })) |
| defer ts.Close() |
| |
| for i, test := range roundTripTests { |
| // Test basic request (no accept-encoding) |
| req, _ := NewRequest("GET", fmt.Sprintf("%s/?testnum=%d&expect_accept=%s", ts.URL, i, test.expectAccept), nil) |
| if test.accept != "" { |
| req.Header.Set("Accept-Encoding", test.accept) |
| } |
| res, err := DefaultTransport.RoundTrip(req) |
| var body []byte |
| if test.compressed { |
| gzip, _ := gzip.NewReader(res.Body) |
| body, err = ioutil.ReadAll(gzip) |
| res.Body.Close() |
| } else { |
| body, err = ioutil.ReadAll(res.Body) |
| } |
| if err != nil { |
| t.Errorf("%d. Error: %q", i, err) |
| continue |
| } |
| if g, e := string(body), responseBody; g != e { |
| t.Errorf("%d. body = %q; want %q", i, g, e) |
| } |
| if g, e := req.Header.Get("Accept-Encoding"), test.accept; g != e { |
| t.Errorf("%d. Accept-Encoding = %q; want %q (it was mutated, in violation of RoundTrip contract)", i, g, e) |
| } |
| if g, e := res.Header.Get("Content-Encoding"), test.accept; g != e { |
| t.Errorf("%d. Content-Encoding = %q; want %q", i, g, e) |
| } |
| } |
| |
| } |
| |
| func TestTransportGzip(t *testing.T) { |
| const testString = "The test string aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" |
| const nRandBytes = 1024 * 1024 |
| ts := httptest.NewServer(HandlerFunc(func(rw ResponseWriter, req *Request) { |
| if g, e := req.Header.Get("Accept-Encoding"), "gzip"; g != e { |
| t.Errorf("Accept-Encoding = %q, want %q", g, e) |
| } |
| rw.Header().Set("Content-Encoding", "gzip") |
| if req.Method == "HEAD" { |
| return |
| } |
| |
| var w io.Writer = rw |
| var buf bytes.Buffer |
| if req.FormValue("chunked") == "0" { |
| w = &buf |
| defer io.Copy(rw, &buf) |
| defer func() { |
| rw.Header().Set("Content-Length", strconv.Itoa(buf.Len())) |
| }() |
| } |
| gz, _ := gzip.NewWriter(w) |
| gz.Write([]byte(testString)) |
| if req.FormValue("body") == "large" { |
| io.CopyN(gz, rand.Reader, nRandBytes) |
| } |
| gz.Close() |
| })) |
| defer ts.Close() |
| |
| for _, chunked := range []string{"1", "0"} { |
| c := &Client{Transport: &Transport{}} |
| |
| // First fetch something large, but only read some of it. |
| res, err := c.Get(ts.URL + "/?body=large&chunked=" + chunked) |
| if err != nil { |
| t.Fatalf("large get: %v", err) |
| } |
| buf := make([]byte, len(testString)) |
| n, err := io.ReadFull(res.Body, buf) |
| if err != nil { |
| t.Fatalf("partial read of large response: size=%d, %v", n, err) |
| } |
| if e, g := testString, string(buf); e != g { |
| t.Errorf("partial read got %q, expected %q", g, e) |
| } |
| res.Body.Close() |
| // Read on the body, even though it's closed |
| n, err = res.Body.Read(buf) |
| if n != 0 || err == nil { |
| t.Errorf("expected error post-closed large Read; got = %d, %v", n, err) |
| } |
| |
| // Then something small. |
| res, err = c.Get(ts.URL + "/?chunked=" + chunked) |
| if err != nil { |
| t.Fatal(err) |
| } |
| body, err := ioutil.ReadAll(res.Body) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if g, e := string(body), testString; g != e { |
| t.Fatalf("body = %q; want %q", g, e) |
| } |
| if g, e := res.Header.Get("Content-Encoding"), ""; g != e { |
| t.Fatalf("Content-Encoding = %q; want %q", g, e) |
| } |
| |
| // Read on the body after it's been fully read: |
| n, err = res.Body.Read(buf) |
| if n != 0 || err == nil { |
| t.Errorf("expected Read error after exhausted reads; got %d, %v", n, err) |
| } |
| res.Body.Close() |
| n, err = res.Body.Read(buf) |
| if n != 0 || err == nil { |
| t.Errorf("expected Read error after Close; got %d, %v", n, err) |
| } |
| } |
| |
| // And a HEAD request too, because they're always weird. |
| c := &Client{Transport: &Transport{}} |
| res, err := c.Head(ts.URL) |
| if err != nil { |
| t.Fatalf("Head: %v", err) |
| } |
| if res.StatusCode != 200 { |
| t.Errorf("Head status=%d; want=200", res.StatusCode) |
| } |
| } |
| |
| func TestTransportProxy(t *testing.T) { |
| ch := make(chan string, 1) |
| ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { |
| ch <- "real server" |
| })) |
| defer ts.Close() |
| proxy := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { |
| ch <- "proxy for " + r.URL.String() |
| })) |
| defer proxy.Close() |
| |
| pu, err := url.Parse(proxy.URL) |
| if err != nil { |
| t.Fatal(err) |
| } |
| c := &Client{Transport: &Transport{Proxy: ProxyURL(pu)}} |
| c.Head(ts.URL) |
| got := <-ch |
| want := "proxy for " + ts.URL + "/" |
| if got != want { |
| t.Errorf("want %q, got %q", want, got) |
| } |
| } |
| |
| // TestTransportGzipRecursive sends a gzip quine and checks that the |
| // client gets the same value back. This is more cute than anything, |
| // but checks that we don't recurse forever, and checks that |
| // Content-Encoding is removed. |
| func TestTransportGzipRecursive(t *testing.T) { |
| ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { |
| w.Header().Set("Content-Encoding", "gzip") |
| w.Write(rgz) |
| })) |
| defer ts.Close() |
| |
| c := &Client{Transport: &Transport{}} |
| res, err := c.Get(ts.URL) |
| if err != nil { |
| t.Fatal(err) |
| } |
| body, err := ioutil.ReadAll(res.Body) |
| if err != nil { |
| t.Fatal(err) |
| } |
| if !bytes.Equal(body, rgz) { |
| t.Fatalf("Incorrect result from recursive gz:\nhave=%x\nwant=%x", |
| body, rgz) |
| } |
| if g, e := res.Header.Get("Content-Encoding"), ""; g != e { |
| t.Fatalf("Content-Encoding = %q; want %q", g, e) |
| } |
| } |
| |
| type fooProto struct{} |
| |
| func (fooProto) RoundTrip(req *Request) (*Response, error) { |
| res := &Response{ |
| Status: "200 OK", |
| StatusCode: 200, |
| Header: make(Header), |
| Body: ioutil.NopCloser(strings.NewReader("You wanted " + req.URL.String())), |
| } |
| return res, nil |
| } |
| |
| func TestTransportAltProto(t *testing.T) { |
| tr := &Transport{} |
| c := &Client{Transport: tr} |
| tr.RegisterProtocol("foo", fooProto{}) |
| res, err := c.Get("foo://bar.com/path") |
| if err != nil { |
| t.Fatal(err) |
| } |
| bodyb, err := ioutil.ReadAll(res.Body) |
| if err != nil { |
| t.Fatal(err) |
| } |
| body := string(bodyb) |
| if e := "You wanted foo://bar.com/path"; body != e { |
| t.Errorf("got response %q, want %q", body, e) |
| } |
| } |
| |
| // rgz is a gzip quine that uncompresses to itself. |
| var rgz = []byte{ |
| 0x1f, 0x8b, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x72, 0x65, 0x63, 0x75, 0x72, 0x73, |
| 0x69, 0x76, 0x65, 0x00, 0x92, 0xef, 0xe6, 0xe0, |
| 0x60, 0x00, 0x83, 0xa2, 0xd4, 0xe4, 0xd2, 0xa2, |
| 0xe2, 0xcc, 0xb2, 0x54, 0x06, 0x00, 0x00, 0x17, |
| 0x00, 0xe8, 0xff, 0x92, 0xef, 0xe6, 0xe0, 0x60, |
| 0x00, 0x83, 0xa2, 0xd4, 0xe4, 0xd2, 0xa2, 0xe2, |
| 0xcc, 0xb2, 0x54, 0x06, 0x00, 0x00, 0x17, 0x00, |
| 0xe8, 0xff, 0x42, 0x12, 0x46, 0x16, 0x06, 0x00, |
| 0x05, 0x00, 0xfa, 0xff, 0x42, 0x12, 0x46, 0x16, |
| 0x06, 0x00, 0x05, 0x00, 0xfa, 0xff, 0x00, 0x05, |
| 0x00, 0xfa, 0xff, 0x00, 0x14, 0x00, 0xeb, 0xff, |
| 0x42, 0x12, 0x46, 0x16, 0x06, 0x00, 0x05, 0x00, |
| 0xfa, 0xff, 0x00, 0x05, 0x00, 0xfa, 0xff, 0x00, |
| 0x14, 0x00, 0xeb, 0xff, 0x42, 0x88, 0x21, 0xc4, |
| 0x00, 0x00, 0x14, 0x00, 0xeb, 0xff, 0x42, 0x88, |
| 0x21, 0xc4, 0x00, 0x00, 0x14, 0x00, 0xeb, 0xff, |
| 0x42, 0x88, 0x21, 0xc4, 0x00, 0x00, 0x14, 0x00, |
| 0xeb, 0xff, 0x42, 0x88, 0x21, 0xc4, 0x00, 0x00, |
| 0x14, 0x00, 0xeb, 0xff, 0x42, 0x88, 0x21, 0xc4, |
| 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, |
| 0x00, 0xff, 0xff, 0x00, 0x17, 0x00, 0xe8, 0xff, |
| 0x42, 0x88, 0x21, 0xc4, 0x00, 0x00, 0x00, 0x00, |
| 0xff, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, |
| 0x17, 0x00, 0xe8, 0xff, 0x42, 0x12, 0x46, 0x16, |
| 0x06, 0x00, 0x00, 0x00, 0xff, 0xff, 0x01, 0x08, |
| 0x00, 0xf7, 0xff, 0x3d, 0xb1, 0x20, 0x85, 0xfa, |
| 0x00, 0x00, 0x00, 0x42, 0x12, 0x46, 0x16, 0x06, |
| 0x00, 0x00, 0x00, 0xff, 0xff, 0x01, 0x08, 0x00, |
| 0xf7, 0xff, 0x3d, 0xb1, 0x20, 0x85, 0xfa, 0x00, |
| 0x00, 0x00, 0x3d, 0xb1, 0x20, 0x85, 0xfa, 0x00, |
| 0x00, 0x00, |
| } |