Skip to content

Commit 34e99cd

Browse files
authored
feat(middleware-flexible-checksums): allow custom checksum algorithm implementations (#7746)
1 parent 637dcf0 commit 34e99cd

10 files changed

Lines changed: 88 additions & 40 deletions

.prettierignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,6 @@ CHANGELOG.md
66
clients/**/src
77
clients/**/README.md
88
private/**
9+
!private/aws-client-api-test/src/**/*
910
packages/nested-clients/src/submodules/*/endpoint/ruleset.ts
1011
**/*.java

packages-internal/middleware-flexible-checksums/src/configuration.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {
1+
import type {
22
BodyLengthCalculator,
33
ChecksumConstructor,
44
Encoder,
@@ -9,7 +9,8 @@ import {
99
StreamHasher,
1010
} from "@smithy/types";
1111

12-
import { RequestChecksumCalculation, ResponseChecksumValidation } from "./constants";
12+
import type { RequestChecksumCalculation, ResponseChecksumValidation } from "./constants";
13+
import type { FlexibleChecksumsInputConfig } from "./resolveFlexibleChecksumsConfig";
1314

1415
/**
1516
* @internal
@@ -74,4 +75,6 @@ export interface PreviouslyResolved {
7475
* Minimum bytes from a stream to buffer into a chunk before passing to chunked encoding.
7576
*/
7677
requestStreamBufferSize: number;
78+
79+
checksumAlgorithms?: FlexibleChecksumsInputConfig["checksumAlgorithms"];
7780
}

packages-internal/middleware-flexible-checksums/src/getChecksumAlgorithmForRequest.spec.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ describe(getChecksumAlgorithmForRequest.name, () => {
5555
});
5656
});
5757

58-
it("throws error if input[requestAlgorithmMember] if not supported by client", () => {
58+
it("does not throw an error if an unknown algo is selected", () => {
5959
const unsupportedAlgo = "unsupportedAlgo";
6060
const mockInput = { [mockRequestAlgorithmMember]: unsupportedAlgo };
6161
const mockOptions = {
@@ -65,10 +65,8 @@ describe(getChecksumAlgorithmForRequest.name, () => {
6565
};
6666
expect(() => {
6767
getChecksumAlgorithmForRequest(mockInput, mockOptions);
68-
}).toThrowError(
69-
`The checksum algorithm "${unsupportedAlgo}" is not supported by the client.` +
70-
` Select one of ${CLIENT_SUPPORTED_ALGORITHMS}.`
71-
);
68+
}).not.toThrowError();
69+
// throwing an error is deferred to the selectChecksumAlgorithmFunction step.
7270
});
7371

7472
describe("returns input[requestAlgorithmMember] if supported by client", () => {

packages-internal/middleware-flexible-checksums/src/getChecksumAlgorithmForRequest.ts

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { ChecksumAlgorithm, DEFAULT_CHECKSUM_ALGORITHM, RequestChecksumCalculation } from "./constants";
2-
import { CLIENT_SUPPORTED_ALGORITHMS } from "./types";
1+
import type { ChecksumAlgorithm } from "./constants";
2+
import { DEFAULT_CHECKSUM_ALGORITHM, RequestChecksumCalculation } from "./constants";
33

44
export interface GetChecksumAlgorithmForRequestOptions {
55
/**
@@ -26,7 +26,7 @@ export interface GetChecksumAlgorithmForRequestOptions {
2626
export const getChecksumAlgorithmForRequest = (
2727
input: any,
2828
{ requestChecksumRequired, requestAlgorithmMember, requestChecksumCalculation }: GetChecksumAlgorithmForRequestOptions
29-
): ChecksumAlgorithm | undefined => {
29+
): ChecksumAlgorithm | string | undefined => {
3030
// The Operation input member that is used to configure request checksum behavior is not set.
3131
if (!requestAlgorithmMember) {
3232
// Select an algorithm only if request checksum calculation is supported
@@ -41,13 +41,6 @@ export const getChecksumAlgorithmForRequest = (
4141
}
4242

4343
const checksumAlgorithm = input[requestAlgorithmMember];
44-
// Validate that at least one algorithm from customer preference is supported by the SDK.
45-
if (!CLIENT_SUPPORTED_ALGORITHMS.includes(checksumAlgorithm)) {
46-
throw new Error(
47-
`The checksum algorithm "${checksumAlgorithm}" is not supported by the client.` +
48-
` Select one of ${CLIENT_SUPPORTED_ALGORITHMS}.`
49-
);
50-
}
5144

52-
return checksumAlgorithm as ChecksumAlgorithm;
45+
return checksumAlgorithm as ChecksumAlgorithm | string;
5346
};

packages-internal/middleware-flexible-checksums/src/getChecksumLocationName.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@ import { ChecksumAlgorithm } from "./constants";
33
/**
44
* Returns location (header/trailer) name to use to populate checksum in.
55
*/
6-
export const getChecksumLocationName = (algorithm: ChecksumAlgorithm): string =>
6+
export const getChecksumLocationName = (algorithm: ChecksumAlgorithm | string): string =>
77
algorithm === ChecksumAlgorithm.MD5 ? "content-md5" : `x-amz-checksum-${algorithm.toLowerCase()}`;

packages-internal/middleware-flexible-checksums/src/resolveFlexibleChecksumsConfig.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ describe(resolveFlexibleChecksumsConfig.name, () => {
3131
requestChecksumCalculation: DEFAULT_REQUEST_CHECKSUM_CALCULATION,
3232
responseChecksumValidation: DEFAULT_RESPONSE_CHECKSUM_VALIDATION,
3333
requestStreamBufferSize: 0,
34+
checksumAlgorithms: {},
3435
});
3536
expect(normalizeProvider).toHaveBeenCalledTimes(2);
3637
});

packages-internal/middleware-flexible-checksums/src/resolveFlexibleChecksumsConfig.ts

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
1-
import { Provider } from "@smithy/types";
1+
import type { ChecksumConstructor, Provider } from "@smithy/types";
22
import { normalizeProvider } from "@smithy/util-middleware";
33

4-
import {
5-
DEFAULT_REQUEST_CHECKSUM_CALCULATION,
6-
DEFAULT_RESPONSE_CHECKSUM_VALIDATION,
7-
RequestChecksumCalculation,
8-
ResponseChecksumValidation,
9-
} from "./constants";
4+
import type { RequestChecksumCalculation, ResponseChecksumValidation } from "./constants";
5+
import { DEFAULT_REQUEST_CHECKSUM_CALCULATION, DEFAULT_RESPONSE_CHECKSUM_VALIDATION } from "./constants";
106

117
/**
128
* @public
@@ -38,6 +34,20 @@ export interface FlexibleChecksumsInputConfig {
3834
* of 8kb or greater.
3935
*/
4036
requestStreamBufferSize?: number | false;
37+
38+
/**
39+
* Optional implementations of checksum algorithms adhering to the
40+
* Checksum interface.
41+
*/
42+
checksumAlgorithms?: {
43+
CRC32?: ChecksumConstructor;
44+
CRC32C?: ChecksumConstructor;
45+
CRC64NVME?: ChecksumConstructor;
46+
SHA1?: ChecksumConstructor;
47+
SHA256?: ChecksumConstructor;
48+
} & {
49+
[algorithmId: string]: ChecksumConstructor;
50+
};
4151
}
4252

4353
/**
@@ -47,6 +57,7 @@ export interface FlexibleChecksumsResolvedConfig {
4757
requestChecksumCalculation: Provider<RequestChecksumCalculation>;
4858
responseChecksumValidation: Provider<ResponseChecksumValidation>;
4959
requestStreamBufferSize: number;
60+
checksumAlgorithms?: FlexibleChecksumsInputConfig["checksumAlgorithms"];
5061
}
5162

5263
/**
@@ -60,5 +71,6 @@ export const resolveFlexibleChecksumsConfig = <T>(
6071
requestChecksumCalculation: normalizeProvider(requestChecksumCalculation ?? DEFAULT_REQUEST_CHECKSUM_CALCULATION),
6172
responseChecksumValidation: normalizeProvider(responseChecksumValidation ?? DEFAULT_RESPONSE_CHECKSUM_VALIDATION),
6273
requestStreamBufferSize: Number(requestStreamBufferSize ?? 0),
74+
checksumAlgorithms: input.checksumAlgorithms ?? {},
6375
});
6476
};

packages-internal/middleware-flexible-checksums/src/selectChecksumAlgorithmFunction.spec.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ describe(selectChecksumAlgorithmFunction.name, () => {
1111
md5: vi.fn(),
1212
sha1: vi.fn(),
1313
sha256: vi.fn(),
14+
checksumAlgorithms: {
15+
novel: class {},
16+
},
1417
};
1518

1619
it.each([
@@ -24,7 +27,11 @@ describe(selectChecksumAlgorithmFunction.name, () => {
2427
});
2528

2629
it("throws an error for unsupported checksum algorithm", () => {
27-
expect(() => selectChecksumAlgorithmFunction("UNSUPPORTED" as any, mockConfig as any)).toThrow();
30+
expect(() => selectChecksumAlgorithmFunction("UNSUPPORTED", mockConfig as any)).toThrow();
31+
});
32+
33+
it("doesn't throw an error if the unknown algo's implementation was provided", () => {
34+
expect(() => selectChecksumAlgorithmFunction("novel", mockConfig as any)).not.toThrow();
2835
});
2936

3037
it("returns Crc64Nvme if crc64NvmeCrtContainer.CrtCrc64Nvme is not a function", () => {
Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,44 @@
11
import { AwsCrc32c } from "@aws-crypto/crc32c";
22
import { Crc64Nvme, crc64NvmeCrtContainer } from "@aws-sdk/crc64-nvme";
3-
import { ChecksumConstructor, HashConstructor } from "@smithy/types";
3+
import type { ChecksumConstructor, HashConstructor } from "@smithy/types";
44

5-
import { PreviouslyResolved } from "./configuration";
5+
import type { PreviouslyResolved } from "./configuration";
66
import { ChecksumAlgorithm } from "./constants";
77
import { getCrc32ChecksumAlgorithmFunction } from "./getCrc32ChecksumAlgorithmFunction";
8+
import { CLIENT_SUPPORTED_ALGORITHMS } from "./types";
89

910
/**
1011
* Returns the function that will compute the checksum for the given {@link ChecksumAlgorithm}.
1112
*/
1213
export const selectChecksumAlgorithmFunction = (
13-
checksumAlgorithm: ChecksumAlgorithm,
14+
checksumAlgorithm: ChecksumAlgorithm | string,
1415
config: PreviouslyResolved
1516
): ChecksumConstructor | HashConstructor => {
17+
const { checksumAlgorithms = {} } = config;
1618
switch (checksumAlgorithm) {
1719
case ChecksumAlgorithm.MD5:
18-
return config.md5;
20+
return checksumAlgorithms?.MD5 ?? config.md5;
1921
case ChecksumAlgorithm.CRC32:
20-
return getCrc32ChecksumAlgorithmFunction();
22+
return checksumAlgorithms?.CRC32 ?? getCrc32ChecksumAlgorithmFunction();
2123
case ChecksumAlgorithm.CRC32C:
22-
return AwsCrc32c;
24+
return checksumAlgorithms?.CRC32C ?? AwsCrc32c;
2325
case ChecksumAlgorithm.CRC64NVME:
2426
if (typeof crc64NvmeCrtContainer.CrtCrc64Nvme !== "function") {
25-
return Crc64Nvme;
27+
return checksumAlgorithms?.CRC64NVME ?? Crc64Nvme;
2628
}
27-
return crc64NvmeCrtContainer.CrtCrc64Nvme;
29+
return checksumAlgorithms?.CRC64NVME ?? crc64NvmeCrtContainer.CrtCrc64Nvme;
2830
case ChecksumAlgorithm.SHA1:
29-
return config.sha1;
31+
return checksumAlgorithms?.SHA1 ?? config.sha1;
3032
case ChecksumAlgorithm.SHA256:
31-
return config.sha256;
33+
return checksumAlgorithms?.SHA256 ?? config.sha256;
3234
default:
33-
throw new Error(`Unsupported checksum algorithm: ${checksumAlgorithm}`);
35+
if (checksumAlgorithms?.[checksumAlgorithm]) {
36+
return checksumAlgorithms[checksumAlgorithm];
37+
}
38+
throw new Error(
39+
`The checksum algorithm "${checksumAlgorithm}" is not supported by the client.` +
40+
` Select one of ${CLIENT_SUPPORTED_ALGORITHMS}, or provide an implementation to ` +
41+
` the client constructor checksums field.`
42+
);
3443
}
3544
};

private/aws-client-api-test/src/client-interface-tests/client-s3/impl/initializeWithMaximalConfiguration.ts

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { S3Client, S3ClientConfigType } from "@aws-sdk/client-s3";
1+
import type { S3ClientConfigType } from "@aws-sdk/client-s3";
2+
import { S3Client } from "@aws-sdk/client-s3";
23
import { AwsRestXmlProtocol } from "@aws-sdk/core/protocols";
34
import { defaultProvider as credentialDefaultProvider, defaultProvider } from "@aws-sdk/credential-provider-node";
45
import { NODE_USE_ARN_REGION_CONFIG_OPTIONS } from "@aws-sdk/middleware-bucket-endpoint";
@@ -22,7 +23,7 @@ import { NODE_MAX_ATTEMPT_CONFIG_OPTIONS, NODE_RETRY_MODE_CONFIG_OPTIONS } from
2223
import { loadConfig as loadNodeConfig } from "@smithy/node-config-provider";
2324
import { NodeHttpHandler, streamCollector } from "@smithy/node-http-handler";
2425
import { loadConfigsForDefaultMode } from "@smithy/smithy-client";
25-
import { EndpointV2, HttpAuthSchemeProvider } from "@smithy/types";
26+
import type { EndpointV2, HttpAuthSchemeProvider } from "@smithy/types";
2627
import { parseUrl } from "@smithy/url-parser";
2728
import { fromBase64, toBase64 } from "@smithy/util-base64";
2829
import { calculateBodyLength } from "@smithy/util-body-length-node";
@@ -144,6 +145,29 @@ export const initializeWithMaximalConfiguration = () => {
144145
bucketEndpoint: false,
145146
requestChecksumCalculation: DEFAULT_REQUEST_CHECKSUM_CALCULATION,
146147
responseChecksumValidation: DEFAULT_RESPONSE_CHECKSUM_VALIDATION,
148+
checksumAlgorithms: {
149+
SHA256: class {
150+
update() {}
151+
reset() {}
152+
async digest() {
153+
return new Uint8Array();
154+
}
155+
},
156+
CRC64NVME: class {
157+
update() {}
158+
reset() {}
159+
async digest() {
160+
return new Uint8Array();
161+
}
162+
},
163+
unknown: class {
164+
update() {}
165+
reset() {}
166+
async digest() {
167+
return new Uint8Array();
168+
}
169+
},
170+
},
147171
requestStreamBufferSize: 8 * 1024,
148172
expectContinueHeader: 8 * 1024 * 1024,
149173
};

0 commit comments

Comments
 (0)