More tests and fixes. Currently fixing Message - see the Version message test

This commit is contained in:
Nick Thomas
2011-05-19 01:06:54 +01:00
parent f3f89283e2
commit 7e31f3976d
3 changed files with 248 additions and 25 deletions

View File

@@ -1,9 +1,44 @@
require 'bindata'
require 'ipaddress'
# Wraps the IPAddress::IPv6 class found in the ipaddress gem to provide easier
# handling of binary-format IP addresses
# @author Nick Thomas <nick@lupine.me.uk>
class Ipv6Address < BinData::Primitive
endian :big
uint128 :address
def get
if address
if address >= 0xffff00000000 && address <= 0xffffffffffff # v6-mapped v4
IPAddress::IPv6::Mapped.parse_u128(address)
else # v6
IPAddress::IPv6.parse_u128(address)
end
else
nil # Nothing set
end
end
def set(v)
v = IPAddress::IPv6::Mapped.new(v.to_s) if v.is_a?(IPAddress::IPv4)
v = IPAddress(v.to_s) unless v.is_a?(IPAddress::IPv6)
if v.respond_to?(:to_u128)
address = v.to_u128
else
raise ArgumentError.new("Can't set #{v.class} to an IPv6Address")
end
end
end
# Implementation of the BitCoin wire protocol, written using bindata.
# Reference: https://en.bitcoin.it/wiki/Protocol_specification
#
# @author Nick Thomas <nick@lupine.me.uk
# @author Nick Thomas <nick@lupine.me.uk>
module BtcWireProto
CURRENT_VERSION = 32100
# Comprehensive list of known networks. The hex values are what you see in
@@ -52,9 +87,9 @@ module BtcWireProto
# @author Nick Thomas <nick@lupine.me.uk>
class ServicesMask < BinData::Record
endian :little
bit63 :undefined
bit7 :top_undefined
bit1 :node_network
bit56 :undefined
end
# Structure holding an IP address and port in a slightly unusual format.
@@ -64,7 +99,7 @@ module BtcWireProto
class NetAddr < BinData::Record
endian :big
services_mask :services
uint128 :ip # IPv6 address. IPv4 addresses given as IPv6-mapped IPv4
ipv6_address :ip # IPv6 address. IPv4 addresses given as IPv6-mapped IPv4
uint16 :port
end
@@ -73,13 +108,14 @@ module BtcWireProto
class TimestampedNetAddr < BinData::Record
endian :little
uint32 :timestamp
uint32 :timestamp # TODO: Allow this to be set with Ruby native types
net_addr :net_addr
end
# Variable-length integer. This is slightly scary.
# @author Nick Thomas <nick@lupine.me.uk>
class VarInt < BinData::BasePrimitive
register_self
def value_to_binary_string(val)
val = val.to_i
@@ -87,8 +123,8 @@ module BtcWireProto
if val < -0xffffffffffffffff # unrepresentable
""
elsif val < 0 # 64-bit negative integer
top_32 = (val & 0xffffffff00000000) >> 32
btm_32 = val & 0x00000000ffffffff
top_32 = ((-val) & 0xffffffff00000000) >> 32
btm_32 = (-val) & 0x00000000ffffffff
[0xff, top_32, btm_32].pack("CVV")
elsif val <= 0xfc # 8-bit (almost) positive integer
[val].pack("C")
@@ -102,8 +138,6 @@ module BtcWireProto
end
def read_and_return_value(io)
return nil if io.length < 1
magic = read_uint8(io)
if magic <= 0xfc # 8-bit (almost) positive integer
magic
@@ -119,9 +153,26 @@ module BtcWireProto
def sensible_default
0
end
end
protected
BinData::RegisteredClasses.register("var_int", VarInt)
def read_uint8(io)
io.readbytes(1).unpack("C").at(0)
end
def read_uint16(io)
io.readbytes(2).unpack("v").at(0)
end
def read_uint32(io)
io.readbytes(4).unpack("V").at(0)
end
def read_uint64(io)
top, bottom = io.readbytes(8).unpack("VV")
(top << 32) | bottom
end
end
# Variable-length pascal string with a variable-length int specifying the
# length. I kid you not.
@@ -194,10 +245,10 @@ module BtcWireProto
services_mask :services
uint64 :timestamp
net_addr :addr_me
net_addr :addr_you, :only_if => lambda { version >= 106 }
uint64 :nonce, :only_if => lambda { version >= 106 }
var_str :sub_version, :only_if => lambda { version >= 106 }
uint32 :start_height, :only_if => lambda { version >= 209 }
net_addr :addr_you, :onlyif => lambda { version >= 106 }
uint64 :nonce, :onlyif => lambda { version >= 106 }
var_str :sub_version, :onlyif => lambda { version >= 106 }
uint32 :start_height, :onlyif => lambda { version >= 209 }
end
# Payload for an addr message in versions earlier than 31402. These are
@@ -307,6 +358,13 @@ module BtcWireProto
uint32 :reply # See REPLYCODES for possible values
end
# Completely empty payload. BinData dies if we don't specify *something*
# in the message payload choices.
# @author Nick Thomas <nick@lupine.me.uk>
class NullPayload < BinData::Record
endian :little
end
# A message sent using the p2p network. Signed by a key so you can tell who
# sent it - if it's signed by a particular key, then we should apparently
# show the message to the user and cease operation until further notice. Fun!
@@ -345,8 +403,9 @@ module BtcWireProto
# @param[Fixnum,nil] version The protocol version. Setting this affects
# the layout of various fields.
def initialize(version = nil)
@version = version || BtcWireProto::CURRENT_VERSION
def initialize_instance(v = nil)
super()
@version = v || BtcWireProto::CURRENT_VERSION
end
message_hdr :header
@@ -365,13 +424,13 @@ module BtcWireProto
check_order "checkorder"
submit_order "submitorder"
alert "alert"
null_payload "null"
end
protected
# Works out what the payload looks like based on the MessageHdr struct
# and (potentially) the version
def payload_choice
puts header.command
return header.command if %w{
version inv getdata getblocks getheaders tx block headers alert
}.include?(header.command)
@@ -383,12 +442,14 @@ module BtcWireProto
) if %w|checkorder submitorder|.include?(header.command)
# These commands don't have any payloads
return nil if %w|verack getaddr ping|.include?(header.command)
return "null" if %w|verack getaddr ping|.include?(header.command) ||
header.command == ""
# Payload has two forms, depending on protocol version. Ugh.
return (@version < 31402 ? "addr_pre31402" : "addr_from31402") if
header.command == "addr"
header.command == "addr"
raise NotImplementedError.new("Unknown command: #{header.command.inspect}")
end
end