CLDSRV-863: Checksums for PutObject and UploadPart#6112
CLDSRV-863: Checksums for PutObject and UploadPart#6112leif-scality wants to merge 15 commits intodevelopment/9.4from
Conversation
Hello leif-scality,My role is to assist you with the merge of this Available options
Available commands
Status report is not available. |
Incorrect fix versionThe
Considering where you are trying to merge, I ignored possible hotfix versions and I expected to find:
Please check the |
❌ 1 Tests Failed:
View the top 1 failed test(s) by shortest run time
To view more test analytics, go to the Test Analytics Dashboard |
|
LGTM |
| _flush(callback) { | ||
| Promise.resolve(this.algo.digestFromHash(this.hash)) | ||
| .then(digest => { this.digest = digest; }) | ||
| .then(() => callback(), err => { |
There was a problem hiding this comment.
prefer using catch for error handling
There was a problem hiding this comment.
claude complains about double calling callback, once in the then and again in the catch if the first callback throws
There was a problem hiding this comment.
you could also use only 1 then
| return callback(); | ||
| } else if (!this.streamClosed && this.readingTrailer && this.trailerBuffer.length !== 0) { | ||
| this.log.error('stream ended without trailer "\r\n"'); | ||
| return callback(errors.IncompleteBody.customizeDescription( |
There was a problem hiding this comment.
you could use errorInstances when using customizeDescription to avoid instantiating twice an error
| crc64nvme: 'AAAAAAAAAAA=', // 12 chars | ||
| }; | ||
|
|
||
| it('no headers: returns crc64nvme with isTrailer=false and expected=undefined', () => { |
There was a problem hiding this comment.
prefer starting tests with should
| error: ChecksumError.XAmzMismatch, | ||
| details: { algorithm: 'crc32', calculated: 'a', expected: 'b' }, | ||
| }); | ||
| assert(result.is.BadDigest); |
There was a problem hiding this comment.
I don't like that is helper is tests because if test fails you compare booleans (false != true) instead of names (InternalError != BadDigest), so you don't have much details.
Can we compre on error name with strings ?
|
LGTM — well-structured checksum pipeline with good error handling and thorough tests. |
| return null; | ||
| } | ||
|
|
||
| if (this.trailerChecksumValue) { |
There was a problem hiding this comment.
validateChecksum() uses a truthy check to detect unexpected trailers on line 56. If setExpectedChecksum is called with an empty string value (e.g. from a malformed trailer like x-amz-checksum-crc32: with nothing after the colon), the check evaluates to false and the unexpected trailer goes undetected. Consider using this.trailerChecksumValue !== undefined for consistency with line 27. — Claude Code
|
| const dataStream = stripTrailingChecksumStream(dataStreamTmp, log, cbOnce); | ||
| return data.put( | ||
| cipherBundle, dataStream, size, objectContext, backendInfo, log, | ||
| cipherBundle, checksumedStream.stream, size, objectContext, backendInfo, log, |
There was a problem hiding this comment.
storeObject.js passes checksumedStream.stream (a ChecksumTransform) to data.put, but ChecksumTransform never has .headers set on it. In the old code, prepareStream always returned a stream with .headers = stream.headers. If any data backend or middleware reads .headers from the stream passed to data.put, this will be undefined.
I checked lib/data/wrapper.js and it does not reference .headers, but worth verifying against all backend implementations (sproxyd, azure, gcp, etc.) that none rely on it.
— Claude Code
There was a problem hiding this comment.
.headers: checked — no backend in lib/data/ or Arsenal reads .headers off the stream passed to data.put.
| if (checksumedStream.stream.writableFinished) { | ||
| return doValidate(); | ||
| } | ||
| checksumedStream.stream.once('finish', doValidate); |
There was a problem hiding this comment.
Potential race: if ChecksumTransform errors during _flush (e.g. crc64nvme digest fails), the error goes through errCb which calls cbOnce. But if writableFinished was false, the doValidate listener is also registered on 'finish'. Since _flush called callback(error), the stream will not emit 'finish' so the listener is harmless — but the 'finish' and error paths are independent, and both can potentially reach cbOnce. The jsutil.once(cb) guard prevents double-callback, but the upgraded onStreamError (line 88-96) could trigger batchDelete even though doValidate also triggers batchDelete on checksum failure. Consider whether both paths could fire in an edge case.
— Claude Code
There was a problem hiding this comment.
double-delete: not a risk — finish and error are mutually exclusive in the Node.js stream lifecycle, so both handlers can't fire for the same stream.
|
Well-structured PR that adds checksum validation for PutObject and UploadPart across all streaming modes. The ChecksumTransform, trailer parsing, and error mapping are clean. Two items to verify: |
| // urlFn() is called lazily at test runtime so that uploadId is available. | ||
| function makeScenarioTests(urlFn) { | ||
| itSkipIfAWS( | ||
| 'testS3PutNoChecksum: signed sha256 in x-amz-content-sha256, no x-amz-checksum header -> 200 OK', |
There was a problem hiding this comment.
same here, prefer starting tests with should
| }; | ||
|
|
||
| describe('prepareStream return value shape', () => { | ||
| it('returns { error: null, stream: ChecksumTransform } for UNSIGNED-PAYLOAD', () => { |
There was a problem hiding this comment.
same here prefer starting test with should
| }); | ||
| }); | ||
|
|
||
| describe('prepareStream STREAMING-AWS4-HMAC-SHA256-PAYLOAD', () => { |
There was a problem hiding this comment.
prefer one global prepareStream describe, and then other describes inside
|
LGTM |
| } | ||
|
|
||
| describe('normal behaviour', () => { | ||
| it('calls data.put with the stream returned by prepareStream', done => { |
| }); | ||
|
|
||
| for (const algo of algos) { | ||
| it(`[${algo}] passes data through unchanged`, async () => { |
| }); | ||
| }); | ||
|
|
||
| describe('ChecksumTransform validateChecksum — non-trailer mode (isTrailer=false)', () => { |
There was a problem hiding this comment.
prefer using one global describe and then others inside
No description provided.