feat: Waku v2 bridge

Issue #12610
This commit is contained in:
Michal Iskierko
2023-11-12 13:29:38 +01:00
parent 56e7bd01ca
commit 6d31343205
6716 changed files with 1982502 additions and 5891 deletions

View File

@@ -0,0 +1,22 @@
package peer_protocol
import (
"net"
"github.com/anacrolix/torrent/bencode"
)
// Marshals to the smallest compact byte representation.
type CompactIp net.IP
var _ bencode.Marshaler = CompactIp{}
func (me CompactIp) MarshalBencode() ([]byte, error) {
return bencode.Marshal(func() []byte {
if ip4 := net.IP(me).To4(); ip4 != nil {
return ip4
} else {
return me
}
}())
}

View File

@@ -0,0 +1,126 @@
package peer_protocol
import (
"bufio"
"encoding/binary"
"fmt"
"io"
"sync"
"github.com/pkg/errors"
)
type Decoder struct {
R *bufio.Reader
Pool *sync.Pool
MaxLength Integer // TODO: Should this include the length header or not?
}
// io.EOF is returned if the source terminates cleanly on a message boundary.
func (d *Decoder) Decode(msg *Message) (err error) {
var length Integer
err = length.Read(d.R)
if err != nil {
return fmt.Errorf("reading message length: %w", err)
}
if length > d.MaxLength {
return errors.New("message too long")
}
if length == 0 {
msg.Keepalive = true
return
}
r := d.R
readByte := func() (byte, error) {
length--
return d.R.ReadByte()
}
c, err := readByte()
if err != nil {
return
}
msg.Type = MessageType(c)
switch msg.Type {
case Choke, Unchoke, Interested, NotInterested, HaveAll, HaveNone:
case Have, AllowedFast, Suggest:
length -= 4
err = msg.Index.Read(r)
case Request, Cancel, Reject:
for _, data := range []*Integer{&msg.Index, &msg.Begin, &msg.Length} {
err = data.Read(r)
if err != nil {
break
}
}
length -= 12
case Bitfield:
b := make([]byte, length)
_, err = io.ReadFull(r, b)
length = 0
msg.Bitfield = unmarshalBitfield(b)
case Piece:
for _, pi := range []*Integer{&msg.Index, &msg.Begin} {
err := pi.Read(r)
if err != nil {
return err
}
}
length -= 8
dataLen := int64(length)
msg.Piece = *d.Pool.Get().(*[]byte)
if int64(cap(msg.Piece)) < dataLen {
return errors.New("piece data longer than expected")
}
msg.Piece = msg.Piece[:dataLen]
_, err := io.ReadFull(r, msg.Piece)
if err != nil {
return fmt.Errorf("reading piece data: %w", err)
}
length = 0
case Extended:
var b byte
b, err = readByte()
if err != nil {
break
}
msg.ExtendedID = ExtensionNumber(b)
msg.ExtendedPayload = make([]byte, length)
_, err = io.ReadFull(r, msg.ExtendedPayload)
if err == io.EOF {
err = io.ErrUnexpectedEOF
}
length = 0
case Port:
err = binary.Read(r, binary.BigEndian, &msg.Port)
length -= 2
default:
err = fmt.Errorf("unknown message type %#v", c)
}
if err == nil && length != 0 {
err = fmt.Errorf("%v unused bytes in message type %v", length, msg.Type)
}
return
}
func readByte(r io.Reader) (b byte, err error) {
var arr [1]byte
n, err := r.Read(arr[:])
b = arr[0]
if n == 1 {
err = nil
return
}
if err == nil {
panic(err)
}
return
}
func unmarshalBitfield(b []byte) (bf []bool) {
for _, c := range b {
for i := 7; i >= 0; i-- {
bf = append(bf, (c>>uint(i))&1 == 1)
}
}
return
}

View File

@@ -0,0 +1,38 @@
package peer_protocol
import (
"net"
)
// http://www.bittorrent.org/beps/bep_0010.html
type (
ExtendedHandshakeMessage struct {
M map[ExtensionName]ExtensionNumber `bencode:"m"`
V string `bencode:"v,omitempty"`
Reqq int `bencode:"reqq,omitempty"`
Encryption bool `bencode:"e,omitempty"`
// BEP 9
MetadataSize int `bencode:"metadata_size,omitempty"`
// The local client port. It would be redundant for the receiving side of
// a connection to send this.
Port int `bencode:"p,omitempty"`
YourIp CompactIp `bencode:"yourip,omitempty"`
Ipv4 CompactIp `bencode:"ipv4,omitempty"`
Ipv6 net.IP `bencode:"ipv6,omitempty"`
}
ExtensionName string
ExtensionNumber int
)
const (
// http://www.bittorrent.org/beps/bep_0011.html
ExtensionNamePex ExtensionName = "ut_pex"
ExtensionDeleteNumber ExtensionNumber = 0
)
func (me *ExtensionNumber) UnmarshalBinary(b []byte) error {
*me = ExtensionNumber(b[0])
return nil
}

