record.go 6.96 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
package peer

import (
	"fmt"
	"time"

	pb "github.com/libp2p/go-libp2p-core/peer/pb"
	"github.com/libp2p/go-libp2p-core/record"

	ma "github.com/multiformats/go-multiaddr"

	"github.com/gogo/protobuf/proto"
)

var _ record.Record = (*PeerRecord)(nil)

func init() {
	record.RegisterType(&PeerRecord{})
}

Vasco Santos's avatar
Vasco Santos committed
21
// PeerRecordEnvelopeDomain is the domain string used for peer records contained in a Envelope.
22 23
const PeerRecordEnvelopeDomain = "libp2p-peer-record"

Vasco Santos's avatar
Vasco Santos committed
24
// PeerRecordEnvelopePayloadType is the type hint used to identify peer records in a Envelope.
25
// Defined in https://github.com/multiformats/multicodec/blob/master/table.csv
Vasco Santos's avatar
Vasco Santos committed
26
// with name "libp2p-peer-record".
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
var PeerRecordEnvelopePayloadType = []byte{0x03, 0x01}

// PeerRecord contains information that is broadly useful to share with other peers,
// either through a direct exchange (as in the libp2p identify protocol), or through
// a Peer Routing provider, such as a DHT.
//
// Currently, a PeerRecord contains the public listen addresses for a peer, but this
// is expected to expand to include other information in the future.
//
// PeerRecords are ordered in time by their Seq field. Newer PeerRecords must have
// greater Seq values than older records. The NewPeerRecord function will create
// a PeerRecord with a timestamp-based Seq value. The other PeerRecord fields should
// be set by the caller:
//
//    rec := peer.NewPeerRecord()
//    rec.PeerID = aPeerID
//    rec.Addrs = someAddrs
//
// Alternatively, you can construct a PeerRecord struct directly and use the TimestampSeq
// helper to set the Seq field:
//
//    rec := peer.PeerRecord{PeerID: aPeerID, Addrs: someAddrs, Seq: peer.TimestampSeq()}
//
// Failing to set the Seq field will not result in an error, however, a PeerRecord with a
// Seq value of zero may be ignored or rejected by other peers.
//
// PeerRecords are intended to be shared with other peers inside a signed
// routing.Envelope, and PeerRecord implements the routing.Record interface
// to facilitate this.
//
// To share a PeerRecord, first call Sign to wrap the record in a Envelope
// and sign it with the local peer's private key:
//
//     rec := &PeerRecord{PeerID: myPeerId, Addrs: myAddrs}
//     envelope, err := rec.Sign(myPrivateKey)
//
// The resulting record.Envelope can be marshalled to a []byte and shared
// publicly. As a convenience, the MarshalSigned method will produce the
// Envelope and marshal it to a []byte in one go:
//
//     rec := &PeerRecord{PeerID: myPeerId, Addrs: myAddrs}
//     recordBytes, err := rec.MarshalSigned(myPrivateKey)
//
// To validate and unmarshal a signed PeerRecord from a remote peer,
// "consume" the containing envelope, which will return both the
// routing.Envelope and the inner Record. The Record must be cast to
// a PeerRecord pointer before use:
//
//     envelope, untypedRecord, err := ConsumeEnvelope(envelopeBytes, PeerRecordEnvelopeDomain)
//     if err != nil {
//       handleError(err)
//       return
//     }
//     peerRec := untypedRecord.(*PeerRecord)
//
type PeerRecord struct {
	// PeerID is the ID of the peer this record pertains to.
	PeerID ID

	// Addrs contains the public addresses of the peer this record pertains to.
	Addrs []ma.Multiaddr

	// Seq is a monotonically-increasing sequence counter that's used to order
	// PeerRecords in time. The interval between Seq values is unspecified,
	// but newer PeerRecords MUST have a greater Seq value than older records
	// for the same peer.
	Seq uint64
}

// NewPeerRecord returns a PeerRecord with a timestamp-based sequence number.
// The returned record is otherwise empty and should be populated by the caller.
func NewPeerRecord() *PeerRecord {
	return &PeerRecord{Seq: TimestampSeq()}
}

