Event stream decoder reports a checksum mismatch when the checksum is read in parts
Maintainers usually reply within 2 days
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 25/100
Research direction
The bug is in NonSeekableStreamDecodingEventStreamIterator, where the trailing four-byte checksum is read with a single read(4) call before comparison. Check how the existing partial-read handling for headers and payload works, then make the checksum read loop until it has four bytes or the stream ends. Done when the reproduction script no longer throws and a partial-read test with one-byte reads passes. Pull request #3373 is already open against this issue, so review it first.
Written by the indexing model from the issue text.
Description
Describe the bug
NonSeekableStreamDecodingEventStreamIterator rejects a valid event when the stream returns its trailing checksum in more than one read.
The decoder handles partial reads for the headers and payload, but reads the four checksum bytes with a single read(4) call. A stream can return fewer bytes even when more data is still coming.
Expected Behavior
I think the decoder should keep reading until it has all four checksum bytes, or reaches the end of the stream.
Current Behavior
The example below throws:
Aws\Api\Parser\Exception\ParserException: Message checksum mismatch.
The frame has a valid checksum. It parses successfully when the read limit is removed.
Reproduction Steps
This example doesn’t need an AWS account or make any network requests.
Install aws/aws-sdk-php version 3.399.2, then run:
<?php
require 'vendor/autoload.php';
use Aws\Api\Parser\NonSeekableStreamDecodingEventStreamIterator;
use GuzzleHttp\Psr7\FnStream;
use GuzzleHttp\Psr7\NoSeekStream;
use GuzzleHttp\Psr7\Utils;
$headers = '';
foreach ([
':message-type' => 'event',
':event-type' => 'messageStop',
':content-type' => 'application/json',
] as $name => $value) {
$headers .= chr(strlen($name)).$name.chr(7)
.pack('n', strlen($value)).$value;
}
$payload = '{"stopReason":"end_turn"}';
$prelude = pack('NN', 16 + strlen($headers) + strlen($payload), strlen($headers));
$frame = $prelude.hash('crc32b', $prelude, true).$headers.$payload;
$frame .= hash('crc32b', $frame, true);
$body = Utils::streamFor($frame);
$stream = new NoSeekStream(FnStream::decorate($body, [
'read' => static fn ($length) => $body->read(min(1, $length)),
]));
$iterator = new NonSeekableStreamDecodingEventStreamIterator($stream);
$iterator->rewind();
Possible Solution
Read the complete trailing checksum before comparing it. The existing partial-read test could also include reads limited to one byte.
Additional Information/Context
This appears to be a missed case from #3165, which fixed partial reads for #3110. Its test limits reads to 20 bytes, so the four-byte checksum still arrives in one read.
I also reproduced this with a local HTTP server that sends the checksum in two parts. The same single checksum read is still present on master.
SDK version used
3.399.2
Environment details
PHP 8.4.25, Ubuntu 24.04.
Generated by Codex, and reviewed by @binaryfire.
- Dominant language
- PHP
- Stars
- 6.2k
- Forks
- 1.3k
- Avg merge
- 2d 7h
- Merged PRs (30d)
- 11
Getting set up
- No Dockerfile or Docker Compose file
- No pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from aws/aws-sdk-php
-
feature-request needs-triage
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
aws/aws-sdk-php#3365 ·
Maintainers usually reply within 2 days
-
Event stream decoder drops the final event when reading it reaches EOFPossibly taken @lazerg claimed this 1 day ago. Open
Difficulty 3/5 Half a day Newbie friendliness 62/100
aws/aws-sdk-php#3374 ·
Maintainers usually reply within 2 days
-
AssumeRoleCredentialProvider replaces non-RuntimeException failures with TypeErrorPossibly taken @lazerg claimed this 5 days ago. Open
Difficulty 2/5 1-3 hours Newbie friendliness 25/100
aws/aws-sdk-php#3370 ·
Maintainers usually reply within 2 days
-
announcement
Difficulty 4/5 3-5 days Newbie friendliness 12/100
aws/aws-sdk-php#3369 ·
Maintainers usually reply within 2 days
-
bug needs-triage
Difficulty 4/5 3-5 days Newbie friendliness 30/100
aws/aws-sdk-php#3364 ·
Maintainers usually reply within 2 days
Similar issues
-
HttpRequestTrait fatals on WordPress 7.2: $blocked_http_requests clashes with WP_UnitTestCase_BaseOpen
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
pluginsGLPI/addressing#127 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 85/100
WordPress/create-block-theme#886 · 1 reaction ·
Maintainers usually reply within 2 days
-
Difficulty 2/5 1-3 hours Newbie friendliness 85/100
humanmade/hm-popup-block#18 ·