WIP: Save agent roles integration work before CHORUS rebrand
- Agent roles and coordination features - Chat API integration testing - New configuration and workspace management 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
23
vendor/github.com/libp2p/go-yamux/v4/.gitignore
generated
vendored
Normal file
23
vendor/github.com/libp2p/go-yamux/v4/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
362
vendor/github.com/libp2p/go-yamux/v4/LICENSE
generated
vendored
Normal file
362
vendor/github.com/libp2p/go-yamux/v4/LICENSE
generated
vendored
Normal file
@@ -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.
|
||||
27
vendor/github.com/libp2p/go-yamux/v4/LICENSE-BSD
generated
vendored
Normal file
27
vendor/github.com/libp2p/go-yamux/v4/LICENSE-BSD
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
87
vendor/github.com/libp2p/go-yamux/v4/README.md
generated
vendored
Normal file
87
vendor/github.com/libp2p/go-yamux/v4/README.md
generated
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
# 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
|
||||
* 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/libp2p/go-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)
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
The last gx published version of this module was: 1.1.5: QmUNMbRUsVYHi1D14annF7Rr7pQAX7TNLwpRCa975ojKnw
|
||||
60
vendor/github.com/libp2p/go-yamux/v4/addr.go
generated
vendored
Normal file
60
vendor/github.com/libp2p/go-yamux/v4/addr.go
generated
vendored
Normal file
@@ -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()
|
||||
}
|
||||
|
||||
// RemoteAddr returns the remote address
|
||||
func (s *Stream) RemoteAddr() net.Addr {
|
||||
return s.session.RemoteAddr()
|
||||
}
|
||||
178
vendor/github.com/libp2p/go-yamux/v4/const.go
generated
vendored
Normal file
178
vendor/github.com/libp2p/go-yamux/v4/const.go
generated
vendored
Normal file
@@ -0,0 +1,178 @@
|
||||
package yamux
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Error struct {
|
||||
msg string
|
||||
timeout, temporary bool
|
||||
}
|
||||
|
||||
func (ye *Error) Error() string {
|
||||
return ye.msg
|
||||
}
|
||||
|
||||
func (ye *Error) Timeout() bool {
|
||||
return ye.timeout
|
||||
}
|
||||
|
||||
func (ye *Error) Temporary() bool {
|
||||
return ye.temporary
|
||||
}
|
||||
|
||||
var (
|
||||
// ErrInvalidVersion means we received a frame with an
|
||||
// invalid version
|
||||
ErrInvalidVersion = &Error{msg: "invalid protocol version"}
|
||||
|
||||
// ErrInvalidMsgType means we received a frame with an
|
||||
// invalid message type
|
||||
ErrInvalidMsgType = &Error{msg: "invalid msg type"}
|
||||
|
||||
// ErrSessionShutdown is used if there is a shutdown during
|
||||
// an operation
|
||||
ErrSessionShutdown = &Error{msg: "session shutdown"}
|
||||
|
||||
// ErrStreamsExhausted is returned if we have no more
|
||||
// stream ids to issue
|
||||
ErrStreamsExhausted = &Error{msg: "streams exhausted"}
|
||||
|
||||
// ErrDuplicateStream is used if a duplicate stream is
|
||||
// opened inbound
|
||||
ErrDuplicateStream = &Error{msg: "duplicate stream initiated"}
|
||||
|
||||
// ErrReceiveWindowExceeded indicates the window was exceeded
|
||||
ErrRecvWindowExceeded = &Error{msg: "recv window exceeded"}
|
||||
|
||||
// ErrTimeout is used when we reach an IO deadline
|
||||
ErrTimeout = &Error{msg: "i/o deadline reached", timeout: true, temporary: true}
|
||||
|
||||
// ErrStreamClosed is returned when using a closed stream
|
||||
ErrStreamClosed = &Error{msg: "stream closed"}
|
||||
|
||||
// ErrUnexpectedFlag is set when we get an unexpected flag
|
||||
ErrUnexpectedFlag = &Error{msg: "unexpected flag"}
|
||||
|
||||
// ErrRemoteGoAway is used when we get a go away from the other side
|
||||
ErrRemoteGoAway = &Error{msg: "remote end is not accepting connections"}
|
||||
|
||||
// ErrStreamReset is sent if a stream is reset. This can happen
|
||||
// if the backlog is exceeded, or if there was a remote GoAway.
|
||||
ErrStreamReset = &Error{msg: "stream reset"}
|
||||
|
||||
// ErrConnectionWriteTimeout indicates that we hit the "safety valve"
|
||||
// timeout writing to the underlying stream connection.
|
||||
ErrConnectionWriteTimeout = &Error{msg: "connection write timeout", timeout: true}
|
||||
|
||||
// ErrKeepAliveTimeout is sent if a missed keepalive caused the stream close
|
||||
ErrKeepAliveTimeout = &Error{msg: "keepalive timeout", timeout: true}
|
||||
)
|
||||
|
||||
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.
|
||||
// It's not an implementation choice, the value defined in the specification.
|
||||
initialStreamWindow = 256 * 1024
|
||||
maxStreamWindow = 16 * 1024 * 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 [headerSize]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 encode(msgType uint8, flags uint16, streamID uint32, length uint32) header {
|
||||
var h header
|
||||
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)
|
||||
return h
|
||||
}
|
||||
80
vendor/github.com/libp2p/go-yamux/v4/deadline.go
generated
vendored
Normal file
80
vendor/github.com/libp2p/go-yamux/v4/deadline.go
generated
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
// Copied from the go standard library.
|
||||
//
|
||||
// Copyright 2010 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE-BSD file.
|
||||
|
||||
package yamux
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// pipeDeadline is an abstraction for handling timeouts.
|
||||
type pipeDeadline struct {
|
||||
mu sync.Mutex // Guards timer and cancel
|
||||
timer *time.Timer
|
||||
cancel chan struct{} // Must be non-nil
|
||||
}
|
||||
|
||||
func makePipeDeadline() pipeDeadline {
|
||||
return pipeDeadline{cancel: make(chan struct{})}
|
||||
}
|
||||
|
||||
// set sets the point in time when the deadline will time out.
|
||||
// A timeout event is signaled by closing the channel returned by waiter.
|
||||
// Once a timeout has occurred, the deadline can be refreshed by specifying a
|
||||
// t value in the future.
|
||||
//
|
||||
// A zero value for t prevents timeout.
|
||||
func (d *pipeDeadline) set(t time.Time) {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
if d.timer != nil && !d.timer.Stop() {
|
||||
<-d.cancel // Wait for the timer callback to finish and close cancel
|
||||
}
|
||||
d.timer = nil
|
||||
|
||||
// Time is zero, then there is no deadline.
|
||||
closed := isClosedChan(d.cancel)
|
||||
if t.IsZero() {
|
||||
if closed {
|
||||
d.cancel = make(chan struct{})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Time in the future, setup a timer to cancel in the future.
|
||||
if dur := time.Until(t); dur > 0 {
|
||||
if closed {
|
||||
d.cancel = make(chan struct{})
|
||||
}
|
||||
d.timer = time.AfterFunc(dur, func() {
|
||||
close(d.cancel)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Time in the past, so close immediately.
|
||||
if !closed {
|
||||
close(d.cancel)
|
||||
}
|
||||
}
|
||||
|
||||
// wait returns a channel that is closed when the deadline is exceeded.
|
||||
func (d *pipeDeadline) wait() chan struct{} {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
return d.cancel
|
||||
}
|
||||
|
||||
func isClosedChan(c <-chan struct{}) bool {
|
||||
select {
|
||||
case <-c:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
141
vendor/github.com/libp2p/go-yamux/v4/mux.go
generated
vendored
Normal file
141
vendor/github.com/libp2p/go-yamux/v4/mux.go
generated
vendored
Normal file
@@ -0,0 +1,141 @@
|
||||
package yamux
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"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
|
||||
|
||||
// PingBacklog is used to limit how many ping acks we can queue.
|
||||
PingBacklog 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
|
||||
|
||||
// MeasureRTTInterval is how often to re-measure the round trip time
|
||||
MeasureRTTInterval 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
|
||||
|
||||
// MaxIncomingStreams is maximum number of concurrent incoming streams
|
||||
// that we accept. If the peer tries to open more streams, those will be
|
||||
// reset immediately.
|
||||
MaxIncomingStreams uint32
|
||||
|
||||
// InitialStreamWindowSize is used to control the initial
|
||||
// window size that we allow for a stream.
|
||||
InitialStreamWindowSize uint32
|
||||
|
||||
// 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
|
||||
|
||||
// ReadBufSize controls the size of the read buffer.
|
||||
//
|
||||
// Set to 0 to disable it.
|
||||
ReadBufSize int
|
||||
|
||||
// WriteCoalesceDelay is the maximum amount of time we'll delay
|
||||
// coalescing a packet before sending it. This should be on the order of
|
||||
// micro-milliseconds.
|
||||
WriteCoalesceDelay time.Duration
|
||||
|
||||
// MaxMessageSize is the maximum size of a message that we'll send on a
|
||||
// stream. This ensures that a single stream doesn't hog a connection.
|
||||
MaxMessageSize uint32
|
||||
}
|
||||
|
||||
// DefaultConfig is used to return a default configuration
|
||||
func DefaultConfig() *Config {
|
||||
return &Config{
|
||||
AcceptBacklog: 256,
|
||||
PingBacklog: 32,
|
||||
EnableKeepAlive: true,
|
||||
KeepAliveInterval: 30 * time.Second,
|
||||
MeasureRTTInterval: 30 * time.Second,
|
||||
ConnectionWriteTimeout: 10 * time.Second,
|
||||
MaxIncomingStreams: 1000,
|
||||
InitialStreamWindowSize: initialStreamWindow,
|
||||
MaxStreamWindowSize: maxStreamWindow,
|
||||
LogOutput: os.Stderr,
|
||||
ReadBufSize: 4096,
|
||||
MaxMessageSize: 64 * 1024,
|
||||
WriteCoalesceDelay: 100 * time.Microsecond,
|
||||
}
|
||||
}
|
||||
|
||||
// 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.MeasureRTTInterval == 0 {
|
||||
return fmt.Errorf("measure-rtt interval must be positive")
|
||||
}
|
||||
|
||||
if config.InitialStreamWindowSize < initialStreamWindow {
|
||||
return errors.New("InitialStreamWindowSize must be larger or equal 256 kB")
|
||||
}
|
||||
if config.MaxStreamWindowSize < config.InitialStreamWindowSize {
|
||||
return errors.New("MaxStreamWindowSize must be larger than the InitialStreamWindowSize")
|
||||
}
|
||||
if config.MaxMessageSize < 1024 {
|
||||
return fmt.Errorf("MaxMessageSize must be greater than a kilobyte")
|
||||
}
|
||||
if config.WriteCoalesceDelay < 0 {
|
||||
return fmt.Errorf("WriteCoalesceDelay must be >= 0")
|
||||
}
|
||||
if config.PingBacklog < 1 {
|
||||
return fmt.Errorf("PingBacklog must be > 0")
|
||||
}
|
||||
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 net.Conn, config *Config, mm func() (MemoryManager, error)) (*Session, error) {
|
||||
if config == nil {
|
||||
config = DefaultConfig()
|
||||
}
|
||||
if err := VerifyConfig(config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newSession(config, conn, false, config.ReadBufSize, mm), nil
|
||||
}
|
||||
|
||||
// Client is used to initialize a new client-side connection.
|
||||
// There must be at most one client-side connection.
|
||||
func Client(conn net.Conn, config *Config, mm func() (MemoryManager, error)) (*Session, error) {
|
||||
if config == nil {
|
||||
config = DefaultConfig()
|
||||
}
|
||||
|
||||
if err := VerifyConfig(config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newSession(config, conn, true, config.ReadBufSize, mm), nil
|
||||
}
|
||||
34
vendor/github.com/libp2p/go-yamux/v4/ping.go
generated
vendored
Normal file
34
vendor/github.com/libp2p/go-yamux/v4/ping.go
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
package yamux
|
||||
|
||||
import "time"
|
||||
|
||||
type ping struct {
|
||||
id uint32
|
||||
// written to by the session on ping response
|
||||
pingResponse chan struct{}
|
||||
|
||||
// closed by the Ping call that sent the ping when done.
|
||||
done chan struct{}
|
||||
// result set before done is closed.
|
||||
err error
|
||||
duration time.Duration
|
||||
}
|
||||
|
||||
func newPing(id uint32) *ping {
|
||||
return &ping{
|
||||
id: id,
|
||||
pingResponse: make(chan struct{}, 1),
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ping) finish(val time.Duration, err error) {
|
||||
p.err = err
|
||||
p.duration = val
|
||||
close(p.done)
|
||||
}
|
||||
|
||||
func (p *ping) wait() (time.Duration, error) {
|
||||
<-p.done
|
||||
return p.duration, p.err
|
||||
}
|
||||
907
vendor/github.com/libp2p/go-yamux/v4/session.go
generated
vendored
Normal file
907
vendor/github.com/libp2p/go-yamux/v4/session.go
generated
vendored
Normal file
@@ -0,0 +1,907 @@
|
||||
package yamux
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"math"
|
||||
"net"
|
||||
"os"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
pool "github.com/libp2p/go-buffer-pool"
|
||||
)
|
||||
|
||||
// The MemoryManager allows management of memory allocations.
|
||||
// Memory is allocated:
|
||||
// 1. When opening / accepting a new stream. This uses the highest priority.
|
||||
// 2. When trying to increase the stream receive window. This uses a lower priority.
|
||||
// This is a subset of the libp2p's resource manager ResourceScopeSpan interface.
|
||||
type MemoryManager interface {
|
||||
ReserveMemory(size int, prio uint8) error
|
||||
|
||||
// ReleaseMemory explicitly releases memory previously reserved with ReserveMemory
|
||||
ReleaseMemory(size int)
|
||||
|
||||
// Done ends the span and releases associated resources.
|
||||
Done()
|
||||
}
|
||||
|
||||
type nullMemoryManagerImpl struct{}
|
||||
|
||||
func (n nullMemoryManagerImpl) ReserveMemory(size int, prio uint8) error { return nil }
|
||||
func (n nullMemoryManagerImpl) ReleaseMemory(size int) {}
|
||||
func (n nullMemoryManagerImpl) Done() {}
|
||||
|
||||
var nullMemoryManager = &nullMemoryManagerImpl{}
|
||||
|
||||
// Session is used to wrap a reliable ordered connection and to
|
||||
// multiplex it into multiple streams.
|
||||
type Session struct {
|
||||
rtt int64 // to be accessed atomically, in nanoseconds
|
||||
|
||||
// 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 net.Conn
|
||||
|
||||
// reader is a buffered reader
|
||||
reader io.Reader
|
||||
|
||||
newMemoryManager func() (MemoryManager, error)
|
||||
|
||||
// pings is used to track inflight pings
|
||||
pingLock sync.Mutex
|
||||
pingID uint32
|
||||
activePing *ping
|
||||
|
||||
// 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.
|
||||
numIncomingStreams uint32
|
||||
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 send messages
|
||||
sendCh chan []byte
|
||||
|
||||
// pingCh and pingCh are used to send pings and pongs
|
||||
pongCh, pingCh chan uint32
|
||||
|
||||
// recvDoneCh is closed when recv() exits to avoid a race
|
||||
// between stream registration and stream shutdown
|
||||
recvDoneCh chan struct{}
|
||||
|
||||
// sendDoneCh is closed when send() exits to avoid a race
|
||||
// between returning from a Stream.Write and exiting from the send loop
|
||||
// (which may be reading a buffer on-load-from Stream.Write).
|
||||
sendDoneCh chan struct{}
|
||||
|
||||
// client is true if we're the client and our stream IDs should be odd.
|
||||
client bool
|
||||
|
||||
// shutdown is used to safely close a session
|
||||
shutdown bool
|
||||
shutdownErr error
|
||||
shutdownCh chan struct{}
|
||||
shutdownLock sync.Mutex
|
||||
|
||||
// keepaliveTimer is a periodic timer for keepalive messages. It's nil
|
||||
// when keepalives are disabled.
|
||||
keepaliveLock sync.Mutex
|
||||
keepaliveTimer *time.Timer
|
||||
keepaliveActive bool
|
||||
}
|
||||
|
||||
// newSession is used to construct a new session
|
||||
func newSession(config *Config, conn net.Conn, client bool, readBuf int, newMemoryManager func() (MemoryManager, error)) *Session {
|
||||
var reader io.Reader = conn
|
||||
if readBuf > 0 {
|
||||
reader = bufio.NewReaderSize(reader, readBuf)
|
||||
}
|
||||
if newMemoryManager == nil {
|
||||
newMemoryManager = func() (MemoryManager, error) { return nullMemoryManager, nil }
|
||||
}
|
||||
s := &Session{
|
||||
config: config,
|
||||
client: client,
|
||||
logger: log.New(config.LogOutput, "", log.LstdFlags),
|
||||
conn: conn,
|
||||
reader: reader,
|
||||
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 []byte, 64),
|
||||
pongCh: make(chan uint32, config.PingBacklog),
|
||||
pingCh: make(chan uint32),
|
||||
recvDoneCh: make(chan struct{}),
|
||||
sendDoneCh: make(chan struct{}),
|
||||
shutdownCh: make(chan struct{}),
|
||||
newMemoryManager: newMemoryManager,
|
||||
}
|
||||
if client {
|
||||
s.nextStreamID = 1
|
||||
} else {
|
||||
s.nextStreamID = 2
|
||||
}
|
||||
if config.EnableKeepAlive {
|
||||
s.startKeepalive()
|
||||
}
|
||||
go s.recv()
|
||||
go s.send()
|
||||
go s.startMeasureRTT()
|
||||
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(ctx context.Context) (net.Conn, error) {
|
||||
conn, err := s.OpenStream(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// OpenStream is used to create a new stream
|
||||
func (s *Session) OpenStream(ctx context.Context) (*Stream, error) {
|
||||
if s.IsClosed() {
|
||||
return nil, s.shutdownErr
|
||||
}
|
||||
if atomic.LoadInt32(&s.remoteGoAway) == 1 {
|
||||
return nil, ErrRemoteGoAway
|
||||
}
|
||||
|
||||
// Block if we have too many inflight SYNs
|
||||
select {
|
||||
case s.synCh <- struct{}{}:
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
case <-s.shutdownCh:
|
||||
return nil, s.shutdownErr
|
||||
}
|
||||
|
||||
span, err := s.newMemoryManager()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create resource scope span: %w", err)
|
||||
}
|
||||
if err := span.ReserveMemory(initialStreamWindow, 255); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
GET_ID:
|
||||
// Get an ID, and check for stream exhaustion
|
||||
id := atomic.LoadUint32(&s.nextStreamID)
|
||||
if id >= math.MaxUint32-1 {
|
||||
span.Done()
|
||||
return nil, ErrStreamsExhausted
|
||||
}
|
||||
if !atomic.CompareAndSwapUint32(&s.nextStreamID, id, id+2) {
|
||||
goto GET_ID
|
||||
}
|
||||
|
||||
// Register the stream
|
||||
stream := newStream(s, id, streamInit, initialStreamWindow, span)
|
||||
s.streamLock.Lock()
|
||||
s.streams[id] = stream
|
||||
s.inflight[id] = struct{}{}
|
||||
s.streamLock.Unlock()
|
||||
|
||||
// Send the window update to create
|
||||
if err := stream.sendWindowUpdate(ctx.Done()); err != nil {
|
||||
defer span.Done()
|
||||
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) {
|
||||
for {
|
||||
select {
|
||||
case stream := <-s.acceptCh:
|
||||
if err := stream.sendWindowUpdate(nil); err != nil {
|
||||
// don't return accept errors.
|
||||
s.logger.Printf("[WARN] error sending window update before accepting: %s", err)
|
||||
continue
|
||||
}
|
||||
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.stopKeepalive()
|
||||
<-s.recvDoneCh
|
||||
<-s.sendDoneCh
|
||||
|
||||
s.streamLock.Lock()
|
||||
defer s.streamLock.Unlock()
|
||||
for id, stream := range s.streams {
|
||||
stream.forceClose()
|
||||
delete(s.streams, id)
|
||||
stream.memorySpan.Done()
|
||||
}
|
||||
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.sendMsg(s.goAway(goAwayNormal), nil, nil)
|
||||
}
|
||||
|
||||
// goAway is used to send a goAway message
|
||||
func (s *Session) goAway(reason uint32) header {
|
||||
atomic.SwapInt32(&s.localGoAway, 1)
|
||||
hdr := encode(typeGoAway, 0, 0, reason)
|
||||
return hdr
|
||||
}
|
||||
|
||||
func (s *Session) measureRTT() {
|
||||
rtt, err := s.Ping()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !atomic.CompareAndSwapInt64(&s.rtt, 0, rtt.Nanoseconds()) {
|
||||
prev := atomic.LoadInt64(&s.rtt)
|
||||
smoothedRTT := prev/2 + rtt.Nanoseconds()/2
|
||||
atomic.StoreInt64(&s.rtt, smoothedRTT)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Session) startMeasureRTT() {
|
||||
s.measureRTT()
|
||||
t := time.NewTicker(s.config.MeasureRTTInterval)
|
||||
defer t.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-s.CloseChan():
|
||||
return
|
||||
case <-t.C:
|
||||
s.measureRTT()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 0 if we don't yet have a measurement
|
||||
func (s *Session) getRTT() time.Duration {
|
||||
return time.Duration(atomic.LoadInt64(&s.rtt))
|
||||
}
|
||||
|
||||
// Ping is used to measure the RTT response time
|
||||
func (s *Session) Ping() (dur time.Duration, err error) {
|
||||
// Prepare a ping.
|
||||
s.pingLock.Lock()
|
||||
// If there's an active ping, jump on the bandwagon.
|
||||
if activePing := s.activePing; activePing != nil {
|
||||
s.pingLock.Unlock()
|
||||
return activePing.wait()
|
||||
}
|
||||
|
||||
// Ok, our job to send the ping.
|
||||
activePing := newPing(s.pingID)
|
||||
s.pingID++
|
||||
s.activePing = activePing
|
||||
s.pingLock.Unlock()
|
||||
|
||||
defer func() {
|
||||
// complete ping promise
|
||||
activePing.finish(dur, err)
|
||||
|
||||
// Unset it.
|
||||
s.pingLock.Lock()
|
||||
s.activePing = nil
|
||||
s.pingLock.Unlock()
|
||||
}()
|
||||
|
||||
// Send the ping request, waiting at most one connection write timeout
|
||||
// to flush it.
|
||||
timer := time.NewTimer(s.config.ConnectionWriteTimeout)
|
||||
defer timer.Stop()
|
||||
select {
|
||||
case s.pingCh <- activePing.id:
|
||||
case <-timer.C:
|
||||
return 0, ErrTimeout
|
||||
case <-s.shutdownCh:
|
||||
return 0, s.shutdownErr
|
||||
}
|
||||
|
||||
// The "time" starts once we've actually sent the ping. Otherwise, we'll
|
||||
// measure the time it takes to flush the queue as well.
|
||||
start := time.Now()
|
||||
|
||||
// Wait for a response, again waiting at most one write timeout.
|
||||
if !timer.Stop() {
|
||||
<-timer.C
|
||||
}
|
||||
timer.Reset(s.config.ConnectionWriteTimeout)
|
||||
select {
|
||||
case <-activePing.pingResponse:
|
||||
case <-timer.C:
|
||||
return 0, ErrTimeout
|
||||
case <-s.shutdownCh:
|
||||
return 0, s.shutdownErr
|
||||
}
|
||||
|
||||
// Compute the RTT
|
||||
return time.Since(start), nil
|
||||
}
|
||||
|
||||
// startKeepalive starts the keepalive process.
|
||||
func (s *Session) startKeepalive() {
|
||||
s.keepaliveLock.Lock()
|
||||
defer s.keepaliveLock.Unlock()
|
||||
s.keepaliveTimer = time.AfterFunc(s.config.KeepAliveInterval, func() {
|
||||
s.keepaliveLock.Lock()
|
||||
if s.keepaliveTimer == nil || s.keepaliveActive {
|
||||
// keepalives have been stopped or a keepalive is active.
|
||||
s.keepaliveLock.Unlock()
|
||||
return
|
||||
}
|
||||
s.keepaliveActive = true
|
||||
s.keepaliveLock.Unlock()
|
||||
|
||||
_, err := s.Ping()
|
||||
|
||||
s.keepaliveLock.Lock()
|
||||
s.keepaliveActive = false
|
||||
if s.keepaliveTimer != nil {
|
||||
s.keepaliveTimer.Reset(s.config.KeepAliveInterval)
|
||||
}
|
||||
s.keepaliveLock.Unlock()
|
||||
|
||||
if err != nil {
|
||||
s.logger.Printf("[ERR] yamux: keepalive failed: %v", err)
|
||||
s.exitErr(ErrKeepAliveTimeout)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// stopKeepalive stops the keepalive process.
|
||||
func (s *Session) stopKeepalive() {
|
||||
s.keepaliveLock.Lock()
|
||||
defer s.keepaliveLock.Unlock()
|
||||
if s.keepaliveTimer != nil {
|
||||
s.keepaliveTimer.Stop()
|
||||
s.keepaliveTimer = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Session) extendKeepalive() {
|
||||
s.keepaliveLock.Lock()
|
||||
if s.keepaliveTimer != nil && !s.keepaliveActive {
|
||||
// Don't stop the timer and drain the channel. This is an
|
||||
// AfterFunc, not a normal timer, and any attempts to drain the
|
||||
// channel will block forever.
|
||||
//
|
||||
// Go will stop the timer for us internally anyways. The docs
|
||||
// say one must stop the timer before calling reset but that's
|
||||
// to ensure that the timer doesn't end up firing immediately
|
||||
// after calling Reset.
|
||||
s.keepaliveTimer.Reset(s.config.KeepAliveInterval)
|
||||
}
|
||||
s.keepaliveLock.Unlock()
|
||||
}
|
||||
|
||||
// send sends the header and body.
|
||||
func (s *Session) sendMsg(hdr header, body []byte, deadline <-chan struct{}) error {
|
||||
select {
|
||||
case <-s.shutdownCh:
|
||||
return s.shutdownErr
|
||||
default:
|
||||
}
|
||||
|
||||
// duplicate as we're sending this async.
|
||||
buf := pool.Get(headerSize + len(body))
|
||||
copy(buf[:headerSize], hdr[:])
|
||||
copy(buf[headerSize:], body)
|
||||
|
||||
select {
|
||||
case <-s.shutdownCh:
|
||||
pool.Put(buf)
|
||||
return s.shutdownErr
|
||||
case s.sendCh <- buf:
|
||||
return nil
|
||||
case <-deadline:
|
||||
pool.Put(buf)
|
||||
return ErrTimeout
|
||||
}
|
||||
}
|
||||
|
||||
// send is a long running goroutine that sends data
|
||||
func (s *Session) send() {
|
||||
if err := s.sendLoop(); err != nil {
|
||||
s.exitErr(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Session) sendLoop() (err error) {
|
||||
defer func() {
|
||||
if rerr := recover(); rerr != nil {
|
||||
fmt.Fprintf(os.Stderr, "caught panic: %s\n%s\n", rerr, debug.Stack())
|
||||
err = fmt.Errorf("panic in yamux send loop: %s", rerr)
|
||||
}
|
||||
}()
|
||||
|
||||
defer close(s.sendDoneCh)
|
||||
|
||||
// Extend the write deadline if we've passed the halfway point. This can
|
||||
// be expensive so this ensures we only have to do this once every
|
||||
// ConnectionWriteTimeout/2 (usually 5s).
|
||||
var lastWriteDeadline time.Time
|
||||
extendWriteDeadline := func() error {
|
||||
now := time.Now()
|
||||
// If over half of the deadline has elapsed, extend it.
|
||||
if now.Add(s.config.ConnectionWriteTimeout / 2).After(lastWriteDeadline) {
|
||||
lastWriteDeadline = now.Add(s.config.ConnectionWriteTimeout)
|
||||
return s.conn.SetWriteDeadline(lastWriteDeadline)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
writer := s.conn
|
||||
|
||||
// FIXME: https://github.com/libp2p/go-libp2p/issues/644
|
||||
// Write coalescing is disabled for now.
|
||||
|
||||
// writer := pool.Writer{W: s.conn}
|
||||
|
||||
// var writeTimeout *time.Timer
|
||||
// var writeTimeoutCh <-chan time.Time
|
||||
// if s.config.WriteCoalesceDelay > 0 {
|
||||
// writeTimeout = time.NewTimer(s.config.WriteCoalesceDelay)
|
||||
// defer writeTimeout.Stop()
|
||||
|
||||
// writeTimeoutCh = writeTimeout.C
|
||||
// } else {
|
||||
// ch := make(chan time.Time)
|
||||
// close(ch)
|
||||
// writeTimeoutCh = ch
|
||||
// }
|
||||
|
||||
for {
|
||||
// yield after processing the last message, if we've shutdown.
|
||||
// s.sendCh is a buffered channel and Go doesn't guarantee select order.
|
||||
select {
|
||||
case <-s.shutdownCh:
|
||||
return nil
|
||||
default:
|
||||
}
|
||||
|
||||
var buf []byte
|
||||
// Make sure to send any pings & pongs first so they don't get stuck behind writes.
|
||||
select {
|
||||
case pingID := <-s.pingCh:
|
||||
buf = pool.Get(headerSize)
|
||||
hdr := encode(typePing, flagSYN, 0, pingID)
|
||||
copy(buf, hdr[:])
|
||||
case pingID := <-s.pongCh:
|
||||
buf = pool.Get(headerSize)
|
||||
hdr := encode(typePing, flagACK, 0, pingID)
|
||||
copy(buf, hdr[:])
|
||||
default:
|
||||
// Then send normal data.
|
||||
select {
|
||||
case buf = <-s.sendCh:
|
||||
case pingID := <-s.pingCh:
|
||||
buf = pool.Get(headerSize)
|
||||
hdr := encode(typePing, flagSYN, 0, pingID)
|
||||
copy(buf, hdr[:])
|
||||
case pingID := <-s.pongCh:
|
||||
buf = pool.Get(headerSize)
|
||||
hdr := encode(typePing, flagACK, 0, pingID)
|
||||
copy(buf, hdr[:])
|
||||
case <-s.shutdownCh:
|
||||
return nil
|
||||
// default:
|
||||
// select {
|
||||
// case buf = <-s.sendCh:
|
||||
// case <-s.shutdownCh:
|
||||
// return nil
|
||||
// case <-writeTimeoutCh:
|
||||
// if err := writer.Flush(); err != nil {
|
||||
// if os.IsTimeout(err) {
|
||||
// err = ErrConnectionWriteTimeout
|
||||
// }
|
||||
// return err
|
||||
// }
|
||||
|
||||
// select {
|
||||
// case buf = <-s.sendCh:
|
||||
// case <-s.shutdownCh:
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// if writeTimeout != nil {
|
||||
// writeTimeout.Reset(s.config.WriteCoalesceDelay)
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
if err := extendWriteDeadline(); err != nil {
|
||||
pool.Put(buf)
|
||||
return err
|
||||
}
|
||||
|
||||
_, err := writer.Write(buf)
|
||||
pool.Put(buf)
|
||||
|
||||
if err != nil {
|
||||
if os.IsTimeout(err) {
|
||||
err = ErrConnectionWriteTimeout
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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() (err error) {
|
||||
defer func() {
|
||||
if rerr := recover(); rerr != nil {
|
||||
fmt.Fprintf(os.Stderr, "caught panic: %s\n%s\n", rerr, debug.Stack())
|
||||
err = fmt.Errorf("panic in yamux receive loop: %s", rerr)
|
||||
}
|
||||
}()
|
||||
defer close(s.recvDoneCh)
|
||||
var hdr header
|
||||
for {
|
||||
// fmt.Printf("ReadFull from %#v\n", s.reader)
|
||||
// Read the header
|
||||
if _, err := io.ReadFull(s.reader, 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
|
||||
}
|
||||
|
||||
// Reset the keepalive timer every time we receive data.
|
||||
// There's no reason to keepalive if we're active. Worse, if the
|
||||
// peer is busy sending us stuff, the pong might get stuck
|
||||
// behind a bunch of data.
|
||||
s.extendKeepalive()
|
||||
|
||||
// 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(io.Discard, s.reader, 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 {
|
||||
stream.incrSendWindow(hdr, flags)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read the new data
|
||||
if err := stream.readData(hdr, flags, s.reader); err != nil {
|
||||
if sendErr := s.sendMsg(s.goAway(goAwayProtoErr), nil, nil); sendErr != nil {
|
||||
s.logger.Printf("[WARN] yamux: failed to send go away: %v", sendErr)
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// handlePing is invoked 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 {
|
||||
select {
|
||||
case s.pongCh <- pingID:
|
||||
default:
|
||||
s.logger.Printf("[WARN] yamux: dropped ping reply")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Handle a response
|
||||
s.pingLock.Lock()
|
||||
// If we have an active ping, and this is a response to that active
|
||||
// ping, complete the ping.
|
||||
if s.activePing != nil && s.activePing.id == pingID {
|
||||
// Don't assume that the peer won't send multiple responses for
|
||||
// the same ping.
|
||||
select {
|
||||
case s.activePing.pingResponse <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
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 {
|
||||
if s.client != (id%2 == 0) {
|
||||
s.logger.Printf("[ERR] yamux: both endpoints are clients")
|
||||
return fmt.Errorf("both yamux endpoints are clients")
|
||||
}
|
||||
// Reject immediately if we are doing a go away
|
||||
if atomic.LoadInt32(&s.localGoAway) == 1 {
|
||||
hdr := encode(typeWindowUpdate, flagRST, id, 0)
|
||||
return s.sendMsg(hdr, nil, nil)
|
||||
}
|
||||
|
||||
// Allocate a new stream
|
||||
span, err := s.newMemoryManager()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create resource span: %w", err)
|
||||
}
|
||||
if err := span.ReserveMemory(initialStreamWindow, 255); err != nil {
|
||||
return err
|
||||
}
|
||||
stream := newStream(s, id, streamSYNReceived, initialStreamWindow, span)
|
||||
|
||||
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.sendMsg(s.goAway(goAwayProtoErr), nil, nil); sendErr != nil {
|
||||
s.logger.Printf("[WARN] yamux: failed to send go away: %v", sendErr)
|
||||
}
|
||||
span.Done()
|
||||
return ErrDuplicateStream
|
||||
}
|
||||
|
||||
if s.numIncomingStreams >= s.config.MaxIncomingStreams {
|
||||
// too many active streams at the same time
|
||||
s.logger.Printf("[WARN] yamux: MaxIncomingStreams exceeded, forcing stream reset")
|
||||
defer span.Done()
|
||||
hdr := encode(typeWindowUpdate, flagRST, id, 0)
|
||||
return s.sendMsg(hdr, nil, nil)
|
||||
}
|
||||
|
||||
s.numIncomingStreams++
|
||||
// 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
|
||||
defer span.Done()
|
||||
s.logger.Printf("[WARN] yamux: backlog exceeded, forcing stream reset")
|
||||
s.deleteStream(id)
|
||||
hdr := encode(typeWindowUpdate, flagRST, id, 0)
|
||||
return s.sendMsg(hdr, nil, nil)
|
||||
}
|
||||
}
|
||||
|
||||
// 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()
|
||||
defer s.streamLock.Unlock()
|
||||
if _, ok := s.inflight[id]; ok {
|
||||
select {
|
||||
case <-s.synCh:
|
||||
default:
|
||||
s.logger.Printf("[ERR] yamux: SYN tracking out of sync")
|
||||
}
|
||||
delete(s.inflight, id)
|
||||
}
|
||||
s.deleteStream(id)
|
||||
}
|
||||
|
||||
func (s *Session) deleteStream(id uint32) {
|
||||
str, ok := s.streams[id]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if s.client == (id%2 == 0) {
|
||||
if s.numIncomingStreams == 0 {
|
||||
s.logger.Printf("[ERR] yamux: numIncomingStreams underflow")
|
||||
// prevent the creation of any new streams
|
||||
s.numIncomingStreams = math.MaxUint32
|
||||
} else {
|
||||
s.numIncomingStreams--
|
||||
}
|
||||
}
|
||||
delete(s.streams, id)
|
||||
str.memorySpan.Done()
|
||||
}
|
||||
|
||||
// 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()
|
||||
}
|
||||
140
vendor/github.com/libp2p/go-yamux/v4/spec.md
generated
vendored
Normal file
140
vendor/github.com/libp2p/go-yamux/v4/spec.md
generated
vendored
Normal file
@@ -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
|
||||
497
vendor/github.com/libp2p/go-yamux/v4/stream.go
generated
vendored
Normal file
497
vendor/github.com/libp2p/go-yamux/v4/stream.go
generated
vendored
Normal file
@@ -0,0 +1,497 @@
|
||||
package yamux
|
||||
|
||||
import (
|
||||
"io"
|
||||
"math"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
type streamState int
|
||||
|
||||
const (
|
||||
streamInit streamState = iota
|
||||
streamSYNSent
|
||||
streamSYNReceived
|
||||
streamEstablished
|
||||
streamFinished
|
||||
)
|
||||
|
||||
type halfStreamState int
|
||||
|
||||
const (
|
||||
halfOpen halfStreamState = iota
|
||||
halfClosed
|
||||
halfReset
|
||||
)
|
||||
|
||||
// Stream is used to represent a logical stream
|
||||
// within a session.
|
||||
type Stream struct {
|
||||
sendWindow uint32
|
||||
|
||||
memorySpan MemoryManager
|
||||
|
||||
id uint32
|
||||
session *Session
|
||||
|
||||
recvWindow uint32
|
||||
epochStart time.Time
|
||||
|
||||
state streamState
|
||||
writeState, readState halfStreamState
|
||||
stateLock sync.Mutex
|
||||
|
||||
recvBuf segmentedBuffer
|
||||
|
||||
recvNotifyCh chan struct{}
|
||||
sendNotifyCh chan struct{}
|
||||
|
||||
readDeadline, writeDeadline pipeDeadline
|
||||
}
|
||||
|
||||
// newStream is used to construct a new stream within a given session for an ID.
|
||||
// It assumes that a memory allocation has been obtained for the initialWindow.
|
||||
func newStream(session *Session, id uint32, state streamState, initialWindow uint32, memorySpan MemoryManager) *Stream {
|
||||
s := &Stream{
|
||||
id: id,
|
||||
session: session,
|
||||
state: state,
|
||||
sendWindow: initialStreamWindow,
|
||||
readDeadline: makePipeDeadline(),
|
||||
writeDeadline: makePipeDeadline(),
|
||||
memorySpan: memorySpan,
|
||||
// Initialize the recvBuf with initialStreamWindow, not config.InitialStreamWindowSize.
|
||||
// The peer isn't allowed to send more data than initialStreamWindow until we've sent
|
||||
// the first window update (which will grant it up to config.InitialStreamWindowSize).
|
||||
recvBuf: newSegmentedBuffer(initialWindow),
|
||||
recvWindow: session.config.InitialStreamWindowSize,
|
||||
epochStart: time.Now(),
|
||||
recvNotifyCh: make(chan struct{}, 1),
|
||||
sendNotifyCh: make(chan struct{}, 1),
|
||||
}
|
||||
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) {
|
||||
START:
|
||||
s.stateLock.Lock()
|
||||
state := s.readState
|
||||
s.stateLock.Unlock()
|
||||
|
||||
switch state {
|
||||
case halfOpen:
|
||||
// Open -> read
|
||||
case halfClosed:
|
||||
empty := s.recvBuf.Len() == 0
|
||||
if empty {
|
||||
return 0, io.EOF
|
||||
}
|
||||
// Closed, but we have data pending -> read.
|
||||
case halfReset:
|
||||
return 0, ErrStreamReset
|
||||
default:
|
||||
panic("unknown state")
|
||||
}
|
||||
|
||||
// If there is no data available, block
|
||||
if s.recvBuf.Len() == 0 {
|
||||
select {
|
||||
case <-s.recvNotifyCh:
|
||||
goto START
|
||||
case <-s.readDeadline.wait():
|
||||
return 0, ErrTimeout
|
||||
}
|
||||
}
|
||||
|
||||
// Read any bytes
|
||||
n, _ = s.recvBuf.Read(b)
|
||||
|
||||
// Send a window update potentially
|
||||
err = s.sendWindowUpdate(s.readDeadline.wait())
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Write is used to write to the stream
|
||||
func (s *Stream) Write(b []byte) (int, error) {
|
||||
var total int
|
||||
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 hdr header
|
||||
|
||||
START:
|
||||
s.stateLock.Lock()
|
||||
state := s.writeState
|
||||
s.stateLock.Unlock()
|
||||
|
||||
switch state {
|
||||
case halfOpen:
|
||||
// Open for writing -> write
|
||||
case halfClosed:
|
||||
return 0, ErrStreamClosed
|
||||
case halfReset:
|
||||
return 0, ErrStreamReset
|
||||
default:
|
||||
panic("unknown state")
|
||||
}
|
||||
|
||||
// If there is no data available, block
|
||||
window := atomic.LoadUint32(&s.sendWindow)
|
||||
if window == 0 {
|
||||
select {
|
||||
case <-s.sendNotifyCh:
|
||||
goto START
|
||||
case <-s.writeDeadline.wait():
|
||||
return 0, ErrTimeout
|
||||
}
|
||||
}
|
||||
|
||||
// Determine the flags if any
|
||||
flags = s.sendFlags()
|
||||
|
||||
// Send up to min(message, window
|
||||
max = min(window, s.session.config.MaxMessageSize-headerSize, uint32(len(b)))
|
||||
|
||||
// Send the header
|
||||
hdr = encode(typeData, flags, s.id, max)
|
||||
if err = s.session.sendMsg(hdr, b[:max], s.writeDeadline.wait()); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Reduce our send window
|
||||
atomic.AddUint32(&s.sendWindow, ^uint32(max-1))
|
||||
|
||||
// Unlock
|
||||
return int(max), err
|
||||
}
|
||||
|
||||
// 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(deadline <-chan struct{}) error {
|
||||
// Determine the flags if any
|
||||
flags := s.sendFlags()
|
||||
|
||||
// Update the receive window.
|
||||
needed, delta := s.recvBuf.GrowTo(s.recvWindow, flags != 0)
|
||||
if !needed {
|
||||
return nil
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
if rtt := s.session.getRTT(); flags == 0 && rtt > 0 && now.Sub(s.epochStart) < rtt*4 {
|
||||
var recvWindow uint32
|
||||
if s.recvWindow > math.MaxUint32/2 {
|
||||
recvWindow = min(math.MaxUint32, s.session.config.MaxStreamWindowSize)
|
||||
} else {
|
||||
recvWindow = min(s.recvWindow*2, s.session.config.MaxStreamWindowSize)
|
||||
}
|
||||
if recvWindow > s.recvWindow {
|
||||
grow := recvWindow - s.recvWindow
|
||||
if err := s.memorySpan.ReserveMemory(int(grow), 128); err == nil {
|
||||
s.recvWindow = recvWindow
|
||||
_, delta = s.recvBuf.GrowTo(s.recvWindow, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
s.epochStart = now
|
||||
hdr := encode(typeWindowUpdate, flags, s.id, delta)
|
||||
return s.session.sendMsg(hdr, nil, deadline)
|
||||
}
|
||||
|
||||
// sendClose is used to send a FIN
|
||||
func (s *Stream) sendClose() error {
|
||||
flags := s.sendFlags()
|
||||
flags |= flagFIN
|
||||
hdr := encode(typeWindowUpdate, flags, s.id, 0)
|
||||
return s.session.sendMsg(hdr, nil, nil)
|
||||
}
|
||||
|
||||
// sendReset is used to send a RST
|
||||
func (s *Stream) sendReset() error {
|
||||
hdr := encode(typeWindowUpdate, flagRST, s.id, 0)
|
||||
return s.session.sendMsg(hdr, nil, nil)
|
||||
}
|
||||
|
||||
// Reset resets the stream (forcibly closes the stream)
|
||||
func (s *Stream) Reset() error {
|
||||
sendReset := false
|
||||
s.stateLock.Lock()
|
||||
switch s.state {
|
||||
case streamFinished:
|
||||
s.stateLock.Unlock()
|
||||
return nil
|
||||
case streamInit:
|
||||
// we haven't sent anything, so we don't need to send a reset.
|
||||
case streamSYNSent, streamSYNReceived, streamEstablished:
|
||||
sendReset = true
|
||||
default:
|
||||
panic("unhandled state")
|
||||
}
|
||||
|
||||
// at least one direction is open, we need to reset.
|
||||
|
||||
// If we've already sent/received an EOF, no need to reset that side.
|
||||
if s.writeState == halfOpen {
|
||||
s.writeState = halfReset
|
||||
}
|
||||
if s.readState == halfOpen {
|
||||
s.readState = halfReset
|
||||
}
|
||||
s.state = streamFinished
|
||||
s.notifyWaiting()
|
||||
s.stateLock.Unlock()
|
||||
if sendReset {
|
||||
_ = s.sendReset()
|
||||
}
|
||||
s.cleanup()
|
||||
return nil
|
||||
}
|
||||
|
||||
// CloseWrite is used to close the stream for writing.
|
||||
func (s *Stream) CloseWrite() error {
|
||||
s.stateLock.Lock()
|
||||
switch s.writeState {
|
||||
case halfOpen:
|
||||
// Open for writing -> close write
|
||||
case halfClosed:
|
||||
s.stateLock.Unlock()
|
||||
return nil
|
||||
case halfReset:
|
||||
s.stateLock.Unlock()
|
||||
return ErrStreamReset
|
||||
default:
|
||||
panic("invalid state")
|
||||
}
|
||||
s.writeState = halfClosed
|
||||
cleanup := s.readState != halfOpen
|
||||
if cleanup {
|
||||
s.state = streamFinished
|
||||
}
|
||||
s.stateLock.Unlock()
|
||||
s.notifyWaiting()
|
||||
|
||||
err := s.sendClose()
|
||||
if cleanup {
|
||||
// we're fully closed, might as well be nice to the user and
|
||||
// free everything early.
|
||||
s.cleanup()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// CloseRead is used to close the stream for writing.
|
||||
func (s *Stream) CloseRead() error {
|
||||
cleanup := false
|
||||
s.stateLock.Lock()
|
||||
switch s.readState {
|
||||
case halfOpen:
|
||||
// Open for reading -> close read
|
||||
case halfClosed, halfReset:
|
||||
s.stateLock.Unlock()
|
||||
return nil
|
||||
default:
|
||||
panic("invalid state")
|
||||
}
|
||||
s.readState = halfReset
|
||||
cleanup = s.writeState != halfOpen
|
||||
if cleanup {
|
||||
s.state = streamFinished
|
||||
}
|
||||
s.stateLock.Unlock()
|
||||
s.notifyWaiting()
|
||||
if cleanup {
|
||||
// we're fully closed, might as well be nice to the user and
|
||||
// free everything early.
|
||||
s.cleanup()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close is used to close the stream.
|
||||
func (s *Stream) Close() error {
|
||||
_ = s.CloseRead() // can't fail.
|
||||
return s.CloseWrite()
|
||||
}
|
||||
|
||||
// forceClose is used for when the session is exiting
|
||||
func (s *Stream) forceClose() {
|
||||
s.stateLock.Lock()
|
||||
if s.readState == halfOpen {
|
||||
s.readState = halfReset
|
||||
}
|
||||
if s.writeState == halfOpen {
|
||||
s.writeState = halfReset
|
||||
}
|
||||
s.state = streamFinished
|
||||
s.notifyWaiting()
|
||||
s.stateLock.Unlock()
|
||||
|
||||
s.readDeadline.set(time.Time{})
|
||||
s.writeDeadline.set(time.Time{})
|
||||
}
|
||||
|
||||
// called when fully closed to release any system resources.
|
||||
func (s *Stream) cleanup() {
|
||||
s.session.closeStream(s.id)
|
||||
s.readDeadline.set(time.Time{})
|
||||
s.writeDeadline.set(time.Time{})
|
||||
}
|
||||
|
||||
// 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) {
|
||||
// Close the stream without holding the state lock
|
||||
var closeStream bool
|
||||
defer func() {
|
||||
if closeStream {
|
||||
s.cleanup()
|
||||
}
|
||||
}()
|
||||
|
||||
if flags&flagACK == flagACK {
|
||||
s.stateLock.Lock()
|
||||
if s.state == streamSYNSent {
|
||||
s.state = streamEstablished
|
||||
}
|
||||
s.stateLock.Unlock()
|
||||
s.session.establishStream(s.id)
|
||||
}
|
||||
if flags&flagFIN == flagFIN {
|
||||
var notify bool
|
||||
s.stateLock.Lock()
|
||||
if s.readState == halfOpen {
|
||||
s.readState = halfClosed
|
||||
if s.writeState != halfOpen {
|
||||
// We're now fully closed.
|
||||
closeStream = true
|
||||
s.state = streamFinished
|
||||
}
|
||||
notify = true
|
||||
}
|
||||
s.stateLock.Unlock()
|
||||
if notify {
|
||||
s.notifyWaiting()
|
||||
}
|
||||
}
|
||||
if flags&flagRST == flagRST {
|
||||
s.stateLock.Lock()
|
||||
if s.readState == halfOpen {
|
||||
s.readState = halfReset
|
||||
}
|
||||
if s.writeState == halfOpen {
|
||||
s.writeState = halfReset
|
||||
}
|
||||
s.state = streamFinished
|
||||
s.stateLock.Unlock()
|
||||
closeStream = true
|
||||
s.notifyWaiting()
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
s.processFlags(flags)
|
||||
// Increase window, unblock a sender
|
||||
atomic.AddUint32(&s.sendWindow, hdr.Length())
|
||||
asyncNotify(s.sendNotifyCh)
|
||||
}
|
||||
|
||||
// readData is used to handle a data frame
|
||||
func (s *Stream) readData(hdr header, flags uint16, conn io.Reader) error {
|
||||
s.processFlags(flags)
|
||||
|
||||
// Check that our recv window is not exceeded
|
||||
length := hdr.Length()
|
||||
if length == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Copy into buffer
|
||||
if err := s.recvBuf.Append(conn, length); err != nil {
|
||||
s.session.logger.Printf("[ERR] yamux: Failed to read stream data on stream %d: %v", s.id, err)
|
||||
return err
|
||||
}
|
||||
// Unblock the reader
|
||||
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.stateLock.Lock()
|
||||
defer s.stateLock.Unlock()
|
||||
if s.readState == halfOpen {
|
||||
s.readDeadline.set(t)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetWriteDeadline sets the deadline for future Write calls
|
||||
func (s *Stream) SetWriteDeadline(t time.Time) error {
|
||||
s.stateLock.Lock()
|
||||
defer s.stateLock.Unlock()
|
||||
if s.writeState == halfOpen {
|
||||
s.writeDeadline.set(t)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
179
vendor/github.com/libp2p/go-yamux/v4/util.go
generated
vendored
Normal file
179
vendor/github.com/libp2p/go-yamux/v4/util.go
generated
vendored
Normal file
@@ -0,0 +1,179 @@
|
||||
package yamux
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
pool "github.com/libp2p/go-buffer-pool"
|
||||
)
|
||||
|
||||
// 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 a set of values
|
||||
func min(values ...uint32) uint32 {
|
||||
m := values[0]
|
||||
for _, v := range values[1:] {
|
||||
if v < m {
|
||||
m = v
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// The segmented buffer looks like:
|
||||
//
|
||||
// | data | empty space |
|
||||
// < window (10) >
|
||||
// < len (5) > < cap (5) >
|
||||
//
|
||||
// As data is read, the buffer gets updated like so:
|
||||
//
|
||||
// | data | empty space |
|
||||
// < window (8) >
|
||||
// < len (3) > < cap (5) >
|
||||
//
|
||||
// It can then grow as follows (given a "max" of 10):
|
||||
//
|
||||
// | data | empty space |
|
||||
// < window (10) >
|
||||
// < len (3) > < cap (7) >
|
||||
//
|
||||
// Data can then be written into the empty space, expanding len,
|
||||
// and shrinking cap:
|
||||
//
|
||||
// | data | empty space |
|
||||
// < window (10) >
|
||||
// < len (5) > < cap (5) >
|
||||
type segmentedBuffer struct {
|
||||
cap uint32
|
||||
len uint32
|
||||
bm sync.Mutex
|
||||
// read position in b[bPos].
|
||||
// We must not reslice any of the buffers in b, as we need to put them back into the pool.
|
||||
readPos int
|
||||
// bPos is an index in b slice. If bPos == len(b), it means that buffer is empty.
|
||||
bPos int
|
||||
// b is used as a growable buffer. Each Append adds []byte to the end of b.
|
||||
// If there is no space available at the end of the buffer (len(b) == cap(b)), but it has space
|
||||
// at the beginning (bPos > 0 and at least 1/4 of the buffer is empty), data inside b is shifted to the beginning.
|
||||
// Each Read reads from b[bPos] and increments bPos if b[bPos] was fully read.
|
||||
b [][]byte
|
||||
}
|
||||
|
||||
// NewSegmentedBuffer allocates a ring buffer.
|
||||
func newSegmentedBuffer(initialCapacity uint32) segmentedBuffer {
|
||||
return segmentedBuffer{cap: initialCapacity, b: make([][]byte, 0, 16)}
|
||||
}
|
||||
|
||||
// Len is the amount of data in the receive buffer.
|
||||
func (s *segmentedBuffer) Len() uint32 {
|
||||
s.bm.Lock()
|
||||
defer s.bm.Unlock()
|
||||
return s.len
|
||||
}
|
||||
|
||||
// If the space to write into + current buffer size has grown to half of the window size,
|
||||
// grow up to that max size, and indicate how much additional space was reserved.
|
||||
func (s *segmentedBuffer) GrowTo(max uint32, force bool) (bool, uint32) {
|
||||
s.bm.Lock()
|
||||
defer s.bm.Unlock()
|
||||
|
||||
currentWindow := s.cap + s.len
|
||||
if currentWindow >= max {
|
||||
return force, 0
|
||||
}
|
||||
delta := max - currentWindow
|
||||
|
||||
if delta < (max/2) && !force {
|
||||
return false, 0
|
||||
}
|
||||
|
||||
s.cap += delta
|
||||
return true, delta
|
||||
}
|
||||
|
||||
func (s *segmentedBuffer) Read(b []byte) (int, error) {
|
||||
s.bm.Lock()
|
||||
defer s.bm.Unlock()
|
||||
if s.bPos == len(s.b) {
|
||||
return 0, io.EOF
|
||||
}
|
||||
data := s.b[s.bPos][s.readPos:]
|
||||
n := copy(b, data)
|
||||
if n == len(data) {
|
||||
pool.Put(s.b[s.bPos])
|
||||
s.b[s.bPos] = nil
|
||||
s.bPos++
|
||||
s.readPos = 0
|
||||
} else {
|
||||
s.readPos += n
|
||||
}
|
||||
if n > 0 {
|
||||
s.len -= uint32(n)
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (s *segmentedBuffer) checkOverflow(l uint32) error {
|
||||
s.bm.Lock()
|
||||
defer s.bm.Unlock()
|
||||
if s.cap < l {
|
||||
return fmt.Errorf("receive window exceeded (remain: %d, recv: %d)", s.cap, l)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *segmentedBuffer) Append(input io.Reader, length uint32) error {
|
||||
if err := s.checkOverflow(length); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dst := pool.Get(int(length))
|
||||
n, err := io.ReadFull(input, dst)
|
||||
if err == io.EOF {
|
||||
err = io.ErrUnexpectedEOF
|
||||
}
|
||||
s.bm.Lock()
|
||||
defer s.bm.Unlock()
|
||||
if n > 0 {
|
||||
s.len += uint32(n)
|
||||
s.cap -= uint32(n)
|
||||
// s.b has no available space at the end, but has space at the beginning
|
||||
if len(s.b) == cap(s.b) && s.bPos > 0 {
|
||||
if s.bPos == len(s.b) {
|
||||
// the buffer is empty, so just move pos
|
||||
s.bPos = 0
|
||||
s.b = s.b[:0]
|
||||
} else if s.bPos > cap(s.b)/4 {
|
||||
// at least 1/4 of buffer is empty, so shift data to the left to free space at the end
|
||||
copied := copy(s.b, s.b[s.bPos:])
|
||||
// clear references to copied data
|
||||
for i := copied; i < len(s.b); i++ {
|
||||
s.b[i] = nil
|
||||
}
|
||||
s.b = s.b[:copied]
|
||||
s.bPos = 0
|
||||
}
|
||||
}
|
||||
s.b = append(s.b, dst[0:n])
|
||||
}
|
||||
return err
|
||||
}
|
||||
3
vendor/github.com/libp2p/go-yamux/v4/version.json
generated
vendored
Normal file
3
vendor/github.com/libp2p/go-yamux/v4/version.json
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"version": "v4.0.1"
|
||||
}
|
||||
Reference in New Issue
Block a user