mirror of
https://github.com/go-i2p/go-i2p-bt.git
synced 2025-07-13 11:54:35 -04:00
441 lines
12 KiB
Go
441 lines
12 KiB
Go
![]() |
// Copyright 2020 xgfone
|
||
|
//
|
||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
// you may not use this file except in compliance with the License.
|
||
|
// You may obtain a copy of the License at
|
||
|
//
|
||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||
|
//
|
||
|
// Unless required by applicable law or agreed to in writing, software
|
||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
// See the License for the specific language governing permissions and
|
||
|
// limitations under the License.
|
||
|
|
||
|
package krpc
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"fmt"
|
||
|
|
||
|
"github.com/xgfone/bt/bencode"
|
||
|
"github.com/xgfone/bt/metainfo"
|
||
|
)
|
||
|
|
||
|
// Predefine some error code.
|
||
|
const (
|
||
|
// BEP 5
|
||
|
ErrorCodeGeneric = 201
|
||
|
ErrorCodeServer = 202
|
||
|
ErrorCodeProtocol = 203
|
||
|
ErrorCodeMethodUnknown = 204
|
||
|
|
||
|
// BEP 44
|
||
|
ErrorCodeMessageValueFieldTooBig = 205
|
||
|
ErrorCodeInvalidSignature = 206
|
||
|
ErrorCodeSaltFieldTooBig = 207
|
||
|
ErrorCodeCasHashMismatched = 301
|
||
|
ErrorCodeSequenceNumberLessThanCurrent = 302
|
||
|
)
|
||
|
|
||
|
// Message represents messages that nodes in the network send to each other
|
||
|
// as specified by the KRPC protocol, which are also referred to as the KRPC
|
||
|
// messages.
|
||
|
//
|
||
|
// There are three types of messages: QUERY, RESPONSE, ERROR
|
||
|
// The message is a dictonary that is then "bencoded"
|
||
|
// (serialization & compression format adopted by the BitTorrent)
|
||
|
// and sent via the UDP connection to peers.
|
||
|
//
|
||
|
// A KRPC message is a single dictionary with two keys common to every message
|
||
|
// and additional keys depending on the type of message. Every message has a key
|
||
|
// "t" with a string value representing a transaction ID. This transaction ID
|
||
|
// is generated by the querying node and is echoed in the response, so responses
|
||
|
// may be correlated with multiple queries to the same node. The transaction ID
|
||
|
// should be encoded as a short string of binary numbers, typically 2 characters
|
||
|
// are enough as they cover 2^16 outstanding queries. The other key contained
|
||
|
// in every KRPC message is "y" with a single character value describing
|
||
|
// the type of message. The value of the "y" key is one of "q" for query,
|
||
|
// "r" for response, or "e" for error.
|
||
|
type Message struct {
|
||
|
T string `bencode:"t"` // required: transaction ID
|
||
|
Y string `bencode:"y"` // required: type of the message: q for QUERY, r for RESPONSE, e for ERROR
|
||
|
Q string `bencode:"q,omitempty"` // Query method (one of 4: "ping", "find_node", "get_peers", "announce_peer")
|
||
|
A QueryArg `bencode:"a,omitempty"` // named arguments sent with a query
|
||
|
R ResponseResult `bencode:"r,omitempty"` // RESPONSE type only
|
||
|
E Error `bencode:"e,omitempty"` // ERROR type only
|
||
|
|
||
|
RO bool `bencode:"ro,omitempty"` // BEP 43: ReadOnly
|
||
|
}
|
||
|
|
||
|
// NewQueryMsg return a QUERY message.
|
||
|
func NewQueryMsg(tid, method string, arg QueryArg) Message {
|
||
|
return Message{T: tid, Y: "q", Q: method, A: arg}
|
||
|
}
|
||
|
|
||
|
// NewResponseMsg return a RESPONSE message.
|
||
|
func NewResponseMsg(tid string, data ResponseResult) Message {
|
||
|
return Message{T: tid, Y: "r", R: data}
|
||
|
}
|
||
|
|
||
|
// NewErrorMsg return a ERROR message.
|
||
|
func NewErrorMsg(tid string, code int, reason string) Message {
|
||
|
return Message{T: tid, Y: "e", E: Error{Code: code, Reason: reason}}
|
||
|
}
|
||
|
|
||
|
// IsQuery reports whether the message is an QUERY.
|
||
|
func (m Message) IsQuery() bool {
|
||
|
return m.Y == "q"
|
||
|
}
|
||
|
|
||
|
// IsResponse reports whether the message is an RESPONSE.
|
||
|
func (m Message) IsResponse() bool {
|
||
|
return m.Y == "r"
|
||
|
}
|
||
|
|
||
|
// IsError reports whether the message is an ERROR.
|
||
|
func (m Message) IsError() bool {
|
||
|
return m.Y == "e"
|
||
|
}
|
||
|
|
||
|
// RID returns the value named "id" in "r".
|
||
|
//
|
||
|
// Return "" instead if no "id".
|
||
|
func (m Message) RID() metainfo.Hash {
|
||
|
return m.R.ID
|
||
|
}
|
||
|
|
||
|
// QID returns the value named "id" in "a", that's, the query arguments.
|
||
|
//
|
||
|
// Return "" instead if no "id".
|
||
|
func (m Message) QID() metainfo.Hash {
|
||
|
return m.A.ID
|
||
|
}
|
||
|
|
||
|
// ID returns the QID or RID.
|
||
|
func (m Message) ID() metainfo.Hash {
|
||
|
switch m.Y {
|
||
|
case "q":
|
||
|
return m.QID()
|
||
|
case "r":
|
||
|
return m.RID()
|
||
|
default:
|
||
|
panic(fmt.Errorf("unknown message type '%s'", m.Y))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Error represents a response error.
|
||
|
type Error struct {
|
||
|
Code int
|
||
|
Reason string
|
||
|
}
|
||
|
|
||
|
// NewError returns a new Error.
|
||
|
func NewError(code int, reason string) Error {
|
||
|
return Error{Code: code, Reason: reason}
|
||
|
}
|
||
|
|
||
|
func (e *Error) decode(vs []interface{}) (err error) {
|
||
|
defer func() {
|
||
|
if r := recover(); r != nil {
|
||
|
err = fmt.Errorf("unpacking %#v: %v", vs, r)
|
||
|
}
|
||
|
}()
|
||
|
|
||
|
e.Code = int(vs[0].(int64))
|
||
|
e.Reason = vs[1].(string)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// UnmarshalBencode implements the interface bencode.Unmarshaler.
|
||
|
func (e *Error) UnmarshalBencode(b []byte) (err error) {
|
||
|
var iface interface{}
|
||
|
if err = bencode.NewDecoder(bytes.NewBuffer(b)).Decode(&iface); err != nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
switch v := iface.(type) {
|
||
|
case []interface{}:
|
||
|
err = e.decode(v)
|
||
|
case string:
|
||
|
e.Reason = v
|
||
|
default:
|
||
|
err = fmt.Errorf(`KRPC error bencode value has unexpected type: %T`, v)
|
||
|
}
|
||
|
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// MarshalBencode implements the interface bencode.Marshaler.
|
||
|
func (e Error) MarshalBencode() (ret []byte, err error) {
|
||
|
if e.Code == 0 && e.Reason == "" {
|
||
|
return nil, nil
|
||
|
}
|
||
|
|
||
|
buf := bytes.NewBuffer(nil)
|
||
|
buf.Grow(32)
|
||
|
err = bencode.NewEncoder(buf).Encode([]interface{}{e.Code, e.Reason})
|
||
|
if err != nil {
|
||
|
ret = buf.Bytes()
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func (e Error) Error() string {
|
||
|
return fmt.Sprintf("KRPC error %d: %s", e.Code, e.Reason)
|
||
|
}
|
||
|
|
||
|
// Want represents the type of nodes that the request wants.
|
||
|
//
|
||
|
// BEP 32
|
||
|
type Want string
|
||
|
|
||
|
// Predefine some wants.
|
||
|
//
|
||
|
// BEP 32
|
||
|
const (
|
||
|
WantNodes Want = "n4"
|
||
|
WantNodes6 Want = "n6"
|
||
|
)
|
||
|
|
||
|
// QueryArg represents the arguments used by the QUERY message.
|
||
|
type QueryArg struct {
|
||
|
// ID is used to identify a querying node.
|
||
|
ID metainfo.Hash `bencode:"id"` // BEP 5
|
||
|
|
||
|
// Target is used to identify the node sought by the queryer.
|
||
|
//
|
||
|
// find_node
|
||
|
Target metainfo.Hash `bencode:"target,omitempty"` // BEP 5
|
||
|
|
||
|
// InfoHash is the infohash of the torrent file.
|
||
|
//
|
||
|
// get_peers, announce_peer
|
||
|
InfoHash metainfo.Hash `bencode:"info_hash,omitempty"` // BEP 5
|
||
|
|
||
|
// Port is the port on where sender is listening to allow others
|
||
|
// to download the torrent.
|
||
|
//
|
||
|
// announce_peer
|
||
|
Port uint16 `bencode:"port,omitempty"` // BEP 5
|
||
|
|
||
|
// Token is the received one from an earlier "get_peers" query.
|
||
|
//
|
||
|
// announce_peer
|
||
|
Token string `bencode:"token,omitempty"` // BEP 5
|
||
|
|
||
|
// ImpliedPort is used by the senders to apparent DHT port
|
||
|
// to improve NAT support.
|
||
|
//
|
||
|
// announce_peer
|
||
|
ImpliedPort bool `bencode:"implied_port,omitempty"` // BEP 5
|
||
|
|
||
|
// Wants is only used to represent to expect "nodes" for "n4"
|
||
|
// or "nodes6" for "n6".
|
||
|
//
|
||
|
// Notice: It only governs the presence of the "nodes" and "nodes6"
|
||
|
// parameters, not the interpretation of "values".
|
||
|
//
|
||
|
// find_node, get_peers
|
||
|
Wants []Want `bencode:"want,omitempty"` // BEP 32
|
||
|
}
|
||
|
|
||
|
// ContainsWant reports whether the request contains the given Want.
|
||
|
func (a QueryArg) ContainsWant(w Want) bool {
|
||
|
for _, want := range a.Wants {
|
||
|
if want == w {
|
||
|
return true
|
||
|
}
|
||
|
}
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
// GetPort returns the real port of the peer.
|
||
|
func (a QueryArg) GetPort(port int) uint16 {
|
||
|
if a.ImpliedPort {
|
||
|
return uint16(port)
|
||
|
}
|
||
|
return a.Port
|
||
|
}
|
||
|
|
||
|
// ResponseResult represents the results used by the RESPONSE message.
|
||
|
type ResponseResult struct {
|
||
|
// ID is used to indentify the queried node, that's, the response node.
|
||
|
ID metainfo.Hash `bencode:"id"` // BEP 5
|
||
|
|
||
|
// Nodes is a string containing the compact node information for the list
|
||
|
// of the ipv4 target node, or the K(8) closest good nodes in routing table
|
||
|
// of the requested ipv4 target.
|
||
|
//
|
||
|
// find_node
|
||
|
Nodes CompactIPv4Node `bencode:"nodes,omitempty"` // BEP 5
|
||
|
|
||
|
// Nodes6 is a string containing the compact node information for the list
|
||
|
// of the ipv6 target node, or the K(8) closest good nodes in routing table
|
||
|
// of the requested ipv6 target.
|
||
|
//
|
||
|
// find_node
|
||
|
Nodes6 CompactIPv6Node `bencode:"nodes6,omitempty"` // BEP 32
|
||
|
|
||
|
// Token is used for future "announce_peer".
|
||
|
//
|
||
|
// get_peers
|
||
|
Token string `bencode:"token,omitempty"` // BEP 5
|
||
|
|
||
|
// Values is a list of the torrent peers.
|
||
|
//
|
||
|
// get_peers
|
||
|
Values CompactAddresses `bencode:"values,omitempty"` // BEP 5
|
||
|
}
|
||
|
|
||
|
/// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
|
||
|
|
||
|
// CompactAddresses represents a group of the compact addresses.
|
||
|
type CompactAddresses []metainfo.Address
|
||
|
|
||
|
// MarshalBinary implements the interface binary.BinaryMarshaler.
|
||
|
func (cas CompactAddresses) MarshalBinary() ([]byte, error) {
|
||
|
ss := make([]string, len(cas))
|
||
|
for i, addr := range cas {
|
||
|
data, err := addr.MarshalBinary()
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
ss[i] = string(data)
|
||
|
}
|
||
|
return bencode.EncodeBytes(ss)
|
||
|
}
|
||
|
|
||
|
// UnmarshalBinary implements the interface binary.BinaryUnmarshaler.
|
||
|
func (cas *CompactAddresses) UnmarshalBinary(b []byte) (err error) {
|
||
|
var ss []string
|
||
|
if err = bencode.DecodeBytes(b, &ss); err == nil {
|
||
|
addrs := make(CompactAddresses, len(ss))
|
||
|
for i, s := range ss {
|
||
|
var addr metainfo.Address
|
||
|
if err = addr.UnmarshalBinary([]byte(s)); err != nil {
|
||
|
return
|
||
|
}
|
||
|
addrs[i] = addr
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return
|
||
|
}
|
||
|
|
||
|
/// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
|
||
|
|
||
|
// CompactIPv4Node is a set of IPv4 Nodes.
|
||
|
type CompactIPv4Node []Node
|
||
|
|
||
|
// MarshalBinary implements the interface binary.BinaryMarshaler.
|
||
|
func (cn CompactIPv4Node) MarshalBinary() ([]byte, error) {
|
||
|
buf := bytes.NewBuffer(nil)
|
||
|
buf.Grow(26 * len(cn))
|
||
|
for _, ni := range cn {
|
||
|
if ni.Addr.IP = ni.Addr.IP.To4(); len(ni.Addr.IP) == 0 {
|
||
|
continue
|
||
|
}
|
||
|
if n, err := ni.WriteBinary(buf); err != nil {
|
||
|
return nil, err
|
||
|
} else if n != 26 {
|
||
|
panic(fmt.Errorf("CompactIPv4NodeInfo: the invalid NodeInfo length '%d'", n))
|
||
|
}
|
||
|
}
|
||
|
return buf.Bytes(), nil
|
||
|
}
|
||
|
|
||
|
// UnmarshalBinary implements the interface binary.BinaryUnmarshaler.
|
||
|
func (cn *CompactIPv4Node) UnmarshalBinary(b []byte) (err error) {
|
||
|
_len := len(b)
|
||
|
if _len%26 != 0 {
|
||
|
return fmt.Errorf("CompactIPv4NodeInfo: invalid bytes length '%d'", _len)
|
||
|
}
|
||
|
|
||
|
nis := make([]Node, 0, _len/26)
|
||
|
for i := 0; i < _len; i += 26 {
|
||
|
var ni Node
|
||
|
if err = ni.UnmarshalBinary(b[i : i+26]); err != nil {
|
||
|
return
|
||
|
}
|
||
|
nis = append(nis, ni)
|
||
|
}
|
||
|
|
||
|
*cn = nis
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// MarshalBencode implements the interface bencode.Marshaler.
|
||
|
func (cn CompactIPv4Node) MarshalBencode() (b []byte, err error) {
|
||
|
if b, err = cn.MarshalBinary(); err == nil {
|
||
|
b, err = bencode.EncodeBytes(b)
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// UnmarshalBencode implements the interface bencode.Unmarshaler.
|
||
|
func (cn *CompactIPv4Node) UnmarshalBencode(b []byte) (err error) {
|
||
|
var s string
|
||
|
if err = bencode.DecodeBytes(b, &s); err == nil {
|
||
|
err = cn.UnmarshalBinary([]byte(s))
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
/// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
|
||
|
|
||
|
// CompactIPv6Node is a set of IPv6 Nodes.
|
||
|
type CompactIPv6Node []Node
|
||
|
|
||
|
// MarshalBinary implements the interface binary.BinaryMarshaler.
|
||
|
func (cn CompactIPv6Node) MarshalBinary() ([]byte, error) {
|
||
|
buf := bytes.NewBuffer(nil)
|
||
|
buf.Grow(38 * len(cn))
|
||
|
for _, ni := range cn {
|
||
|
ni.Addr.IP = ni.Addr.IP.To16()
|
||
|
if n, err := ni.WriteBinary(buf); err != nil {
|
||
|
return nil, err
|
||
|
} else if n != 38 {
|
||
|
panic(fmt.Errorf("CompactIPv6NodeInfo: the invalid NodeInfo length '%d'", n))
|
||
|
}
|
||
|
}
|
||
|
return buf.Bytes(), nil
|
||
|
}
|
||
|
|
||
|
// UnmarshalBinary implements the interface binary.BinaryUnmarshaler.
|
||
|
func (cn *CompactIPv6Node) UnmarshalBinary(b []byte) (err error) {
|
||
|
_len := len(b)
|
||
|
if _len%38 != 0 {
|
||
|
return fmt.Errorf("CompactIPv6NodeInfo: invalid bytes length '%d'", _len)
|
||
|
}
|
||
|
|
||
|
nis := make([]Node, 0, _len/38)
|
||
|
for i := 0; i < _len; i += 38 {
|
||
|
var ni Node
|
||
|
if err = ni.UnmarshalBinary(b[i : i+38]); err != nil {
|
||
|
return
|
||
|
}
|
||
|
nis = append(nis, ni)
|
||
|
}
|
||
|
|
||
|
*cn = nis
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// MarshalBencode implements the interface bencode.Marshaler.
|
||
|
func (cn CompactIPv6Node) MarshalBencode() (b []byte, err error) {
|
||
|
if b, err = cn.MarshalBinary(); err == nil {
|
||
|
b, err = bencode.EncodeBytes(b)
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// UnmarshalBencode implements the interface bencode.Unmarshaler.
|
||
|
func (cn *CompactIPv6Node) UnmarshalBencode(b []byte) (err error) {
|
||
|
var s string
|
||
|
if err = bencode.DecodeBytes(b, &s); err == nil {
|
||
|
err = cn.UnmarshalBinary([]byte(s))
|
||
|
}
|
||
|
return
|
||
|
}
|