Merge MessageHdr into Message and make Ipv6Address a BasePrimitive
The former makes setting payload_length sensibly much easier, with no drawbacks that I can see, while the latter lets us override assign to make comparisons between instances sane. All good.
This commit is contained in:
@@ -4,41 +4,58 @@ 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
|
||||
class Ipv6Address < BinData::BasePrimitive
|
||||
register_self if BinData::VERSION < "1.3.2"
|
||||
|
||||
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
|
||||
def value_to_binary_string(v)
|
||||
ip = any_to_ipaddress(v)
|
||||
raise ArgumentError.new("Can't convert #{v.class} to Ipv6Address") unless ip
|
||||
ip.data
|
||||
end
|
||||
|
||||
def read_and_return_value(io)
|
||||
address = read_uint128(io)
|
||||
if address >= 0xffff00000000 && address <= 0xffffffffffff # v6-mapped v4
|
||||
IPAddress::IPv6::Mapped.parse_u128(address)
|
||||
else # v6
|
||||
IPAddress::IPv6.parse_u128(address)
|
||||
end
|
||||
end
|
||||
|
||||
def sensible_default
|
||||
IPAddress("::")
|
||||
end
|
||||
|
||||
def assign(v)
|
||||
super(any_to_ipaddress(v))
|
||||
end
|
||||
|
||||
def set(v)
|
||||
protected
|
||||
|
||||
def any_to_ipaddress(v)
|
||||
v = IPAddress(v) if v.is_a?(String)
|
||||
v = IPAddress::IPv6::Mapped.new(v.to_s) if v.is_a?(IPAddress::IPv4)
|
||||
if v.is_a?(Fixnum) || v.is_a?(Bignum)
|
||||
v = if v < 2**32
|
||||
IPAddress::IPv6::Mapped.parse_u128(v)
|
||||
else
|
||||
IPAddress::IPv6::parse_u128(v)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
if v.respond_to?(:to_u128)
|
||||
self.address = v.to_u128
|
||||
if v.is_a?(IPAddress::IPv4)
|
||||
IPAddress::IPv6::Mapped.new("::ffff:#{v.to_s}")
|
||||
elsif v.is_a?(IPAddress::IPv6)
|
||||
v
|
||||
elsif v.is_a?(Fixnum) || v.is_a?(Bignum) || v.respond_to?(:to_u128)
|
||||
v = v.to_u128 if v.respond_to?(:to_u128)
|
||||
if v < 2**32
|
||||
IPAddress::IPv6::Mapped.new("::ffff:#{IPAddress::IPv4.parse_u32(v).to_s}")
|
||||
else
|
||||
IPAddress::IPv6.parse_u128(v)
|
||||
end
|
||||
else
|
||||
raise ArgumentError.new("Can't set #{v.class} to an IPv6Address")
|
||||
end
|
||||
end
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def read_uint128(io)
|
||||
top, middle1, middle2, bottom = io.readbytes(16).unpack("NNNN")
|
||||
(top << 96) | (middle1 << 64) | (middle2 << 32) | (bottom)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# Implementation of the BitCoin wire protocol, written using bindata.
|
||||
@@ -395,30 +412,6 @@ module BtcWireProto
|
||||
|
||||
## Top-level message format ##
|
||||
|
||||
# Found at the start of all Bitcoin messages.
|
||||
# @author Nick Thomas <nick@lupine.me.uk>
|
||||
class MessageHdr < BinData::Record
|
||||
endian :little
|
||||
uint32 :magic
|
||||
string :command, :length => 12
|
||||
uint32 :payload_len
|
||||
uint32 :checksum, :onlyif => :has_checksum?
|
||||
|
||||
def cmd_sym
|
||||
c = command.strip
|
||||
c == "" ? nil : c.to_sym
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# version and verack messages don't have a checksum. The rest do.
|
||||
# @return[Boolean] does this message header have a checksum field or not?
|
||||
def has_checksum?
|
||||
!%w|version verack|.include?(command.strip)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Everything on the wire is a Message.
|
||||
# @author Nick Thomas <nick@lupine.me.uk>
|
||||
class Message < BinData::Record
|
||||
@@ -430,11 +423,11 @@ module BtcWireProto
|
||||
@version = v || ::BtcWireProto::CURRENT_VERSION(:main)
|
||||
end
|
||||
|
||||
message_hdr :header
|
||||
|
||||
def cmd_sym
|
||||
header ? header.cmd_sym : nil
|
||||
end
|
||||
endian :little
|
||||
uint32 :magic
|
||||
string :command, :length => 12
|
||||
uint32 :payload_len, :value => lambda { payload.num_bytes }
|
||||
uint32 :checksum, :onlyif => :has_checksum?
|
||||
|
||||
choice :payload, :selection => :payload_choice do
|
||||
version "version"
|
||||
@@ -453,10 +446,20 @@ module BtcWireProto
|
||||
null_payload "null"
|
||||
end
|
||||
|
||||
def cmd_sym
|
||||
c = command.strip
|
||||
c == "" ? nil : c.to_sym
|
||||
end
|
||||
# version and verack messages don't have a checksum. The rest do.
|
||||
# @return[Boolean] does this message header have a checksum field or not?
|
||||
def has_checksum?
|
||||
!%w|version verack|.include?(command.to_s.strip)
|
||||
end
|
||||
|
||||
# Works out what the payload looks like based on the MessageHdr struct
|
||||
# and (potentially) the version
|
||||
def payload_choice
|
||||
cmd = header.command.to_s.strip
|
||||
cmd = command.to_s.strip
|
||||
return cmd if %w{
|
||||
version inv getdata getblocks getheaders tx block headers alert
|
||||
}.include?(cmd)
|
||||
|
Reference in New Issue
Block a user