oneliner 0.2.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -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