From 976cf3e9f8c9fde505dba38cf07f6f56263d1ef5 Mon Sep 17 00:00:00 2001 From: fatedier Date: Wed, 25 Apr 2018 02:34:07 +0800 Subject: [PATCH] use yamux instead of smux --- client/control.go | 8 +- glide.lock | 10 +- glide.yaml | 5 +- server/service.go | 4 +- .../smux => hashicorp/yamux}/.gitignore | 1 - vendor/github.com/hashicorp/yamux/LICENSE | 362 +++++ vendor/github.com/hashicorp/yamux/README.md | 86 ++ vendor/github.com/hashicorp/yamux/addr.go | 60 + .../github.com/hashicorp/yamux/bench_test.go | 123 ++ vendor/github.com/hashicorp/yamux/const.go | 157 +++ .../github.com/hashicorp/yamux/const_test.go | 72 + vendor/github.com/hashicorp/yamux/mux.go | 87 ++ vendor/github.com/hashicorp/yamux/session.go | 646 +++++++++ .../hashicorp/yamux/session_test.go | 1256 +++++++++++++++++ vendor/github.com/hashicorp/yamux/spec.md | 140 ++ vendor/github.com/hashicorp/yamux/stream.go | 470 ++++++ vendor/github.com/hashicorp/yamux/util.go | 43 + .../github.com/hashicorp/yamux/util_test.go | 50 + .../github.com/spf13/cobra/cobra/cmd/init.go | 2 +- vendor/github.com/spf13/cobra/command.go | 10 - vendor/github.com/spf13/cobra/command_test.go | 105 -- vendor/github.com/xtaci/smux/.travis.yml | 15 - vendor/github.com/xtaci/smux/LICENSE | 21 - vendor/github.com/xtaci/smux/README.md | 99 -- vendor/github.com/xtaci/smux/curve.jpg | Bin 106626 -> 0 bytes vendor/github.com/xtaci/smux/frame.go | 60 - vendor/github.com/xtaci/smux/mux.go | 80 -- vendor/github.com/xtaci/smux/mux.jpg | Bin 6199 -> 0 bytes vendor/github.com/xtaci/smux/mux_test.go | 69 - vendor/github.com/xtaci/smux/session.go | 353 ----- vendor/github.com/xtaci/smux/session_test.go | 667 --------- vendor/github.com/xtaci/smux/smux.png | Bin 9891 -> 0 bytes vendor/github.com/xtaci/smux/stream.go | 261 ---- 33 files changed, 3567 insertions(+), 1755 deletions(-) rename vendor/github.com/{xtaci/smux => hashicorp/yamux}/.gitignore (97%) create mode 100644 vendor/github.com/hashicorp/yamux/LICENSE create mode 100644 vendor/github.com/hashicorp/yamux/README.md create mode 100644 vendor/github.com/hashicorp/yamux/addr.go create mode 100644 vendor/github.com/hashicorp/yamux/bench_test.go create mode 100644 vendor/github.com/hashicorp/yamux/const.go create mode 100644 vendor/github.com/hashicorp/yamux/const_test.go create mode 100644 vendor/github.com/hashicorp/yamux/mux.go create mode 100644 vendor/github.com/hashicorp/yamux/session.go create mode 100644 vendor/github.com/hashicorp/yamux/session_test.go create mode 100644 vendor/github.com/hashicorp/yamux/spec.md create mode 100644 vendor/github.com/hashicorp/yamux/stream.go create mode 100644 vendor/github.com/hashicorp/yamux/util.go create mode 100644 vendor/github.com/hashicorp/yamux/util_test.go delete mode 100644 vendor/github.com/xtaci/smux/.travis.yml delete mode 100644 vendor/github.com/xtaci/smux/LICENSE delete mode 100644 vendor/github.com/xtaci/smux/README.md delete mode 100644 vendor/github.com/xtaci/smux/curve.jpg delete mode 100644 vendor/github.com/xtaci/smux/frame.go delete mode 100644 vendor/github.com/xtaci/smux/mux.go delete mode 100644 vendor/github.com/xtaci/smux/mux.jpg delete mode 100644 vendor/github.com/xtaci/smux/mux_test.go delete mode 100644 vendor/github.com/xtaci/smux/session.go delete mode 100644 vendor/github.com/xtaci/smux/session_test.go delete mode 100644 vendor/github.com/xtaci/smux/smux.png delete mode 100644 vendor/github.com/xtaci/smux/stream.go diff --git a/client/control.go b/client/control.go index 9342238f..ba37a2db 100644 --- a/client/control.go +++ b/client/control.go @@ -21,8 +21,6 @@ import ( "sync" "time" - "github.com/xtaci/smux" - "github.com/fatedier/frp/g" "github.com/fatedier/frp/models/config" "github.com/fatedier/frp/models/msg" @@ -32,6 +30,8 @@ import ( "github.com/fatedier/frp/utils/shutdown" "github.com/fatedier/frp/utils/util" "github.com/fatedier/frp/utils/version" + + fmux "github.com/hashicorp/yamux" ) const ( @@ -51,7 +51,7 @@ type Control struct { conn frpNet.Conn // tcp stream multiplexing, if enabled - session *smux.Session + session *fmux.Session // put a message in this channel to send it over control connection to server sendCh chan (msg.Message) @@ -198,7 +198,7 @@ func (ctl *Control) login() (err error) { }() if g.GlbClientCfg.TcpMux { - session, errRet := smux.Client(conn, nil) + session, errRet := fmux.Client(conn, nil) if errRet != nil { return errRet } diff --git a/glide.lock b/glide.lock index 84313f01..48015ce0 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: 367ad1f2515b51db9d04d5620fd88843fb6faabf303fe3103b896ef7a3f5a126 -updated: 2018-04-23T02:33:52.913905+08:00 +hash: e2a62cbc49d9da8ff95682f5c0b7731a7047afdd139acddb691c51ea98f726e1 +updated: 2018-04-25T02:41:38.15698+08:00 imports: - name: github.com/armon/go-socks5 version: e75332964ef517daa070d7c38a9466a0d687e0a5 @@ -17,6 +17,8 @@ imports: version: 5979233c5d6225d4a8e438cdd0b411888449ddab - name: github.com/gorilla/websocket version: ea4d1f681babbce9545c9c5f3d5194a789c89f5b +- name: github.com/hashicorp/yamux + version: 2658be15c5f05e76244154714161f17e3e77de2e - name: github.com/inconshreveable/mousetrap version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75 - name: github.com/julienschmidt/httprouter @@ -38,7 +40,7 @@ imports: - name: github.com/rodaine/table version: 212a2ad1c462ed4d5b5511ea2b480a573281dbbd - name: github.com/spf13/cobra - version: 615425954c3b0d9485a7027d4d451fdcdfdee84e + version: a1f051bc3eba734da4772d60e2d677f47cf93ef4 - name: github.com/spf13/pflag version: 583c0c0531f06d5278b7d917446061adc344b5cd - name: github.com/stretchr/testify @@ -57,8 +59,6 @@ imports: - sm4 - name: github.com/vaughan0/go-ini version: a98ad7ee00ec53921f08832bc06ecf7fd600e6a1 -- name: github.com/xtaci/smux - version: 2de5471dfcbc029f5fe1392b83fe784127c4943e - name: golang.org/x/crypto version: e1a4589e7d3ea14a3352255d04b6f1a418845e5e subpackages: diff --git a/glide.yaml b/glide.yaml index 2cc15352..2765c56d 100644 --- a/glide.yaml +++ b/glide.yaml @@ -46,8 +46,6 @@ import: - sm4 - package: github.com/vaughan0/go-ini version: a98ad7ee00ec53921f08832bc06ecf7fd600e6a1 -- package: github.com/xtaci/smux - version: 2de5471dfcbc029f5fe1392b83fe784127c4943e - package: golang.org/x/crypto version: e1a4589e7d3ea14a3352255d04b6f1a418845e5e subpackages: @@ -71,3 +69,6 @@ import: version: v1.0.0 - package: github.com/gorilla/websocket version: v1.2.0 +- package: github.com/hashicorp/yamux +- package: github.com/spf13/cobra + version: v0.0.2 diff --git a/server/service.go b/server/service.go index 48f49e07..dc6f4ff8 100644 --- a/server/service.go +++ b/server/service.go @@ -29,7 +29,7 @@ import ( "github.com/fatedier/frp/utils/version" "github.com/fatedier/frp/utils/vhost" - "github.com/xtaci/smux" + fmux "github.com/hashicorp/yamux" ) const ( @@ -234,7 +234,7 @@ func (svr *Service) HandleListener(l frpNet.Listener) { } if g.GlbServerCfg.TcpMux { - session, err := smux.Server(frpConn, nil) + session, err := fmux.Server(frpConn, nil) if err != nil { log.Warn("Failed to create mux connection: %v", err) frpConn.Close() diff --git a/vendor/github.com/xtaci/smux/.gitignore b/vendor/github.com/hashicorp/yamux/.gitignore similarity index 97% rename from vendor/github.com/xtaci/smux/.gitignore rename to vendor/github.com/hashicorp/yamux/.gitignore index daf913b1..83656241 100644 --- a/vendor/github.com/xtaci/smux/.gitignore +++ b/vendor/github.com/hashicorp/yamux/.gitignore @@ -21,4 +21,3 @@ _testmain.go *.exe *.test -*.prof diff --git a/vendor/github.com/hashicorp/yamux/LICENSE b/vendor/github.com/hashicorp/yamux/LICENSE new file mode 100644 index 00000000..f0e5c79e --- /dev/null +++ b/vendor/github.com/hashicorp/yamux/LICENSE @@ -0,0 +1,362 @@ +Mozilla Public License, version 2.0 + +1. Definitions + +1.1. "Contributor" + + means each individual or legal entity that creates, contributes to the + creation of, or owns Covered Software. + +1.2. "Contributor Version" + + means the combination of the Contributions of others (if any) used by a + Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + + means Source Code Form to which the initial Contributor has attached the + notice in Exhibit A, the Executable Form of such Source Code Form, and + Modifications of such Source Code Form, in each case including portions + thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + a. that the initial Contributor has attached the notice described in + Exhibit B to the Covered Software; or + + b. that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the terms of + a Secondary License. + +1.6. "Executable Form" + + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + + means a work that combines Covered Software with other material, in a + separate file or files, that is not Covered Software. + +1.8. "License" + + means this document. + +1.9. "Licensable" + + means having the right to grant, to the maximum extent possible, whether + at the time of the initial grant or subsequently, any and all of the + rights conveyed by this License. + +1.10. "Modifications" + + means any of the following: + + a. any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered Software; or + + b. any new file in Source Code Form that contains any Covered Software. + +1.11. "Patent Claims" of a Contributor + + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the License, + by the making, using, selling, offering for sale, having made, import, + or transfer of either its Contributions or its Contributor Version. + +1.12. "Secondary License" + + means either the GNU General Public License, Version 2.0, the GNU Lesser + General Public License, Version 2.1, the GNU Affero General Public + License, Version 3.0, or any later versions of those licenses. + +1.13. "Source Code Form" + + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that controls, is + controlled by, or is under common control with You. For purposes of this + definition, "control" means (a) the power, direct or indirect, to cause + the direction or management of such entity, whether by contract or + otherwise, or (b) ownership of more than fifty percent (50%) of the + outstanding shares or beneficial ownership of such entity. + + +2. License Grants and Conditions + +2.1. Grants + + Each Contributor hereby grants You a world-wide, royalty-free, + non-exclusive license: + + a. under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + + b. under Patent Claims of such Contributor to make, use, sell, offer for + sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + + The licenses granted in Section 2.1 with respect to any Contribution + become effective for each Contribution on the date the Contributor first + distributes such Contribution. + +2.3. Limitations on Grant Scope + + The licenses granted in this Section 2 are the only rights granted under + this License. No additional rights or licenses will be implied from the + distribution or licensing of Covered Software under this License. + Notwithstanding Section 2.1(b) above, no patent license is granted by a + Contributor: + + a. for any code that a Contributor has removed from Covered Software; or + + b. for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + + c. under Patent Claims infringed by Covered Software in the absence of + its Contributions. + + This License does not grant any rights in the trademarks, service marks, + or logos of any Contributor (except as may be necessary to comply with + the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + + No Contributor makes additional grants as a result of Your choice to + distribute the Covered Software under a subsequent version of this + License (see Section 10.2) or under the terms of a Secondary License (if + permitted under the terms of Section 3.3). + +2.5. Representation + + Each Contributor represents that the Contributor believes its + Contributions are its original creation(s) or it has sufficient rights to + grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + + This License is not intended to limit any rights You have under + applicable copyright doctrines of fair use, fair dealing, or other + equivalents. + +2.7. Conditions + + Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in + Section 2.1. + + +3. Responsibilities + +3.1. Distribution of Source Form + + All distribution of Covered Software in Source Code Form, including any + Modifications that You create or to which You contribute, must be under + the terms of this License. You must inform recipients that the Source + Code Form of the Covered Software is governed by the terms of this + License, and how they can obtain a copy of this License. You may not + attempt to alter or restrict the recipients' rights in the Source Code + Form. + +3.2. Distribution of Executable Form + + If You distribute Covered Software in Executable Form then: + + a. such Covered Software must also be made available in Source Code Form, + as described in Section 3.1, and You must inform recipients of the + Executable Form how they can obtain a copy of such Source Code Form by + reasonable means in a timely manner, at a charge no more than the cost + of distribution to the recipient; and + + b. You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter the + recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + + You may create and distribute a Larger Work under terms of Your choice, + provided that You also comply with the requirements of this License for + the Covered Software. If the Larger Work is a combination of Covered + Software with a work governed by one or more Secondary Licenses, and the + Covered Software is not Incompatible With Secondary Licenses, this + License permits You to additionally distribute such Covered Software + under the terms of such Secondary License(s), so that the recipient of + the Larger Work may, at their option, further distribute the Covered + Software under the terms of either this License or such Secondary + License(s). + +3.4. Notices + + You may not remove or alter the substance of any license notices + (including copyright notices, patent notices, disclaimers of warranty, or + limitations of liability) contained within the Source Code Form of the + Covered Software, except that You may alter any license notices to the + extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + + You may choose to offer, and to charge a fee for, warranty, support, + indemnity or liability obligations to one or more recipients of Covered + Software. However, You may do so only on Your own behalf, and not on + behalf of any Contributor. You must make it absolutely clear that any + such warranty, support, indemnity, or liability obligation is offered by + You alone, and You hereby agree to indemnify every Contributor for any + liability incurred by such Contributor as a result of warranty, support, + indemnity or liability terms You offer. You may include additional + disclaimers of warranty and limitations of liability specific to any + jurisdiction. + +4. Inability to Comply Due to Statute or Regulation + + If it is impossible for You to comply with any of the terms of this License + with respect to some or all of the Covered Software due to statute, + judicial order, or regulation then You must: (a) comply with the terms of + this License to the maximum extent possible; and (b) describe the + limitations and the code they affect. Such description must be placed in a + text file included with all distributions of the Covered Software under + this License. Except to the extent prohibited by statute or regulation, + such description must be sufficiently detailed for a recipient of ordinary + skill to be able to understand it. + +5. Termination + +5.1. The rights granted under this License will terminate automatically if You + fail to comply with any of its terms. However, if You become compliant, + then the rights granted under this License from a particular Contributor + are reinstated (a) provisionally, unless and until such Contributor + explicitly and finally terminates Your grants, and (b) on an ongoing + basis, if such Contributor fails to notify You of the non-compliance by + some reasonable means prior to 60 days after You have come back into + compliance. Moreover, Your grants from a particular Contributor are + reinstated on an ongoing basis if such Contributor notifies You of the + non-compliance by some reasonable means, this is the first time You have + received notice of non-compliance with this License from such + Contributor, and You become compliant prior to 30 days after Your receipt + of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent + infringement claim (excluding declaratory judgment actions, + counter-claims, and cross-claims) alleging that a Contributor Version + directly or indirectly infringes any patent, then the rights granted to + You by any and all Contributors for the Covered Software under Section + 2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user + license agreements (excluding distributors and resellers) which have been + validly granted by You or Your distributors under this License prior to + termination shall survive termination. + +6. Disclaimer of Warranty + + Covered Software is provided under this License on an "as is" basis, + without warranty of any kind, either expressed, implied, or statutory, + including, without limitation, warranties that the Covered Software is free + of defects, merchantable, fit for a particular purpose or non-infringing. + The entire risk as to the quality and performance of the Covered Software + is with You. Should any Covered Software prove defective in any respect, + You (not any Contributor) assume the cost of any necessary servicing, + repair, or correction. This disclaimer of warranty constitutes an essential + part of this License. No use of any Covered Software is authorized under + this License except under this disclaimer. + +7. Limitation of Liability + + Under no circumstances and under no legal theory, whether tort (including + negligence), contract, or otherwise, shall any Contributor, or anyone who + distributes Covered Software as permitted above, be liable to You for any + direct, indirect, special, incidental, or consequential damages of any + character including, without limitation, damages for lost profits, loss of + goodwill, work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses, even if such party shall have been + informed of the possibility of such damages. This limitation of liability + shall not apply to liability for death or personal injury resulting from + such party's negligence to the extent applicable law prohibits such + limitation. Some jurisdictions do not allow the exclusion or limitation of + incidental or consequential damages, so this exclusion and limitation may + not apply to You. + +8. Litigation + + Any litigation relating to this License may be brought only in the courts + of a jurisdiction where the defendant maintains its principal place of + business and such litigation shall be governed by laws of that + jurisdiction, without reference to its conflict-of-law provisions. Nothing + in this Section shall prevent a party's ability to bring cross-claims or + counter-claims. + +9. Miscellaneous + + This License represents the complete agreement concerning the subject + matter hereof. If any provision of this License is held to be + unenforceable, such provision shall be reformed only to the extent + necessary to make it enforceable. Any law or regulation which provides that + the language of a contract shall be construed against the drafter shall not + be used to construe this License against a Contributor. + + +10. Versions of the License + +10.1. New Versions + + Mozilla Foundation is the license steward. Except as provided in Section + 10.3, no one other than the license steward has the right to modify or + publish new versions of this License. Each version will be given a + distinguishing version number. + +10.2. Effect of New Versions + + You may distribute the Covered Software under the terms of the version + of the License under which You originally received the Covered Software, + or under the terms of any subsequent version published by the license + steward. + +10.3. Modified Versions + + If you create software not governed by this License, and you want to + create a new license for such software, you may create and use a + modified version of this License if you rename the license and remove + any references to the name of the license steward (except to note that + such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary + Licenses If You choose to distribute Source Code Form that is + Incompatible With Secondary Licenses under the terms of this version of + the License, the notice described in Exhibit B of this License must be + attached. + +Exhibit A - Source Code Form License Notice + + This Source Code Form is subject to the + terms of the Mozilla Public License, v. + 2.0. If a copy of the MPL was not + distributed with this file, You can + obtain one at + http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular file, +then You may include the notice in a location (such as a LICENSE file in a +relevant directory) where a recipient would be likely to look for such a +notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice + + This Source Code Form is "Incompatible + With Secondary Licenses", as defined by + the Mozilla Public License, v. 2.0. \ No newline at end of file diff --git a/vendor/github.com/hashicorp/yamux/README.md b/vendor/github.com/hashicorp/yamux/README.md new file mode 100644 index 00000000..d4db7fc9 --- /dev/null +++ b/vendor/github.com/hashicorp/yamux/README.md @@ -0,0 +1,86 @@ +# Yamux + +Yamux (Yet another Multiplexer) is a multiplexing library for Golang. +It relies on an underlying connection to provide reliability +and ordering, such as TCP or Unix domain sockets, and provides +stream-oriented multiplexing. It is inspired by SPDY but is not +interoperable with it. + +Yamux features include: + +* Bi-directional streams + * Streams can be opened by either client or server + * Useful for NAT traversal + * Server-side push support +* Flow control + * Avoid starvation + * Back-pressure to prevent overwhelming a receiver +* Keep Alives + * Enables persistent connections over a load balancer +* Efficient + * Enables thousands of logical streams with low overhead + +## Documentation + +For complete documentation, see the associated [Godoc](http://godoc.org/github.com/hashicorp/yamux). + +## Specification + +The full specification for Yamux is provided in the `spec.md` file. +It can be used as a guide to implementors of interoperable libraries. + +## Usage + +Using Yamux is remarkably simple: + +```go + +func client() { + // Get a TCP connection + conn, err := net.Dial(...) + if err != nil { + panic(err) + } + + // Setup client side of yamux + session, err := yamux.Client(conn, nil) + if err != nil { + panic(err) + } + + // Open a new stream + stream, err := session.Open() + if err != nil { + panic(err) + } + + // Stream implements net.Conn + stream.Write([]byte("ping")) +} + +func server() { + // Accept a TCP connection + conn, err := listener.Accept() + if err != nil { + panic(err) + } + + // Setup server side of yamux + session, err := yamux.Server(conn, nil) + if err != nil { + panic(err) + } + + // Accept a stream + stream, err := session.Accept() + if err != nil { + panic(err) + } + + // Listen for a message + buf := make([]byte, 4) + stream.Read(buf) +} + +``` + diff --git a/vendor/github.com/hashicorp/yamux/addr.go b/vendor/github.com/hashicorp/yamux/addr.go new file mode 100644 index 00000000..be6ebca9 --- /dev/null +++ b/vendor/github.com/hashicorp/yamux/addr.go @@ -0,0 +1,60 @@ +package yamux + +import ( + "fmt" + "net" +) + +// hasAddr is used to get the address from the underlying connection +type hasAddr interface { + LocalAddr() net.Addr + RemoteAddr() net.Addr +} + +// yamuxAddr is used when we cannot get the underlying address +type yamuxAddr struct { + Addr string +} + +func (*yamuxAddr) Network() string { + return "yamux" +} + +func (y *yamuxAddr) String() string { + return fmt.Sprintf("yamux:%s", y.Addr) +} + +// Addr is used to get the address of the listener. +func (s *Session) Addr() net.Addr { + return s.LocalAddr() +} + +// LocalAddr is used to get the local address of the +// underlying connection. +func (s *Session) LocalAddr() net.Addr { + addr, ok := s.conn.(hasAddr) + if !ok { + return &yamuxAddr{"local"} + } + return addr.LocalAddr() +} + +// RemoteAddr is used to get the address of remote end +// of the underlying connection +func (s *Session) RemoteAddr() net.Addr { + addr, ok := s.conn.(hasAddr) + if !ok { + return &yamuxAddr{"remote"} + } + return addr.RemoteAddr() +} + +// LocalAddr returns the local address +func (s *Stream) LocalAddr() net.Addr { + return s.session.LocalAddr() +} + +// LocalAddr returns the remote address +func (s *Stream) RemoteAddr() net.Addr { + return s.session.RemoteAddr() +} diff --git a/vendor/github.com/hashicorp/yamux/bench_test.go b/vendor/github.com/hashicorp/yamux/bench_test.go new file mode 100644 index 00000000..5fc1c550 --- /dev/null +++ b/vendor/github.com/hashicorp/yamux/bench_test.go @@ -0,0 +1,123 @@ +package yamux + +import ( + "testing" +) + +func BenchmarkPing(b *testing.B) { + client, server := testClientServer() + defer client.Close() + defer server.Close() + + for i := 0; i < b.N; i++ { + rtt, err := client.Ping() + if err != nil { + b.Fatalf("err: %v", err) + } + if rtt == 0 { + b.Fatalf("bad: %v", rtt) + } + } +} + +func BenchmarkAccept(b *testing.B) { + client, server := testClientServer() + defer client.Close() + defer server.Close() + + go func() { + for i := 0; i < b.N; i++ { + stream, err := server.AcceptStream() + if err != nil { + return + } + stream.Close() + } + }() + + for i := 0; i < b.N; i++ { + stream, err := client.Open() + if err != nil { + b.Fatalf("err: %v", err) + } + stream.Close() + } +} + +func BenchmarkSendRecv(b *testing.B) { + client, server := testClientServer() + defer client.Close() + defer server.Close() + + sendBuf := make([]byte, 512) + recvBuf := make([]byte, 512) + + doneCh := make(chan struct{}) + go func() { + stream, err := server.AcceptStream() + if err != nil { + return + } + defer stream.Close() + for i := 0; i < b.N; i++ { + if _, err := stream.Read(recvBuf); err != nil { + b.Fatalf("err: %v", err) + } + } + close(doneCh) + }() + + stream, err := client.Open() + if err != nil { + b.Fatalf("err: %v", err) + } + defer stream.Close() + for i := 0; i < b.N; i++ { + if _, err := stream.Write(sendBuf); err != nil { + b.Fatalf("err: %v", err) + } + } + <-doneCh +} + +func BenchmarkSendRecvLarge(b *testing.B) { + client, server := testClientServer() + defer client.Close() + defer server.Close() + const sendSize = 512 * 1024 * 1024 + const recvSize = 4 * 1024 + + sendBuf := make([]byte, sendSize) + recvBuf := make([]byte, recvSize) + + b.ResetTimer() + recvDone := make(chan struct{}) + + go func() { + stream, err := server.AcceptStream() + if err != nil { + return + } + defer stream.Close() + for i := 0; i < b.N; i++ { + for j := 0; j < sendSize/recvSize; j++ { + if _, err := stream.Read(recvBuf); err != nil { + b.Fatalf("err: %v", err) + } + } + } + close(recvDone) + }() + + stream, err := client.Open() + if err != nil { + b.Fatalf("err: %v", err) + } + defer stream.Close() + for i := 0; i < b.N; i++ { + if _, err := stream.Write(sendBuf); err != nil { + b.Fatalf("err: %v", err) + } + } + <-recvDone +} diff --git a/vendor/github.com/hashicorp/yamux/const.go b/vendor/github.com/hashicorp/yamux/const.go new file mode 100644 index 00000000..4f529382 --- /dev/null +++ b/vendor/github.com/hashicorp/yamux/const.go @@ -0,0 +1,157 @@ +package yamux + +import ( + "encoding/binary" + "fmt" +) + +var ( + // ErrInvalidVersion means we received a frame with an + // invalid version + ErrInvalidVersion = fmt.Errorf("invalid protocol version") + + // ErrInvalidMsgType means we received a frame with an + // invalid message type + ErrInvalidMsgType = fmt.Errorf("invalid msg type") + + // ErrSessionShutdown is used if there is a shutdown during + // an operation + ErrSessionShutdown = fmt.Errorf("session shutdown") + + // ErrStreamsExhausted is returned if we have no more + // stream ids to issue + ErrStreamsExhausted = fmt.Errorf("streams exhausted") + + // ErrDuplicateStream is used if a duplicate stream is + // opened inbound + ErrDuplicateStream = fmt.Errorf("duplicate stream initiated") + + // ErrReceiveWindowExceeded indicates the window was exceeded + ErrRecvWindowExceeded = fmt.Errorf("recv window exceeded") + + // ErrTimeout is used when we reach an IO deadline + ErrTimeout = fmt.Errorf("i/o deadline reached") + + // ErrStreamClosed is returned when using a closed stream + ErrStreamClosed = fmt.Errorf("stream closed") + + // ErrUnexpectedFlag is set when we get an unexpected flag + ErrUnexpectedFlag = fmt.Errorf("unexpected flag") + + // ErrRemoteGoAway is used when we get a go away from the other side + ErrRemoteGoAway = fmt.Errorf("remote end is not accepting connections") + + // ErrConnectionReset is sent if a stream is reset. This can happen + // if the backlog is exceeded, or if there was a remote GoAway. + ErrConnectionReset = fmt.Errorf("connection reset") + + // ErrConnectionWriteTimeout indicates that we hit the "safety valve" + // timeout writing to the underlying stream connection. + ErrConnectionWriteTimeout = fmt.Errorf("connection write timeout") + + // ErrKeepAliveTimeout is sent if a missed keepalive caused the stream close + ErrKeepAliveTimeout = fmt.Errorf("keepalive timeout") +) + +const ( + // protoVersion is the only version we support + protoVersion uint8 = 0 +) + +const ( + // Data is used for data frames. They are followed + // by length bytes worth of payload. + typeData uint8 = iota + + // WindowUpdate is used to change the window of + // a given stream. The length indicates the delta + // update to the window. + typeWindowUpdate + + // Ping is sent as a keep-alive or to measure + // the RTT. The StreamID and Length value are echoed + // back in the response. + typePing + + // GoAway is sent to terminate a session. The StreamID + // should be 0 and the length is an error code. + typeGoAway +) + +const ( + // SYN is sent to signal a new stream. May + // be sent with a data payload + flagSYN uint16 = 1 << iota + + // ACK is sent to acknowledge a new stream. May + // be sent with a data payload + flagACK + + // FIN is sent to half-close the given stream. + // May be sent with a data payload. + flagFIN + + // RST is used to hard close a given stream. + flagRST +) + +const ( + // initialStreamWindow is the initial stream window size + initialStreamWindow uint32 = 256 * 1024 +) + +const ( + // goAwayNormal is sent on a normal termination + goAwayNormal uint32 = iota + + // goAwayProtoErr sent on a protocol error + goAwayProtoErr + + // goAwayInternalErr sent on an internal error + goAwayInternalErr +) + +const ( + sizeOfVersion = 1 + sizeOfType = 1 + sizeOfFlags = 2 + sizeOfStreamID = 4 + sizeOfLength = 4 + headerSize = sizeOfVersion + sizeOfType + sizeOfFlags + + sizeOfStreamID + sizeOfLength +) + +type header []byte + +func (h header) Version() uint8 { + return h[0] +} + +func (h header) MsgType() uint8 { + return h[1] +} + +func (h header) Flags() uint16 { + return binary.BigEndian.Uint16(h[2:4]) +} + +func (h header) StreamID() uint32 { + return binary.BigEndian.Uint32(h[4:8]) +} + +func (h header) Length() uint32 { + return binary.BigEndian.Uint32(h[8:12]) +} + +func (h header) String() string { + return fmt.Sprintf("Vsn:%d Type:%d Flags:%d StreamID:%d Length:%d", + h.Version(), h.MsgType(), h.Flags(), h.StreamID(), h.Length()) +} + +func (h header) encode(msgType uint8, flags uint16, streamID uint32, length uint32) { + h[0] = protoVersion + h[1] = msgType + binary.BigEndian.PutUint16(h[2:4], flags) + binary.BigEndian.PutUint32(h[4:8], streamID) + binary.BigEndian.PutUint32(h[8:12], length) +} diff --git a/vendor/github.com/hashicorp/yamux/const_test.go b/vendor/github.com/hashicorp/yamux/const_test.go new file mode 100644 index 00000000..153da18b --- /dev/null +++ b/vendor/github.com/hashicorp/yamux/const_test.go @@ -0,0 +1,72 @@ +package yamux + +import ( + "testing" +) + +func TestConst(t *testing.T) { + if protoVersion != 0 { + t.Fatalf("bad: %v", protoVersion) + } + + if typeData != 0 { + t.Fatalf("bad: %v", typeData) + } + if typeWindowUpdate != 1 { + t.Fatalf("bad: %v", typeWindowUpdate) + } + if typePing != 2 { + t.Fatalf("bad: %v", typePing) + } + if typeGoAway != 3 { + t.Fatalf("bad: %v", typeGoAway) + } + + if flagSYN != 1 { + t.Fatalf("bad: %v", flagSYN) + } + if flagACK != 2 { + t.Fatalf("bad: %v", flagACK) + } + if flagFIN != 4 { + t.Fatalf("bad: %v", flagFIN) + } + if flagRST != 8 { + t.Fatalf("bad: %v", flagRST) + } + + if goAwayNormal != 0 { + t.Fatalf("bad: %v", goAwayNormal) + } + if goAwayProtoErr != 1 { + t.Fatalf("bad: %v", goAwayProtoErr) + } + if goAwayInternalErr != 2 { + t.Fatalf("bad: %v", goAwayInternalErr) + } + + if headerSize != 12 { + t.Fatalf("bad header size") + } +} + +func TestEncodeDecode(t *testing.T) { + hdr := header(make([]byte, headerSize)) + hdr.encode(typeWindowUpdate, flagACK|flagRST, 1234, 4321) + + if hdr.Version() != protoVersion { + t.Fatalf("bad: %v", hdr) + } + if hdr.MsgType() != typeWindowUpdate { + t.Fatalf("bad: %v", hdr) + } + if hdr.Flags() != flagACK|flagRST { + t.Fatalf("bad: %v", hdr) + } + if hdr.StreamID() != 1234 { + t.Fatalf("bad: %v", hdr) + } + if hdr.Length() != 4321 { + t.Fatalf("bad: %v", hdr) + } +} diff --git a/vendor/github.com/hashicorp/yamux/mux.go b/vendor/github.com/hashicorp/yamux/mux.go new file mode 100644 index 00000000..7abc7c74 --- /dev/null +++ b/vendor/github.com/hashicorp/yamux/mux.go @@ -0,0 +1,87 @@ +package yamux + +import ( + "fmt" + "io" + "os" + "time" +) + +// Config is used to tune the Yamux session +type Config struct { + // AcceptBacklog is used to limit how many streams may be + // waiting an accept. + AcceptBacklog int + + // EnableKeepalive is used to do a period keep alive + // messages using a ping. + EnableKeepAlive bool + + // KeepAliveInterval is how often to perform the keep alive + KeepAliveInterval time.Duration + + // ConnectionWriteTimeout is meant to be a "safety valve" timeout after + // we which will suspect a problem with the underlying connection and + // close it. This is only applied to writes, where's there's generally + // an expectation that things will move along quickly. + ConnectionWriteTimeout time.Duration + + // MaxStreamWindowSize is used to control the maximum + // window size that we allow for a stream. + MaxStreamWindowSize uint32 + + // LogOutput is used to control the log destination + LogOutput io.Writer +} + +// DefaultConfig is used to return a default configuration +func DefaultConfig() *Config { + return &Config{ + AcceptBacklog: 256, + EnableKeepAlive: true, + KeepAliveInterval: 30 * time.Second, + ConnectionWriteTimeout: 10 * time.Second, + MaxStreamWindowSize: initialStreamWindow, + LogOutput: os.Stderr, + } +} + +// VerifyConfig is used to verify the sanity of configuration +func VerifyConfig(config *Config) error { + if config.AcceptBacklog <= 0 { + return fmt.Errorf("backlog must be positive") + } + if config.KeepAliveInterval == 0 { + return fmt.Errorf("keep-alive interval must be positive") + } + if config.MaxStreamWindowSize < initialStreamWindow { + return fmt.Errorf("MaxStreamWindowSize must be larger than %d", initialStreamWindow) + } + return nil +} + +// Server is used to initialize a new server-side connection. +// There must be at most one server-side connection. If a nil config is +// provided, the DefaultConfiguration will be used. +func Server(conn io.ReadWriteCloser, config *Config) (*Session, error) { + if config == nil { + config = DefaultConfig() + } + if err := VerifyConfig(config); err != nil { + return nil, err + } + return newSession(config, conn, false), nil +} + +// Client is used to initialize a new client-side connection. +// There must be at most one client-side connection. +func Client(conn io.ReadWriteCloser, config *Config) (*Session, error) { + if config == nil { + config = DefaultConfig() + } + + if err := VerifyConfig(config); err != nil { + return nil, err + } + return newSession(config, conn, true), nil +} diff --git a/vendor/github.com/hashicorp/yamux/session.go b/vendor/github.com/hashicorp/yamux/session.go new file mode 100644 index 00000000..d8446fa6 --- /dev/null +++ b/vendor/github.com/hashicorp/yamux/session.go @@ -0,0 +1,646 @@ +package yamux + +import ( + "bufio" + "fmt" + "io" + "io/ioutil" + "log" + "math" + "net" + "strings" + "sync" + "sync/atomic" + "time" +) + +// Session is used to wrap a reliable ordered connection and to +// multiplex it into multiple streams. +type Session struct { + // remoteGoAway indicates the remote side does + // not want futher connections. Must be first for alignment. + remoteGoAway int32 + + // localGoAway indicates that we should stop + // accepting futher connections. Must be first for alignment. + localGoAway int32 + + // nextStreamID is the next stream we should + // send. This depends if we are a client/server. + nextStreamID uint32 + + // config holds our configuration + config *Config + + // logger is used for our logs + logger *log.Logger + + // conn is the underlying connection + conn io.ReadWriteCloser + + // bufRead is a buffered reader + bufRead *bufio.Reader + + // pings is used to track inflight pings + pings map[uint32]chan struct{} + pingID uint32 + pingLock sync.Mutex + + // streams maps a stream id to a stream, and inflight has an entry + // for any outgoing stream that has not yet been established. Both are + // protected by streamLock. + streams map[uint32]*Stream + inflight map[uint32]struct{} + streamLock sync.Mutex + + // synCh acts like a semaphore. It is sized to the AcceptBacklog which + // is assumed to be symmetric between the client and server. This allows + // the client to avoid exceeding the backlog and instead blocks the open. + synCh chan struct{} + + // acceptCh is used to pass ready streams to the client + acceptCh chan *Stream + + // sendCh is used to mark a stream as ready to send, + // or to send a header out directly. + sendCh chan sendReady + + // recvDoneCh is closed when recv() exits to avoid a race + // between stream registration and stream shutdown + recvDoneCh chan struct{} + + // shutdown is used to safely close a session + shutdown bool + shutdownErr error + shutdownCh chan struct{} + shutdownLock sync.Mutex +} + +// sendReady is used to either mark a stream as ready +// or to directly send a header +type sendReady struct { + Hdr []byte + Body io.Reader + Err chan error +} + +// newSession is used to construct a new session +func newSession(config *Config, conn io.ReadWriteCloser, client bool) *Session { + s := &Session{ + config: config, + logger: log.New(config.LogOutput, "", log.LstdFlags), + conn: conn, + bufRead: bufio.NewReader(conn), + pings: make(map[uint32]chan struct{}), + streams: make(map[uint32]*Stream), + inflight: make(map[uint32]struct{}), + synCh: make(chan struct{}, config.AcceptBacklog), + acceptCh: make(chan *Stream, config.AcceptBacklog), + sendCh: make(chan sendReady, 64), + recvDoneCh: make(chan struct{}), + shutdownCh: make(chan struct{}), + } + if client { + s.nextStreamID = 1 + } else { + s.nextStreamID = 2 + } + go s.recv() + go s.send() + if config.EnableKeepAlive { + go s.keepalive() + } + return s +} + +// IsClosed does a safe check to see if we have shutdown +func (s *Session) IsClosed() bool { + select { + case <-s.shutdownCh: + return true + default: + return false + } +} + +// CloseChan returns a read-only channel which is closed as +// soon as the session is closed. +func (s *Session) CloseChan() <-chan struct{} { + return s.shutdownCh +} + +// NumStreams returns the number of currently open streams +func (s *Session) NumStreams() int { + s.streamLock.Lock() + num := len(s.streams) + s.streamLock.Unlock() + return num +} + +// Open is used to create a new stream as a net.Conn +func (s *Session) Open() (net.Conn, error) { + conn, err := s.OpenStream() + if err != nil { + return nil, err + } + return conn, nil +} + +// OpenStream is used to create a new stream +func (s *Session) OpenStream() (*Stream, error) { + if s.IsClosed() { + return nil, ErrSessionShutdown + } + if atomic.LoadInt32(&s.remoteGoAway) == 1 { + return nil, ErrRemoteGoAway + } + + // Block if we have too many inflight SYNs + select { + case s.synCh <- struct{}{}: + case <-s.shutdownCh: + return nil, ErrSessionShutdown + } + +GET_ID: + // Get an ID, and check for stream exhaustion + id := atomic.LoadUint32(&s.nextStreamID) + if id >= math.MaxUint32-1 { + return nil, ErrStreamsExhausted + } + if !atomic.CompareAndSwapUint32(&s.nextStreamID, id, id+2) { + goto GET_ID + } + + // Register the stream + stream := newStream(s, id, streamInit) + s.streamLock.Lock() + s.streams[id] = stream + s.inflight[id] = struct{}{} + s.streamLock.Unlock() + + // Send the window update to create + if err := stream.sendWindowUpdate(); err != nil { + select { + case <-s.synCh: + default: + s.logger.Printf("[ERR] yamux: aborted stream open without inflight syn semaphore") + } + return nil, err + } + return stream, nil +} + +// Accept is used to block until the next available stream +// is ready to be accepted. +func (s *Session) Accept() (net.Conn, error) { + conn, err := s.AcceptStream() + if err != nil { + return nil, err + } + return conn, err +} + +// AcceptStream is used to block until the next available stream +// is ready to be accepted. +func (s *Session) AcceptStream() (*Stream, error) { + select { + case stream := <-s.acceptCh: + if err := stream.sendWindowUpdate(); err != nil { + return nil, err + } + return stream, nil + case <-s.shutdownCh: + return nil, s.shutdownErr + } +} + +// Close is used to close the session and all streams. +// Attempts to send a GoAway before closing the connection. +func (s *Session) Close() error { + s.shutdownLock.Lock() + defer s.shutdownLock.Unlock() + + if s.shutdown { + return nil + } + s.shutdown = true + if s.shutdownErr == nil { + s.shutdownErr = ErrSessionShutdown + } + close(s.shutdownCh) + s.conn.Close() + <-s.recvDoneCh + + s.streamLock.Lock() + defer s.streamLock.Unlock() + for _, stream := range s.streams { + stream.forceClose() + } + return nil +} + +// exitErr is used to handle an error that is causing the +// session to terminate. +func (s *Session) exitErr(err error) { + s.shutdownLock.Lock() + if s.shutdownErr == nil { + s.shutdownErr = err + } + s.shutdownLock.Unlock() + s.Close() +} + +// GoAway can be used to prevent accepting further +// connections. It does not close the underlying conn. +func (s *Session) GoAway() error { + return s.waitForSend(s.goAway(goAwayNormal), nil) +} + +// goAway is used to send a goAway message +func (s *Session) goAway(reason uint32) header { + atomic.SwapInt32(&s.localGoAway, 1) + hdr := header(make([]byte, headerSize)) + hdr.encode(typeGoAway, 0, 0, reason) + return hdr +} + +// Ping is used to measure the RTT response time +func (s *Session) Ping() (time.Duration, error) { + // Get a channel for the ping + ch := make(chan struct{}) + + // Get a new ping id, mark as pending + s.pingLock.Lock() + id := s.pingID + s.pingID++ + s.pings[id] = ch + s.pingLock.Unlock() + + // Send the ping request + hdr := header(make([]byte, headerSize)) + hdr.encode(typePing, flagSYN, 0, id) + if err := s.waitForSend(hdr, nil); err != nil { + return 0, err + } + + // Wait for a response + start := time.Now() + select { + case <-ch: + case <-time.After(s.config.ConnectionWriteTimeout): + s.pingLock.Lock() + delete(s.pings, id) // Ignore it if a response comes later. + s.pingLock.Unlock() + return 0, ErrTimeout + case <-s.shutdownCh: + return 0, ErrSessionShutdown + } + + // Compute the RTT + return time.Now().Sub(start), nil +} + +// keepalive is a long running goroutine that periodically does +// a ping to keep the connection alive. +func (s *Session) keepalive() { + for { + select { + case <-time.After(s.config.KeepAliveInterval): + _, err := s.Ping() + if err != nil { + s.logger.Printf("[ERR] yamux: keepalive failed: %v", err) + s.exitErr(ErrKeepAliveTimeout) + return + } + case <-s.shutdownCh: + return + } + } +} + +// waitForSendErr waits to send a header, checking for a potential shutdown +func (s *Session) waitForSend(hdr header, body io.Reader) error { + errCh := make(chan error, 1) + return s.waitForSendErr(hdr, body, errCh) +} + +// waitForSendErr waits to send a header with optional data, checking for a +// potential shutdown. Since there's the expectation that sends can happen +// in a timely manner, we enforce the connection write timeout here. +func (s *Session) waitForSendErr(hdr header, body io.Reader, errCh chan error) error { + t := timerPool.Get() + timer := t.(*time.Timer) + timer.Reset(s.config.ConnectionWriteTimeout) + defer func() { + timer.Stop() + select { + case <-timer.C: + default: + } + timerPool.Put(t) + }() + + ready := sendReady{Hdr: hdr, Body: body, Err: errCh} + select { + case s.sendCh <- ready: + case <-s.shutdownCh: + return ErrSessionShutdown + case <-timer.C: + return ErrConnectionWriteTimeout + } + + select { + case err := <-errCh: + return err + case <-s.shutdownCh: + return ErrSessionShutdown + case <-timer.C: + return ErrConnectionWriteTimeout + } +} + +// sendNoWait does a send without waiting. Since there's the expectation that +// the send happens right here, we enforce the connection write timeout if we +// can't queue the header to be sent. +func (s *Session) sendNoWait(hdr header) error { + t := timerPool.Get() + timer := t.(*time.Timer) + timer.Reset(s.config.ConnectionWriteTimeout) + defer func() { + timer.Stop() + select { + case <-timer.C: + default: + } + timerPool.Put(t) + }() + + select { + case s.sendCh <- sendReady{Hdr: hdr}: + return nil + case <-s.shutdownCh: + return ErrSessionShutdown + case <-timer.C: + return ErrConnectionWriteTimeout + } +} + +// send is a long running goroutine that sends data +func (s *Session) send() { + for { + select { + case ready := <-s.sendCh: + // Send a header if ready + if ready.Hdr != nil { + sent := 0 + for sent < len(ready.Hdr) { + n, err := s.conn.Write(ready.Hdr[sent:]) + if err != nil { + s.logger.Printf("[ERR] yamux: Failed to write header: %v", err) + asyncSendErr(ready.Err, err) + s.exitErr(err) + return + } + sent += n + } + } + + // Send data from a body if given + if ready.Body != nil { + _, err := io.Copy(s.conn, ready.Body) + if err != nil { + s.logger.Printf("[ERR] yamux: Failed to write body: %v", err) + asyncSendErr(ready.Err, err) + s.exitErr(err) + return + } + } + + // No error, successful send + asyncSendErr(ready.Err, nil) + case <-s.shutdownCh: + return + } + } +} + +// recv is a long running goroutine that accepts new data +func (s *Session) recv() { + if err := s.recvLoop(); err != nil { + s.exitErr(err) + } +} + +// Ensure that the index of the handler (typeData/typeWindowUpdate/etc) matches the message type +var ( + handlers = []func(*Session, header) error{ + typeData: (*Session).handleStreamMessage, + typeWindowUpdate: (*Session).handleStreamMessage, + typePing: (*Session).handlePing, + typeGoAway: (*Session).handleGoAway, + } +) + +// recvLoop continues to receive data until a fatal error is encountered +func (s *Session) recvLoop() error { + defer close(s.recvDoneCh) + hdr := header(make([]byte, headerSize)) + for { + // Read the header + if _, err := io.ReadFull(s.bufRead, hdr); err != nil { + if err != io.EOF && !strings.Contains(err.Error(), "closed") && !strings.Contains(err.Error(), "reset by peer") { + s.logger.Printf("[ERR] yamux: Failed to read header: %v", err) + } + return err + } + + // Verify the version + if hdr.Version() != protoVersion { + s.logger.Printf("[ERR] yamux: Invalid protocol version: %d", hdr.Version()) + return ErrInvalidVersion + } + + mt := hdr.MsgType() + if mt < typeData || mt > typeGoAway { + return ErrInvalidMsgType + } + + if err := handlers[mt](s, hdr); err != nil { + return err + } + } +} + +// handleStreamMessage handles either a data or window update frame +func (s *Session) handleStreamMessage(hdr header) error { + // Check for a new stream creation + id := hdr.StreamID() + flags := hdr.Flags() + if flags&flagSYN == flagSYN { + if err := s.incomingStream(id); err != nil { + return err + } + } + + // Get the stream + s.streamLock.Lock() + stream := s.streams[id] + s.streamLock.Unlock() + + // If we do not have a stream, likely we sent a RST + if stream == nil { + // Drain any data on the wire + if hdr.MsgType() == typeData && hdr.Length() > 0 { + s.logger.Printf("[WARN] yamux: Discarding data for stream: %d", id) + if _, err := io.CopyN(ioutil.Discard, s.bufRead, int64(hdr.Length())); err != nil { + s.logger.Printf("[ERR] yamux: Failed to discard data: %v", err) + return nil + } + } else { + s.logger.Printf("[WARN] yamux: frame for missing stream: %v", hdr) + } + return nil + } + + // Check if this is a window update + if hdr.MsgType() == typeWindowUpdate { + if err := stream.incrSendWindow(hdr, flags); err != nil { + if sendErr := s.sendNoWait(s.goAway(goAwayProtoErr)); sendErr != nil { + s.logger.Printf("[WARN] yamux: failed to send go away: %v", sendErr) + } + return err + } + return nil + } + + // Read the new data + if err := stream.readData(hdr, flags, s.bufRead); err != nil { + if sendErr := s.sendNoWait(s.goAway(goAwayProtoErr)); sendErr != nil { + s.logger.Printf("[WARN] yamux: failed to send go away: %v", sendErr) + } + return err + } + return nil +} + +// handlePing is invokde for a typePing frame +func (s *Session) handlePing(hdr header) error { + flags := hdr.Flags() + pingID := hdr.Length() + + // Check if this is a query, respond back in a separate context so we + // don't interfere with the receiving thread blocking for the write. + if flags&flagSYN == flagSYN { + go func() { + hdr := header(make([]byte, headerSize)) + hdr.encode(typePing, flagACK, 0, pingID) + if err := s.sendNoWait(hdr); err != nil { + s.logger.Printf("[WARN] yamux: failed to send ping reply: %v", err) + } + }() + return nil + } + + // Handle a response + s.pingLock.Lock() + ch := s.pings[pingID] + if ch != nil { + delete(s.pings, pingID) + close(ch) + } + s.pingLock.Unlock() + return nil +} + +// handleGoAway is invokde for a typeGoAway frame +func (s *Session) handleGoAway(hdr header) error { + code := hdr.Length() + switch code { + case goAwayNormal: + atomic.SwapInt32(&s.remoteGoAway, 1) + case goAwayProtoErr: + s.logger.Printf("[ERR] yamux: received protocol error go away") + return fmt.Errorf("yamux protocol error") + case goAwayInternalErr: + s.logger.Printf("[ERR] yamux: received internal error go away") + return fmt.Errorf("remote yamux internal error") + default: + s.logger.Printf("[ERR] yamux: received unexpected go away") + return fmt.Errorf("unexpected go away received") + } + return nil +} + +// incomingStream is used to create a new incoming stream +func (s *Session) incomingStream(id uint32) error { + // Reject immediately if we are doing a go away + if atomic.LoadInt32(&s.localGoAway) == 1 { + hdr := header(make([]byte, headerSize)) + hdr.encode(typeWindowUpdate, flagRST, id, 0) + return s.sendNoWait(hdr) + } + + // Allocate a new stream + stream := newStream(s, id, streamSYNReceived) + + s.streamLock.Lock() + defer s.streamLock.Unlock() + + // Check if stream already exists + if _, ok := s.streams[id]; ok { + s.logger.Printf("[ERR] yamux: duplicate stream declared") + if sendErr := s.sendNoWait(s.goAway(goAwayProtoErr)); sendErr != nil { + s.logger.Printf("[WARN] yamux: failed to send go away: %v", sendErr) + } + return ErrDuplicateStream + } + + // Register the stream + s.streams[id] = stream + + // Check if we've exceeded the backlog + select { + case s.acceptCh <- stream: + return nil + default: + // Backlog exceeded! RST the stream + s.logger.Printf("[WARN] yamux: backlog exceeded, forcing connection reset") + delete(s.streams, id) + stream.sendHdr.encode(typeWindowUpdate, flagRST, id, 0) + return s.sendNoWait(stream.sendHdr) + } +} + +// closeStream is used to close a stream once both sides have +// issued a close. If there was an in-flight SYN and the stream +// was not yet established, then this will give the credit back. +func (s *Session) closeStream(id uint32) { + s.streamLock.Lock() + if _, ok := s.inflight[id]; ok { + select { + case <-s.synCh: + default: + s.logger.Printf("[ERR] yamux: SYN tracking out of sync") + } + } + delete(s.streams, id) + s.streamLock.Unlock() +} + +// establishStream is used to mark a stream that was in the +// SYN Sent state as established. +func (s *Session) establishStream(id uint32) { + s.streamLock.Lock() + if _, ok := s.inflight[id]; ok { + delete(s.inflight, id) + } else { + s.logger.Printf("[ERR] yamux: established stream without inflight SYN (no tracking entry)") + } + select { + case <-s.synCh: + default: + s.logger.Printf("[ERR] yamux: established stream without inflight SYN (didn't have semaphore)") + } + s.streamLock.Unlock() +} diff --git a/vendor/github.com/hashicorp/yamux/session_test.go b/vendor/github.com/hashicorp/yamux/session_test.go new file mode 100644 index 00000000..1645e2b7 --- /dev/null +++ b/vendor/github.com/hashicorp/yamux/session_test.go @@ -0,0 +1,1256 @@ +package yamux + +import ( + "bytes" + "fmt" + "io" + "io/ioutil" + "log" + "reflect" + "runtime" + "strings" + "sync" + "testing" + "time" +) + +type logCapture struct{ bytes.Buffer } + +func (l *logCapture) logs() []string { + return strings.Split(strings.TrimSpace(l.String()), "\n") +} + +func (l *logCapture) match(expect []string) bool { + return reflect.DeepEqual(l.logs(), expect) +} + +func captureLogs(s *Session) *logCapture { + buf := new(logCapture) + s.logger = log.New(buf, "", 0) + return buf +} + +type pipeConn struct { + reader *io.PipeReader + writer *io.PipeWriter + writeBlocker sync.Mutex +} + +func (p *pipeConn) Read(b []byte) (int, error) { + return p.reader.Read(b) +} + +func (p *pipeConn) Write(b []byte) (int, error) { + p.writeBlocker.Lock() + defer p.writeBlocker.Unlock() + return p.writer.Write(b) +} + +func (p *pipeConn) Close() error { + p.reader.Close() + return p.writer.Close() +} + +func testConn() (io.ReadWriteCloser, io.ReadWriteCloser) { + read1, write1 := io.Pipe() + read2, write2 := io.Pipe() + conn1 := &pipeConn{reader: read1, writer: write2} + conn2 := &pipeConn{reader: read2, writer: write1} + return conn1, conn2 +} + +func testConf() *Config { + conf := DefaultConfig() + conf.AcceptBacklog = 64 + conf.KeepAliveInterval = 100 * time.Millisecond + conf.ConnectionWriteTimeout = 250 * time.Millisecond + return conf +} + +func testConfNoKeepAlive() *Config { + conf := testConf() + conf.EnableKeepAlive = false + return conf +} + +func testClientServer() (*Session, *Session) { + return testClientServerConfig(testConf()) +} + +func testClientServerConfig(conf *Config) (*Session, *Session) { + conn1, conn2 := testConn() + client, _ := Client(conn1, conf) + server, _ := Server(conn2, conf) + return client, server +} + +func TestPing(t *testing.T) { + client, server := testClientServer() + defer client.Close() + defer server.Close() + + rtt, err := client.Ping() + if err != nil { + t.Fatalf("err: %v", err) + } + if rtt == 0 { + t.Fatalf("bad: %v", rtt) + } + + rtt, err = server.Ping() + if err != nil { + t.Fatalf("err: %v", err) + } + if rtt == 0 { + t.Fatalf("bad: %v", rtt) + } +} + +func TestPing_Timeout(t *testing.T) { + client, server := testClientServerConfig(testConfNoKeepAlive()) + defer client.Close() + defer server.Close() + + // Prevent the client from responding + clientConn := client.conn.(*pipeConn) + clientConn.writeBlocker.Lock() + + errCh := make(chan error, 1) + go func() { + _, err := server.Ping() // Ping via the server session + errCh <- err + }() + + select { + case err := <-errCh: + if err != ErrTimeout { + t.Fatalf("err: %v", err) + } + case <-time.After(client.config.ConnectionWriteTimeout * 2): + t.Fatalf("failed to timeout within expected %v", client.config.ConnectionWriteTimeout) + } + + // Verify that we recover, even if we gave up + clientConn.writeBlocker.Unlock() + + go func() { + _, err := server.Ping() // Ping via the server session + errCh <- err + }() + + select { + case err := <-errCh: + if err != nil { + t.Fatalf("err: %v", err) + } + case <-time.After(client.config.ConnectionWriteTimeout): + t.Fatalf("timeout") + } +} + +func TestCloseBeforeAck(t *testing.T) { + cfg := testConf() + cfg.AcceptBacklog = 8 + client, server := testClientServerConfig(cfg) + + defer client.Close() + defer server.Close() + + for i := 0; i < 8; i++ { + s, err := client.OpenStream() + if err != nil { + t.Fatal(err) + } + s.Close() + } + + for i := 0; i < 8; i++ { + s, err := server.AcceptStream() + if err != nil { + t.Fatal(err) + } + s.Close() + } + + done := make(chan struct{}) + go func() { + defer close(done) + s, err := client.OpenStream() + if err != nil { + t.Fatal(err) + } + s.Close() + }() + + select { + case <-done: + case <-time.After(time.Second * 5): + t.Fatal("timed out trying to open stream") + } +} + +func TestAccept(t *testing.T) { + client, server := testClientServer() + defer client.Close() + defer server.Close() + + if client.NumStreams() != 0 { + t.Fatalf("bad") + } + if server.NumStreams() != 0 { + t.Fatalf("bad") + } + + wg := &sync.WaitGroup{} + wg.Add(4) + + go func() { + defer wg.Done() + stream, err := server.AcceptStream() + if err != nil { + t.Fatalf("err: %v", err) + } + if id := stream.StreamID(); id != 1 { + t.Fatalf("bad: %v", id) + } + if err := stream.Close(); err != nil { + t.Fatalf("err: %v", err) + } + }() + + go func() { + defer wg.Done() + stream, err := client.AcceptStream() + if err != nil { + t.Fatalf("err: %v", err) + } + if id := stream.StreamID(); id != 2 { + t.Fatalf("bad: %v", id) + } + if err := stream.Close(); err != nil { + t.Fatalf("err: %v", err) + } + }() + + go func() { + defer wg.Done() + stream, err := server.OpenStream() + if err != nil { + t.Fatalf("err: %v", err) + } + if id := stream.StreamID(); id != 2 { + t.Fatalf("bad: %v", id) + } + if err := stream.Close(); err != nil { + t.Fatalf("err: %v", err) + } + }() + + go func() { + defer wg.Done() + stream, err := client.OpenStream() + if err != nil { + t.Fatalf("err: %v", err) + } + if id := stream.StreamID(); id != 1 { + t.Fatalf("bad: %v", id) + } + if err := stream.Close(); err != nil { + t.Fatalf("err: %v", err) + } + }() + + doneCh := make(chan struct{}) + go func() { + wg.Wait() + close(doneCh) + }() + + select { + case <-doneCh: + case <-time.After(time.Second): + panic("timeout") + } +} + +func TestNonNilInterface(t *testing.T) { + _, server := testClientServer() + server.Close() + + conn, err := server.Accept() + if err != nil && conn != nil { + t.Error("bad: accept should return a connection of nil value") + } + + conn, err = server.Open() + if err != nil && conn != nil { + t.Error("bad: open should return a connection of nil value") + } +} + +func TestSendData_Small(t *testing.T) { + client, server := testClientServer() + defer client.Close() + defer server.Close() + + wg := &sync.WaitGroup{} + wg.Add(2) + + go func() { + defer wg.Done() + stream, err := server.AcceptStream() + if err != nil { + t.Fatalf("err: %v", err) + } + + if server.NumStreams() != 1 { + t.Fatalf("bad") + } + + buf := make([]byte, 4) + for i := 0; i < 1000; i++ { + n, err := stream.Read(buf) + if err != nil { + t.Fatalf("err: %v", err) + } + if n != 4 { + t.Fatalf("short read: %d", n) + } + if string(buf) != "test" { + t.Fatalf("bad: %s", buf) + } + } + + if err := stream.Close(); err != nil { + t.Fatalf("err: %v", err) + } + }() + + go func() { + defer wg.Done() + stream, err := client.Open() + if err != nil { + t.Fatalf("err: %v", err) + } + + if client.NumStreams() != 1 { + t.Fatalf("bad") + } + + for i := 0; i < 1000; i++ { + n, err := stream.Write([]byte("test")) + if err != nil { + t.Fatalf("err: %v", err) + } + if n != 4 { + t.Fatalf("short write %d", n) + } + } + + if err := stream.Close(); err != nil { + t.Fatalf("err: %v", err) + } + }() + + doneCh := make(chan struct{}) + go func() { + wg.Wait() + close(doneCh) + }() + select { + case <-doneCh: + case <-time.After(time.Second): + panic("timeout") + } + + if client.NumStreams() != 0 { + t.Fatalf("bad") + } + if server.NumStreams() != 0 { + t.Fatalf("bad") + } +} + +func TestSendData_Large(t *testing.T) { + client, server := testClientServer() + defer client.Close() + defer server.Close() + + const ( + sendSize = 250 * 1024 * 1024 + recvSize = 4 * 1024 + ) + + data := make([]byte, sendSize) + for idx := range data { + data[idx] = byte(idx % 256) + } + + wg := &sync.WaitGroup{} + wg.Add(2) + + go func() { + defer wg.Done() + stream, err := server.AcceptStream() + if err != nil { + t.Fatalf("err: %v", err) + } + var sz int + buf := make([]byte, recvSize) + for i := 0; i < sendSize/recvSize; i++ { + n, err := stream.Read(buf) + if err != nil { + t.Fatalf("err: %v", err) + } + if n != recvSize { + t.Fatalf("short read: %d", n) + } + sz += n + for idx := range buf { + if buf[idx] != byte(idx%256) { + t.Fatalf("bad: %v %v %v", i, idx, buf[idx]) + } + } + } + + if err := stream.Close(); err != nil { + t.Fatalf("err: %v", err) + } + + t.Logf("cap=%d, n=%d\n", stream.recvBuf.Cap(), sz) + }() + + go func() { + defer wg.Done() + stream, err := client.Open() + if err != nil { + t.Fatalf("err: %v", err) + } + + n, err := stream.Write(data) + if err != nil { + t.Fatalf("err: %v", err) + } + if n != len(data) { + t.Fatalf("short write %d", n) + } + + if err := stream.Close(); err != nil { + t.Fatalf("err: %v", err) + } + }() + + doneCh := make(chan struct{}) + go func() { + wg.Wait() + close(doneCh) + }() + select { + case <-doneCh: + case <-time.After(5 * time.Second): + panic("timeout") + } +} + +func TestGoAway(t *testing.T) { + client, server := testClientServer() + defer client.Close() + defer server.Close() + + if err := server.GoAway(); err != nil { + t.Fatalf("err: %v", err) + } + + _, err := client.Open() + if err != ErrRemoteGoAway { + t.Fatalf("err: %v", err) + } +} + +func TestManyStreams(t *testing.T) { + client, server := testClientServer() + defer client.Close() + defer server.Close() + + wg := &sync.WaitGroup{} + + acceptor := func(i int) { + defer wg.Done() + stream, err := server.AcceptStream() + if err != nil { + t.Fatalf("err: %v", err) + } + defer stream.Close() + + buf := make([]byte, 512) + for { + n, err := stream.Read(buf) + if err == io.EOF { + return + } + if err != nil { + t.Fatalf("err: %v", err) + } + if n == 0 { + t.Fatalf("err: %v", err) + } + } + } + sender := func(i int) { + defer wg.Done() + stream, err := client.Open() + if err != nil { + t.Fatalf("err: %v", err) + } + defer stream.Close() + + msg := fmt.Sprintf("%08d", i) + for i := 0; i < 1000; i++ { + n, err := stream.Write([]byte(msg)) + if err != nil { + t.Fatalf("err: %v", err) + } + if n != len(msg) { + t.Fatalf("short write %d", n) + } + } + } + + for i := 0; i < 50; i++ { + wg.Add(2) + go acceptor(i) + go sender(i) + } + + wg.Wait() +} + +func TestManyStreams_PingPong(t *testing.T) { + client, server := testClientServer() + defer client.Close() + defer server.Close() + + wg := &sync.WaitGroup{} + + ping := []byte("ping") + pong := []byte("pong") + + acceptor := func(i int) { + defer wg.Done() + stream, err := server.AcceptStream() + if err != nil { + t.Fatalf("err: %v", err) + } + defer stream.Close() + + buf := make([]byte, 4) + for { + // Read the 'ping' + n, err := stream.Read(buf) + if err == io.EOF { + return + } + if err != nil { + t.Fatalf("err: %v", err) + } + if n != 4 { + t.Fatalf("err: %v", err) + } + if !bytes.Equal(buf, ping) { + t.Fatalf("bad: %s", buf) + } + + // Shrink the internal buffer! + stream.Shrink() + + // Write out the 'pong' + n, err = stream.Write(pong) + if err != nil { + t.Fatalf("err: %v", err) + } + if n != 4 { + t.Fatalf("err: %v", err) + } + } + } + sender := func(i int) { + defer wg.Done() + stream, err := client.OpenStream() + if err != nil { + t.Fatalf("err: %v", err) + } + defer stream.Close() + + buf := make([]byte, 4) + for i := 0; i < 1000; i++ { + // Send the 'ping' + n, err := stream.Write(ping) + if err != nil { + t.Fatalf("err: %v", err) + } + if n != 4 { + t.Fatalf("short write %d", n) + } + + // Read the 'pong' + n, err = stream.Read(buf) + if err != nil { + t.Fatalf("err: %v", err) + } + if n != 4 { + t.Fatalf("err: %v", err) + } + if !bytes.Equal(buf, pong) { + t.Fatalf("bad: %s", buf) + } + + // Shrink the buffer + stream.Shrink() + } + } + + for i := 0; i < 50; i++ { + wg.Add(2) + go acceptor(i) + go sender(i) + } + + wg.Wait() +} + +func TestHalfClose(t *testing.T) { + client, server := testClientServer() + defer client.Close() + defer server.Close() + + stream, err := client.Open() + if err != nil { + t.Fatalf("err: %v", err) + } + if _, err = stream.Write([]byte("a")); err != nil { + t.Fatalf("err: %v", err) + } + + stream2, err := server.Accept() + if err != nil { + t.Fatalf("err: %v", err) + } + stream2.Close() // Half close + + buf := make([]byte, 4) + n, err := stream2.Read(buf) + if err != nil { + t.Fatalf("err: %v", err) + } + if n != 1 { + t.Fatalf("bad: %v", n) + } + + // Send more + if _, err = stream.Write([]byte("bcd")); err != nil { + t.Fatalf("err: %v", err) + } + stream.Close() + + // Read after close + n, err = stream2.Read(buf) + if err != nil { + t.Fatalf("err: %v", err) + } + if n != 3 { + t.Fatalf("bad: %v", n) + } + + // EOF after close + n, err = stream2.Read(buf) + if err != io.EOF { + t.Fatalf("err: %v", err) + } + if n != 0 { + t.Fatalf("bad: %v", n) + } +} + +func TestReadDeadline(t *testing.T) { + client, server := testClientServer() + defer client.Close() + defer server.Close() + + stream, err := client.Open() + if err != nil { + t.Fatalf("err: %v", err) + } + defer stream.Close() + + stream2, err := server.Accept() + if err != nil { + t.Fatalf("err: %v", err) + } + defer stream2.Close() + + if err := stream.SetReadDeadline(time.Now().Add(5 * time.Millisecond)); err != nil { + t.Fatalf("err: %v", err) + } + + buf := make([]byte, 4) + if _, err := stream.Read(buf); err != ErrTimeout { + t.Fatalf("err: %v", err) + } +} + +func TestWriteDeadline(t *testing.T) { + client, server := testClientServer() + defer client.Close() + defer server.Close() + + stream, err := client.Open() + if err != nil { + t.Fatalf("err: %v", err) + } + defer stream.Close() + + stream2, err := server.Accept() + if err != nil { + t.Fatalf("err: %v", err) + } + defer stream2.Close() + + if err := stream.SetWriteDeadline(time.Now().Add(50 * time.Millisecond)); err != nil { + t.Fatalf("err: %v", err) + } + + buf := make([]byte, 512) + for i := 0; i < int(initialStreamWindow); i++ { + _, err := stream.Write(buf) + if err != nil && err == ErrTimeout { + return + } else if err != nil { + t.Fatalf("err: %v", err) + } + } + t.Fatalf("Expected timeout") +} + +func TestBacklogExceeded(t *testing.T) { + client, server := testClientServer() + defer client.Close() + defer server.Close() + + // Fill the backlog + max := client.config.AcceptBacklog + for i := 0; i < max; i++ { + stream, err := client.Open() + if err != nil { + t.Fatalf("err: %v", err) + } + defer stream.Close() + + if _, err := stream.Write([]byte("foo")); err != nil { + t.Fatalf("err: %v", err) + } + } + + // Attempt to open a new stream + errCh := make(chan error, 1) + go func() { + _, err := client.Open() + errCh <- err + }() + + // Shutdown the server + go func() { + time.Sleep(10 * time.Millisecond) + server.Close() + }() + + select { + case err := <-errCh: + if err == nil { + t.Fatalf("open should fail") + } + case <-time.After(time.Second): + t.Fatalf("timeout") + } +} + +func TestKeepAlive(t *testing.T) { + client, server := testClientServer() + defer client.Close() + defer server.Close() + + time.Sleep(200 * time.Millisecond) + + // Ping value should increase + client.pingLock.Lock() + defer client.pingLock.Unlock() + if client.pingID == 0 { + t.Fatalf("should ping") + } + + server.pingLock.Lock() + defer server.pingLock.Unlock() + if server.pingID == 0 { + t.Fatalf("should ping") + } +} + +func TestKeepAlive_Timeout(t *testing.T) { + conn1, conn2 := testConn() + + clientConf := testConf() + clientConf.ConnectionWriteTimeout = time.Hour // We're testing keep alives, not connection writes + clientConf.EnableKeepAlive = false // Just test one direction, so it's deterministic who hangs up on whom + client, _ := Client(conn1, clientConf) + defer client.Close() + + server, _ := Server(conn2, testConf()) + defer server.Close() + + _ = captureLogs(client) // Client logs aren't part of the test + serverLogs := captureLogs(server) + + errCh := make(chan error, 1) + go func() { + _, err := server.Accept() // Wait until server closes + errCh <- err + }() + + // Prevent the client from responding + clientConn := client.conn.(*pipeConn) + clientConn.writeBlocker.Lock() + + select { + case err := <-errCh: + if err != ErrKeepAliveTimeout { + t.Fatalf("unexpected error: %v", err) + } + case <-time.After(1 * time.Second): + t.Fatalf("timeout waiting for timeout") + } + + if !server.IsClosed() { + t.Fatalf("server should have closed") + } + + if !serverLogs.match([]string{"[ERR] yamux: keepalive failed: i/o deadline reached"}) { + t.Fatalf("server log incorect: %v", serverLogs.logs()) + } +} + +func TestLargeWindow(t *testing.T) { + conf := DefaultConfig() + conf.MaxStreamWindowSize *= 2 + + client, server := testClientServerConfig(conf) + defer client.Close() + defer server.Close() + + stream, err := client.Open() + if err != nil { + t.Fatalf("err: %v", err) + } + defer stream.Close() + + stream2, err := server.Accept() + if err != nil { + t.Fatalf("err: %v", err) + } + defer stream2.Close() + + stream.SetWriteDeadline(time.Now().Add(10 * time.Millisecond)) + buf := make([]byte, conf.MaxStreamWindowSize) + n, err := stream.Write(buf) + if err != nil { + t.Fatalf("err: %v", err) + } + if n != len(buf) { + t.Fatalf("short write: %d", n) + } +} + +type UnlimitedReader struct{} + +func (u *UnlimitedReader) Read(p []byte) (int, error) { + runtime.Gosched() + return len(p), nil +} + +func TestSendData_VeryLarge(t *testing.T) { + client, server := testClientServer() + defer client.Close() + defer server.Close() + + var n int64 = 1 * 1024 * 1024 * 1024 + var workers int = 16 + + wg := &sync.WaitGroup{} + wg.Add(workers * 2) + + for i := 0; i < workers; i++ { + go func() { + defer wg.Done() + stream, err := server.AcceptStream() + if err != nil { + t.Fatalf("err: %v", err) + } + defer stream.Close() + + buf := make([]byte, 4) + _, err = stream.Read(buf) + if err != nil { + t.Fatalf("err: %v", err) + } + if !bytes.Equal(buf, []byte{0, 1, 2, 3}) { + t.Fatalf("bad header") + } + + recv, err := io.Copy(ioutil.Discard, stream) + if err != nil { + t.Fatalf("err: %v", err) + } + if recv != n { + t.Fatalf("bad: %v", recv) + } + }() + } + for i := 0; i < workers; i++ { + go func() { + defer wg.Done() + stream, err := client.Open() + if err != nil { + t.Fatalf("err: %v", err) + } + defer stream.Close() + + _, err = stream.Write([]byte{0, 1, 2, 3}) + if err != nil { + t.Fatalf("err: %v", err) + } + + unlimited := &UnlimitedReader{} + sent, err := io.Copy(stream, io.LimitReader(unlimited, n)) + if err != nil { + t.Fatalf("err: %v", err) + } + if sent != n { + t.Fatalf("bad: %v", sent) + } + }() + } + + doneCh := make(chan struct{}) + go func() { + wg.Wait() + close(doneCh) + }() + select { + case <-doneCh: + case <-time.After(20 * time.Second): + panic("timeout") + } +} + +func TestBacklogExceeded_Accept(t *testing.T) { + client, server := testClientServer() + defer client.Close() + defer server.Close() + + max := 5 * client.config.AcceptBacklog + go func() { + for i := 0; i < max; i++ { + stream, err := server.Accept() + if err != nil { + t.Fatalf("err: %v", err) + } + defer stream.Close() + } + }() + + // Fill the backlog + for i := 0; i < max; i++ { + stream, err := client.Open() + if err != nil { + t.Fatalf("err: %v", err) + } + defer stream.Close() + + if _, err := stream.Write([]byte("foo")); err != nil { + t.Fatalf("err: %v", err) + } + } +} + +func TestSession_WindowUpdateWriteDuringRead(t *testing.T) { + client, server := testClientServerConfig(testConfNoKeepAlive()) + defer client.Close() + defer server.Close() + + var wg sync.WaitGroup + wg.Add(2) + + // Choose a huge flood size that we know will result in a window update. + flood := int64(client.config.MaxStreamWindowSize) - 1 + + // The server will accept a new stream and then flood data to it. + go func() { + defer wg.Done() + + stream, err := server.AcceptStream() + if err != nil { + t.Fatalf("err: %v", err) + } + defer stream.Close() + + n, err := stream.Write(make([]byte, flood)) + if err != nil { + t.Fatalf("err: %v", err) + } + if int64(n) != flood { + t.Fatalf("short write: %d", n) + } + }() + + // The client will open a stream, block outbound writes, and then + // listen to the flood from the server, which should time out since + // it won't be able to send the window update. + go func() { + defer wg.Done() + + stream, err := client.OpenStream() + if err != nil { + t.Fatalf("err: %v", err) + } + defer stream.Close() + + conn := client.conn.(*pipeConn) + conn.writeBlocker.Lock() + + _, err = stream.Read(make([]byte, flood)) + if err != ErrConnectionWriteTimeout { + t.Fatalf("err: %v", err) + } + }() + + wg.Wait() +} + +func TestSession_PartialReadWindowUpdate(t *testing.T) { + client, server := testClientServerConfig(testConfNoKeepAlive()) + defer client.Close() + defer server.Close() + + var wg sync.WaitGroup + wg.Add(1) + + // Choose a huge flood size that we know will result in a window update. + flood := int64(client.config.MaxStreamWindowSize) + var wr *Stream + + // The server will accept a new stream and then flood data to it. + go func() { + defer wg.Done() + + var err error + wr, err = server.AcceptStream() + if err != nil { + t.Fatalf("err: %v", err) + } + defer wr.Close() + + if wr.sendWindow != client.config.MaxStreamWindowSize { + t.Fatalf("sendWindow: exp=%d, got=%d", client.config.MaxStreamWindowSize, wr.sendWindow) + } + + n, err := wr.Write(make([]byte, flood)) + if err != nil { + t.Fatalf("err: %v", err) + } + if int64(n) != flood { + t.Fatalf("short write: %d", n) + } + if wr.sendWindow != 0 { + t.Fatalf("sendWindow: exp=%d, got=%d", 0, wr.sendWindow) + } + }() + + stream, err := client.OpenStream() + if err != nil { + t.Fatalf("err: %v", err) + } + defer stream.Close() + + wg.Wait() + + _, err = stream.Read(make([]byte, flood/2+1)) + + if exp := uint32(flood/2 + 1); wr.sendWindow != exp { + t.Errorf("sendWindow: exp=%d, got=%d", exp, wr.sendWindow) + } +} + +func TestSession_sendNoWait_Timeout(t *testing.T) { + client, server := testClientServerConfig(testConfNoKeepAlive()) + defer client.Close() + defer server.Close() + + var wg sync.WaitGroup + wg.Add(2) + + go func() { + defer wg.Done() + + stream, err := server.AcceptStream() + if err != nil { + t.Fatalf("err: %v", err) + } + defer stream.Close() + }() + + // The client will open the stream and then block outbound writes, we'll + // probe sendNoWait once it gets into that state. + go func() { + defer wg.Done() + + stream, err := client.OpenStream() + if err != nil { + t.Fatalf("err: %v", err) + } + defer stream.Close() + + conn := client.conn.(*pipeConn) + conn.writeBlocker.Lock() + + hdr := header(make([]byte, headerSize)) + hdr.encode(typePing, flagACK, 0, 0) + for { + err = client.sendNoWait(hdr) + if err == nil { + continue + } else if err == ErrConnectionWriteTimeout { + break + } else { + t.Fatalf("err: %v", err) + } + } + }() + + wg.Wait() +} + +func TestSession_PingOfDeath(t *testing.T) { + client, server := testClientServerConfig(testConfNoKeepAlive()) + defer client.Close() + defer server.Close() + + var wg sync.WaitGroup + wg.Add(2) + + var doPingOfDeath sync.Mutex + doPingOfDeath.Lock() + + // This is used later to block outbound writes. + conn := server.conn.(*pipeConn) + + // The server will accept a stream, block outbound writes, and then + // flood its send channel so that no more headers can be queued. + go func() { + defer wg.Done() + + stream, err := server.AcceptStream() + if err != nil { + t.Fatalf("err: %v", err) + } + defer stream.Close() + + conn.writeBlocker.Lock() + for { + hdr := header(make([]byte, headerSize)) + hdr.encode(typePing, 0, 0, 0) + err = server.sendNoWait(hdr) + if err == nil { + continue + } else if err == ErrConnectionWriteTimeout { + break + } else { + t.Fatalf("err: %v", err) + } + } + + doPingOfDeath.Unlock() + }() + + // The client will open a stream and then send the server a ping once it + // can no longer write. This makes sure the server doesn't deadlock reads + // while trying to reply to the ping with no ability to write. + go func() { + defer wg.Done() + + stream, err := client.OpenStream() + if err != nil { + t.Fatalf("err: %v", err) + } + defer stream.Close() + + // This ping will never unblock because the ping id will never + // show up in a response. + doPingOfDeath.Lock() + go func() { client.Ping() }() + + // Wait for a while to make sure the previous ping times out, + // then turn writes back on and make sure a ping works again. + time.Sleep(2 * server.config.ConnectionWriteTimeout) + conn.writeBlocker.Unlock() + if _, err = client.Ping(); err != nil { + t.Fatalf("err: %v", err) + } + }() + + wg.Wait() +} + +func TestSession_ConnectionWriteTimeout(t *testing.T) { + client, server := testClientServerConfig(testConfNoKeepAlive()) + defer client.Close() + defer server.Close() + + var wg sync.WaitGroup + wg.Add(2) + + go func() { + defer wg.Done() + + stream, err := server.AcceptStream() + if err != nil { + t.Fatalf("err: %v", err) + } + defer stream.Close() + }() + + // The client will open the stream and then block outbound writes, we'll + // tee up a write and make sure it eventually times out. + go func() { + defer wg.Done() + + stream, err := client.OpenStream() + if err != nil { + t.Fatalf("err: %v", err) + } + defer stream.Close() + + conn := client.conn.(*pipeConn) + conn.writeBlocker.Lock() + + // Since the write goroutine is blocked then this will return a + // timeout since it can't get feedback about whether the write + // worked. + n, err := stream.Write([]byte("hello")) + if err != ErrConnectionWriteTimeout { + t.Fatalf("err: %v", err) + } + if n != 0 { + t.Fatalf("lied about writes: %d", n) + } + }() + + wg.Wait() +} diff --git a/vendor/github.com/hashicorp/yamux/spec.md b/vendor/github.com/hashicorp/yamux/spec.md new file mode 100644 index 00000000..183d797b --- /dev/null +++ b/vendor/github.com/hashicorp/yamux/spec.md @@ -0,0 +1,140 @@ +# Specification + +We use this document to detail the internal specification of Yamux. +This is used both as a guide for implementing Yamux, but also for +alternative interoperable libraries to be built. + +# Framing + +Yamux uses a streaming connection underneath, but imposes a message +framing so that it can be shared between many logical streams. Each +frame contains a header like: + +* Version (8 bits) +* Type (8 bits) +* Flags (16 bits) +* StreamID (32 bits) +* Length (32 bits) + +This means that each header has a 12 byte overhead. +All fields are encoded in network order (big endian). +Each field is described below: + +## Version Field + +The version field is used for future backward compatibility. At the +current time, the field is always set to 0, to indicate the initial +version. + +## Type Field + +The type field is used to switch the frame message type. The following +message types are supported: + +* 0x0 Data - Used to transmit data. May transmit zero length payloads + depending on the flags. + +* 0x1 Window Update - Used to updated the senders receive window size. + This is used to implement per-session flow control. + +* 0x2 Ping - Used to measure RTT. It can also be used to heart-beat + and do keep-alives over TCP. + +* 0x3 Go Away - Used to close a session. + +## Flag Field + +The flags field is used to provide additional information related +to the message type. The following flags are supported: + +* 0x1 SYN - Signals the start of a new stream. May be sent with a data or + window update message. Also sent with a ping to indicate outbound. + +* 0x2 ACK - Acknowledges the start of a new stream. May be sent with a data + or window update message. Also sent with a ping to indicate response. + +* 0x4 FIN - Performs a half-close of a stream. May be sent with a data + message or window update. + +* 0x8 RST - Reset a stream immediately. May be sent with a data or + window update message. + +## StreamID Field + +The StreamID field is used to identify the logical stream the frame +is addressing. The client side should use odd ID's, and the server even. +This prevents any collisions. Additionally, the 0 ID is reserved to represent +the session. + +Both Ping and Go Away messages should always use the 0 StreamID. + +## Length Field + +The meaning of the length field depends on the message type: + +* Data - provides the length of bytes following the header +* Window update - provides a delta update to the window size +* Ping - Contains an opaque value, echoed back +* Go Away - Contains an error code + +# Message Flow + +There is no explicit connection setup, as Yamux relies on an underlying +transport to be provided. However, there is a distinction between client +and server side of the connection. + +## Opening a stream + +To open a stream, an initial data or window update frame is sent +with a new StreamID. The SYN flag should be set to signal a new stream. + +The receiver must then reply with either a data or window update frame +with the StreamID along with the ACK flag to accept the stream or with +the RST flag to reject the stream. + +Because we are relying on the reliable stream underneath, a connection +can begin sending data once the SYN flag is sent. The corresponding +ACK does not need to be received. This is particularly well suited +for an RPC system where a client wants to open a stream and immediately +fire a request without waiting for the RTT of the ACK. + +This does introduce the possibility of a connection being rejected +after data has been sent already. This is a slight semantic difference +from TCP, where the conection cannot be refused after it is opened. +Clients should be prepared to handle this by checking for an error +that indicates a RST was received. + +## Closing a stream + +To close a stream, either side sends a data or window update frame +along with the FIN flag. This does a half-close indicating the sender +will send no further data. + +Once both sides have closed the connection, the stream is closed. + +Alternatively, if an error occurs, the RST flag can be used to +hard close a stream immediately. + +## Flow Control + +When Yamux is initially starts each stream with a 256KB window size. +There is no window size for the session. + +To prevent the streams from stalling, window update frames should be +sent regularly. Yamux can be configured to provide a larger limit for +windows sizes. Both sides assume the initial 256KB window, but can +immediately send a window update as part of the SYN/ACK indicating a +larger window. + +Both sides should track the number of bytes sent in Data frames +only, as only they are tracked as part of the window size. + +## Session termination + +When a session is being terminated, the Go Away message should +be sent. The Length should be set to one of the following to +provide an error code: + +* 0x0 Normal termination +* 0x1 Protocol error +* 0x2 Internal error diff --git a/vendor/github.com/hashicorp/yamux/stream.go b/vendor/github.com/hashicorp/yamux/stream.go new file mode 100644 index 00000000..aa239197 --- /dev/null +++ b/vendor/github.com/hashicorp/yamux/stream.go @@ -0,0 +1,470 @@ +package yamux + +import ( + "bytes" + "io" + "sync" + "sync/atomic" + "time" +) + +type streamState int + +const ( + streamInit streamState = iota + streamSYNSent + streamSYNReceived + streamEstablished + streamLocalClose + streamRemoteClose + streamClosed + streamReset +) + +// Stream is used to represent a logical stream +// within a session. +type Stream struct { + recvWindow uint32 + sendWindow uint32 + + id uint32 + session *Session + + state streamState + stateLock sync.Mutex + + recvBuf *bytes.Buffer + recvLock sync.Mutex + + controlHdr header + controlErr chan error + controlHdrLock sync.Mutex + + sendHdr header + sendErr chan error + sendLock sync.Mutex + + recvNotifyCh chan struct{} + sendNotifyCh chan struct{} + + readDeadline atomic.Value // time.Time + writeDeadline atomic.Value // time.Time +} + +// newStream is used to construct a new stream within +// a given session for an ID +func newStream(session *Session, id uint32, state streamState) *Stream { + s := &Stream{ + id: id, + session: session, + state: state, + controlHdr: header(make([]byte, headerSize)), + controlErr: make(chan error, 1), + sendHdr: header(make([]byte, headerSize)), + sendErr: make(chan error, 1), + recvWindow: initialStreamWindow, + sendWindow: initialStreamWindow, + recvNotifyCh: make(chan struct{}, 1), + sendNotifyCh: make(chan struct{}, 1), + } + s.readDeadline.Store(time.Time{}) + s.writeDeadline.Store(time.Time{}) + return s +} + +// Session returns the associated stream session +func (s *Stream) Session() *Session { + return s.session +} + +// StreamID returns the ID of this stream +func (s *Stream) StreamID() uint32 { + return s.id +} + +// Read is used to read from the stream +func (s *Stream) Read(b []byte) (n int, err error) { + defer asyncNotify(s.recvNotifyCh) +START: + s.stateLock.Lock() + switch s.state { + case streamLocalClose: + fallthrough + case streamRemoteClose: + fallthrough + case streamClosed: + s.recvLock.Lock() + if s.recvBuf == nil || s.recvBuf.Len() == 0 { + s.recvLock.Unlock() + s.stateLock.Unlock() + return 0, io.EOF + } + s.recvLock.Unlock() + case streamReset: + s.stateLock.Unlock() + return 0, ErrConnectionReset + } + s.stateLock.Unlock() + + // If there is no data available, block + s.recvLock.Lock() + if s.recvBuf == nil || s.recvBuf.Len() == 0 { + s.recvLock.Unlock() + goto WAIT + } + + // Read any bytes + n, _ = s.recvBuf.Read(b) + s.recvLock.Unlock() + + // Send a window update potentially + err = s.sendWindowUpdate() + return n, err + +WAIT: + var timeout <-chan time.Time + var timer *time.Timer + readDeadline := s.readDeadline.Load().(time.Time) + if !readDeadline.IsZero() { + delay := readDeadline.Sub(time.Now()) + timer = time.NewTimer(delay) + timeout = timer.C + } + select { + case <-s.recvNotifyCh: + if timer != nil { + timer.Stop() + } + goto START + case <-timeout: + return 0, ErrTimeout + } +} + +// Write is used to write to the stream +func (s *Stream) Write(b []byte) (n int, err error) { + s.sendLock.Lock() + defer s.sendLock.Unlock() + total := 0 + for total < len(b) { + n, err := s.write(b[total:]) + total += n + if err != nil { + return total, err + } + } + return total, nil +} + +// write is used to write to the stream, may return on +// a short write. +func (s *Stream) write(b []byte) (n int, err error) { + var flags uint16 + var max uint32 + var body io.Reader +START: + s.stateLock.Lock() + switch s.state { + case streamLocalClose: + fallthrough + case streamClosed: + s.stateLock.Unlock() + return 0, ErrStreamClosed + case streamReset: + s.stateLock.Unlock() + return 0, ErrConnectionReset + } + s.stateLock.Unlock() + + // If there is no data available, block + window := atomic.LoadUint32(&s.sendWindow) + if window == 0 { + goto WAIT + } + + // Determine the flags if any + flags = s.sendFlags() + + // Send up to our send window + max = min(window, uint32(len(b))) + body = bytes.NewReader(b[:max]) + + // Send the header + s.sendHdr.encode(typeData, flags, s.id, max) + if err = s.session.waitForSendErr(s.sendHdr, body, s.sendErr); err != nil { + return 0, err + } + + // Reduce our send window + atomic.AddUint32(&s.sendWindow, ^uint32(max-1)) + + // Unlock + return int(max), err + +WAIT: + var timeout <-chan time.Time + writeDeadline := s.writeDeadline.Load().(time.Time) + if !writeDeadline.IsZero() { + delay := writeDeadline.Sub(time.Now()) + timeout = time.After(delay) + } + select { + case <-s.sendNotifyCh: + goto START + case <-timeout: + return 0, ErrTimeout + } + return 0, nil +} + +// sendFlags determines any flags that are appropriate +// based on the current stream state +func (s *Stream) sendFlags() uint16 { + s.stateLock.Lock() + defer s.stateLock.Unlock() + var flags uint16 + switch s.state { + case streamInit: + flags |= flagSYN + s.state = streamSYNSent + case streamSYNReceived: + flags |= flagACK + s.state = streamEstablished + } + return flags +} + +// sendWindowUpdate potentially sends a window update enabling +// further writes to take place. Must be invoked with the lock. +func (s *Stream) sendWindowUpdate() error { + s.controlHdrLock.Lock() + defer s.controlHdrLock.Unlock() + + // Determine the delta update + max := s.session.config.MaxStreamWindowSize + var bufLen uint32 + s.recvLock.Lock() + if s.recvBuf != nil { + bufLen = uint32(s.recvBuf.Len()) + } + delta := (max - bufLen) - s.recvWindow + + // Determine the flags if any + flags := s.sendFlags() + + // Check if we can omit the update + if delta < (max/2) && flags == 0 { + s.recvLock.Unlock() + return nil + } + + // Update our window + s.recvWindow += delta + s.recvLock.Unlock() + + // Send the header + s.controlHdr.encode(typeWindowUpdate, flags, s.id, delta) + if err := s.session.waitForSendErr(s.controlHdr, nil, s.controlErr); err != nil { + return err + } + return nil +} + +// sendClose is used to send a FIN +func (s *Stream) sendClose() error { + s.controlHdrLock.Lock() + defer s.controlHdrLock.Unlock() + + flags := s.sendFlags() + flags |= flagFIN + s.controlHdr.encode(typeWindowUpdate, flags, s.id, 0) + if err := s.session.waitForSendErr(s.controlHdr, nil, s.controlErr); err != nil { + return err + } + return nil +} + +// Close is used to close the stream +func (s *Stream) Close() error { + closeStream := false + s.stateLock.Lock() + switch s.state { + // Opened means we need to signal a close + case streamSYNSent: + fallthrough + case streamSYNReceived: + fallthrough + case streamEstablished: + s.state = streamLocalClose + goto SEND_CLOSE + + case streamLocalClose: + case streamRemoteClose: + s.state = streamClosed + closeStream = true + goto SEND_CLOSE + + case streamClosed: + case streamReset: + default: + panic("unhandled state") + } + s.stateLock.Unlock() + return nil +SEND_CLOSE: + s.stateLock.Unlock() + s.sendClose() + s.notifyWaiting() + if closeStream { + s.session.closeStream(s.id) + } + return nil +} + +// forceClose is used for when the session is exiting +func (s *Stream) forceClose() { + s.stateLock.Lock() + s.state = streamClosed + s.stateLock.Unlock() + s.notifyWaiting() +} + +// processFlags is used to update the state of the stream +// based on set flags, if any. Lock must be held +func (s *Stream) processFlags(flags uint16) error { + // Close the stream without holding the state lock + closeStream := false + defer func() { + if closeStream { + s.session.closeStream(s.id) + } + }() + + s.stateLock.Lock() + defer s.stateLock.Unlock() + if flags&flagACK == flagACK { + if s.state == streamSYNSent { + s.state = streamEstablished + } + s.session.establishStream(s.id) + } + if flags&flagFIN == flagFIN { + switch s.state { + case streamSYNSent: + fallthrough + case streamSYNReceived: + fallthrough + case streamEstablished: + s.state = streamRemoteClose + s.notifyWaiting() + case streamLocalClose: + s.state = streamClosed + closeStream = true + s.notifyWaiting() + default: + s.session.logger.Printf("[ERR] yamux: unexpected FIN flag in state %d", s.state) + return ErrUnexpectedFlag + } + } + if flags&flagRST == flagRST { + s.state = streamReset + closeStream = true + s.notifyWaiting() + } + return nil +} + +// notifyWaiting notifies all the waiting channels +func (s *Stream) notifyWaiting() { + asyncNotify(s.recvNotifyCh) + asyncNotify(s.sendNotifyCh) +} + +// incrSendWindow updates the size of our send window +func (s *Stream) incrSendWindow(hdr header, flags uint16) error { + if err := s.processFlags(flags); err != nil { + return err + } + + // Increase window, unblock a sender + atomic.AddUint32(&s.sendWindow, hdr.Length()) + asyncNotify(s.sendNotifyCh) + return nil +} + +// readData is used to handle a data frame +func (s *Stream) readData(hdr header, flags uint16, conn io.Reader) error { + if err := s.processFlags(flags); err != nil { + return err + } + + // Check that our recv window is not exceeded + length := hdr.Length() + if length == 0 { + return nil + } + + // Wrap in a limited reader + conn = &io.LimitedReader{R: conn, N: int64(length)} + + // Copy into buffer + s.recvLock.Lock() + + if length > s.recvWindow { + s.session.logger.Printf("[ERR] yamux: receive window exceeded (stream: %d, remain: %d, recv: %d)", s.id, s.recvWindow, length) + return ErrRecvWindowExceeded + } + + if s.recvBuf == nil { + // Allocate the receive buffer just-in-time to fit the full data frame. + // This way we can read in the whole packet without further allocations. + s.recvBuf = bytes.NewBuffer(make([]byte, 0, length)) + } + if _, err := io.Copy(s.recvBuf, conn); err != nil { + s.session.logger.Printf("[ERR] yamux: Failed to read stream data: %v", err) + s.recvLock.Unlock() + return err + } + + // Decrement the receive window + s.recvWindow -= length + s.recvLock.Unlock() + + // Unblock any readers + asyncNotify(s.recvNotifyCh) + return nil +} + +// SetDeadline sets the read and write deadlines +func (s *Stream) SetDeadline(t time.Time) error { + if err := s.SetReadDeadline(t); err != nil { + return err + } + if err := s.SetWriteDeadline(t); err != nil { + return err + } + return nil +} + +// SetReadDeadline sets the deadline for future Read calls. +func (s *Stream) SetReadDeadline(t time.Time) error { + s.readDeadline.Store(t) + return nil +} + +// SetWriteDeadline sets the deadline for future Write calls +func (s *Stream) SetWriteDeadline(t time.Time) error { + s.writeDeadline.Store(t) + return nil +} + +// Shrink is used to compact the amount of buffers utilized +// This is useful when using Yamux in a connection pool to reduce +// the idle memory utilization. +func (s *Stream) Shrink() { + s.recvLock.Lock() + if s.recvBuf != nil && s.recvBuf.Len() == 0 { + s.recvBuf = nil + } + s.recvLock.Unlock() +} diff --git a/vendor/github.com/hashicorp/yamux/util.go b/vendor/github.com/hashicorp/yamux/util.go new file mode 100644 index 00000000..8a73e924 --- /dev/null +++ b/vendor/github.com/hashicorp/yamux/util.go @@ -0,0 +1,43 @@ +package yamux + +import ( + "sync" + "time" +) + +var ( + timerPool = &sync.Pool{ + New: func() interface{} { + timer := time.NewTimer(time.Hour * 1e6) + timer.Stop() + return timer + }, + } +) + +// asyncSendErr is used to try an async send of an error +func asyncSendErr(ch chan error, err error) { + if ch == nil { + return + } + select { + case ch <- err: + default: + } +} + +// asyncNotify is used to signal a waiting goroutine +func asyncNotify(ch chan struct{}) { + select { + case ch <- struct{}{}: + default: + } +} + +// min computes the minimum of two values +func min(a, b uint32) uint32 { + if a < b { + return a + } + return b +} diff --git a/vendor/github.com/hashicorp/yamux/util_test.go b/vendor/github.com/hashicorp/yamux/util_test.go new file mode 100644 index 00000000..dd14623a --- /dev/null +++ b/vendor/github.com/hashicorp/yamux/util_test.go @@ -0,0 +1,50 @@ +package yamux + +import ( + "testing" +) + +func TestAsyncSendErr(t *testing.T) { + ch := make(chan error) + asyncSendErr(ch, ErrTimeout) + select { + case <-ch: + t.Fatalf("should not get") + default: + } + + ch = make(chan error, 1) + asyncSendErr(ch, ErrTimeout) + select { + case <-ch: + default: + t.Fatalf("should get") + } +} + +func TestAsyncNotify(t *testing.T) { + ch := make(chan struct{}) + asyncNotify(ch) + select { + case <-ch: + t.Fatalf("should not get") + default: + } + + ch = make(chan struct{}, 1) + asyncNotify(ch) + select { + case <-ch: + default: + t.Fatalf("should get") + } +} + +func TestMin(t *testing.T) { + if min(1, 2) != 1 { + t.Fatalf("bad") + } + if min(2, 1) != 1 { + t.Fatalf("bad") + } +} diff --git a/vendor/github.com/spf13/cobra/cobra/cmd/init.go b/vendor/github.com/spf13/cobra/cobra/cmd/init.go index d65e6c8c..24413701 100644 --- a/vendor/github.com/spf13/cobra/cobra/cmd/init.go +++ b/vendor/github.com/spf13/cobra/cobra/cmd/init.go @@ -65,7 +65,7 @@ Init will not use an existing directory with contents.`, initializeProject(project) fmt.Fprintln(cmd.OutOrStdout(), `Your Cobra application is ready at -`+project.AbsPath()+` +`+project.AbsPath()+`. Give it a try by going there and running `+"`go run main.go`."+` Add commands to it by running `+"`cobra add [cmdname]`.") diff --git a/vendor/github.com/spf13/cobra/command.go b/vendor/github.com/spf13/cobra/command.go index 34d1bf36..15b81127 100644 --- a/vendor/github.com/spf13/cobra/command.go +++ b/vendor/github.com/spf13/cobra/command.go @@ -27,9 +27,6 @@ import ( flag "github.com/spf13/pflag" ) -// FParseErrWhitelist configures Flag parse errors to be ignored -type FParseErrWhitelist flag.ParseErrorsWhitelist - // Command is just that, a command for your application. // E.g. 'go run ...' - 'run' is the command. Cobra requires // you to define the usage and description as part of your command @@ -140,9 +137,6 @@ type Command struct { // TraverseChildren parses flags on all parents before executing child command. TraverseChildren bool - //FParseErrWhitelist flag parse errors to be ignored - FParseErrWhitelist FParseErrWhitelist - // commands is the list of commands supported by this program. commands []*Command // parent is a parent command for this command. @@ -1469,10 +1463,6 @@ func (c *Command) ParseFlags(args []string) error { } beforeErrorBufLen := c.flagErrorBuf.Len() c.mergePersistentFlags() - - //do it here after merging all flags and just before parse - c.Flags().ParseErrorsWhitelist = flag.ParseErrorsWhitelist(c.FParseErrWhitelist) - err := c.Flags().Parse(args) // Print warnings if they occurred (e.g. deprecated flag messages). if c.flagErrorBuf.Len()-beforeErrorBufLen > 0 && err == nil { diff --git a/vendor/github.com/spf13/cobra/command_test.go b/vendor/github.com/spf13/cobra/command_test.go index ccee031d..d874a9a5 100644 --- a/vendor/github.com/spf13/cobra/command_test.go +++ b/vendor/github.com/spf13/cobra/command_test.go @@ -1626,108 +1626,3 @@ func TestCalledAs(t *testing.T) { t.Run(name, tc.test) } } - -func TestFParseErrWhitelistBackwardCompatibility(t *testing.T) { - c := &Command{Use: "c", Run: emptyRun} - c.Flags().BoolP("boola", "a", false, "a boolean flag") - - output, err := executeCommand(c, "c", "-a", "--unknown", "flag") - if err == nil { - t.Error("expected unknown flag error") - } - checkStringContains(t, output, "unknown flag: --unknown") -} - -func TestFParseErrWhitelistSameCommand(t *testing.T) { - c := &Command{ - Use: "c", - Run: emptyRun, - FParseErrWhitelist: FParseErrWhitelist{ - UnknownFlags: true, - }, - } - c.Flags().BoolP("boola", "a", false, "a boolean flag") - - _, err := executeCommand(c, "c", "-a", "--unknown", "flag") - if err != nil { - t.Error("unexpected error: ", err) - } -} - -func TestFParseErrWhitelistParentCommand(t *testing.T) { - root := &Command{ - Use: "root", - Run: emptyRun, - FParseErrWhitelist: FParseErrWhitelist{ - UnknownFlags: true, - }, - } - - c := &Command{ - Use: "child", - Run: emptyRun, - } - c.Flags().BoolP("boola", "a", false, "a boolean flag") - - root.AddCommand(c) - - output, err := executeCommand(root, "child", "-a", "--unknown", "flag") - if err == nil { - t.Error("expected unknown flag error") - } - checkStringContains(t, output, "unknown flag: --unknown") -} - -func TestFParseErrWhitelistChildCommand(t *testing.T) { - root := &Command{ - Use: "root", - Run: emptyRun, - } - - c := &Command{ - Use: "child", - Run: emptyRun, - FParseErrWhitelist: FParseErrWhitelist{ - UnknownFlags: true, - }, - } - c.Flags().BoolP("boola", "a", false, "a boolean flag") - - root.AddCommand(c) - - _, err := executeCommand(root, "child", "-a", "--unknown", "flag") - if err != nil { - t.Error("unexpected error: ", err.Error()) - } -} - -func TestFParseErrWhitelistSiblingCommand(t *testing.T) { - root := &Command{ - Use: "root", - Run: emptyRun, - } - - c := &Command{ - Use: "child", - Run: emptyRun, - FParseErrWhitelist: FParseErrWhitelist{ - UnknownFlags: true, - }, - } - c.Flags().BoolP("boola", "a", false, "a boolean flag") - - s := &Command{ - Use: "sibling", - Run: emptyRun, - } - s.Flags().BoolP("boolb", "b", false, "a boolean flag") - - root.AddCommand(c) - root.AddCommand(s) - - output, err := executeCommand(root, "sibling", "-b", "--unknown", "flag") - if err == nil { - t.Error("expected unknown flag error") - } - checkStringContains(t, output, "unknown flag: --unknown") -} diff --git a/vendor/github.com/xtaci/smux/.travis.yml b/vendor/github.com/xtaci/smux/.travis.yml deleted file mode 100644 index 1ad083ca..00000000 --- a/vendor/github.com/xtaci/smux/.travis.yml +++ /dev/null @@ -1,15 +0,0 @@ -language: go -go: - - tip - -before_install: - - go get -t -v ./... - -install: - - go get github.com/xtaci/smux - -script: - - go test -coverprofile=coverage.txt -covermode=atomic -bench . - -after_success: - - bash <(curl -s https://codecov.io/bash) diff --git a/vendor/github.com/xtaci/smux/LICENSE b/vendor/github.com/xtaci/smux/LICENSE deleted file mode 100644 index eed41acb..00000000 --- a/vendor/github.com/xtaci/smux/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2016-2017 Daniel Fu - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/vendor/github.com/xtaci/smux/README.md b/vendor/github.com/xtaci/smux/README.md deleted file mode 100644 index c01c346e..00000000 --- a/vendor/github.com/xtaci/smux/README.md +++ /dev/null @@ -1,99 +0,0 @@ -smux - -[![GoDoc][1]][2] [![MIT licensed][3]][4] [![Build Status][5]][6] [![Go Report Card][7]][8] [![Coverage Statusd][9]][10] - -smux - -[1]: https://godoc.org/github.com/xtaci/smux?status.svg -[2]: https://godoc.org/github.com/xtaci/smux -[3]: https://img.shields.io/badge/license-MIT-blue.svg -[4]: LICENSE -[5]: https://travis-ci.org/xtaci/smux.svg?branch=master -[6]: https://travis-ci.org/xtaci/smux -[7]: https://goreportcard.com/badge/github.com/xtaci/smux -[8]: https://goreportcard.com/report/github.com/xtaci/smux -[9]: https://codecov.io/gh/xtaci/smux/branch/master/graph/badge.svg -[10]: https://codecov.io/gh/xtaci/smux - -## Introduction - -Smux ( **S**imple **MU**ltiple**X**ing) is a multiplexing library for Golang. It relies on an underlying connection to provide reliability and ordering, such as TCP or [KCP](https://github.com/xtaci/kcp-go), and provides stream-oriented multiplexing. The original intention of this library is to power the connection management for [kcp-go](https://github.com/xtaci/kcp-go). - -## Features - -1. Tiny, less than 600 LOC. -2. ***Token bucket*** controlled receiving, which provides smoother bandwidth graph(see picture below). -3. Session-wide receive buffer, shared among streams, tightly controlled overall memory usage. -4. Minimized header(8Bytes), maximized payload. -5. Well-tested on millions of devices in [kcptun](https://github.com/xtaci/kcptun). - -![smooth bandwidth curve](curve.jpg) - -## Documentation - -For complete documentation, see the associated [Godoc](https://godoc.org/github.com/xtaci/smux). - -## Specification - -``` -VERSION(1B) | CMD(1B) | LENGTH(2B) | STREAMID(4B) | DATA(LENGTH) -``` - -## Usage - -The API of smux are mostly taken from [yamux](https://github.com/hashicorp/yamux) - -```go - -func client() { - // Get a TCP connection - conn, err := net.Dial(...) - if err != nil { - panic(err) - } - - // Setup client side of smux - session, err := smux.Client(conn, nil) - if err != nil { - panic(err) - } - - // Open a new stream - stream, err := session.OpenStream() - if err != nil { - panic(err) - } - - // Stream implements io.ReadWriteCloser - stream.Write([]byte("ping")) -} - -func server() { - // Accept a TCP connection - conn, err := listener.Accept() - if err != nil { - panic(err) - } - - // Setup server side of smux - session, err := smux.Server(conn, nil) - if err != nil { - panic(err) - } - - // Accept a stream - stream, err := session.AcceptStream() - if err != nil { - panic(err) - } - - // Listen for a message - buf := make([]byte, 4) - stream.Read(buf) -} - -``` - -## Status - -Stable diff --git a/vendor/github.com/xtaci/smux/curve.jpg b/vendor/github.com/xtaci/smux/curve.jpg deleted file mode 100644 index 3fc4863f48f45fb92eeb8ca928ff5560c3eb6cfb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 106626 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

hBy17FvOmKn8BK30z51Qr);ommd3Q~n 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%0A&i73jN#(38T*{c%Syt48FJO$##T^6r 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 diff --git a/vendor/github.com/xtaci/smux/frame.go b/vendor/github.com/xtaci/smux/frame.go deleted file mode 100644 index 36062d7b..00000000 --- a/vendor/github.com/xtaci/smux/frame.go +++ /dev/null @@ -1,60 +0,0 @@ -package smux - -import ( - "encoding/binary" - "fmt" -) - -const ( - version = 1 -) - -const ( // cmds - cmdSYN byte = iota // stream open - cmdFIN // stream close, a.k.a EOF mark - cmdPSH // data push - cmdNOP // no operation -) - -const ( - sizeOfVer = 1 - sizeOfCmd = 1 - sizeOfLength = 2 - sizeOfSid = 4 - headerSize = sizeOfVer + sizeOfCmd + sizeOfSid + sizeOfLength -) - -// Frame defines a packet from or to be multiplexed into a single connection -type Frame struct { - ver byte - cmd byte - sid uint32 - data []byte -} - -func newFrame(cmd byte, sid uint32) Frame { - return Frame{ver: version, cmd: cmd, sid: sid} -} - -type rawHeader []byte - -func (h rawHeader) Version() byte { - return h[0] -} - -func (h rawHeader) Cmd() byte { - return h[1] -} - -func (h rawHeader) Length() uint16 { - return binary.LittleEndian.Uint16(h[2:]) -} - -func (h rawHeader) StreamID() uint32 { - return binary.LittleEndian.Uint32(h[4:]) -} - -func (h rawHeader) String() string { - return fmt.Sprintf("Version:%d Cmd:%d StreamID:%d Length:%d", - h.Version(), h.Cmd(), h.StreamID(), h.Length()) -} diff --git a/vendor/github.com/xtaci/smux/mux.go b/vendor/github.com/xtaci/smux/mux.go deleted file mode 100644 index afcf58b4..00000000 --- a/vendor/github.com/xtaci/smux/mux.go +++ /dev/null @@ -1,80 +0,0 @@ -package smux - -import ( - "fmt" - "io" - "time" - - "github.com/pkg/errors" -) - -// Config is used to tune the Smux session -type Config struct { - // KeepAliveInterval is how often to send a NOP command to the remote - KeepAliveInterval time.Duration - - // KeepAliveTimeout is how long the session - // will be closed if no data has arrived - KeepAliveTimeout time.Duration - - // MaxFrameSize is used to control the maximum - // frame size to sent to the remote - MaxFrameSize int - - // MaxReceiveBuffer is used to control the maximum - // number of data in the buffer pool - MaxReceiveBuffer int -} - -// DefaultConfig is used to return a default configuration -func DefaultConfig() *Config { - return &Config{ - KeepAliveInterval: 10 * time.Second, - KeepAliveTimeout: 30 * time.Second, - MaxFrameSize: 4096, - MaxReceiveBuffer: 4194304, - } -} - -// VerifyConfig is used to verify the sanity of configuration -func VerifyConfig(config *Config) error { - if config.KeepAliveInterval == 0 { - return errors.New("keep-alive interval must be positive") - } - if config.KeepAliveTimeout < config.KeepAliveInterval { - return fmt.Errorf("keep-alive timeout must be larger than keep-alive interval") - } - if config.MaxFrameSize <= 0 { - return errors.New("max frame size must be positive") - } - if config.MaxFrameSize > 65535 { - return errors.New("max frame size must not be larger than 65535") - } - if config.MaxReceiveBuffer <= 0 { - return errors.New("max receive buffer must be positive") - } - return nil -} - -// Server is used to initialize a new server-side connection. -func Server(conn io.ReadWriteCloser, config *Config) (*Session, error) { - if config == nil { - config = DefaultConfig() - } - if err := VerifyConfig(config); err != nil { - return nil, err - } - return newSession(config, conn, false), nil -} - -// Client is used to initialize a new client-side connection. -func Client(conn io.ReadWriteCloser, config *Config) (*Session, error) { - if config == nil { - config = DefaultConfig() - } - - if err := VerifyConfig(config); err != nil { - return nil, err - } - return newSession(config, conn, true), nil -} diff --git a/vendor/github.com/xtaci/smux/mux.jpg b/vendor/github.com/xtaci/smux/mux.jpg deleted file mode 100644 index dde2e11aa782b734b5f94b373873d1675cf32b2b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6199 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 0 { - s.nextStreamIDLock.Unlock() - return nil, errors.New(errGoAway) - } - - s.nextStreamID += 2 - sid := s.nextStreamID - if sid == sid%2 { // stream-id overflows - s.goAway = 1 - s.nextStreamIDLock.Unlock() - return nil, errors.New(errGoAway) - } - s.nextStreamIDLock.Unlock() - - stream := newStream(sid, s.config.MaxFrameSize, s) - - if _, err := s.writeFrame(newFrame(cmdSYN, sid)); err != nil { - return nil, errors.Wrap(err, "writeFrame") - } - - s.streamLock.Lock() - s.streams[sid] = stream - s.streamLock.Unlock() - return stream, nil -} - -// AcceptStream is used to block until the next available stream -// is ready to be accepted. -func (s *Session) AcceptStream() (*Stream, error) { - var deadline <-chan time.Time - if d, ok := s.deadline.Load().(time.Time); ok && !d.IsZero() { - timer := time.NewTimer(d.Sub(time.Now())) - defer timer.Stop() - deadline = timer.C - } - select { - case stream := <-s.chAccepts: - return stream, nil - case <-deadline: - return nil, errTimeout - case <-s.die: - return nil, errors.New(errBrokenPipe) - } -} - -// Close is used to close the session and all streams. -func (s *Session) Close() (err error) { - s.dieLock.Lock() - - select { - case <-s.die: - s.dieLock.Unlock() - return errors.New(errBrokenPipe) - default: - close(s.die) - s.dieLock.Unlock() - s.streamLock.Lock() - for k := range s.streams { - s.streams[k].sessionClose() - } - s.streamLock.Unlock() - s.notifyBucket() - return s.conn.Close() - } -} - -// notifyBucket notifies recvLoop that bucket is available -func (s *Session) notifyBucket() { - select { - case s.bucketNotify <- struct{}{}: - default: - } -} - -// IsClosed does a safe check to see if we have shutdown -func (s *Session) IsClosed() bool { - select { - case <-s.die: - return true - default: - return false - } -} - -// NumStreams returns the number of currently open streams -func (s *Session) NumStreams() int { - if s.IsClosed() { - return 0 - } - s.streamLock.Lock() - defer s.streamLock.Unlock() - return len(s.streams) -} - -// SetDeadline sets a deadline used by Accept* calls. -// A zero time value disables the deadline. -func (s *Session) SetDeadline(t time.Time) error { - s.deadline.Store(t) - return nil -} - -// notify the session that a stream has closed -func (s *Session) streamClosed(sid uint32) { - s.streamLock.Lock() - if n := s.streams[sid].recycleTokens(); n > 0 { // return remaining tokens to the bucket - if atomic.AddInt32(&s.bucket, int32(n)) > 0 { - s.notifyBucket() - } - } - delete(s.streams, sid) - s.streamLock.Unlock() -} - -// returnTokens is called by stream to return token after read -func (s *Session) returnTokens(n int) { - if atomic.AddInt32(&s.bucket, int32(n)) > 0 { - s.notifyBucket() - } -} - -// session read a frame from underlying connection -// it's data is pointed to the input buffer -func (s *Session) readFrame(buffer []byte) (f Frame, err error) { - if _, err := io.ReadFull(s.conn, buffer[:headerSize]); err != nil { - return f, errors.Wrap(err, "readFrame") - } - - dec := rawHeader(buffer) - if dec.Version() != version { - return f, errors.New(errInvalidProtocol) - } - - f.ver = dec.Version() - f.cmd = dec.Cmd() - f.sid = dec.StreamID() - if length := dec.Length(); length > 0 { - if _, err := io.ReadFull(s.conn, buffer[headerSize:headerSize+length]); err != nil { - return f, errors.Wrap(err, "readFrame") - } - f.data = buffer[headerSize : headerSize+length] - } - return f, nil -} - -// recvLoop keeps on reading from underlying connection if tokens are available -func (s *Session) recvLoop() { - buffer := make([]byte, (1<<16)+headerSize) - for { - for atomic.LoadInt32(&s.bucket) <= 0 && !s.IsClosed() { - <-s.bucketNotify - } - - if f, err := s.readFrame(buffer); err == nil { - atomic.StoreInt32(&s.dataReady, 1) - - switch f.cmd { - case cmdNOP: - case cmdSYN: - s.streamLock.Lock() - if _, ok := s.streams[f.sid]; !ok { - stream := newStream(f.sid, s.config.MaxFrameSize, s) - s.streams[f.sid] = stream - select { - case s.chAccepts <- stream: - case <-s.die: - } - } - s.streamLock.Unlock() - case cmdFIN: - s.streamLock.Lock() - if stream, ok := s.streams[f.sid]; ok { - stream.markRST() - stream.notifyReadEvent() - } - s.streamLock.Unlock() - case cmdPSH: - s.streamLock.Lock() - if stream, ok := s.streams[f.sid]; ok { - atomic.AddInt32(&s.bucket, -int32(len(f.data))) - stream.pushBytes(f.data) - stream.notifyReadEvent() - } - s.streamLock.Unlock() - default: - s.Close() - return - } - } else { - s.Close() - return - } - } -} - -func (s *Session) keepalive() { - tickerPing := time.NewTicker(s.config.KeepAliveInterval) - tickerTimeout := time.NewTicker(s.config.KeepAliveTimeout) - defer tickerPing.Stop() - defer tickerTimeout.Stop() - for { - select { - case <-tickerPing.C: - s.writeFrame(newFrame(cmdNOP, 0)) - s.notifyBucket() // force a signal to the recvLoop - case <-tickerTimeout.C: - if !atomic.CompareAndSwapInt32(&s.dataReady, 1, 0) { - s.Close() - return - } - case <-s.die: - return - } - } -} - -func (s *Session) sendLoop() { - buf := make([]byte, (1<<16)+headerSize) - for { - select { - case <-s.die: - return - case request, ok := <-s.writes: - if !ok { - continue - } - buf[0] = request.frame.ver - buf[1] = request.frame.cmd - binary.LittleEndian.PutUint16(buf[2:], uint16(len(request.frame.data))) - binary.LittleEndian.PutUint32(buf[4:], request.frame.sid) - copy(buf[headerSize:], request.frame.data) - n, err := s.conn.Write(buf[:headerSize+len(request.frame.data)]) - - n -= headerSize - if n < 0 { - n = 0 - } - - result := writeResult{ - n: n, - err: err, - } - - request.result <- result - close(request.result) - } - } -} - -// writeFrame writes the frame to the underlying connection -// and returns the number of bytes written if successful -func (s *Session) writeFrame(f Frame) (n int, err error) { - req := writeRequest{ - frame: f, - result: make(chan writeResult, 1), - } - select { - case <-s.die: - return 0, errors.New(errBrokenPipe) - case s.writes <- req: - } - - result := <-req.result - return result.n, result.err -} diff --git a/vendor/github.com/xtaci/smux/session_test.go b/vendor/github.com/xtaci/smux/session_test.go deleted file mode 100644 index 2147d8f7..00000000 --- a/vendor/github.com/xtaci/smux/session_test.go +++ /dev/null @@ -1,667 +0,0 @@ -package smux - -import ( - crand "crypto/rand" - "encoding/binary" - "fmt" - "io" - "log" - "math/rand" - "net" - "net/http" - _ "net/http/pprof" - "strings" - "sync" - "testing" - "time" -) - -func init() { - go func() { - log.Println(http.ListenAndServe("localhost:6060", nil)) - }() - log.SetFlags(log.LstdFlags | log.Lshortfile) - ln, err := net.Listen("tcp", "127.0.0.1:19999") - if err != nil { - // handle error - panic(err) - } - go func() { - for { - conn, err := ln.Accept() - if err != nil { - // handle error - } - go handleConnection(conn) - } - }() -} - -func handleConnection(conn net.Conn) { - session, _ := Server(conn, nil) - for { - if stream, err := session.AcceptStream(); err == nil { - go func(s io.ReadWriteCloser) { - buf := make([]byte, 65536) - for { - n, err := s.Read(buf) - if err != nil { - return - } - s.Write(buf[:n]) - } - }(stream) - } else { - return - } - } -} - -func TestEcho(t *testing.T) { - cli, err := net.Dial("tcp", "127.0.0.1:19999") - if err != nil { - t.Fatal(err) - } - session, _ := Client(cli, nil) - stream, _ := session.OpenStream() - const N = 100 - buf := make([]byte, 10) - var sent string - var received string - for i := 0; i < N; i++ { - msg := fmt.Sprintf("hello%v", i) - stream.Write([]byte(msg)) - sent += msg - if n, err := stream.Read(buf); err != nil { - t.Fatal(err) - } else { - received += string(buf[:n]) - } - } - if sent != received { - t.Fatal("data mimatch") - } - session.Close() -} - -func TestSpeed(t *testing.T) { - cli, err := net.Dial("tcp", "127.0.0.1:19999") - if err != nil { - t.Fatal(err) - } - session, _ := Client(cli, nil) - stream, _ := session.OpenStream() - t.Log(stream.LocalAddr(), stream.RemoteAddr()) - - start := time.Now() - var wg sync.WaitGroup - wg.Add(1) - go func() { - buf := make([]byte, 1024*1024) - nrecv := 0 - for { - n, err := stream.Read(buf) - if err != nil { - t.Fatal(err) - break - } else { - nrecv += n - if nrecv == 4096*4096 { - break - } - } - } - stream.Close() - t.Log("time for 16MB rtt", time.Since(start)) - wg.Done() - }() - msg := make([]byte, 8192) - for i := 0; i < 2048; i++ { - stream.Write(msg) - } - wg.Wait() - session.Close() -} - -func TestParallel(t *testing.T) { - cli, err := net.Dial("tcp", "127.0.0.1:19999") - if err != nil { - t.Fatal(err) - } - session, _ := Client(cli, nil) - - par := 1000 - messages := 100 - var wg sync.WaitGroup - wg.Add(par) - for i := 0; i < par; i++ { - stream, _ := session.OpenStream() - go func(s *Stream) { - buf := make([]byte, 20) - for j := 0; j < messages; j++ { - msg := fmt.Sprintf("hello%v", j) - s.Write([]byte(msg)) - if _, err := s.Read(buf); err != nil { - break - } - } - s.Close() - wg.Done() - }(stream) - } - t.Log("created", session.NumStreams(), "streams") - wg.Wait() - session.Close() -} - -func TestCloseThenOpen(t *testing.T) { - cli, err := net.Dial("tcp", "127.0.0.1:19999") - if err != nil { - t.Fatal(err) - } - session, _ := Client(cli, nil) - session.Close() - if _, err := session.OpenStream(); err == nil { - t.Fatal("opened after close") - } -} - -func TestStreamDoubleClose(t *testing.T) { - cli, err := net.Dial("tcp", "127.0.0.1:19999") - if err != nil { - t.Fatal(err) - } - session, _ := Client(cli, nil) - stream, _ := session.OpenStream() - stream.Close() - if err := stream.Close(); err == nil { - t.Log("double close doesn't return error") - } - session.Close() -} - -func TestConcurrentClose(t *testing.T) { - cli, err := net.Dial("tcp", "127.0.0.1:19999") - if err != nil { - t.Fatal(err) - } - session, _ := Client(cli, nil) - numStreams := 100 - streams := make([]*Stream, 0, numStreams) - var wg sync.WaitGroup - wg.Add(numStreams) - for i := 0; i < 100; i++ { - stream, _ := session.OpenStream() - streams = append(streams, stream) - } - for _, s := range streams { - stream := s - go func() { - stream.Close() - wg.Done() - }() - } - session.Close() - wg.Wait() -} - -func TestTinyReadBuffer(t *testing.T) { - cli, err := net.Dial("tcp", "127.0.0.1:19999") - if err != nil { - t.Fatal(err) - } - session, _ := Client(cli, nil) - stream, _ := session.OpenStream() - const N = 100 - tinybuf := make([]byte, 6) - var sent string - var received string - for i := 0; i < N; i++ { - msg := fmt.Sprintf("hello%v", i) - sent += msg - nsent, err := stream.Write([]byte(msg)) - if err != nil { - t.Fatal("cannot write") - } - nrecv := 0 - for nrecv < nsent { - if n, err := stream.Read(tinybuf); err == nil { - nrecv += n - received += string(tinybuf[:n]) - } else { - t.Fatal("cannot read with tiny buffer") - } - } - } - - if sent != received { - t.Fatal("data mimatch") - } - session.Close() -} - -func TestIsClose(t *testing.T) { - cli, err := net.Dial("tcp", "127.0.0.1:19999") - if err != nil { - t.Fatal(err) - } - session, _ := Client(cli, nil) - session.Close() - if session.IsClosed() != true { - t.Fatal("still open after close") - } -} - -func TestKeepAliveTimeout(t *testing.T) { - ln, err := net.Listen("tcp", "127.0.0.1:29999") - if err != nil { - // handle error - panic(err) - } - go func() { - ln.Accept() - }() - - cli, err := net.Dial("tcp", "127.0.0.1:29999") - if err != nil { - t.Fatal(err) - } - - config := DefaultConfig() - config.KeepAliveInterval = time.Second - config.KeepAliveTimeout = 2 * time.Second - session, _ := Client(cli, config) - <-time.After(3 * time.Second) - if session.IsClosed() != true { - t.Fatal("keepalive-timeout failed") - } -} - -func TestServerEcho(t *testing.T) { - ln, err := net.Listen("tcp", "127.0.0.1:39999") - if err != nil { - // handle error - panic(err) - } - go func() { - if conn, err := ln.Accept(); err == nil { - session, _ := Server(conn, nil) - if stream, err := session.OpenStream(); err == nil { - const N = 100 - buf := make([]byte, 10) - for i := 0; i < N; i++ { - msg := fmt.Sprintf("hello%v", i) - stream.Write([]byte(msg)) - if n, err := stream.Read(buf); err != nil { - t.Fatal(err) - } else if string(buf[:n]) != msg { - t.Fatal(err) - } - } - stream.Close() - } else { - t.Fatal(err) - } - } else { - t.Fatal(err) - } - }() - - cli, err := net.Dial("tcp", "127.0.0.1:39999") - if err != nil { - t.Fatal(err) - } - if session, err := Client(cli, nil); err == nil { - if stream, err := session.AcceptStream(); err == nil { - buf := make([]byte, 65536) - for { - n, err := stream.Read(buf) - if err != nil { - break - } - stream.Write(buf[:n]) - } - } else { - t.Fatal(err) - } - } else { - t.Fatal(err) - } -} - -func TestSendWithoutRecv(t *testing.T) { - cli, err := net.Dial("tcp", "127.0.0.1:19999") - if err != nil { - t.Fatal(err) - } - session, _ := Client(cli, nil) - stream, _ := session.OpenStream() - const N = 100 - for i := 0; i < N; i++ { - msg := fmt.Sprintf("hello%v", i) - stream.Write([]byte(msg)) - } - buf := make([]byte, 1) - if _, err := stream.Read(buf); err != nil { - t.Fatal(err) - } - stream.Close() -} - -func TestWriteAfterClose(t *testing.T) { - cli, err := net.Dial("tcp", "127.0.0.1:19999") - if err != nil { - t.Fatal(err) - } - session, _ := Client(cli, nil) - stream, _ := session.OpenStream() - stream.Close() - if _, err := stream.Write([]byte("write after close")); err == nil { - t.Fatal("write after close failed") - } -} - -func TestReadStreamAfterSessionClose(t *testing.T) { - cli, err := net.Dial("tcp", "127.0.0.1:19999") - if err != nil { - t.Fatal(err) - } - session, _ := Client(cli, nil) - stream, _ := session.OpenStream() - session.Close() - buf := make([]byte, 10) - if _, err := stream.Read(buf); err != nil { - t.Log(err) - } else { - t.Fatal("read stream after session close succeeded") - } -} - -func TestWriteStreamAfterConnectionClose(t *testing.T) { - cli, err := net.Dial("tcp", "127.0.0.1:19999") - if err != nil { - t.Fatal(err) - } - session, _ := Client(cli, nil) - stream, _ := session.OpenStream() - session.conn.Close() - if _, err := stream.Write([]byte("write after connection close")); err == nil { - t.Fatal("write after connection close failed") - } -} - -func TestNumStreamAfterClose(t *testing.T) { - cli, err := net.Dial("tcp", "127.0.0.1:19999") - if err != nil { - t.Fatal(err) - } - session, _ := Client(cli, nil) - if _, err := session.OpenStream(); err == nil { - if session.NumStreams() != 1 { - t.Fatal("wrong number of streams after opened") - } - session.Close() - if session.NumStreams() != 0 { - t.Fatal("wrong number of streams after session closed") - } - } else { - t.Fatal(err) - } - cli.Close() -} - -func TestRandomFrame(t *testing.T) { - // pure random - cli, err := net.Dial("tcp", "127.0.0.1:19999") - if err != nil { - t.Fatal(err) - } - session, _ := Client(cli, nil) - for i := 0; i < 100; i++ { - rnd := make([]byte, rand.Uint32()%1024) - io.ReadFull(crand.Reader, rnd) - session.conn.Write(rnd) - } - cli.Close() - - // double syn - cli, err = net.Dial("tcp", "127.0.0.1:19999") - if err != nil { - t.Fatal(err) - } - session, _ = Client(cli, nil) - for i := 0; i < 100; i++ { - f := newFrame(cmdSYN, 1000) - session.writeFrame(f) - } - cli.Close() - - // random cmds - cli, err = net.Dial("tcp", "127.0.0.1:19999") - if err != nil { - t.Fatal(err) - } - allcmds := []byte{cmdSYN, cmdFIN, cmdPSH, cmdNOP} - session, _ = Client(cli, nil) - for i := 0; i < 100; i++ { - f := newFrame(allcmds[rand.Int()%len(allcmds)], rand.Uint32()) - session.writeFrame(f) - } - cli.Close() - - // random cmds & sids - cli, err = net.Dial("tcp", "127.0.0.1:19999") - if err != nil { - t.Fatal(err) - } - session, _ = Client(cli, nil) - for i := 0; i < 100; i++ { - f := newFrame(byte(rand.Uint32()), rand.Uint32()) - session.writeFrame(f) - } - cli.Close() - - // random version - cli, err = net.Dial("tcp", "127.0.0.1:19999") - if err != nil { - t.Fatal(err) - } - session, _ = Client(cli, nil) - for i := 0; i < 100; i++ { - f := newFrame(byte(rand.Uint32()), rand.Uint32()) - f.ver = byte(rand.Uint32()) - session.writeFrame(f) - } - cli.Close() - - // incorrect size - cli, err = net.Dial("tcp", "127.0.0.1:19999") - if err != nil { - t.Fatal(err) - } - session, _ = Client(cli, nil) - - f := newFrame(byte(rand.Uint32()), rand.Uint32()) - rnd := make([]byte, rand.Uint32()%1024) - io.ReadFull(crand.Reader, rnd) - f.data = rnd - - buf := make([]byte, headerSize+len(f.data)) - buf[0] = f.ver - buf[1] = f.cmd - binary.LittleEndian.PutUint16(buf[2:], uint16(len(rnd)+1)) /// incorrect size - binary.LittleEndian.PutUint32(buf[4:], f.sid) - copy(buf[headerSize:], f.data) - - session.conn.Write(buf) - t.Log(rawHeader(buf)) - cli.Close() -} - -func TestReadDeadline(t *testing.T) { - cli, err := net.Dial("tcp", "127.0.0.1:19999") - if err != nil { - t.Fatal(err) - } - session, _ := Client(cli, nil) - stream, _ := session.OpenStream() - const N = 100 - buf := make([]byte, 10) - var readErr error - for i := 0; i < N; i++ { - msg := fmt.Sprintf("hello%v", i) - stream.Write([]byte(msg)) - stream.SetReadDeadline(time.Now().Add(-1 * time.Minute)) - if _, readErr = stream.Read(buf); readErr != nil { - break - } - } - if readErr != nil { - if !strings.Contains(readErr.Error(), "i/o timeout") { - t.Fatalf("Wrong error: %v", readErr) - } - } else { - t.Fatal("No error when reading with past deadline") - } - session.Close() -} - -func TestWriteDeadline(t *testing.T) { - cli, err := net.Dial("tcp", "127.0.0.1:19999") - if err != nil { - t.Fatal(err) - } - session, _ := Client(cli, nil) - stream, _ := session.OpenStream() - buf := make([]byte, 10) - var writeErr error - for { - stream.SetWriteDeadline(time.Now().Add(-1 * time.Minute)) - if _, writeErr = stream.Write(buf); writeErr != nil { - if !strings.Contains(writeErr.Error(), "i/o timeout") { - t.Fatalf("Wrong error: %v", writeErr) - } - break - } - } - session.Close() -} - -func BenchmarkAcceptClose(b *testing.B) { - cli, err := net.Dial("tcp", "127.0.0.1:19999") - if err != nil { - b.Fatal(err) - } - session, _ := Client(cli, nil) - for i := 0; i < b.N; i++ { - if stream, err := session.OpenStream(); err == nil { - stream.Close() - } else { - b.Fatal(err) - } - } -} -func BenchmarkConnSmux(b *testing.B) { - cs, ss, err := getSmuxStreamPair() - if err != nil { - b.Fatal(err) - } - defer cs.Close() - defer ss.Close() - bench(b, cs, ss) -} - -func BenchmarkConnTCP(b *testing.B) { - cs, ss, err := getTCPConnectionPair() - if err != nil { - b.Fatal(err) - } - defer cs.Close() - defer ss.Close() - bench(b, cs, ss) -} - -func getSmuxStreamPair() (*Stream, *Stream, error) { - c1, c2, err := getTCPConnectionPair() - if err != nil { - return nil, nil, err - } - - s, err := Server(c2, nil) - if err != nil { - return nil, nil, err - } - c, err := Client(c1, nil) - if err != nil { - return nil, nil, err - } - var ss *Stream - done := make(chan error) - go func() { - var rerr error - ss, rerr = s.AcceptStream() - done <- rerr - close(done) - }() - cs, err := c.OpenStream() - if err != nil { - return nil, nil, err - } - err = <-done - if err != nil { - return nil, nil, err - } - - return cs, ss, nil -} - -func getTCPConnectionPair() (net.Conn, net.Conn, error) { - lst, err := net.Listen("tcp", "127.0.0.1:0") - if err != nil { - return nil, nil, err - } - - var conn0 net.Conn - var err0 error - done := make(chan struct{}) - go func() { - conn0, err0 = lst.Accept() - close(done) - }() - - conn1, err := net.Dial("tcp", lst.Addr().String()) - if err != nil { - return nil, nil, err - } - - <-done - if err0 != nil { - return nil, nil, err0 - } - return conn0, conn1, nil -} - -func bench(b *testing.B, rd io.Reader, wr io.Writer) { - buf := make([]byte, 128*1024) - buf2 := make([]byte, 128*1024) - b.SetBytes(128 * 1024) - b.ResetTimer() - - var wg sync.WaitGroup - wg.Add(1) - go func() { - defer wg.Done() - count := 0 - for { - n, _ := rd.Read(buf2) - count += n - if count == 128*1024*b.N { - return - } - } - }() - for i := 0; i < b.N; i++ { - wr.Write(buf) - } - wg.Wait() -} diff --git a/vendor/github.com/xtaci/smux/smux.png b/vendor/github.com/xtaci/smux/smux.png deleted file mode 100644 index 26aba3b7274043fef9950b956f6c471280d54cea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9891 zcmV;UCS2KxP)4Tx07!|IR|i;A$rhelQV0nx5CJKncce%c2pt3g>0l!y0YZt85LE2QqJnEd z#ex-G6;W_4fGjpFSXcz?^?``W%DPyqgr;TA*E>a=;^F=vX?0NrZIsUg~XNgDGFoc7eE)0&6xU4#W&`W~`4KJFtaP zUuchp^YcPtp5H1?hg_qc%cul!UV}#P3ptfl)FWOfy?FnipovVN2J7y@0Xu z(qn>UzE}cJln^$`fy?s`ka57cJVC6S8wR_}H%jhXoRbwPx5swyvVx*yb;gc}@}oz6 zJM&|ElNT8ynu2gfd6lFq43Rv*-5wvMsxTUkMNAp zL3l#AMd%{j199M~G>Y767QayV19K*r0=i(dKzVj4FlOJV4Q>iqJK?^#C*%*g;|yFI z*x(akCO)_~%*_dEzh@{Ofi5CLL9BA5v)o({4h=Q&^jSPF_kDOd|Of~{aD*aHrL!{8{W2TkA{xCE|& zc5n+kfPK*mUV;HI1cng=At5w`j%XqJhzVkaI3TWw7ZQMkAyG&ol8mGw0z`}~K$an; zNExyf*^N{ozal4*bI28>1G$HEBYnsl^;dj$Z7$Oph45As)1%8)_#0=tm;ws{HVlDA3v4hw{93+uQS|lry zCn_84ACy@o@LUK9zAo(=8ll+`Kte~P`qTsF& zp}JRYDQ^`O6%7^L6sIck6_+S( zRy?A3S+QI3BUO!RN%f;nr;4eosr#vCsrRUFX*8NCjYUhM<Os{jsxQu_YWI&(i&}d+GD_H|U?y z|AVQ)^kHT(H#09V2Mlx!f(^t5yA9e5h7HXOV+{)pYYiV6QH)%TQjInkH5&~W>l;ro zUSM2peBVUD#MLC-WRppY$$L{X(|FV6ruC*T%rwnH%nHn^%^sT5%)QNX%`44sS`aN< zEix^(TeMrEmQI%GmKB!QtxzjxE56lss}5^|wVSoTy3+cN4b{fSCf}yorrVZb8)jQ* zTW|Yng7Ji;3F{`bO!#W&WS3>P*Y1J6n*C(^Lirn~pTcK*z<74UVrT zT216l+%fU46WwWw({iU~r!USf&O+xJ=ROx>mt>c1E_YnjT_asrxn6c7xcRy*c58C` z=SKrJ9PU~9nRK*PYa!0Nz( zAg7@DL1%*r!J)zHg71XrhHyfvLIx(gOkOzoLMSyfI&^F3lQ7FLVOZl7bjsu@>!&;n zHwhPn*GGVekcjmWk0Q+?b0dG7N}L)wwPNbCDEp`dQ7zH*=vmPRqlaRAW7fnxh&7KD z$2P|)$4!r`ihCdLA72*Vl`tV;VZyaU?L>ZJLlP+|E@@xV;Ix2g8>jV7cbZ;2{q_vA z8FOb`nW;T9Yv!q0%Cp$BjwKV4;7UKCLg^JH0%;pYO-t%72{^oUt?GeP%@F{>+iA_^jG&a&~g|Nr9Rm zQ*dFn-t4)vJ94aYigUVh-E%kQ4hTbq`$ULnhNvM=9UgO9#pdFl#NGK``4#ye=ETi8 zHdl47U~cO?%Xus3Jue6>_+>smpEJLCfx&{M3%VEjEZn^aS(LJo(DeCzt=%?yprD9?6o#h_Ocdo!!@K?00bX>V*<<}B! z$<<^WyWx zRUOnhxTYGd7FYKjntrJLu>awP8k?HkwT#-*BjAYm$REEZ|9bmq=+Wk5uE%PQn;hSE zf_|c;4yl`0H&CBm-_?-V&~b9|$>v6n#uLBU{kaL0?TsCiI!c9f8)~4kekyzjw4|+H~FQ1B@e(o)L zMtfHNT>p913x^j?eSv-Le@y$M_hrt@;r`-3RsY=f%KX*wzgT~@4kQfpz81b7d9&)R z*4zE>CcbMPj2wJClsz>3zT|_}hXWs7Kel{||MYx#&S%o+&0j3OG=2^J`fwzBWF&_z zWJ`|%7#wJ7DgbZx13*iJXNPtGC`z)smsAkheNl?Qf5Ni+nN*ws*v|qWb_oDs@Qktp z4iku}@GUud$FKnE=r}gyB1(01kj0|H8w}yY$jDn=07#7hd=ZX}3~wJ9`LY+1y$8VA zT-lvZB1Cw;-(?{|ZLJjWqx=(Q{%kgGdIskJ000SaNLh0L02UUN`F-pt=3Yd zmX=bx+V_1Y)(}Y$A`yv5^gr+A^UO@{y)%=UiA?gHN6gIKzWd#o^WA&j^PY2a{mVk8 zm?L10fJa9lw?|ty8#hP5904%`rlOfY1aky3VFXM?%Y;ETFJX>=sc7bJz#M^07y(n! zGGUO-OPC{IDw_ElFh?L0M!;0GOc-SI66Oe)ie~->%n`_h5ik`k69(D5ggFAHqM5$| za|AMB1bke+#=p1d#??4VzH*u9)-B2#Sdj7s1d@-RA2*%L<>t)>%n`^ABjB{6T{(J) z&i=EUqPA_Km>pZ_+WAQOmle!ba{Ks@e~FU(SBm^gg;AmM6{%3UiWJhQDHW|z$F}(0 z&tP7r-Exk8>66Fv3Z0hrFFDgI^eB%_zi?j}WtQg7os0ONT}kDBW>hea82VdIj=$~o z9Qi(6=_jt2i^n35BLiaFxqI|Emm{R&f74Pa{EyjmaLNyKe#-`zx?1&_ZuH0x)0J)f zyznTE|NWpcUzXAB(aQA3^R;DJZb>_OI*wlcbPc7ZrGKEPE>N~)Ak7`w#wz=r(VJ=A zt|+TCM?XAze;FFux1qN9rhPH=#@8FQWptT~#2cb7EY>5< zHfio&_4| z`BXIu@XPB`Pi!B2ljk3%Yu9hO)aJmhwFD62QX}`EaymsziI1ZVAHL~uSA2ttP;lM) zRN?WCRI__80@zWeRa+`t!ToFKLIL@xajoK1xlAEx|KKb>eDW?zOmck|1|W0#e1h~k zK?U=RepC!AD9z7EB4!cXTd|-58L6(VlZzobv|YbiF>N`^OpJApC#qT|rF-A7MzCnN zfUg|9-hJfKUGV2@Od`!+eJWiWMuNSzP_Aj65OF=DjyJugQ?#uk2GjM6`dJ|_+sNwn z8AvbfJw-iF#L_eW?4hq=qB>EO@ZJ`Oc11kt!b_tVcqTM|$`nMCvY z|C{N1WmyiV?Kv7tvz8r~mdB5F#OsYkKYBb_OT z#v5JLNT#ZfStZ@-QFQBO3LTj}P20B$k3LDQ#{NY4gNjmW(seq&c^xH2o}+7#XDKBi zUKpwi6e~`b+1}J04ji2NSIn4Tk8H_;}^zutaP#d8WwU^}jD^&-l@lbm^k9 zyeBgNL%;NR6@uMOCZGSbo9b36N>~CpBj53p_uPduveDmcI?aT7K zCCzH-czXF_Wxw+U2GV0Q7m`l_KUzJoCv6_~!97#5Lnp6@nXGrG8p{1z?A2uY^5;Fu z`;ldAKX_3*nPqucx(w;lkbLv!q5a31Zk9s?3=9AeD*1WAQHoEzNgW<6%QKFA@2si| zu%%bWYJx2@<~O-H0CizAD_+F?dskJzJdtjtXbET4PoCoS8j*j=P&%<-4u#K~<%zyH z+~yHxYW%`88TKASm@T2Q`K;V}j+Qgb?1%tB8OBd!RrI|VnC(!fauGTcb&V#>@!Yc5 zk-lbj82~BG7>JNbd}0bsS#U%kfzHnM-Fa_^6~~f|5p+y4W8!)Mo>0!)#Y)kxN2}>U zST;h=u33>K?)k?*j}WL)RTzQn-Lbl|PhbDChhpQBmH9@-x4-VAXkH4*q)Vyv8?_V7Qb3*R0J5tz;qI+=l;_ueKfYlC1 z)`cveW~FFJvFiO+aP4~HW?~OHZWQ(lE=~o@lq0|5A(SVtYc?6%kxXUw!6*Eq#~0O| zVd$C_i`W(spRZm^p%J`a)5)g2F|=UKX=xFkJhy=t00Wps51z<5%SOuq0TA){17$6f z?!E^T#NemCA?#dog}hsV-(XJ%Q0q6Q&jBfxNg3V~Ki#UlEX(WCtQ0M;e}M7^Zr!>K zyXMK4Us=Y;X!7AO>KzeJJ&wlEi+fH{k0a64H#(VLqp0b}BaC7$zn(l7Pvhqt6s&d- zZn0^W$@<+r!ah3P-dOKXP- zc2J;*cnJRTmmJJO1Fti2*wzC=+i(`nv{E#`VEH;6{f|F&Lfl5N+V^R1$G>DLYWVJ8 z%F8}!R+1|>&uk?qAk4k=89Whpv0=1q$pGqi2PU?$KC_t7U|2U##iU2Wg)u@#0 zQeK&6rDy@6>L<~Og>%IX*6!Jla^<$FW_9D(=bh1@msW+{@Bwz0p-d>go}4PssRq^M zHZD+q0Crf)+MQAK_l5||q`U9)vV8|Y3!bQ8o98rQ@!f&2c>|LQ01BDxJbaPX?~Z08 zcL92~ZADoTYEnCdnzB*;95Y4ct~x0#gG4A}b`mR%Db3U=mBb!CND-@+QlW}fs1CD2 zRDvhl(w95a@%fpDOrF1(NTX-$6J7eGcLVZMP=dgNoU`Kgmy2b~=Tp>sw_ZZ6JUyADj3JcZq*8*RijXW@T#uijp*Am~1I9egh>w zz;;NOUSuMeBH54>V5MljMT$}N=U6R`=pLC9g{kqf?z{-~W z7ufAay@xQ`%Bip5k^-=h$tAX1PG5YCu+M${4nPHhatO0K*6cV>dyZa`RTFMGx@D=f zl9wc=Q#8zK_L%qJIUblYo(fm4Mo%xi) z2?*JH-RnBlY&~`(0o@0Xzlyi#RD;>Punw@8&imTft2d}^voOIbfy)gH{@>3&s2mjl zErkQE_jSs`UY3zg(a?YRm4!5Dth^Nf9&6uyopz7;iVAXMt4^$JMXT3TmPbb1@nf{` z>9&-tF^X#&a3XUgL-rRgCkcBF*yWB0e5yWWE+b&)kyCHiccflUE(1!MYJ8Iz1;!E* zGWwn7g5B{4`=gzSlKc39%cwFPWQQ@9DjFjq^vL+B1ZIa$HVqxb%gB$(k3-6JT)d7- zG;E?PPH`uX)1qe|qtxqicD%N-6PXFjzZ=F5ppZd5>QRwGzIJQF*C~{Nl+|KJjIdkY zEO2`SU{qFdO#x8BO$MA)m|rHd-ApA0Q$&^T?dftwp8@IOM>c~Z9Sj}cHlmLebY&>PvCFln|^>+*Ct{sw*FB;((c z_uvg}2N(7wAdE7>QZXX>Hkfpomyz)dRH>wVDI0==s-TyqD-{j*Q>0o=hm>vKq_K=H zzC=F0zSLpP5-QWWopw0RtXxXlM-JDPaYrVge0kFF6RNt2iqP7)OTO1OJI!K%G=%Q2s8{qJ=g9$Z<}p? z){Fqa4uBpiF@oC8T6UYh*P9L9{srW-A6O+B4KAy?epX7it7zzKP}LeDE=bq|Ewz0= zy=)Qv)v28b5e_?BaGl$Xe_5rQiHWrR>!GqNx2C~3I+a=Xn7IJ?Tgn6VN(zTXa3Dvy z7-z(?-JJ3sUkGM+UnF0w0bqmO%N-(Qs*QjF-DH;aGw`eduF@HJT z*Y7RWIEi#X`D@%7XGRU&OrFeo!Cv%bk76E^Qgntz`Gp`A!@V82tfCLEu!2_#$A40AWNtUjavZf~Jyq`fJXPU8m7HGs7e_8YL}ymJ$|HKM64ge&GM4YUMBZzkFwWqt5EKd~*0A-DS6jVg*!VlD* z#3{-ck-&^=sM>+C-VqtY98i^j0fOr-X7?lP&nyM@%_$tr$6ml6d{3ZpWhc@9GVRs^rpvy>x!lS}IzzE)}b#rbET-+@@;6sq_-X{1`$mudIW;0+b6&696OtN>oye z?L(a(si4ZvP`a(#h-D*&B9&FJ;#KbcmTkoF@=PWM)84p-tPIi&g;eDuec9%7bokU2 zX%UP+IE@IWK7sdSG7%@ua5z5%`+@yZCIDDmJ#j|X?!8QNNzu^pph{J>{g2tPRpb|` zre2rB^~PO-bGZ!0xU9`c{*wPYW(xrz(aDEB>of08y${vZRpse;e1;MrcBD)qIQmw! z#6#S&>{j--lZnyhRX$`wB?wV~{gX~~r^KM%YfQ*SY!#ds0}7l>L>ugj1{uivWki0K z31UH>I2SL=^2#(TMO)RcJFR)U4;`B`gA&f3cIbwVbND%(Bu3x1P2uEEl_c2-L9VJN zONZ0vr+peIzd+PMpiY(3A@Z?@c%Yu%D*@%46`kT5%Pd1FY}-aSZ5$Gz5;(QO79|s7 zMmuJ+l(Q_W>zh#yUiK+XPD2n?M9hjD z09O($F%d7(Ru-;3Eofrc2bB*EWMIcfFkfKkH3+=6b?~_o|DYi|56Kk~d4(0>36KXt zkW}oiRAMms@i0hCvJYPS&%+U|$Fa z$*(vbrA#(3+J`>~z!iJm-}hB5M$LIY1X3@87^wuai%x#}^9TXVkVy-6vV}5$?xs62 z#nnBE#hZ>X=u1N^xClk=L+mgN>zdv}T9^psqieUMS zGu*Im0%Wcd%#6Bzjc0`bh#`~5c|VJ5>Ht3;LW z4e1_HG$gDyb1Ab`60T!-Y%aOXx6tPT< zx}!Ln*Wpuf1aM*Mu$Ibj0Y-V*D>#MVb@pdpu{1vH{(VMSRSbL7$M~qAeFQ~au~ok8 z_vJmJ!SXAQG)A1E8Z!&kDJXE#I3)FSveyacG zAaQ2eqURnb7^PK$nEr@A;wizbFzmI4TId?b4#8VEGo($EQ0?J<00rJ2&43i2|BR@= zKYsEM4SlVVwvN$n!1K?t9c%tKZ3%}2Cb9Rzhu?1_?1yyn;$xMS*)UG44*tNn!QKdG z+5O?M(l!9>jQ`+%s-}3@ArcvwAR^!oKNwVEuu4?3Fyb@BqVbEYFhW-0ffj|I;qjEr!LzqdF9xt5a0+{ z9YHGHj@kq}7=usQw^%j7Hr4hTMamZNjQ@Qx#S z5jCoGNPsxn$S_M`DL0JC(yGt_#=T)SQ-vb{nyCU%it=6)Onl6kV>NL=uyn_4`6k=U z`i@vMjwqRRmNxIRoJmKJkZ{qstevI zY-_5Jd>{@y!w(8bgX+b^J;;2{;5RstU2d@41?juz&^4lt?SD8`6k~yhi?@kGinjK> z{)9jO5+%j#6;LWn*wb(*M#|MVHg2DxgmV#MshE}`hk#`R(|FKO%AGf#EK8b($80!;2oKhPc&{L2AN=}mBZ~8I28fLPoNc-3 zixHF2HTJv_#=y31gfoCEjVq#ybnHsIo~)bciiXo@o|w1HsU@Sk2jB~yNwD9^3Ibl` zFGC(C05+Z$pppO+#=O_uYDs5#UAU*XdZ`$~vCnPt1id(hBepms$N=@=1@y-6}n z(Qs~ByPsy!gCoa@FbDT?kW^3dd(|iGyJn1NEdq$bHsuMyaaPlYw-R(GUHfoP0XUIS z!79PvJ*wKk-VmHpR|Kcgc#~Lts}(zRA|}rX#4zoqePXC1HyTu>8r2){9#ws&+dZAd z9O>)5EQ4cav44bjGEOI*%g7;^BCZIO7G{AiZ7PZ*sO+@`kO!cID*{B--f98#IG9^L znXo;>m(n4&8E({r6?3sMGMxi_+KptouzQDKbs&^C;QhE0;R02J=d?<|@<4QBm;p-h zJ`8)YqBXJMW|++sr$zwZN;rpK*w6Mb7=!n-lj?bPQBk?~fDnykSq%muYP0dZHrt~R zg$kh?Ou6?XAXPM7x6+c6nCX$g+{J5jlV72Dh*B*6m_%$Um2UK0&%<#@Kwn|J2HYLU zFn>Y+ycEFk60_Qp*!8>3J}o${XxZo1%{wtiAoE5b)4-6-yCw5V<_KiH5ik`k>wPC? zkFwzin2MGSzf|* 0 { - s.sess.returnTokens(n) - return n, nil - } else if atomic.LoadInt32(&s.rstflag) == 1 { - _ = s.Close() - return 0, io.EOF - } - - select { - case <-s.chReadEvent: - goto READ - case <-deadline: - return n, errTimeout - case <-s.die: - return 0, errors.New(errBrokenPipe) - } -} - -// Write implements net.Conn -func (s *Stream) Write(b []byte) (n int, err error) { - var deadline <-chan time.Time - if d, ok := s.writeDeadline.Load().(time.Time); ok && !d.IsZero() { - timer := time.NewTimer(d.Sub(time.Now())) - defer timer.Stop() - deadline = timer.C - } - - select { - case <-s.die: - return 0, errors.New(errBrokenPipe) - default: - } - - frames := s.split(b, cmdPSH, s.id) - sent := 0 - for k := range frames { - req := writeRequest{ - frame: frames[k], - result: make(chan writeResult, 1), - } - - select { - case s.sess.writes <- req: - case <-s.die: - return sent, errors.New(errBrokenPipe) - case <-deadline: - return sent, errTimeout - } - - select { - case result := <-req.result: - sent += result.n - if result.err != nil { - return sent, result.err - } - case <-s.die: - return sent, errors.New(errBrokenPipe) - case <-deadline: - return sent, errTimeout - } - } - return sent, nil -} - -// Close implements net.Conn -func (s *Stream) Close() error { - s.dieLock.Lock() - - select { - case <-s.die: - s.dieLock.Unlock() - return errors.New(errBrokenPipe) - default: - close(s.die) - s.dieLock.Unlock() - s.sess.streamClosed(s.id) - _, err := s.sess.writeFrame(newFrame(cmdFIN, s.id)) - return err - } -} - -// SetReadDeadline sets the read deadline as defined by -// net.Conn.SetReadDeadline. -// A zero time value disables the deadline. -func (s *Stream) SetReadDeadline(t time.Time) error { - s.readDeadline.Store(t) - return nil -} - -// SetWriteDeadline sets the write deadline as defined by -// net.Conn.SetWriteDeadline. -// A zero time value disables the deadline. -func (s *Stream) SetWriteDeadline(t time.Time) error { - s.writeDeadline.Store(t) - return nil -} - -// SetDeadline sets both read and write deadlines as defined by -// net.Conn.SetDeadline. -// A zero time value disables the deadlines. -func (s *Stream) SetDeadline(t time.Time) error { - if err := s.SetReadDeadline(t); err != nil { - return err - } - if err := s.SetWriteDeadline(t); err != nil { - return err - } - return nil -} - -// session closes the stream -func (s *Stream) sessionClose() { - s.dieLock.Lock() - defer s.dieLock.Unlock() - - select { - case <-s.die: - default: - close(s.die) - } -} - -// LocalAddr satisfies net.Conn interface -func (s *Stream) LocalAddr() net.Addr { - if ts, ok := s.sess.conn.(interface { - LocalAddr() net.Addr - }); ok { - return ts.LocalAddr() - } - return nil -} - -// RemoteAddr satisfies net.Conn interface -func (s *Stream) RemoteAddr() net.Addr { - if ts, ok := s.sess.conn.(interface { - RemoteAddr() net.Addr - }); ok { - return ts.RemoteAddr() - } - return nil -} - -// pushBytes a slice into buffer -func (s *Stream) pushBytes(p []byte) { - s.bufferLock.Lock() - s.buffer.Write(p) - s.bufferLock.Unlock() -} - -// recycleTokens transform remaining bytes to tokens(will truncate buffer) -func (s *Stream) recycleTokens() (n int) { - s.bufferLock.Lock() - n = s.buffer.Len() - s.buffer.Reset() - s.bufferLock.Unlock() - return -} - -// split large byte buffer into smaller frames, reference only -func (s *Stream) split(bts []byte, cmd byte, sid uint32) []Frame { - frames := make([]Frame, 0, len(bts)/s.frameSize+1) - for len(bts) > s.frameSize { - frame := newFrame(cmd, sid) - frame.data = bts[:s.frameSize] - bts = bts[s.frameSize:] - frames = append(frames, frame) - } - if len(bts) > 0 { - frame := newFrame(cmd, sid) - frame.data = bts - frames = append(frames, frame) - } - return frames -} - -// notify read event -func (s *Stream) notifyReadEvent() { - select { - case s.chReadEvent <- struct{}{}: - default: - } -} - -// mark this stream has been reset -func (s *Stream) markRST() { - atomic.StoreInt32(&s.rstflag, 1) -} - -var errTimeout error = &timeoutError{} - -type timeoutError struct{} - -func (e *timeoutError) Error() string { return "i/o timeout" } -func (e *timeoutError) Timeout() bool { return true } -func (e *timeoutError) Temporary() bool { return true }