compress-bsc 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +2 -0
- data/Gemfile +8 -0
- data/LICENSE +192 -0
- data/README.md +279 -0
- data/Rakefile +96 -0
- data/bin/rbsc +306 -0
- data/certs/djberg96_pub.pem +26 -0
- data/compress-bsc.gemspec +45 -0
- data/coverage/assets/0.13.2/DataTables-1.10.20/images/sort_asc.png +0 -0
- data/coverage/assets/0.13.2/DataTables-1.10.20/images/sort_asc_disabled.png +0 -0
- data/coverage/assets/0.13.2/DataTables-1.10.20/images/sort_both.png +0 -0
- data/coverage/assets/0.13.2/DataTables-1.10.20/images/sort_desc.png +0 -0
- data/coverage/assets/0.13.2/DataTables-1.10.20/images/sort_desc_disabled.png +0 -0
- data/coverage/assets/0.13.2/application.css +1 -0
- data/coverage/assets/0.13.2/application.js +7 -0
- data/coverage/assets/0.13.2/colorbox/border.png +0 -0
- data/coverage/assets/0.13.2/colorbox/controls.png +0 -0
- data/coverage/assets/0.13.2/colorbox/loading.gif +0 -0
- data/coverage/assets/0.13.2/colorbox/loading_background.png +0 -0
- data/coverage/assets/0.13.2/favicon_green.png +0 -0
- data/coverage/assets/0.13.2/favicon_red.png +0 -0
- data/coverage/assets/0.13.2/favicon_yellow.png +0 -0
- data/coverage/assets/0.13.2/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
- data/coverage/assets/0.13.2/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
- data/coverage/assets/0.13.2/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
- data/coverage/assets/0.13.2/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
- data/coverage/assets/0.13.2/images/ui-bg_glass_75_dadada_1x400.png +0 -0
- data/coverage/assets/0.13.2/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
- data/coverage/assets/0.13.2/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
- data/coverage/assets/0.13.2/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
- data/coverage/assets/0.13.2/images/ui-icons_222222_256x240.png +0 -0
- data/coverage/assets/0.13.2/images/ui-icons_2e83ff_256x240.png +0 -0
- data/coverage/assets/0.13.2/images/ui-icons_454545_256x240.png +0 -0
- data/coverage/assets/0.13.2/images/ui-icons_888888_256x240.png +0 -0
- data/coverage/assets/0.13.2/images/ui-icons_cd0a0a_256x240.png +0 -0
- data/coverage/assets/0.13.2/loading.gif +0 -0
- data/coverage/assets/0.13.2/magnify.png +0 -0
- data/coverage/index.html +4779 -0
- data/examples/usage_example.rb +215 -0
- data/lib/compress/bsc/compressor.rb +81 -0
- data/lib/compress/bsc/decompressor.rb +159 -0
- data/lib/compress/bsc/error.rb +18 -0
- data/lib/compress/bsc/library.rb +100 -0
- data/lib/compress/bsc/version.rb +5 -0
- data/lib/compress/bsc.rb +26 -0
- data/lib/compress-bsc.rb +5 -0
- data/spec/compressor_spec.rb +124 -0
- data/spec/decompressor_spec.rb +135 -0
- data/spec/error_spec.rb +63 -0
- data/spec/examples.txt +60 -0
- data/spec/ffi_bsc_spec.rb +101 -0
- data/spec/library_spec.rb +97 -0
- data/spec/spec_helper.rb +53 -0
- data.tar.gz.sig +0 -0
- metadata +232 -0
- metadata.gz.sig +0 -0
@@ -0,0 +1,215 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require_relative '../lib/compress/bsc'
|
4
|
+
|
5
|
+
# Example usage of compress-bsc
|
6
|
+
def main
|
7
|
+
puts "compress-bsc Example Usage"
|
8
|
+
puts "=" * 40
|
9
|
+
|
10
|
+
begin
|
11
|
+
# Initialize the BSC library
|
12
|
+
puts "Initializing BSC library..."
|
13
|
+
bsc = Compress::BSC.new
|
14
|
+
puts "✓ BSC library initialized successfully"
|
15
|
+
|
16
|
+
# Initialize with custom features
|
17
|
+
puts "\nInitializing BSC library with custom features..."
|
18
|
+
bsc_custom = Compress::BSC.new(features: Compress::BSC::Library::LIBBSC_FEATURE_FASTMODE)
|
19
|
+
puts "✓ BSC library with custom features initialized successfully"
|
20
|
+
|
21
|
+
# Initialize with multiple features
|
22
|
+
puts "\nInitializing BSC library with multiple features..."
|
23
|
+
bsc_multi = Compress::BSC.new(
|
24
|
+
features: Compress::BSC::Library::LIBBSC_FEATURE_FASTMODE |
|
25
|
+
Compress::BSC::Library::LIBBSC_FEATURE_MULTITHREADING |
|
26
|
+
Compress::BSC::Library::LIBBSC_FEATURE_LARGEPAGES
|
27
|
+
)
|
28
|
+
puts "✓ BSC library with multiple features initialized successfully"
|
29
|
+
|
30
|
+
# Test data
|
31
|
+
original_data = generate_test_data
|
32
|
+
puts "\nOriginal data size: #{original_data.bytesize} bytes"
|
33
|
+
|
34
|
+
# Basic compression/decompression
|
35
|
+
puts "\n1. Basic Compression Test"
|
36
|
+
puts "-" * 30
|
37
|
+
|
38
|
+
compressed = bsc.compress(original_data)
|
39
|
+
puts "Compressed size: #{compressed.bytesize} bytes"
|
40
|
+
|
41
|
+
ratio = original_data.bytesize.to_f / compressed.bytesize
|
42
|
+
puts "Compression ratio: #{ratio.round(2)}:1"
|
43
|
+
|
44
|
+
decompressed = bsc.decompress(compressed)
|
45
|
+
puts "Decompression successful: #{original_data == decompressed}"
|
46
|
+
|
47
|
+
# Advanced compression with custom settings
|
48
|
+
puts "\n2. Advanced Compression Test"
|
49
|
+
puts "-" * 30
|
50
|
+
|
51
|
+
compressor = Compress::BSC::Compressor.new(
|
52
|
+
block_sorter: Compress::BSC::Library::LIBBSC_BLOCKSORTER_BWT,
|
53
|
+
coder: Compress::BSC::Library::LIBBSC_CODER_QLFC_ADAPTIVE,
|
54
|
+
features: Compress::BSC::Library::LIBBSC_FEATURE_FASTMODE |
|
55
|
+
Compress::BSC::Library::LIBBSC_FEATURE_MULTITHREADING
|
56
|
+
)
|
57
|
+
|
58
|
+
compressed_advanced = compressor.compress(original_data)
|
59
|
+
puts "Advanced compressed size: #{compressed_advanced.bytesize} bytes"
|
60
|
+
|
61
|
+
ratio_advanced = original_data.bytesize.to_f / compressed_advanced.bytesize
|
62
|
+
puts "Advanced compression ratio: #{ratio_advanced.round(2)}:1"
|
63
|
+
|
64
|
+
decompressor = Compress::BSC::Decompressor.new
|
65
|
+
decompressed_advanced = decompressor.decompress(compressed_advanced)
|
66
|
+
puts "Advanced decompression successful: #{original_data == decompressed_advanced}"
|
67
|
+
|
68
|
+
# LZP preprocessing test
|
69
|
+
puts "\n3. LZP Preprocessing Test"
|
70
|
+
puts "-" * 30
|
71
|
+
|
72
|
+
lzp_compressor = Compress::BSC::Compressor.new(
|
73
|
+
lzp_hash_size: Compress::BSC::Library::LIBBSC_DEFAULT_LZPHASHSIZE,
|
74
|
+
lzp_min_len: Compress::BSC::Library::LIBBSC_DEFAULT_LZPMINLEN,
|
75
|
+
block_sorter: Compress::BSC::Library::LIBBSC_BLOCKSORTER_BWT,
|
76
|
+
coder: Compress::BSC::Library::LIBBSC_CODER_QLFC_STATIC
|
77
|
+
)
|
78
|
+
|
79
|
+
compressed_lzp = lzp_compressor.compress(original_data)
|
80
|
+
puts "LZP compressed size: #{compressed_lzp.bytesize} bytes"
|
81
|
+
|
82
|
+
ratio_lzp = original_data.bytesize.to_f / compressed_lzp.bytesize
|
83
|
+
puts "LZP compression ratio: #{ratio_lzp.round(2)}:1"
|
84
|
+
|
85
|
+
decompressed_lzp = decompressor.decompress(compressed_lzp)
|
86
|
+
puts "LZP decompression successful: #{original_data == decompressed_lzp}"
|
87
|
+
|
88
|
+
# Block info test
|
89
|
+
puts "\n4. Block Information Test"
|
90
|
+
puts "-" * 30
|
91
|
+
|
92
|
+
info = Compress::BSC::Decompressor.block_info(compressed)
|
93
|
+
puts "Block size: #{info[:block_size]} bytes"
|
94
|
+
puts "Data size: #{info[:data_size]} bytes"
|
95
|
+
puts "Header overhead: #{info[:block_size] - info[:data_size]} bytes"
|
96
|
+
|
97
|
+
# File compression test
|
98
|
+
puts "\n5. File Compression Test"
|
99
|
+
puts "-" * 30
|
100
|
+
|
101
|
+
test_file_compression
|
102
|
+
|
103
|
+
# Performance comparison
|
104
|
+
puts "\n6. Performance Comparison"
|
105
|
+
puts "-" * 30
|
106
|
+
|
107
|
+
performance_test(original_data)
|
108
|
+
|
109
|
+
puts "\n✓ All tests completed successfully!"
|
110
|
+
|
111
|
+
rescue Compress::BSC::Error => e
|
112
|
+
puts "❌ BSC Error: #{e.error_name} (#{e.code})"
|
113
|
+
puts e.message
|
114
|
+
rescue LoadError => e
|
115
|
+
puts "❌ Library not found: #{e.message}"
|
116
|
+
puts "Please install libbsc library"
|
117
|
+
rescue => e
|
118
|
+
puts "❌ Unexpected error: #{e.message}"
|
119
|
+
puts e.backtrace.first(3)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def generate_test_data
|
124
|
+
# Generate mixed test data for better compression testing
|
125
|
+
text_data = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " * 200
|
126
|
+
repetitive_data = "ABCDEFGH" * 500
|
127
|
+
random_data = Random.bytes(1000)
|
128
|
+
binary_data = (0..255).to_a.pack('C*') * 10
|
129
|
+
|
130
|
+
text_data + repetitive_data + random_data + binary_data
|
131
|
+
end
|
132
|
+
|
133
|
+
def test_file_compression
|
134
|
+
test_content = generate_test_data
|
135
|
+
input_file = 'example_input.txt'
|
136
|
+
compressed_file = 'example_output.bsc'
|
137
|
+
decompressed_file = 'example_restored.txt'
|
138
|
+
|
139
|
+
begin
|
140
|
+
# Write test file
|
141
|
+
File.binwrite(input_file, test_content)
|
142
|
+
|
143
|
+
# Compress file
|
144
|
+
compressor = Compress::BSC::Compressor.new
|
145
|
+
compressed_size = compressor.compress_file(input_file, compressed_file)
|
146
|
+
puts "File compressed: #{File.size(input_file)} → #{compressed_size} bytes"
|
147
|
+
|
148
|
+
# Decompress file
|
149
|
+
decompressor = Compress::BSC::Decompressor.new
|
150
|
+
decompressed_size = decompressor.decompress_file(compressed_file, decompressed_file)
|
151
|
+
puts "File decompressed: #{compressed_size} → #{decompressed_size} bytes"
|
152
|
+
|
153
|
+
# Verify integrity
|
154
|
+
original_content = File.binread(input_file)
|
155
|
+
restored_content = File.binread(decompressed_file)
|
156
|
+
puts "File integrity verified: #{original_content == restored_content}"
|
157
|
+
|
158
|
+
ensure
|
159
|
+
# Clean up
|
160
|
+
[input_file, compressed_file, decompressed_file].each do |file|
|
161
|
+
File.delete(file) if File.exist?(file)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def performance_test(data)
|
167
|
+
require 'benchmark'
|
168
|
+
|
169
|
+
puts "Testing with #{data.bytesize} bytes of data..."
|
170
|
+
|
171
|
+
results = Benchmark.bm(20) do |x|
|
172
|
+
compressed_basic = nil
|
173
|
+
compressed_adaptive = nil
|
174
|
+
compressed_fast = nil
|
175
|
+
compressed_lzp = nil
|
176
|
+
|
177
|
+
x.report("Basic compression:") do
|
178
|
+
compressor = Compress::BSC::Compressor.new
|
179
|
+
compressed_basic = compressor.compress(data)
|
180
|
+
end
|
181
|
+
|
182
|
+
x.report("Adaptive coder:") do
|
183
|
+
compressor = Compress::BSC::Compressor.new(
|
184
|
+
coder: Compress::BSC::Library::LIBBSC_CODER_QLFC_ADAPTIVE
|
185
|
+
)
|
186
|
+
compressed_adaptive = compressor.compress(data)
|
187
|
+
end
|
188
|
+
|
189
|
+
x.report("Fast coder:") do
|
190
|
+
compressor = Compress::BSC::Compressor.new(
|
191
|
+
coder: Compress::BSC::Library::LIBBSC_CODER_QLFC_FAST
|
192
|
+
)
|
193
|
+
compressed_fast = compressor.compress(data)
|
194
|
+
end
|
195
|
+
|
196
|
+
x.report("With LZP:") do
|
197
|
+
compressor = Compress::BSC::Compressor.new(
|
198
|
+
lzp_hash_size: 15,
|
199
|
+
lzp_min_len: 128
|
200
|
+
)
|
201
|
+
compressed_lzp = compressor.compress(data)
|
202
|
+
end
|
203
|
+
|
204
|
+
# Show compression ratios
|
205
|
+
puts "\nCompression Ratios:"
|
206
|
+
puts " Basic: #{(data.bytesize.to_f / compressed_basic.bytesize).round(2)}:1" if compressed_basic
|
207
|
+
puts " Adaptive: #{(data.bytesize.to_f / compressed_adaptive.bytesize).round(2)}:1" if compressed_adaptive
|
208
|
+
puts " Fast: #{(data.bytesize.to_f / compressed_fast.bytesize).round(2)}:1" if compressed_fast
|
209
|
+
puts " LZP: #{(data.bytesize.to_f / compressed_lzp.bytesize).round(2)}:1" if compressed_lzp
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
if __FILE__ == $0
|
214
|
+
main
|
215
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module Compress
|
2
|
+
class BSC
|
3
|
+
class Compressor
|
4
|
+
attr_reader :lzp_hash_size, :lzp_min_len, :block_sorter, :coder, :features
|
5
|
+
|
6
|
+
def initialize(options = {})
|
7
|
+
@lzp_hash_size = options[:lzp_hash_size] || 0 # Disable LZP by default
|
8
|
+
@lzp_min_len = options[:lzp_min_len] || 0 # Disable LZP by default
|
9
|
+
@block_sorter = options[:block_sorter] || Library::LIBBSC_DEFAULT_BLOCKSORTER
|
10
|
+
@coder = options[:coder] || Library::LIBBSC_DEFAULT_CODER
|
11
|
+
@features = options[:features] || Library::LIBBSC_DEFAULT_FEATURES
|
12
|
+
end
|
13
|
+
|
14
|
+
def compress(input_data)
|
15
|
+
raise ArgumentError, "Input data cannot be nil" if input_data.nil?
|
16
|
+
raise ArgumentError, "Input data must be a string" unless input_data.is_a?(String)
|
17
|
+
|
18
|
+
return input_data if input_data.empty?
|
19
|
+
|
20
|
+
# Store original encoding for later restoration
|
21
|
+
original_encoding = input_data.encoding
|
22
|
+
|
23
|
+
# Convert to binary for compression (make a copy first!)
|
24
|
+
binary_data = input_data.dup.force_encoding(Encoding::BINARY)
|
25
|
+
input_size = binary_data.bytesize
|
26
|
+
|
27
|
+
# Calculate maximum possible output size (worst case)
|
28
|
+
# BSC adds a header, so we need at least input_size + HEADER_SIZE
|
29
|
+
max_output_size = input_size + Library::LIBBSC_HEADER_SIZE + 1024 # Add some buffer
|
30
|
+
|
31
|
+
# Allocate input and output buffers
|
32
|
+
input_ptr = FFI::MemoryPointer.new(:char, input_size)
|
33
|
+
input_ptr.put_bytes(0, binary_data)
|
34
|
+
|
35
|
+
output_ptr = FFI::MemoryPointer.new(:char, max_output_size)
|
36
|
+
|
37
|
+
begin
|
38
|
+
# Perform compression
|
39
|
+
result = Library.bsc_compress(
|
40
|
+
input_ptr,
|
41
|
+
output_ptr,
|
42
|
+
input_size,
|
43
|
+
@lzp_hash_size,
|
44
|
+
@lzp_min_len,
|
45
|
+
@block_sorter,
|
46
|
+
@coder,
|
47
|
+
@features
|
48
|
+
)
|
49
|
+
|
50
|
+
if result == Library::LIBBSC_NOT_COMPRESSIBLE
|
51
|
+
# Return original data if not compressible
|
52
|
+
return input_data
|
53
|
+
end
|
54
|
+
|
55
|
+
Error.check_result(result)
|
56
|
+
|
57
|
+
# Extract compressed data and add encoding marker
|
58
|
+
compressed_data = output_ptr.get_bytes(0, result)
|
59
|
+
|
60
|
+
# Store original encoding in the compressed data metadata
|
61
|
+
# We'll use a simple approach: prepend encoding name as a header
|
62
|
+
encoding_name = original_encoding.name
|
63
|
+
encoding_header = [encoding_name.bytesize, encoding_name].pack("Ca*")
|
64
|
+
|
65
|
+
# Return with encoding header
|
66
|
+
(encoding_header + compressed_data).force_encoding(Encoding::BINARY)
|
67
|
+
ensure
|
68
|
+
input_ptr.free if input_ptr
|
69
|
+
output_ptr.free if output_ptr
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def compress_file(input_path, output_path)
|
74
|
+
input_data = File.binread(input_path)
|
75
|
+
compressed_data = compress(input_data)
|
76
|
+
File.binwrite(output_path, compressed_data)
|
77
|
+
compressed_data.bytesize
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
module Compress
|
2
|
+
class BSC
|
3
|
+
class Decompressor
|
4
|
+
attr_reader :features
|
5
|
+
|
6
|
+
def initialize(options = {})
|
7
|
+
@features = options[:features] || Library::LIBBSC_DEFAULT_FEATURES
|
8
|
+
end
|
9
|
+
|
10
|
+
def decompress(compressed_data)
|
11
|
+
raise ArgumentError, "Compressed data cannot be nil" if compressed_data.nil?
|
12
|
+
raise ArgumentError, "Compressed data must be a string" unless compressed_data.is_a?(String)
|
13
|
+
|
14
|
+
return compressed_data if compressed_data.empty?
|
15
|
+
|
16
|
+
# Extract encoding information from header
|
17
|
+
encoding_name = nil
|
18
|
+
actual_compressed_data = compressed_data
|
19
|
+
|
20
|
+
if compressed_data.bytesize > 1
|
21
|
+
# Try to extract encoding header
|
22
|
+
encoding_name_length = compressed_data.bytes[0]
|
23
|
+
if encoding_name_length > 0 && encoding_name_length < 50 && compressed_data.bytesize > encoding_name_length + 1
|
24
|
+
encoding_name = compressed_data[1, encoding_name_length]
|
25
|
+
actual_compressed_data = compressed_data[(encoding_name_length + 1)..-1]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
input_size = actual_compressed_data.bytesize
|
30
|
+
|
31
|
+
# Need at least header size for the BSC data
|
32
|
+
if input_size < Library::LIBBSC_HEADER_SIZE
|
33
|
+
raise Error.new(Library::LIBBSC_DATA_CORRUPT)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Allocate input buffer
|
37
|
+
input_ptr = FFI::MemoryPointer.new(:char, input_size)
|
38
|
+
input_ptr.put_bytes(0, actual_compressed_data)
|
39
|
+
|
40
|
+
# Get block info to determine output size
|
41
|
+
block_size_ptr = FFI::MemoryPointer.new(:int)
|
42
|
+
data_size_ptr = FFI::MemoryPointer.new(:int)
|
43
|
+
|
44
|
+
result = Library.bsc_block_info(
|
45
|
+
input_ptr,
|
46
|
+
input_size,
|
47
|
+
block_size_ptr,
|
48
|
+
data_size_ptr,
|
49
|
+
@features
|
50
|
+
)
|
51
|
+
|
52
|
+
Error.check_result(result)
|
53
|
+
|
54
|
+
block_size = block_size_ptr.read_int
|
55
|
+
data_size = data_size_ptr.read_int
|
56
|
+
|
57
|
+
# Validate sizes
|
58
|
+
if input_size < block_size || data_size <= 0
|
59
|
+
raise Error.new(Library::LIBBSC_DATA_CORRUPT)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Allocate output buffer
|
63
|
+
output_ptr = FFI::MemoryPointer.new(:char, data_size)
|
64
|
+
|
65
|
+
begin
|
66
|
+
# Perform decompression
|
67
|
+
result = Library.bsc_decompress(
|
68
|
+
input_ptr,
|
69
|
+
input_size,
|
70
|
+
output_ptr,
|
71
|
+
data_size,
|
72
|
+
@features
|
73
|
+
)
|
74
|
+
|
75
|
+
Error.check_result(result)
|
76
|
+
|
77
|
+
# Extract decompressed data
|
78
|
+
decompressed_data = output_ptr.get_bytes(0, data_size)
|
79
|
+
|
80
|
+
# Restore original encoding if available
|
81
|
+
if encoding_name && !encoding_name.empty?
|
82
|
+
begin
|
83
|
+
target_encoding = Encoding.find(encoding_name)
|
84
|
+
decompressed_data.force_encoding(target_encoding)
|
85
|
+
rescue ArgumentError
|
86
|
+
# If encoding is not found, keep as binary
|
87
|
+
decompressed_data.force_encoding(Encoding::BINARY)
|
88
|
+
end
|
89
|
+
else
|
90
|
+
decompressed_data.force_encoding(Encoding::BINARY)
|
91
|
+
end
|
92
|
+
|
93
|
+
decompressed_data
|
94
|
+
ensure
|
95
|
+
input_ptr.free if input_ptr
|
96
|
+
output_ptr.free if output_ptr
|
97
|
+
block_size_ptr.free if block_size_ptr
|
98
|
+
data_size_ptr.free if data_size_ptr
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def decompress_file(input_path, output_path)
|
103
|
+
compressed_data = File.binread(input_path)
|
104
|
+
decompressed_data = decompress(compressed_data)
|
105
|
+
File.binwrite(output_path, decompressed_data)
|
106
|
+
decompressed_data.bytesize
|
107
|
+
end
|
108
|
+
|
109
|
+
def self.block_info(compressed_data, features = Library::LIBBSC_DEFAULT_FEATURES)
|
110
|
+
raise ArgumentError, "Compressed data cannot be nil" if compressed_data.nil?
|
111
|
+
raise ArgumentError, "Compressed data must be a string" unless compressed_data.is_a?(String)
|
112
|
+
|
113
|
+
# Extract actual compressed data, skipping encoding header if present
|
114
|
+
actual_compressed_data = compressed_data
|
115
|
+
|
116
|
+
if compressed_data.bytesize > 1
|
117
|
+
# Try to extract encoding header
|
118
|
+
encoding_name_length = compressed_data.bytes[0]
|
119
|
+
if encoding_name_length > 0 && encoding_name_length < 50 && compressed_data.bytesize > encoding_name_length + 1
|
120
|
+
actual_compressed_data = compressed_data[(encoding_name_length + 1)..-1]
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
input_size = actual_compressed_data.bytesize
|
125
|
+
|
126
|
+
if input_size < Library::LIBBSC_HEADER_SIZE
|
127
|
+
raise Error.new(Library::LIBBSC_DATA_CORRUPT)
|
128
|
+
end
|
129
|
+
|
130
|
+
input_ptr = FFI::MemoryPointer.new(:char, input_size)
|
131
|
+
input_ptr.put_bytes(0, actual_compressed_data)
|
132
|
+
|
133
|
+
block_size_ptr = FFI::MemoryPointer.new(:int)
|
134
|
+
data_size_ptr = FFI::MemoryPointer.new(:int)
|
135
|
+
|
136
|
+
begin
|
137
|
+
result = Library.bsc_block_info(
|
138
|
+
input_ptr,
|
139
|
+
input_size,
|
140
|
+
block_size_ptr,
|
141
|
+
data_size_ptr,
|
142
|
+
features
|
143
|
+
)
|
144
|
+
|
145
|
+
Error.check_result(result)
|
146
|
+
|
147
|
+
{
|
148
|
+
block_size: block_size_ptr.read_int,
|
149
|
+
data_size: data_size_ptr.read_int
|
150
|
+
}
|
151
|
+
ensure
|
152
|
+
input_ptr.free if input_ptr
|
153
|
+
block_size_ptr.free if block_size_ptr
|
154
|
+
data_size_ptr.free if data_size_ptr
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Compress
|
2
|
+
class BSC
|
3
|
+
class Error < StandardError
|
4
|
+
attr_reader :code, :error_name
|
5
|
+
|
6
|
+
def initialize(code)
|
7
|
+
@code = code
|
8
|
+
@error_name = Library.error_name(code)
|
9
|
+
super("BSC Error: #{@error_name} (#{@code})")
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.check_result(result)
|
13
|
+
raise Error.new(result) if result < Library::LIBBSC_NO_ERROR
|
14
|
+
result
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'ffi'
|
2
|
+
|
3
|
+
module Compress
|
4
|
+
class BSC
|
5
|
+
module Library
|
6
|
+
extend FFI::Library
|
7
|
+
|
8
|
+
begin
|
9
|
+
ffi_lib 'bsc'
|
10
|
+
rescue LoadError
|
11
|
+
ffi_lib '/usr/local/lib/libbsc.so'
|
12
|
+
rescue LoadError
|
13
|
+
ffi_lib 'libbsc'
|
14
|
+
end
|
15
|
+
|
16
|
+
# Version constants
|
17
|
+
LIBBSC_VERSION_MAJOR = 3
|
18
|
+
LIBBSC_VERSION_MINOR = 3
|
19
|
+
LIBBSC_VERSION_PATCH = 9
|
20
|
+
LIBBSC_VERSION_STRING = "3.3.9"
|
21
|
+
|
22
|
+
# Error codes
|
23
|
+
LIBBSC_NO_ERROR = 0
|
24
|
+
LIBBSC_BAD_PARAMETER = -1
|
25
|
+
LIBBSC_NOT_ENOUGH_MEMORY = -2
|
26
|
+
LIBBSC_NOT_COMPRESSIBLE = -3
|
27
|
+
LIBBSC_NOT_SUPPORTED = -4
|
28
|
+
LIBBSC_UNEXPECTED_EOB = -5
|
29
|
+
LIBBSC_DATA_CORRUPT = -6
|
30
|
+
LIBBSC_GPU_ERROR = -7
|
31
|
+
LIBBSC_GPU_NOT_SUPPORTED = -8
|
32
|
+
LIBBSC_GPU_NOT_ENOUGH_MEMORY = -9
|
33
|
+
|
34
|
+
# Block sorter constants
|
35
|
+
LIBBSC_BLOCKSORTER_NONE = 0
|
36
|
+
LIBBSC_BLOCKSORTER_BWT = 1
|
37
|
+
LIBBSC_BLOCKSORTER_ST3 = 3
|
38
|
+
LIBBSC_BLOCKSORTER_ST4 = 4
|
39
|
+
LIBBSC_BLOCKSORTER_ST5 = 5
|
40
|
+
LIBBSC_BLOCKSORTER_ST6 = 6
|
41
|
+
LIBBSC_BLOCKSORTER_ST7 = 7
|
42
|
+
LIBBSC_BLOCKSORTER_ST8 = 8
|
43
|
+
|
44
|
+
# Coder constants
|
45
|
+
LIBBSC_CODER_NONE = 0
|
46
|
+
LIBBSC_CODER_QLFC_STATIC = 1
|
47
|
+
LIBBSC_CODER_QLFC_ADAPTIVE = 2
|
48
|
+
LIBBSC_CODER_QLFC_FAST = 3
|
49
|
+
|
50
|
+
# Feature constants
|
51
|
+
LIBBSC_FEATURE_NONE = 0
|
52
|
+
LIBBSC_FEATURE_FASTMODE = 1
|
53
|
+
LIBBSC_FEATURE_MULTITHREADING = 2
|
54
|
+
LIBBSC_FEATURE_LARGEPAGES = 4
|
55
|
+
LIBBSC_FEATURE_CUDA = 8
|
56
|
+
|
57
|
+
# Default values
|
58
|
+
LIBBSC_DEFAULT_LZPHASHSIZE = 15
|
59
|
+
LIBBSC_DEFAULT_LZPMINLEN = 128
|
60
|
+
LIBBSC_DEFAULT_BLOCKSORTER = LIBBSC_BLOCKSORTER_BWT
|
61
|
+
LIBBSC_DEFAULT_CODER = LIBBSC_CODER_QLFC_STATIC
|
62
|
+
LIBBSC_DEFAULT_FEATURES = LIBBSC_FEATURE_FASTMODE | LIBBSC_FEATURE_MULTITHREADING
|
63
|
+
|
64
|
+
# Header size
|
65
|
+
LIBBSC_HEADER_SIZE = 28
|
66
|
+
|
67
|
+
# Function bindings
|
68
|
+
attach_function :bsc_init, [:int], :int
|
69
|
+
attach_function :bsc_init_full, [:int, :pointer, :pointer, :pointer], :int
|
70
|
+
|
71
|
+
attach_function :bsc_compress, [:pointer, :pointer, :int, :int, :int, :int, :int, :int], :int
|
72
|
+
attach_function :bsc_decompress, [:pointer, :int, :pointer, :int, :int], :int
|
73
|
+
|
74
|
+
attach_function :bsc_block_info, [:pointer, :int, :pointer, :pointer, :int], :int
|
75
|
+
|
76
|
+
# Platform functions
|
77
|
+
attach_function :bsc_malloc, [:size_t], :pointer
|
78
|
+
attach_function :bsc_zero_malloc, [:size_t], :pointer
|
79
|
+
attach_function :bsc_free, [:pointer], :void
|
80
|
+
|
81
|
+
# Error code names for debugging
|
82
|
+
ERROR_NAMES = {
|
83
|
+
LIBBSC_NO_ERROR => 'LIBBSC_NO_ERROR',
|
84
|
+
LIBBSC_BAD_PARAMETER => 'LIBBSC_BAD_PARAMETER',
|
85
|
+
LIBBSC_NOT_ENOUGH_MEMORY => 'LIBBSC_NOT_ENOUGH_MEMORY',
|
86
|
+
LIBBSC_NOT_COMPRESSIBLE => 'LIBBSC_NOT_COMPRESSIBLE',
|
87
|
+
LIBBSC_NOT_SUPPORTED => 'LIBBSC_NOT_SUPPORTED',
|
88
|
+
LIBBSC_UNEXPECTED_EOB => 'LIBBSC_UNEXPECTED_EOB',
|
89
|
+
LIBBSC_DATA_CORRUPT => 'LIBBSC_DATA_CORRUPT',
|
90
|
+
LIBBSC_GPU_ERROR => 'LIBBSC_GPU_ERROR',
|
91
|
+
LIBBSC_GPU_NOT_SUPPORTED => 'LIBBSC_GPU_NOT_SUPPORTED',
|
92
|
+
LIBBSC_GPU_NOT_ENOUGH_MEMORY => 'LIBBSC_GPU_NOT_ENOUGH_MEMORY'
|
93
|
+
}.freeze
|
94
|
+
|
95
|
+
def self.error_name(code)
|
96
|
+
ERROR_NAMES[code] || "UNKNOWN_ERROR(#{code})"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
data/lib/compress/bsc.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'ffi'
|
2
|
+
require_relative 'bsc/version'
|
3
|
+
require_relative 'bsc/library'
|
4
|
+
require_relative 'bsc/compressor'
|
5
|
+
require_relative 'bsc/decompressor'
|
6
|
+
require_relative 'bsc/error'
|
7
|
+
|
8
|
+
module Compress
|
9
|
+
class BSC
|
10
|
+
# Initialize the BSC library with configurable features
|
11
|
+
def initialize(features: Library::LIBBSC_DEFAULT_FEATURES)
|
12
|
+
result = Library.bsc_init(features)
|
13
|
+
raise Error.new(result) unless result == Library::LIBBSC_NO_ERROR
|
14
|
+
end
|
15
|
+
|
16
|
+
# Simple compression interface
|
17
|
+
def compress(data, options = {})
|
18
|
+
Compressor.new(options).compress(data)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Simple decompression interface
|
22
|
+
def decompress(data, options = {})
|
23
|
+
Decompressor.new(options).decompress(data)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|