aspera-cli 4.19.0 → 4.21.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +46 -0
  4. data/CONTRIBUTING.md +18 -4
  5. data/README.md +886 -510
  6. data/bin/asession +27 -20
  7. data/examples/build_exec +65 -76
  8. data/examples/build_exec_rubyc +40 -0
  9. data/examples/get_proto_file.rb +7 -0
  10. data/lib/aspera/agent/alpha.rb +18 -24
  11. data/lib/aspera/agent/base.rb +2 -18
  12. data/lib/aspera/agent/connect.rb +34 -15
  13. data/lib/aspera/agent/direct.rb +44 -54
  14. data/lib/aspera/agent/httpgw.rb +2 -3
  15. data/lib/aspera/agent/node.rb +11 -21
  16. data/lib/aspera/agent/{trsdk.rb → transferd.rb} +27 -51
  17. data/lib/aspera/api/alee.rb +15 -0
  18. data/lib/aspera/api/aoc.rb +139 -105
  19. data/lib/aspera/api/ats.rb +1 -1
  20. data/lib/aspera/api/cos_node.rb +1 -1
  21. data/lib/aspera/api/httpgw.rb +15 -10
  22. data/lib/aspera/api/node.rb +70 -32
  23. data/lib/aspera/ascmd.rb +56 -48
  24. data/lib/aspera/ascp/installation.rb +166 -70
  25. data/lib/aspera/ascp/management.rb +30 -8
  26. data/lib/aspera/assert.rb +10 -5
  27. data/lib/aspera/cli/formatter.rb +166 -162
  28. data/lib/aspera/cli/hints.rb +2 -1
  29. data/lib/aspera/cli/info.rb +12 -10
  30. data/lib/aspera/cli/main.rb +28 -13
  31. data/lib/aspera/cli/manager.rb +7 -2
  32. data/lib/aspera/cli/plugin.rb +17 -31
  33. data/lib/aspera/cli/plugins/alee.rb +3 -3
  34. data/lib/aspera/cli/plugins/aoc.rb +246 -208
  35. data/lib/aspera/cli/plugins/ats.rb +16 -14
  36. data/lib/aspera/cli/plugins/config.rb +154 -94
  37. data/lib/aspera/cli/plugins/console.rb +3 -3
  38. data/lib/aspera/cli/plugins/cos.rb +1 -0
  39. data/lib/aspera/cli/plugins/faspex.rb +15 -23
  40. data/lib/aspera/cli/plugins/faspex5.rb +64 -50
  41. data/lib/aspera/cli/plugins/faspio.rb +2 -2
  42. data/lib/aspera/cli/plugins/httpgw.rb +1 -1
  43. data/lib/aspera/cli/plugins/node.rb +174 -109
  44. data/lib/aspera/cli/plugins/orchestrator.rb +14 -13
  45. data/lib/aspera/cli/plugins/preview.rb +8 -9
  46. data/lib/aspera/cli/plugins/server.rb +5 -9
  47. data/lib/aspera/cli/plugins/shares.rb +2 -2
  48. data/lib/aspera/cli/sync_actions.rb +2 -2
  49. data/lib/aspera/cli/transfer_agent.rb +12 -14
  50. data/lib/aspera/cli/transfer_progress.rb +37 -17
  51. data/lib/aspera/cli/version.rb +1 -1
  52. data/lib/aspera/command_line_builder.rb +4 -5
  53. data/lib/aspera/coverage.rb +13 -1
  54. data/lib/aspera/environment.rb +75 -25
  55. data/lib/aspera/faspex_gw.rb +2 -2
  56. data/lib/aspera/json_rpc.rb +1 -1
  57. data/lib/aspera/keychain/macos_security.rb +7 -12
  58. data/lib/aspera/log.rb +3 -4
  59. data/lib/aspera/node_simulator.rb +230 -112
  60. data/lib/aspera/oauth/base.rb +64 -83
  61. data/lib/aspera/oauth/factory.rb +52 -6
  62. data/lib/aspera/oauth/generic.rb +4 -8
  63. data/lib/aspera/oauth/jwt.rb +6 -3
  64. data/lib/aspera/oauth/url_json.rb +1 -2
  65. data/lib/aspera/oauth/web.rb +5 -2
  66. data/lib/aspera/persistency_action_once.rb +16 -8
  67. data/lib/aspera/persistency_folder.rb +20 -2
  68. data/lib/aspera/preview/generator.rb +1 -1
  69. data/lib/aspera/preview/utils.rb +11 -17
  70. data/lib/aspera/products/alpha.rb +30 -0
  71. data/lib/aspera/products/connect.rb +48 -0
  72. data/lib/aspera/products/other.rb +82 -0
  73. data/lib/aspera/products/transferd.rb +54 -0
  74. data/lib/aspera/rest.rb +116 -87
  75. data/lib/aspera/secret_hider.rb +2 -2
  76. data/lib/aspera/ssh.rb +31 -24
  77. data/lib/aspera/transfer/faux_file.rb +4 -4
  78. data/lib/aspera/transfer/parameters.rb +16 -17
  79. data/lib/aspera/transfer/spec.rb +12 -12
  80. data/lib/aspera/transfer/spec.yaml +22 -20
  81. data/lib/aspera/transfer/sync.rb +2 -10
  82. data/lib/aspera/transfer/uri.rb +3 -3
  83. data/lib/aspera/uri_reader.rb +1 -1
  84. data/lib/aspera/web_auth.rb +166 -17
  85. data/lib/aspera/web_server_simple.rb +4 -3
  86. data/lib/transferd_pb.rb +86 -0
  87. data/lib/transferd_services_pb.rb +84 -0
  88. data.tar.gz.sig +0 -0
  89. metadata +58 -22
  90. metadata.gz.sig +0 -0
  91. data/lib/aspera/ascp/products.rb +0 -156
