@@ -65,6 +65,7 @@ import PolicyObject from '../../src/tdf/PolicyObject.js';
6565import { type CryptoService , type DecryptResult } from './crypto/declarations.js' ;
6666import { CentralDirectory } from './utils/zip-reader.js' ;
6767import { SymmetricCipher } from './ciphers/symmetric-cipher-base.js' ;
68+ import { allPool , anyPool } from '../../src/concurrency.js' ;
6869
6970// TODO: input validation on manifest JSON
7071const DEFAULT_SEGMENT_SIZE = 1024 * 1024 ;
@@ -163,6 +164,7 @@ export type DecryptConfiguration = {
163164 fileStreamServiceWorker ?: string ;
164165 assertionVerificationKeys ?: AssertionVerificationKeys ;
165166 noVerifyAssertions ?: boolean ;
167+ concurrencyLimit ?: number ;
166168} ;
167169
168170export type UpsertConfiguration = {
@@ -904,17 +906,24 @@ export function splitLookupTableFactory(
904906 return splitPotentials ;
905907}
906908
909+ type RewrapResponseData = {
910+ key : Uint8Array ;
911+ metadata : Record < string , unknown > ;
912+ } ;
913+
907914async function unwrapKey ( {
908915 manifest,
909916 allowedKases,
910917 authProvider,
911918 dpopKeys,
919+ concurrencyLimit,
912920 entity,
913921 cryptoService,
914922} : {
915923 manifest : Manifest ;
916924 allowedKases : OriginAllowList ;
917925 authProvider : AuthProvider | AppIdAuthProvider ;
926+ concurrencyLimit ?: number ;
918927 dpopKeys : CryptoKeyPair ;
919928 entity : EntityObject | undefined ;
920929 cryptoService : CryptoService ;
@@ -928,7 +937,7 @@ async function unwrapKey({
928937 const splitPotentials = splitLookupTableFactory ( keyAccess , allowedKases ) ;
929938 const isAppIdProvider = authProvider && isAppIdProviderCheck ( authProvider ) ;
930939
931- async function tryKasRewrap ( keySplitInfo : KeyAccessObject ) {
940+ async function tryKasRewrap ( keySplitInfo : KeyAccessObject ) : Promise < RewrapResponseData > {
932941 const url = `${ keySplitInfo . url } /${ isAppIdProvider ? '' : 'v2/' } rewrap` ;
933942 const ephemeralEncryptionKeys = await cryptoService . cryptoToPemPair (
934943 await cryptoService . generateKeyPair ( )
@@ -982,77 +991,44 @@ async function unwrapKey({
982991 } ;
983992 }
984993
985- // Get unique split IDs to determine if we have an OR or AND condition
986- const splitIds = new Set ( Object . keys ( splitPotentials ) ) ;
987-
988- // If we have only one split ID, it's an OR condition
989- if ( splitIds . size === 1 ) {
990- const [ splitId ] = splitIds ;
994+ const poolSize = concurrencyLimit === undefined ? 1 : concurrencyLimit > 1 ? concurrencyLimit : 1 ;
995+ const splitPromises : Record < string , Promise < RewrapResponseData > > = { } ;
996+ for ( const splitId of Object . keys ( splitPotentials ) ) {
991997 const potentials = splitPotentials [ splitId ] ;
992-
993- try {
994- // OR condition: Try all KAS servers for this split, take first success
995- const result = await Promise . any (
996- Object . values ( potentials ) . map ( async ( keySplitInfo ) => {
997- try {
998- return await tryKasRewrap ( keySplitInfo ) ;
999- } catch ( e ) {
1000- // Rethrow with more context
1001- throw handleRewrapError ( e as Error | AxiosError ) ;
1002- }
1003- } )
998+ if ( ! potentials || ! Object . keys ( potentials ) . length ) {
999+ throw new UnsafeUrlError (
1000+ `Unreconstructable key - no valid KAS found for split ${ JSON . stringify ( splitId ) } ` ,
1001+ ''
10041002 ) ;
1005-
1006- const reconstructedKey = keyMerge ( [ result . key ] ) ;
1007- return {
1008- reconstructedKeyBinary : Binary . fromArrayBuffer ( reconstructedKey ) ,
1009- metadata : result . metadata ,
1010- } ;
1011- } catch ( error ) {
1012- if ( error instanceof AggregateError ) {
1013- // All KAS servers failed
1014- throw error . errors [ 0 ] ; // Throw the first error since we've already wrapped them
1015- }
1016- throw error ;
10171003 }
1018- } else {
1019- // AND condition: We need successful results from all different splits
1020- const splitResults = await Promise . all (
1021- Object . entries ( splitPotentials ) . map ( async ( [ splitId , potentials ] ) => {
1022- if ( ! potentials || ! Object . keys ( potentials ) . length ) {
1023- throw new UnsafeUrlError (
1024- `Unreconstructable key - no valid KAS found for split ${ JSON . stringify ( splitId ) } ` ,
1025- ''
1026- ) ;
1027- }
1028-
1004+ const anyPromises : Record < string , Promise < RewrapResponseData > > = { } ;
1005+ for ( const [ kas , keySplitInfo ] of Object . entries ( potentials ) ) {
1006+ anyPromises [ kas ] = ( async ( ) => {
10291007 try {
1030- // For each split, try all potential KAS servers until one succeeds
1031- return await Promise . any (
1032- Object . values ( potentials ) . map ( async ( keySplitInfo ) => {
1033- try {
1034- return await tryKasRewrap ( keySplitInfo ) ;
1035- } catch ( e ) {
1036- throw handleRewrapError ( e as Error | AxiosError ) ;
1037- }
1038- } )
1039- ) ;
1040- } catch ( error ) {
1041- if ( error instanceof AggregateError ) {
1042- // All KAS servers for this split failed
1043- throw error . errors [ 0 ] ; // Throw the first error since we've already wrapped them
1044- }
1045- throw error ;
1008+ return await tryKasRewrap ( keySplitInfo ) ;
1009+ } catch ( e ) {
1010+ throw handleRewrapError ( e as Error | AxiosError ) ;
10461011 }
1047- } )
1048- ) ;
1049-
1012+ } ) ( ) ;
1013+ }
1014+ splitPromises [ splitId ] = anyPool ( poolSize , anyPromises ) ;
1015+ }
1016+ try {
1017+ const splitResults = await allPool ( poolSize , splitPromises ) ;
10501018 // Merge all the split keys
10511019 const reconstructedKey = keyMerge ( splitResults . map ( ( r ) => r . key ) ) ;
10521020 return {
10531021 reconstructedKeyBinary : Binary . fromArrayBuffer ( reconstructedKey ) ,
10541022 metadata : splitResults [ 0 ] . metadata , // Use metadata from first split
10551023 } ;
1024+ } catch ( e ) {
1025+ if ( e instanceof AggregateError ) {
1026+ const errors = e . errors ;
1027+ if ( errors . length === 1 ) {
1028+ throw errors [ 0 ] ;
1029+ }
1030+ }
1031+ throw e ;
10561032 }
10571033}
10581034
0 commit comments