1package nghttp2 2 3import ( 4 "bufio" 5 "bytes" 6 "crypto/tls" 7 "encoding/binary" 8 "errors" 9 "fmt" 10 "github.com/tatsuhiro-t/go-nghttp2" 11 "golang.org/x/net/http2" 12 "golang.org/x/net/http2/hpack" 13 "golang.org/x/net/websocket" 14 "io" 15 "io/ioutil" 16 "net" 17 "net/http" 18 "net/http/httptest" 19 "net/url" 20 "os" 21 "os/exec" 22 "sort" 23 "strconv" 24 "strings" 25 "syscall" 26 "testing" 27 "time" 28) 29 30const ( 31 serverBin = buildDir + "/src/nghttpx" 32 serverPort = 3009 33 testDir = sourceDir + "/integration-tests" 34 logDir = buildDir + "/integration-tests" 35) 36 37func pair(name, value string) hpack.HeaderField { 38 return hpack.HeaderField{ 39 Name: name, 40 Value: value, 41 } 42} 43 44type serverTester struct { 45 args []string // command-line arguments 46 cmd *exec.Cmd // test frontend server process, which is test subject 47 url string // test frontend server URL 48 t *testing.T 49 ts *httptest.Server // backend server 50 frontendHost string // frontend server host 51 backendHost string // backend server host 52 conn net.Conn // connection to frontend server 53 h2PrefaceSent bool // HTTP/2 preface was sent in conn 54 nextStreamID uint32 // next stream ID 55 fr *http2.Framer // HTTP/2 framer 56 headerBlkBuf bytes.Buffer // buffer to store encoded header block 57 enc *hpack.Encoder // HTTP/2 HPACK encoder 58 header http.Header // received header fields 59 dec *hpack.Decoder // HTTP/2 HPACK decoder 60 authority string // server's host:port 61 frCh chan http2.Frame // used for incoming HTTP/2 frame 62 errCh chan error 63} 64 65// newServerTester creates test context for plain TCP frontend 66// connection. 67func newServerTester(args []string, t *testing.T, handler http.HandlerFunc) *serverTester { 68 return newServerTesterInternal(args, t, handler, false, serverPort, nil) 69} 70 71func newServerTesterConnectPort(args []string, t *testing.T, handler http.HandlerFunc, port int) *serverTester { 72 return newServerTesterInternal(args, t, handler, false, port, nil) 73} 74 75func newServerTesterHandler(args []string, t *testing.T, handler http.Handler) *serverTester { 76 return newServerTesterInternal(args, t, handler, false, serverPort, nil) 77} 78 79// newServerTester creates test context for TLS frontend connection. 80func newServerTesterTLS(args []string, t *testing.T, handler http.HandlerFunc) *serverTester { 81 return newServerTesterInternal(args, t, handler, true, serverPort, nil) 82} 83 84func newServerTesterTLSConnectPort(args []string, t *testing.T, handler http.HandlerFunc, port int) *serverTester { 85 return newServerTesterInternal(args, t, handler, true, port, nil) 86} 87 88// newServerTester creates test context for TLS frontend connection 89// with given clientConfig 90func newServerTesterTLSConfig(args []string, t *testing.T, handler http.HandlerFunc, clientConfig *tls.Config) *serverTester { 91 return newServerTesterInternal(args, t, handler, true, serverPort, clientConfig) 92} 93 94// newServerTesterInternal creates test context. If frontendTLS is 95// true, set up TLS frontend connection. connectPort is the server 96// side port where client connection is made. 97func newServerTesterInternal(src_args []string, t *testing.T, handler http.Handler, frontendTLS bool, connectPort int, clientConfig *tls.Config) *serverTester { 98 ts := httptest.NewUnstartedServer(handler) 99 100 args := []string{} 101 102 var backendTLS, dns, externalDNS, acceptProxyProtocol, redirectIfNotTLS, affinityCookie, alpnH1 bool 103 104 for _, k := range src_args { 105 switch k { 106 case "--http2-bridge": 107 backendTLS = true 108 case "--dns": 109 dns = true 110 case "--external-dns": 111 dns = true 112 externalDNS = true 113 case "--accept-proxy-protocol": 114 acceptProxyProtocol = true 115 case "--redirect-if-not-tls": 116 redirectIfNotTLS = true 117 case "--affinity-cookie": 118 affinityCookie = true 119 case "--alpn-h1": 120 alpnH1 = true 121 default: 122 args = append(args, k) 123 } 124 } 125 if backendTLS { 126 nghttp2.ConfigureServer(ts.Config, &nghttp2.Server{}) 127 // According to httptest/server.go, we have to set 128 // NextProtos separately for ts.TLS. NextProtos set 129 // in nghttp2.ConfigureServer is effectively ignored. 130 ts.TLS = new(tls.Config) 131 ts.TLS.NextProtos = append(ts.TLS.NextProtos, "h2") 132 ts.StartTLS() 133 args = append(args, "-k") 134 } else { 135 ts.Start() 136 } 137 scheme := "http" 138 if frontendTLS { 139 scheme = "https" 140 args = append(args, testDir+"/server.key", testDir+"/server.crt") 141 } 142 143 backendURL, err := url.Parse(ts.URL) 144 if err != nil { 145 t.Fatalf("Error parsing URL from httptest.Server: %v", err) 146 } 147 148 // URL.Host looks like "127.0.0.1:8080", but we want 149 // "127.0.0.1,8080" 150 b := "-b" 151 if !externalDNS { 152 b += fmt.Sprintf("%v;", strings.Replace(backendURL.Host, ":", ",", -1)) 153 } else { 154 sep := strings.LastIndex(backendURL.Host, ":") 155 if sep == -1 { 156 t.Fatalf("backendURL.Host %v does not contain separator ':'", backendURL.Host) 157 } 158 // We use awesome service nip.io. 159 b += fmt.Sprintf("%v.nip.io,%v;", backendURL.Host[:sep], backendURL.Host[sep+1:]) 160 } 161 162 if backendTLS { 163 b += ";proto=h2;tls" 164 } 165 if dns { 166 b += ";dns" 167 } 168 169 if redirectIfNotTLS { 170 b += ";redirect-if-not-tls" 171 } 172 173 if affinityCookie { 174 b += ";affinity=cookie;affinity-cookie-name=affinity;affinity-cookie-path=/foo/bar" 175 } 176 177 noTLS := ";no-tls" 178 if frontendTLS { 179 noTLS = "" 180 } 181 182 var proxyProto string 183 if acceptProxyProtocol { 184 proxyProto = ";proxyproto" 185 } 186 187 args = append(args, fmt.Sprintf("-f127.0.0.1,%v%v%v", serverPort, noTLS, proxyProto), b, 188 "--errorlog-file="+logDir+"/log.txt", "-LINFO") 189 190 authority := fmt.Sprintf("127.0.0.1:%v", connectPort) 191 192 st := &serverTester{ 193 cmd: exec.Command(serverBin, args...), 194 t: t, 195 ts: ts, 196 url: fmt.Sprintf("%v://%v", scheme, authority), 197 frontendHost: fmt.Sprintf("127.0.0.1:%v", serverPort), 198 backendHost: backendURL.Host, 199 nextStreamID: 1, 200 authority: authority, 201 frCh: make(chan http2.Frame), 202 errCh: make(chan error), 203 } 204 205 st.cmd.Stdout = os.Stdout 206 st.cmd.Stderr = os.Stderr 207 208 if err := st.cmd.Start(); err != nil { 209 st.t.Fatalf("Error starting %v: %v", serverBin, err) 210 } 211 212 retry := 0 213 for { 214 time.Sleep(50 * time.Millisecond) 215 216 var conn net.Conn 217 var err error 218 if frontendTLS { 219 var tlsConfig *tls.Config 220 if clientConfig == nil { 221 tlsConfig = new(tls.Config) 222 } else { 223 tlsConfig = clientConfig 224 } 225 tlsConfig.InsecureSkipVerify = true 226 if alpnH1 { 227 tlsConfig.NextProtos = []string{"http/1.1"} 228 } else { 229 tlsConfig.NextProtos = []string{"h2"} 230 } 231 conn, err = tls.Dial("tcp", authority, tlsConfig) 232 } else { 233 conn, err = net.Dial("tcp", authority) 234 } 235 if err != nil { 236 retry += 1 237 if retry >= 100 { 238 st.Close() 239 st.t.Fatalf("Error server is not responding too long; server command-line arguments may be invalid") 240 } 241 continue 242 } 243 if frontendTLS { 244 tlsConn := conn.(*tls.Conn) 245 cs := tlsConn.ConnectionState() 246 if !cs.NegotiatedProtocolIsMutual { 247 st.Close() 248 st.t.Fatalf("Error negotiated next protocol is not mutual") 249 } 250 } 251 st.conn = conn 252 break 253 } 254 255 st.fr = http2.NewFramer(st.conn, st.conn) 256 st.enc = hpack.NewEncoder(&st.headerBlkBuf) 257 st.dec = hpack.NewDecoder(4096, func(f hpack.HeaderField) { 258 st.header.Add(f.Name, f.Value) 259 }) 260 261 return st 262} 263 264func (st *serverTester) Close() { 265 if st.conn != nil { 266 st.conn.Close() 267 } 268 if st.cmd != nil { 269 done := make(chan struct{}) 270 go func() { 271 st.cmd.Wait() 272 close(done) 273 }() 274 275 st.cmd.Process.Signal(syscall.SIGQUIT) 276 277 select { 278 case <-done: 279 case <-time.After(10 * time.Second): 280 st.cmd.Process.Kill() 281 <-done 282 } 283 } 284 if st.ts != nil { 285 st.ts.Close() 286 } 287} 288 289func (st *serverTester) readFrame() (http2.Frame, error) { 290 go func() { 291 f, err := st.fr.ReadFrame() 292 if err != nil { 293 st.errCh <- err 294 return 295 } 296 st.frCh <- f 297 }() 298 299 select { 300 case f := <-st.frCh: 301 return f, nil 302 case err := <-st.errCh: 303 return nil, err 304 case <-time.After(5 * time.Second): 305 return nil, errors.New("timeout waiting for frame") 306 } 307} 308 309type requestParam struct { 310 name string // name for this request to identify the request in log easily 311 streamID uint32 // stream ID, automatically assigned if 0 312 method string // method, defaults to GET 313 scheme string // scheme, defaults to http 314 authority string // authority, defaults to backend server address 315 path string // path, defaults to / 316 header []hpack.HeaderField // additional request header fields 317 body []byte // request body 318 trailer []hpack.HeaderField // trailer part 319 httpUpgrade bool // true if upgraded to HTTP/2 through HTTP Upgrade 320 noEndStream bool // true if END_STREAM should not be sent 321} 322 323// wrapper for request body to set trailer part 324type chunkedBodyReader struct { 325 trailer []hpack.HeaderField 326 trailerWritten bool 327 body io.Reader 328 req *http.Request 329} 330 331func (cbr *chunkedBodyReader) Read(p []byte) (n int, err error) { 332 // document says that we have to set http.Request.Trailer 333 // after request was sent and before body returns EOF. 334 if !cbr.trailerWritten { 335 cbr.trailerWritten = true 336 for _, h := range cbr.trailer { 337 cbr.req.Trailer.Set(h.Name, h.Value) 338 } 339 } 340 return cbr.body.Read(p) 341} 342 343func (st *serverTester) websocket(rp requestParam) (*serverResponse, error) { 344 urlstring := st.url + "/echo" 345 346 config, err := websocket.NewConfig(urlstring, st.url) 347 if err != nil { 348 st.t.Fatalf("websocket.NewConfig(%q, %q) returned error: %v", urlstring, st.url, err) 349 } 350 351 config.Header.Add("Test-Case", rp.name) 352 for _, h := range rp.header { 353 config.Header.Add(h.Name, h.Value) 354 } 355 356 ws, err := websocket.NewClient(config, st.conn) 357 if err != nil { 358 st.t.Fatalf("Error creating websocket client: %v", err) 359 } 360 361 if _, err := ws.Write(rp.body); err != nil { 362 st.t.Fatalf("ws.Write() returned error: %v", err) 363 } 364 365 msg := make([]byte, 1024) 366 var n int 367 if n, err = ws.Read(msg); err != nil { 368 st.t.Fatalf("ws.Read() returned error: %v", err) 369 } 370 371 res := &serverResponse{ 372 body: msg[:n], 373 } 374 375 return res, nil 376} 377 378func (st *serverTester) http1(rp requestParam) (*serverResponse, error) { 379 method := "GET" 380 if rp.method != "" { 381 method = rp.method 382 } 383 384 var body io.Reader 385 var cbr *chunkedBodyReader 386 if rp.body != nil { 387 body = bytes.NewBuffer(rp.body) 388 if len(rp.trailer) != 0 { 389 cbr = &chunkedBodyReader{ 390 trailer: rp.trailer, 391 body: body, 392 } 393 body = cbr 394 } 395 } 396 397 reqURL := st.url 398 399 if rp.path != "" { 400 u, err := url.Parse(st.url) 401 if err != nil { 402 st.t.Fatalf("Error parsing URL from st.url %v: %v", st.url, err) 403 } 404 u.Path = "" 405 u.RawQuery = "" 406 reqURL = u.String() + rp.path 407 } 408 409 req, err := http.NewRequest(method, reqURL, body) 410 if err != nil { 411 return nil, err 412 } 413 for _, h := range rp.header { 414 req.Header.Add(h.Name, h.Value) 415 } 416 req.Header.Add("Test-Case", rp.name) 417 if cbr != nil { 418 cbr.req = req 419 // this makes request use chunked encoding 420 req.ContentLength = -1 421 req.Trailer = make(http.Header) 422 for _, h := range cbr.trailer { 423 req.Trailer.Set(h.Name, "") 424 } 425 } 426 if err := req.Write(st.conn); err != nil { 427 return nil, err 428 } 429 resp, err := http.ReadResponse(bufio.NewReader(st.conn), req) 430 if err != nil { 431 return nil, err 432 } 433 respBody, err := ioutil.ReadAll(resp.Body) 434 if err != nil { 435 return nil, err 436 } 437 resp.Body.Close() 438 439 res := &serverResponse{ 440 status: resp.StatusCode, 441 header: resp.Header, 442 body: respBody, 443 connClose: resp.Close, 444 } 445 446 return res, nil 447} 448 449func (st *serverTester) http2(rp requestParam) (*serverResponse, error) { 450 st.headerBlkBuf.Reset() 451 st.header = make(http.Header) 452 453 var id uint32 454 if rp.streamID != 0 { 455 id = rp.streamID 456 if id >= st.nextStreamID && id%2 == 1 { 457 st.nextStreamID = id + 2 458 } 459 } else { 460 id = st.nextStreamID 461 st.nextStreamID += 2 462 } 463 464 if !st.h2PrefaceSent { 465 st.h2PrefaceSent = true 466 fmt.Fprint(st.conn, "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n") 467 if err := st.fr.WriteSettings(); err != nil { 468 return nil, err 469 } 470 } 471 472 res := &serverResponse{ 473 streamID: id, 474 } 475 476 streams := make(map[uint32]*serverResponse) 477 streams[id] = res 478 479 if !rp.httpUpgrade { 480 method := "GET" 481 if rp.method != "" { 482 method = rp.method 483 } 484 _ = st.enc.WriteField(pair(":method", method)) 485 486 scheme := "http" 487 if rp.scheme != "" { 488 scheme = rp.scheme 489 } 490 _ = st.enc.WriteField(pair(":scheme", scheme)) 491 492 authority := st.authority 493 if rp.authority != "" { 494 authority = rp.authority 495 } 496 _ = st.enc.WriteField(pair(":authority", authority)) 497 498 path := "/" 499 if rp.path != "" { 500 path = rp.path 501 } 502 _ = st.enc.WriteField(pair(":path", path)) 503 504 _ = st.enc.WriteField(pair("test-case", rp.name)) 505 506 for _, h := range rp.header { 507 _ = st.enc.WriteField(h) 508 } 509 510 err := st.fr.WriteHeaders(http2.HeadersFrameParam{ 511 StreamID: id, 512 EndStream: len(rp.body) == 0 && len(rp.trailer) == 0 && !rp.noEndStream, 513 EndHeaders: true, 514 BlockFragment: st.headerBlkBuf.Bytes(), 515 }) 516 if err != nil { 517 return nil, err 518 } 519 520 if len(rp.body) != 0 { 521 // TODO we assume rp.body fits in 1 frame 522 if err := st.fr.WriteData(id, len(rp.trailer) == 0 && !rp.noEndStream, rp.body); err != nil { 523 return nil, err 524 } 525 } 526 527 if len(rp.trailer) != 0 { 528 st.headerBlkBuf.Reset() 529 for _, h := range rp.trailer { 530 _ = st.enc.WriteField(h) 531 } 532 err := st.fr.WriteHeaders(http2.HeadersFrameParam{ 533 StreamID: id, 534 EndStream: true, 535 EndHeaders: true, 536 BlockFragment: st.headerBlkBuf.Bytes(), 537 }) 538 if err != nil { 539 return nil, err 540 } 541 } 542 } 543loop: 544 for { 545 fr, err := st.readFrame() 546 if err != nil { 547 return res, err 548 } 549 switch f := fr.(type) { 550 case *http2.HeadersFrame: 551 _, err := st.dec.Write(f.HeaderBlockFragment()) 552 if err != nil { 553 return res, err 554 } 555 sr, ok := streams[f.FrameHeader.StreamID] 556 if !ok { 557 st.header = make(http.Header) 558 break 559 } 560 sr.header = cloneHeader(st.header) 561 var status int 562 status, err = strconv.Atoi(sr.header.Get(":status")) 563 if err != nil { 564 return res, fmt.Errorf("Error parsing status code: %v", err) 565 } 566 sr.status = status 567 if f.StreamEnded() { 568 if streamEnded(res, streams, sr) { 569 break loop 570 } 571 } 572 case *http2.PushPromiseFrame: 573 _, err := st.dec.Write(f.HeaderBlockFragment()) 574 if err != nil { 575 return res, err 576 } 577 sr := &serverResponse{ 578 streamID: f.PromiseID, 579 reqHeader: cloneHeader(st.header), 580 } 581 streams[sr.streamID] = sr 582 case *http2.DataFrame: 583 sr, ok := streams[f.FrameHeader.StreamID] 584 if !ok { 585 break 586 } 587 sr.body = append(sr.body, f.Data()...) 588 if f.StreamEnded() { 589 if streamEnded(res, streams, sr) { 590 break loop 591 } 592 } 593 case *http2.RSTStreamFrame: 594 sr, ok := streams[f.FrameHeader.StreamID] 595 if !ok { 596 break 597 } 598 sr.errCode = f.ErrCode 599 if streamEnded(res, streams, sr) { 600 break loop 601 } 602 case *http2.GoAwayFrame: 603 if f.ErrCode == http2.ErrCodeNo { 604 break 605 } 606 res.errCode = f.ErrCode 607 res.connErr = true 608 break loop 609 case *http2.SettingsFrame: 610 if f.IsAck() { 611 break 612 } 613 if err := st.fr.WriteSettingsAck(); err != nil { 614 return res, err 615 } 616 } 617 } 618 sort.Sort(ByStreamID(res.pushResponse)) 619 return res, nil 620} 621 622func streamEnded(mainSr *serverResponse, streams map[uint32]*serverResponse, sr *serverResponse) bool { 623 delete(streams, sr.streamID) 624 if mainSr.streamID != sr.streamID { 625 mainSr.pushResponse = append(mainSr.pushResponse, sr) 626 } 627 return len(streams) == 0 628} 629 630type serverResponse struct { 631 status int // HTTP status code 632 header http.Header // response header fields 633 body []byte // response body 634 streamID uint32 // stream ID in HTTP/2 635 errCode http2.ErrCode // error code received in HTTP/2 RST_STREAM or GOAWAY 636 connErr bool // true if HTTP/2 connection error 637 connClose bool // Connection: close is included in response header in HTTP/1 test 638 reqHeader http.Header // http request header, currently only sotres pushed request header 639 pushResponse []*serverResponse // pushed response 640} 641 642type ByStreamID []*serverResponse 643 644func (b ByStreamID) Len() int { 645 return len(b) 646} 647 648func (b ByStreamID) Swap(i, j int) { 649 b[i], b[j] = b[j], b[i] 650} 651 652func (b ByStreamID) Less(i, j int) bool { 653 return b[i].streamID < b[j].streamID 654} 655 656func cloneHeader(h http.Header) http.Header { 657 h2 := make(http.Header, len(h)) 658 for k, vv := range h { 659 vv2 := make([]string, len(vv)) 660 copy(vv2, vv) 661 h2[k] = vv2 662 } 663 return h2 664} 665 666func noopHandler(w http.ResponseWriter, r *http.Request) { 667 ioutil.ReadAll(r.Body) 668} 669 670type APIResponse struct { 671 Status string `json:"status,omitempty"` 672 Code int `json:"code,omitempty"` 673 Data map[string]interface{} `json:"data,omitempty"` 674} 675 676type proxyProtocolV2 struct { 677 command proxyProtocolV2Command 678 sourceAddress net.Addr 679 destinationAddress net.Addr 680 additionalData []byte 681} 682 683type proxyProtocolV2Command int 684 685const ( 686 proxyProtocolV2CommandLocal proxyProtocolV2Command = 0x0 687 proxyProtocolV2CommandProxy proxyProtocolV2Command = 0x1 688) 689 690type proxyProtocolV2Family int 691 692const ( 693 proxyProtocolV2FamilyUnspec proxyProtocolV2Family = 0x0 694 proxyProtocolV2FamilyInet proxyProtocolV2Family = 0x1 695 proxyProtocolV2FamilyInet6 proxyProtocolV2Family = 0x2 696 proxyProtocolV2FamilyUnix proxyProtocolV2Family = 0x3 697) 698 699type proxyProtocolV2Protocol int 700 701const ( 702 proxyProtocolV2ProtocolUnspec proxyProtocolV2Protocol = 0x0 703 proxyProtocolV2ProtocolStream proxyProtocolV2Protocol = 0x1 704 proxyProtocolV2ProtocolDgram proxyProtocolV2Protocol = 0x2 705) 706 707func writeProxyProtocolV2(w io.Writer, hdr proxyProtocolV2) { 708 w.Write([]byte{0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A}) 709 w.Write([]byte{byte(0x20 | hdr.command)}) 710 711 switch srcAddr := hdr.sourceAddress.(type) { 712 case *net.TCPAddr: 713 dstAddr := hdr.destinationAddress.(*net.TCPAddr) 714 if len(srcAddr.IP) != len(dstAddr.IP) { 715 panic("len(srcAddr.IP) != len(dstAddr.IP)") 716 } 717 var fam byte 718 if len(srcAddr.IP) == 4 { 719 fam = byte(proxyProtocolV2FamilyInet << 4) 720 } else { 721 fam = byte(proxyProtocolV2FamilyInet6 << 4) 722 } 723 fam |= byte(proxyProtocolV2ProtocolStream) 724 w.Write([]byte{fam}) 725 length := uint16(len(srcAddr.IP)*2 + 4 + len(hdr.additionalData)) 726 binary.Write(w, binary.BigEndian, length) 727 w.Write(srcAddr.IP) 728 w.Write(dstAddr.IP) 729 binary.Write(w, binary.BigEndian, uint16(srcAddr.Port)) 730 binary.Write(w, binary.BigEndian, uint16(dstAddr.Port)) 731 case *net.UnixAddr: 732 dstAddr := hdr.destinationAddress.(*net.UnixAddr) 733 if len(srcAddr.Name) > 108 { 734 panic("too long Unix source address") 735 } 736 if len(dstAddr.Name) > 108 { 737 panic("too long Unix destination address") 738 } 739 fam := byte(proxyProtocolV2FamilyUnix << 4) 740 switch srcAddr.Net { 741 case "unix": 742 fam |= byte(proxyProtocolV2ProtocolStream) 743 case "unixdgram": 744 fam |= byte(proxyProtocolV2ProtocolDgram) 745 default: 746 fam |= byte(proxyProtocolV2ProtocolUnspec) 747 } 748 w.Write([]byte{fam}) 749 length := uint16(216 + len(hdr.additionalData)) 750 binary.Write(w, binary.BigEndian, length) 751 zeros := make([]byte, 108) 752 w.Write([]byte(srcAddr.Name)) 753 w.Write(zeros[:108-len(srcAddr.Name)]) 754 w.Write([]byte(dstAddr.Name)) 755 w.Write(zeros[:108-len(dstAddr.Name)]) 756 default: 757 fam := byte(proxyProtocolV2FamilyUnspec<<4) | byte(proxyProtocolV2ProtocolUnspec) 758 w.Write([]byte{fam}) 759 length := uint16(len(hdr.additionalData)) 760 binary.Write(w, binary.BigEndian, length) 761 } 762 763 w.Write(hdr.additionalData) 764} 765