package client
import (
"context"
"fmt"
"time"
"github.com/libp2p/go-libp2p/core/host"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/peerstore"
"github.com/libp2p/go-libp2p/core/record"
pbv2 "github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/pb"
"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/proto"
"github.com/libp2p/go-libp2p/p2p/protocol/circuitv2/util"
ma "github.com/multiformats/go-multiaddr"
)
var ReserveTimeout = time .Minute
type Reservation struct {
Expiration time .Time
Addrs []ma .Multiaddr
LimitDuration time .Duration
LimitData uint64
Voucher *proto .ReservationVoucher
}
type ReservationError struct {
Status pbv2 .Status
Reason string
err error
}
func (re ReservationError ) Error () string {
return fmt .Sprintf ("reservation error: status: %s reason: %s err: %s" , pbv2 .Status_name [int32 (re .Status )], re .Reason , re .err )
}
func (re ReservationError ) Unwrap () error {
return re .err
}
func Reserve (ctx context .Context , h host .Host , ai peer .AddrInfo ) (*Reservation , error ) {
if len (ai .Addrs ) > 0 {
h .Peerstore ().AddAddrs (ai .ID , ai .Addrs , peerstore .TempAddrTTL )
}
s , err := h .NewStream (ctx , ai .ID , proto .ProtoIDv2Hop )
if err != nil {
return nil , ReservationError {Status : pbv2 .Status_CONNECTION_FAILED , Reason : "failed to open stream" , err : err }
}
defer s .Close ()
rd := util .NewDelimitedReader (s , maxMessageSize )
wr := util .NewDelimitedWriter (s )
defer rd .Close ()
var msg pbv2 .HopMessage
msg .Type = pbv2 .HopMessage_RESERVE .Enum ()
s .SetDeadline (time .Now ().Add (ReserveTimeout ))
if err := wr .WriteMsg (&msg ); err != nil {
s .Reset ()
return nil , ReservationError {Status : pbv2 .Status_CONNECTION_FAILED , Reason : "error writing reservation message" , err : err }
}
msg .Reset ()
if err := rd .ReadMsg (&msg ); err != nil {
s .Reset ()
return nil , ReservationError {Status : pbv2 .Status_CONNECTION_FAILED , Reason : "error reading reservation response message: %w" , err : err }
}
if msg .GetType () != pbv2 .HopMessage_STATUS {
return nil , ReservationError {Status : pbv2 .Status_MALFORMED_MESSAGE , Reason : fmt .Sprintf ("unexpected relay response: not a status message (%d)" , msg .GetType ())}
}
if status := msg .GetStatus (); status != pbv2 .Status_OK {
return nil , ReservationError {Status : msg .GetStatus (), Reason : "reservation failed" }
}
rsvp := msg .GetReservation ()
if rsvp == nil {
return nil , ReservationError {Status : pbv2 .Status_MALFORMED_MESSAGE , Reason : "missing reservation info" }
}
result := &Reservation {}
result .Expiration = time .Unix (int64 (rsvp .GetExpire ()), 0 )
if result .Expiration .Before (time .Now ()) {
return nil , ReservationError {
Status : pbv2 .Status_MALFORMED_MESSAGE ,
Reason : fmt .Sprintf ("received reservation with expiration date in the past: %s" , result .Expiration ),
}
}
addrs := rsvp .GetAddrs ()
result .Addrs = make ([]ma .Multiaddr , 0 , len (addrs ))
for _ , ab := range addrs {
a , err := ma .NewMultiaddrBytes (ab )
if err != nil {
log .Warnf ("ignoring unparsable relay address: %s" , err )
continue
}
result .Addrs = append (result .Addrs , a )
}
voucherBytes := rsvp .GetVoucher ()
if voucherBytes != nil {
env , rec , err := record .ConsumeEnvelope (voucherBytes , proto .RecordDomain )
if err != nil {
return nil , ReservationError {
Status : pbv2 .Status_MALFORMED_MESSAGE ,
Reason : fmt .Sprintf ("error consuming voucher envelope: %s" , err ),
err : err ,
}
}
voucher , ok := rec .(*proto .ReservationVoucher )
if !ok {
return nil , ReservationError {
Status : pbv2 .Status_MALFORMED_MESSAGE ,
Reason : fmt .Sprintf ("unexpected voucher record type: %+T" , rec ),
}
}
signerPeerID , err := peer .IDFromPublicKey (env .PublicKey )
if err != nil {
return nil , ReservationError {
Status : pbv2 .Status_MALFORMED_MESSAGE ,
Reason : fmt .Sprintf ("invalid voucher signing public key: %s" , err ),
err : err ,
}
}
if signerPeerID != voucher .Relay {
return nil , ReservationError {
Status : pbv2 .Status_MALFORMED_MESSAGE ,
Reason : fmt .Sprintf ("invalid voucher relay id: expected %s, got %s" , signerPeerID , voucher .Relay ),
}
}
if h .ID () != voucher .Peer {
return nil , ReservationError {
Status : pbv2 .Status_MALFORMED_MESSAGE ,
Reason : fmt .Sprintf ("invalid voucher peer id: expected %s, got %s" , h .ID (), voucher .Peer ),
}
}
result .Voucher = voucher
}
limit := msg .GetLimit ()
if limit != nil {
result .LimitDuration = time .Duration (limit .GetDuration ()) * time .Second
result .LimitData = limit .GetData ()
}
return result , nil
}
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 .