atmos-ruby 1.4.0.7

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