@@ -16,8 +16,6 @@ module Aspera
16
16
  class Node < Aspera::Rest
17
17
  SCOPE_SEPARATOR = ':'
18
18
  SCOPE_NODE_PREFIX = 'node.'
19
- # prefix for ruby code for filter (deprecated)
20
- MATCH_EXEC_PREFIX = 'exec:'
21
19
  MATCH_TYPES = [String, Proc, Regexp, NilClass].freeze
22
20
  SIGNATURE_DELIMITER = '==SIGNATURE=='
23
21
  BEARER_TOKEN_VALIDITY_DEFAULT = 86400
@@ -25,7 +23,7 @@ module Aspera
25
23
  REQUIRED_APP_INFO_FIELDS = %i[api app node_info workspace_id workspace_name].freeze
26
24
  # methods of @app_info[:api]
27
25
  REQUIRED_APP_API_METHODS = %i[node_api_from add_ts_tags].freeze
28
- private_constant :SCOPE_SEPARATOR, :SCOPE_NODE_PREFIX, :MATCH_EXEC_PREFIX, :MATCH_TYPES,
26
+ private_constant :SCOPE_SEPARATOR, :SCOPE_NODE_PREFIX, :MATCH_TYPES,
29
27
  :SIGNATURE_DELIMITER, :BEARER_TOKEN_VALIDITY_DEFAULT,
30
28
  :REQUIRED_APP_INFO_FIELDS, :REQUIRED_APP_API_METHODS
31
29
 
@@ -33,6 +31,8 @@ module Aspera
33
31
  ACCESS_LEVELS = %w[delete list mkdir preview read rename write].freeze
34
32
  HEADER_X_ASPERA_ACCESS_KEY = 'X-Aspera-AccessKey'
35
33
  HEADER_X_TOTAL_COUNT = 'X-Total-Count'
34
+ HEADER_X_CACHE_CONTROL = 'X-Aspera-Cache-Control'
35
+ HEADER_X_NEXT_ITER_TOKEN = 'X-Aspera-Next-Iteration-Token'
36
36
  SCOPE_USER = 'user:all'
37
37
  SCOPE_ADMIN = 'admin:all'
38
38
  PATH_SEPARATOR = '/'
@@ -42,22 +42,23 @@ module Aspera
42
42
 
43
43
  # class instance variable, access with accessors on class
44
44
  @use_standard_ports = true
45
+ @use_node_cache = true
45
46
 
46
47
  class << self
48
+ # set to false to read transfer parameters from download_setup
47
49
  attr_accessor :use_standard_ports
50
+ # set to false to bypass cache in redis
51
+ attr_accessor :use_node_cache
48
52
 
49
53
  # For access keys: provide expression to match entry in folder
54
+ # @param match_expression one of supported types
55
+ # @return lambda function
50
56
  def file_matcher(match_expression)
51
57
  case match_expression
52
58
  when Proc then return match_expression
53
59
  when Regexp then return ->(f){f['name'].match?(match_expression)}
54
60
  when String
55
- if match_expression.start_with?(MATCH_EXEC_PREFIX)
56
- code = "->(f){#{match_expression[MATCH_EXEC_PREFIX.length..-1]}}"
57
- Log.log.warn{"Use of prefix #{MATCH_EXEC_PREFIX} is deprecated (4.15), instead use: @ruby:'#{code}'"}
58
- return Environment.secure_eval(code, __FILE__, __LINE__)
59
- end
60
- return lambda{|f|File.fnmatch(match_expression, f['name'], File::FNM_DOTMATCH)}
61
+ return ->(f){File.fnmatch(match_expression, f['name'], File::FNM_DOTMATCH)}
61
62
  when NilClass then return ->(_){true}
62
63
  else Aspera.error_unexpected_value(match_expression.class.name, exception_class: Cli::BadArgument)
63
64
  end
@@ -146,6 +147,17 @@ module Aspera
146
147
  end
147
148
  end
148
149
 
150
+ # Call node API, possibly adding cache control header, as globally specified
151
+ def read_with_cache(subpath, query=nil)
152
+ headers = {'Accept' => 'application/json'}
153
+ headers[HEADER_X_CACHE_CONTROL] = 'no-cache' unless self.class.use_node_cache
154
+ return call(
155
+ operation: 'GET',
156
+ subpath: subpath,
157
+ headers: headers,
158
+ query: query)[:data]
159
+ end
160
+
149
161
  # update transfer spec with special additional tags
150
162
  def add_tspec_info(tspec)
151
163
  tspec.deep_merge!(@add_tspec) unless @add_tspec.nil?
@@ -171,7 +183,7 @@ module Aspera
171
183
  def entry_has_link_information(entry)
172
184
  # if target information is missing in folder, try to get it on entry
173
185
  if entry['target_node_id'].nil? || entry['target_id'].nil?
174
- link_entry = read("files/#{entry['id']}")[:data]
186
+ link_entry = read("files/#{entry['id']}")
175
187
  entry['target_node_id'] = link_entry['target_node_id']
176
188
  entry['target_id'] = link_entry['target_id']
177
189
  end
@@ -183,11 +195,11 @@ module Aspera
183
195
  # Recursively browse in a folder (with non-recursive method)
184
196
  # sub folders are processed if the processing method returns true
185
197
  # links are processed on the respective node
198
+ # @param method_sym [Symbol] processing method, arguments: entry, path, state
186
199
  # @param state [Object] state object sent to processing method
187
200
  # @param top_file_id [String] file id to start at (default = access key root file id)
188
201
  # @param top_file_path [String] path of top folder (default = /)
189
- # @param block [Proc] processing method, arguments: entry, path, state
190
- def process_folder_tree(method_sym:, state:, top_file_id:, top_file_path: '/')
202
+ def process_folder_tree(method_sym:, state:, top_file_id:, top_file_path: '/', query: nil)
191
203
  Aspera.assert(!top_file_path.nil?){'top_file_path not set'}