View File

@@ -0,0 +1,147 @@
package peer_protocol
import (
"encoding/hex"
"errors"
"fmt"
"io"
"strconv"
"github.com/anacrolix/torrent/metainfo"
)
type ExtensionBit uint
const (
ExtensionBitDHT = 0 // http://www.bittorrent.org/beps/bep_0005.html
ExtensionBitExtended = 20 // http://www.bittorrent.org/beps/bep_0010.html
ExtensionBitFast = 2 // http://www.bittorrent.org/beps/bep_0006.html
)
func handshakeWriter(w io.Writer, bb <-chan []byte, done chan<- error) {
var err error
for b := range bb {
_, err = w.Write(b)
if err != nil {
break
}
}
done <- err
}
type (
PeerExtensionBits [8]byte
)
func (pex PeerExtensionBits) String() string {
return hex.EncodeToString(pex[:])
}
func NewPeerExtensionBytes(bits ...ExtensionBit) (ret PeerExtensionBits) {
for _, b := range bits {
ret.SetBit(b, true)
}
return
}
func (pex PeerExtensionBits) SupportsExtended() bool {
return pex.GetBit(ExtensionBitExtended)
}
func (pex PeerExtensionBits) SupportsDHT() bool {
return pex.GetBit(ExtensionBitDHT)
}
func (pex PeerExtensionBits) SupportsFast() bool {
return pex.GetBit(ExtensionBitFast)
}
func (pex *PeerExtensionBits) SetBit(bit ExtensionBit, on bool) {
if on {
pex[7-bit/8] |= 1 << (bit % 8)
} else {
pex[7-bit/8] &^= 1 << (bit % 8)
}
}
func (pex PeerExtensionBits) GetBit(bit ExtensionBit) bool {
return pex[7-bit/8]&(1<<(bit%8)) != 0
}
type HandshakeResult struct {
PeerExtensionBits
PeerID [20]byte
metainfo.Hash
}
// ih is nil if we expect the peer to declare the InfoHash, such as when the peer initiated the
// connection. Returns ok if the Handshake was successful, and err if there was an unexpected
// condition other than the peer simply abandoning the Handshake.
func Handshake(
sock io.ReadWriter, ih *metainfo.Hash, peerID [20]byte, extensions PeerExtensionBits,
) (
res HandshakeResult, err error,
) {
// Bytes to be sent to the peer. Should never block the sender.
postCh := make(chan []byte, 4)
// A single error value sent when the writer completes.
writeDone := make(chan error, 1)
// Performs writes to the socket and ensures posts don't block.
go handshakeWriter(sock, postCh, writeDone)
defer func() {
close(postCh) // Done writing.
if err != nil {
return
}
// Wait until writes complete before returning from handshake.
err = <-writeDone
if err != nil {
err = fmt.Errorf("error writing: %w", err)
}
}()
post := func(bb []byte) {
select {
case postCh <- bb:
default:
panic("mustn't block while posting")
}
}
post([]byte(Protocol))
post(extensions[:])
if ih != nil { // We already know what we want.
post(ih[:])
post(peerID[:])
}
var b [68]byte
_, err = io.ReadFull(sock, b[:68])
if err != nil {
return res, fmt.Errorf("while reading: %w", err)
}
if string(b[:20]) != Protocol {
return res, errors.New("unexpected protocol string")
}
copyExact := func(dst, src []byte) {
if dstLen, srcLen := uint64(len(dst)), uint64(len(src)); dstLen != srcLen {
panic("dst len " + strconv.FormatUint(dstLen, 10) + " != src len " + strconv.FormatUint(srcLen, 10))
}
copy(dst, src)
}
copyExact(res.PeerExtensionBits[:], b[20:28])
copyExact(res.Hash[:], b[28:48])
copyExact(res.PeerID[:], b[48:68])
// peerExtensions.Add(res.PeerExtensionBits.String(), 1)
// TODO: Maybe we can just drop peers here if we're not interested. This
// could prevent them trying to reconnect, falsely believing there was
// just a problem.
if ih == nil { // We were waiting for the peer to tell us what they wanted.
post(res.Hash[:])
post(peerID[:])
}
return
}

View File

