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.
@@ -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