Skip to content

Fix PubSub DataSetWriter sequence number uint16 overflow#1955

Merged
oroulet merged 1 commit into
FreeOpcUa:masterfrom
Lidang-Jiang:fix/pubsub-seq-overflow
Apr 5, 2026
Merged

Fix PubSub DataSetWriter sequence number uint16 overflow#1955
oroulet merged 1 commit into
FreeOpcUa:masterfrom
Lidang-Jiang:fix/pubsub-seq-overflow

Conversation

@Lidang-Jiang

Copy link
Copy Markdown
Contributor

Summary

Changes

  • asyncua/pubsub/writer.py: Wrap sequence number with modulo arithmetic: self._seq_no = (self._seq_no % 65535) + 1
  • tests/test_pubsub.py: Add regression test verifying sequence number wraps from 65535 back to 1

Root Cause

self._seq_no += 1 increments without bound. When UInt16(self._seq_no) is serialized via struct.pack('<H', value) with a value > 65535, it raises struct.error. At 30Hz publishing frequency, this crashes the writer after ~36 minutes.

Fix

Use (self._seq_no % 65535) + 1 so the sequence number cycles within [1, 65535], matching the valid UInt16 range.

Before (overflow demonstration)
=== Demonstrating the overflow bug (before fix) ===

UInt16 sequence number increments without bound:
  seq_no = 65535 -> struct.pack OK
  seq_no = 65536 -> struct.pack FAIL: 'H' format requires 0 <= number <= 65535
  seq_no = 65537 -> struct.pack FAIL: 'H' format requires 0 <= number <= 65535
  seq_no = 65538 -> struct.pack FAIL: 'H' format requires 0 <= number <= 65535

At 30Hz publishing frequency, this overflow occurs after:
  65535 / 30 = 2184 seconds = ~36.4 minutes
After (fix verified)
=== After fix: sequence number wraps correctly ===

Sequence number behavior around boundary:
  seq_no = 65534 -> struct.pack OK (bytes: feff)
  seq_no = 65535 -> struct.pack OK (bytes: ffff)
  seq_no = 1 -> struct.pack OK (bytes: 0100)
  seq_no = 2 -> struct.pack OK (bytes: 0200)
  seq_no = 3 -> struct.pack OK (bytes: 0300)

Simulating 200,000 messages (no overflow):
  All 200,000 iterations completed without error. Final seq_no=3395

At 30Hz, 200,000 messages = 111 minutes of continuous operation
Test output
$ pytest tests/test_pubsub.py -v

tests/test_pubsub.py::test_uadp_basic PASSED                             [ 16%]
tests/test_pubsub.py::test_connection PASSED                             [ 33%]
tests/test_pubsub.py::test_full_simple PASSED                            [ 50%]
tests/test_pubsub.py::test_datasource_and_subscribed_dataset PASSED      [ 66%]
tests/test_pubsub.py::test_dataset_writer_seq_no_wraps_at_uint16_max PASSED [ 83%]
tests/test_pubsub.py::test_load_save_ua_binary_publisher PASSED          [100%]

============================== 6 passed in 8.41s ===============================

Test plan

  • Sequence number wraps from 65535 to 1 (not 0)
  • Normal incrementing behavior preserved for values < 65535
  • Existing PubSub tests pass (no regression)

The sequence number in DataSetWriter.generate_uadp_dataset() incremented
without bound, causing a struct.pack overflow when the value exceeded
65535 (UInt16 max). At 30Hz publishing frequency, this crashes the
writer after ~36 minutes.

Wrap the sequence number using modulo arithmetic so it cycles from
65535 back to 1, matching the valid UInt16 range.

Closes FreeOpcUa#1953

Signed-off-by: Lidang-Jiang <lidangjiang@gmail.com>
@oroulet oroulet merged commit baed488 into FreeOpcUa:master Apr 5, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

OPC UA PubSub dataset header sequence number uint16 overflow

2 participants