Skip to content

Commit

Permalink
Added transforms
Browse files Browse the repository at this point in the history
  • Loading branch information
dmendel committed Jun 26, 2023
1 parent b540ae6 commit 5d4f2ac
Show file tree
Hide file tree
Showing 9 changed files with 299 additions and 36 deletions.
1 change: 1 addition & 0 deletions ChangeLog.rdoc
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
* Allow for nested tracing.
* Skip :until_valid is now fast for :asserted_value.
* Added Section - a way to transform the data stream.
* Added transforms for brotli, lz4, xor, zlib, zstd.

== Version 2.4.15 (2023-02-07)

Expand Down
35 changes: 35 additions & 0 deletions lib/bindata/transform/brotli.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
require 'brotli'

module BinData
module Transform
# Transforms a brotli compressed data stream.
#
# gem install brotli
class Brotli < BinData::IO::Transform
transform_changes_stream_length!

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

def read(n)
@read ||= ::Brotli::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(::Brotli::deflate(@write))
end
end
end
end
35 changes: 35 additions & 0 deletions lib/bindata/transform/lz4.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
require 'extlz4'

module BinData
module Transform
# Transforms a LZ4 compressed data stream.
#
# gem install extlz4
class LZ4 < BinData::IO::Transform
transform_changes_stream_length!

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

def read(n)
@read ||= ::LZ4::decode(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(::LZ4::encode(@write))
end
end
end
end
35 changes: 35 additions & 0 deletions lib/bindata/transform/lzma.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
require 'xz'

module BinData
module Transform
# Transforms a lzma compressed data stream.
#
# gem install ruby-xz
class Lzma < BinData::IO::Transform
transform_changes_stream_length!

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

def read(n)
@read ||= ::XZ::decompress(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(::XZ::compress(@write))
end
end
end
end
19 changes: 19 additions & 0 deletions lib/bindata/transform/xor.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
module BinData
module Transform
# Transforms the data stream by xoring each byte.
class Xor < 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
end
end
35 changes: 35 additions & 0 deletions lib/bindata/transform/xz.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
require 'xz'

module BinData
module Transform
# Transforms a xz compressed data stream.
#
# gem install ruby-xz
class XZ < BinData::IO::Transform
transform_changes_stream_length!

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

def read(n)
@read ||= ::XZ::decompress(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(::XZ::compress(@write))
end
end
end
end
33 changes: 33 additions & 0 deletions lib/bindata/transform/zlib.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
require 'zlib'

module BinData
module Transform
# Transforms a zlib compressed data stream.
class Zlib < BinData::IO::Transform
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
end
end
35 changes: 35 additions & 0 deletions lib/bindata/transform/zstd.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
require 'zstd-ruby'

module BinData
module Transform
# Transforms a zstd compressed data stream.
#
# gem install zstd-ruby
class Zstd < BinData::IO::Transform
transform_changes_stream_length!

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

def read(n)
@read ||= ::Zstd::decompress(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(::Zstd::compress(@write))
end
end
end
end
107 changes: 71 additions & 36 deletions test/section_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,60 +4,70 @@

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
require 'bindata/transform/xor'

obj = BinData::Section.new(transform: -> { XorTransform.new(0xff) },
obj = BinData::Section.new(transform: -> { BinData::Transform::Xor.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'
begin
require 'brotli'
it "transform brotli" do
require 'bindata/transform/brotli'

transform_changes_stream_length!
class BrotliRecord < BinData::Record
int32le :len, value: -> { s.num_bytes }
section :s, transform: -> { BinData::Transform::Brotli.new(len) } do
int32le :str_len, value: -> { str.length }
string :str, read_length: :str_len

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

def read(n)
@read ||= Zlib::Inflate.inflate(chain_read(@length))
@read.slice!(0...n)
end
obj = BrotliRecord.new
data = "highly compressable" * 100
obj.s.str = data
_(obj.len).must_be :<, (data.length / 10)

def write(data)
@write ||= create_empty_binary_string
@write << data
end
str = obj.to_binary_s
obj = BrotliRecord.read(str)
_(obj.s.str).must_equal data
end
rescue LoadError; end

def after_read_transform
raise IOError, "didn't read all data" unless @read.empty?
end
begin
require 'extlz4'
it "transform lz4" do
require 'bindata/transform/lz4'

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

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

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

str = obj.to_binary_s
obj = LZ4Record.read(str)
_(obj.s.str).must_equal data
end
rescue LoadError; end

it "transform zlib" do
require 'bindata/transform/zlib'

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

Expand All @@ -73,4 +83,29 @@ class ZlibRecord < BinData::Record
obj = ZlibRecord.read(str)
_(obj.s.str).must_equal data
end

begin
require 'zstd-ruby'
it "transform zstd" do
require 'bindata/transform/zstd'

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

end
end

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

str = obj.to_binary_s
obj = ZstdRecord.read(str)
_(obj.s.str).must_equal data
end
rescue LoadError; end
end

0 comments on commit 5d4f2ac

Please sign in to comment.