oneliner 0.2.6
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.
- data/GPL-2 +339 -0
- data/README +37 -0
- data/ext/extconf.rb +33 -0
- data/ext/oneliner_ext.c +289 -0
- data/lib/oneliner.rb +21 -0
- data/lib/oneliner/superstring.rb +385 -0
- data/tests/superstring_benchmark.rb +60 -0
- data/tests/superstring_test.rb +50 -0
- data/tests/test_helper.rb +35 -0
- data/tests/weird_string +473 -0
- metadata +57 -0
data/lib/oneliner.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# Archipelago - a distributed computing toolkit for ruby
|
2
|
+
# Copyright (C) 2006 Martin Kihlgren <zond at troja dot ath dot cx>
|
3
|
+
#
|
4
|
+
# This program is free software; you can redistribute it and/or
|
5
|
+
# modify it under the terms of the GNU General Public License
|
6
|
+
# as published by the Free Software Foundation; either version 2
|
7
|
+
# of the License, or (at your option) any later version.
|
8
|
+
#
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12
|
+
# GNU General Public License for more details.
|
13
|
+
#
|
14
|
+
# You should have received a copy of the GNU General Public License
|
15
|
+
# along with this program; if not, write to the Free Software
|
16
|
+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
17
|
+
|
18
|
+
$: << File.dirname(File.expand_path(__FILE__))
|
19
|
+
|
20
|
+
require 'oneliner_ext'
|
21
|
+
require 'oneliner/superstring'
|
@@ -0,0 +1,385 @@
|
|
1
|
+
# Archipelago - a distributed computing toolkit for ruby
|
2
|
+
# Copyright (C) 2006 Martin Kihlgren <zond at troja dot ath dot cx>
|
3
|
+
#
|
4
|
+
# This program is free software; you can redistribute it and/or
|
5
|
+
# modify it under the terms of the GNU General Public License
|
6
|
+
# as published by the Free Software Foundation; either version 2
|
7
|
+
# of the License, or (at your option) any later version.
|
8
|
+
#
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12
|
+
# GNU General Public License for more details.
|
13
|
+
#
|
14
|
+
# You should have received a copy of the GNU General Public License
|
15
|
+
# along with this program; if not, write to the Free Software
|
16
|
+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
17
|
+
|
18
|
+
require 'set'
|
19
|
+
require 'digest/sha2'
|
20
|
+
|
21
|
+
module Oneliner
|
22
|
+
|
23
|
+
#
|
24
|
+
# A String subclass providing Online Code functionality.
|
25
|
+
#
|
26
|
+
class SuperString < String
|
27
|
+
|
28
|
+
E = 0.01
|
29
|
+
Q = 3
|
30
|
+
F = (Math.log((E ** 2) / 4) / Math.log(1 - (E / 2))).abs.to_i
|
31
|
+
P = [0, (1 - ((1 + (1 / F)) / (1 + E)))]
|
32
|
+
2.upto(F) do |i|
|
33
|
+
P << (((1 - P[1]) * F) / ((F - 1) * i * (i - 1)))
|
34
|
+
end
|
35
|
+
|
36
|
+
#
|
37
|
+
# Will return a String containing +requested_size+ nr of bytes
|
38
|
+
# as an online coded chunk of blocks for this SuperString.
|
39
|
+
#
|
40
|
+
def encode(requested_size)
|
41
|
+
raise "requested size is too small for metadata (8 bytes)" unless requested_size > 7
|
42
|
+
|
43
|
+
ensure_encode_format
|
44
|
+
srand
|
45
|
+
seed = rand(Context::INT_MAX)
|
46
|
+
context = Context.new
|
47
|
+
context.seed(seed)
|
48
|
+
|
49
|
+
rval = [self.size].pack("i")
|
50
|
+
rval << [seed].pack("i")
|
51
|
+
|
52
|
+
blocks = []
|
53
|
+
wanted_blocks = ((requested_size - 8) * 8) / @block_size
|
54
|
+
1.upto(wanted_blocks) do |n|
|
55
|
+
blocks << generate_check_block(context, n - 1)
|
56
|
+
end
|
57
|
+
|
58
|
+
return rval + compact(blocks)
|
59
|
+
end
|
60
|
+
|
61
|
+
#
|
62
|
+
# Will try to decode this SuperString using the given +chunk+ and all
|
63
|
+
# formerly given chunks.
|
64
|
+
#
|
65
|
+
# Returns whether decoding is done.
|
66
|
+
#
|
67
|
+
def decode!(chunk)
|
68
|
+
raise "#{chunk.inspect} is too small for metadata (8 bytes)" unless chunk.size > 7
|
69
|
+
|
70
|
+
@decode_done = nil
|
71
|
+
|
72
|
+
context = Context.new
|
73
|
+
|
74
|
+
requested_size = chunk[0..3].unpack("i").first
|
75
|
+
|
76
|
+
ensure_decode_format(requested_size)
|
77
|
+
|
78
|
+
the_seed = chunk[4..7].unpack("i").first
|
79
|
+
|
80
|
+
chunk_data = build_chunk_data(chunk, context, the_seed)
|
81
|
+
|
82
|
+
@known_nr_of_blocks += chunk_data.size
|
83
|
+
@known_chunks.unshift(chunk_data)
|
84
|
+
|
85
|
+
if @known_nr_of_blocks > @nr_of_blocks
|
86
|
+
|
87
|
+
nil while do_decode(context)
|
88
|
+
|
89
|
+
if decode_done?
|
90
|
+
self.replace(compact(@blocks[0...@nr_of_data_blocks])[0...requested_size])
|
91
|
+
return true
|
92
|
+
else
|
93
|
+
return false
|
94
|
+
end
|
95
|
+
|
96
|
+
else
|
97
|
+
return false
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
#
|
102
|
+
# Returns whether decoding is done.
|
103
|
+
#
|
104
|
+
def decode_done?
|
105
|
+
return false unless defined?(@decode_done) && defined?(@nr_of_data_blocks)
|
106
|
+
|
107
|
+
unless @decode_done
|
108
|
+
data = @blocks[0...@nr_of_data_blocks]
|
109
|
+
@decode_done = data.compact.size == data.size
|
110
|
+
end
|
111
|
+
return @decode_done
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
|
116
|
+
#
|
117
|
+
# Misc stuff
|
118
|
+
#
|
119
|
+
|
120
|
+
#
|
121
|
+
# Builds a Hash with index of each block in the +chunk+
|
122
|
+
# paired with the block itself and the source blocks for
|
123
|
+
# that block determined using +context+ and +the_seed+.
|
124
|
+
#
|
125
|
+
def build_chunk_data(chunk, context, the_seed)
|
126
|
+
blocks_to_return = {}
|
127
|
+
|
128
|
+
context.seed(the_seed)
|
129
|
+
|
130
|
+
expand(chunk[8..-1]).each_with_index do |block, index|
|
131
|
+
this_degree = get_degree(context)
|
132
|
+
these_blocks = []
|
133
|
+
this_degree.times do |m|
|
134
|
+
these_blocks << context.random(@nr_of_blocks)
|
135
|
+
end
|
136
|
+
|
137
|
+
blocks_to_return[index] = [block, these_blocks]
|
138
|
+
end
|
139
|
+
|
140
|
+
return blocks_to_return
|
141
|
+
end
|
142
|
+
|
143
|
+
#
|
144
|
+
# Split +s+ up into @block_size (in bits if less than 8, otherwise closed matching nr of bytes) pieces
|
145
|
+
# and return them in an array.
|
146
|
+
#
|
147
|
+
def expand(s)
|
148
|
+
if @block_size < 8
|
149
|
+
rval = s.unpack("b*").first
|
150
|
+
rval = rval.gsub(/(.{#{@block_size},#{@block_size}})/, "\\1#{"0" * (8 - @block_size)}")
|
151
|
+
rval += "0" * (8 - (rval.size % 8)) if (rval.size % 8) > 0
|
152
|
+
rval = [rval].pack("b*")
|
153
|
+
return rval.split(//)
|
154
|
+
else
|
155
|
+
bytes_per_block = @block_size / 8
|
156
|
+
rval = s
|
157
|
+
rval += "\000" * (bytes_per_block - (rval.size % bytes_per_block)) if (rval.size % bytes_per_block) > 0
|
158
|
+
return rval.hack(bytes_per_block)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
#
|
163
|
+
# Glue +blocks+ together into a String.
|
164
|
+
#
|
165
|
+
def compact(blocks)
|
166
|
+
if @block_size < 8
|
167
|
+
rval = blocks.join
|
168
|
+
rval = rval.unpack("b*")
|
169
|
+
rval = rval.first.gsub(/(.{#{@block_size},#{@block_size}}).{#{8 - @block_size},#{8 - @block_size}}/,
|
170
|
+
"\\1")
|
171
|
+
rval += "0" * (8 - (rval.size % 8)) if (rval.size % 8) > 0
|
172
|
+
return [rval].pack("b*")
|
173
|
+
else
|
174
|
+
return blocks.join
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
#
|
179
|
+
# Determine and save the nr of aux_blocks we want for this SuperString.
|
180
|
+
#
|
181
|
+
def set_nr_of_aux_blocks
|
182
|
+
@nr_of_aux_blocks ||= (0.55 * Q * E * @nr_of_data_blocks).ceil
|
183
|
+
end
|
184
|
+
|
185
|
+
#
|
186
|
+
# Determine and save the block_size we want for this SuperString.
|
187
|
+
#
|
188
|
+
def set_block_size
|
189
|
+
unless defined?(@block_size)
|
190
|
+
if size < (256 + 128)
|
191
|
+
@block_size = 1
|
192
|
+
else
|
193
|
+
@block_size = size / 256
|
194
|
+
end
|
195
|
+
end
|
196
|
+
return @block_size
|
197
|
+
end
|
198
|
+
|
199
|
+
#
|
200
|
+
# Determine and save the nr_of_blocks we want for this SuperString.
|
201
|
+
#
|
202
|
+
def set_nr_of_blocks
|
203
|
+
@nr_of_blocks ||= @blocks.size
|
204
|
+
end
|
205
|
+
|
206
|
+
#
|
207
|
+
# Decoding stuff
|
208
|
+
#
|
209
|
+
|
210
|
+
def do_decode(context)
|
211
|
+
found_new_block = false
|
212
|
+
@known_chunks.clone.each_with_index do |chunk_data, i|
|
213
|
+
this_chunk_found_new_block, this_chunk_informative = decode_chunk(chunk_data)
|
214
|
+
found_new_block = found_new_block || this_chunk_found_new_block
|
215
|
+
@known_chunks.delete(i) unless this_chunk_informative
|
216
|
+
end
|
217
|
+
return aux_decode || found_new_block
|
218
|
+
end
|
219
|
+
|
220
|
+
def aux_decode
|
221
|
+
got_new_block = false
|
222
|
+
|
223
|
+
@nr_of_data_blocks.upto(@nr_of_blocks - 1) do |i|
|
224
|
+
|
225
|
+
aux_block = @blocks[i]
|
226
|
+
if aux_block
|
227
|
+
|
228
|
+
source_blocks = @aux_hash[i]
|
229
|
+
if source_blocks.size == 1
|
230
|
+
block_nr = source_blocks.first
|
231
|
+
|
232
|
+
if @blocks[block_nr].nil?
|
233
|
+
@blocks[block_nr] = aux_block
|
234
|
+
got_new_block = true
|
235
|
+
@blocks[i] = nil
|
236
|
+
end
|
237
|
+
else
|
238
|
+
missing_blocks = source_blocks.size
|
239
|
+
missing_block = nil
|
240
|
+
xor_sum = aux_block
|
241
|
+
source_blocks.each do |block|
|
242
|
+
source_block = @blocks[block]
|
243
|
+
if source_block
|
244
|
+
missing_blocks -= 1
|
245
|
+
xor_sum ^= source_block
|
246
|
+
else
|
247
|
+
missing_block = block
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
if missing_blocks == 1
|
252
|
+
@blocks[missing_block] = xor_sum
|
253
|
+
got_new_block = true
|
254
|
+
@blocks[i] = nil
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
return got_new_block
|
261
|
+
end
|
262
|
+
|
263
|
+
def decode_chunk(blocks)
|
264
|
+
got_new_block = false
|
265
|
+
got_informative_block = false
|
266
|
+
|
267
|
+
blocks.clone.each do |index, block_data|
|
268
|
+
|
269
|
+
block, block_numbers = block_data
|
270
|
+
|
271
|
+
got_informative_block = true
|
272
|
+
|
273
|
+
if block_numbers.size == 1
|
274
|
+
block_nr = block_numbers.first
|
275
|
+
|
276
|
+
if @blocks[block_nr].nil?
|
277
|
+
@blocks[block_nr] = block
|
278
|
+
got_new_block = true
|
279
|
+
blocks.delete(index)
|
280
|
+
end
|
281
|
+
else
|
282
|
+
if decode_multiple(block_data, @blocks, @nr_of_blocks)
|
283
|
+
got_new_block = true
|
284
|
+
blocks.delete(index)
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
end
|
289
|
+
|
290
|
+
return [got_new_block, got_informative_block]
|
291
|
+
end
|
292
|
+
|
293
|
+
def generate_aux_hash
|
294
|
+
rval = Array.new(@nr_of_blocks)
|
295
|
+
|
296
|
+
context = Context.new
|
297
|
+
context.seed(@nr_of_data_blocks)
|
298
|
+
|
299
|
+
0.upto(@nr_of_data_blocks - 1) do |i|
|
300
|
+
1.upto(Q) do
|
301
|
+
aux_block_nr = @nr_of_data_blocks + context.random(@nr_of_aux_blocks)
|
302
|
+
(rval[aux_block_nr] ||= []) << i
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
return rval
|
307
|
+
end
|
308
|
+
|
309
|
+
def ensure_decode_format(requested_size)
|
310
|
+
unless defined?(@blocks)
|
311
|
+
self.concat("\000" * requested_size)
|
312
|
+
set_block_size
|
313
|
+
@nr_of_data_blocks = expand(self).size
|
314
|
+
set_nr_of_aux_blocks
|
315
|
+
@blocks = Array.new(@nr_of_data_blocks)
|
316
|
+
@blocks += Array.new(@nr_of_aux_blocks)
|
317
|
+
set_nr_of_blocks
|
318
|
+
@aux_hash = generate_aux_hash
|
319
|
+
@known_chunks = []
|
320
|
+
@known_nr_of_blocks = 0
|
321
|
+
@known_block_nrs_by_seed = {}
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
def ensure_size(requested_size)
|
326
|
+
if size == 0
|
327
|
+
concat("\000" * requested_size)
|
328
|
+
@decoded_blocks = Set.new
|
329
|
+
elsif size != requested_size
|
330
|
+
raise "size of #{self} (#{size}) is wrong, should be #{requested_size}"
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
#
|
335
|
+
# Encoding stuff
|
336
|
+
#
|
337
|
+
|
338
|
+
def generate_check_block(context, n)
|
339
|
+
degree = get_degree(context)
|
340
|
+
|
341
|
+
block_nr = context.random(@nr_of_blocks)
|
342
|
+
rval = @blocks[block_nr]
|
343
|
+
|
344
|
+
2.upto(degree) do
|
345
|
+
block_nr = context.random(@nr_of_blocks)
|
346
|
+
rval ^= @blocks[block_nr]
|
347
|
+
end
|
348
|
+
return rval
|
349
|
+
end
|
350
|
+
|
351
|
+
def generate_aux_blocks
|
352
|
+
rval = Array.new(@nr_of_aux_blocks)
|
353
|
+
|
354
|
+
context = Context.new
|
355
|
+
context.seed(@nr_of_data_blocks)
|
356
|
+
|
357
|
+
b_size = nil
|
358
|
+
@blocks.each_with_index do |b, i|
|
359
|
+
1.upto(Q) do
|
360
|
+
aux_block_nr = context.random(@nr_of_aux_blocks)
|
361
|
+
if rval[aux_block_nr].nil?
|
362
|
+
rval[aux_block_nr] = b
|
363
|
+
else
|
364
|
+
rval[aux_block_nr] ^= b
|
365
|
+
end
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
369
|
+
return rval
|
370
|
+
end
|
371
|
+
|
372
|
+
def ensure_encode_format
|
373
|
+
unless defined?(@blocks)
|
374
|
+
set_block_size
|
375
|
+
@blocks = expand(self)
|
376
|
+
@nr_of_data_blocks = @blocks.size
|
377
|
+
set_nr_of_aux_blocks
|
378
|
+
@blocks += generate_aux_blocks
|
379
|
+
@nr_of_blocks = @blocks.size
|
380
|
+
end
|
381
|
+
end
|
382
|
+
|
383
|
+
end
|
384
|
+
|
385
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
|
2
|
+
require File.join(File.dirname(__FILE__), 'test_helper')
|
3
|
+
|
4
|
+
class SuperStringTest < Test::Unit::TestCase
|
5
|
+
|
6
|
+
def test_decoding
|
7
|
+
puts "required for 512 blocks: #{required(1, 64)}"
|
8
|
+
puts "required for 1024 blocks: #{required(1, 128)}"
|
9
|
+
puts "required for 2048 blocks: #{required(1, 256)}"
|
10
|
+
puts "required for 3064 blocks: #{required(1, 127 + 256)}"
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_speed
|
14
|
+
7.upto(20) do |n|
|
15
|
+
decode(1 << n)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def randstr(size)
|
22
|
+
rval = ""
|
23
|
+
size.times do
|
24
|
+
rval += rand(128).chr
|
25
|
+
end
|
26
|
+
rval
|
27
|
+
end
|
28
|
+
|
29
|
+
def decode(n)
|
30
|
+
s2 = Oneliner::SuperString.new(randstr(n))
|
31
|
+
bm("#{n} bytes in 40% chunks", :n => 10) do
|
32
|
+
s = Oneliner::SuperString.new
|
33
|
+
nil while !s.decode!(s2.encode((n * 0.4).to_i + 8))
|
34
|
+
end
|
35
|
+
bm("#{n} bytes in 5% chunks", :n => 10) do
|
36
|
+
s = Oneliner::SuperString.new
|
37
|
+
nil while !s.decode!(s2.encode((n * 0.05).to_i + 8))
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def required(n, size)
|
42
|
+
total_req = 0
|
43
|
+
n.times do
|
44
|
+
s = Oneliner::SuperString.new
|
45
|
+
s2 = Oneliner::SuperString.new(" " * size)
|
46
|
+
total_req += measure_required(s, s2)
|
47
|
+
end
|
48
|
+
return total_req / n.to_f
|
49
|
+
end
|
50
|
+
|
51
|
+
def measure_required(dest, source)
|
52
|
+
dest.decode!(source.encode(source.size))
|
53
|
+
while (!dest.decode!(source.encode((source.size.to_f * 0.01).ceil + 8)))
|
54
|
+
print "."
|
55
|
+
STDOUT.flush
|
56
|
+
end
|
57
|
+
return dest.instance_eval do @known_nr_of_blocks end.to_f / source.instance_eval do @nr_of_data_blocks end.to_f
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|