Skip to content

Commit

Permalink
Implement Section. Closes #101
Browse files Browse the repository at this point in the history
  • Loading branch information
dmendel committed Jun 25, 2023
1 parent bcd2aa2 commit b540ae6
Show file tree
Hide file tree
Showing 5 changed files with 177 additions and 0 deletions.
1 change: 1 addition & 0 deletions ChangeLog.rdoc
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* Ruby 2.5 is now required.
* Allow for nested tracing.
* Skip :until_valid is now fast for :asserted_value.
* Added Section - a way to transform the data stream.

== Version 2.4.15 (2023-02-07)

Expand Down
1 change: 1 addition & 0 deletions lib/bindata.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
require 'bindata/primitive'
require 'bindata/record'
require 'bindata/rest'
require 'bindata/section'
require 'bindata/skip'
require 'bindata/string'
require 'bindata/stringz'
Expand Down
2 changes: 2 additions & 0 deletions lib/bindata/dsl.rb
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ def parser_abilities
choice: [:to_choice_params, :choices, [:multiple_fields, :all_or_none_fieldnames, :fieldnames_are_values]],
delayed_io: [:to_object_params, :type, [:multiple_fields, :optional_fieldnames, :hidden_fields]],
primitive: [:to_struct_params, :struct, [:multiple_fields, :optional_fieldnames]],
section: [:to_object_params, :type, [:multiple_fields, :optional_fieldnames]],
skip: [:to_object_params, :until_valid, [:multiple_fields, :optional_fieldnames]]
}
end
Expand Down Expand Up @@ -377,6 +378,7 @@ def params_from_block(&block)
buffer: BinData::Buffer,
choice: BinData::Choice,
delayed_io: BinData::DelayedIO,
section: BinData::Section,
skip: BinData::Skip,
struct: BinData::Struct
}
Expand Down
97 changes: 97 additions & 0 deletions lib/bindata/section.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
require 'bindata/base'
require 'bindata/dsl'

module BinData
# A Section is a layer on top of a stream that transforms the underlying
# data. This allows BinData to process a stream that has multiple
# encodings. e.g. Some data data is compressed or encrypted.
#
# require 'bindata'
#
# class XorTransform < BinData::IO::Transform
# def initialize(xor)
# super()
# @xor = xor
# end
#
# def read(n)
# chain_read(n).bytes.map { |byte| (byte ^ @xor).chr }.join
# end
#
# def write(data)
# chain_write(data.bytes.map { |byte| (byte ^ @xor).chr }.join)
# end
# end
#
# obj = BinData::Section.new(transform: -> { XorTransform.new(0xff) },
# type: [:string, read_length: 5])
#
# obj.read("\x97\x9A\x93\x93\x90") #=> "hello"
#
#
# == Parameters
#
# Parameters may be provided at initialisation to control the behaviour of
# an object. These params are:
#
# <tt>:transform</tt>:: A callable that returns a new BinData::IO::Transform.
# <tt>:type</tt>:: The single type inside the buffer. Use a struct if
# multiple fields are required.
class Section < BinData::Base
extend DSLMixin

dsl_parser :section
arg_processor :section

mandatory_parameters :transform, :type

def initialize_instance
@type = get_parameter(:type).instantiate(nil, self)
end

def clear?
@type.clear?
end

def assign(val)
@type.assign(val)
end

def snapshot
@type.snapshot
end

def respond_to_missing?(symbol, include_all = false) # :nodoc:
@type.respond_to?(symbol, include_all) || super
end

def method_missing(symbol, *args, &block) # :nodoc:
@type.__send__(symbol, *args, &block)
end

def do_read(io) # :nodoc:
io.transform(eval_parameter(:transform)) do |transformed_io, _raw_io|
@type.do_read(transformed_io)
end
end

def do_write(io) # :nodoc:
io.transform(eval_parameter(:transform)) do |transformed_io, _raw_io|
@type.do_write(transformed_io)
end
end

def do_num_bytes # :nodoc:
to_binary_s.size
end
end

class SectionArgProcessor < BaseArgProcessor
include MultiFieldArgSeparator

def sanitize_parameters!(obj_class, params)
params.merge!(obj_class.dsl_params)
params.sanitize_object_prototype(:type)
end
end
end
76 changes: 76 additions & 0 deletions test/section_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#!/usr/bin/env ruby

require File.expand_path(File.join(File.dirname(__FILE__), "test_helper"))

describe BinData::Section do
it "transforms data byte at a time" do
class XorTransform < BinData::IO::Transform
def initialize(xor)
super()
@xor = xor
end

def read(n)
chain_read(n).bytes.map { |byte| (byte ^ @xor).chr }.join
end

def write(data)
chain_write(data.bytes.map { |byte| (byte ^ @xor).chr }.join)
end
end

obj = BinData::Section.new(transform: -> { XorTransform.new(0xff) },
type: [:string, read_length: 5])

_(obj.read("\x97\x9A\x93\x93\x90")).must_equal "hello"
end

it "expands data" do
class ZlibTransform < BinData::IO::Transform
require 'zlib'

transform_changes_stream_length!

def initialize(read_length)
super()
@length = read_length
end

def read(n)
@read ||= Zlib::Inflate.inflate(chain_read(@length))
@read.slice!(0...n)
end

def write(data)
@write ||= create_empty_binary_string
@write << data
end

def after_read_transform
raise IOError, "didn't read all data" unless @read.empty?
end

def after_write_transform
chain_write(Zlib::Deflate.deflate(@write))
end
end

class ZlibRecord < BinData::Record
int32le :len, value: -> { s.num_bytes }
section :s, transform: -> { ZlibTransform.new(len) } do
int32le :str_len, value: -> { str.length }
string :str, read_length: :str_len

end
end

obj = ZlibRecord.new
data = "highly compressable" * 100
obj.s.str = data
_(obj.len).must_be :<, (data.length / 10)

str = obj.to_binary_s
obj = ZlibRecord.read(str)
_(obj.s.str).must_equal data
end
end

0 comments on commit b540ae6

Please sign in to comment.