diff --git a/bep40.go b/bep40.go new file mode 100644 index 00000000..b92ef9d9 --- /dev/null +++ b/bep40.go @@ -0,0 +1,82 @@ +package torrent + +import ( + "bytes" + "encoding/binary" + "fmt" + "hash/crc32" + + "net" +) + +var table = crc32.MakeTable(crc32.Castagnoli) + +type peerPriority = uint32 + +type ipPort struct { + IP net.IP + Port uint16 +} + +func sameSubnet(ones, bits int, a, b net.IP) bool { + mask := net.CIDRMask(ones, bits) + return a.Mask(mask).Equal(b.Mask(mask)) +} + +func ipv4Mask(a, b net.IP) net.IPMask { + if !sameSubnet(16, 32, a, b) { + return net.IPv4Mask(0xff, 0xff, 0x55, 0x55) + } + if !sameSubnet(24, 32, a, b) { + return net.IPv4Mask(0xff, 0xff, 0xff, 0x55) + } + return net.IPv4Mask(0xff, 0xff, 0xff, 0xff) +} + +func mask(prefix, bytes int) net.IPMask { + ret := make(net.IPMask, bytes) + for i := range ret { + ret[i] = 0x55 + } + for i := 0; i < prefix; i++ { + ret[i] = 0xff + } + return ret +} + +func ipv6Mask(a, b net.IP) net.IPMask { + for i := 6; i <= 16; i++ { + if !sameSubnet(i*8, 128, a, b) { + return mask(i, 16) + } + } + panic(fmt.Sprintf("%s %s", a, b)) +} + +func bep40PriorityBytes(a, b ipPort) []byte { + if a.IP.Equal(b.IP) { + var ret [4]byte + binary.BigEndian.PutUint16(ret[0:2], a.Port) + binary.BigEndian.PutUint16(ret[2:4], b.Port) + return ret[:] + } + if a4, b4 := a.IP.To4(), b.IP.To4(); a4 != nil && b4 != nil { + m := ipv4Mask(a.IP, b.IP) + return append(a4.Mask(m), b4.Mask(m)...) + } + if a6, b6 := a.IP.To16(), b.IP.To16(); a6 != nil && b6 != nil { + m := ipv6Mask(a.IP, b.IP) + return append(a6.Mask(m), b6.Mask(m)...) + } + panic(fmt.Sprintf("%s %s", a.IP, b.IP)) +} + +func bep40Priority(a, b ipPort) peerPriority { + bs := bep40PriorityBytes(a, b) + i := len(bs) / 2 + _a, _b := bs[:i], bs[i:] + if bytes.Compare(_a, _b) > 0 { + bs = append(_b, _a...) + } + return crc32.Checksum(bs, table) +} diff --git a/bep40_test.go b/bep40_test.go new file mode 100644 index 00000000..4c82a5ad --- /dev/null +++ b/bep40_test.go @@ -0,0 +1,27 @@ +package torrent + +import ( + "net" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestBep40Priority(t *testing.T) { + assert.EqualValues(t, 0xec2d7224, bep40Priority( + ipPort{net.ParseIP("123.213.32.10"), 0}, + ipPort{net.ParseIP("98.76.54.32"), 0}, + )) + assert.EqualValues(t, 0xec2d7224, bep40Priority( + ipPort{net.ParseIP("98.76.54.32"), 0}, + ipPort{net.ParseIP("123.213.32.10"), 0}, + )) + assert.Equal(t, peerPriority(0x99568189), bep40Priority( + ipPort{net.ParseIP("123.213.32.10"), 0}, + ipPort{net.ParseIP("123.213.32.234"), 0}, + )) + assert.EqualValues(t, "\x00\x00\x00\x00", bep40PriorityBytes( + ipPort{net.ParseIP("123.213.32.234"), 0}, + ipPort{net.ParseIP("123.213.32.234"), 0}, + )) +}