module BitStruct # Implements access to a 'segment' of a byte string, specified by # offset (from bit 0) and length (in bits). Supports little and big # endian data input and output. # # NOTE: This is highly inefficient. If a segment is byte-aligned, # please use the optimized ByteSlicer class. class BitSlicer module Endianess LITTLE_ENDIAN = LITTLE = INTEL = 0 BIG_ENDIAN = BIG = MOTOROLA = 1 NETWORK_BYTE_ORDER = BIG_ENDIAN end include Endianess def initialize( bit_offset, bit_length, endianess = MOTOROLA ) raise ArgumentError if bit_offset < 0 || bit_length < 0 @bit_offset = bit_offset @bit_length = bit_length @endianess = endianess end def data=( new_data ) raise ArgumentError if ( @bit_offset + @bit_length ) > new_data.length * 8 @data = new_data end def data @data end def get_bytes align_shift = required_shift bytes = get_raw_bytes # Step 1: Shift all bytes to the right to align bit 0 # (in case bit_offset is not on byte border). BitSlicer.shift_bytes_right( bytes, align_shift ) # Step 2: Strip highest (unused/empty) byte if necessary # (in case bit_length is on byte border, but bit_offset not). data_length = length_in_bytes while bytes.length > data_length bytes = bytes[ 1, bytes.length - 1 ] end # Step 3: Apply mask to 'highest' byte # (in case bit_length is not on byte border). bytes[ 0 ] &= highest_byte_mask bytes end def set_bytes( bytes ) align_shift = required_shift # Step 1: Insert empty bytes to allow overflow from shifting. data_length = insert_length_in_bytes while bytes.length < data_length bytes.insert 0, 0x00 end while bytes.length > data_length bytes = bytes[ 1, bytes.length - 1 ] end # Step 2: Shift all bytes to the left to align bit 0 with the 'destination postion' # (in case bit_offset is not on byte border). BitSlicer.shift_bytes_left( bytes, align_shift ) # Step 3: Mask out unused parts of first and last byte. bytes[ 0 ] &= insert_first_byte_mask bytes[ bytes.length - 1 ] &= insert_last_byte_mask # Step 4: Merge existing data into first and last byte. bytes[ 0 ] |= masked_first_byte bytes[ bytes.length - 1 ] |= masked_last_byte # Step 5: Write back the byte data. set_raw_bytes bytes data end #private def get_data( idx ) return @data[ idx ] if @endianess == MOTOROLA return @data[ -idx ] if @endianess == INTEL end def set_data( idx, val ) @data[ idx ] = val if @endianess == MOTOROLA @data[ -idx ] = val if @endianess == INTEL end def first_byte data.length - ( ( @bit_offset + @bit_length ) / 8.0 ).ceil end def last_byte data.length - 1 - @bit_offset / 8 end def length_in_bytes ( @bit_length / 8.0 ).ceil end def insert_length_in_bytes ( ( ( @bit_offset & 7 ) + @bit_length ) / 8.0 ).ceil end def masked_first_byte get_data( first_byte ) & merge_first_byte_mask end def masked_last_byte get_data( last_byte ) & merge_last_byte_mask end def required_shift @bit_offset & 7 end def get_raw_bytes raw_bytes = [] first_byte.upto( last_byte ) do |idx| raw_bytes << get_data( idx ) end raw_bytes end def set_raw_bytes( raw_bytes ) first_byte.upto( last_byte ) do |idx| set_data idx, raw_bytes[ idx - first_byte ] end end def highest_byte_mask mask_bits = ( ( @bit_length ) & 7 ) mask_bits = 8 if mask_bits == 0 ( 2 ** mask_bits ) - 1 end def insert_first_byte_mask mask_bits = ( ( @bit_offset + @bit_length ) & 7 ) mask_bits = 8 if mask_bits == 0 ( 2 ** mask_bits ) - 1 end def insert_last_byte_mask mask_bits = 0xFF ( mask_bits << ( @bit_offset & 7 ) ) & 0xFF end def merge_first_byte_mask 255 - insert_first_byte_mask end def merge_last_byte_mask 255 - insert_last_byte_mask end def self.right_shifted( before, subject, bits ) ( ( before << 8 | subject ) >> bits ) & 0xFF end def self.left_shifted( subject, after, bits ) ( ( ( subject << 8 | after ) << bits ) >> 8 ) & 0xFF end def self.shift_bytes_right( bytes, bit_shift_count ) return if bit_shift_count == 0 raise ArgumentError if bit_shift_count < 0 || bit_shift_count > 8 from = bytes.length - 1 from.downto( 0 ) do |idx| before = idx > 0 ? bytes[ idx - 1 ] : 0 bytes[ idx ] = BitSlicer.right_shifted( before, bytes[ idx ], bit_shift_count ) end bytes end def self.shift_bytes_left( bytes, bit_shift_count ) return if bit_shift_count == 0 raise ArgumentError if bit_shift_count < 0 || bit_shift_count > 8 0.upto( bytes.length - 1 ) do |idx| after = bytes[ idx + 1 ] || 0 bytes[ idx ] = BitSlicer.left_shifted( bytes[ idx ], after, bit_shift_count ) end bytes end end end