package multistream
import (
"bytes"
"errors"
"fmt"
"io"
"os"
"runtime/debug"
)
type ErrNotSupported [T StringLike ] struct {
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
}
type ErrUnrecognizedResponse [T StringLike ] struct {
Actual T
Expected T
}
func (e ErrUnrecognizedResponse [T ]) Error () string {
return fmt .Sprintf ("unrecognized response. expected: %s (or na). got: %s" , e .Expected , e .Actual )
}
var ErrNoProtocols = errors .New ("no protocols specified" )
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
}()
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
}
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
}
switch err := SelectProtoOrFail (protos [0 ], rwc ); err .(type ) {
case nil :
return protos [0 ], nil
case ErrNotSupported [T ]:
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 ErrUnrecognizedResponse [T ]{
Actual : tok ,
Expected : proto ,
}
}
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .