From 8878ffa39745a5a42719368bc7f66dc4f5f11ef0 Mon Sep 17 00:00:00 2001 From: Pigbibi <20649888+Pigbibi@users.noreply.github.com> Date: Thu, 16 Jul 2026 06:50:43 +0800 Subject: [PATCH 1/2] feat: add canonical gateway epoch values Co-Authored-By: Codex --- .github/workflows/ci.yml | 1 + scripts/gateway_epoch_value.py | 35 +++++++++++++++++++++++++++++++ tests/test_gateway_epoch_value.py | 18 ++++++++++++++++ 3 files changed, 54 insertions(+) create mode 100644 scripts/gateway_epoch_value.py create mode 100644 tests/test_gateway_epoch_value.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 79014cc..1ae578f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,5 +21,6 @@ jobs: set -euo pipefail bash tests/test_install_2fa_bot_watcher.sh bash tests/test_wait_for_ib_gateway_ready.sh + python3 tests/test_gateway_epoch_value.py bash tests/test_workflow_shared_config.sh bash tests/test_docker_compose_ports.sh diff --git a/scripts/gateway_epoch_value.py b/scripts/gateway_epoch_value.py new file mode 100644 index 0000000..a737fa0 --- /dev/null +++ b/scripts/gateway_epoch_value.py @@ -0,0 +1,35 @@ +"""Validated canonical epoch values; intentionally independent from recovery logic.""" +from __future__ import annotations +import calendar,re +from dataclasses import dataclass +from datetime import datetime +class EpochValueError(ValueError): + def __init__(self): super().__init__('invalid epoch value') +STAMP=re.compile(r'^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)(?:\.(\d{1,9}))?(Z|\+00:00)$') +def parse_epoch_ns(value: object)->int: + try: + if not isinstance(value,str): raise EpochValueError() + m=STAMP.fullmatch(value) + if not m: raise EpochValueError() + year,month,day,hour,minute,second=map(int,m.groups()[:6]) + if year<1970: raise EpochValueError() + datetime(year,month,day,hour,minute,second) + return calendar.timegm((year,month,day,hour,minute,second))*1_000_000_000+int(((m.group(7)or'')+'0'*9)[:9]) + except (ValueError,OverflowError,TypeError,EpochValueError): raise EpochValueError() from None +def _valid_ns(value: object)->int: + if type(value) is not int or value<0: raise EpochValueError() + return value +def _valid_id(value: object)->str: + if not isinstance(value,str) or not value or value.strip()!=value: raise EpochValueError() + return value +@dataclass(frozen=True,order=True) +class EpochIdentity: + container_id:str + started_at_epoch_ns:int + def __post_init__(self): object.__setattr__(self,'container_id',_valid_id(self.container_id)); object.__setattr__(self,'started_at_epoch_ns',_valid_ns(self.started_at_epoch_ns)) + def serialize(self)->tuple[str,int]: return (self.container_id,self.started_at_epoch_ns) +@dataclass(frozen=True,order=True) +class EpochCursor: + lower_bound_epoch_ns:int + def __post_init__(self): object.__setattr__(self,'lower_bound_epoch_ns',_valid_ns(self.lower_bound_epoch_ns)) + def serialize(self)->int:return self.lower_bound_epoch_ns diff --git a/tests/test_gateway_epoch_value.py b/tests/test_gateway_epoch_value.py new file mode 100644 index 0000000..85b87c6 --- /dev/null +++ b/tests/test_gateway_epoch_value.py @@ -0,0 +1,18 @@ +from __future__ import annotations +import unittest,sys +from dataclasses import replace +from pathlib import Path +sys.path.insert(0,str(Path(__file__).resolve().parents[1])) +from scripts.gateway_epoch_value import EpochCursor,EpochIdentity,EpochValueError,parse_epoch_ns +class T(unittest.TestCase): + def test_equivalence_order_and_separation(self): + a=parse_epoch_ns('2026-07-15T16:00:00.1Z'); b=parse_epoch_ns('2026-07-15T16:00:00.100000000+00:00'); self.assertEqual(a,b); self.assertLess(a,parse_epoch_ns('2026-07-15T16:00:00.100000001Z')) + identity=EpochIdentity('c',a); self.assertEqual(identity,replace(identity)); self.assertNotEqual(identity,EpochIdentity('c',a+1)); self.assertNotEqual(EpochCursor(a),EpochCursor(a+1)) + def test_invalid_is_sanitized(self): + for value in ('','2026-02-30T00:00:00Z','2026-01-01T00:00:00+01:00','2026-01-01T00:00:00.1234567890Z',None,1): + with self.subTest(value=value): + with self.assertRaisesRegex(EpochValueError,'invalid epoch value'): parse_epoch_ns(value) + def test_constructors_revalidate(self): + with self.assertRaises(EpochValueError): EpochIdentity('',0) + with self.assertRaises(EpochValueError): EpochCursor(-1) +if __name__=='__main__':unittest.main() From be1748e5aadb18985d341b124c61cbbb3a8078b9 Mon Sep 17 00:00:00 2001 From: Pigbibi <20649888+Pigbibi@users.noreply.github.com> Date: Thu, 16 Jul 2026 06:54:27 +0800 Subject: [PATCH 2/2] fix: bound canonical gateway epoch values Co-Authored-By: Codex --- scripts/gateway_epoch_value.py | 8 +++++--- tests/test_gateway_epoch_value.py | 4 ++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/scripts/gateway_epoch_value.py b/scripts/gateway_epoch_value.py index a737fa0..9733fc6 100644 --- a/scripts/gateway_epoch_value.py +++ b/scripts/gateway_epoch_value.py @@ -6,6 +6,8 @@ class EpochValueError(ValueError): def __init__(self): super().__init__('invalid epoch value') STAMP=re.compile(r'^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)(?:\.(\d{1,9}))?(Z|\+00:00)$') +MAX_EPOCH_NS=253402300799999999999 +CONTAINER_ID=re.compile(r'^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$') def parse_epoch_ns(value: object)->int: try: if not isinstance(value,str): raise EpochValueError() @@ -14,13 +16,13 @@ def parse_epoch_ns(value: object)->int: year,month,day,hour,minute,second=map(int,m.groups()[:6]) if year<1970: raise EpochValueError() datetime(year,month,day,hour,minute,second) - return calendar.timegm((year,month,day,hour,minute,second))*1_000_000_000+int(((m.group(7)or'')+'0'*9)[:9]) + return _valid_ns(calendar.timegm((year,month,day,hour,minute,second))*1_000_000_000+int(((m.group(7)or'')+'0'*9)[:9])) except (ValueError,OverflowError,TypeError,EpochValueError): raise EpochValueError() from None def _valid_ns(value: object)->int: - if type(value) is not int or value<0: raise EpochValueError() + if type(value) is not int or value<0 or value>MAX_EPOCH_NS: raise EpochValueError() return value def _valid_id(value: object)->str: - if not isinstance(value,str) or not value or value.strip()!=value: raise EpochValueError() + if not isinstance(value,str) or not CONTAINER_ID.fullmatch(value): raise EpochValueError() return value @dataclass(frozen=True,order=True) class EpochIdentity: diff --git a/tests/test_gateway_epoch_value.py b/tests/test_gateway_epoch_value.py index 85b87c6..193730d 100644 --- a/tests/test_gateway_epoch_value.py +++ b/tests/test_gateway_epoch_value.py @@ -15,4 +15,8 @@ def test_invalid_is_sanitized(self): def test_constructors_revalidate(self): with self.assertRaises(EpochValueError): EpochIdentity('',0) with self.assertRaises(EpochValueError): EpochCursor(-1) + for bad in ('a/b','a\\b','a\n', ' ' * 129): + with self.subTest(bad=bad): + with self.assertRaises(EpochValueError): EpochIdentity(bad,0) + with self.assertRaises(EpochValueError): EpochCursor(253402300800000000000) if __name__=='__main__':unittest.main()