Skip to content

Read numeric WS dates as epoch nanoseconds - #87

Merged
aleksandar-apostolov merged 1 commit into
developfrom
fix/and-1516-ws-date-units
Oct 7, 2026
Merged

aleksandar-apostolov merged 1 commit into
developfrom
fix/and-1516-ws-date-units

Conversation

@gpunto

@gpunto gpunto commented Oct 2, 2026 •

Copy link
Copy Markdown
Collaborator

Goal

Closes AND-1516. The connected user returned by Feeds' connect() has nonsense dates:

StreamConnectedUser(createdAt=Mon Apr 08 08:40:59 GMT+01:00 55634307, ..., updatedAt=... 55957965, ..., lastActive=... 56754844, ...)

The v2 gateway (/api/v2/connect, used by Feeds) writes every time field as integer epoch nanoseconds ("created_at":1755586996702859000), and LenientDateAdapter read any number as epoch millis.

This is pre-existing, not a regression from the 4.x to 5.x move: the old DateMillisAdapter made the same assumption.

Why nanoseconds unconditionally

I considered checking the number's magnitude to tell the units apart, but there is nothing to tell apart. On the backend, the encoding depends only on the route's JSON profile, and no client option changes it:

Route Dates on the wire
/api/v2/connect (Feeds) integer epoch nanoseconds
/connect (Chat) RFC3339 strings
/video/connect (Video) RFC3339 strings

The encoder behind the v2 profile writes t.UnixNano(), and writes 0 for zero or pre-1970 times. No path writes millis or micros. The Feeds clients on other platforms already rely on this: feeds-js divides numbers by 1e6, and iOS core divides by 1e9. Neither checks the magnitude.

Implementation

  • LenientDateAdapter.fromJson reads NUMBER as epoch nanoseconds, truncated to millis for Date. The string branch is unchanged.
  • toJson still writes millis. The health check sends the connected event back to the server, so this keeps the outbound payload byte-for-byte what it is today. Reads and writes now use different units, so writing a date and reading it back no longer returns the same value; nothing in production does that. The KDoc explains the asymmetry.
  • Video is unaffected because it sends strings. Chat does not use core.

Full precision (Instant) was considered and left out: StreamConnectedUser exposes Date publicly, nothing needs sub-millisecond precision on these fields, and the Feeds models already truncate to millis.

Testing

  • ./gradlew :stream-android-core:testDebugUnitTest: 757 tests, 0 failures.
  • Changed the numeric read tests from millis to nanos, and removed the two write-then-read assertions because the units now differ on purpose.
  • New connection.ok test built from a real Feeds handshake payload, plus a test that 0 reads as the epoch.
  • On an emulator, I ran the Feeds sample against this branch from Maven Local. The connected user now shows createdAt=Aug 19 2025, updatedAt=Dec 15 2025 and lastActive set to the current time, and the health check exchange still works.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Numeric gateway dates are now interpreted as epoch nanoseconds and converted to milliseconds when read. Numeric zero correctly represents the Unix epoch.
    • Outbound dates continue to be serialized as epoch milliseconds.

The v2 gateway Feeds connects to (/api/v2/connect) encodes every time
field as integer epoch nanoseconds, but LenientDateAdapter read numbers
as epoch millis. StreamConnectedUser.createdAt, updatedAt and lastActive
came out tens of millions of years in the future.

No gateway sends numeric dates in any other unit (chat /connect and
video /video/connect send RFC3339 strings), so a number is now always
nanoseconds. Writes stay in millis so the health check payload sent back
to the server is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@gpunto gpunto added the pr:bug Bug fix label Oct 2, 2026
@github-actions

github-actions Bot commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

PR checklist ✅

All required conditions are satisfied:

  • Title length is OK (or ignored by label).
  • At least one pr: label exists.
  • Sections ### Goal, ### Implementation, and ### Testing are filled, or the PR is bot-authored.
  • An issue is linked (Linear ticket or GitHub issue), or the PR is bot-authored.

🎉 Great job! This PR is ready for review.

@sonarqubecloud

sonarqubecloud Bot commented Oct 2, 2026

Copy link
Copy Markdown

@gpunto
gpunto marked this pull request as ready for review October 2, 2026 14:14
@gpunto
gpunto enabled auto-merge (squash) October 2, 2026 14:14
@gpunto
gpunto disabled auto-merge October 2, 2026 14:14
@coderabbitai

coderabbitai Bot commented Oct 2, 2026

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

🧰 Additional context used
📚 Code guidelines (1)
AGENTS.md — auto-discovered

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 17b94a86-652d-4573-af1f-c2fd16e70004

📥 Commits

Reviewing files that changed from the base of the PR and between 4c29da9 and 0bf4610.

📒 Files selected for processing (3)
  • stream-android-core/src/main/java/io/getstream/android/core/internal/serialization/moshi/StreamCoreMoshiProvider.kt
  • stream-android-core/src/test/java/io/getstream/android/core/internal/serialization/moshi/MoshiProviderTest.kt
  • stream-android-core/src/test/java/io/getstream/android/core/internal/serialization/moshi/StreamCoreMoshiProviderDateParsingTest.kt

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.


Walkthrough

Numeric dates are now read as epoch nanoseconds and converted to milliseconds. RFC3339 string and null handling remain unchanged. Outbound dates remain epoch milliseconds.

Changes

Numeric date parsing

Layer / File(s) Summary
Date adapter behavior and validation
stream-android-core/src/main/java/io/getstream/android/core/internal/serialization/moshi/StreamCoreMoshiProvider.kt, stream-android-core/src/test/java/io/getstream/android/core/internal/serialization/moshi/MoshiProviderTest.kt, stream-android-core/src/test/java/io/getstream/android/core/internal/serialization/moshi/StreamCoreMoshiProviderDateParsingTest.kt
The adapter converts numeric epoch-nanosecond input to milliseconds. Tests cover parsed user dates, zero, null input, and outbound epoch-millisecond serialization.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix

Suggested reviewers: aleksandar-apostolov

Merge Risk: ⚪ Minimal · up to 0bf46

No actionable merge-blocking issue is established; the change is mergeable after normal checks.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 0bf46

The date correction does not show a new authorization or data-access path. However, timestamps echoed in connection heartbeats change, and server acceptance of those values is not independently confirmed.

Retained concerns

  • Low · architecture · inferred: The corrected numeric dates change values in the echoed heartbeat payload by a factor of 1,000,000. Server acceptance of millisecond values on this path is unverified; if the server requires nanosecond echoes, the change could cause disconnects or repeated recovery attempts for the affected socket session. No rejection or security-control bypass has been demonstrated.
Security review details

Security Blast Radius

  • inferred — The demonstrated exposure is gateway-supplied timestamps decoded within an Android socket session, exposed in its connected-user result, and echoed through that session. No independently attacker-controlled timestamp source or privileged date-dependent sink was established in the inspected path. Downstream consumers are not covered by this conclusion.

Trust Boundaries and Controls

  • observed — The adapter retains the same numeric, string, and null token branches. Numeric input still passes through nextLong, unsupported token types still raise JsonDataException, and event subtype registration remains unchanged. The change affects value semantics rather than adding a parser entrypoint.

Resilience and Maintainability Implications

  • observed — The inspected path converts dates once and reads them during heartbeat serialization without modifying them. Monitoring starts after the successful handshake transition; liveness timeout invokes disconnect. Cancellation invokes cleanup, whose atomic guard makes repeated cleanup idempotent, stops monitoring, and clears the stored connected event. These local controls do not establish how the server handles incompatible heartbeat values.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.69% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 13 functions across 3 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: numeric WebSocket dates are now read as epoch nanoseconds.
Description check ✅ Passed The description includes the goal, implementation details, testing results, issue reference, and deployment validation. It omits the required Checklist section and its explicit confirmations, but the …
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🛠️ Fix failing CI checks 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

A rabbit checks the dates at night,
Nanoseconds step to milliseconds right.
Zero rests at Unix’s start,
Outbound times keep their millisecond part.
The rabbit hops, then bounds from sight.

Comment @coderabbitai help to get the list of available commands.

@gpunto
gpunto requested a review from a team October 2, 2026 15:42
@aleksandar-apostolov
aleksandar-apostolov merged commit a540598 into develop Oct 7, 2026
14 of 16 checks passed
@aleksandar-apostolov
aleksandar-apostolov deleted the fix/and-1516-ws-date-units branch October 7, 2026 07:33
@stream-public-bot stream-public-bot added the released Included in a release label Oct 7, 2026
@stream-public-bot

Copy link
Copy Markdown
Collaborator

🚀 Available in v5.1.1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr:bug Bug fix released Included in a release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants