github.com/ezoic/ws@v1.0.4-0.20220713205711-5c1d69e074c5/dialer_test.go (about) 1 package ws 2 3 import ( 4 "bufio" 5 "bytes" 6 "context" 7 "encoding/base64" 8 "fmt" 9 "io" 10 "io/ioutil" 11 "math/rand" 12 "net" 13 "net/http" 14 "net/url" 15 "testing" 16 "time" 17 18 "github.com/ezoic/httphead" 19 ) 20 21 func TestDialerRequest(t *testing.T) { 22 for _, test := range []struct { 23 dialer Dialer 24 url string 25 exp *http.Request 26 err bool 27 }{ 28 { 29 url: "wss://example.org/chat", 30 exp: setProto(1, 1, 31 mustMakeRequest("GET", "wss://example.org/chat", http.Header{ 32 headerUpgrade: []string{"websocket"}, 33 headerConnection: []string{"Upgrade"}, 34 headerSecVersion: []string{"13"}, 35 headerSecKey: []string{"some key"}, 36 }), 37 ), 38 }, 39 { 40 dialer: Dialer{ 41 Protocols: []string{"foo", "bar"}, 42 Extensions: []httphead.Option{ 43 httphead.NewOption("foo", map[string]string{ 44 "bar": "1", 45 }), 46 httphead.NewOption("baz", nil), 47 }, 48 Header: HandshakeHeaderHTTP(http.Header{ 49 "Origin": []string{"who knows"}, 50 }), 51 }, 52 url: "wss://example.org/chat", 53 exp: setProto(1, 1, 54 mustMakeRequest("GET", "wss://example.org/chat", http.Header{ 55 headerUpgrade: []string{"websocket"}, 56 headerConnection: []string{"Upgrade"}, 57 headerSecVersion: []string{"13"}, 58 headerSecKey: []string{"some key"}, 59 60 headerSecProtocol: []string{"foo, bar"}, 61 headerSecExtensions: []string{"foo;bar=1,baz"}, 62 63 "Origin": []string{"who knows"}, 64 }), 65 ), 66 }, 67 } { 68 t.Run("", func(t *testing.T) { 69 u, err := url.ParseRequestURI(test.url) 70 if err != nil { 71 t.Fatal(err) 72 } 73 74 var buf bytes.Buffer 75 conn := struct { 76 io.Reader 77 io.Writer 78 }{io.LimitReader(&buf, 0), &buf} 79 80 _, _, err = test.dialer.Upgrade(&conn, u) 81 if err == io.EOF { 82 err = nil 83 } 84 if test.err && err == nil { 85 t.Errorf("expected error; got nil") 86 } 87 if !test.err && err != nil { 88 t.Errorf("unexpected error: %s", err) 89 } 90 if test.err { 91 return 92 } 93 94 act := buf.Bytes() 95 exp := dumpRequest(test.exp) 96 97 act = sortHeaders(maskHeader(act, headerSecKey, "<masked>")) 98 exp = sortHeaders(maskHeader(exp, headerSecKey, "<masked>")) 99 100 if !bytes.Equal(act, exp) { 101 t.Errorf("unexpected request:\nact:\n%s\nexp:\n%s\n", act, exp) 102 } 103 if _, err := http.ReadRequest(bufio.NewReader(&buf)); err != nil { 104 t.Errorf("read request error: %s", err) 105 return 106 } 107 }) 108 } 109 } 110 111 func makeAccept(nonce []byte) []byte { 112 accept := make([]byte, acceptSize) 113 initAcceptFromNonce(accept, nonce) 114 return accept 115 } 116 117 func BenchmarkPutAccept(b *testing.B) { 118 nonce := make([]byte, nonceSize) 119 _, err := rand.Read(nonce[:]) 120 if err != nil { 121 b.Fatal(err) 122 } 123 p := make([]byte, acceptSize) 124 b.StartTimer() 125 for i := 0; i < b.N; i++ { 126 initAcceptFromNonce(p, nonce) 127 } 128 } 129 130 func BenchmarkCheckNonce(b *testing.B) { 131 nonce := make([]byte, nonceSize) 132 _, err := rand.Read(nonce[:]) 133 if err != nil { 134 b.Fatal(err) 135 } 136 137 accept := makeAccept(nonce) 138 139 b.ResetTimer() 140 for i := 0; i < b.N; i++ { 141 _ = checkAcceptFromNonce(nonce, accept) 142 } 143 } 144 145 func TestDialerHandshake(t *testing.T) { 146 const ( 147 acceptNo = iota 148 acceptInvalid 149 acceptValid 150 ) 151 for _, test := range []struct { 152 name string 153 dialer Dialer 154 res *http.Response 155 frames []Frame 156 accept int 157 err error 158 wantBuffer bool 159 }{ 160 { 161 res: &http.Response{ 162 StatusCode: 101, 163 ProtoMajor: 1, 164 ProtoMinor: 1, 165 Header: http.Header{ 166 headerConnection: []string{"Upgrade"}, 167 headerUpgrade: []string{"websocket"}, 168 }, 169 }, 170 accept: acceptValid, 171 }, 172 { 173 dialer: Dialer{ 174 Protocols: []string{"xml", "json", "soap"}, 175 }, 176 res: &http.Response{ 177 StatusCode: 101, 178 ProtoMajor: 1, 179 ProtoMinor: 1, 180 Header: http.Header{ 181 headerConnection: []string{"Upgrade"}, 182 headerUpgrade: []string{"websocket"}, 183 headerSecProtocol: []string{"json"}, 184 }, 185 }, 186 accept: acceptValid, 187 }, 188 { 189 dialer: Dialer{ 190 Protocols: []string{"xml", "json", "soap"}, 191 }, 192 res: &http.Response{ 193 StatusCode: 101, 194 ProtoMajor: 1, 195 ProtoMinor: 1, 196 Header: http.Header{ 197 headerConnection: []string{"Upgrade"}, 198 headerUpgrade: []string{"websocket"}, 199 }, 200 }, 201 accept: acceptValid, 202 }, 203 { 204 dialer: Dialer{ 205 Extensions: []httphead.Option{ 206 httphead.NewOption("foo", map[string]string{ 207 "bar": "1", 208 }), 209 httphead.NewOption("baz", nil), 210 }, 211 }, 212 res: &http.Response{ 213 StatusCode: 101, 214 ProtoMajor: 1, 215 ProtoMinor: 1, 216 Header: http.Header{ 217 headerConnection: []string{"Upgrade"}, 218 headerUpgrade: []string{"websocket"}, 219 headerSecExtensions: []string{"foo;bar=1"}, 220 }, 221 }, 222 accept: acceptValid, 223 }, 224 { 225 dialer: Dialer{ 226 Extensions: []httphead.Option{ 227 httphead.NewOption("foo", map[string]string{ 228 "bar": "1", 229 }), 230 httphead.NewOption("baz", nil), 231 }, 232 }, 233 res: &http.Response{ 234 StatusCode: 101, 235 ProtoMajor: 1, 236 ProtoMinor: 1, 237 Header: http.Header{ 238 headerConnection: []string{"Upgrade"}, 239 headerUpgrade: []string{"websocket"}, 240 }, 241 }, 242 accept: acceptValid, 243 }, 244 { 245 dialer: Dialer{ 246 Protocols: []string{"xml", "json", "soap"}, 247 Extensions: []httphead.Option{ 248 httphead.NewOption("foo", map[string]string{ 249 "bar": "1", 250 }), 251 httphead.NewOption("baz", nil), 252 }, 253 }, 254 res: &http.Response{ 255 StatusCode: 101, 256 ProtoMajor: 1, 257 ProtoMinor: 1, 258 Header: http.Header{ 259 headerConnection: []string{"Upgrade"}, 260 headerUpgrade: []string{"websocket"}, 261 headerSecProtocol: []string{"json"}, 262 headerSecExtensions: []string{"foo;bar=1"}, 263 }, 264 }, 265 accept: acceptValid, 266 }, 267 { 268 name: "resp with frames", 269 res: &http.Response{ 270 StatusCode: 101, 271 ProtoMajor: 1, 272 ProtoMinor: 1, 273 Header: http.Header{ 274 headerConnection: []string{"Upgrade"}, 275 headerUpgrade: []string{"websocket"}, 276 }, 277 }, 278 accept: acceptValid, 279 frames: []Frame{ 280 NewTextFrame([]byte("hello, gopherizer!")), 281 }, 282 wantBuffer: true, 283 }, 284 { 285 name: "resp with body", 286 res: &http.Response{ 287 StatusCode: 101, 288 ProtoMajor: 1, 289 ProtoMinor: 1, 290 Header: http.Header{ 291 headerConnection: []string{"Upgrade"}, 292 headerUpgrade: []string{"websocket"}, 293 }, 294 Body: ioutil.NopCloser(bytes.NewReader([]byte(`hello, gopher!`))), 295 ContentLength: 14, 296 }, 297 accept: acceptValid, 298 wantBuffer: true, 299 }, 300 301 // Error cases. 302 303 { 304 name: "bad proto", 305 res: &http.Response{ 306 StatusCode: 101, 307 ProtoMajor: 2, 308 ProtoMinor: 1, 309 Header: make(http.Header), 310 }, 311 err: ErrHandshakeBadProtocol, 312 }, 313 { 314 name: "bad status", 315 res: &http.Response{ 316 StatusCode: 400, 317 ProtoMajor: 1, 318 ProtoMinor: 1, 319 Header: make(http.Header), 320 }, 321 err: StatusError(400), 322 wantBuffer: false, 323 }, 324 { 325 name: "bad status with body", 326 res: &http.Response{ 327 StatusCode: 400, 328 ProtoMajor: 1, 329 ProtoMinor: 1, 330 Header: make(http.Header), 331 Body: ioutil.NopCloser(bytes.NewReader( 332 []byte(`<error description here>`), 333 )), 334 ContentLength: 24, 335 }, 336 err: StatusError(400), 337 wantBuffer: false, 338 }, 339 { 340 name: "bad upgrade", 341 res: &http.Response{ 342 StatusCode: 101, 343 ProtoMajor: 1, 344 ProtoMinor: 1, 345 Header: http.Header{ 346 headerConnection: []string{"Upgrade"}, 347 }, 348 }, 349 accept: acceptValid, 350 err: ErrHandshakeBadUpgrade, 351 }, 352 { 353 name: "bad upgrade", 354 res: &http.Response{ 355 StatusCode: 101, 356 ProtoMajor: 1, 357 ProtoMinor: 1, 358 Header: http.Header{ 359 headerConnection: []string{"Upgrade"}, 360 headerUpgrade: []string{"oops"}, 361 }, 362 }, 363 accept: acceptValid, 364 err: ErrHandshakeBadUpgrade, 365 }, 366 { 367 name: "bad connection", 368 res: &http.Response{ 369 StatusCode: 101, 370 ProtoMajor: 1, 371 ProtoMinor: 1, 372 Header: http.Header{ 373 headerUpgrade: []string{"websocket"}, 374 }, 375 }, 376 accept: acceptValid, 377 err: ErrHandshakeBadConnection, 378 }, 379 { 380 name: "bad connection", 381 res: &http.Response{ 382 StatusCode: 101, 383 ProtoMajor: 1, 384 ProtoMinor: 1, 385 Header: http.Header{ 386 headerConnection: []string{"oops!"}, 387 headerUpgrade: []string{"websocket"}, 388 }, 389 }, 390 accept: acceptValid, 391 err: ErrHandshakeBadConnection, 392 }, 393 { 394 name: "bad accept", 395 res: &http.Response{ 396 StatusCode: 101, 397 ProtoMajor: 1, 398 ProtoMinor: 1, 399 Header: http.Header{ 400 headerConnection: []string{"Upgrade"}, 401 headerUpgrade: []string{"websocket"}, 402 }, 403 }, 404 accept: acceptInvalid, 405 err: ErrHandshakeBadSecAccept, 406 }, 407 { 408 name: "bad accept", 409 res: &http.Response{ 410 StatusCode: 101, 411 ProtoMajor: 1, 412 ProtoMinor: 1, 413 Header: http.Header{ 414 headerConnection: []string{"Upgrade"}, 415 headerUpgrade: []string{"websocket"}, 416 }, 417 }, 418 accept: acceptNo, 419 err: ErrHandshakeBadSecAccept, 420 }, 421 { 422 name: "bad subprotocol", 423 res: &http.Response{ 424 StatusCode: 101, 425 ProtoMajor: 1, 426 ProtoMinor: 1, 427 Header: http.Header{ 428 headerConnection: []string{"Upgrade"}, 429 headerUpgrade: []string{"websocket"}, 430 headerSecProtocol: []string{"oops!"}, 431 }, 432 }, 433 accept: acceptValid, 434 err: ErrHandshakeBadSubProtocol, 435 }, 436 { 437 name: "bad extensions", 438 res: &http.Response{ 439 StatusCode: 101, 440 ProtoMajor: 1, 441 ProtoMinor: 1, 442 Header: http.Header{ 443 headerConnection: []string{"Upgrade"}, 444 headerUpgrade: []string{"websocket"}, 445 headerSecExtensions: []string{"foo,bar;baz=1"}, 446 }, 447 }, 448 accept: acceptValid, 449 err: ErrHandshakeBadExtensions, 450 }, 451 { 452 name: "bad extensions", 453 dialer: Dialer{ 454 Extensions: []httphead.Option{ 455 httphead.NewOption("foo", map[string]string{ 456 "bar": "1", 457 }), 458 }, 459 }, 460 res: &http.Response{ 461 StatusCode: 101, 462 ProtoMajor: 1, 463 ProtoMinor: 1, 464 Header: http.Header{ 465 headerConnection: []string{"Upgrade"}, 466 headerUpgrade: []string{"websocket"}, 467 headerSecExtensions: []string{"foo;bar=2"}, 468 }, 469 }, 470 accept: acceptValid, 471 err: ErrHandshakeBadExtensions, 472 }, 473 } { 474 t.Run(test.name, func(t *testing.T) { 475 client, server := net.Pipe() 476 go func() { 477 // This routine is our fake web-server. It reads request after 478 // client wrote it. Then it optionally could send some frames 479 // set in test case. 480 req, err := http.ReadRequest(bufio.NewReader(client)) 481 if err != nil { 482 t.Fatal(err) 483 } 484 485 switch test.accept { 486 case acceptInvalid: 487 k := make([]byte, nonceSize) 488 rand.Read(k) 489 nonce := string(k) 490 accept := makeAccept(strToBytes(nonce)) 491 test.res.Header.Set(headerSecAccept, string(accept)) 492 case acceptValid: 493 nonce := req.Header.Get(headerSecKey) 494 accept := makeAccept(strToBytes(nonce)) 495 test.res.Header.Set(headerSecAccept, string(accept)) 496 } 497 498 test.res.Request = req 499 bts := dumpResponse(test.res) 500 501 var buf bytes.Buffer 502 for _, f := range test.frames { 503 if err := WriteFrame(&buf, f); err != nil { 504 t.Fatal(err) 505 } 506 bts = append(bts, buf.Bytes()...) 507 buf.Reset() 508 } 509 510 client.Write(bts) 511 client.Close() 512 }() 513 514 conn := &stubConn{ 515 read: func(p []byte) (int, error) { 516 return server.Read(p) 517 }, 518 write: func(p []byte) (int, error) { 519 n, err := server.Write(p) 520 return n, err 521 }, 522 close: func() error { return nil }, 523 } 524 525 test.dialer.NetDial = func(_ context.Context, _, _ string) (net.Conn, error) { 526 return conn, nil 527 } 528 test.dialer.OnStatusError = func(status int, reason []byte, r io.Reader) { 529 res, err := http.ReadResponse( 530 bufio.NewReader(r), 531 nil, 532 ) 533 if err != nil { 534 t.Errorf("read response inside OnStatusError error: %v", err) 535 } 536 if act, exp := dumpResponse(res), dumpResponse(test.res); !bytes.Equal(act, exp) { 537 t.Errorf( 538 "unexpected response from OnStatusError:\nact:\n%s\nexp:\n%s\n", 539 act, exp, 540 ) 541 } 542 } 543 544 _, br, _, err := test.dialer.Dial(context.Background(), "ws://ezoic.com") 545 if test.err != err { 546 t.Fatalf("unexpected error: %v;\n\twant %v", err, test.err) 547 } 548 549 if (test.wantBuffer || len(test.frames) > 0) && br == nil { 550 t.Fatalf("Dial() returned empty bufio.Reader") 551 } 552 if !test.wantBuffer && br != nil { 553 t.Fatalf("Dial() returned non-empty bufio.Reader") 554 } 555 for i, exp := range test.frames { 556 act, err := ReadFrame(br) 557 if err != nil { 558 t.Fatalf("can not read %d-th frame: %v", i, err) 559 } 560 if act.Header != exp.Header { 561 t.Fatalf( 562 "unexpected %d-th frame header: %v; want %v", 563 i, act.Header, exp.Header, 564 ) 565 } 566 if !bytes.Equal(act.Payload, exp.Payload) { 567 t.Fatalf( 568 "unexpected %d-th frame payload:\n%v\nwant:\n%v", 569 i, act.Payload, exp.Payload, 570 ) 571 } 572 } 573 }) 574 } 575 } 576 577 // Used to emulate net.Error behavior, which is usually returned when 578 // connection deadline exceeds. 579 type errTimeout struct { 580 error 581 } 582 583 func (errTimeout) Timeout() bool { return true } 584 func (errTimeout) Temporary() bool { return false } 585 586 func TestDialerCancelation(t *testing.T) { 587 ioErrDeadline := errTimeout{ 588 fmt.Errorf("stub: i/o timeout"), 589 } 590 591 for _, test := range []struct { 592 name string 593 dialer Dialer 594 dialDelay time.Duration 595 ctxTimeout time.Duration 596 ctxCancelAfter time.Duration 597 err error 598 }{ 599 { 600 ctxTimeout: time.Millisecond * 100, 601 err: context.DeadlineExceeded, 602 }, 603 { 604 ctxCancelAfter: time.Millisecond * 100, 605 err: context.Canceled, 606 }, 607 { 608 dialer: Dialer{ 609 Timeout: time.Millisecond * 100, 610 }, 611 ctxTimeout: time.Millisecond * 150, 612 err: context.DeadlineExceeded, 613 }, 614 { 615 ctxTimeout: time.Millisecond * 100, 616 dialDelay: time.Millisecond * 200, 617 err: context.DeadlineExceeded, 618 }, 619 { 620 ctxCancelAfter: time.Millisecond * 100, 621 dialDelay: time.Millisecond * 200, 622 err: context.Canceled, 623 }, 624 } { 625 t.Run(test.name, func(t *testing.T) { 626 var timer *time.Timer 627 deadline := make(chan error, 10) 628 conn := &stubConn{ 629 setDeadline: func(t time.Time) error { 630 if timer != nil { 631 timer.Stop() 632 } 633 if t.IsZero() { 634 return nil 635 } 636 d := t.Sub(time.Now()) 637 if d < 0 { 638 deadline <- ioErrDeadline 639 } else { 640 timer = time.AfterFunc(d, func() { 641 deadline <- ioErrDeadline 642 }) 643 } 644 645 return nil 646 }, 647 read: func(p []byte) (int, error) { 648 if err := <-deadline; err != nil { 649 return 0, err 650 } 651 return len(p), nil 652 }, 653 write: func(p []byte) (int, error) { 654 if err := <-deadline; err != nil { 655 return 0, err 656 } 657 return len(p), nil 658 }, 659 } 660 661 test.dialer.NetDial = func(ctx context.Context, _, _ string) (net.Conn, error) { 662 if t := test.dialDelay; t != 0 { 663 delay := time.After(t) 664 select { 665 case <-delay: 666 case <-ctx.Done(): 667 return nil, ctx.Err() 668 } 669 } 670 return conn, nil 671 } 672 673 ctx := context.Background() 674 if t := test.ctxTimeout; t != 0 { 675 var cancel context.CancelFunc 676 ctx, cancel = context.WithTimeout(ctx, t) 677 defer cancel() 678 } 679 if t := test.ctxCancelAfter; t != 0 { 680 var cancel context.CancelFunc 681 ctx, cancel = context.WithCancel(ctx) 682 time.AfterFunc(t, cancel) 683 } 684 685 _, _, _, err := test.dialer.Dial(ctx, "ws://ezoic.com") 686 if err != test.err { 687 t.Fatalf("unexpected error: %q; want %q", err, test.err) 688 } 689 }) 690 } 691 } 692 693 func BenchmarkDialer(b *testing.B) { 694 for _, test := range []struct { 695 dialer Dialer 696 }{ 697 { 698 dialer: DefaultDialer, 699 }, 700 } { 701 // We need to "mock" the rand.Read method used to generate nonce random 702 // bytes for Sec-WebSocket-Key header. 703 rand.Seed(0) 704 need := b.N * nonceKeySize 705 nonceBytes := make([]byte, need) 706 n, err := rand.Read(nonceBytes) 707 if err != nil { 708 b.Fatal(err) 709 } 710 if n != need { 711 b.Fatalf("not enough random nonce bytes: %d; want %d", n, need) 712 } 713 rand.Seed(0) 714 715 resp := &http.Response{ 716 StatusCode: 101, 717 ProtoMajor: 1, 718 ProtoMinor: 1, 719 Header: http.Header{ 720 headerConnection: []string{"Upgrade"}, 721 headerUpgrade: []string{"websocket"}, 722 headerSecAccept: []string{"fill it later"}, 723 }, 724 } 725 rs := make([][]byte, b.N) 726 for i := range rs { 727 nonce := make([]byte, nonceSize) 728 base64.StdEncoding.Encode( 729 nonce, 730 nonceBytes[i*nonceKeySize:i*nonceKeySize+nonceKeySize], 731 ) 732 accept := makeAccept(nonce) 733 resp.Header[headerSecAccept] = []string{string(accept)} 734 rs[i] = dumpResponse(resp) 735 } 736 737 var i int 738 conn := stubConn{ 739 read: func(p []byte) (int, error) { 740 bts := rs[i] 741 if len(p) < len(bts) { 742 b.Fatalf("short buffer") 743 } 744 return copy(p, bts), io.EOF 745 }, 746 write: func(p []byte) (int, error) { 747 return len(p), nil 748 }, 749 } 750 var nc net.Conn = conn 751 test.dialer.NetDial = func(_ context.Context, net, addr string) (net.Conn, error) { 752 return nc, nil 753 } 754 755 b.ResetTimer() 756 for i = 0; i < b.N; i++ { 757 _, _, _, err := test.dialer.Dial(context.Background(), "ws://example.org") 758 if err != nil { 759 b.Fatal(err) 760 } 761 } 762 } 763 } 764 765 func TestHostPort(t *testing.T) { 766 for _, test := range []struct { 767 name string 768 host string 769 port string 770 771 expHostname string 772 expHostport string 773 }{ 774 { 775 host: "foo", 776 port: ":80", 777 expHostname: "foo", 778 expHostport: "foo:80", 779 }, 780 { 781 host: "foo:1234", 782 port: ":80", 783 expHostname: "foo", 784 expHostport: "foo:1234", 785 }, 786 { 787 name: "ipv4", 788 host: "127.0.0.1", 789 port: ":80", 790 expHostname: "127.0.0.1", 791 expHostport: "127.0.0.1:80", 792 }, 793 { 794 name: "ipv4", 795 host: "127.0.0.1:1234", 796 port: ":80", 797 expHostname: "127.0.0.1", 798 expHostport: "127.0.0.1:1234", 799 }, 800 { 801 name: "ipv6", 802 host: "[0:0:0:0:0:0:0:1]", 803 port: ":80", 804 expHostname: "[0:0:0:0:0:0:0:1]", 805 expHostport: "[0:0:0:0:0:0:0:1]:80", 806 }, 807 { 808 name: "ipv6", 809 host: "[0:0:0:0:0:0:0:1]:1234", 810 port: ":80", 811 expHostname: "[0:0:0:0:0:0:0:1]", 812 expHostport: "[0:0:0:0:0:0:0:1]:1234", 813 }, 814 } { 815 t.Run(test.name, func(t *testing.T) { 816 actHostname, actHostport := hostport(test.host, test.port) 817 if actHostname != test.expHostname { 818 t.Errorf( 819 "actual hostname = %q; want %q", 820 actHostname, test.expHostname, 821 ) 822 } 823 if actHostport != test.expHostport { 824 t.Errorf( 825 "actual hostname = %q; want %q", 826 actHostport, test.expHostport, 827 ) 828 } 829 }) 830 } 831 } 832 833 type stubConn struct { 834 read func([]byte) (int, error) 835 write func([]byte) (int, error) 836 close func() error 837 setDeadline func(time.Time) error 838 setWriteDeadline func(time.Time) error 839 setReadDeadline func(time.Time) error 840 } 841 842 func (s stubConn) Read(p []byte) (int, error) { return s.read(p) } 843 func (s stubConn) Write(p []byte) (int, error) { return s.write(p) } 844 func (s stubConn) LocalAddr() net.Addr { return nil } 845 func (s stubConn) RemoteAddr() net.Addr { return nil } 846 func (s stubConn) Close() error { 847 if s.close != nil { 848 return s.close() 849 } 850 return nil 851 } 852 func (s stubConn) SetDeadline(t time.Time) error { 853 if s.setDeadline != nil { 854 return s.setDeadline(t) 855 } 856 return nil 857 } 858 func (s stubConn) SetReadDeadline(t time.Time) error { 859 if s.setReadDeadline != nil { 860 return s.setReadDeadline(t) 861 } 862 return nil 863 } 864 func (s stubConn) SetWriteDeadline(t time.Time) error { 865 if s.setWriteDeadline != nil { 866 return s.setWriteDeadline(t) 867 } 868 return nil 869 }