@eluvio/elv-client-js 4.0.86 → 4.0.88

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eluvio/elv-client-js",
3
- "version": "4.0.86",
3
+ "version": "4.0.88",
4
4
  "description": "Javascript client for the Eluvio Content Fabric",
5
5
  "main": "src/index.js",
6
6
  "author": "Kevin Talmadge",
@@ -0,0 +1,98 @@
1
+ const UrlJoin = require("url-join");
2
+ const HttpClient = require("./HttpClient");
3
+ const UUID = require("uuid").v4;
4
+
5
+ const {ValidateParameters} = require("./Validation");
6
+
7
+ const ContentObjectAudit = {
8
+ async AuditContentObject({client, libraryId, objectId, versionHash, salt, samples, live=false}) {
9
+ if(!salt){
10
+ salt = client.utils.B64(UUID());
11
+ }
12
+
13
+ if(!samples) {
14
+ samples = [
15
+ Math.random() * 0.33,
16
+ Math.random() * 0.33 + 0.33,
17
+ Math.random() * 0.33 + 0.66
18
+ ];
19
+ }
20
+
21
+ // Ensure only a max of 3 samples
22
+ samples = samples.slice(0, 3);
23
+
24
+ if(versionHash) {
25
+ objectId = client.utils.DecodeVersionHash(versionHash).objectId;
26
+ }
27
+
28
+ if(!libraryId) {
29
+ libraryId = await client.ContentObjectLibraryId({objectId});
30
+ }
31
+
32
+ ValidateParameters({libraryId, objectId, versionHash});
33
+
34
+ let queryParams = {salt, samples};
35
+
36
+
37
+ if(live) {
38
+ queryParams.now = Date.now();
39
+ }
40
+
41
+ // Test against the node the client is currently using, plus a batch of fresh nodes
42
+ const uris = [
43
+ client.HttpClient.uris[client.HttpClient.uriIndex],
44
+ ...(await client.Configuration({
45
+ configUrl: client.configUrl,
46
+ clientIP: client.clientIP,
47
+ region: client.region
48
+ })).fabricURIs
49
+ ]
50
+ .filter((v, i, s) => s.indexOf(v) === i);
51
+
52
+ const httpClient = new HttpClient({uris});
53
+
54
+ let path = UrlJoin("qlibs", libraryId, "q", versionHash || objectId, live ? "call/live/audit" : "audit");
55
+ let responses = await httpClient.RequestAll({
56
+ headers: await client.authClient.AuthorizationHeader({libraryId, objectId, versionHash}),
57
+ queryParams: queryParams,
58
+ method: "GET",
59
+ path
60
+ });
61
+
62
+ let auditHash, verified;
63
+ let audits = [];
64
+ for(const response of responses) {
65
+ let url = new URL(response.url);
66
+ let audit = { host: url.hostname };
67
+
68
+ if(!response.ok) {
69
+ audit.error = response;
70
+ audit.errorMessage = response.message || JSON.stringify(response);
71
+ } else {
72
+ let res = await client.utils.ResponseToJson(response);
73
+ if(auditHash === undefined) {
74
+ auditHash = res.audit_hash;
75
+ } else if(res.audit_hash !== auditHash) {
76
+ verified = false;
77
+ } else if(verified === undefined) {
78
+ verified = true;
79
+ }
80
+
81
+ audit.audit_hash = res.audit_hash;
82
+ }
83
+
84
+ audits.push(audit);
85
+ }
86
+
87
+ verified = verified || false;
88
+
89
+ return {
90
+ verified,
91
+ salt,
92
+ samples,
93
+ audits,
94
+ };
95
+ },
96
+ };
97
+
98
+ module.exports = ContentObjectAudit;
package/src/ElvClient.js CHANGED
@@ -9,8 +9,6 @@ const EthClient = require("./EthClient");
9
9
  const UserProfileClient = require("./UserProfileClient");
10
10
  const HttpClient = require("./HttpClient");
11
11
  const RemoteSigner = require("./RemoteSigner");
12
-
13
- // const ContentObjectVerification = require("./ContentObjectVerification");
14
12
  const Utils = require("./Utils");
