udp: implement bep41

This commit is contained in:
Leo Balduf 2016-08-30 22:21:05 -04:00
parent 6260570635
commit c667497c6d
2 changed files with 123 additions and 26 deletions

View file

@ -1,8 +1,11 @@
package udp
import (
"bytes"
"encoding/binary"
"fmt"
"net"
"sync"
"github.com/chihaya/chihaya/bittorrent"
)
@ -37,11 +40,12 @@ var (
bittorrent.Stopped,
}
errMalformedPacket = bittorrent.ClientError("malformed packet")
errMalformedIP = bittorrent.ClientError("malformed IP address")
errMalformedEvent = bittorrent.ClientError("malformed event ID")
errUnknownAction = bittorrent.ClientError("unknown action ID")
errBadConnectionID = bittorrent.ClientError("bad connection ID")
errMalformedPacket = bittorrent.ClientError("malformed packet")
errMalformedIP = bittorrent.ClientError("malformed IP address")
errMalformedEvent = bittorrent.ClientError("malformed event ID")
errUnknownAction = bittorrent.ClientError("unknown action ID")
errBadConnectionID = bittorrent.ClientError("bad connection ID")
errUnknownOptionType = bittorrent.ClientError("unknown option type")
)
// ParseAnnounce parses an AnnounceRequest from a UDP request.
@ -76,7 +80,7 @@ func ParseAnnounce(r Request, allowIPSpoofing bool) (*bittorrent.AnnounceRequest
numWant := binary.BigEndian.Uint32(r.Packet[92:96])
port := binary.BigEndian.Uint16(r.Packet[96:98])
params, err := handleOptionalParameters(r.Packet)
params, err := handleOptionalParameters(r.Packet[98:])
if err != nil {
return nil, err
}
@ -97,43 +101,65 @@ func ParseAnnounce(r Request, allowIPSpoofing bool) (*bittorrent.AnnounceRequest
}, nil
}
type buffer struct {
bytes.Buffer
}
var bufferFree = sync.Pool{
New: func() interface{} { return new(buffer) },
}
func newBuffer() *buffer {
return bufferFree.Get().(*buffer)
}
func (b *buffer) free() {
b.Reset()
bufferFree.Put(b)
}
// handleOptionalParameters parses the optional parameters as described in BEP
// 41 and updates an announce with the values parsed.
func handleOptionalParameters(packet []byte) (params bittorrent.Params, err error) {
if len(packet) <= 98 {
return
func handleOptionalParameters(packet []byte) (bittorrent.Params, error) {
if len(packet) == 0 {
return bittorrent.ParseURLData("")
}
optionStartIndex := 98
for optionStartIndex < len(packet)-1 {
option := packet[optionStartIndex]
var buf = newBuffer()
defer buf.free()
for i := 0; i < len(packet); {
option := packet[i]
switch option {
case optionEndOfOptions:
return
return bittorrent.ParseURLData(buf.String())
case optionNOP:
optionStartIndex++
i++
case optionURLData:
if optionStartIndex+1 > len(packet)-1 {
return params, errMalformedPacket
if i+1 >= len(packet) {
return nil, errMalformedPacket
}
length := int(packet[optionStartIndex+1])
if optionStartIndex+1+length > len(packet)-1 {
return params, errMalformedPacket
length := int(packet[i+1])
if i+2+length > len(packet) {
return nil, errMalformedPacket
}
// TODO(chihaya): Actually parse the URL Data as described in BEP 41
// into something that fulfills the bittorrent.Params interface.
n, err := buf.Write(packet[i+2 : i+2+length])
if err != nil {
return nil, err
}
if n != length {
return nil, fmt.Errorf("expected to write %d bytes, wrote %d", length, n)
}
optionStartIndex += 1 + length
i += 2 + length
default:
return
return nil, errUnknownOptionType
}
}
return
return bittorrent.ParseURLData(buf.String())
}
// ParseScrape parses a ScrapeRequest from a UDP request.

View file

@ -0,0 +1,71 @@
package udp
import "testing"
var table = []struct {
data []byte
values map[string]string
err error
}{
{
[]byte{0x2, 0x5, '/', '?', 'a', '=', 'b'},
map[string]string{"a": "b"},
nil,
},
{
[]byte{0x2, 0x0},
map[string]string{},
nil,
},
{
[]byte{0x2, 0x1},
nil,
errMalformedPacket,
},
{
[]byte{0x2},
nil,
errMalformedPacket,
},
{
[]byte{0x2, 0x8, '/', 'c', '/', 'd', '?', 'a', '=', 'b'},
map[string]string{"a": "b"},
nil,
},
{
[]byte{0x2, 0x2, '/', '?', 0x2, 0x3, 'a', '=', 'b'},
map[string]string{"a": "b"},
nil,
},
{
[]byte{0x2, 0x9, '/', '?', 'a', '=', 'b', '%', '2', '0', 'c'},
map[string]string{"a": "b c"},
nil,
},
}
func TestHandleOptionalParameters(t *testing.T) {
for _, testCase := range table {
params, err := handleOptionalParameters(testCase.data)
if err != testCase.err {
if testCase.err == nil {
t.Fatalf("expected no parsing error for %x but got %s", testCase.data, err)
} else {
t.Fatalf("expected parsing error for %x", testCase.data)
}
}
if testCase.values != nil {
if params == nil {
t.Fatalf("expected values %v for %x", testCase.values, testCase.data)
} else {
for key, want := range testCase.values {
if got, ok := params.String(key); !ok {
t.Fatalf("params missing entry %s for data %x", key, testCase.data)
} else if got != want {
t.Fatalf("expected param %s=%s, but was %s for data %x", key, want, got, testCase.data)
}
}
}
}
}
}