260 lines
7.9 KiB
Ruby
260 lines
7.9 KiB
Ruby
# -*- coding: binary -*-
|
|
class BitStruct
|
|
# Class for signed integers in network order, 1-16 bits, or 8n bits.
|
|
# Declared with BitStruct.signed.
|
|
class SignedField < Field
|
|
# Used in describe.
|
|
def self.class_name
|
|
@class_name ||= "signed"
|
|
end
|
|
|
|
def add_accessors_to(cl, attr = name) # :nodoc:
|
|
offset_byte = offset / 8
|
|
offset_bit = offset % 8
|
|
|
|
length_bit = offset_bit + length
|
|
length_byte = (length_bit/8.0).ceil
|
|
last_byte = offset_byte + length_byte - 1
|
|
max = 2**length-1
|
|
mid = 2**(length-1)
|
|
max_unsigned = 2**length
|
|
to_signed = proc {|n| (n>=mid) ? n - max_unsigned : n}
|
|
# to_signed = proc {|n| (n>=mid) ? -((n ^ max) + 1) : n}
|
|
|
|
divisor = options[:fixed] || options["fixed"]
|
|
divisor_f = divisor && divisor.to_f
|
|
# if divisor and not divisor.is_a? Fixnum
|
|
# raise ArgumentError, "fixed-point divisor must be a fixnum"
|
|
# end
|
|
|
|
endian = (options[:endian] || options["endian"]).to_s
|
|
case endian
|
|
when "native"
|
|
ctl = length_byte <= 2 ? "s" : "l"
|
|
if length == 16 or length == 32
|
|
to_signed = proc {|n| n}
|
|
# with pack support, to_signed can be replaced with no-op
|
|
end
|
|
when "little"
|
|
ctl = length_byte <= 2 ? "v" : "V"
|
|
when "network", "big", ""
|
|
ctl = length_byte <= 2 ? "n" : "N"
|
|
else
|
|
raise ArgumentError,
|
|
"Unrecognized endian option: #{endian.inspect}"
|
|
end
|
|
|
|
data_is_big_endian =
|
|
([1234].pack(ctl) == [1234].pack(length_byte <= 2 ? "n" : "N"))
|
|
|
|
if length_byte == 1
|
|
rest = 8 - length_bit
|
|
mask = ["0"*offset_bit + "1"*length + "0"*rest].pack("B8")[0].ord
|
|
mask2 = ["1"*offset_bit + "0"*length + "1"*rest].pack("B8")[0].ord
|
|
|
|
cl.class_eval do
|
|
if divisor
|
|
define_method attr do ||
|
|
to_signed[(self[offset_byte] & mask) >> rest] / divisor_f
|
|
end
|
|
|
|
define_method "#{attr}=" do |val|
|
|
val = (val * divisor).round
|
|
self[offset_byte] =
|
|
(self[offset_byte] & mask2) | ((val<<rest) & mask)
|
|
end
|
|
|
|
else
|
|
define_method attr do ||
|
|
to_signed[(self[offset_byte] & mask) >> rest]
|
|
end
|
|
|
|
define_method "#{attr}=" do |val|
|
|
self[offset_byte] =
|
|
(self[offset_byte] & mask2) | ((val<<rest) & mask)
|
|
end
|
|
end
|
|
end
|
|
|
|
elsif offset_bit == 0 and length % 8 == 0
|
|
field_length = length
|
|
byte_range = offset_byte..last_byte
|
|
|
|
cl.class_eval do
|
|
case field_length
|
|
when 8
|
|
if divisor
|
|
define_method attr do ||
|
|
to_signed[self[offset_byte]] / divisor_f
|
|
end
|
|
|
|
define_method "#{attr}=" do |val|
|
|
val = (val * divisor).round
|
|
self[offset_byte] = val
|
|
end
|
|
|
|
else
|
|
define_method attr do ||
|
|
to_signed[self[offset_byte]]
|
|
end
|
|
|
|
define_method "#{attr}=" do |val|
|
|
self[offset_byte] = val
|
|
end
|
|
end
|
|
|
|
when 16, 32
|
|
if divisor
|
|
define_method attr do ||
|
|
to_signed[self[byte_range].unpack(ctl).first] / divisor_f
|
|
end
|
|
|
|
define_method "#{attr}=" do |val|
|
|
val = (val * divisor).round
|
|
self[byte_range] = [val].pack(ctl)
|
|
end
|
|
|
|
else
|
|
define_method attr do ||
|
|
to_signed[self[byte_range].unpack(ctl).first]
|
|
end
|
|
|
|
define_method "#{attr}=" do |val|
|
|
self[byte_range] = [val].pack(ctl)
|
|
end
|
|
end
|
|
|
|
else
|
|
reader_helper = proc do |substr|
|
|
bytes = substr.unpack("C*")
|
|
bytes.reverse! unless data_is_big_endian
|
|
bytes.inject do |sum, byte|
|
|
(sum << 8) + byte
|
|
end
|
|
end
|
|
|
|
writer_helper = proc do |val|
|
|
bytes = []
|
|
val += max_unsigned if val < 0
|
|
while val > 0
|
|
bytes.push val % 256
|
|
val = val >> 8
|
|
end
|
|
if bytes.length < length_byte
|
|
bytes.concat [0] * (length_byte - bytes.length)
|
|
end
|
|
|
|
bytes.reverse! if data_is_big_endian
|
|
bytes.pack("C*")
|
|
end
|
|
|
|
if divisor
|
|
define_method attr do ||
|
|
to_signed[reader_helper[self[byte_range]] / divisor_f]
|
|
end
|
|
|
|
define_method "#{attr}=" do |val|
|
|
self[byte_range] = writer_helper[(val * divisor).round]
|
|
end
|
|
|
|
else
|
|
define_method attr do ||
|
|
to_signed[reader_helper[self[byte_range]]]
|
|
end
|
|
|
|
define_method "#{attr}=" do |val|
|
|
self[byte_range] = writer_helper[val]
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
elsif length_byte == 2 # unaligned field that fits within two whole bytes
|
|
byte_range = offset_byte..last_byte
|
|
rest = 16 - length_bit
|
|
|
|
mask = ["0"*offset_bit + "1"*length + "0"*rest]
|
|
mask = mask.pack("B16").unpack(ctl).first
|
|
|
|
mask2 = ["1"*offset_bit + "0"*length + "1"*rest]
|
|
mask2 = mask2.pack("B16").unpack(ctl).first
|
|
|
|
cl.class_eval do
|
|
if divisor
|
|
define_method attr do ||
|
|
to_signed[(self[byte_range].unpack(ctl).first & mask) >> rest] /
|
|
divisor_f
|
|
end
|
|
|
|
define_method "#{attr}=" do |val|
|
|
val = (val * divisor).round
|
|
x = (self[byte_range].unpack(ctl).first & mask2) |
|
|
((val<<rest) & mask)
|
|
self[byte_range] = [x].pack(ctl)
|
|
end
|
|
|
|
else
|
|
define_method attr do ||
|
|
to_signed[(self[byte_range].unpack(ctl).first & mask) >> rest]
|
|
end
|
|
|
|
define_method "#{attr}=" do |val|
|
|
x = (self[byte_range].unpack(ctl).first & mask2) |
|
|
((val<<rest) & mask)
|
|
self[byte_range] = [x].pack(ctl)
|
|
end
|
|
end
|
|
end
|
|
|
|
elsif length_byte == 3 # unaligned field that fits within 3 whole bytes
|
|
byte_range = offset_byte..last_byte
|
|
rest = 32 - length_bit
|
|
|
|
mask = ["0"*offset_bit + "1"*length + "0"*rest]
|
|
mask = mask.pack("B32").unpack(ctl).first
|
|
|
|
mask2 = ["1"*offset_bit + "0"*length + "1"*rest]
|
|
mask2 = mask2.pack("B32").unpack(ctl).first
|
|
|
|
cl.class_eval do
|
|
if divisor
|
|
define_method attr do ||
|
|
bytes = self[byte_range]
|
|
bytes << 0
|
|
to_signed[((bytes.unpack(ctl).first & mask) >> rest)] /
|
|
divisor_f
|
|
end
|
|
|
|
define_method "#{attr}=" do |val|
|
|
val = (val * divisor).round
|
|
bytes = self[byte_range]
|
|
bytes << 0
|
|
x = (bytes.unpack(ctl).first & mask2) |
|
|
((val<<rest) & mask)
|
|
self[byte_range] = [x].pack(ctl)[0..2]
|
|
end
|
|
|
|
else
|
|
define_method attr do ||
|
|
bytes = self[byte_range]
|
|
bytes << 0
|
|
to_signed[(bytes.unpack(ctl).first & mask) >> rest]
|
|
end
|
|
|
|
define_method "#{attr}=" do |val|
|
|
bytes = self[byte_range]
|
|
bytes << 0
|
|
x = (bytes.unpack(ctl).first & mask2) |
|
|
((val<<rest) & mask)
|
|
self[byte_range] = [x].pack(ctl)[0..2]
|
|
end
|
|
end
|
|
end
|
|
|
|
else
|
|
raise "unsupported: #{inspect}"
|
|
end
|
|
end
|
|
end
|
|
end
|