@@ -0,0 +1,43 @@
package peer_protocol
import (
"encoding/binary"
"io"
"github.com/pkg/errors"
)
type Integer uint32
func (i *Integer) UnmarshalBinary(b []byte) error {
if len(b) != 4 {
return errors.New("expected 4 bytes")
}
*i = Integer(binary.BigEndian.Uint32(b))
return nil
}
func (i *Integer) Read(r io.Reader) error {
var b [4]byte
n, err := io.ReadFull(r, b[:])
if err == nil {
if n != 4 {
panic(n)
}
return i.UnmarshalBinary(b[:])
}
return err
}
// It's perfectly fine to cast these to an int. TODO: Or is it?
func (i Integer) Int() int {
return int(i)
}
func (i Integer) Uint64() uint64 {
return uint64(i)
}
func (i Integer) Uint32() uint32 {
return uint32(i)
}

View File

@@ -0,0 +1,30 @@
// Code generated by "stringer -type=MessageType"; DO NOT EDIT.
package peer_protocol
import "strconv"
const (
_MessageType_name_0 = "ChokeUnchokeInterestedNotInterestedHaveBitfieldRequestPieceCancelPort"
_MessageType_name_1 = "SuggestHaveAllHaveNoneRejectAllowedFast"
_MessageType_name_2 = "Extended"
)
var (
_MessageType_index_0 = [...]uint8{0, 5, 12, 22, 35, 39, 47, 54, 59, 65, 69}
_MessageType_index_1 = [...]uint8{0, 7, 14, 22, 28, 39}
)
func (i MessageType) String() string {
switch {
case i <= 9:
return _MessageType_name_0[_MessageType_index_0[i]:_MessageType_index_0[i+1]]
case 13 <= i && i <= 17:
i -= 13
return _MessageType_name_1[_MessageType_index_1[i]:_MessageType_index_1[i+1]]
case i == 20:
return _MessageType_name_2
default:
return "MessageType(" + strconv.FormatInt(int64(i), 10) + ")"
}
}

View File

@@ -0,0 +1,42 @@
package peer_protocol
import (
"github.com/anacrolix/torrent/bencode"
)
const (
// http://bittorrent.org/beps/bep_0009.html. Note that there's an
// LT_metadata, but I've never implemented it.
ExtensionNameMetadata = "ut_metadata"
)
type (
ExtendedMetadataRequestMsg struct {
Piece int `bencode:"piece"`
TotalSize int `bencode:"total_size"`
Type ExtendedMetadataRequestMsgType `bencode:"msg_type"`
}
ExtendedMetadataRequestMsgType int
)
func MetadataExtensionRequestMsg(peerMetadataExtensionId ExtensionNumber, piece int) Message {
return Message{
Type: Extended,
ExtendedID: peerMetadataExtensionId,
ExtendedPayload: bencode.MustMarshal(ExtendedMetadataRequestMsg{
Piece: piece,
Type: RequestMetadataExtensionMsgType,
}),
}
}
// Returns the expected piece size for this request message. This is needed to determine the offset
// into an extension message payload that the request metadata piece data starts.
func (me ExtendedMetadataRequestMsg) PieceSize() int {
ret := me.TotalSize - me.Piece*(1<<14)
if ret > 1<<14 {
ret = 1 << 14
}
return ret
}

View File

@@ -0,0 +1,139 @@
package peer_protocol
import (
"bufio"
"bytes"
"encoding"
"encoding/binary"
"fmt"
)
// This is a lazy union representing all the possible fields for messages. Go doesn't have ADTs, and
// I didn't choose to use type-assertions.
type Message struct {
Keepalive bool
Type MessageType
Index, Begin, Length Integer
Piece []byte
Bitfield []bool
ExtendedID ExtensionNumber
ExtendedPayload []byte
Port uint16
}
var _ interface {
encoding.BinaryUnmarshaler
encoding.BinaryMarshaler
} = (*Message)(nil)
func MakeCancelMessage(piece, offset, length Integer) Message {
return Message{
Type: Cancel,
Index: piece,
Begin: offset,
Length: length,
}
}
func (msg Message) RequestSpec() (ret RequestSpec) {
return RequestSpec{
msg.Index,
msg.Begin,
func() Integer {
if msg.Type == Piece {
return Integer(len(msg.Piece))
} else {
return msg.Length
}
}(),
}
}
func (msg Message) MustMarshalBinary() []byte {
b, err := msg.MarshalBinary()
if err != nil {
panic(err)
}
return b
}
func (msg Message) MarshalBinary() (data []byte, err error) {
var buf bytes.Buffer
if !msg.Keepalive {
err = buf.WriteByte(byte(msg.Type))
if err != nil {
return
}
switch msg.Type {
case Choke, Unchoke, Interested, NotInterested, HaveAll, HaveNone:
case Have:
err = binary.Write(&buf, binary.BigEndian, msg.Index)
case Request, Cancel, Reject:
for _, i := range []Integer{msg.Index, msg.Begin, msg.Length} {
err = binary.Write(&buf, binary.BigEndian, i)
if err != nil {
break
}
}
case Bitfield:
_, err = buf.Write(marshalBitfield(msg.Bitfield))
case Piece:
for _, i := range []Integer{msg.Index, msg.Begin} {
err = binary.Write(&buf, binary.BigEndian, i)
if err != nil {
return
}
}
n, err := buf.Write(msg.Piece)
if err != nil {
break
}
if n != len(msg.Piece) {
panic(n)
}
case Extended:
err = buf.WriteByte(byte(msg.ExtendedID))
if err != nil {
return
}
_, err = buf.Write(msg.ExtendedPayload)
case Port:
err = binary.Write(&buf, binary.BigEndian, msg.Port)
default:
err = fmt.Errorf("unknown message type: %v", msg.Type)
}
}
data = make([]byte, 4+buf.Len())
binary.BigEndian.PutUint32(data, uint32(buf.Len()))
if buf.Len() != copy(data[4:], buf.Bytes()) {
panic("bad copy")
}
return
}
func marshalBitfield(bf []bool) (b []byte) {
b = make([]byte, (len(bf)+7)/8)
for i, have := range bf {
if !have {
continue
}
c := b[i/8]
c |= 1 << uint(7-i%8)
b[i/8] = c
}
return
}
func (me *Message) UnmarshalBinary(b []byte) error {
d := Decoder{
R: bufio.NewReader(bytes.NewReader(b)),
}
err := d.Decode(me)
if err != nil {
return err
}
if d.R.Buffered() != 0 {
return fmt.Errorf("%d trailing bytes", d.R.Buffered())
}
return nil
}

