Files
mochi/bittorrent/peer.go
Lawrence, Rendall 665899017e (untested) move unsafe string conversion to pkg
* fix invalid UDP f/e start when no workers provided
* add bench for entire server
2023-03-22 17:51:13 +03:00

139 lines
3.7 KiB
Go

// Package bittorrent implements all of the abstractions used to decouple the
// protocol of a BitTorrent tracker from the logic of handling Announces and
// Scrapes.
package bittorrent
import (
"crypto/sha1"
"crypto/sha256"
"encoding/hex"
"fmt"
"net/netip"
"github.com/rs/zerolog"
"github.com/sot-tech/mochi/pkg/str2bytes"
)
// PeerIDLen is length of peer id field in bytes
const PeerIDLen = 20
// PeerID represents a peer ID.
type PeerID [PeerIDLen]byte
// ErrInvalidPeerIDSize holds error about invalid PeerID size
var ErrInvalidPeerIDSize = fmt.Errorf("peer ID must be %d bytes", PeerIDLen)
var zeroPeerID PeerID
// NewPeerID creates a PeerID from a byte slice.
//
// It panics if b is not 20 bytes long.
func NewPeerID(b []byte) (PeerID, error) {
if len(b) != PeerIDLen {
return zeroPeerID, ErrInvalidPeerIDSize
}
return PeerID(b), nil
}
// Bytes returns slice of bytes represents this PeerID
func (p PeerID) Bytes() []byte {
return p[:]
}
// String implements fmt.Stringer, returning the base16 encoded PeerID.
func (p PeerID) String() string {
return hex.EncodeToString(p.Bytes())
}
// RawString returns a 20-byte string of the raw bytes of the ID.
func (p PeerID) RawString() string {
return str2bytes.BytesToString(p.Bytes())
}
// InfoHash represents an infohash.
type InfoHash string
const (
// InfoHashV1Len is the same as sha1.Size
InfoHashV1Len = sha1.Size
// InfoHashV2Len ... sha256.Size
InfoHashV2Len = sha256.Size
)
// ErrInvalidHashSize holds error about invalid InfoHash size
var ErrInvalidHashSize = fmt.Errorf("info hash must be either %d (for torrent V1) or %d (V2) bytes or same sizes x2 (if HEX encoded)", InfoHashV1Len, InfoHashV2Len)
// NewInfoHash creates an InfoHash from raw/hex byte slice.
func NewInfoHash(data []byte) (InfoHash, error) {
var ih InfoHash
switch l := len(data); l {
case InfoHashV1Len, InfoHashV2Len:
ih = InfoHash(data)
case InfoHashV1Len * 2, InfoHashV2Len * 2:
bb := make([]byte, l/2)
if _, err := hex.Decode(bb, data); err != nil {
return "", err
}
ih = InfoHash(str2bytes.BytesToString(bb))
default:
return "", ErrInvalidHashSize
}
return ih, nil
}
// NewInfoHashString creates an InfoHash from raw/hex string.
func NewInfoHashString(data string) (InfoHash, error) {
return NewInfoHash(str2bytes.StringToBytes(data))
}
// TruncateV1 returns truncated to 20-bytes length array of the corresponding InfoHash.
// If InfoHash is V2 (32 bytes), it will be truncated to 20 bytes
// according to BEP52.
func (i InfoHash) TruncateV1() InfoHash {
if len(i) == InfoHashV2Len {
return i[:InfoHashV1Len]
}
return i
}
// Bytes returns slice of bytes represents this InfoHash
func (i InfoHash) Bytes() []byte {
return str2bytes.StringToBytes(string(i))
}
// String implements fmt.Stringer, returning the base16 encoded InfoHash.
func (i InfoHash) String() string {
return hex.EncodeToString(i.Bytes())
}
// RawString returns a string of the raw bytes of the InfoHash.
func (i InfoHash) RawString() string {
return string(i)
}
// Peer represents the connection details of a peer that is returned in an
// announce response.
type Peer struct {
ID PeerID
netip.AddrPort
}
// Addr returns unmapped peer's IP address
func (p Peer) Addr() netip.Addr {
return p.AddrPort.Addr().Unmap()
}
// MarshalZerologObject writes fields into zerolog event
func (p Peer) MarshalZerologObject(e *zerolog.Event) {
e.Stringer("id", p.ID).
Stringer("address", p.Addr()).
Uint16("port", p.Port())
}
// ClientError represents an error that should be exposed to the client over
// the BitTorrent protocol implementation.
type ClientError string
// Error implements the error interface for ClientError.
func (c ClientError) Error() string { return string(c) }