15
13
  const Crypto = require("./Crypto");
16
14
  const {LogMessage} = require("./LogMessage");
@@ -161,6 +159,8 @@ class ElvClient {
161
159
  assumeV3=false,
162
160
  service="default"
163
161
  }) {
162
+ this.Configuration = ElvClient.Configuration;
163
+
164
164
  this.utils = Utils;
165
165
 
166
166
  this.contentSpaceId = contentSpaceId;
@@ -283,7 +283,7 @@ class ElvClient {
283
283
  * @return {Object} - An object using network names as keys and configuration URLs as values.
284
284
  */
285
285
  static Networks() {
286
- return networks;
286
+ return Object.assign({}, networks);
287
287
  }
288
288
 
289
289
  /**
@@ -390,6 +390,8 @@ class ElvClient {
390
390
  });
391
391
 
392
392
  client.configUrl = configUrl;
393
+ client.region = region;
394
+ client.clientIP = clientIP;
393
395
 
394
396
  return client;
395
397
  }
@@ -469,6 +471,8 @@ class ElvClient {
469
471
  region
470
472
  });
471
473
 
474
+ this.region = region;
475
+
472
476
  this.authServiceURIs = authServiceURIs;
473
477
  this.fabricURIs = fabricURIs;
474
478
  this.ethereumURIs = ethereumURIs;
@@ -318,6 +318,8 @@ class FrameClient {
318
318
  "AddContentObjectGroupPermission",
319
319
  "AddLibraryContentType",
320
320
  "AssetMetadata",
321
+ "AuditContentObject",
322
+ "AuditStream",
321
323
  "AvailableDRMs",
322
324
  "AvailableOfferings",
323
325
  "AwaitPending",
@@ -497,7 +499,6 @@ class FrameClient {
497
499
  "UploadPartChunk",
498
500
  "UploadStatus",
499
501
  "UseRegion",
500
- "VerifyContentObject",
501
502
  "Visibility",
502
503
  "WriteTokenNodeUrl"
503
504
  ];
package/src/HttpClient.js CHANGED
@@ -15,8 +15,9 @@ class HttpClient {
15
15
  this.retries = Math.max(3, uris.length);
16
16
  }
17
17
 
18
- BaseURI() {
19
- return new URI(this.uris[this.uriIndex]);
18
+ BaseURI(uriIndex) {
19
+ if(uriIndex === undefined) { uriIndex = this.uriIndex; }
20
+ return new URI(this.uris[uriIndex]);
20
21
  }
21
22
 
22
23
  static Fetch(url, params={}) {
@@ -59,9 +60,10 @@ class HttpClient {
59
60
  attempts=0,
60
61
  allowFailover=true,
61
62
  forceFailover=false,
62
- allowRetry=true
63
+ allowRetry=true,
64
+ uriIndex
63
65
  }) {
64
- let baseURI = this.BaseURI();
66
+ let baseURI = this.BaseURI(uriIndex);
65
67
 
66
68
  // If URL contains a write token, it must go to the correct server and can not fail over
67
69
  const writeTokenMatch = path.replace(/^\//, "").match(/(qlibs\/ilib[a-zA-Z0-9]+|q|qid)\/(tqw__[a-zA-Z0-9]+)/);
@@ -141,6 +143,7 @@ class HttpClient {
141
143
  bodyType,
142
144
  headers,
143
145
  attempts: attempts + 1,
146
+ uriIndex,
144
147
  forceFailover
145
148
  });
146
149
  }
@@ -176,6 +179,35 @@ class HttpClient {
176
179
  return response;
177
180
  }
178
181
 
182
+ async RequestAll({
183
+ method,
184
+ path,
185
+ queryParams={},
186
+ body,
187
+ bodyType="JSON",
188
+ headers={},
189
+ }) {
190
+ return await Promise.all(
191
+ Array.from(new Array(this.uris.length).keys())
192
+ .map(async uriIndex => {
193
+ try {
194
+ return await this.Request({
195
+ method,
196
+ path,
197
+ queryParams,
198
+ body,
199
+ bodyType,
200
+ headers,
201
+ allowFailover: false,
202
+ uriIndex
203
+ });
204
+ } catch(error) {
205
+ return error;
206
+ }
207
+ })
208
+ );
209
+ }
210
+
179
211
  URL({path, queryParams={}}) {
180
212
  let baseURI = this.BaseURI();
181
213
 
package/src/Validation.js CHANGED
@@ -52,8 +52,11 @@ exports.ValidatePartHash = (partHash) => {
52
52
  }
53
53
  };
54
54
 
55
- exports.ValidateParameters = ({libraryId, objectId, versionHash}) => {
56
- if(versionHash) {
55
+ exports.ValidateParameters = ({libraryId, objectId, versionHash, writeToken}) => {
56
+ if(writeToken) {
57
+ if(versionHash) throw Error(`Cannot specify writeToken and versionHash at same time (token:${writeToken}, hash:${versionHash})`);
58
+ exports.ValidateWriteToken(writeToken);
59
+ } else if(versionHash) {
57
60
  exports.ValidateVersion(versionHash);
58
61
  } else {
59
62
  exports.ValidateLibrary(libraryId);
@@ -8,6 +8,7 @@ const UrlJoin = require("url-join");
8
8
  const objectPath = require("object-path");
9
9
 
10
10
  const HttpClient = require("../HttpClient");
11
+ const ContentObjectAudit = require("../ContentObjectAudit");
11
12
 
12
13
  const {
13
14
  ValidateLibrary,
@@ -3054,24 +3055,26 @@ exports.Collection = async function({collectionType}) {
3054
3055
  /* Verification */
3055
3056
 
3056
3057
  /**
3057
- * Verify the specified content object
3058
+ * Audit the specified content object against several content fabric nodes
3058
3059
  *
3059
3060
  * @methodGroup Content Objects
3060
3061
  * @namedParams
3061
- * @param {string} libraryId - ID of the library
3062
- * @param {string} objectId - ID of the object
3063
- * @param {string} versionHash - Hash of the content object version
3062
+ * @param {string=} libraryId - ID of the library
3063
+ * @param {string=} objectId - ID of the object
3064
+ * @param {string=} versionHash - Version hash of the object -- if not specified, latest version is returned
3065
+ * @param {string=} salt - base64-encoded byte sequence for salting the audit hash
3066
+ * @param {Array<number>=} samples - list of percentages (0.0 - <1.0) used for sampling the content part list, up to 3
3064
3067
  *
3065
- * @returns {Promise<Object>} - Response describing verification results
3068
+ * @returns {Promise<Object>} - Response describing audit results
3066
3069
  */
3067
- exports.VerifyContentObject = async function({libraryId, objectId, versionHash}) {
3068
- ValidateParameters({libraryId, objectId, versionHash});
3069
-
3070
- return await ContentObjectVerification.VerifyContentObject({
3070
+ exports.AuditContentObject = async function({libraryId, objectId, versionHash, salt, samples}) {
3071
+ return await ContentObjectAudit.AuditContentObject({
3071
3072
  client: this,
3072
3073
  libraryId,
3073
3074
  objectId,
3074
- versionHash
3075
+ versionHash,
3076
+ salt,
3077
+ samples
3075
3078
  });
3076
3079
  };
3077
3080
 
@@ -34,20 +34,22 @@ const {
34
34
  * @namedParams
35
35
  * @param {string=} libraryId - ID of the library
36
36
  * @param {string=} objectId - ID of the object
37
+ * @param {string=} path - ID of the object
37
38
  * @param {string=} versionHash - Hash of the object version - if not specified, most recent version will be used
39
+ * @param {string=} writeToken - Write token of a draft (incompatible with versionHash)
38
40
  */
39
- exports.ListFiles = async function({libraryId, objectId, versionHash}) {
40
- ValidateParameters({libraryId, objectId, versionHash});
41
+ exports.ListFiles = async function({libraryId, objectId, path = "", versionHash, writeToken}) {
42
+ ValidateParameters({libraryId, objectId, versionHash, writeToken});
41
43
 
42
44
  if(versionHash) { objectId = this.utils.DecodeVersionHash(versionHash).objectId; }
43
45
 
44
- let path = UrlJoin("q", versionHash || objectId, "meta", "files");
46
+ let urlPath = UrlJoin("q", writeToken || versionHash || objectId, "files_list", path);
45
47
 
46
48
  return this.utils.ResponseToJson(
47
49
  this.HttpClient.Request({
48
50
  headers: await this.authClient.AuthorizationHeader({libraryId, objectId, versionHash}),
49
51
  method: "GET",
50
- path: path,
52
+ path: urlPath,
51
53
  })
52
54
  );
53
55
  };
@@ -554,7 +556,7 @@ exports.UploadFileData = async function({libraryId, objectId, writeToken, encryp
554
556
  const jobStatus = await this.UploadJobStatus({libraryId, objectId, writeToken, uploadId, jobId});
555
557
 
556
558
  // Find the status of this file
557
- let fileStatus = jobStatus.files.find(item => item.path == filePath);
559
+ let fileStatus = jobStatus.files.find(item => item.path === filePath);
558
560
  if(encryption && encryption !== "none") {
559
561
  fileStatus = fileStatus.encrypted;
560
562
  }
@@ -157,7 +157,7 @@ class LiveConf {
157
157
  recordingBitrate: Math.max(this.probeData.streams[index].bit_rate, 128000),
158
158
  recordingChannels: this.probeData.streams[index].channels,
159
159
  playoutLabel: `Audio ${index}`
160
- }
160
+ };
161
161
  }
162
162
  }
163
163
  return audioStreams;
@@ -382,36 +382,36 @@ class LiveConf {
382
382
  return sync_id;
383
383
  }
384
384
 
385
- /*
385
+ /*
386
386
  * Generate audio streams recording configuration based on the optional custom settings.
387
387
  * If no custom "audio" section is present, record all the acceptable audio streams found in the probe
388
388
  */
389
389
  generateAudioStreamsConfig({customSettings}) {
390
390
 
391
391
  let audioStreams = {};
392
- if (customSettings && customSettings.audio) {
393
- for (let i = 0; i < Object.keys(customSettings.audio).length; i ++) {
392
+ if(customSettings && customSettings.audio) {
393
+ for(let i = 0; i < Object.keys(customSettings.audio).length; i ++) {
394
394
  let audioIdx = Object.keys(customSettings.audio)[i];
395
395
  let audio = customSettings.audio[audioIdx];
396
396
  audioStreams[audioIdx] = {
397
397
  recordingBitrate: audio.recording_bitrate || 192000,
398
398
  recordingChannels: audio.recording_channels || 2,
399
399
  };
400
- if (audio.playout) {
401
- audioStreams[audioIdx].playoutLabel = audio.playout_label || `Audio ${audioIdx}`
400
+ if(audio.playout) {
401
+ audioStreams[audioIdx].playoutLabel = audio.playout_label || `Audio ${audioIdx}`;
402
402
  }
403
403
  }
404
404
  }
405
405
 
406
406
  // If no audio streams specified in custom config, set up all the suitable audio streams in the probe
407
- if (!customSettings.audio) {
407
+ if(!customSettings.audio) {
408
408
  audioStreams = this.getAudioStreamsFromProbe();
409
409
  }
410
410
 
411
411
  return audioStreams;
412
412
  }
413
413
 
414
- /*
414
+ /*
415
415
  * Generate the live recording config as required by QFAB, based on defaults and optional custom settings.
416
416
  */
417
417
  generateLiveConf({customSettings}) {
@@ -439,7 +439,7 @@ class LiveConf {
439
439
  conf.live_recording.recording_config.recording_params.xc_params.enc_height = videoStream.height;
440
440
  conf.live_recording.recording_config.recording_params.xc_params.enc_width = videoStream.width;
441
441
 
442
- for (let i =0; i < Object.keys(audioStreams).length; i ++) {
442
+ for(let i =0; i < Object.keys(audioStreams).length; i ++) {
443
443
  conf.live_recording.recording_config.recording_params.xc_params.audio_index[i] = parseInt(Object.keys(audioStreams)[i]);
444
444
  }
445
445
 
@@ -549,7 +549,7 @@ class LiveConf {
549
549
  let globalAudioBitrate = 0;
550
550
  let nAudio = 0;
551
551
 
552
- for (let i = 0; i < Object.keys(audioStreams).length; i ++ ) {
552
+ for(let i = 0; i < Object.keys(audioStreams).length; i ++ ) {
553
553
  let audioLadderSpec = {...LadderSpecAudio};
554
554
  const audioIndex = Object.keys(audioStreams)[i];
555
555
  const audio = audioStreams[audioIndex];
@@ -561,7 +561,7 @@ class LiveConf {
561
561
  audioLadderSpec.stream_label = audio.playoutLabel ? audio.playoutLabel : null;
562
562
 
563
563
  conf.live_recording.recording_config.recording_params.ladder_specs.push(audioLadderSpec);
564
- if (audio.recordingBitrate > globalAudioBitrate) {
564
+ if(audio.recordingBitrate > globalAudioBitrate) {
565
565
  globalAudioBitrate = audio.recordingBitrate;
566
566
  }
567
567
  nAudio ++;
@@ -10,6 +10,7 @@ const fs = require("fs");
10
10
  const HttpClient = require("../HttpClient");
11
11
  const Fraction = require("fraction.js");
12
12
  const {ValidateObject, ValidatePresence} = require("../Validation");
13
+ const ContentObjectAudit = require("../ContentObjectAudit");
13
14
 
14
15
  const MakeTxLessToken = async({client, libraryId, objectId, versionHash}) => {
15
16
  const tok = await client.authClient.AuthorizationToken({libraryId, objectId,
@@ -445,7 +446,7 @@ exports.StreamStatus = async function({name, stopLro=false, showParams=false}) {
445
446
  let videoLastFinalizationTimeEpochSec = -1;
446
447
  let videoFinalizedParts = 0;
447
448
  let sinceLastFinalize = -1;
448
- if (period.finalized_parts_info && period.finalized_parts_info.video && period.finalized_parts_info.video.last_finalization_time) {
449
+ if(period.finalized_parts_info && period.finalized_parts_info.video && period.finalized_parts_info.video.last_finalization_time) {
449
450
  videoLastFinalizationTimeEpochSec = period.finalized_parts_info.video.last_finalization_time / 1000000;
450
451
  videoFinalizedParts = period.finalized_parts_info.video.n_parts;
451
452
  sinceLastFinalize = Math.floor(new Date().getTime() / 1000) - videoLastFinalizationTimeEpochSec;
@@ -497,6 +498,9 @@ exports.StreamStatus = async function({name, stopLro=false, showParams=false}) {
497
498
  state = lroStatus.state;
498
499
  status.warnings = lroStatus.custom && lroStatus.custom.warnings;
499
500
  status.quality = lroStatus.custom && lroStatus.custom.quality;
501
+ if (lroStatus.custom && lroStatus.custom.status) {
502
+ status.recording_status = lroStatus.custom.status
503
+ }
500
504
  } catch(error) {
501
505
  console.log("LRO Status (failed): ", error.response.statusCode);
502
506
  status.state = "stopped";
@@ -733,7 +737,7 @@ exports.StreamCreate = async function({name, start=false}) {
733
737
  */
734
738
  exports.StreamStartOrStopOrReset = async function({name, op}) {
735
739
  try {
736
- let status = await this.StreamStatus({name})
740
+ let status = await this.StreamStatus({name});
737
741
  if(status.state != "stopped") {
738
742
  if(op === "start") {
739
743
  status.error = "Unable to start stream - state: " + status.state;
@@ -1937,3 +1941,26 @@ exports.StreamAddWatermark = async function({
1937
1941
 
1938
1942
  return response;
1939
1943
  };
1944
+
1945
+ /**
1946
+ * Audit the specified live stream against several content fabric nodes
1947
+ *
1948
+ * @methodGroup Live Stream
1949
+ * @namedParams
1950
+ * @param {string=} objectId - Object ID of the live stream
1951
+ * @param {string=} versionHash - Version hash of the live stream -- if not specified, latest version is returned
1952
+ * @param {string=} salt - base64-encoded byte sequence for salting the audit hash
1953
+ * @param {Array<number>=} samples - list of percentages (0.0 - <1.0) used for sampling the content part list, up to 3
1954
+ *
1955
+ * @returns {Promise<Object>} - Response describing audit results
1956
+ */
1957
+ exports.AuditStream = async function({objectId, versionHash, salt, samples}) {
1958
+ return await ContentObjectAudit.AuditContentObject({
1959
+ client: this,
1960
+ objectId,
1961
+ versionHash,
1962
+ salt,
1963
+ samples,
1964
+ live: true
1965
+ });
1966
+ };
@@ -1,210 +0,0 @@
1
- const CBOR = require("cbor");
2
- const SJCL = require("sjcl");
3
- const MultiHash = require("multihashes");
4
- const DeepEqual = require("deep-equal");
5
- const Utils = require("./Utils");
6
-
7
- const ContentObjectVerification = {
8
- async VerifyContentObject({client, libraryId, objectId, versionHash}) {
9
- let response = {
10
- hash: versionHash
11
- };
12
-
13
- const partHash = Utils.DecodeVersionHash(versionHash).partHash;
14
-
15
- const qpartsResponse = await client.QParts({libraryId, objectId, partHash, format: "arrayBuffer"})
16
- .then(response => Buffer.from(response));
17
- const partVerification = ContentObjectVerification._VerifyPart({partHash: partHash, qpartsResponse: qpartsResponse});
18
-
19
- if(partVerification.valid) {
20
- response.qref = { valid: true };
21
- } else {
22
- response.qref = { valid: false, error: partVerification.error.message };
23
- }
24
-
25
- response.qref.hash = partHash;
26
-
27
- if(response.qref.valid) {
28
- // Validate Metadata
29
- const qmdHash = partVerification.cbor.QmdHash.value;
30
- const metadataPartHash = "hqp_" + MultiHash.toB58String(qmdHash.slice(1, qmdHash.length));
31
- const metadataPartResponse = await client.QParts({libraryId, objectId, partHash: metadataPartHash, format: "arrayBuffer"})
32
- .then(response => Buffer.from(response));
33
-
34
- const metadataVerification = ContentObjectVerification._VerifyPart({partHash: metadataPartHash, qpartsResponse: metadataPartResponse});
35
-
36
- if(metadataVerification.valid) {
37
- response.qmd = { valid: true };
38
- } else {
39
- response.qmd = { valid: false, error: metadataVerification.error.message };
40
- }
41
-
42
- response.qmd.hash = metadataPartHash;
43
-
44
- if(response.qmd.valid && libraryId) {
45
- // If the library ID is provided, compare some metadata in the CBOR response
46
- // to the metadata from the /meta endpoint
47
- const metadata = await client.ContentObjectMetadata({
48
- libraryId: libraryId,
49
- objectId,
50
- versionHash: partHash.replace("hqp_", "hq__")
51
- });
52
-
53
- response.qmd.check = ContentObjectVerification._VerifyMetadata({metadataCbor: metadataVerification.cbor, metadata: metadata});
54
- }
55
-
56
- // Validate Qstruct
57
-
58
- const qstructHash = partVerification.cbor.QstructHash.value;
59
- const structPartHash = "hqp_" + MultiHash.toB58String(qstructHash.slice(1, qstructHash.length));
60
- const structPartResponse = await client.QParts({libraryId, objectId, partHash: structPartHash, format: "arrayBuffer"})
61
- .then(response => Buffer.from(response));
62
- const structVerification = ContentObjectVerification._VerifyPart({partHash: structPartHash, qpartsResponse: structPartResponse});
63
-
64
- if(structVerification.valid) {
65
- response.qstruct = { valid: true };
66
- } else {
67
- response.qstruct = { valid: false, error: structVerification.error.message };
68
- }
69
-
70
- response.qstruct.hash = structPartHash;
71
-
72
- if(response.qstruct.valid) {
73
- response.qstruct.parts = ContentObjectVerification._FormatQStruct(structVerification.cbor.Parts);
74
- }
75
- }
76
-
77
- response.valid =
78
- response.qref.valid &&
79
- response.qmd.valid &&
80
- response.qstruct.valid &&
81
- (!response.qmd.check || response.qmd.check.valid);
82
-
83
- return response;
84
- },
85
-
86
- // Content verification methods //
87
-
88
- _FormatQStruct(structParts) {
89
- if(!structParts) { return []; }
90
-
91
- return structParts.map(structPart => {
92
- return {
93
- hash: "hqp_" + MultiHash.toB58String(structPart.Hash.value.slice(1, structPart.Hash.length)),
94
- size: structPart.Size
95
- };
96
- });
97
- },
98
-
99
- _Hash(thing) {
100
- function fromBits(arr) {
101
- var out = [], bl = SJCL.bitArray.bitLength(arr), i, tmp;
102
- for(i=0; i<bl/8; i++) {
103
- if((i&3) === 0) {
104
- tmp = arr[i/4];
105
- }
106
- out.push(tmp >>> 24);
107
- tmp <<= 8;
108
- }
109
- return out;
110
- }
111
-
112
- function toBits(bytes) {
113
- var out = [], i, tmp=0;
114
- for(i=0; i<bytes.length; i++) {
115
- tmp = tmp << 8 | bytes[i];
116
- if((i&3) === 3) {
117
- out.push(tmp);
118
- tmp = 0;
119
- }
120
- }
121
- if(i&3) {
122
- out.push(SJCL.bitArray.partial(8*(i&3), tmp));
123
- }
124
- return out;
125
- }
126
-
127
- let digest = SJCL.hash.sha256.hash(toBits(thing));
128
- let bytes = fromBits(digest);
129
- let out = Buffer.from(bytes, "binary");
130
-
131
- return MultiHash.toB58String(MultiHash.encode(out, "sha2-256"));
132
- },
133
-
134
- _ParseCBOR(cborResponse) {
135
- let buffer = cborResponse.slice(7, cborResponse.length);
136
- let hex = buffer.toString("hex");
137
- return CBOR.decodeFirstSync(hex);
138
- },
139
-
140
- _VerifyPart({partHash, qpartsResponse}) {
141
- try {
142
- if(ContentObjectVerification._Hash(qpartsResponse) !== partHash.replace("hqp_", "")) {
143
- throw Error("Hashes do not match");
144
- }
145
-
146
- let cbor = ContentObjectVerification._ParseCBOR(qpartsResponse);
147
-
148
- return {
149
- valid: true,
150
- cbor: cbor
151
- };
152
- } catch(error) {
153
- return {
154
- valid: false,
155
- error: error
156
- };
157
- }
158
- },
159
-
160
- _VerifyMetadata({metadataCbor, metadata}) {
161
- if(!metadataCbor) { metadataCbor = {}; }
162
- if(!metadata) { metadata = {}; }
163
-
164
- let response = {
165
- valid: true,
166
- invalidValues: []
167
- };
168
-
169
- const cborKeys = Object.keys(metadataCbor);
170
- const metadataKeys = Object.keys(metadata);
171
-
172
- // Find any difference between top level keys
173
- const differentKeys = cborKeys
174
- .filter(x => !metadataKeys.includes(x))
175
- .concat(metadataKeys.filter(x => !cborKeys.includes(x)));
176
-
177
- for(const key of differentKeys) {
178
- const cborValue = metadataCbor[key];
179
- const metadataValue = metadata[key];
180
-
181
- response.invalidValues.push({
182
- key: key,
183
- cborValue: cborValue,
184
- metadataValue: metadataValue
185
- });
186
- }
187
-
188
- // Deep comparison of up to 5 keys
189
- for(const fieldToValidate of Object.keys(metadataCbor).slice(0, 5)) {
190
- const cborValue = metadataCbor[fieldToValidate];
191
- const metadataValue = metadata[fieldToValidate];
192
-
193
- if(!DeepEqual(cborValue, metadataValue)) {
194
- response.invalidValues.push({
195
- key: fieldToValidate,
196
- cborValue: cborValue,
197
- metadataValue: metadataValue
198
- });
199
- }
200
- }
201
-
202
- if(response.invalidValues.length !== 0) {
203
- response.valid = false;
204
- }
205
-
206
- return response;
207
- },
208
- };
209
-
210
- module.exports = ContentObjectVerification;