Skip to content

Conversation

smilkuri
Copy link
Contributor

Issue

Internal JS-6196

Description

  • Adds validation to check total number of UploadPart requests the SDK has sent matches the expected number of parts.
  • Adds validation to check the content length of each UploadPart request matches the expected part size.

Testing

Locally

dev-dsk-smilkuri-1a-17aece6b % yarn test

 RUN  v3.2.4 /local/home/smilkuri/aws-sdk-js-v3/lib/lib-storage

 ✓ src/chunks/getChunkUint8Array.spec.ts (6 tests) 7ms
 ✓ src/chunks/getDataReadableStream.spec.ts (4 tests) 168ms
 ✓ src/chunks/getDataReadable.spec.ts (3 tests) 167ms
 ✓ src/index.spec.ts (1 test) 2ms
 ✓ src/Upload.spec.ts (34 tests) 22888ms
   ✓ Upload > should add tags to the object if tags have been added multi-part  22807ms

 Test Files  5 passed (5)
      Tests  48 passed (48)
   Start at  16:15:06
   Duration  23.90s (transform 861ms, setup 0ms, collect 2.42s, tests 23.23s, environment 1ms, prepare 584ms)

Additional context

Add any other context about the PR here.

Checklist

  • [n/a] If the PR is a feature, add integration tests (*.integ.spec.ts).
  • [n/a] If you wrote E2E tests, are they resilient to concurrent I/O?
  • [n/a] If adding new public functions, did you add the @public tag and enable doc generation on the package?

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@smilkuri smilkuri requested a review from a team as a code owner September 19, 2025 16:22
@smilkuri smilkuri changed the title fixyarn test(lib-storage): add validation for partCount and contentLength for multipart upload test(lib-storage): add validation for partCount and contentLength for multipart upload Sep 19, 2025
@smilkuri smilkuri changed the title test(lib-storage): add validation for partCount and contentLength for multipart upload fix(lib-storage): add validation for partCount and contentLength for multipart upload Sep 19, 2025
});
}

private __calculatePartSize(targetPartSizeBytes: number, minPartSize: number): number {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable name targetPartSizeBytes is inaccurate because it is not a part size. It is the size of the entire object.

There's no need for the second arg since it is only ever called with Upload.MIN_PART_SIZE.

But beyond that, there's no need for this function since it's only called once.

this.partSize = Math.max(
  Upload.MIN_PART_SIZE, 
  Math.floor((this.totalBytes ?? 0) / this.MAX_PARTS)
);

this.abortController = options.abortController ?? new AbortController();

this.partSize = this.__calculatePartSize(this.totalBytes ?? 0, Upload.MIN_PART_SIZE);
this.expectedPartsCount = this.totalBytes ? Math.ceil(this.totalBytes / this.partSize) : 1;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get the sense that this is a check for whether totalBytes was able to be calculated and not whether or not it is the numeric value 0.

In that case, it would be clearer to state the check as this.totalBytes !== undefined ?. However, with a zero byte input, the expected part count is indeed 1, so perhaps no change is needed.


But, why would expectedPartsCount be 1 if totalBytes could not be calculated? It could be a stream of an unknown byte length. In this case, both totalBytes and expectedPartCount should be undefined, since they would not be knowable.


private uploadedParts: CompletedPart[] = [];
private uploadEnqueuedPartsCount = 0;
private expectedPartsCount = 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it should not be assigned a value here, since 0 is never possible and a value is always assigned in the constructor.

But since my comments below understand this value could be impossible to calculate, in which case it should be undefined. Therefore the type should be private expectedPartsCount?: number;.


let result;
if (this.isMultiPart) {
if (this.totalBytes && this.uploadedParts.length !== this.expectedPartsCount) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

totalBytes can be zero and expectedPartsCount would be 1 in that case.

This should instead be

const { expectedPartsCount, uploadedParts } = this;

if (expectedPartsCount !== undefined 
    && uploadedParts.length !== expectedPartsCount) {

if (this.isMultiPart) {
if (this.totalBytes && this.uploadedParts.length !== this.expectedPartsCount) {
throw new Error(
`Expected ${this.expectedPartsCount} number of parts but uploaded only ${this.uploadedParts.length} parts`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
`Expected ${this.expectedPartsCount} number of parts but uploaded only ${this.uploadedParts.length} parts`
`Expected ${this.expectedPartsCount} part(s) but uploaded ${this.uploadedParts.length} part(s).`

Can't say "only" because the check is for inequality. The uploaded parts count could be higher.

return Math.max(minPartSize, calculatedPartSize);
}

private __validateUploadPart(dataPart: RawDataPart, partSize: number): void {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is partSize an argument to this method when it is only ever the same instance variable this.partSize?

}

private __validateUploadPart(dataPart: RawDataPart, partSize: number): void {
const actualPartSize = byteLength(dataPart.data) || 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the fallback value of undefined should be used, since real data may be zero bytes.

return;
}

if (actualPartSize === 0) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (actualPartSize === 0) {
if (actualPartSize === undefined) {

}

if (actualPartSize === 0) {
throw new Error(`Content length is missing on the data for part number ${dataPart.partNumber}`);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This statement implies the response header is missing, since content length usually refers to the header of that name.

But it's not related to that header. This error branch is only due to programming error on our part, right? The error message should be developer-facing, e.g.

Error: a dataPart was generated without a measurable data chunk size.

return;
}

this.__validateUploadPart(dataPart, this.partSize);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be moved closer to UploadPartCommand to prevent any changes happening to dataPart between the validation and sending out the data

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants