@@ -235,47 +235,75 @@ extern "C" {
235235 }
236236
237237 int pw_decoder_read_validated_tag (pw_decoder_t *self, uint32_t *tag_out) {
238- int pos_before = self->codedInputStream .CurrentPosition ();
239-
240- uint64_t raw64;
241- if (!self->codedInputStream .ReadVarint64 (&raw64)) {
242- // Use ConsumedEntireMessage() to distinguish
243- // legitimate end-of-stream from actual errors (like >10-byte varints).
244- // Note: CurrentPosition() alone is insufficient because ReadVarint64's
245- // fast-path array reader does not advance the buffer pointer on failure.
246- if (self->codedInputStream .ConsumedEntireMessage ()) {
247- return 0 ; // legitimate end of stream
248- }
249- return -1 ; // error (>10-byte varint, truncated varint, etc.)
238+ // Zero-initialize so the Kotlin caller always has a meaningful value
239+ // for error diagnostics, even on early-return error paths.
240+ *tag_out = 0 ;
241+
242+ // Sub-message boundary: BytesUntilLimit() == 0 when a PushLimit scope
243+ // is active and all bytes within it have been consumed. This mirrors
244+ // ReadTag()'s internal limit check.
245+ if (self->codedInputStream .BytesUntilLimit () == 0 ) {
246+ return 0 ;
250247 }
251248
252- int pos_after = self->codedInputStream .CurrentPosition ();
253- int bytes_used = pos_after - pos_before;
254-
255- // A zero tag value read from actual bytes is invalid (field number 0).
256- if (raw64 == 0 ) {
257- return -1 ;
249+ // Read the first byte via ReadRaw to reliably distinguish top-level
250+ // EOF from a varint start. BytesUntilLimit() is either -1 (no limit,
251+ // top-level context where current_limit_ is INT_MAX) or positive
252+ // (inside a PushLimit scope with remaining data). In either case,
253+ // ReadRaw(1) failure means the underlying stream is exhausted.
254+ //
255+ // This avoids ReadTag()'s ambiguous return-0-for-both-EOF-and-errors
256+ // and ConsumedEntireMessage()'s broken behavior at the top level (no
257+ // limit set → legitimate_message_end_ is never set).
258+ uint8_t b;
259+ if (!self->codedInputStream .ReadRaw (&b, 1 )) {
260+ // BytesUntilLimit == -1: top-level EOF (no limit active, stream
261+ // exhausted). BytesUntilLimit > 0: the limit says data should be
262+ // available but the stream is exhausted — I/O error or truncation.
263+ if (self->codedInputStream .BytesUntilLimit () > 0 ) {
264+ return -1 ; // truncated stream
265+ }
266+ return 0 ; // top-level EOF
258267 }
259268
260- // Tag must fit in 32 bits (29-bit field number + 3-bit wire type).
261- if (raw64 > UINT32_MAX ) {
262- return -1 ;
269+ // Parse the varint manually, tracking byte count for overlong detection.
270+ // Tags are uint32 (max 5 varint bytes), but we must read up to 10 bytes
271+ // to detect the >10-byte varint conformance case.
272+ uint64_t result = b & 0x7F ;
273+ int bytes_used = 1 ;
274+
275+ while (b >= 0x80 ) {
276+ if (bytes_used >= 10 ) {
277+ // Write partial result for diagnostics before returning error.
278+ *tag_out = static_cast <uint32_t >(result & UINT32_MAX );
279+ return -1 ; // varint exceeds 10 bytes
280+ }
281+ if (!self->codedInputStream .ReadRaw (&b, 1 )) {
282+ *tag_out = static_cast <uint32_t >(result & UINT32_MAX );
283+ return -1 ; // truncated varint
284+ }
285+ result |= static_cast <uint64_t >(b & 0x7F ) << (7 * bytes_used);
286+ bytes_used++;
263287 }
264288
289+ // Write the decoded value for diagnostics on all remaining error paths.
290+ *tag_out = static_cast <uint32_t >(result & UINT32_MAX );
291+
292+ if (result == 0 ) return -1 ; // zero tag (invalid field number 0)
293+ if (result > UINT32_MAX ) return -1 ; // exceeds 32-bit tag range
294+
265295 // Reject overlong varint encoding: the varint used more bytes than the
266296 // minimum required for its value. Each varint byte carries 7 payload bits.
267297 int min_bytes;
268- if (raw64 < (1ULL << 7 )) min_bytes = 1 ;
269- else if (raw64 < (1ULL << 14 )) min_bytes = 2 ;
270- else if (raw64 < (1ULL << 21 )) min_bytes = 3 ;
271- else if (raw64 < (1ULL << 28 )) min_bytes = 4 ;
272- else min_bytes = 5 ;
273-
274- if (bytes_used > min_bytes) {
275- return -1 ;
276- }
298+ if (result < (1ULL << 7 )) min_bytes = 1 ;
299+ else if (result < (1ULL << 14 )) min_bytes = 2 ;
300+ else if (result < (1ULL << 21 )) min_bytes = 3 ;
301+ else if (result < (1ULL << 28 )) min_bytes = 4 ;
302+ else min_bytes = 5 ;
303+
304+ if (bytes_used > min_bytes) return -1 ;
277305
278- *tag_out = static_cast < uint32_t >(raw64);
306+ // Success — *tag_out already set above.
279307 return 1 ;
280308 }
281309
0 commit comments