192
204
  Log.log.debug{"process_folder_tree: node=#{@app_info ? @app_info[:node_info]['id'] : 'nil'}, file id=#{top_file_id}, path=#{top_file_path}"}
193
205
  # start at top folder
@@ -200,44 +212,51 @@ module Aspera
200
212
  # get folder content
201
213
  folder_contents =
202
214
  begin
203
- read("files/#{current_item[:id]}/files")[:data]
215
+ # TODO: use header
216
+ read_with_cache("files/#{current_item[:id]}/files")
204
217
  rescue StandardError => e
205
218
  Log.log.warn{"#{current_item[:path]}: #{e.class} #{e.message}"}
206
219
  []
207
220
  end
208
221
  Log.log.debug{Log.dump(:folder_contents, folder_contents)}
209
222
  folder_contents.each do |entry|
210
- relative_path = File.join(current_item[:path], entry['name'])
211
- Log.log.debug{"process_folder_tree: checking #{relative_path}"}
223
+ if entry.key?('error')
224
+ if entry['error'].is_a?(Hash) && entry['error'].key?('user_message')
225
+ Log.log.error(entry['error']['user_message'])
226
+ end
227
+ next
228
+ end
229
+ current_path = File.join(current_item[:path], entry['name'])
230
+ Log.log.debug{"process_folder_tree: checking #{current_path}"}
212
231
  # call block, continue only if method returns true
213
- next unless send(method_sym, entry, relative_path, state)
232
+ next unless send(method_sym, entry, current_path, state)
214
233
  # entry type is file, folder or link
215
234
  case entry['type']
216
235
  when 'folder'
217
- folders_to_explore.push({id: entry['id'], path: relative_path})
236
+ folders_to_explore.push({id: entry['id'], path: current_path})
218
237
  when 'link'
219
238
  if entry_has_link_information(entry)
220
239
  node_id_to_node(entry['target_node_id'])&.process_folder_tree(
221
240
  method_sym: method_sym,
222
241
  state: state,
223
242
  top_file_id: entry['target_id'],
224
- top_file_path: relative_path)
243
+ top_file_path: current_path)
225
244
  end
226
245
  end
227
246
  end
228
247
  end
229
248
  end
230
249
 
231
- # Navigate the path from given file id
250
+ # Navigate the path from given file id on current node, and return the node and file id of target.
251
+ # If the path ends with a "/" or process_last_link is true then if the last item in path is a link, it is followed.
232
252
  # @param top_file_id [String] id initial file id
233
- # @param path [String] file path
253
+ # @param path [String] file or folder path (end with "/" is like setting process_last_link)
254
+ # @param process_last_link [Boolean] if true, follow the last link
234
255
  # @return [Hash] {.api,.file_id}
235
- def resolve_api_fid(top_file_id, path)
256
+ def resolve_api_fid(top_file_id, path, process_last_link=false)
236
257
  Aspera.assert_type(top_file_id, String)
237
258
  Aspera.assert_type(path, String)
238
- # if last element is a link and followed by "/", we list the content of that folder, else we return the link
239
- process_last_link = path.end_with?(PATH_SEPARATOR)
240
- # keep only non-empty elements
259
+ process_last_link ||= path.end_with?(PATH_SEPARATOR)
241
260
  path_elements = path.split(PATH_SEPARATOR).reject(&:empty?)
242
261
  return {api: self, file_id: top_file_id} if path_elements.empty?
243
262
  resolve_state = {path: path_elements, result: nil, process_last_link: process_last_link}
@@ -247,13 +266,19 @@ module Aspera
247
266
  return resolve_state[:result]
248
267
  end
249
268
 
250
- def find_files(top_file_id, test_block)
269
+ def find_files(top_file_id, test_lambda)
251
270
  Log.log.debug{"find_files: file id=#{top_file_id}"}
252
- find_state = {found: [], test_block: test_block}
271
+ find_state = {found: [], test_lambda: test_lambda}
253
272
  process_folder_tree(method_sym: :process_find_files, state: find_state, top_file_id: top_file_id)
254
273
  return find_state[:found]
255
274
  end
256
275
 
276
+ def list_files(top_file_id)
277
+ find_state = {found: []}
278
+ process_folder_tree(method_sym: :process_list_files, state: find_state, top_file_id: top_file_id)
279
+ return find_state[:found]
280
+ end
281
+
257
282
  def refreshed_transfer_token
258
283
  return oauth.token(refresh: true)
259
284
  end
@@ -265,7 +290,7 @@ module Aspera
265
290
  full_spec = create(
266
291
  'files/download_setup',
267
292
  {transfer_requests: [{transfer_request: {paths: [{source: '/'}]}}]}
268
- )[:data]['transfer_specs'].first['transfer_spec']
293
+ )['transfer_specs'].first['transfer_spec']
269
294
  # set available fields
270
295
  @std_t_spec_cache = Transfer::Spec::TRANSPORT_FIELDS.each_with_object({}) do |i, h|
271
296
  h[i] = full_spec[i] if full_spec.key?(i)
@@ -275,6 +300,9 @@ module Aspera
275
300
  end
276
301
 
277
302
  # Create transfer spec for gen4
303
+ # @param file_id destination or source folder (id)
304
+ # @param direction one of Transfer::Spec::DIRECTION_SEND, Transfer::Spec::DIRECTION_RECEIVE
305
+ # @param ts_merge additional transfer spec to merge
278
306
  def transfer_spec_gen4(file_id, direction, ts_merge=nil)
279
307
  ak_name = nil
280
308
  ak_token = nil
@@ -288,6 +316,9 @@ module Aspera
288
316
  # TODO: token_generation_lambda = lambda{|do_refresh|oauth.token(refresh: do_refresh)}
289
317
  # get bearer token, possibly use cache
290
318
  ak_token = oauth.token
319
+ when :none
320
+ ak_name = params[:headers][HEADER_X_ASPERA_ACCESS_KEY]
321
+ ak_token = params[:headers]['Authorization']
291
322
  else Aspera.error_unexpected_value(auth_params[:type])
292
323
  end
293
324
  transfer_spec = {
@@ -317,13 +348,13 @@ module Aspera
317
348
  if !@app_info.nil? && !@app_info[:node_info]['transfer_url'].nil? && !@app_info[:node_info]['transfer_url'].empty?
318
349
  transfer_spec['remote_host'] = @app_info[:node_info]['transfer_url']
319
350
  end
320
- info = read('info')[:data]
351
+ info = read('info')
321
352
  # get the transfer user from info on access key
322
353
  transfer_spec['remote_user'] = info['transfer_user'] if info['transfer_user']
323
354
  # get settings from name.value array to hash key.value
324
355
  settings = info['settings']&.each_with_object({}){|i, h|h[i['name']] = i['value']}
325
356
  # check WSS ports
326
- %w[wss_enabled wss_port].each do |i|
357
+ Transfer::Spec::WSS_FIELDS.each do |i|
327
358
  transfer_spec[i] = settings[i] if settings.key?(i)
328
359
  end if settings.is_a?(Hash)
329
360
  else
@@ -336,8 +367,8 @@ module Aspera
336
367
 
337
368
  private
338
369
 
370
+ # method called in loop for each entry for `resolve_api_fid`
339
371
  def process_api_fid(entry, path, state)
340
- # this block is called recursively for each entry in folder
341
372
  # stop digging here if not in right path
342
373
  return false unless entry['name'].eql?(state[:path].first)
343
374
  # ok it matches, so we remove the match, and continue digging
@@ -380,11 +411,18 @@ module Aspera
380
411
  return true
381
412
  end
382
413
 
383
- def process_find_files(entry, _path, state)
384
- state[:found].push(entry.merge({'path' => path})) if state[:test_block].call(entry)
414
+ # method called in loop for each entry for `find_files`
415
+ def process_find_files(entry, path, state)
416
+ state[:found].push(entry.merge({'path' => path})) if state[:test_lambda].call(entry)
385
417
  # test all files deeply
386
418
  return true
387
419
  end
420
+
421
+ # method called in loop for each entry for `list_files`
422
+ def process_list_files(entry, path, state)
423
+ state[:found].push(entry.merge({'path' => path}))
424
+ return false
425
+ end
388
426
  end
389
427
  end
390
428
  end
data/lib/aspera/ascmd.rb CHANGED
@@ -22,7 +22,54 @@ module Aspera
22
22
  mv: 2,
23
23
  rm: 1
24
24
  }.freeze
