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.
- data/lib/EsuApi.rb +1409 -0
- metadata +98 -0
data/lib/EsuApi.rb
ADDED
|
@@ -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
|