More tests and fixes. Currently fixing Message - see the Version message test
This commit is contained in:
@@ -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
|
||||
|
Reference in New Issue
Block a user