25
- private_constant :OPS_ARGS
25
+
26
+ # protocol is based on Type-Length-Value
27
+ # type start at one, but array index start at zero
28
+ ENUM_START = 1
29
+
30
+ # description of result structures (see ascmdtypes.h).
31
+ # Base types are big endian
32
+ # key = name of type
33
+ # index in array `fields` is the type (minus ENUM_START)
34
+ # decoding always start at `result`
35
+ # some fields have special handling indicated by `special`
36
+ # field_list, list_tlv_list, list_tlv_restart are composed with a list of TLV
37
+ TYPES_DESCR = {
38
+ result: {decode: :field_list,
39
+ fields: [{name: :file, is_a: :stat}, {name: :dir, is_a: :stat, special: :list_tlv_list}, {name: :size, is_a: :size}, {name: :error, is_a: :error},
40
+ {name: :info, is_a: :info}, {name: :success, is_a: nil, special: :return_true}, {name: :exit, is_a: nil},
41
+ {name: :df, is_a: :mnt, special: :list_tlv_restart}, {name: :md5sum, is_a: :md5sum}]},
42
+ stat: {decode: :field_list,
43
+ fields: [{name: :name, is_a: :zstr}, {name: :size, is_a: :int64}, {name: :mode, is_a: :int32, check: nil}, {name: :zmode, is_a: :zstr},
44
+ {name: :uid, is_a: :int32, check: nil}, {name: :zuid, is_a: :zstr}, {name: :gid, is_a: :int32, check: nil}, {name: :zgid, is_a: :zstr},
45
+ {name: :ctime, is_a: :epoch}, {name: :zctime, is_a: :zstr}, {name: :mtime, is_a: :epoch}, {name: :zmtime, is_a: :zstr},
46
+ {name: :atime, is_a: :epoch}, {name: :zatime, is_a: :zstr}, {name: :symlink, is_a: :zstr}, {name: :errno, is_a: :int32},
47
+ {name: :errstr, is_a: :zstr}]},
48
+ info: {decode: :field_list,
49
+ fields: [{name: :platform, is_a: :zstr}, {name: :version, is_a: :zstr}, {name: :lang, is_a: :zstr}, {name: :territory, is_a: :zstr},
50
+ {name: :codeset, is_a: :zstr}, {name: :lc_ctype, is_a: :zstr}, {name: :lc_numeric, is_a: :zstr}, {name: :lc_time, is_a: :zstr},
51
+ {name: :lc_all, is_a: :zstr}, {name: :dev, is_a: :zstr, special: :list_multiple}, {name: :browse_caps, is_a: :zstr},
52
+ {name: :protocol, is_a: :zstr}]},
53
+ size: {decode: :field_list,
54
+ fields: [{name: :size, is_a: :int64}, {name: :fcount, is_a: :int32}, {name: :dcount, is_a: :int32}, {name: :failed_fcount, is_a: :int32},
55
+ {name: :failed_dcount, is_a: :int32}]},
56
+ error: {decode: :field_list,
57
+ fields: [{name: :errno, is_a: :int32}, {name: :errstr, is_a: :zstr}]},
58
+ mnt: {decode: :field_list,
59
+ fields: [{name: :fs, is_a: :zstr}, {name: :dir, is_a: :zstr}, {name: :is_a, is_a: :zstr}, {name: :total, is_a: :int64},
60
+ {name: :used, is_a: :int64}, {name: :free, is_a: :int64}, {name: :fcount, is_a: :int64}, {name: :errno, is_a: :int32},
61
+ {name: :errstr, is_a: :zstr}]},
62
+ md5sum: {decode: :field_list, fields: [{name: :md5sum, is_a: :zstr}]},
63
+ int8: {decode: :base, unpack: 'C', size: 1},
64
+ int32: {decode: :base, unpack: 'L>', size: 4},
65
+ int64: {decode: :base, unpack: 'Q>', size: 8},
66
+ epoch: {decode: :base, unpack: 'Q>', size: 8},
67
+ zstr: {decode: :base, unpack: 'Z*'},
68
+ blist: {decode: :buffer_list}
69
+ }.freeze
70
+
71
+ private_constant :TYPES_DESCR, :ENUM_START, :OPS_ARGS
72
+
26
73
  # list of supported actions
