Skip to content

Timestamp ProtoAdapter silently encodes out-of-range Instant values #3548

@mdub

Description

@mdub

Summary

Wire's ProtoAdapter for google.protobuf.Timestamp does not validate that Instant values fall within the protobuf spec's valid range (0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z). Out-of-range values like Instant.MIN are silently serialized, producing messages that are technically invalid per the protobuf spec.

These invalid messages decode fine (the wire format is just int64), but blow up downstream when any code calls Timestamps.checkValid() or Timestamps.toMillis() — for example, Confluent's ProtobufData converter.

Reproducer

import com.squareup.wire.ProtoAdapter
import com.squareup.wire.ofEpochSecond
import java.time.Instant

// This succeeds — but the resulting bytes contain an invalid Timestamp
val instant = ofEpochSecond(Instant.MIN.epochSecond, 0L)
val bytes = ProtoAdapter.INSTANT.encode(instant)

// Round-trip through Wire works fine (no validation)
val decoded = ProtoAdapter.INSTANT.decode(bytes)
println(decoded.getEpochSecond()) // -31557014167219200

// But Google's protobuf-java-util rejects it
val timestamp = com.google.protobuf.Timestamp.newBuilder()
    .setSeconds(decoded.getEpochSecond())
    .build()
com.google.protobuf.util.Timestamps.toMillis(timestamp) // throws IllegalArgumentException
java.lang.IllegalArgumentException: Timestamp is not valid.
Seconds (-31557014167219200) must be in range [-62,135,596,800, +253,402,300,799].

Affected code

ProtoAdapter.kt line 1295-1300:

override fun encode(writer: ProtoWriter, value: Instant) {
    val seconds = value.getEpochSecond()
    if (seconds != 0L) INT64.encodeWithTag(writer, 1, seconds)
    val nanos = value.getNano()
    if (nanos != 0) INT32.encodeWithTag(writer, 2, nanos)
}

No range check on seconds.

Suggestion

Validate during Timestamp().build() that seconds and nanos are within (protobuf spec) range, throwing IllegalArgumentException they aren't.

Or, perform that validation during encode().

This would catch bugs at the point of serialization rather than letting invalid data propagate silently.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions