revault_api 0.6.0-aarch64-linux → 0.6.1-aarch64-linux
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.
- checksums.yaml +4 -4
- data/README.md +140 -423
- data/lib/revault/binding_operations.rb +1255 -0
- data/lib/revault/domain_models.rb +808 -0
- data/lib/revault/native_library.rb +80 -0
- data/lib/revault/vault.rb +1600 -0
- data/native/linux-aarch64-gnu/librevault_api.so +0 -0
- data/native/linux-aarch64-gnu/librevault_ruby_shim.so +0 -0
- data/native/revault_ruby_shim.c +557 -0
- data/revault_api.rb +11 -0
- metadata +25 -3
|
@@ -0,0 +1,808 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Revault
|
|
4
|
+
module Bindings
|
|
5
|
+
# Public reciprocal exchange status. Verification is a local trust decision; no management capability or private key is returned.
|
|
6
|
+
class ContactExchangeStatus
|
|
7
|
+
# Random identifier of the saved exchange.
|
|
8
|
+
attr_reader :id
|
|
9
|
+
# Pinned HTTPS relay origin.
|
|
10
|
+
attr_reader :relay
|
|
11
|
+
# Invitation URL to share only with the intended recipient.
|
|
12
|
+
attr_reader :invitation
|
|
13
|
+
# Signed claimed inviter email, not independently verified by the relay.
|
|
14
|
+
attr_reader :inviter_email
|
|
15
|
+
# Signed claimed intended recipient email.
|
|
16
|
+
attr_reader :recipient_email
|
|
17
|
+
# Full shared fingerprint to compare independently; empty until both contributions are available.
|
|
18
|
+
attr_reader :fingerprint
|
|
19
|
+
# Signed invitation expiry in Unix milliseconds.
|
|
20
|
+
attr_reader :expires_ms
|
|
21
|
+
# Whether both signed public key bundles are locally pinned.
|
|
22
|
+
attr_reader :accepted
|
|
23
|
+
# Whether this Vault independently verified and saved the contact.
|
|
24
|
+
attr_reader :verified
|
|
25
|
+
# Local saved contact name, or empty before verification.
|
|
26
|
+
attr_reader :contact_name
|
|
27
|
+
# Whether the relay operation needs retrying; locally saved state is retained.
|
|
28
|
+
attr_reader :relay_pending
|
|
29
|
+
# Creates a domain value from named fields.
|
|
30
|
+
def initialize(id: '', relay: '', invitation: '', inviter_email: '', recipient_email: '', fingerprint: '', expires_ms: 0, accepted: false, verified: false, contact_name: '', relay_pending: false) = (@id = id; @relay = relay; @invitation = invitation; @inviter_email = inviter_email; @recipient_email = recipient_email; @fingerprint = fingerprint; @expires_ms = expires_ms; @accepted = accepted; @verified = verified; @contact_name = contact_name; @relay_pending = relay_pending)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Ordered ContactExchangeStatus values returned by the corresponding list operation.
|
|
34
|
+
class ContactExchangeStatusList
|
|
35
|
+
# Ordered ContactExchangeStatus values returned by the corresponding list operation.
|
|
36
|
+
attr_reader :values
|
|
37
|
+
# Creates a domain value from named fields.
|
|
38
|
+
def initialize(values: []) = (@values = values)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Metadata for one file, directory, or symbolic link stored at a lockbox path.
|
|
42
|
+
class LockboxEntry
|
|
43
|
+
# Absolute lockbox path of the stored entry.
|
|
44
|
+
attr_reader :path
|
|
45
|
+
# Filesystem kind: file, directory, or symbolic link.
|
|
46
|
+
attr_reader :kind
|
|
47
|
+
# Logical file length in bytes; zero for directories.
|
|
48
|
+
attr_reader :length
|
|
49
|
+
# Portable Unix permission bits stored with the entry.
|
|
50
|
+
attr_reader :permissions
|
|
51
|
+
# Creates a domain value from named fields.
|
|
52
|
+
def initialize(path: '', kind: 0, length: 0, permissions: 0) = (@path = path; @kind = kind; @length = length; @permissions = permissions)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Ordered lockbox entries selected by a list operation.
|
|
56
|
+
class LockboxEntryList
|
|
57
|
+
# Ordered lockbox entries selected by a list operation.
|
|
58
|
+
attr_reader :entries
|
|
59
|
+
# Creates a domain value from named fields.
|
|
60
|
+
def initialize(entries: []) = (@entries = entries)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# The metadata found for a lockbox path, or no value when the path is absent.
|
|
64
|
+
class OptionalLockboxEntry
|
|
65
|
+
# The metadata found for a lockbox path, or no value when the path is absent.
|
|
66
|
+
attr_reader :value
|
|
67
|
+
# Creates a domain value from named fields.
|
|
68
|
+
def initialize(value: nil) = (@value = value)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Ordered names or identifiers returned by a vault list operation.
|
|
72
|
+
class StringList
|
|
73
|
+
# Ordered names or identifiers returned by a vault list operation.
|
|
74
|
+
attr_reader :values
|
|
75
|
+
# Creates a domain value from named fields.
|
|
76
|
+
def initialize(values: []) = (@values = values)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# A source and destination pair used to rename a variable or form record atomically.
|
|
80
|
+
class PathMove
|
|
81
|
+
# Existing variable name or form-record path to rename.
|
|
82
|
+
attr_reader :source
|
|
83
|
+
# New variable name or form-record path.
|
|
84
|
+
attr_reader :destination
|
|
85
|
+
# Creates a domain value from named fields.
|
|
86
|
+
def initialize(source: '', destination: '') = (@source = source; @destination = destination)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Atomic variable or form-record renames supplied to a move operation.
|
|
90
|
+
class PathMoveList
|
|
91
|
+
# Atomic variable or form-record renames supplied to a move operation.
|
|
92
|
+
attr_reader :values
|
|
93
|
+
# Creates a domain value from named fields.
|
|
94
|
+
def initialize(values: []) = (@values = values)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# One named input in a reusable form definition, including its display label and sensitivity kind.
|
|
98
|
+
class FormField
|
|
99
|
+
# Stable field identifier used when reading and writing records.
|
|
100
|
+
attr_reader :id
|
|
101
|
+
# Human-readable label presented to a person entering data.
|
|
102
|
+
attr_reader :label
|
|
103
|
+
# Field kind that determines validation and secret handling.
|
|
104
|
+
attr_reader :kind
|
|
105
|
+
# Whether a record must provide a value for this field.
|
|
106
|
+
attr_reader :required
|
|
107
|
+
# Multiline guidance captured by this field definition revision.
|
|
108
|
+
attr_reader :description
|
|
109
|
+
# Whether guidance is present, including explicitly empty text.
|
|
110
|
+
attr_reader :has_description
|
|
111
|
+
# Creates a domain value from named fields.
|
|
112
|
+
def initialize(id: '', label: '', kind: '', required: false, description: '', has_description: false) = (@id = id; @label = label; @kind = kind; @required = required; @description = description; @has_description = has_description)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Ordered field definitions supplied when defining a form.
|
|
116
|
+
class FormFieldList
|
|
117
|
+
# Ordered field definitions supplied when defining a form.
|
|
118
|
+
attr_reader :values
|
|
119
|
+
# Creates a domain value from named fields.
|
|
120
|
+
def initialize(values: []) = (@values = values)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# A versioned form schema used to validate and label structured records in a lockbox.
|
|
124
|
+
class FormDefinition
|
|
125
|
+
# Stable identifier shared by every revision of this form type.
|
|
126
|
+
attr_reader :type_id
|
|
127
|
+
# Short name used to resolve the current form revision.
|
|
128
|
+
attr_reader :form_alias
|
|
129
|
+
# Monotonically increasing revision number.
|
|
130
|
+
attr_reader :revision
|
|
131
|
+
# Human-readable name shown for this form.
|
|
132
|
+
attr_reader :name
|
|
133
|
+
# Explanation shown to people completing the form.
|
|
134
|
+
attr_reader :description
|
|
135
|
+
# Ordered inputs accepted by this form revision.
|
|
136
|
+
attr_reader :fields
|
|
137
|
+
# Creates a domain value from named fields.
|
|
138
|
+
def initialize(type_id: '', form_alias: '', revision: 0, name: '', description: '', fields: []) = (@type_id = type_id; @form_alias = form_alias; @revision = revision; @name = name; @description = description; @fields = fields)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Ordered FormDefinition values returned by the corresponding list operation.
|
|
142
|
+
class FormDefinitionList
|
|
143
|
+
# Ordered FormDefinition values returned by the corresponding list operation.
|
|
144
|
+
attr_reader :values
|
|
145
|
+
# Creates a domain value from named fields.
|
|
146
|
+
def initialize(values: []) = (@values = values)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# The current value and sensitivity metadata for one field in a stored form record.
|
|
150
|
+
class FormValue
|
|
151
|
+
# Identifier of the form field that owns this value.
|
|
152
|
+
attr_reader :field_id
|
|
153
|
+
# Display label captured from the form revision.
|
|
154
|
+
attr_reader :label
|
|
155
|
+
# Field kind captured from the form revision.
|
|
156
|
+
attr_reader :kind
|
|
157
|
+
# Plain value, or an empty string when the field is secret.
|
|
158
|
+
attr_reader :value
|
|
159
|
+
# Whether the value must be read through a scoped secret callback.
|
|
160
|
+
attr_reader :secret
|
|
161
|
+
# Creates a domain value from named fields.
|
|
162
|
+
def initialize(field_id: '', label: '', kind: '', value: '', secret: false) = (@field_id = field_id; @label = label; @kind = kind; @value = value; @secret = secret)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# A named structured record stored at a lockbox path and tied to a form-definition revision.
|
|
166
|
+
class FormRecord
|
|
167
|
+
# Absolute lockbox path that identifies the record.
|
|
168
|
+
attr_reader :path
|
|
169
|
+
# Human-readable name assigned to this record.
|
|
170
|
+
attr_reader :name
|
|
171
|
+
# Stable identifier of the record's form type.
|
|
172
|
+
attr_reader :type_id
|
|
173
|
+
# Alias of the form definition used by the record.
|
|
174
|
+
attr_reader :definition_alias
|
|
175
|
+
# Exact form revision against which the record was created.
|
|
176
|
+
attr_reader :definition_revision
|
|
177
|
+
# Ordered non-secret field metadata and values.
|
|
178
|
+
attr_reader :values
|
|
179
|
+
# Multiline context specific to this record.
|
|
180
|
+
attr_reader :description
|
|
181
|
+
# Whether the record description is present, including empty text.
|
|
182
|
+
attr_reader :has_description
|
|
183
|
+
# Creates a domain value from named fields.
|
|
184
|
+
def initialize(path: '', name: '', type_id: '', definition_alias: '', definition_revision: 0, values: [], description: '', has_description: false) = (@path = path; @name = name; @type_id = type_id; @definition_alias = definition_alias; @definition_revision = definition_revision; @values = values; @description = description; @has_description = has_description)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Ordered FormRecord values returned by the corresponding list operation.
|
|
188
|
+
class FormRecordList
|
|
189
|
+
# Ordered FormRecord values returned by the corresponding list operation.
|
|
190
|
+
attr_reader :values
|
|
191
|
+
# Creates a domain value from named fields.
|
|
192
|
+
def initialize(values: []) = (@values = values)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# The form record found at a lockbox path, or no value when absent.
|
|
196
|
+
class OptionalFormRecord
|
|
197
|
+
# The form record found at a lockbox path, or no value when absent.
|
|
198
|
+
attr_reader :value
|
|
199
|
+
# Creates a domain value from named fields.
|
|
200
|
+
def initialize(value: nil) = (@value = value)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# The requested non-secret form value, or no value when the field is absent.
|
|
204
|
+
class OptionalFormValue
|
|
205
|
+
# The requested non-secret form value, or no value when the field is absent.
|
|
206
|
+
attr_reader :value
|
|
207
|
+
# Creates a domain value from named fields.
|
|
208
|
+
def initialize(value: nil) = (@value = value)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# The files and metadata recovered, or found damaged, while inspecting or salvaging a lockbox.
|
|
212
|
+
class RecoveryReport
|
|
213
|
+
# Files whose complete contents remain recoverable.
|
|
214
|
+
attr_reader :intact_files
|
|
215
|
+
# Number of completely recoverable files.
|
|
216
|
+
attr_reader :intact_file_count
|
|
217
|
+
# Number of files for which only some content is recoverable.
|
|
218
|
+
attr_reader :partial_files
|
|
219
|
+
# Number of encrypted records that failed validation.
|
|
220
|
+
attr_reader :corrupt_records
|
|
221
|
+
# Whether a usable table of contents was recovered.
|
|
222
|
+
attr_reader :toc_recovered
|
|
223
|
+
# Whether variable metadata was recovered.
|
|
224
|
+
attr_reader :variables_recovered
|
|
225
|
+
# Number of recovered variables.
|
|
226
|
+
attr_reader :variable_count
|
|
227
|
+
# Whether form definitions and records were recovered.
|
|
228
|
+
attr_reader :forms_recovered
|
|
229
|
+
# Number of recovered form definitions.
|
|
230
|
+
attr_reader :form_definition_count
|
|
231
|
+
# Number of recovered form records.
|
|
232
|
+
attr_reader :form_record_count
|
|
233
|
+
# Creates a domain value from named fields.
|
|
234
|
+
def initialize(intact_files: [], intact_file_count: 0, partial_files: 0, corrupt_records: 0, toc_recovered: false, variables_recovered: false, variable_count: 0, forms_recovered: false, form_definition_count: 0, form_record_count: 0) = (@intact_files = intact_files; @intact_file_count = intact_file_count; @partial_files = partial_files; @corrupt_records = corrupt_records; @toc_recovered = toc_recovered; @variables_recovered = variables_recovered; @variable_count = variable_count; @forms_recovered = forms_recovered; @form_definition_count = form_definition_count; @form_record_count = form_record_count)
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# One password or contact credential that can unlock a lockbox content key.
|
|
238
|
+
class KeySlot
|
|
239
|
+
# Stable slot identifier used when removing this access method.
|
|
240
|
+
attr_reader :id
|
|
241
|
+
# Access method, such as password or contact key.
|
|
242
|
+
attr_reader :protection
|
|
243
|
+
# Cryptographic algorithm protecting the content key.
|
|
244
|
+
attr_reader :algorithm
|
|
245
|
+
# Creates a domain value from named fields.
|
|
246
|
+
def initialize(id: 0, protection: '', algorithm: '') = (@id = id; @protection = protection; @algorithm = algorithm)
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
# Ordered KeySlot values returned by the corresponding list operation.
|
|
250
|
+
class KeySlotList
|
|
251
|
+
# Ordered KeySlot values returned by the corresponding list operation.
|
|
252
|
+
attr_reader :values
|
|
253
|
+
# Creates a domain value from named fields.
|
|
254
|
+
def initialize(values: []) = (@values = values)
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# Current capacity, occupancy, hit, and miss counters for an open lockbox cache.
|
|
258
|
+
class CacheStats
|
|
259
|
+
# Maximum decoded-page memory permitted for the cache.
|
|
260
|
+
attr_reader :limit_bytes
|
|
261
|
+
# Decoded-page memory currently held by the cache.
|
|
262
|
+
attr_reader :used_bytes
|
|
263
|
+
# Number of decoded pages currently cached.
|
|
264
|
+
attr_reader :entries
|
|
265
|
+
# Reads served by an already decoded page.
|
|
266
|
+
attr_reader :hits
|
|
267
|
+
# Reads that required decoding another page.
|
|
268
|
+
attr_reader :misses
|
|
269
|
+
# Creates a domain value from named fields.
|
|
270
|
+
def initialize(limit_bytes: 0, used_bytes: 0, entries: 0, hits: 0, misses: 0) = (@limit_bytes = limit_bytes; @used_bytes = used_bytes; @entries = entries; @hits = hits; @misses = misses)
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
# Time spent reading host files and preparing encrypted pages during the latest import work.
|
|
274
|
+
class ImportStats
|
|
275
|
+
# Nanoseconds spent reading host filesystem metadata, as decimal text.
|
|
276
|
+
attr_reader :host_stat_nanos
|
|
277
|
+
# Nanoseconds spent reading host file content, as decimal text.
|
|
278
|
+
attr_reader :host_read_nanos
|
|
279
|
+
# Nanoseconds spent preparing encrypted records, as decimal text.
|
|
280
|
+
attr_reader :frame_prepare_nanos
|
|
281
|
+
# Nanoseconds spent writing encrypted pages, as decimal text.
|
|
282
|
+
attr_reader :page_write_nanos
|
|
283
|
+
# Creates a domain value from named fields.
|
|
284
|
+
def initialize(host_stat_nanos: '', host_read_nanos: '', frame_prepare_nanos: '', page_write_nanos: '') = (@host_stat_nanos = host_stat_nanos; @host_read_nanos = host_read_nanos; @frame_prepare_nanos = frame_prepare_nanos; @page_write_nanos = page_write_nanos)
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
# One logical object recorded inside an inspected encrypted lockbox page.
|
|
288
|
+
class PageObject
|
|
289
|
+
# Object identifier recorded in the encrypted page.
|
|
290
|
+
attr_reader :id
|
|
291
|
+
# Kind of logical object stored in the page.
|
|
292
|
+
attr_reader :kind
|
|
293
|
+
# Encrypted object payload length in bytes.
|
|
294
|
+
attr_reader :payload_len
|
|
295
|
+
# Creates a domain value from named fields.
|
|
296
|
+
def initialize(id: 0, kind: '', payload_len: 0) = (@id = id; @kind = kind; @payload_len = payload_len)
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
# Layout and utilization details for one encrypted page in a lockbox archive.
|
|
300
|
+
class PageInspection
|
|
301
|
+
# Byte offset at which the page begins in the archive.
|
|
302
|
+
attr_reader :offset
|
|
303
|
+
# Identifier stored in the page header.
|
|
304
|
+
attr_reader :page_id
|
|
305
|
+
# Commit sequence that wrote this page.
|
|
306
|
+
attr_reader :sequence
|
|
307
|
+
# Total encoded page size in bytes.
|
|
308
|
+
attr_reader :page_size
|
|
309
|
+
# Encrypted body length in bytes.
|
|
310
|
+
attr_reader :encrypted_body_len
|
|
311
|
+
# Unused capacity remaining in the page.
|
|
312
|
+
attr_reader :unused_bytes
|
|
313
|
+
# Number of logical objects stored in the page.
|
|
314
|
+
attr_reader :object_count
|
|
315
|
+
# Logical objects discovered in the page.
|
|
316
|
+
attr_reader :objects
|
|
317
|
+
# Creates a domain value from named fields.
|
|
318
|
+
def initialize(offset: 0, page_id: 0, sequence: 0, page_size: 0, encrypted_body_len: 0, unused_bytes: 0, object_count: 0, objects: []) = (@offset = offset; @page_id = page_id; @sequence = sequence; @page_size = page_size; @encrypted_body_len = encrypted_body_len; @unused_bytes = unused_bytes; @object_count = object_count; @objects = objects)
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
# Ordered PageInspection values returned by the corresponding list operation.
|
|
322
|
+
class PageInspectionList
|
|
323
|
+
# Ordered PageInspection values returned by the corresponding list operation.
|
|
324
|
+
attr_reader :values
|
|
325
|
+
# Creates a domain value from named fields.
|
|
326
|
+
def initialize(values: []) = (@values = values)
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
# Header, owner-signature, and key-slot information read from a lockbox file without opening its contents.
|
|
330
|
+
class FileInspection
|
|
331
|
+
# Stable binary identifier read from the lockbox header.
|
|
332
|
+
attr_reader :lockbox_id
|
|
333
|
+
# Whether the archive header passed structural validation.
|
|
334
|
+
attr_reader :header_readable
|
|
335
|
+
# Latest readable access-key directory generation.
|
|
336
|
+
attr_reader :key_directory_generation
|
|
337
|
+
# Number of readable redundant key-directory copies.
|
|
338
|
+
attr_reader :key_directory_copy_count
|
|
339
|
+
# Whether commits require an owner signature.
|
|
340
|
+
attr_reader :owner_signed
|
|
341
|
+
# Password and contact access methods found in the header.
|
|
342
|
+
attr_reader :key_slots
|
|
343
|
+
# Creates a domain value from named fields.
|
|
344
|
+
def initialize(lockbox_id: [], header_readable: false, key_directory_generation: 0, key_directory_copy_count: 0, owner_signed: false, key_slots: []) = (@lockbox_id = lockbox_id; @header_readable = header_readable; @key_directory_generation = key_directory_generation; @key_directory_copy_count = key_directory_copy_count; @owner_signed = owner_signed; @key_slots = key_slots)
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
# One active or retired generation of the contact keys belonging to a named vault profile.
|
|
348
|
+
class ProfileGeneration
|
|
349
|
+
# Generation number used to address this key version.
|
|
350
|
+
attr_reader :index
|
|
351
|
+
# Lifecycle state, such as active or retired.
|
|
352
|
+
attr_reader :status
|
|
353
|
+
# Fingerprint of this generation's contact public key.
|
|
354
|
+
attr_reader :contact_fingerprint
|
|
355
|
+
# Creation time in Unix milliseconds.
|
|
356
|
+
attr_reader :created_at_unix_ms
|
|
357
|
+
# Retirement time in Unix milliseconds when retired.
|
|
358
|
+
attr_reader :retired_at_unix_ms
|
|
359
|
+
# Whether a retirement time is present.
|
|
360
|
+
attr_reader :has_retired_at
|
|
361
|
+
# Creates a domain value from named fields.
|
|
362
|
+
def initialize(index: 0, status: '', contact_fingerprint: [], created_at_unix_ms: 0, retired_at_unix_ms: 0, has_retired_at: false) = (@index = index; @status = status; @contact_fingerprint = contact_fingerprint; @created_at_unix_ms = created_at_unix_ms; @retired_at_unix_ms = retired_at_unix_ms; @has_retired_at = has_retired_at)
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
# The active generation and rotation history for a named vault profile.
|
|
366
|
+
class ProfileHistory
|
|
367
|
+
# Vault profile name whose generations are listed.
|
|
368
|
+
attr_reader :name
|
|
369
|
+
# Generation number currently used for new access grants.
|
|
370
|
+
attr_reader :active_generation
|
|
371
|
+
# Active and retired contact-key generations.
|
|
372
|
+
attr_reader :generations
|
|
373
|
+
# Creates a domain value from named fields.
|
|
374
|
+
def initialize(name: '', active_generation: 0, generations: []) = (@name = name; @active_generation = active_generation; @generations = generations)
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
# A local Vault alias mapping a normalized name to a stable lockbox identity; resolution does not open the target.
|
|
378
|
+
class LockboxAlias
|
|
379
|
+
# Case-sensitive normalized NFC alias name.
|
|
380
|
+
attr_reader :name
|
|
381
|
+
# Stable binary target identity; verify it when opening the resolved path.
|
|
382
|
+
attr_reader :lockbox_id
|
|
383
|
+
# Creates a domain value from named fields.
|
|
384
|
+
def initialize(name: '', lockbox_id: []) = (@name = name; @lockbox_id = lockbox_id)
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
# Ordered LockboxAlias values returned by the corresponding list operation.
|
|
388
|
+
class LockboxAliasList
|
|
389
|
+
# Ordered LockboxAlias values returned by the corresponding list operation.
|
|
390
|
+
attr_reader :values
|
|
391
|
+
# Creates a domain value from named fields.
|
|
392
|
+
def initialize(values: []) = (@values = values)
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
# A lockbox identifier and host path remembered by the local vault for later discovery.
|
|
396
|
+
class KnownLockbox
|
|
397
|
+
# Stable binary identifier of the remembered lockbox.
|
|
398
|
+
attr_reader :lockbox_id
|
|
399
|
+
# Last known host filesystem path of the lockbox.
|
|
400
|
+
attr_reader :path
|
|
401
|
+
# Most recent observation time in Unix milliseconds.
|
|
402
|
+
attr_reader :last_seen_unix_ms
|
|
403
|
+
# Creates a domain value from named fields.
|
|
404
|
+
def initialize(lockbox_id: [], path: '', last_seen_unix_ms: 0) = (@lockbox_id = lockbox_id; @path = path; @last_seen_unix_ms = last_seen_unix_ms)
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
# Ordered KnownLockbox values returned by the corresponding list operation.
|
|
408
|
+
class KnownLockboxList
|
|
409
|
+
# Ordered KnownLockbox values returned by the corresponding list operation.
|
|
410
|
+
attr_reader :values
|
|
411
|
+
# Creates a domain value from named fields.
|
|
412
|
+
def initialize(values: []) = (@values = values)
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
# A local human-readable label attached to one lockbox access slot.
|
|
416
|
+
class AccessSlotLabel
|
|
417
|
+
# Lockbox whose access slot is labelled.
|
|
418
|
+
attr_reader :lockbox_id
|
|
419
|
+
# Stable identifier of the labelled access slot.
|
|
420
|
+
attr_reader :slot_id
|
|
421
|
+
# Local human-readable label for the access slot.
|
|
422
|
+
attr_reader :name
|
|
423
|
+
# Last label update time in Unix milliseconds.
|
|
424
|
+
attr_reader :updated_at_unix_ms
|
|
425
|
+
# Creates a domain value from named fields.
|
|
426
|
+
def initialize(lockbox_id: [], slot_id: 0, name: '', updated_at_unix_ms: 0) = (@lockbox_id = lockbox_id; @slot_id = slot_id; @name = name; @updated_at_unix_ms = updated_at_unix_ms)
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
# Ordered AccessSlotLabel values returned by the corresponding list operation.
|
|
430
|
+
class AccessSlotLabelList
|
|
431
|
+
# Ordered AccessSlotLabel values returned by the corresponding list operation.
|
|
432
|
+
attr_reader :values
|
|
433
|
+
# Creates a domain value from named fields.
|
|
434
|
+
def initialize(values: []) = (@values = values)
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
# A logical or physical byte range emitted while walking the contents of a lockbox.
|
|
438
|
+
class StreamChunk
|
|
439
|
+
# Lockbox file path to which this byte range belongs.
|
|
440
|
+
attr_reader :path
|
|
441
|
+
# Logical byte offset within the file.
|
|
442
|
+
attr_reader :file_offset
|
|
443
|
+
# Logical range length in bytes.
|
|
444
|
+
attr_reader :length
|
|
445
|
+
# Archive byte offset, when physical streaming is requested.
|
|
446
|
+
attr_reader :physical_offset
|
|
447
|
+
# Whether the range represents a sparse zero-filled extent.
|
|
448
|
+
attr_reader :sparse
|
|
449
|
+
# File bytes for a populated logical range.
|
|
450
|
+
attr_reader :data
|
|
451
|
+
# Creates a domain value from named fields.
|
|
452
|
+
def initialize(path: '', file_offset: 0, length: 0, physical_offset: 0, sparse: false, data: []) = (@path = path; @file_offset = file_offset; @length = length; @physical_offset = physical_offset; @sparse = sparse; @data = data)
|
|
453
|
+
end
|
|
454
|
+
|
|
455
|
+
# Ordered StreamChunk values returned by the corresponding list operation.
|
|
456
|
+
class StreamChunkList
|
|
457
|
+
# Ordered StreamChunk values returned by the corresponding list operation.
|
|
458
|
+
attr_reader :values
|
|
459
|
+
# Creates a domain value from named fields.
|
|
460
|
+
def initialize(values: []) = (@values = values)
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
# The workload and worker policies currently applied to an open lockbox.
|
|
464
|
+
class RuntimeOptions
|
|
465
|
+
# I/O workload policy used to tune page access.
|
|
466
|
+
attr_reader :workload_profile
|
|
467
|
+
# Worker scheduling policy and effective parallelism.
|
|
468
|
+
attr_reader :worker_policy
|
|
469
|
+
# Creates a domain value from named fields.
|
|
470
|
+
def initialize(workload_profile: '', worker_policy: '') = (@workload_profile = workload_profile; @worker_policy = worker_policy)
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
# The name and sensitivity classification of a variable stored in a lockbox.
|
|
474
|
+
class Variable
|
|
475
|
+
# Name used to address the variable in the lockbox.
|
|
476
|
+
attr_reader :name
|
|
477
|
+
# Whether the value is ordinary text or a protected secret.
|
|
478
|
+
attr_reader :sensitivity
|
|
479
|
+
# Multiline description stored with this variable.
|
|
480
|
+
attr_reader :description
|
|
481
|
+
# Whether the variable description is present, including empty text.
|
|
482
|
+
attr_reader :has_description
|
|
483
|
+
# Creates a domain value from named fields.
|
|
484
|
+
def initialize(name: '', sensitivity: '', description: '', has_description: false) = (@name = name; @sensitivity = sensitivity; @description = description; @has_description = has_description)
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
# Ordered Variable values returned by the corresponding list operation.
|
|
488
|
+
class VariableList
|
|
489
|
+
# Ordered Variable values returned by the corresponding list operation.
|
|
490
|
+
attr_reader :values
|
|
491
|
+
# Creates a domain value from named fields.
|
|
492
|
+
def initialize(values: []) = (@values = values)
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
# A text lookup that distinguishes an absent value from an empty string.
|
|
496
|
+
class OptionalString
|
|
497
|
+
# A text lookup that distinguishes an absent value from an empty string.
|
|
498
|
+
attr_reader :present
|
|
499
|
+
# A text lookup that distinguishes an absent value from an empty string.
|
|
500
|
+
attr_reader :value
|
|
501
|
+
# Creates a domain value from named fields.
|
|
502
|
+
def initialize(present: false, value: '') = (@present = present; @value = value)
|
|
503
|
+
end
|
|
504
|
+
|
|
505
|
+
# Whether a lockbox is owner-signed and, when available, the signing-key fingerprint.
|
|
506
|
+
class OwnerInspection
|
|
507
|
+
# Whether the lockbox requires owner-signed commits.
|
|
508
|
+
attr_reader :signed
|
|
509
|
+
# Owner signing-key fingerprint when one is configured.
|
|
510
|
+
attr_reader :fingerprint
|
|
511
|
+
# Whether an owner fingerprint is available.
|
|
512
|
+
attr_reader :has_fingerprint
|
|
513
|
+
# Creates a domain value from named fields.
|
|
514
|
+
def initialize(signed: false, fingerprint: '', has_fingerprint: false) = (@signed = signed; @fingerprint = fingerprint; @has_fingerprint = has_fingerprint)
|
|
515
|
+
end
|
|
516
|
+
|
|
517
|
+
# A named recipient public key stored in the local vault address book.
|
|
518
|
+
class Contact
|
|
519
|
+
# Local address-book name of the contact.
|
|
520
|
+
attr_reader :name
|
|
521
|
+
# Serialized contact public key used to grant lockbox access.
|
|
522
|
+
attr_reader :key
|
|
523
|
+
# Creates a domain value from named fields.
|
|
524
|
+
def initialize(name: '', key: []) = (@name = name; @key = key)
|
|
525
|
+
end
|
|
526
|
+
|
|
527
|
+
# Ordered Contact values returned by the corresponding list operation.
|
|
528
|
+
class ContactList
|
|
529
|
+
# Ordered Contact values returned by the corresponding list operation.
|
|
530
|
+
attr_reader :values
|
|
531
|
+
# Creates a domain value from named fields.
|
|
532
|
+
def initialize(values: []) = (@values = values)
|
|
533
|
+
end
|
|
534
|
+
|
|
535
|
+
# Ordered ProfileHistory values returned by the corresponding list operation.
|
|
536
|
+
class ProfileHistoryList
|
|
537
|
+
# Ordered ProfileHistory values returned by the corresponding list operation.
|
|
538
|
+
attr_reader :values
|
|
539
|
+
# Creates a domain value from named fields.
|
|
540
|
+
def initialize(values: []) = (@values = values)
|
|
541
|
+
end
|
|
542
|
+
|
|
543
|
+
# A lockbox key currently held by the local Session Agent, identified by lockbox and path.
|
|
544
|
+
class AgentEntry
|
|
545
|
+
# Stable lockbox identifier for the cached key.
|
|
546
|
+
attr_reader :id
|
|
547
|
+
# Host path associated with the cached lockbox key.
|
|
548
|
+
attr_reader :path
|
|
549
|
+
# Creates a domain value from named fields.
|
|
550
|
+
def initialize(id: '', path: '') = (@id = id; @path = path)
|
|
551
|
+
end
|
|
552
|
+
|
|
553
|
+
# Ordered AgentEntry values returned by the corresponding list operation.
|
|
554
|
+
class AgentEntryList
|
|
555
|
+
# Ordered AgentEntry values returned by the corresponding list operation.
|
|
556
|
+
attr_reader :values
|
|
557
|
+
# Creates a domain value from named fields.
|
|
558
|
+
def initialize(values: []) = (@values = values)
|
|
559
|
+
end
|
|
560
|
+
|
|
561
|
+
# The host capabilities used to protect cached secrets across suspend and sleep.
|
|
562
|
+
class SleepSupport
|
|
563
|
+
# Whether suspend notification support is compiled for this host.
|
|
564
|
+
attr_reader :suspend_notifications
|
|
565
|
+
# Whether sleep inhibition support is compiled for this host.
|
|
566
|
+
attr_reader :sleep_inhibition
|
|
567
|
+
# Whether this build includes the host integration; this is not runtime watcher readiness.
|
|
568
|
+
attr_reader :supported
|
|
569
|
+
# Whether the current compatible Session Agent reports an active suspend cache watcher; false when absent or unavailable. Notification delivery is platform-dependent.
|
|
570
|
+
attr_reader :cache_clearing_active
|
|
571
|
+
# Creates a domain value from named fields.
|
|
572
|
+
def initialize(suspend_notifications: false, sleep_inhibition: false, supported: false, cache_clearing_active: false) = (@suspend_notifications = suspend_notifications; @sleep_inhibition = sleep_inhibition; @supported = supported; @cache_clearing_active = cache_clearing_active)
|
|
573
|
+
end
|
|
574
|
+
|
|
575
|
+
# Availability and configuration of the platform credential store used for the Vault passphrase.
|
|
576
|
+
class PlatformStatus
|
|
577
|
+
# Whether a usable platform credential store exists.
|
|
578
|
+
attr_reader :supported
|
|
579
|
+
# Whether the user disabled platform credential store integration.
|
|
580
|
+
attr_reader :disabled
|
|
581
|
+
# Application-specific scope used to isolate the stored password.
|
|
582
|
+
attr_reader :scope
|
|
583
|
+
# Platform credential store backend in use.
|
|
584
|
+
attr_reader :backend
|
|
585
|
+
# Credential item name used by the backend.
|
|
586
|
+
attr_reader :item
|
|
587
|
+
# Creates a domain value from named fields.
|
|
588
|
+
def initialize(supported: false, disabled: false, scope: '', backend: '', item: '') = (@supported = supported; @disabled = disabled; @scope = scope; @backend = backend; @item = item)
|
|
589
|
+
end
|
|
590
|
+
|
|
591
|
+
# One text result returned by the native API.
|
|
592
|
+
class StringValue
|
|
593
|
+
# One text result returned by the native API.
|
|
594
|
+
attr_reader :value
|
|
595
|
+
# Creates a domain value from named fields.
|
|
596
|
+
def initialize(value: '') = (@value = value)
|
|
597
|
+
end
|
|
598
|
+
|
|
599
|
+
# The version, size, checksum, and creation time of an exported local-vault backup.
|
|
600
|
+
class VaultBackupManifest
|
|
601
|
+
# Backup container format version.
|
|
602
|
+
attr_reader :format_version
|
|
603
|
+
# Backup creation time in Unix milliseconds.
|
|
604
|
+
attr_reader :created_at_unix_ms
|
|
605
|
+
# Metadata-vault filename stored in the backup.
|
|
606
|
+
attr_reader :vault_file_name
|
|
607
|
+
# Encrypted vault payload size in bytes.
|
|
608
|
+
attr_reader :vault_size
|
|
609
|
+
# Lowercase SHA-256 digest of the encrypted vault payload.
|
|
610
|
+
attr_reader :vault_sha256
|
|
611
|
+
# Creates a domain value from named fields.
|
|
612
|
+
def initialize(format_version: 0, created_at_unix_ms: 0, vault_file_name: '', vault_size: 0, vault_sha256: '') = (@format_version = format_version; @created_at_unix_ms = created_at_unix_ms; @vault_file_name = vault_file_name; @vault_size = vault_size; @vault_sha256 = vault_sha256)
|
|
613
|
+
end
|
|
614
|
+
|
|
615
|
+
# Structured category, version, guidance, and artifact context for the most recent native failure.
|
|
616
|
+
class ErrorDetails
|
|
617
|
+
# Stable error category suitable for programmatic handling.
|
|
618
|
+
attr_reader :category
|
|
619
|
+
# Kind of archive or vault artifact involved in the failure.
|
|
620
|
+
attr_reader :artifact_kind
|
|
621
|
+
# Format version read from the failing artifact.
|
|
622
|
+
attr_reader :found_version
|
|
623
|
+
# Newest format version supported by this library.
|
|
624
|
+
attr_reader :supported_version
|
|
625
|
+
# Human-readable explanation of the failure.
|
|
626
|
+
attr_reader :message
|
|
627
|
+
# Suggested corrective action for the caller or user.
|
|
628
|
+
attr_reader :guidance
|
|
629
|
+
# Creates a domain value from named fields.
|
|
630
|
+
def initialize(category: '', artifact_kind: '', found_version: 0, supported_version: 0, message: '', guidance: '') = (@category = category; @artifact_kind = artifact_kind; @found_version = found_version; @supported_version = supported_version; @message = message; @guidance = guidance)
|
|
631
|
+
end
|
|
632
|
+
|
|
633
|
+
end
|
|
634
|
+
|
|
635
|
+
module Internal
|
|
636
|
+
# FlatBuffer reader and input encoder used only by the FFI layer.
|
|
637
|
+
module DomainCodec
|
|
638
|
+
module_function
|
|
639
|
+
|
|
640
|
+
SCHEMA = {
|
|
641
|
+
'ContactExchangeStatus' => [[:id, 'string'], [:relay, 'string'], [:invitation, 'string'], [:inviter_email, 'string'], [:recipient_email, 'string'], [:fingerprint, 'string'], [:expires_ms, 'ulong'], [:accepted, 'bool'], [:verified, 'bool'], [:contact_name, 'string'], [:relay_pending, 'bool']],
|
|
642
|
+
'ContactExchangeStatusList' => [[:values, '[revault.internal.ContactExchangeStatus]']],
|
|
643
|
+
'LockboxEntry' => [[:path, 'string'], [:kind, 'revault.internal.LockboxEntryKind'], [:length, 'ulong'], [:permissions, 'uint']],
|
|
644
|
+
'LockboxEntryList' => [[:entries, '[revault.internal.LockboxEntry]']],
|
|
645
|
+
'OptionalLockboxEntry' => [[:value, 'revault.internal.LockboxEntry']],
|
|
646
|
+
'StringList' => [[:values, '[string]']],
|
|
647
|
+
'PathMove' => [[:source, 'string'], [:destination, 'string']],
|
|
648
|
+
'PathMoveList' => [[:values, '[revault.internal.PathMove]']],
|
|
649
|
+
'FormField' => [[:id, 'string'], [:label, 'string'], [:kind, 'string'], [:required, 'bool'], [:description, 'string'], [:has_description, 'bool']],
|
|
650
|
+
'FormFieldList' => [[:values, '[revault.internal.FormField]']],
|
|
651
|
+
'FormDefinition' => [[:type_id, 'string'], [:alias, 'string'], [:revision, 'uint'], [:name, 'string'], [:description, 'string'], [:fields, '[revault.internal.FormField]']],
|
|
652
|
+
'FormDefinitionList' => [[:values, '[revault.internal.FormDefinition]']],
|
|
653
|
+
'FormValue' => [[:field_id, 'string'], [:label, 'string'], [:kind, 'string'], [:value, 'string'], [:secret, 'bool']],
|
|
654
|
+
'FormRecord' => [[:path, 'string'], [:name, 'string'], [:type_id, 'string'], [:definition_alias, 'string'], [:definition_revision, 'uint'], [:values, '[revault.internal.FormValue]'], [:description, 'string'], [:has_description, 'bool']],
|
|
655
|
+
'FormRecordList' => [[:values, '[revault.internal.FormRecord]']],
|
|
656
|
+
'OptionalFormRecord' => [[:value, 'revault.internal.FormRecord']],
|
|
657
|
+
'OptionalFormValue' => [[:value, 'revault.internal.FormValue']],
|
|
658
|
+
'RecoveryReport' => [[:intact_files, '[revault.internal.LockboxEntry]'], [:intact_file_count, 'ulong'], [:partial_files, 'ulong'], [:corrupt_records, 'ulong'], [:toc_recovered, 'bool'], [:variables_recovered, 'bool'], [:variable_count, 'ulong'], [:forms_recovered, 'bool'], [:form_definition_count, 'ulong'], [:form_record_count, 'ulong']],
|
|
659
|
+
'KeySlot' => [[:id, 'ulong'], [:protection, 'string'], [:algorithm, 'string']],
|
|
660
|
+
'KeySlotList' => [[:values, '[revault.internal.KeySlot]']],
|
|
661
|
+
'CacheStats' => [[:limit_bytes, 'ulong'], [:used_bytes, 'ulong'], [:entries, 'ulong'], [:hits, 'ulong'], [:misses, 'ulong']],
|
|
662
|
+
'ImportStats' => [[:host_stat_nanos, 'string'], [:host_read_nanos, 'string'], [:frame_prepare_nanos, 'string'], [:page_write_nanos, 'string']],
|
|
663
|
+
'PageObject' => [[:id, 'ulong'], [:kind, 'string'], [:payload_len, 'ulong']],
|
|
664
|
+
'PageInspection' => [[:offset, 'ulong'], [:page_id, 'ulong'], [:sequence, 'ulong'], [:page_size, 'ulong'], [:encrypted_body_len, 'ulong'], [:unused_bytes, 'ulong'], [:object_count, 'ulong'], [:objects, '[revault.internal.PageObject]']],
|
|
665
|
+
'PageInspectionList' => [[:values, '[revault.internal.PageInspection]']],
|
|
666
|
+
'FileInspection' => [[:lockbox_id, '[ubyte]'], [:header_readable, 'bool'], [:key_directory_generation, 'ulong'], [:key_directory_copy_count, 'ulong'], [:owner_signed, 'bool'], [:key_slots, '[revault.internal.KeySlot]']],
|
|
667
|
+
'ProfileGeneration' => [[:index, 'uint'], [:status, 'string'], [:contact_fingerprint, '[ubyte]'], [:created_at_unix_ms, 'ulong'], [:retired_at_unix_ms, 'ulong'], [:has_retired_at, 'bool']],
|
|
668
|
+
'ProfileHistory' => [[:name, 'string'], [:active_generation, 'uint'], [:generations, '[revault.internal.ProfileGeneration]']],
|
|
669
|
+
'LockboxAlias' => [[:name, 'string'], [:lockbox_id, '[ubyte]']],
|
|
670
|
+
'LockboxAliasList' => [[:values, '[revault.internal.LockboxAlias]']],
|
|
671
|
+
'KnownLockbox' => [[:lockbox_id, '[ubyte]'], [:path, 'string'], [:last_seen_unix_ms, 'ulong']],
|
|
672
|
+
'KnownLockboxList' => [[:values, '[revault.internal.KnownLockbox]']],
|
|
673
|
+
'AccessSlotLabel' => [[:lockbox_id, '[ubyte]'], [:slot_id, 'ulong'], [:name, 'string'], [:updated_at_unix_ms, 'ulong']],
|
|
674
|
+
'AccessSlotLabelList' => [[:values, '[revault.internal.AccessSlotLabel]']],
|
|
675
|
+
'StreamChunk' => [[:path, 'string'], [:file_offset, 'ulong'], [:length, 'ulong'], [:physical_offset, 'ulong'], [:sparse, 'bool'], [:data, '[ubyte]']],
|
|
676
|
+
'StreamChunkList' => [[:values, '[revault.internal.StreamChunk]']],
|
|
677
|
+
'RuntimeOptions' => [[:workload_profile, 'string'], [:worker_policy, 'string']],
|
|
678
|
+
'Variable' => [[:name, 'string'], [:sensitivity, 'string'], [:description, 'string'], [:has_description, 'bool']],
|
|
679
|
+
'VariableList' => [[:values, '[revault.internal.Variable]']],
|
|
680
|
+
'OptionalString' => [[:present, 'bool'], [:value, 'string']],
|
|
681
|
+
'OwnerInspection' => [[:signed, 'bool'], [:fingerprint, 'string'], [:has_fingerprint, 'bool']],
|
|
682
|
+
'Contact' => [[:name, 'string'], [:key, '[ubyte]']],
|
|
683
|
+
'ContactList' => [[:values, '[revault.internal.Contact]']],
|
|
684
|
+
'ProfileHistoryList' => [[:values, '[revault.internal.ProfileHistory]']],
|
|
685
|
+
'AgentEntry' => [[:id, 'string'], [:path, 'string']],
|
|
686
|
+
'AgentEntryList' => [[:values, '[revault.internal.AgentEntry]']],
|
|
687
|
+
'SleepSupport' => [[:suspend_notifications, 'bool'], [:sleep_inhibition, 'bool'], [:supported, 'bool'], [:cache_clearing_active, 'bool']],
|
|
688
|
+
'PlatformStatus' => [[:supported, 'bool'], [:disabled, 'bool'], [:scope, 'string'], [:backend, 'string'], [:item, 'string']],
|
|
689
|
+
'StringValue' => [[:value, 'string']],
|
|
690
|
+
'VaultBackupManifest' => [[:format_version, 'uint'], [:created_at_unix_ms, 'ulong'], [:vault_file_name, 'string'], [:vault_size, 'ulong'], [:vault_sha256, 'string']],
|
|
691
|
+
'ErrorDetails' => [[:category, 'string'], [:artifact_kind, 'string'], [:found_version, 'uint'], [:supported_version, 'uint'], [:message, 'string'], [:guidance, 'string']]
|
|
692
|
+
}.freeze
|
|
693
|
+
|
|
694
|
+
def decode(name, bytes)
|
|
695
|
+
table(name, bytes.b, bytes.unpack1('V'))
|
|
696
|
+
end
|
|
697
|
+
|
|
698
|
+
def table(name, bytes, position)
|
|
699
|
+
values = {}
|
|
700
|
+
SCHEMA.fetch(name).each_with_index do |(field, type), index|
|
|
701
|
+
location = field_location(bytes, position, index)
|
|
702
|
+
public_field = field == :alias ? :form_alias : field
|
|
703
|
+
values[public_field] = read_value(bytes, location, type)
|
|
704
|
+
end
|
|
705
|
+
Bindings.const_get(name).new(**values)
|
|
706
|
+
end
|
|
707
|
+
|
|
708
|
+
def field_location(bytes, table_position, index)
|
|
709
|
+
vtable = table_position - bytes.unpack1('l<', offset: table_position)
|
|
710
|
+
vtable_length = bytes.unpack1('v', offset: vtable)
|
|
711
|
+
entry = vtable + 4 + (index * 2)
|
|
712
|
+
return nil if entry + 2 > vtable + vtable_length
|
|
713
|
+
offset = bytes.unpack1('v', offset: entry)
|
|
714
|
+
offset.zero? ? nil : table_position + offset
|
|
715
|
+
end
|
|
716
|
+
|
|
717
|
+
def read_value(bytes, location, type)
|
|
718
|
+
return default_value(type) unless location
|
|
719
|
+
if type.start_with?('[')
|
|
720
|
+
read_vector(bytes, location, type[1...-1])
|
|
721
|
+
elsif SCHEMA.key?(type.split('.').last)
|
|
722
|
+
table(type.split('.').last, bytes, location + bytes.unpack1('V', offset: location))
|
|
723
|
+
else
|
|
724
|
+
read_scalar(bytes, location, type)
|
|
725
|
+
end
|
|
726
|
+
end
|
|
727
|
+
|
|
728
|
+
def read_vector(bytes, location, type)
|
|
729
|
+
vector = location + bytes.unpack1('V', offset: location)
|
|
730
|
+
length = bytes.unpack1('V', offset: vector)
|
|
731
|
+
start = vector + 4
|
|
732
|
+
return bytes.byteslice(start, length) if type == 'ubyte'
|
|
733
|
+
Array.new(length) do |index|
|
|
734
|
+
element = start + (index * 4)
|
|
735
|
+
if type == 'string'
|
|
736
|
+
read_string(bytes, element)
|
|
737
|
+
elsif SCHEMA.key?(type.split('.').last)
|
|
738
|
+
table(type.split('.').last, bytes, element + bytes.unpack1('V', offset: element))
|
|
739
|
+
else
|
|
740
|
+
read_scalar(bytes, element, type)
|
|
741
|
+
end
|
|
742
|
+
end
|
|
743
|
+
end
|
|
744
|
+
|
|
745
|
+
def read_scalar(bytes, location, type)
|
|
746
|
+
case type
|
|
747
|
+
when 'string' then read_string(bytes, location)
|
|
748
|
+
when 'bool', 'ubyte' then bytes.getbyte(location) != 0
|
|
749
|
+
when 'ulong' then bytes.unpack1('Q<', offset: location)
|
|
750
|
+
else bytes.unpack1('V', offset: location)
|
|
751
|
+
end
|
|
752
|
+
end
|
|
753
|
+
|
|
754
|
+
def read_string(bytes, location)
|
|
755
|
+
value = location + bytes.unpack1('V', offset: location)
|
|
756
|
+
bytes.byteslice(value + 4, bytes.unpack1('V', offset: value)).force_encoding(Encoding::UTF_8)
|
|
757
|
+
end
|
|
758
|
+
|
|
759
|
+
def default_value(type)
|
|
760
|
+
return [] if type.start_with?('[')
|
|
761
|
+
return '' if type == 'string'
|
|
762
|
+
return false if type == 'bool'
|
|
763
|
+
return 0 if %w[uint ulong ubyte].include?(type) || type.end_with?('LockboxEntryKind')
|
|
764
|
+
nil
|
|
765
|
+
end
|
|
766
|
+
|
|
767
|
+
def encode_path_moves(values) = encode_table_vector(values, :path_move)
|
|
768
|
+
def encode_form_fields(values) = encode_table_vector(values, :form_field)
|
|
769
|
+
|
|
770
|
+
def encode_table_vector(values, kind)
|
|
771
|
+
bytes = [0].pack('V')
|
|
772
|
+
append_vtable(bytes, [4], 8)
|
|
773
|
+
root = align(bytes); bytes << [root - 4, 0].pack('V2')
|
|
774
|
+
patch_u32(bytes, 0, root)
|
|
775
|
+
vector = align(bytes); patch_u32(bytes, root + 4, vector - (root + 4)); bytes << [values.length].pack('V') << ("\0" * (values.length * 4))
|
|
776
|
+
values.each_with_index do |value, index|
|
|
777
|
+
position = kind == :path_move ? append_path_move(bytes, value) : append_form_field(bytes, value)
|
|
778
|
+
element = vector + 4 + (index * 4); patch_u32(bytes, element, position - element)
|
|
779
|
+
end
|
|
780
|
+
bytes
|
|
781
|
+
end
|
|
782
|
+
|
|
783
|
+
def append_path_move(bytes, value)
|
|
784
|
+
vtable = append_vtable(bytes, [4, 8], 12); table = align(bytes); bytes << [table - vtable, 0, 0].pack('V3')
|
|
785
|
+
append_string_field(bytes, table + 4, value.source); append_string_field(bytes, table + 8, value.destination); table
|
|
786
|
+
end
|
|
787
|
+
|
|
788
|
+
def append_form_field(bytes, value)
|
|
789
|
+
vtable = append_vtable(bytes, [4, 8, 12, 16, 20, 24], 28); table = align(bytes); bytes << [table - vtable, 0, 0, 0].pack('V4') << [value.required ? 1 : 0].pack('C') << "\0\0\0" << [0].pack('V') << [value.has_description ? 1 : 0].pack('C') << "\0\0\0"
|
|
790
|
+
append_string_field(bytes, table + 4, value.id); append_string_field(bytes, table + 8, value.label); append_string_field(bytes, table + 12, value.kind); append_string_field(bytes, table + 20, value.description); table
|
|
791
|
+
end
|
|
792
|
+
|
|
793
|
+
def append_vtable(bytes, offsets, object_size)
|
|
794
|
+
align(bytes, 2); position = bytes.bytesize; bytes << [4 + (offsets.length * 2), object_size, *offsets].pack('v*'); position
|
|
795
|
+
end
|
|
796
|
+
|
|
797
|
+
def append_string_field(bytes, field, value)
|
|
798
|
+
target = align(bytes); patch_u32(bytes, field, target - field); bytes << [value.bytesize].pack('V') << value.b << "\0"
|
|
799
|
+
end
|
|
800
|
+
|
|
801
|
+
def align(bytes, alignment = 4)
|
|
802
|
+
bytes << "\0" until (bytes.bytesize % alignment).zero?; bytes.bytesize
|
|
803
|
+
end
|
|
804
|
+
|
|
805
|
+
def patch_u32(bytes, offset, value) = bytes[offset, 4] = [value].pack('V')
|
|
806
|
+
end
|
|
807
|
+
end
|
|
808
|
+
end
|