27
74
  OPERATIONS = OPS_ARGS.keys.freeze
28
75
 
@@ -55,6 +102,7 @@ module Aspera
55
102
  arg_batches.each do |args|
56
103
  command = [main_command]
57
104
  # enclose arguments in double quotes, protect backslash and double quotes
105
+ # ascmd uses space as token separator, and optional quotes ('") or \ to escape
58
106
  args.each do |v|
59
107
  command.push(%Q{"#{v.gsub(/["\\]/){|s|"\\#{s}"}}"})
60
108
  end
@@ -106,47 +154,6 @@ module Aspera
106
154
  def extended_message; "ascmd: errno=#{@errno} errstr=\"#{@errstr}\" command=#{@command} arguments=#{@arguments&.join(',')}"; end
107
155
  end
108
156
 
109
- # description of result structures (see ascmdtypes.h). Base types are big endian
110
- # key = name of type
111
- TYPES_DESCR = {
112
- result: {decode: :field_list,
113
- fields: [{name: :file, is_a: :stat}, {name: :dir, is_a: :stat, special: :sub_struct}, {name: :size, is_a: :size}, {name: :error, is_a: :error},
114
- {name: :info, is_a: :info}, {name: :success, is_a: nil, special: :return_true}, {name: :exit, is_a: nil},
115
- {name: :df, is_a: :mnt, special: :restart_on_first}, {name: :md5sum, is_a: :md5sum}]},
116
- stat: {decode: :field_list,
117
- fields: [{name: :name, is_a: :zstr}, {name: :size, is_a: :int64}, {name: :mode, is_a: :int32, check: nil}, {name: :zmode, is_a: :zstr},
118
- {name: :uid, is_a: :int32, check: nil}, {name: :zuid, is_a: :zstr}, {name: :gid, is_a: :int32, check: nil}, {name: :zgid, is_a: :zstr},
119
- {name: :ctime, is_a: :epoch}, {name: :zctime, is_a: :zstr}, {name: :mtime, is_a: :epoch}, {name: :zmtime, is_a: :zstr},
120
- {name: :atime, is_a: :epoch}, {name: :zatime, is_a: :zstr}, {name: :symlink, is_a: :zstr}, {name: :errno, is_a: :int32},
121
- {name: :errstr, is_a: :zstr}]},
122
- info: {decode: :field_list,
123
- fields: [{name: :platform, is_a: :zstr}, {name: :version, is_a: :zstr}, {name: :lang, is_a: :zstr}, {name: :territory, is_a: :zstr},
124
- {name: :codeset, is_a: :zstr}, {name: :lc_ctype, is_a: :zstr}, {name: :lc_numeric, is_a: :zstr}, {name: :lc_time, is_a: :zstr},
125
- {name: :lc_all, is_a: :zstr}, {name: :dev, is_a: :zstr, special: :multiple}, {name: :browse_caps, is_a: :zstr},
126
- {name: :protocol, is_a: :zstr}]},
127
- size: {decode: :field_list,
128
- fields: [{name: :size, is_a: :int64}, {name: :fcount, is_a: :int32}, {name: :dcount, is_a: :int32}, {name: :failed_fcount, is_a: :int32},
129
- {name: :failed_dcount, is_a: :int32}]},
130
- error: {decode: :field_list,
131
- fields: [{name: :errno, is_a: :int32}, {name: :errstr, is_a: :zstr}]},
132
- mnt: {decode: :field_list,
133
- fields: [{name: :fs, is_a: :zstr}, {name: :dir, is_a: :zstr}, {name: :is_a, is_a: :zstr}, {name: :total, is_a: :int64},
134
- {name: :used, is_a: :int64}, {name: :free, is_a: :int64}, {name: :fcount, is_a: :int64}, {name: :errno, is_a: :int32},
135
- {name: :errstr, is_a: :zstr}]},
136
- md5sum: {decode: :field_list, fields: [{name: :md5sum, is_a: :zstr}]},
137
- int8: {decode: :base, unpack: 'C', size: 1},
138
- int32: {decode: :base, unpack: 'L>', size: 4},
139
- int64: {decode: :base, unpack: 'Q>', size: 8},
140
- epoch: {decode: :base, unpack: 'Q>', size: 8},
141
- zstr: {decode: :base, unpack: 'Z*'},
142
- blist: {decode: :buffer_list}
143
- }.freeze
144
-
145
- # protocol enum start at one, but array index start at zero
146
- ENUM_START = 1
147
-
148
- private_constant :TYPES_DESCR, :ENUM_START
149
-
150
157
  class << self
