atmos-ruby 1.4.0.7

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.
Files changed (2) hide show
  1. data/lib/EsuApi.rb +1409 -0
  2. metadata +98 -0
@@ -0,0 +1,1409 @@
1
+ # Copyright © 2010, EMC Corporation.
2
+ # Redistribution and use in source and binary forms, with or without modification,
3
+ # are permitted provided that the following conditions are met:
4
+ #
5
+ # + Redistributions of source code must retain the above copyright notice,
6
+ # this list of conditions and the following disclaimer.
7
+ # + Redistributions in binary form must reproduce the above copyright
8
+ # notice, this list of conditions and the following disclaimer in the
9
+ # documentation and/or other materials provided with the distribution.
10
+ # + The name of EMC Corporation may not be used to endorse or promote
11
+ # products derived from this software without specific prior written
12
+ # permission.
13
+ #
14
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
15
+ # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
16
+ # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17
+ # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
18
+ # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24
+ # POSSIBILITY OF SUCH DAMAGE.
25
+
26
+ require 'net/http'
27
+ require 'net/https'
28
+ require 'uri'
29
+ require 'time'
30
+ require 'base64'
31
+ require 'hmac-sha1'
32
+ require 'nokogiri'
33
+ require 'cgi'
34
+
35
+ # The EsuApi Module provides access to the EMC Atmos REST APIs
36
+ module EsuApi
37
+ # The EsuRestApi object creates a connection to the Atmos REST API
38
+ #
39
+ # Note that this class is not thread-safe. Each instance maps one-to-one to
40
+ # a Net::HTTP session. Therefore, each instance must be kept in a thread
41
+ # local or pooled using something like common-pool:
42
+ # http://www.pluitsolutions.com/projects/common-pool
43
+ #
44
+ class EsuRestApi
45
+ GET = "GET"
46
+ POST = "POST"
47
+ PUT = "PUT"
48
+ DELETE = "DELETE"
49
+ HEAD = "HEAD"
50
+ ID_EXTRACTOR = /\/[0-9a-zA-Z]+\/objects\/([0-9a-f]{44})/
51
+ OID_TEST = /^[0-9a-f]{44}$/
52
+ PATH_TEST = /^\/.*/
53
+
54
+ # Creates a new connection
55
+ #
56
+ # * host - the access point host
57
+ # * port - the port to connect with
58
+ # * uid - the Atmos UID
59
+ # * secret - the base64-encoded secret key for the UID
60
+ def initialize( host, port, uid, secret )
61
+ @host = host
62
+ @port = port
63
+ @uid = uid
64
+ @secret = Base64.decode64( secret )
65
+ @session = Net::HTTP.new( host, port ).start
66
+
67
+ @context = "/rest"
68
+ end
69
+
70
+ # Creates a new object in Atmos and returns its Object ID
71
+ #
72
+ # * acl - the ACL to apply to the object. May be nil.
73
+ # * metadata - the object's initial metadata as a hash of name => Metadata,
74
+ # may be nil.
75
+ # * data - data for the object. If nil, a zero-length object will be
76
+ # created.
77
+ # * mimetype - the object's mimetype. If not specified, will default
78
+ # to application/octet-stream.
79
+ # * hash - optional. If specified, the object will be populated with
80
+ # the hash of the data passed. If uploading a file in multiple chunks,
81
+ # the same hash object should be passed to the subsequent update calls.
82
+ def create_object( acl, metadata, data, mimetype, hash = nil)
83
+ uri = URI::HTTP.build( {:host => @host, :port => @port,
84
+ :path => @context + "/objects" } )
85
+
86
+ headers = {}
87
+ if( data == nil )
88
+ data = ""
89
+ end
90
+
91
+ headers["content-length"] = String(data.length())
92
+
93
+ if( acl )
94
+ process_acl( acl, headers )
95
+ end
96
+
97
+ if( metadata )
98
+ process_metadata( metadata, headers )
99
+ end
100
+
101
+ if( hash )
102
+ update_hash( hash, data, headers )
103
+ end
104
+
105
+ request = build_request( EsuRestApi::POST, uri, headers, mimetype )
106
+ request.body = data
107
+
108
+ response = @session.request( request )
109
+
110
+ handle_error( response )
111
+
112
+ return ID_EXTRACTOR.match( response["location"] )[1].to_s
113
+ end
114
+
115
+ # Creates an object on the given path. The path must start with a slash
116
+ # (/) character. When complete, returns the ID of the new object.
117
+ #
118
+ # * path - the path in the namespace, e.g. "/myfile.txt"
119
+ # * acl - the ACL to apply to the object. May be nil. Should be an
120
+ # #Array of #Grant objects
121
+ # * metadata - the object's initial metadata as a hash of name => Metadata,
122
+ # may be nil.
123
+ # * data - data for the object. If nil, a zero-length object will be
124
+ # created.
125
+ # * mimetype - the object's mimetype. If not specified, will default
126
+ # to application/octet-stream.
127
+ # * hash - optional. If specified, the object will be populated with
128
+ # the hash of the data passed. If uploading a file in multiple chunks,
129
+ # the same hash object should be passed to the subsequent update calls.
130
+ def create_object_on_path( path, acl, metadata, data, mimetype, hash = nil)
131
+ uri = URI::HTTP.build( {:host => @host, :port => @port,
132
+ :path => build_resource( path ) } )
133
+
134
+ headers = {}
135
+ if( data == nil )
136
+ data = ""
137
+ end
138
+
139
+ headers["content-length"] = String(data.length())
140
+
141
+ if( acl )
142
+ process_acl( acl, headers )
143
+ end
144
+
145
+ if( metadata )
146
+ process_metadata( metadata, headers )
147
+ end
148
+
149
+ if( hash )
150
+ update_hash( hash, data, headers )
151
+ end
152
+
153
+ request = build_request( EsuRestApi::POST, uri, headers, mimetype )
154
+ request.body = data
155
+
156
+ response = @session.request( request )
157
+
158
+ handle_error( response )
159
+
160
+ return ID_EXTRACTOR.match( response["location"] )[1].to_s
161
+ end
162
+
163
+ # Deletes an object.
164
+ #
165
+ # * id - A string containing either an Object ID or a path
166
+ def delete_object( id )
167
+ uri = URI::HTTP.build( {:host => @host, :port => @port,
168
+ :path => build_resource(id) } )
169
+
170
+ headers = {}
171
+
172
+ request = build_request( EsuRestApi::DELETE, uri, headers, nil )
173
+
174
+ response = @session.request( request )
175
+
176
+ handle_error( response )
177
+ end
178
+
179
+ # Reads an object's content.
180
+ #
181
+ # * id - A string containing either an Object ID or an object path.
182
+ # * extent - If nil, the entire object will be returned. Otherwise, only
183
+ # the requested extent will be returned
184
+ # * checksum - Optional. If specified, the data will be added to the
185
+ # given checksum object. Note that Atmos currently only supports
186
+ # read checksums on erasure coded objects. If you're reading a file
187
+ # in sequential chunks, pass the same checksum object to each request.
188
+ # When complete, check the checksum object's expected_value against
189
+ # it's to_s() value.
190
+ def read_object( id, extent, checksum = nil )
191
+ uri = URI::HTTP.build( {:host => @host, :port => @port,
192
+ :path => build_resource(id) } )
193
+
194
+ headers = {}
195
+
196
+ if( extent != nil )
197
+ headers["range"] = "#{extent}"
198
+ end
199
+
200
+ request = build_request( EsuRestApi::GET, uri, headers, nil )
201
+
202
+ response = @session.request( request )
203
+
204
+ handle_error( response )
205
+
206
+ if( checksum != nil )
207
+ checksum.update( response.body )
208
+ if( response["x-emc-wschecksum"] != nil )
209
+ checksum.expected_value = response["x-emc-wschecksum"]
210
+ end
211
+ end
212
+
213
+
214
+ return response.body
215
+ end
216
+
217
+ # Updates an object in Atmos
218
+ #
219
+ # * id - a String containing either an object ID or an object path.
220
+ # * acl - the ACL to apply to the object. May be nil. Should be an
221
+ # #Array of #Grant objects
222
+ # * metadata - the object's initial metadata as a hash of name => Metadata,
223
+ # may be nil.
224
+ # * data - data for the object. If nil, a zero-length object will be
225
+ # created.
226
+ # * mimetype - the object's mimetype. If not specified, will default
227
+ # to application/octet-stream.
228
+ # * hash - optional. If specified, the object will be populated with
229
+ # the hash of the data passed. If uploading a file in multiple chunks,
230
+ # the same hash object should be passed to the subsequent update calls.
231
+ def update_object( id, acl, metadata, data, extent, mimetype, hash = nil)
232
+ uri = URI::HTTP.build( {:host => @host, :port => @port,
233
+ :path => build_resource(id) } )
234
+
235
+ headers = {}
236
+ if( data == nil )
237
+ data = ""
238
+ end
239
+
240
+ headers["content-length"] = String(data.length())
241
+
242
+ if( extent != nil )
243
+ headers["range"] = "${extent}"
244
+ end
245
+
246
+ if( acl )
247
+ process_acl( acl, headers )
248
+ end
249
+
250
+ if( metadata )
251
+ process_metadata( metadata, headers )
252
+ end
253
+
254
+ if( hash )
255
+ update_hash( hash, data, headers )
256
+ end
257
+
258
+ request = build_request( EsuRestApi::PUT, uri, headers, mimetype )
259
+ request.body = data
260
+
261
+ response = @session.request( request )
262
+
263
+ handle_error( response )
264
+ end
265
+
266
+ # Gets an object's ACL. Returns an #Array containing #Grant objects.
267
+ #
268
+ # * id - a #String containing either an object ID or an object path
269
+ def get_acl( id )
270
+ uri = URI::HTTP.build( {:host => @host, :port => @port,
271
+ :path => build_resource(id), :query => "acl" } )
272
+
273
+ headers = {}
274
+
275
+ request = build_request( EsuRestApi::GET, uri, headers, nil )
276
+
277
+ response = @session.request( request )
278
+
279
+ handle_error( response )
280
+
281
+ # Parse returned ACLs
282
+ acl = []
283
+ parse_acl( acl, response["x-emc-groupacl"], EsuApi::Grantee::GROUP )
284
+ parse_acl( acl, response["x-emc-useracl"], EsuApi::Grantee::USER )
285
+
286
+ return acl
287
+
288
+ end
289
+
290
+ # Gets the user metadata on an object. Returns a #Hash of metadata name =>
291
+ # #Metadata objects.
292
+ #
293
+ # * id - a String containing either an object ID or an object path
294
+ # * tags - Optional. If specified, an Array of Strings containing the
295
+ # user metadata tags to fetch from the server.
296
+ def get_user_metadata( id, tags = nil )
297
+ uri = URI::HTTP.build( {:host => @host, :port => @port,
298
+ :path => build_resource(id), :query => "metadata/user" } )
299
+
300
+ headers = {}
301
+ if( tags != nil )
302
+ process_tags( tags, headers )
303
+ end
304
+
305
+ request = build_request( EsuRestApi::GET, uri, headers, nil )
306
+
307
+ response = @session.request( request )
308
+
309
+ handle_error( response )
310
+
311
+ # Parse returned metadata
312
+ meta = {}
313
+ parse_metadata( meta, response["x-emc-meta"], false )
314
+ parse_metadata( meta, response["x-emc-listable-meta"], true )
315
+
316
+ return meta
317
+ end
318
+
319
+ # Gets the system metadata on an object. Returns a #Hash of metadata name =>
320
+ # #Metadata objects, e.g. ctime, atime, mtime, size
321
+ #
322
+ # * id - a String containing either an object ID or an object path
323
+ # * tags - Optional. If specified, an Array of Strings containing the
324
+ # system metadata tags to fetch from the server.
325
+ def get_system_metadata( id, tags = nil )
326
+ uri = URI::HTTP.build( {:host => @host, :port => @port,
327
+ :path => build_resource(id), :query => "metadata/system" } )
328
+
329
+ headers = {}
330
+ if( tags != nil )
331
+ process_tags( tags, headers )
332
+ end
333
+
334
+ request = build_request( EsuRestApi::GET, uri, headers, nil )
335
+
336
+ response = @session.request( request )
337
+
338
+ handle_error( response )
339
+
340
+ # Parse returned metadata
341
+ meta = {}
342
+ parse_metadata( meta, response["x-emc-meta"], false )
343
+
344
+ return meta
345
+ end
346
+
347
+ # Deletes user metadata from an object
348
+ #
349
+ # * id - a String containing an object ID or an object path
350
+ # * tags - an Array containing the names of the user metadata elements
351
+ # to delete from the object.
352
+ def delete_user_metadata( id, tags )
353
+ uri = URI::HTTP.build( {:host => @host, :port => @port,
354
+ :path => build_resource(id), :query => "metadata/user" } )
355
+
356
+ headers = {}
357
+ headers["x-emc-tags"] = tags.join( "," )
358
+
359
+ request = build_request( EsuRestApi::DELETE, uri, headers, nil )
360
+
361
+ response = @session.request( request )
362
+
363
+ handle_error( response )
364
+ end
365
+
366
+ # Creates a new version of an object. Returns the ID of the new version.
367
+ # Note that versions are immutable. See #restore_version.
368
+ #
369
+ # * id - a String containing an object ID or an object path.
370
+ def version_object( id )
371
+ uri = URI::HTTP.build( {:host => @host, :port => @port,
372
+ :path => build_resource(id), :query => "versions" } )
373
+
374
+ headers = {}
375
+
376
+ request = build_request( EsuRestApi::POST, uri, headers, nil )
377
+
378
+ response = @session.request( request )
379
+
380
+ handle_error( response )
381
+
382
+ # Parse returned ID
383
+ return ID_EXTRACTOR.match( response["location"] )[1].to_s
384
+ end
385
+
386
+ # Deletes an object version. Note that you'll get an access error if
387
+ # you pass a version ID to #delete_object
388
+ #
389
+ # * vid - a String containing an object version ID
390
+ def delete_version( vid )
391
+ uri = URI::HTTP.build( {:host => @host, :port => @port,
392
+ :path => build_resource(vid), :query => "versions" } )
393
+
394
+ headers = {}
395
+
396
+ request = build_request( EsuRestApi::DELETE, uri, headers, nil )
397
+
398
+ response = @session.request( request )
399
+
400
+ handle_error( response )
401
+ end
402
+
403
+ # Restores ("promotes") a version to the base object content, i.e. the
404
+ # object's content will be replaced with the contents of the version.
405
+ #
406
+ # * id - a String containing the object ID or object path of the base
407
+ # object
408
+ # * vid - a String containing the object version ID to replace the base
409
+ # content with
410
+ def restore_version( id, vid )
411
+ uri = URI::HTTP.build( {:host => @host, :port => @port,
412
+ :path => build_resource(id), :query => "versions" } )
413
+
414
+ headers = {}
415
+ headers["x-emc-version-oid"] = vid
416
+
417
+ request = build_request( EsuRestApi::PUT, uri, headers, nil )
418
+
419
+ response = @session.request( request )
420
+
421
+ handle_error( response )
422
+ end
423
+
424
+ # Lists the versions of an object. Returns an Array of Strings containing
425
+ # the object version IDs.
426
+ #
427
+ # * id - a String containing the object ID or object path of the base
428
+ # object
429
+ def list_versions(id)
430
+ uri = URI::HTTP.build( {:host => @host, :port => @port,
431
+ :path => build_resource(id), :query => "versions" } )
432
+
433
+ headers = {}
434
+
435
+ request = build_request( EsuRestApi::GET, uri, headers, nil )
436
+
437
+ response = @session.request( request )
438
+
439
+ handle_error( response )
440
+
441
+ # Parse returned IDs
442
+ return parse_version_list( response )
443
+ end
444
+
445
+ # Fetches the user metadata, system metadata, mimetype, and ACL for an
446
+ # object. Returned as a hash with the key symbols:
447
+ # * :meta
448
+ # * :acl
449
+ # * :mimetype
450
+ #
451
+ # * id - A String containing an object ID or object path
452
+ def get_object_metadata( id )
453
+ uri = URI::HTTP.build( {:host => @host, :port => @port,
454
+ :path => build_resource(id) })
455
+
456
+ headers = {}
457
+
458
+ request = build_request( EsuRestApi::HEAD, uri, headers, nil )
459
+
460
+ response = @session.request( request )
461
+
462
+ handle_error( response )
463
+
464
+ # Parse returned metadata
465
+ om = {}
466
+ meta = {}
467
+ parse_metadata( meta, response["x-emc-meta"], false )
468
+ parse_metadata( meta, response["x-emc-listable-meta"], true )
469
+ om[:meta] = meta
470
+
471
+ # Parse returned ACLs
472
+ acl = []
473
+ parse_acl( acl, response["x-emc-groupacl"], EsuApi::Grantee::GROUP )
474
+ parse_acl( acl, response["x-emc-useracl"], EsuApi::Grantee::USER )
475
+ om[:acl] = acl
476
+
477
+ # Get mimetype
478
+ om[:mimetype] = response["content-type"]
479
+
480
+ return om
481
+
482
+ end
483
+
484
+ # Returns the listable tags present in the system.
485
+ #
486
+ # * tag_root - Optional. If specified, only the listable tags under
487
+ # the root are returned. If omitted, the root tags will be returned.
488
+ def get_listable_tags( tag_root = nil )
489
+ uri = URI::HTTP.build( {:host => @host, :port => @port,
490
+ :path => @context + "/objects", :query => "listabletags" } )
491
+
492
+ headers = {}
493
+ if( tag_root != nil )
494
+ headers["x-emc-tags"] = tag_root
495
+ end
496
+
497
+ request = build_request( EsuRestApi::GET, uri, headers, nil )
498
+
499
+ response = @session.request( request )
500
+
501
+ handle_error( response )
502
+
503
+ return parse_tags( response )
504
+
505
+ end
506
+
507
+ # Returns a #ServiceInformation object containing the version of Atmos
508
+ # the client is connected to.
509
+ def get_service_information()
510
+ uri = URI::HTTP.build( {:host => @host, :port => @port,
511
+ :path => @context + "/service"} )
512
+
513
+ headers = {}
514
+
515
+ request = build_request( EsuRestApi::GET, uri, headers, nil )
516
+
517
+ response = @session.request( request )
518
+
519
+ handle_error( response )
520
+
521
+ return parse_service_information( response )
522
+ end
523
+
524
+ # Generates a pre-signed URL to read an object that can be shared with
525
+ # external users or systems.
526
+ #
527
+ # * id - a String containing an object ID or object path
528
+ # * expires - a #Time object containing the expiration date and time in UTC
529
+ def get_shareable_url( id, expires )
530
+ uri = URI::HTTP.build( {:host => @host, :port => @port,
531
+ :path => build_resource(id) })
532
+
533
+ sb = "GET\n"
534
+ sb += uri.path.downcase+ "\n"
535
+ sb += @uid + "\n"
536
+ sb += String(expires.to_i())
537
+
538
+ signature = sign( sb )
539
+ uri.query = "uid=#{CGI::escape(@uid)}&expires=#{expires.to_i()}&signature=#{CGI::escape(signature)}"
540
+
541
+ return uri
542
+
543
+ end
544
+
545
+ # Lists the contents of a directory. Returns an Array of #DirectoryEntry
546
+ # objects.
547
+ #
548
+ # * dir - a String containing a directory path. Note that directory paths
549
+ # must end with a slash ('/') character.
550
+ def list_directory( dir )
551
+ if !/^\/.*\/$/.match( dir )
552
+ throw "Invalid directory '#{dir}'. Directories must start and end with a slash (/)"
553
+ end
554
+
555
+ uri = URI::HTTP.build( {:host => @host, :port => @port,
556
+ :path => build_resource(dir) } )
557
+
558
+ headers = {}
559
+
560
+ request = build_request( EsuRestApi::GET, uri, headers, nil )
561
+
562
+ response = @session.request( request )
563
+
564
+ handle_error( response )
565
+
566
+ return parse_directory( response, dir )
567
+ end
568
+
569
+ # Lists the objects tagged with the given listable metadata tag. Returns
570
+ # an Array of object IDs.
571
+ #
572
+ # * tag - the tag whose contents to list.
573
+ def list_objects( tag )
574
+ uri = URI::HTTP.build( {:host => @host, :port => @port,
575
+ :path => @context + "/objects" } )
576
+
577
+ headers = {}
578
+ headers["x-emc-tags"] = tag
579
+
580
+ request = build_request( EsuRestApi::GET, uri, headers, nil )
581
+
582
+ response = @session.request( request )
583
+
584
+ handle_error( response )
585
+
586
+ return parse_object_list( response )
587
+ end
588
+
589
+ # Lists the objects tagged with the given listable metadata tag. Returns
590
+ # a Hash of object ID => #ObjectMetadata elements
591
+ #
592
+ # * tag - the tag whose contents to list
593
+ def list_objects_with_metadata( tag )
594
+ uri = URI::HTTP.build( {:host => @host, :port => @port,
595
+ :path => @context + "/objects" } )
596
+
597
+ headers = {}
598
+ headers["x-emc-tags"] = tag
599
+ headers["x-emc-include-meta"] = "1"
600
+
601
+ request = build_request( EsuRestApi::GET, uri, headers, nil )
602
+
603
+ response = @session.request( request )
604
+
605
+ handle_error( response )
606
+
607
+ return parse_object_list_with_metadata( response )
608
+
609
+ end
610
+
611
+ # Gets the list of user metadatda tags on an object
612
+ #
613
+ # * id - a String containing the object ID or object path
614
+ def list_user_metadata_tags( id )
615
+ uri = URI::HTTP.build( {:host => @host, :port => @port,
616
+ :path => build_resource(id), :query => "metadata/tags" } )
617
+
618
+ headers = {}
619
+
620
+ request = build_request( EsuRestApi::GET, uri, headers, nil )
621
+
622
+ response = @session.request( request )
623
+
624
+ handle_error( response )
625
+
626
+ # Parse returned metadata
627
+ tags = []
628
+ parse_tag_list( tags, response["x-emc-tags"] )
629
+ parse_tag_list( tags, response["x-emc-listable-tags"] )
630
+
631
+ return tags
632
+ end
633
+
634
+ # Renames an object in the namespace
635
+ #
636
+ # * source - the path of the source object
637
+ # * destination - the path of the destination object
638
+ # * overwrite - if true, the destination will be overwritten if it exists.
639
+ # If false, the operation will fail if the destination exists. Note that
640
+ # overwriting an object is asynchronous; it may take a few seconds for the
641
+ # destination object to be replaced.
642
+ def rename( source, destination, overwrite = false )
643
+ uri = URI::HTTP.build( {:host => @host, :port => @port,
644
+ :path => build_resource(source), :query => "rename" } )
645
+
646
+ headers = {}
647
+ headers["x-emc-path"] = destination
648
+ if( overwrite )
649
+ headers["x-emc-force"] = "#{overwrite}"
650
+ end
651
+
652
+ request = build_request( EsuRestApi::POST, uri, headers, nil )
653
+
654
+ response = @session.request( request )
655
+
656
+ handle_error( response )
657
+
658
+ end
659
+
660
+ #####################
661
+ ## Private Methods ##
662
+ #####################
663
+ private
664
+
665
+ def parse_metadata( meta, value, listable )
666
+ entries = value.split( "," )
667
+ entries.each{ |entvalue|
668
+ nv = entvalue.split( "=", -2 )
669
+ #print "#{nv[0].strip}=#{nv[1]}\n"
670
+ m = EsuApi::Metadata.new( nv[0].strip, nv[1], listable )
671
+ meta[nv[0].strip] = m
672
+ }
673
+ end
674
+
675
+ def parse_acl( acl, value, grantee_type )
676
+ #print "Parse: #{grantee_type}\n"
677
+ entries = value.split(",")
678
+ entries.each { |entval|
679
+ nv = entval.split( "=", -2 )
680
+ #print "#{nv[0]}=#{nv[1]}\n"
681
+ acl.push( EsuApi::Grant.new( EsuApi::Grantee.new( nv[0].strip, grantee_type ), nv[1] ) )
682
+ }
683
+ end
684
+
685
+ def process_acl( acl, headers )
686
+ usergrants = []
687
+ groupgrants = []
688
+ acl.each { |grant|
689
+ if( grant.grantee.grantee_type == EsuApi::Grantee::USER )
690
+ usergrants.push( grant )
691
+ else
692
+ groupgrants.push( grant )
693
+ end
694
+ }
695
+
696
+ if( usergrants.size > 0 )
697
+ headers[ "x-emc-useracl" ] = usergrants.join( "," )
698
+ end
699
+
700
+ if( groupgrants.size > 0 )
701
+ headers[ "x-emc-groupacl" ] = groupgrants.join( "," )
702
+ end
703
+
704
+ end
705
+
706
+ def process_metadata( meta, headers )
707
+ listable = []
708
+ regular = []
709
+
710
+ meta.each { |key,value|
711
+ if( value.listable )
712
+ listable.push( value )
713
+ else
714
+ regular.push( value )
715
+ end
716
+ }
717
+
718
+ if( listable.size > 0 )
719
+ headers["x-emc-listable-meta"] = listable.join( "," )
720
+ end
721
+
722
+ if( regular.size > 0 )
723
+ headers["x-emc-meta"] = regular.join( "," )
724
+ end
725
+ end
726
+
727
+ def process_tags( tags, headers )
728
+ headers["x-emc-tags"] = tags.join(",")
729
+ end
730
+
731
+ def handle_error( response )
732
+ if( Integer(response.code) > 399 )
733
+ if( response.body )
734
+ throw "Error executing request: code: " + response.code + " body: " + response.body
735
+ else
736
+ throw "Error executing request: code: " + response.code + " message: " + response.message
737
+ end
738
+ end
739
+ end
740
+
741
+ def build_resource( identifier )
742
+ resource = @context
743
+ if( OID_TEST.match( identifier) )
744
+ return resource + "/objects/" + identifier
745
+ elsif( PATH_TEST.match( identifier ) )
746
+ return resource + "/namespace" + identifier
747
+ else
748
+ throw "Could not determine type of identifier for #{identifier}"
749
+ end
750
+ end
751
+
752
+ def build_request( method, uri, headers, mimetype )
753
+ if( mimetype == nil )
754
+ mimetype = "application/octet-stream"
755
+ end
756
+ headers["content-type"] = mimetype
757
+
758
+ # Add request date
759
+ headers["date"] = Time.now().httpdate()
760
+ headers["x-emc-uid"] = @uid
761
+
762
+ # Build signature string
763
+ signstring = ""
764
+ signstring += method
765
+ signstring += "\n"
766
+ if( mimetype )
767
+ signstring += mimetype
768
+ end
769
+ signstring += "\n"
770
+ if( headers["range"] )
771
+ signstring += headers["range"]
772
+ end
773
+ signstring += "\n"
774
+ signstring += headers["date"]
775
+ signstring += "\n"
776
+
777
+ # Once most users go to Ruby 1.9 we can
778
+ # make this work with Unicode.
779
+ signstring += URI.unescape( uri.path ).downcase
780
+ if( uri.query )
781
+ signstring += "?" + uri.query
782
+ end
783
+ signstring += "\n"
784
+
785
+ customheaders = {}
786
+ headers.each { |key,value|
787
+ if key == "x-emc-date"
788
+ #skip
789
+ elsif key =~ /^x-emc-/
790
+ customheaders[ key.downcase ] = value
791
+ end
792
+ }
793
+ header_arr = customheaders.sort()
794
+ first = true
795
+ header_arr.each { |key,value|
796
+ # Values are lowercase and whitespace-normalized
797
+ signstring += key + ":" + value.strip.chomp.squeeze( " " ) + "\n"
798
+ }
799
+
800
+ headers["x-emc-signature"] = sign( signstring.chomp )
801
+
802
+ #print "uri: " + uri.to_s() +"\n" + " path: " + uri.path + "\n"
803
+
804
+ case method
805
+ when EsuRestApi::GET
806
+ return Net::HTTP::Get.new( uri.request_uri, headers )
807
+ when EsuRestApi::POST
808
+ return Net::HTTP::Post.new( uri.request_uri, headers )
809
+ when EsuRestApi::PUT
810
+ return Net::HTTP::Put.new( uri.request_uri, headers )
811
+ when EsuRestApi::DELETE
812
+ return Net::HTTP::Delete.new( uri.request_uri, headers )
813
+ when EsuRestApi::HEAD
814
+ return Net::HTTP::Head.new( uri.request_uri, headers )
815
+ end
816
+ end
817
+
818
+ def sign( string )
819
+ value = HMAC::SHA1.digest( @secret, string )
820
+ signature = Base64.encode64( value ).chomp()
821
+ #print "String to sign: #{string}\nSignature: #{signature}\nValue: #{value}\n"
822
+ return signature
823
+ end
824
+
825
+ #
826
+ # Uses Nokogiri to select the OIDs from the response using XPath
827
+ #
828
+ def parse_version_list( response )
829
+ #print( "parse_version_list: #{response.body}\n" )
830
+ v_ids = []
831
+ doc = Nokogiri::XML( response.body )
832
+
833
+ # Locate OID tags
834
+ doc.xpath( '//xmlns:OID' ).each { |node|
835
+ #print( "Found node #{node}\n" )
836
+ v_ids.push( node.content )
837
+ }
838
+
839
+ return v_ids
840
+ end
841
+
842
+ def parse_service_information( response )
843
+ doc = Nokogiri::XML( response.body )
844
+
845
+ # Locate atmos version
846
+ return EsuApi::ServiceInformation.new( doc.xpath('//xmlns:Atmos')[0].content )
847
+ end
848
+
849
+ def parse_tags( response )
850
+ tags = []
851
+ parse_tag_list( tags, response["x-emc-listable-tags"] )
852
+ return tags
853
+ end
854
+
855
+ def parse_directory( response, dir )
856
+ #print( "parse_directory #{response.body}\n")
857
+ doc = Nokogiri::XML( response.body )
858
+ entries = []
859
+
860
+ doc.xpath( '//xmlns:DirectoryEntry' ).each { |entry|
861
+ oid = entry.xpath( './xmlns:ObjectID' )[0].content
862
+ fname = entry.xpath( './xmlns:Filename' )[0].content
863
+ ftype = entry.xpath( './xmlns:FileType' )[0].content
864
+
865
+ if( ftype == 'directory' )
866
+ fname += '/'
867
+ end
868
+ #print "found #{dir+fname}\n"
869
+ entries.push( EsuApi::DirectoryEntry.new( oid, dir+fname, fname, ftype ) )
870
+ }
871
+
872
+ return entries
873
+ end
874
+
875
+ def parse_object_list( response )
876
+ doc = Nokogiri::XML( response.body )
877
+ objects = []
878
+ doc.xpath( '//xmlns:ObjectID' ).each { |entry|
879
+ objects.push( entry.content )
880
+ }
881
+
882
+ return objects
883
+ end
884
+
885
+ def parse_object_list_with_metadata( response )
886
+ #print( "Objects with Metadata response: #{response.body}\n")
887
+ doc = Nokogiri::XML( response.body )
888
+ objects = {}
889
+
890
+ doc.xpath( '//xmlns:Object').each { |entry|
891
+ oid = entry.xpath( './xmlns:ObjectID' )[0].content
892
+ smeta = parse_object_metadata_xml( entry, './xmlns:SystemMetadataList/xmlns:Metadata', false )
893
+ umeta = parse_object_metadata_xml( entry, './xmlns:UserMetadataList/xmlns:Metadata', true )
894
+
895
+ om = EsuApi::ObjectMetadata.new(oid,smeta,umeta)
896
+ objects[oid] = om
897
+
898
+ }
899
+
900
+ return objects
901
+ end
902
+
903
+ def parse_object_metadata_xml( entry, selector, parse_listable )
904
+ meta = {}
905
+
906
+ entry.xpath( selector ).each { |mentry|
907
+ name = mentry.xpath( './xmlns:Name' )[0].content
908
+ value = mentry.xpath( './xmlns:Value' )[0].content
909
+ listable = false
910
+ if( parse_listable )
911
+ listable = mentry.xpath( './xmlns:Listable' )[0].content == "true"
912
+ end
913
+ meta[name] = EsuApi::Metadata.new( name, value, listable )
914
+ }
915
+
916
+ return meta
917
+ end
918
+
919
+ def parse_tag_list( tags, value )
920
+ value.split(",").each() { |tag|
921
+ tags.push( tag.strip() )
922
+ }
923
+ end
924
+
925
+ def update_hash( hash, data, headers )
926
+ hash.update( data )
927
+ headers["x-emc-wschecksum"] = "#{hash}"
928
+ end
929
+ end
930
+
931
+ class Extent
932
+ def initialize( offset, size )
933
+ @offset = offset
934
+ @size = size
935
+ end
936
+
937
+ def to_s()
938
+ eend = offset + size - 1
939
+ return "Bytes=#{offset}-#{eend}"
940
+ end
941
+
942
+ attr_accessor :offset, :size
943
+ end
944
+
945
+ class Grant
946
+ READ = "READ"
947
+ WRITE = "WRITE"
948
+ FULL_CONTROL = "FULL_CONTROL"
949
+ def initialize( grantee, permission )
950
+ @grantee = grantee
951
+ @permission = permission
952
+ end
953
+
954
+ def to_s()
955
+ #print "Grant::to_s()\n"
956
+ return "#{@grantee}=#{@permission}"
957
+ end
958
+
959
+ def ==(other_grant)
960
+ return @grantee == other_grant.grantee && @permission == other_grant.permission
961
+ end
962
+
963
+ attr_accessor :grantee, :permission
964
+ end
965
+
966
+ class Grantee
967
+ USER = "USER"
968
+ GROUP = "GROUP"
969
+ def initialize( name, grantee_type )
970
+ @name = name
971
+ @grantee_type = grantee_type
972
+ end
973
+ OTHER = EsuApi::Grantee.new( "other", EsuApi::Grantee::GROUP )
974
+
975
+ def to_s()
976
+ return @name
977
+ end
978
+
979
+ def ==(other_grantee)
980
+ return @name == other_grantee.name && @grantee_type == other_grantee.grantee_type
981
+ end
982
+
983
+ attr_accessor :name, :grantee_type
984
+ end
985
+
986
+ class Metadata
987
+ def initialize( name, value, listable )
988
+ @name = name
989
+ @value = value
990
+ @listable = listable
991
+ end
992
+
993
+ def to_s()
994
+ return "#{name}=#{value}"
995
+ end
996
+
997
+ def ==(other_meta)
998
+ return @name==other_meta.name && @value==other_meta.value && @listable==other_meta.listable
999
+ end
1000
+
1001
+ attr_accessor :name, :value, :listable
1002
+ end
1003
+
1004
+ class ServiceInformation
1005
+ def initialize( atmos_version )
1006
+ @atmos_version = atmos_version
1007
+ end
1008
+
1009
+ attr_accessor :atmos_version
1010
+ end
1011
+
1012
+ class DirectoryEntry
1013
+ def initialize( oid, path, filename, filetype )
1014
+ @id = oid
1015
+ @path = path
1016
+ @filename = filename
1017
+ @filetype = filetype
1018
+ end
1019
+
1020
+ def ==(other_entry)
1021
+ return @path == other_entry.path
1022
+ end
1023
+
1024
+ attr_accessor :id, :path, :filename, :filetype
1025
+ end
1026
+
1027
+ class ObjectMetadata
1028
+ def initialize( oid, smeta, umeta )
1029
+ @id = oid
1030
+ @system_metadata = smeta
1031
+ @user_metadata = umeta
1032
+ end
1033
+
1034
+ attr_accessor :id, :system_metadata, :user_metadata
1035
+ end
1036
+
1037
+ class Checksum
1038
+ SHA0 = "SHA0"
1039
+
1040
+ def initialize( algorithm )
1041
+ @algorithm = algorithm
1042
+ @hash = EsuApi::SHA0.new();
1043
+ @offset = 0;
1044
+ @expected_value = ""
1045
+ end
1046
+
1047
+ def update( data )
1048
+ @offset += data.length()
1049
+ @hash.hashUpdate( data )
1050
+ end
1051
+
1052
+ def to_s()
1053
+ value = @hash.clone().hashFinal(nil)
1054
+
1055
+ hval = ""
1056
+ value.each_byte { |b|
1057
+ hval += "%.2x" % b
1058
+ }
1059
+ return "#{@algorithm}/#{@offset}/#{hval}"
1060
+ end
1061
+
1062
+ attr_accessor :expected_value
1063
+ end
1064
+
1065
+ class SHA0
1066
+
1067
+ BLOCK_SIZE = 64
1068
+
1069
+ def initialize()
1070
+ @state = []
1071
+ @constants = []
1072
+ @buffer = ""
1073
+ @state[0] = 0x67452301
1074
+ @state[1] = 0xefcdab89
1075
+ @state[2] = 0x98badcfe
1076
+ @state[3] = 0x10325476
1077
+ @state[4] = 0xc3d2e1f0
1078
+
1079
+ @constants[0] = 0x5a827999
1080
+ @constants[1] = 0x6ed9eba1
1081
+ @constants[2] = 0x8f1bbcdc
1082
+ @constants[3] = 0xca62c1d6
1083
+
1084
+ @counter = 0
1085
+ end
1086
+
1087
+ # Creates a deep copy of the object. This allows you to get the
1088
+ # current hash value at various offsets without disrupting the
1089
+ # hash in progress, e.g.
1090
+ # <code>
1091
+ # sha = EsuApi::SHA0.new()
1092
+ # sha.hashUpdate( block1 )
1093
+ # shacopy = sha.clone()
1094
+ # partialHash = shacopy.hashFinal(nil)
1095
+ # sha.hashUpdate( block2 )
1096
+ # ...
1097
+ # </code>
1098
+ def clone()
1099
+ copy = EsuApi::SHA0.new()
1100
+
1101
+ copy.state[0] = @state[0]
1102
+ copy.state[1] = @state[1]
1103
+ copy.state[2] = @state[2]
1104
+ copy.state[3] = @state[3]
1105
+ copy.state[4] = @state[4]
1106
+
1107
+ copy.counter = @counter
1108
+
1109
+ copy.buffer = @buffer+"" # Clone the string
1110
+
1111
+ return copy
1112
+ end
1113
+
1114
+ def hashUpdate( data )
1115
+ # Break up into 64 byte chunks.
1116
+ i=0
1117
+
1118
+ while( i<data.length )
1119
+ if( data.length - i + @buffer.length >= BLOCK_SIZE )
1120
+ usedBytes = SHA0::BLOCK_SIZE-@buffer.length
1121
+ @buffer += data.slice( i, usedBytes )
1122
+
1123
+ internalHashUpdate( @buffer )
1124
+ @counter += SHA0::BLOCK_SIZE << 3
1125
+ @buffer = ""
1126
+ i+= usedBytes
1127
+ else
1128
+ # Save remaining bytes for next chunk
1129
+ @buffer += data.slice( i, data.length-i )
1130
+ i += data.length-i
1131
+ end
1132
+ end
1133
+ end
1134
+
1135
+ def hashFinal( data )
1136
+ if( data == nil )
1137
+ data = ""
1138
+ end
1139
+
1140
+ # Consume up to the last block
1141
+ hashUpdate( data )
1142
+ @counter += @buffer.length << 3
1143
+
1144
+ # Append the bits 1000 0000
1145
+ @buffer += 0x80.chr
1146
+
1147
+ # See if we have enough room to pad out the final block
1148
+ if( @buffer.length > SHA0::BLOCK_SIZE-8 )
1149
+ while( @buffer.length < SHA0::BLOCK_SIZE )
1150
+ @buffer+=0.chr
1151
+ end
1152
+ internalHashUpdate( @buffer )
1153
+ @buffer = ""
1154
+
1155
+ # Write a zero buffer.
1156
+ (0..SHA0::BLOCK_SIZE-9).each{ |i|
1157
+ @buffer[i] = 0.chr
1158
+ }
1159
+ end
1160
+
1161
+ # Expand the buffer out to a block size
1162
+ while( @buffer.length < SHA0::BLOCK_SIZE-8 )
1163
+ @buffer += 0.chr
1164
+ end
1165
+
1166
+ # Append the bit count (8 bytes) to buffer
1167
+ carr = []
1168
+ carr.push( 0 )
1169
+ carr.push( @counter )
1170
+ @buffer += carr.pack( "N*" )
1171
+
1172
+ # Process the final block
1173
+ internalHashUpdate( @buffer )
1174
+
1175
+ # var output:ByteArray = new ByteArray()
1176
+ # output.writeUnsignedInt( state[0] )
1177
+ # output.writeUnsignedInt( state[1] )
1178
+ # output.writeUnsignedInt( state[2] )
1179
+ # output.writeUnsignedInt( state[3] )
1180
+ # output.writeUnsignedInt( state[4] )
1181
+ output = @state.pack( "N*" )
1182
+
1183
+ return output
1184
+ end
1185
+
1186
+ def internalHashUpdate( data )
1187
+ # Expand the buffer into an array of uints
1188
+ nblk = data.unpack( "N*" )
1189
+
1190
+ # Expand into an array of 80 uints
1191
+ (16..79).each{ |i|
1192
+ nblk[i] = nblk[i-3] ^ nblk[i-8] ^ nblk[i-14] ^ nblk[i-16]
1193
+ }
1194
+
1195
+ # Do the rounds
1196
+ a = truncate(@state[0])
1197
+ b = truncate(@state[1])
1198
+ c = truncate(@state[2])
1199
+ d = truncate(@state[3])
1200
+ e = truncate(@state[4])
1201
+
1202
+ e = truncadd( e, rol( a, 5 ) + f1(b, c, d) + nblk[0] )
1203
+ b =rol( b, 30 )
1204
+ d = d = truncadd( d, rol( e, 5 ) + f1(a, b, c) + nblk[1] )
1205
+ a =rol( a, 30 )
1206
+ c = c = truncadd( c, rol( d, 5 ) + f1(e, a, b) + nblk[2] )
1207
+ e =rol( e, 30 )
1208
+ b = truncadd( b, rol( c, 5 ) + f1(d, e, a) + nblk[3] )
1209
+ d =rol( d, 30 )
1210
+ a = truncadd( a, rol( b, 5 ) + f1(c, d, e) + nblk[4] )
1211
+ c =rol( c, 30 )
1212
+ e = truncadd( e, rol( a, 5 ) + f1(b, c, d) + nblk[5] )
1213
+ b =rol( b, 30 )
1214
+ d = d = truncadd( d, rol( e, 5 ) + f1(a, b, c) + nblk[6] )
1215
+ a =rol( a, 30 )
1216
+ c = c = truncadd( c, rol( d, 5 ) + f1(e, a, b) + nblk[7] )
1217
+ e =rol( e, 30 )
1218
+ b = truncadd( b, rol( c, 5 ) + f1(d, e, a) + nblk[8] )
1219
+ d =rol( d, 30 )
1220
+ a = truncadd( a, rol( b, 5 ) + f1(c, d, e) + nblk[9] )
1221
+ c =rol( c, 30 )
1222
+ e = truncadd( e, rol( a, 5 ) + f1(b, c, d) + nblk[10] )
1223
+ b =rol( b, 30 )
1224
+ d = d = truncadd( d, rol( e, 5 ) + f1(a, b, c) + nblk[11] )
1225
+ a =rol( a, 30 )
1226
+ c = truncadd( c, rol( d, 5 ) + f1(e, a, b) + nblk[12] )
1227
+ e =rol( e, 30 )
1228
+ b = truncadd( b, rol( c, 5 ) + f1(d, e, a) + nblk[13] )
1229
+ d =rol( d, 30 )
1230
+ a = truncadd( a, rol( b, 5 ) + f1(c, d, e) + nblk[14] )
1231
+ c =rol( c, 30 )
1232
+ e = truncadd( e, rol( a, 5 ) + f1(b, c, d) + nblk[15] )
1233
+ b =rol( b, 30 )
1234
+ d = d = truncadd( d, rol( e, 5 ) + f1(a, b, c) + nblk[16] )
1235
+ a =rol( a, 30 )
1236
+ c = truncadd( c, rol( d, 5 ) + f1(e, a, b) + nblk[17] )
1237
+ e =rol( e, 30 )
1238
+ b = truncadd( b, rol( c, 5 ) + f1(d, e, a) + nblk[18] )
1239
+ d =rol( d, 30 )
1240
+ a = truncadd( a, rol( b, 5 ) + f1(c, d, e) + nblk[19] )
1241
+ c =rol( c, 30 )
1242
+ e = truncadd( e, rol( a, 5 ) + f2(b, c, d) + nblk[20] )
1243
+ b =rol( b, 30 )
1244
+ d = d = truncadd( d, rol( e, 5 ) + f2(a, b, c) + nblk[21] )
1245
+ a =rol( a, 30 )
1246
+ c = truncadd( c, rol( d, 5 ) + f2(e, a, b) + nblk[22] )
1247
+ e =rol( e, 30 )
1248
+ b = truncadd( b, rol( c, 5 ) + f2(d, e, a) + nblk[23] )
1249
+ d =rol( d, 30 )
1250
+ a = truncadd( a, rol( b, 5 ) + f2(c, d, e) + nblk[24] )
1251
+ c =rol( c, 30 )
1252
+ e = truncadd( e, rol( a, 5 ) + f2(b, c, d) + nblk[25] )
1253
+ b =rol( b, 30 )
1254
+ d = d = truncadd( d, rol( e, 5 ) + f2(a, b, c) + nblk[26] )
1255
+ a =rol( a, 30 )
1256
+ c = truncadd( c, rol( d, 5 ) + f2(e, a, b) + nblk[27] )
1257
+ e =rol( e, 30 )
1258
+ b = truncadd( b, rol( c, 5 ) + f2(d, e, a) + nblk[28] )
1259
+ d =rol( d, 30 )
1260
+ a = truncadd( a, rol( b, 5 ) + f2(c, d, e) + nblk[29] )
1261
+ c =rol( c, 30 )
1262
+ e = truncadd( e, rol( a, 5 ) + f2(b, c, d) + nblk[30] )
1263
+ b =rol( b, 30 )
1264
+ d = d = truncadd( d, rol( e, 5 ) + f2(a, b, c) + nblk[31] )
1265
+ a =rol( a, 30 )
1266
+ c = truncadd( c, rol( d, 5 ) + f2(e, a, b) + nblk[32] )
1267
+ e =rol( e, 30 )
1268
+ b = truncadd( b, rol( c, 5 ) + f2(d, e, a) + nblk[33] )
1269
+ d =rol( d, 30 )
1270
+ a = truncadd( a, rol( b, 5 ) + f2(c, d, e) + nblk[34] )
1271
+ c =rol( c, 30 )
1272
+ e = truncadd( e, rol( a, 5 ) + f2(b, c, d) + nblk[35] )
1273
+ b =rol( b, 30 )
1274
+ d = d = truncadd( d, rol( e, 5 ) + f2(a, b, c) + nblk[36] )
1275
+ a =rol( a, 30 )
1276
+ c = truncadd( c, rol( d, 5 ) + f2(e, a, b) + nblk[37] )
1277
+ e =rol( e, 30 )
1278
+ b = truncadd( b, rol( c, 5 ) + f2(d, e, a) + nblk[38] )
1279
+ d =rol( d, 30 )
1280
+ a = truncadd( a, rol( b, 5 ) + f2(c, d, e) + nblk[39] )
1281
+ c =rol( c, 30 )
1282
+ e = truncadd( e, rol( a, 5 ) + f3(b, c, d) + nblk[40] )
1283
+ b =rol( b, 30 )
1284
+ d = d = truncadd( d, rol( e, 5 ) + f3(a, b, c) + nblk[41] )
1285
+ a =rol( a, 30 )
1286
+ c = truncadd( c, rol( d, 5 ) + f3(e, a, b) + nblk[42] )
1287
+ e =rol( e, 30 )
1288
+ b = truncadd( b, rol( c, 5 ) + f3(d, e, a) + nblk[43] )
1289
+ d =rol( d, 30 )
1290
+ a = truncadd( a, rol( b, 5 ) + f3(c, d, e) + nblk[44] )
1291
+ c =rol( c, 30 )
1292
+ e = truncadd( e, rol( a, 5 ) + f3(b, c, d) + nblk[45] )
1293
+ b =rol( b, 30 )
1294
+ d = d = truncadd( d, rol( e, 5 ) + f3(a, b, c) + nblk[46] )
1295
+ a =rol( a, 30 )
1296
+ c = truncadd( c, rol( d, 5 ) + f3(e, a, b) + nblk[47] )
1297
+ e =rol( e, 30 )
1298
+ b = truncadd( b, rol( c, 5 ) + f3(d, e, a) + nblk[48] )
1299
+ d =rol( d, 30 )
1300
+ a = truncadd( a, rol( b, 5 ) + f3(c, d, e) + nblk[49] )
1301
+ c =rol( c, 30 )
1302
+ e = truncadd( e, rol( a, 5 ) + f3(b, c, d) + nblk[50] )
1303
+ b =rol( b, 30 )
1304
+ d = d = truncadd( d, rol( e, 5 ) + f3(a, b, c) + nblk[51] )
1305
+ a =rol( a, 30 )
1306
+ c = truncadd( c, rol( d, 5 ) + f3(e, a, b) + nblk[52] )
1307
+ e =rol( e, 30 )
1308
+ b = truncadd( b, rol( c, 5 ) + f3(d, e, a) + nblk[53] )
1309
+ d =rol( d, 30 )
1310
+ a = truncadd( a, rol( b, 5 ) + f3(c, d, e) + nblk[54] )
1311
+ c =rol( c, 30 )
1312
+ e = truncadd( e, rol( a, 5 ) + f3(b, c, d) + nblk[55] )
1313
+ b =rol( b, 30 )
1314
+ d = d = truncadd( d, rol( e, 5 ) + f3(a, b, c) + nblk[56] )
1315
+ a =rol( a, 30 )
1316
+ c = truncadd( c, rol( d, 5 ) + f3(e, a, b) + nblk[57] )
1317
+ e =rol( e, 30 )
1318
+ b = truncadd( b, rol( c, 5 ) + f3(d, e, a) + nblk[58] )
1319
+ d =rol( d, 30 )
1320
+ a = truncadd( a, rol( b, 5 ) + f3(c, d, e) + nblk[59] )
1321
+ c =rol( c, 30 )
1322
+ e = truncadd( e, rol( a, 5 ) + f4(b, c, d) + nblk[60] )
1323
+ b =rol( b, 30 )
1324
+ d = d = truncadd( d, rol( e, 5 ) + f4(a, b, c) + nblk[61] )
1325
+ a =rol( a, 30 )
1326
+ c = truncadd( c, rol( d, 5 ) + f4(e, a, b) + nblk[62] )
1327
+ e =rol( e, 30 )
1328
+ b = truncadd( b, rol( c, 5 ) + f4(d, e, a) + nblk[63] )
1329
+ d =rol( d, 30 )
1330
+ a = truncadd( a, rol( b, 5 ) + f4(c, d, e) + nblk[64] )
1331
+ c =rol( c, 30 )
1332
+ e = truncadd( e, rol( a, 5 ) + f4(b, c, d) + nblk[65] )
1333
+ b =rol( b, 30 )
1334
+ d = d = truncadd( d, rol( e, 5 ) + f4(a, b, c) + nblk[66] )
1335
+ a =rol( a, 30 )
1336
+ c = truncadd( c, rol( d, 5 ) + f4(e, a, b) + nblk[67] )
1337
+ e =rol( e, 30 )
1338
+ b = truncadd( b, rol( c, 5 ) + f4(d, e, a) + nblk[68] )
1339
+ d =rol( d, 30 )
1340
+ a = truncadd( a, rol( b, 5 ) + f4(c, d, e) + nblk[69] )
1341
+ c =rol( c, 30 )
1342
+ e = truncadd( e, rol( a, 5 ) + f4(b, c, d) + nblk[70] )
1343
+ b =rol( b, 30 )
1344
+ d = d = truncadd( d, rol( e, 5 ) + f4(a, b, c) + nblk[71] )
1345
+ a =rol( a, 30 )
1346
+ c = truncadd( c, rol( d, 5 ) + f4(e, a, b) + nblk[72] )
1347
+ e =rol( e, 30 )
1348
+ b = truncadd( b, rol( c, 5 ) + f4(d, e, a) + nblk[73] )
1349
+ d =rol( d, 30 )
1350
+ a = truncadd( a, rol( b, 5 ) + f4(c, d, e) + nblk[74] )
1351
+ c =rol( c, 30 )
1352
+ e = truncadd( e, rol( a, 5 ) + f4(b, c, d) + nblk[75] )
1353
+ b =rol( b, 30 )
1354
+ d = d = truncadd( d, rol( e, 5 ) + f4(a, b, c) + nblk[76] )
1355
+ a =rol( a, 30 )
1356
+ c = truncadd( c, rol( d, 5 ) + f4(e, a, b) + nblk[77] )
1357
+ e =rol( e, 30 )
1358
+ b = truncadd( b, rol( c, 5 ) + f4(d, e, a) + nblk[78] )
1359
+ d =rol( d, 30 )
1360
+ a = truncadd( a, rol( b, 5 ) + f4(c, d, e) + nblk[79] )
1361
+ c =rol( c, 30 )
1362
+
1363
+ # Update state
1364
+ @state[0] = truncate( a + @state[0] )
1365
+ @state[1] = truncate( b + @state[1] )
1366
+ @state[2] = truncate( c + @state[2] )
1367
+ @state[3] = truncate( d + @state[3] )
1368
+ @state[4] = truncate( e + @state[4] )
1369
+
1370
+ end
1371
+
1372
+ # Roll left; truncate to 32 bits.
1373
+ def rol( val, steps )
1374
+ return truncate( val << steps )|truncate( val >> 32-steps )
1375
+ end
1376
+
1377
+ # Truncates to 32 bits to prevent Fixnum from becoming Bignum
1378
+ def truncate( val )
1379
+ return val & 0xffffffff
1380
+ end
1381
+
1382
+ # Truncate-and-add function
1383
+ def truncadd( v1, v2, v3=0, v4=0 )
1384
+ return truncate( v1+v2+v3+v4 )
1385
+ end
1386
+
1387
+ # Round 1 mix function
1388
+ def f1( a, b, c )
1389
+ return truncate((c^(a&(b^c))) + @constants[0])
1390
+ end
1391
+
1392
+ # Round 2 mix function
1393
+ def f2( a, b, c )
1394
+ return truncate((a^b^c) + @constants[1])
1395
+ end
1396
+
1397
+ # Round 3 mix function
1398
+ def f3( a, b, c )
1399
+ return truncate(((a&b)|(c&(a|b))) + @constants[2])
1400
+ end
1401
+
1402
+ # Round 4 mix function
1403
+ def f4( a, b, c )
1404
+ return truncate((a^b^c) + @constants[3])
1405
+ end
1406
+
1407
+ attr_accessor :state, :buffer, :counter
1408
+ end
1409
+ end