View File

@@ -0,0 +1,47 @@
package peer_protocol
import (
"github.com/anacrolix/dht/v2/krpc"
"github.com/anacrolix/torrent/bencode"
)
type PexMsg struct {
Added krpc.CompactIPv4NodeAddrs `bencode:"added"`
AddedFlags []PexPeerFlags `bencode:"added.f"`
Added6 krpc.CompactIPv6NodeAddrs `bencode:"added6"`
Added6Flags []PexPeerFlags `bencode:"added6.f"`
Dropped krpc.CompactIPv4NodeAddrs `bencode:"dropped"`
Dropped6 krpc.CompactIPv6NodeAddrs `bencode:"dropped6"`
}
func (m *PexMsg) Len() int {
return len(m.Added) + len(m.Added6) + len(m.Dropped) + len(m.Dropped6)
}
func (m *PexMsg) Message(pexExtendedId ExtensionNumber) Message {
payload := bencode.MustMarshal(m)
return Message{
Type: Extended,
ExtendedID: pexExtendedId,
ExtendedPayload: payload,
}
}
func LoadPexMsg(b []byte) (ret PexMsg, err error) {
err = bencode.Unmarshal(b, &ret)
return
}
type PexPeerFlags byte
func (me PexPeerFlags) Get(f PexPeerFlags) bool {
return me&f == f
}
const (
PexPrefersEncryption PexPeerFlags = 1 << iota
PexSeedUploadOnly
PexSupportsUtp
PexHolepunchSupport
PexOutgoingConn
)

View File

@@ -0,0 +1,52 @@
package peer_protocol
const (
Protocol = "\x13BitTorrent protocol"
)
type MessageType byte
//go:generate stringer -type=MessageType
func (mt MessageType) FastExtension() bool {
return mt >= Suggest && mt <= AllowedFast
}
func (mt *MessageType) UnmarshalBinary(b []byte) error {
*mt = MessageType(b[0])
return nil
}
const (
// BEP 3
Choke MessageType = 0
Unchoke MessageType = 1
Interested MessageType = 2
NotInterested MessageType = 3
Have MessageType = 4
Bitfield MessageType = 5
Request MessageType = 6
Piece MessageType = 7
Cancel MessageType = 8
// BEP 5
Port MessageType = 9
// BEP 6 - Fast extension
Suggest MessageType = 0x0d // 13
HaveAll MessageType = 0x0e // 14
HaveNone MessageType = 0x0f // 15
Reject MessageType = 0x10 // 16
AllowedFast MessageType = 0x11 // 17
// BEP 10
Extended MessageType = 20
)
const (
HandshakeExtendedID = 0
RequestMetadataExtensionMsgType ExtendedMetadataRequestMsgType = 0
DataMetadataExtensionMsgType ExtendedMetadataRequestMsgType = 1
RejectMetadataExtensionMsgType ExtendedMetadataRequestMsgType = 2
)

View File

@@ -0,0 +1,11 @@
package peer_protocol
import "fmt"
type RequestSpec struct {
Index, Begin, Length Integer
}
func (me RequestSpec) String() string {
return fmt.Sprintf("{%d %d %d}", me.Index, me.Begin, me.Length)
}