// PeerRecordFromAddrInfo creates a PeerRecord from an AddrInfo struct.
// The returned record will have a timestamp-based sequence number.
func PeerRecordFromAddrInfo(info AddrInfo) *PeerRecord {
	rec := NewPeerRecord()
	rec.PeerID = info.ID
	rec.Addrs = info.Addrs
	return rec
}

111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
// PeerRecordFromProtobuf creates a PeerRecord from a protobuf PeerRecord
// struct.
func PeerRecordFromProtobuf(msg *pb.PeerRecord) (*PeerRecord, error) {
	record := &PeerRecord{}

	var id ID
	if err := id.UnmarshalBinary(msg.PeerId); err != nil {
		return nil, err
	}

	record.PeerID = id
	record.Addrs = addrsFromProtobuf(msg.Addresses)
	record.Seq = msg.Seq

	return record, nil
}

128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
// TimestampSeq is a helper to generate a timestamp-based sequence number for a PeerRecord.
func TimestampSeq() uint64 {
	return uint64(time.Now().UnixNano())
}

// Domain is used when signing and validating PeerRecords contained in Envelopes.
// It is constant for all PeerRecord instances.
func (r *PeerRecord) Domain() string {
	return PeerRecordEnvelopeDomain
}

// Codec is a binary identifier for the PeerRecord type. It is constant for all PeerRecord instances.
func (r *PeerRecord) Codec() []byte {
	return PeerRecordEnvelopePayloadType
}

// UnmarshalRecord parses a PeerRecord from a byte slice.
// This method is called automatically when consuming a record.Envelope
// whose PayloadType indicates that it contains a PeerRecord.
// It is generally not necessary or recommended to call this method directly.
func (r *PeerRecord) UnmarshalRecord(bytes []byte) error {
	if r == nil {
		return fmt.Errorf("cannot unmarshal PeerRecord to nil receiver")
	}

	var msg pb.PeerRecord
	err := proto.Unmarshal(bytes, &msg)
	if err != nil {
		return err
	}
158 159

	rPtr, err := PeerRecordFromProtobuf(&msg)
160 161 162
	if err != nil {
		return err
	}
163 164
	*r = *rPtr

165 166 167 168 169 170 171
	return nil
}

// MarshalRecord serializes a PeerRecord to a byte slice.
// This method is called automatically when constructing a routing.Envelope
// using Seal or PeerRecord.Sign.
func (r *PeerRecord) MarshalRecord() ([]byte, error) {
172
	msg, err := r.ToProtobuf()
173 174 175
	if err != nil {
		return nil, err
	}
176
	return proto.Marshal(msg)
177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192
}

// Equal returns true if the other PeerRecord is identical to this one.
func (r *PeerRecord) Equal(other *PeerRecord) bool {
	if other == nil {
		return r == nil
	}
	if r.PeerID != other.PeerID {
		return false
	}
	if r.Seq != other.Seq {
		return false
	}
	if len(r.Addrs) != len(other.Addrs) {
		return false
	}
Marten Seemann's avatar
Marten Seemann committed
193
	for i := range r.Addrs {
194 195 196 197 198 199 200
		if !r.Addrs[i].Equal(other.Addrs[i]) {
			return false
		}
	}
	return true
}

201 202 203 204 205 206 207 208 209 210 211 212 213
// ToProtobuf returns the equivalent Protocol Buffer struct object of a PeerRecord.
func (r *PeerRecord) ToProtobuf() (*pb.PeerRecord, error) {
	idBytes, err := r.PeerID.MarshalBinary()
	if err != nil {
		return nil, err
	}
	return &pb.PeerRecord{
		PeerId:    idBytes,
		Addresses: addrsToProtobuf(r.Addrs),
		Seq:       r.Seq,
	}, nil
}

214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232
func addrsFromProtobuf(addrs []*pb.PeerRecord_AddressInfo) []ma.Multiaddr {
	var out []ma.Multiaddr
	for _, addr := range addrs {
		a, err := ma.NewMultiaddrBytes(addr.Multiaddr)
		if err != nil {
			continue
		}
		out = append(out, a)
	}
	return out
}

func addrsToProtobuf(addrs []ma.Multiaddr) []*pb.PeerRecord_AddressInfo {
	var out []*pb.PeerRecord_AddressInfo
	for _, addr := range addrs {
		out = append(out, &pb.PeerRecord_AddressInfo{Multiaddr: addr.Bytes()})
	}
	return out
}