Integrate BACKBEAT SDK and resolve KACHING license validation
Major integrations and fixes: - Added BACKBEAT SDK integration for P2P operation timing - Implemented beat-aware status tracking for distributed operations - Added Docker secrets support for secure license management - Resolved KACHING license validation via HTTPS/TLS - Updated docker-compose configuration for clean stack deployment - Disabled rollback policies to prevent deployment failures - Added license credential storage (CHORUS-DEV-MULTI-001) Technical improvements: - BACKBEAT P2P operation tracking with phase management - Enhanced configuration system with file-based secrets - Improved error handling for license validation - Clean separation of KACHING and CHORUS deployment stacks 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
21
vendor/github.com/multiformats/go-multistream/LICENSE
generated
vendored
Normal file
21
vendor/github.com/multiformats/go-multistream/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Jeromy Johnson
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
128
vendor/github.com/multiformats/go-multistream/README.md
generated
vendored
Normal file
128
vendor/github.com/multiformats/go-multistream/README.md
generated
vendored
Normal file
@@ -0,0 +1,128 @@
|
||||
# go-multistream
|
||||
|
||||
[](http://ipn.io)
|
||||
[](https://github.com/multiformats/multiformats)
|
||||
[](https://webchat.freenode.net/?channels=%23ipfs)
|
||||
[](https://github.com/RichardLitt/standard-readme)
|
||||
[](https://godoc.org/github.com/multiformats/go-multistream)
|
||||
[](https://travis-ci.org/multiformats/go-multistream)
|
||||
[](https://codecov.io/github/multiformats/go-multistream?branch=master)
|
||||
|
||||
> an implementation of the multistream protocol in go
|
||||
|
||||
This package implements a simple stream router for the multistream-select protocol.
|
||||
The protocol is defined [here](https://github.com/multiformats/multistream-select).
|
||||
|
||||
## Table of Contents
|
||||
|
||||
|
||||
- [Install](#install)
|
||||
- [Usage](#usage)
|
||||
- [Maintainers](#maintainers)
|
||||
- [Contribute](#contribute)
|
||||
- [License](#license)
|
||||
|
||||
## Install
|
||||
|
||||
`go-multistream` is a standard Go module which can be installed with:
|
||||
|
||||
```sh
|
||||
go get github.com/multiformats/go-multistream
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Example
|
||||
|
||||
This example shows how to use a multistream muxer. A muxer uses user-added handlers to handle different "protocols". The first step when interacting with a connection handler by the muxer is to select the protocol (the example uses `SelectProtoOrFail`). This will then let the muxer use the right handler.
|
||||
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
|
||||
ms "github.com/multiformats/go-multistream"
|
||||
)
|
||||
|
||||
// This example creates a multistream muxer, adds handlers for the protocols
|
||||
// "/cats" and "/dogs" and exposes it on a localhost:8765. It then opens connections
|
||||
// to that port, selects the protocols and tests that the handlers are working.
|
||||
func main() {
|
||||
mux := ms.NewMultistreamMuxer[string]()
|
||||
mux.AddHandler("/cats", func(proto string, rwc io.ReadWriteCloser) error {
|
||||
fmt.Fprintln(rwc, proto, ": HELLO I LIKE CATS")
|
||||
return rwc.Close()
|
||||
})
|
||||
mux.AddHandler("/dogs", func(proto string, rwc io.ReadWriteCloser) error {
|
||||
fmt.Fprintln(rwc, proto, ": HELLO I LIKE DOGS")
|
||||
return rwc.Close()
|
||||
})
|
||||
|
||||
list, err := net.Listen("tcp", ":8765")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
con, err := list.Accept()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
go mux.Handle(con)
|
||||
}
|
||||
}()
|
||||
|
||||
// The Muxer is ready, let's test it
|
||||
conn, err := net.Dial("tcp", ":8765")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Create a new multistream to talk to the muxer
|
||||
// which will negotiate that we want to talk with /cats
|
||||
mstream := ms.NewMSSelect(conn, "/cats")
|
||||
cats, err := ioutil.ReadAll(mstream)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Printf("%s", cats)
|
||||
mstream.Close()
|
||||
|
||||
// A different way of talking to the muxer
|
||||
// is to manually selecting the protocol ourselves
|
||||
conn, err = net.Dial("tcp", ":8765")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
err = ms.SelectProtoOrFail("/dogs", conn)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
dogs, err := ioutil.ReadAll(conn)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Printf("%s", dogs)
|
||||
conn.Close()
|
||||
}
|
||||
```
|
||||
|
||||
## Contribute
|
||||
|
||||
Contributions welcome. Please check out [the issues](https://github.com/multiformats/go-multistream/issues).
|
||||
|
||||
Check out our [contributing document](https://github.com/multiformats/multiformats/blob/master/contributing.md) for more information on how we work, and about contributing in general. Please be aware that all interactions related to multiformats are subject to the IPFS [Code of Conduct](https://github.com/ipfs/community/blob/master/code-of-conduct.md).
|
||||
|
||||
Small note: If editing the README, please conform to the [standard-readme](https://github.com/RichardLitt/standard-readme) specification.
|
||||
|
||||
## License
|
||||
|
||||
[MIT](LICENSE) © 2016 Jeromy Johnson
|
||||
151
vendor/github.com/multiformats/go-multistream/client.go
generated
vendored
Normal file
151
vendor/github.com/multiformats/go-multistream/client.go
generated
vendored
Normal file
@@ -0,0 +1,151 @@
|
||||
package multistream
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"runtime/debug"
|
||||
)
|
||||
|
||||
// ErrNotSupported is the error returned when the muxer doesn't support
|
||||
// the protocols tried for the handshake.
|
||||
type ErrNotSupported[T StringLike] struct {
|
||||
|
||||
// Slice of protocols that were not supported by the muxer
|
||||
Protos []T
|
||||
}
|
||||
|
||||
func (e ErrNotSupported[T]) Error() string {
|
||||
return fmt.Sprintf("protocols not supported: %v", e.Protos)
|
||||
}
|
||||
|
||||
func (e ErrNotSupported[T]) Is(target error) bool {
|
||||
_, ok := target.(ErrNotSupported[T])
|
||||
return ok
|
||||
}
|
||||
|
||||
// ErrNoProtocols is the error returned when the no protocols have been
|
||||
// specified.
|
||||
var ErrNoProtocols = errors.New("no protocols specified")
|
||||
|
||||
// SelectProtoOrFail performs the initial multistream handshake
|
||||
// to inform the muxer of the protocol that will be used to communicate
|
||||
// on this ReadWriteCloser. It returns an error if, for example,
|
||||
// the muxer does not know how to handle this protocol.
|
||||
func SelectProtoOrFail[T StringLike](proto T, rwc io.ReadWriteCloser) (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 selecting protocol: %s", rerr)
|
||||
}
|
||||
}()
|
||||
|
||||
errCh := make(chan error, 1)
|
||||
go func() {
|
||||
var buf bytes.Buffer
|
||||
if err := delitmWriteAll(&buf, []byte(ProtocolID), []byte(proto)); err != nil {
|
||||
errCh <- err
|
||||
return
|
||||
}
|
||||
_, err := io.Copy(rwc, &buf)
|
||||
errCh <- err
|
||||
}()
|
||||
// We have to read *both* errors.
|
||||
err1 := readMultistreamHeader(rwc)
|
||||
err2 := readProto(proto, rwc)
|
||||
if werr := <-errCh; werr != nil {
|
||||
return werr
|
||||
}
|
||||
if err1 != nil {
|
||||
return err1
|
||||
}
|
||||
if err2 != nil {
|
||||
return err2
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SelectOneOf will perform handshakes with the protocols on the given slice
|
||||
// until it finds one which is supported by the muxer.
|
||||
func SelectOneOf[T StringLike](protos []T, rwc io.ReadWriteCloser) (proto T, 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 selecting one of protocols: %s", rerr)
|
||||
}
|
||||
}()
|
||||
|
||||
if len(protos) == 0 {
|
||||
return "", ErrNoProtocols
|
||||
}
|
||||
|
||||
// Use SelectProtoOrFail to pipeline the /multistream/1.0.0 handshake
|
||||
// with an attempt to negotiate the first protocol. If that fails, we
|
||||
// can continue negotiating the rest of the protocols normally.
|
||||
//
|
||||
// This saves us a round trip.
|
||||
switch err := SelectProtoOrFail(protos[0], rwc); err.(type) {
|
||||
case nil:
|
||||
return protos[0], nil
|
||||
case ErrNotSupported[T]: // try others
|
||||
default:
|
||||
return "", err
|
||||
}
|
||||
proto, err = selectProtosOrFail(protos[1:], rwc)
|
||||
if _, ok := err.(ErrNotSupported[T]); ok {
|
||||
return "", ErrNotSupported[T]{protos}
|
||||
}
|
||||
return proto, err
|
||||
}
|
||||
|
||||
func selectProtosOrFail[T StringLike](protos []T, rwc io.ReadWriteCloser) (T, error) {
|
||||
for _, p := range protos {
|
||||
err := trySelect(p, rwc)
|
||||
switch err := err.(type) {
|
||||
case nil:
|
||||
return p, nil
|
||||
case ErrNotSupported[T]:
|
||||
default:
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return "", ErrNotSupported[T]{protos}
|
||||
}
|
||||
|
||||
func readMultistreamHeader(r io.Reader) error {
|
||||
tok, err := ReadNextToken[string](r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if tok != ProtocolID {
|
||||
return errors.New("received mismatch in protocol id")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func trySelect[T StringLike](proto T, rwc io.ReadWriteCloser) error {
|
||||
err := delimWriteBuffered(rwc, []byte(proto))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return readProto(proto, rwc)
|
||||
}
|
||||
|
||||
func readProto[T StringLike](proto T, r io.Reader) error {
|
||||
tok, err := ReadNextToken[T](r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch tok {
|
||||
case proto:
|
||||
return nil
|
||||
case "na":
|
||||
return ErrNotSupported[T]{[]T{proto}}
|
||||
default:
|
||||
return fmt.Errorf("unrecognized response: %s", tok)
|
||||
}
|
||||
}
|
||||
160
vendor/github.com/multiformats/go-multistream/lazyClient.go
generated
vendored
Normal file
160
vendor/github.com/multiformats/go-multistream/lazyClient.go
generated
vendored
Normal file
@@ -0,0 +1,160 @@
|
||||
package multistream
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// NewMSSelect returns a new Multistream which is able to perform
|
||||
// protocol selection with a MultistreamMuxer.
|
||||
func NewMSSelect[T StringLike](c io.ReadWriteCloser, proto T) LazyConn {
|
||||
return &lazyClientConn[T]{
|
||||
protos: []T{ProtocolID, proto},
|
||||
con: c,
|
||||
}
|
||||
}
|
||||
|
||||
// NewMultistream returns a multistream for the given protocol. This will not
|
||||
// perform any protocol selection. If you are using a MultistreamMuxer, use
|
||||
// NewMSSelect.
|
||||
func NewMultistream[T StringLike](c io.ReadWriteCloser, proto T) LazyConn {
|
||||
return &lazyClientConn[T]{
|
||||
protos: []T{proto},
|
||||
con: c,
|
||||
}
|
||||
}
|
||||
|
||||
// lazyClientConn is a ReadWriteCloser adapter that lazily negotiates a protocol
|
||||
// using multistream-select on first use.
|
||||
//
|
||||
// It *does not* block writes waiting for the other end to respond. Instead, it
|
||||
// simply assumes the negotiation went successfully and starts writing data.
|
||||
// See: https://github.com/multiformats/go-multistream/issues/20
|
||||
type lazyClientConn[T StringLike] struct {
|
||||
// Used to ensure we only trigger the write half of the handshake once.
|
||||
rhandshakeOnce sync.Once
|
||||
rerr error
|
||||
|
||||
// Used to ensure we only trigger the read half of the handshake once.
|
||||
whandshakeOnce sync.Once
|
||||
werr error
|
||||
|
||||
// The sequence of protocols to negotiate.
|
||||
protos []T
|
||||
|
||||
// The inner connection.
|
||||
con io.ReadWriteCloser
|
||||
}
|
||||
|
||||
// Read reads data from the io.ReadWriteCloser.
|
||||
//
|
||||
// If the protocol hasn't yet been negotiated, this method triggers the write
|
||||
// half of the handshake and then waits for the read half to complete.
|
||||
//
|
||||
// It returns an error if the read half of the handshake fails.
|
||||
func (l *lazyClientConn[T]) Read(b []byte) (int, error) {
|
||||
l.rhandshakeOnce.Do(func() {
|
||||
go l.whandshakeOnce.Do(l.doWriteHandshake)
|
||||
l.doReadHandshake()
|
||||
})
|
||||
if l.rerr != nil {
|
||||
return 0, l.rerr
|
||||
}
|
||||
if len(b) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
return l.con.Read(b)
|
||||
}
|
||||
|
||||
func (l *lazyClientConn[T]) doReadHandshake() {
|
||||
for _, proto := range l.protos {
|
||||
// read protocol
|
||||
tok, err := ReadNextToken[T](l.con)
|
||||
if err != nil {
|
||||
l.rerr = err
|
||||
return
|
||||
}
|
||||
|
||||
if tok == "na" {
|
||||
l.rerr = ErrNotSupported[T]{[]T{proto}}
|
||||
return
|
||||
}
|
||||
if tok != proto {
|
||||
l.rerr = fmt.Errorf("protocol mismatch in lazy handshake ( %s != %s )", tok, proto)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *lazyClientConn[T]) doWriteHandshake() {
|
||||
l.doWriteHandshakeWithData(nil)
|
||||
}
|
||||
|
||||
// Perform the write handshake but *also* write some extra data.
|
||||
func (l *lazyClientConn[T]) doWriteHandshakeWithData(extra []byte) int {
|
||||
buf := getWriter(l.con)
|
||||
defer putWriter(buf)
|
||||
|
||||
for _, proto := range l.protos {
|
||||
l.werr = delimWrite(buf, []byte(proto))
|
||||
if l.werr != nil {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
n := 0
|
||||
if len(extra) > 0 {
|
||||
n, l.werr = buf.Write(extra)
|
||||
if l.werr != nil {
|
||||
return n
|
||||
}
|
||||
}
|
||||
l.werr = buf.Flush()
|
||||
return n
|
||||
}
|
||||
|
||||
// Write writes the given buffer to the underlying connection.
|
||||
//
|
||||
// If the protocol has not yet been negotiated, write waits for the write half
|
||||
// of the handshake to complete triggers (but does not wait for) the read half.
|
||||
//
|
||||
// Write *also* ignores errors from the read half of the handshake (in case the
|
||||
// stream is actually write only).
|
||||
func (l *lazyClientConn[T]) Write(b []byte) (int, error) {
|
||||
n := 0
|
||||
l.whandshakeOnce.Do(func() {
|
||||
go l.rhandshakeOnce.Do(l.doReadHandshake)
|
||||
n = l.doWriteHandshakeWithData(b)
|
||||
})
|
||||
if l.werr != nil || n > 0 {
|
||||
return n, l.werr
|
||||
}
|
||||
return l.con.Write(b)
|
||||
}
|
||||
|
||||
// Close closes the underlying io.ReadWriteCloser
|
||||
//
|
||||
// This does not flush anything.
|
||||
func (l *lazyClientConn[T]) Close() error {
|
||||
// As the client, we flush the handshake on close to cover an
|
||||
// interesting edge-case where the server only speaks a single protocol
|
||||
// and responds eagerly with that protocol before waiting for out
|
||||
// handshake.
|
||||
//
|
||||
// Again, we must not read the error because the other end may have
|
||||
// closed the stream for reading. I mean, we're the initiator so that's
|
||||
// strange... but it's still allowed
|
||||
_ = l.Flush()
|
||||
return l.con.Close()
|
||||
}
|
||||
|
||||
// Flush sends the handshake.
|
||||
func (l *lazyClientConn[T]) Flush() error {
|
||||
l.whandshakeOnce.Do(func() {
|
||||
go l.rhandshakeOnce.Do(l.doReadHandshake)
|
||||
l.doWriteHandshake()
|
||||
})
|
||||
return l.werr
|
||||
}
|
||||
344
vendor/github.com/multiformats/go-multistream/multistream.go
generated
vendored
Normal file
344
vendor/github.com/multiformats/go-multistream/multistream.go
generated
vendored
Normal file
@@ -0,0 +1,344 @@
|
||||
// Package multistream implements a simple stream router for the
|
||||
// multistream-select protocoli. The protocol is defined at
|
||||
// https://github.com/multiformats/multistream-select
|
||||
package multistream
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"runtime/debug"
|
||||
"sync"
|
||||
|
||||
"github.com/multiformats/go-varint"
|
||||
)
|
||||
|
||||
// ErrTooLarge is an error to signal that an incoming message was too large
|
||||
var ErrTooLarge = errors.New("incoming message was too large")
|
||||
|
||||
// ProtocolID identifies the multistream protocol itself and makes sure
|
||||
// the multistream muxers on both sides of a channel can work with each other.
|
||||
const ProtocolID = "/multistream/1.0.0"
|
||||
|
||||
var writerPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
return bufio.NewWriter(nil)
|
||||
},
|
||||
}
|
||||
|
||||
// StringLike is an interface that supports all types with underlying type
|
||||
// string
|
||||
type StringLike interface {
|
||||
~string
|
||||
}
|
||||
|
||||
// HandlerFunc is a user-provided function used by the MultistreamMuxer to
|
||||
// handle a protocol/stream.
|
||||
type HandlerFunc[T StringLike] func(protocol T, rwc io.ReadWriteCloser) error
|
||||
|
||||
// Handler is a wrapper to HandlerFunc which attaches a name (protocol) and a
|
||||
// match function which can optionally be used to select a handler by other
|
||||
// means than the name.
|
||||
type Handler[T StringLike] struct {
|
||||
MatchFunc func(T) bool
|
||||
Handle HandlerFunc[T]
|
||||
AddName T
|
||||
}
|
||||
|
||||
// MultistreamMuxer is a muxer for multistream. Depending on the stream
|
||||
// protocol tag it will select the right handler and hand the stream off to it.
|
||||
type MultistreamMuxer[T StringLike] struct {
|
||||
handlerlock sync.RWMutex
|
||||
handlers []Handler[T]
|
||||
}
|
||||
|
||||
// NewMultistreamMuxer creates a muxer.
|
||||
func NewMultistreamMuxer[T StringLike]() *MultistreamMuxer[T] {
|
||||
return new(MultistreamMuxer[T])
|
||||
}
|
||||
|
||||
// LazyConn is the connection type returned by the lazy negotiation functions.
|
||||
type LazyConn interface {
|
||||
io.ReadWriteCloser
|
||||
// Flush flushes the lazy negotiation, if any.
|
||||
Flush() error
|
||||
}
|
||||
|
||||
func writeUvarint(w io.Writer, i uint64) error {
|
||||
varintbuf := make([]byte, 16)
|
||||
n := varint.PutUvarint(varintbuf, i)
|
||||
_, err := w.Write(varintbuf[:n])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func delimWriteBuffered(w io.Writer, mes []byte) error {
|
||||
bw := getWriter(w)
|
||||
defer putWriter(bw)
|
||||
|
||||
err := delimWrite(bw, mes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return bw.Flush()
|
||||
}
|
||||
|
||||
func delitmWriteAll(w io.Writer, messages ...[]byte) error {
|
||||
for _, mes := range messages {
|
||||
if err := delimWrite(w, mes); err != nil {
|
||||
return fmt.Errorf("failed to write messages %s, err: %v ", string(mes), err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func delimWrite(w io.Writer, mes []byte) error {
|
||||
err := writeUvarint(w, uint64(len(mes)+1))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = w.Write(mes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = w.Write([]byte{'\n'})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func fulltextMatch[T StringLike](s T) func(T) bool {
|
||||
return func(a T) bool {
|
||||
return a == s
|
||||
}
|
||||
}
|
||||
|
||||
// AddHandler attaches a new protocol handler to the muxer.
|
||||
func (msm *MultistreamMuxer[T]) AddHandler(protocol T, handler HandlerFunc[T]) {
|
||||
msm.AddHandlerWithFunc(protocol, fulltextMatch(protocol), handler)
|
||||
}
|
||||
|
||||
// AddHandlerWithFunc attaches a new protocol handler to the muxer with a match.
|
||||
// If the match function returns true for a given protocol tag, the protocol
|
||||
// will be selected even if the handler name and protocol tags are different.
|
||||
func (msm *MultistreamMuxer[T]) AddHandlerWithFunc(protocol T, match func(T) bool, handler HandlerFunc[T]) {
|
||||
msm.handlerlock.Lock()
|
||||
defer msm.handlerlock.Unlock()
|
||||
|
||||
msm.removeHandler(protocol)
|
||||
msm.handlers = append(msm.handlers, Handler[T]{
|
||||
MatchFunc: match,
|
||||
Handle: handler,
|
||||
AddName: protocol,
|
||||
})
|
||||
}
|
||||
|
||||
// RemoveHandler removes the handler with the given name from the muxer.
|
||||
func (msm *MultistreamMuxer[T]) RemoveHandler(protocol T) {
|
||||
msm.handlerlock.Lock()
|
||||
defer msm.handlerlock.Unlock()
|
||||
|
||||
msm.removeHandler(protocol)
|
||||
}
|
||||
|
||||
func (msm *MultistreamMuxer[T]) removeHandler(protocol T) {
|
||||
for i, h := range msm.handlers {
|
||||
if h.AddName == protocol {
|
||||
msm.handlers = append(msm.handlers[:i], msm.handlers[i+1:]...)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Protocols returns the list of handler-names added to this this muxer.
|
||||
func (msm *MultistreamMuxer[T]) Protocols() []T {
|
||||
msm.handlerlock.RLock()
|
||||
defer msm.handlerlock.RUnlock()
|
||||
|
||||
var out []T
|
||||
for _, h := range msm.handlers {
|
||||
out = append(out, h.AddName)
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// ErrIncorrectVersion is an error reported when the muxer protocol negotiation
|
||||
// fails because of a ProtocolID mismatch.
|
||||
var ErrIncorrectVersion = errors.New("client connected with incorrect version")
|
||||
|
||||
func (msm *MultistreamMuxer[T]) findHandler(proto T) *Handler[T] {
|
||||
msm.handlerlock.RLock()
|
||||
defer msm.handlerlock.RUnlock()
|
||||
|
||||
for _, h := range msm.handlers {
|
||||
if h.MatchFunc(proto) {
|
||||
return &h
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Negotiate performs protocol selection and returns the protocol name and
|
||||
// the matching handler function for it (or an error).
|
||||
func (msm *MultistreamMuxer[T]) Negotiate(rwc io.ReadWriteCloser) (proto T, handler HandlerFunc[T], 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 multistream negotiation: %s", rerr)
|
||||
}
|
||||
}()
|
||||
|
||||
// Send the multistream protocol ID
|
||||
// Ignore the error here. We want the handshake to finish, even if the
|
||||
// other side has closed this rwc for writing. They may have sent us a
|
||||
// message and closed. Future writers will get an error anyways.
|
||||
_ = delimWriteBuffered(rwc, []byte(ProtocolID))
|
||||
line, err := ReadNextToken[T](rwc)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
if line != ProtocolID {
|
||||
rwc.Close()
|
||||
return "", nil, ErrIncorrectVersion
|
||||
}
|
||||
|
||||
loop:
|
||||
for {
|
||||
// Now read and respond to commands until they send a valid protocol id
|
||||
tok, err := ReadNextToken[T](rwc)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
h := msm.findHandler(tok)
|
||||
if h == nil {
|
||||
if err := delimWriteBuffered(rwc, []byte("na")); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
continue loop
|
||||
}
|
||||
|
||||
// Ignore the error here. We want the handshake to finish, even if the
|
||||
// other side has closed this rwc for writing. They may have sent us a
|
||||
// message and closed. Future writers will get an error anyways.
|
||||
_ = delimWriteBuffered(rwc, []byte(tok))
|
||||
|
||||
// hand off processing to the sub-protocol handler
|
||||
return tok, h.Handle, nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Handle performs protocol negotiation on a ReadWriteCloser
|
||||
// (i.e. a connection). It will find a matching handler for the
|
||||
// incoming protocol and pass the ReadWriteCloser to it.
|
||||
func (msm *MultistreamMuxer[T]) Handle(rwc io.ReadWriteCloser) error {
|
||||
p, h, err := msm.Negotiate(rwc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return h(p, rwc)
|
||||
}
|
||||
|
||||
// ReadNextToken extracts a token from a Reader. It is used during
|
||||
// protocol negotiation and returns a string.
|
||||
func ReadNextToken[T StringLike](r io.Reader) (T, error) {
|
||||
tok, err := ReadNextTokenBytes(r)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return T(tok), nil
|
||||
}
|
||||
|
||||
// ReadNextTokenBytes extracts a token from a Reader. It is used
|
||||
// during protocol negotiation and returns a byte slice.
|
||||
func ReadNextTokenBytes(r io.Reader) ([]byte, error) {
|
||||
data, err := lpReadBuf(r)
|
||||
switch err {
|
||||
case nil:
|
||||
return data, nil
|
||||
case ErrTooLarge:
|
||||
return nil, ErrTooLarge
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
func lpReadBuf(r io.Reader) ([]byte, error) {
|
||||
br, ok := r.(io.ByteReader)
|
||||
if !ok {
|
||||
br = &byteReader{r}
|
||||
}
|
||||
|
||||
length, err := varint.ReadUvarint(br)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if length > 1024 {
|
||||
return nil, ErrTooLarge
|
||||
}
|
||||
|
||||
buf := make([]byte, length)
|
||||
_, err = io.ReadFull(r, buf)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
err = io.ErrUnexpectedEOF
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(buf) == 0 || buf[length-1] != '\n' {
|
||||
return nil, errors.New("message did not have trailing newline")
|
||||
}
|
||||
|
||||
// slice off the trailing newline
|
||||
buf = buf[:length-1]
|
||||
|
||||
return buf, nil
|
||||
|
||||
}
|
||||
|
||||
// byteReader implements the ByteReader interface that ReadUVarint requires
|
||||
type byteReader struct {
|
||||
io.Reader
|
||||
}
|
||||
|
||||
func (br *byteReader) ReadByte() (byte, error) {
|
||||
var b [1]byte
|
||||
n, err := br.Read(b[:])
|
||||
if n == 1 {
|
||||
return b[0], nil
|
||||
}
|
||||
if err == nil {
|
||||
if n != 0 {
|
||||
panic("read more bytes than buffer size")
|
||||
}
|
||||
err = io.ErrNoProgress
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
|
||||
func getWriter(w io.Writer) *bufio.Writer {
|
||||
bw := writerPool.Get().(*bufio.Writer)
|
||||
bw.Reset(w)
|
||||
return bw
|
||||
}
|
||||
|
||||
func putWriter(bw *bufio.Writer) {
|
||||
bw.Reset(nil)
|
||||
writerPool.Put(bw)
|
||||
}
|
||||
3
vendor/github.com/multiformats/go-multistream/version.json
generated
vendored
Normal file
3
vendor/github.com/multiformats/go-multistream/version.json
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"version": "v0.4.1"
|
||||
}
|
||||
Reference in New Issue
Block a user