151
158
  # get description of structure's field, @param struct_name, @param typed_buffer provides field name
152
159
  def field_description(struct_name, typed_buffer)
@@ -175,6 +182,7 @@ module Aspera
175
182
  Log.log.trace1{"#{' .' * indent_level}-> base:#{byte_array} -> #{result}"}
176
183
  result = Time.at(result) if type_name.eql?(:epoch)
177
184
  when :buffer_list
185
+ # return a list of type_buffer
178
186
  result = []
179
187
  until buffer.empty?
180
188
  btype = parse(buffer, :int8, indent_level)
@@ -193,16 +201,16 @@ module Aspera
193
201
  field_info = field_description(type_name, typed_buffer)
194
202
  Log.log.trace1{"#{' .' * indent_level}+ field(special=#{field_info[:special]})=#{field_info[:name]}".green}
195
203
  case field_info[:special]
196
- when nil
204
+ when nil # normal case
197
205
  result[field_info[:name]] = parse(typed_buffer[:buffer], field_info[:is_a], indent_level)
198
- when :return_true
206
+ when :return_true # nothing to parse, just return true
199
207
  result[field_info[:name]] = true
200
- when :sub_struct
201
- result[field_info[:name]] = parse(typed_buffer[:buffer], :blist, indent_level).map{|r|parse(r[:buffer], field_info[:is_a], indent_level)}
202
- when :multiple
208
+ when :list_multiple # field appears multiple times, and is an array of values (base type)
203
209
  result[field_info[:name]] ||= []
204
210
  result[field_info[:name]].push(parse(typed_buffer[:buffer], field_info[:is_a], indent_level))
205
- when :restart_on_first
211
+ when :list_tlv_list # field is an array of values in a list of buffers
212
+ result[field_info[:name]] = parse(typed_buffer[:buffer], :blist, indent_level).map{|r|parse(r[:buffer], field_info[:is_a], indent_level)}
213
+ when :list_tlv_restart # field is an array of values, but a new value is started on index 1
206
214
  fl = result[field_info[:name]] = []
207
215
  parse(typed_buffer[:buffer], :blist, indent_level).map do |tb|
208
216
  fl.push({}) if tb[:btype].eql?(ENUM_START)