diff --git a/.ai b/.ai index a8539561e..7219c81b8 160000 --- a/.ai +++ b/.ai @@ -1 +1 @@ -Subproject commit a8539561e1a691c01518410b5259fdf7056c136f +Subproject commit 7219c81b83dd9f9f45f87763edebe6eda7bce18a diff --git a/.audit/oberstet_fix_1771.md b/.audit/oberstet_fix_1771.md new file mode 100644 index 000000000..4ac2b0efc --- /dev/null +++ b/.audit/oberstet_fix_1771.md @@ -0,0 +1,8 @@ +- [ ] I did **not** use any AI-assistance tools to help create this pull request. +- [x] I **did** use AI-assistance tools to *help* create this pull request. +- [x] I have read, understood and followed the projects' [AI Policy](https://github.com/crossbario/autobahn-python/blob/main/AI_POLICY.md) when creating code, documentation etc. for this pull request. + +Submitted by: @oberstet +Date: 2025-11-21 +Related issue(s): #1771 +Branch: oberstet:fix_1771 diff --git a/.github/workflows/generate_summary.py b/.github/workflows/generate_summary.py index 0908f32af..4bb92ee31 100644 --- a/.github/workflows/generate_summary.py +++ b/.github/workflows/generate_summary.py @@ -15,62 +15,65 @@ def generate_summary(json_file: Path, title: str) -> str: """ Generate a markdown summary table from an index.json file. - + Args: json_file: Path to the index.json file title: Title for the summary section - + Returns: Markdown formatted summary table as a string """ if not json_file.exists(): return f"⚠️ {json_file} not found\n" - + try: - with open(json_file, 'r') as f: + with open(json_file, "r") as f: data = json.load(f) except (json.JSONDecodeError, IOError) as e: return f"❌ Error reading {json_file}: {e}\n" - + # Build markdown table lines = [ "", f"## {title}", "", "| Testee | Cases OK / Total | Status |", - "|--------|------------------|---------|" + "|--------|------------------|---------|", ] - + for testee, cases in data.items(): total_cases = len(cases) ok_cases = 0 - + for case_id, case_data in cases.items(): behavior = case_data.get("behavior") behavior_close = case_data.get("behaviorClose") - + # Test passes if both behaviors are OK, or both are INFORMATIONAL - if (behavior == "OK" and behavior_close == "OK") or \ - (behavior == "INFORMATIONAL" and behavior_close == "INFORMATIONAL"): + if (behavior == "OK" and behavior_close == "OK") or ( + behavior == "INFORMATIONAL" and behavior_close == "INFORMATIONAL" + ): ok_cases += 1 - - status = '✅' if ok_cases == total_cases else '❌' - lines.append(f'| {testee} | {ok_cases} / {total_cases} | {status} |') - - return '\n'.join(lines) + + status = "✅" if ok_cases == total_cases else "❌" + lines.append(f"| {testee} | {ok_cases} / {total_cases} | {status} |") + + return "\n".join(lines) def main(): """Main entry point.""" - parser = argparse.ArgumentParser(description='Generate WebSocket conformance summary table') - parser.add_argument('json_file', type=Path, help='Path to the index.json file') - parser.add_argument('title', help='Title for the summary section') - + parser = argparse.ArgumentParser( + description="Generate WebSocket conformance summary table" + ) + parser.add_argument("json_file", type=Path, help="Path to the index.json file") + parser.add_argument("title", help="Title for the summary section") + args = parser.parse_args() - + summary = generate_summary(args.json_file, args.title) print(summary) if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/.github/workflows/verify_conformance.py b/.github/workflows/verify_conformance.py index 77ea6195b..50fa15715 100644 --- a/.github/workflows/verify_conformance.py +++ b/.github/workflows/verify_conformance.py @@ -15,103 +15,120 @@ def verify_conformance(json_file: Path, test_type: str) -> bool: """ Verify 100% conformance for a given index.json file. - + Args: json_file: Path to the index.json file test_type: Type of test (e.g., "Client" or "Server") - + Returns: True if all tests passed, False otherwise """ if not json_file.exists(): print(f"❌ {json_file} not found") return False - + print(f"==> Checking {test_type} conformance...") - + try: - with open(json_file, 'r') as f: + with open(json_file, "r") as f: data = json.load(f) except (json.JSONDecodeError, IOError) as e: print(f"❌ Error reading {json_file}: {e}") return False - + all_passed = True total_testees = len(data) passed_testees = 0 - + for testee, cases in data.items(): total_cases = len(cases) ok_cases = 0 failed_cases = [] - + for case_id, case_data in cases.items(): behavior = case_data.get("behavior") behavior_close = case_data.get("behaviorClose") - + # Test passes if both behaviors are OK, or both are INFORMATIONAL - if (behavior == "OK" and behavior_close == "OK") or \ - (behavior == "INFORMATIONAL" and behavior_close == "INFORMATIONAL"): + if (behavior == "OK" and behavior_close == "OK") or ( + behavior == "INFORMATIONAL" and behavior_close == "INFORMATIONAL" + ): ok_cases += 1 else: - failed_cases.append({ - "case_id": case_id, - "behavior": behavior, - "behaviorClose": behavior_close - }) - + failed_cases.append( + { + "case_id": case_id, + "behavior": behavior, + "behaviorClose": behavior_close, + } + ) + if ok_cases == total_cases: - print(f'✅ {testee}: {ok_cases}/{total_cases} tests passed') + print(f"✅ {testee}: {ok_cases}/{total_cases} tests passed") passed_testees += 1 else: - print(f'❌ {testee}: {ok_cases}/{total_cases} tests passed') + print(f"❌ {testee}: {ok_cases}/{total_cases} tests passed") # Show details of first few failed cases for debugging for i, failed_case in enumerate(failed_cases[:3]): - print(f' Failed case {failed_case["case_id"]}: ' - f'behavior={failed_case["behavior"]}, ' - f'behaviorClose={failed_case["behaviorClose"]}') + print( + f" Failed case {failed_case['case_id']}: " + f"behavior={failed_case['behavior']}, " + f"behaviorClose={failed_case['behaviorClose']}" + ) if len(failed_cases) > 3: - print(f' ... and {len(failed_cases) - 3} more failed cases') + print(f" ... and {len(failed_cases) - 3} more failed cases") all_passed = False - - print('') - print(f'{test_type} Summary: {passed_testees}/{total_testees} testees passed all tests') - + + print("") + print( + f"{test_type} Summary: {passed_testees}/{total_testees} testees passed all tests" + ) + if not all_passed: - print(f'❌ {test_type} conformance: FAILED - Not all tests passed') + print(f"❌ {test_type} conformance: FAILED - Not all tests passed") return False else: - print(f'✅ {test_type} conformance: PASSED - All tests passed') + print(f"✅ {test_type} conformance: PASSED - All tests passed") return True def main(): """Main entry point.""" - parser = argparse.ArgumentParser(description='Verify WebSocket conformance test results') - parser.add_argument('client_json', type=Path, help='Path to client index.json file') - parser.add_argument('server_json', type=Path, help='Path to server index.json file') - + parser = argparse.ArgumentParser( + description="Verify WebSocket conformance test results" + ) + parser.add_argument("client_json", type=Path, help="Path to client index.json file") + parser.add_argument("server_json", type=Path, help="Path to server index.json file") + args = parser.parse_args() - + print("==> Verifying 100% WebSocket conformance...") - + client_passed = verify_conformance(args.client_json, "Client") print("") server_passed = verify_conformance(args.server_json, "Server") - + print("") print("==> Overall WebSocket Conformance Verification:") - + if client_passed and server_passed: - print("✅ PASSED - Both client and server conformance tests achieved 100% pass rate") + print( + "✅ PASSED - Both client and server conformance tests achieved 100% pass rate" + ) sys.exit(0) else: - print("❌ FAILED - One or more conformance tests did not achieve 100% pass rate") + print( + "❌ FAILED - One or more conformance tests did not achieve 100% pass rate" + ) print("") - print("This means the WebSocket implementation has conformance issues that need to be addressed.") - print("Download the detailed reports from the workflow artifacts to investigate specific failures.") + print( + "This means the WebSocket implementation has conformance issues that need to be addressed." + ) + print( + "Download the detailed reports from the workflow artifacts to investigate specific failures." + ) sys.exit(1) if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 1cf4d61e2..1c1418ad8 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -179,8 +179,7 @@ jobs: with: path: ${{ env.UV_CACHE_DIR }} key: - uv-cache-${{ matrix.platform }}-${{ matrix.arch - }}-${{ hashFiles('pyproject.toml') }} + uv-cache-${{ matrix.platform }}-${{ matrix.arch }}-${{ hashFiles('pyproject.toml') }} restore-keys: | uv-cache-${{ matrix.platform }}-${{ matrix.arch }}- uv-cache-${{ matrix.platform }}- diff --git a/.proto b/.proto index c849243f5..713b51b40 160000 --- a/.proto +++ b/.proto @@ -1 +1 @@ -Subproject commit c849243f55f52d0be40dc5f9f0fc9a9556664b19 +Subproject commit 713b51b40fd0a8bf4120147a13ff7b78b50c53ff diff --git a/ENUMERATION_AUDIT.md b/ENUMERATION_AUDIT.md new file mode 100644 index 000000000..6641e79b0 --- /dev/null +++ b/ENUMERATION_AUDIT.md @@ -0,0 +1,364 @@ +# WAMP Enumeration Audit - Findings Report + +**Date:** 2025-11-23 +**Scope:** FlatBuffers schema vs Python implementation vs WAMP specification +**Purpose:** Ensure all enumeration-like WAMP Options & Details are consistently defined across all three sources + +--- + +## Executive Summary + +Conducted systematic audit of all enumeration-like values used in WAMP messages across: +1. **FlatBuffers schema** (`autobahn/wamp/flatbuffers/types.fbs`) +2. **Python implementation** (`autobahn/wamp/message.py`, `autobahn/wamp/types.py`) +3. **WAMP protocol specification** (`wamp-proto/rfc/`) + +**Key Findings:** +- ✅ **1 fully aligned** enumeration type (Match) +- 🔴 **2 critical inconsistencies** requiring immediate fixes +- 🟡 **4 missing enumerations** worth adding +- 🟢 **Several nice-to-have enhancements** + +--- + +## Detailed Findings + +### 1. ✅ Topic/Procedure Matching Methods - ALIGNED + +**Field:** `Subscribe.Options.match`, `Register.Options.match` + +| Value | WAMP Spec | FlatBuffers (Match enum) | Python | +|-------|-----------|-------------------------|--------| +| `"exact"` | ✅ | ✅ EXACT = 0 | ✅ MATCH_EXACT | +| `"prefix"` | ✅ | ✅ PREFIX = 1 | ✅ MATCH_PREFIX | +| `"wildcard"` | ✅ | ✅ WILDCARD = 2 | ✅ MATCH_WILDCARD | + +**Status:** ✅ Complete - All three sources perfectly aligned + +--- + +### 2. 🔴 CRITICAL: Call Cancellation Modes - MISALIGNED + +**Field:** `Cancel.Options.mode` + +| Value | WAMP Spec | FlatBuffers (CancelMode enum) | Python (Cancel.*) | +|-------|-----------|------------------------------|-------------------| +| `"skip"` | ✅ rfc_call_canceling.md | ✅ SKIP = 0 | ✅ SKIP = "skip" | +| `"kill"` | ✅ rfc_call_canceling.md | ✅ KILL = 2 ❌ **Wrong value** | ✅ KILL = "kill" | +| `"killnowait"` | ✅ rfc_call_canceling.md | ❌ **MISSING** | ✅ KILLNOWAIT = "killnowait" | +| `"abort"` | ❌ **NOT in spec** | ✅ ABORT = 1 ❌ **Extra value** | ❌ NOT in Python | + +**Issues:** +1. **FlatBuffers has wrong enum values:** + - Should be: `SKIP=0, KILL=1, KILLNOWAIT=2` + - Currently: `SKIP=0, ABORT=1, KILL=2` +2. **ABORT doesn't exist in spec or Python** - appears to be a mistake +3. **KILLNOWAIT missing from FlatBuffers** - needs to be added + +**WAMP Spec Reference:** `/home/oberstet/work/wamp/wamp-proto/rfc/text/advanced/rpc_call_canceling.md:122` + +**Action Required:** +- **Fix `types.fbs` CancelMode enum to:** + ```fbs + enum CancelMode: uint8 { + SKIP = 0, + KILL = 1, + KILLNOWAIT = 2 + } + ``` +- **Regenerate FlatBuffers wrappers** +- **Update any code using ABORT or wrong enum values** + +**GitHub Issue:** To be filed in `autobahn-python` + +--- + +### 3. 🔴 CRITICAL: RPC Invocation Policies - INCONSISTENT + +**Field:** `Register.Options.invoke` + +| Value | WAMP Spec | FlatBuffers (InvocationPolicy) | Python (Register.INVOKE_*) | +|-------|-----------|--------------------------------|----------------------------| +| `"single"` | ✅ rfc_shared_registration.md | ✅ SINGLE = 0 | ✅ INVOKE_SINGLE | +| `"first"` | ✅ rfc_shared_registration.md | ✅ FIRST = 1 | ✅ INVOKE_FIRST | +| `"last"` | ✅ rfc_shared_registration.md | ✅ LAST = 2 | ✅ INVOKE_LAST | +| `"roundrobin"` | ✅ rfc_shared_registration.md | ✅ ROUNDROBIN = 3 | ✅ INVOKE_ROUNDROBIN | +| `"random"` | ✅ rfc_shared_registration.md | ✅ RANDOM = 4 | ✅ INVOKE_RANDOM | +| `"all"` | ❌ **NOT in spec** | ❌ MISSING | ✅ INVOKE_ALL = "all" | + +**Issues:** +1. **Python defines `Register.INVOKE_ALL = "all"`** but it's NOT in WAMP spec +2. **Internal Python inconsistency:** `RegisterOptions` in `types.py:1070` validates against `["single", "first", "last", "roundrobin", "random"]` (doesn't include `"all"`) + +**WAMP Spec Reference:** `/home/oberstet/work/wamp/wamp-proto/rfc/text/advanced/rpc_shared_registration.md:14-23` + +**Action Required - Choose one:** + +**Option A: Add to spec (if functionally needed)** +- Propose adding `invoke="all"` to WAMP spec +- Add to FlatBuffers after spec acceptance +- Fix Python validation in types.py + +**Option B: Remove from Python (if not used)** +- Remove `Register.INVOKE_ALL` constant +- Verify no code uses this value +- Document as unsupported + +**GitHub Issues:** +- `autobahn-python`: Document the inconsistency +- `wamp-proto`: Clarify if `"all"` should be in spec (if choosing Option A) + +--- + +### 4. 🟡 MEDIUM: Payload Passthru Mode - INCOMPLETE + +**Spec defines three related enumerations for end-to-end encryption:** + +#### 4a. Encryption Schemes (`ppt_scheme`) + +**WAMP Spec:** +- `"wamp"` (E2E encryption) +- `"mqtt"` (MQTT payload gateway) +- Custom schemes with `"x_"` prefix + +**Python (`message.py`):** +- `ENC_ALGO_CRYPTOBOX = 1` → `"cryptobox"` +- `ENC_ALGO_MQTT = 2` → `"mqtt"` +- `ENC_ALGO_XBR = 3` → `"xbr"` + +**FlatBuffers (`types.fbs`):** +- `Payload` enum: `PLAIN, CRYPTOBOX, OPAQUE` + +**Issues:** +- Terminology mismatch: spec says "scheme", Python says "algo", FlatBuffers says "Payload" +- XBR exists in Python but not explicitly in spec - clarify if spec should include it +- MQTT: mapping unclear between spec and Python + +#### 4b. Payload Serializers (`ppt_serializer`) + +**WAMP Spec:** +- `"native"`, `"json"`, `"msgpack"`, `"cbor"`, `"ubjson"`, `"flatbuffers"` + +**Python (`message.py`):** +- `ENC_SER_JSON = 1`, `ENC_SER_MSGPACK = 2`, `ENC_SER_CBOR = 3`, `ENC_SER_UBJSON = 4`, `ENC_SER_FLATBUFFERS = 6`, `ENC_SER_OPAQUE = 5` + +**FlatBuffers (`types.fbs`):** +- `Serializer` enum: `TRANSPORT, JSON, MSGPACK, CBOR, UBJSON, OPAQUE, FLATBUFFERS, FLEXBUFFERS` + +**Issues:** +- Spec uses `"native"`, Python/FlatBuffers use `"TRANSPORT"` - clarify equivalence +- FlatBuffers has `FLEXBUFFERS` not in spec or Python + +#### 4c. Encryption Ciphers (`ppt_cipher`) + +**WAMP Spec:** +- `"xsalsa20poly1305"` +- `"aes256gcm"` + +**Python:** ❌ No enumeration (string values only) +**FlatBuffers:** ❌ **MISSING entirely** + +**WAMP Spec Reference:** `/home/oberstet/work/wamp/wamp-proto/rfc/text/advanced/payload_passthru_mode.md` + +**Action Required:** +1. **Add `Cipher` enum to `types.fbs`:** + ```fbs + enum Cipher: uint8 { + XSALSA20POLY1305 = 0, + AES256GCM = 1 + } + ``` +2. **Harmonize terminology** across spec/Python/FlatBuffers +3. **Clarify XBR status** - should it be in spec? + +**GitHub Issues:** +- `autobahn-python`: Add Cipher enum to FlatBuffers, harmonize naming +- `wamp-proto`: Clarify XBR encryption scheme status + +--- + +### 5. 🟡 MEDIUM: Authentication Methods - NOT ENUMERATED + +**WAMP Spec defines:** +- `"anonymous"` +- `"ticket"` +- `"wampcra"` +- `"cryptosign"` +- `"wamp-scram"` + +**Python/FlatBuffers:** ❌ These are string values in `HELLO.Details.authmethods`, no enum defined + +**WAMP Spec References:** +- `/home/oberstet/work/wamp/wamp-proto/rfc/text/advanced/authentication_*.md` + +**Action Required (Optional):** +- Consider adding `AuthMethod` enum to FlatBuffers for type safety +- Would enable compile-time checking of auth methods + +**GitHub Issue:** `autobahn-python` - Enhancement: Add AuthMethod enum + +--- + +### 6. 🟢 LOW: Channel Binding Types - NOT ENUMERATED + +**WAMP Spec (for Cryptosign/SCRAM):** +- `"tls-unique"` +- `"tls-exporter"` +- `"tls-server-end-point"` (SCRAM only) + +**Python/FlatBuffers:** ❌ String values only, no enum + +**Action:** Low priority - could add for completeness + +--- + +### 7. 🟢 LOW: Key Derivation Functions (WAMP-SCRAM) - NOT ENUMERATED + +**WAMP Spec:** +- `"argon2id13"` +- `"pbkdf2"` + +**Python/FlatBuffers:** ❌ String values only, no enum + +**Action:** Low priority - could add for completeness + +--- + +### 8. 🟢 LOW: Transport Details - NOT IN FLATBUFFERS + +**Python (`types.py` - TransportDetails class):** +- Channel types: `FUNCTION, MEMORY, SERIAL, TCP, TLS, UDP, DTLS` +- Channel framing: `NATIVE, WEBSOCKET, RAWSOCKET` +- Channel serializers: `JSON, MSGPACK, CBOR, UBJSON, FLATBUFFERS` + +**FlatBuffers:** ❌ No enums for transport details + +**Action:** Evaluate if transport metadata should be in FlatBuffers schema + +--- + +## Summary by Priority + +### 🔴 CRITICAL - Must Fix + +| Issue | Component | Action | GitHub Repo | +|-------|-----------|--------|-------------| +| CancelMode enum wrong | FlatBuffers schema | Fix enum values, add KILLNOWAIT | autobahn-python | +| InvocationPolicy "all" | Python + maybe Spec | Decide: add to spec OR remove from Python | Both repos | +| RegisterOptions validation | Python types.py | Make consistent with message.py | autobahn-python | + +### 🟡 MEDIUM - Should Add + +| Issue | Component | Action | GitHub Repo | +|-------|-----------|--------|-------------| +| Cipher enum missing | FlatBuffers schema | Add XSALSA20POLY1305, AES256GCM | autobahn-python | +| Payload terminology | All | Harmonize naming (scheme/algo/payload) | Both repos | +| XBR encryption scheme | Spec | Clarify if should be standardized | wamp-proto | +| AuthMethod enum | FlatBuffers schema | Add for type safety | autobahn-python | + +### 🟢 LOW - Nice to Have + +| Issue | Component | Action | GitHub Repo | +|-------|-----------|--------|-------------| +| ChannelBinding enum | FlatBuffers | Add for completeness | autobahn-python | +| KDF enum | FlatBuffers | Add for completeness | autobahn-python | +| Transport enums | FlatBuffers | Evaluate usefulness | autobahn-python | + +--- + +## Recommended Next Steps + +1. **Review this audit with project maintainers** +2. **File GitHub issues** for critical items (template below) +3. **Fix CancelMode enum** in types.fbs (breaking change - needs version bump) +4. **Decide on InvocationPolicy "all"** value +5. **Add missing Cipher enum** +6. **Consider harmonizing terminology** across spec/implementations + +--- + +## GitHub Issue Templates + +### Template: CancelMode Enum Fix (autobahn-python) + +```markdown +## Issue: CancelMode enum in FlatBuffers schema is incorrect + +**Priority:** Critical (breaking change required) + +**Problem:** +The `CancelMode` enum in `autobahn/wamp/flatbuffers/types.fbs` does not match the WAMP specification or Python implementation: + +- FlatBuffers has: `SKIP=0, ABORT=1, KILL=2` +- Should be: `SKIP=0, KILL=1, KILLNOWAIT=2` + +**WAMP Spec:** https://wamp-proto.org/wamp_latest.html#rpc-call-canceling +(Also: `wamp-proto/rfc/text/advanced/rpc_call_canceling.md:122`) + +**Impact:** +- FlatBuffers messages using CancelMode cannot represent "killnowait" +- "ABORT" value doesn't exist in spec and is not used in Python + +**Proposed Fix:** +```fbs +enum CancelMode: uint8 { + SKIP = 0, + KILL = 1, + KILLNOWAIT = 2 +} +``` + +**Breaking Change:** Yes - existing FlatBuffers binary messages using ABORT or KILL will be incompatible + +**Related:** Full audit in `ENUMERATION_AUDIT.md` +``` + +--- + +### Template: InvocationPolicy "all" Clarification (wamp-proto) + +```markdown +## Question: Should `invoke="all"` be in WAMP spec for shared registrations? + +**Context:** +Autobahn|Python defines `Register.INVOKE_ALL = "all"` constant, but this value is NOT in the WAMP specification for shared registrations. + +**WAMP Spec Currently Defines:** +- `single`, `first`, `last`, `roundrobin`, `random` + +**Reference:** https://wamp-proto.org/wamp_latest.html#rpc-shared-registrations +(Also: `wamp-proto/rfc/text/advanced/rpc_shared_registration.md:14-23`) + +**Questions:** +1. Should `invoke="all"` be added to the spec? +2. What would the semantics be? (invoke ALL registered callees, collect all results?) +3. Or should this be removed from Autobahn|Python as non-standard? + +**Note:** Autobahn|Python's `RegisterOptions` validation in `types.py:1070` does NOT include `"all"`, creating an internal inconsistency. + +**Related:** Full audit in `autobahn-python/ENUMERATION_AUDIT.md` +``` + +--- + +## Files Examined + +**FlatBuffers:** +- `autobahn/wamp/flatbuffers/types.fbs` + +**Python:** +- `autobahn/wamp/message.py` +- `autobahn/wamp/types.py` + +**WAMP Spec:** +- `wamp-proto/rfc/text/advanced/rpc_call_canceling.md` +- `wamp-proto/rfc/text/advanced/rpc_shared_registration.md` +- `wamp-proto/rfc/text/advanced/pubsub_pattern_based_subscription.md` +- `wamp-proto/rfc/text/advanced/rpc_pattern_based_registration.md` +- `wamp-proto/rfc/text/advanced/payload_passthru_mode.md` +- `wamp-proto/rfc/text/advanced/authentication_*.md` +- `wamp-proto/rfc/text/advanced/transport_rawsocket.md` + +--- + +**End of Report** diff --git a/ENUMERATION_AUDIT_ANSWERS.md b/ENUMERATION_AUDIT_ANSWERS.md new file mode 100644 index 000000000..d08f68091 --- /dev/null +++ b/ENUMERATION_AUDIT_ANSWERS.md @@ -0,0 +1,436 @@ +# ANSWERS & DESIGN DECISIONS to/wrt: WAMP Enumeration Audit - Findings Report + +**Author:** Tobias Oberstein +**Date:** 2025-11-23 +**Report:** `ENUMERATION_AUDIT.md` + +--- + +### 1. ✅ Topic/Procedure Matching Methods - ALIGNED + +good! + +### 2. 🔴 CRITICAL: Call Cancellation Modes - MISALIGNED + +1. yes, agreed! FlatBuffers has wrong enum values: should be:`SKIP=0, KILL=1, KILLNOWAIT=2` + +2. yes, ABORT doesn't exist in spec or Python: should be removed from Flatbuffers schema + +3. yes, KILLNOWAIT missing from FlatBuffers: should be added to Flatbuffers schema (`KILLNOWAIT=2`) + +so yes, I agree with Action Required! + +### 3. 🔴 CRITICAL: RPC Invocation Policies - INCONSISTENT + +`INVOKE_ALL = "all"` should be removed from Flatbuffers and Python in autobahn-python! + +crucially, it is _not_ used in crossbar.io, here is a simple/quick check: + +``` +oberstet@asgard1:~/work/wamp/crossbar$ find crossbar/router/dealer.py -name "*.py" -exec grep -Hi "INVOKE_" {} \; +crossbar/router/dealer.py: def __init__(self, invoke=message.Register.INVOKE_SINGLE): +crossbar/router/dealer.py: # _add_invoke_request and _remove_invoke_request +crossbar/router/dealer.py: # this needs to update all four places where we track invocations similar to _remove_invoke_request +crossbar/router/dealer.py: if registration.extra.invoke == message.Register.INVOKE_SINGLE: +crossbar/router/dealer.py: message.Register.INVOKE_SINGLE, +crossbar/router/dealer.py: message.Register.INVOKE_FIRST, +crossbar/router/dealer.py: message.Register.INVOKE_LAST, +crossbar/router/dealer.py: if registration.extra.invoke == message.Register.INVOKE_SINGLE: +crossbar/router/dealer.py: elif registration.extra.invoke == message.Register.INVOKE_FIRST: +crossbar/router/dealer.py: elif registration.extra.invoke == message.Register.INVOKE_LAST: +crossbar/router/dealer.py: elif registration.extra.invoke == message.Register.INVOKE_ROUNDROBIN: +crossbar/router/dealer.py: elif registration.extra.invoke == message.Register.INVOKE_RANDOM: +crossbar/router/dealer.py: self._add_invoke_request( +crossbar/router/dealer.py: def _add_invoke_request( +crossbar/router/dealer.py: invoke_request = InvocationRequest( +crossbar/router/dealer.py: self._invocations[invocation_request_id] = invoke_request +crossbar/router/dealer.py: self._invocations_by_call[session._session_id, call.request] = invoke_request +crossbar/router/dealer.py: invokes.append(invoke_request) +crossbar/router/dealer.py: invokes.append(invoke_request) +crossbar/router/dealer.py: if _can_cancel(invoke_request.caller, "caller"): +crossbar/router/dealer.py: invoke_request.caller, +crossbar/router/dealer.py: if _can_cancel(invoke_request.callee, "callee"): +crossbar/router/dealer.py: invoke_request.callee, +crossbar/router/dealer.py: invoke_request.id, +crossbar/router/dealer.py: self._remove_invoke_request(invoke_request) +crossbar/router/dealer.py: invoke_request.timeout_call = self._cancel_timers.call_later(timeout, _cancel_both_sides) +crossbar/router/dealer.py: return invoke_request +crossbar/router/dealer.py: def _remove_invoke_request(self, invocation_request): +crossbar/router/dealer.py: self._remove_invoke_request(invocation_request) +crossbar/router/dealer.py: self._remove_invoke_request(invoke) +oberstet@asgard1:~/work/wamp/crossbar$ +``` + +so: **Option B: Remove from Python (if not used)** + +### 4. 🟡 MEDIUM: Payload Passthru Mode - INCOMPLETE + +first, yes, this is a slight mess (historical reasons), and it is not fully completed in the spec, but it _is_ used in AutobahnPython, AutobahnJS and most importantly in Crossbar.io: the End-to-end Encryption, the MQTT and the XBR (Ethereum blockchain, WAMP in data markets) stuff! + +we need to untangle this "without breaking things". "not breaking" things means we need to be able to "test it", and we will only come to that once we have modernized the crossbar repo, and respective examples! + +on the other hand, I _do_ want to rectify autobahn-python including flatbuffers scheme and python code _now_ to what _is_ already described & defined in the WAMP spec in "14.1. Payload Passthru Mode"! + +so "some" fallout is unavoidable in the short term. + +to start with, this means the attributes related to "Payload Passthru Mode" should be renamed in the Flatbuffers schema to bring it in line with the spec: + +- `enc_algo` => `ppt_scheme` +- `enc_serializer` => `ppt_serializer` +- NEW! => `ppt_cipher` +- `enc_key` => `ppt_keyid` + +in this attribute order, and with docstrings/comments from the WAMP protocol spec, and most importantly the types from the spec (which are string for all 4 attributes)! + +**sidenote: we do want the descriptions in Flatbuffers schemata from all types/pieces to show up in generated API docs (Doxygen-style documentation comments), and hence we want to use `///` _everywhere_!** + +this means changes to: + +- `autobahn/wamp/flatbuffers/pubsub.fbs` +- `autobahn/wamp/flatbuffers/rpc.fbs` + +we should also clean up the enum types (names) accordingly, here is the complete desired target: + +``` +/// The specific scheme in use with Payload Passthru (PPT) mode for the application payload. +ppt_scheme: PPTScheme; + +/// The specific serializer encoding the application payload with the Payload Passthru (PPT) scheme in use. +ppt_serializer: PPTSerializer; + +/// The cryptographic algorithm ("cipher") encrypting the application payload with the Payload Passthru (PPT) scheme in use. +ppt_cipher: PPTCipher; + +/// The identifier or reference to the encryption key that was used to encrypt the payload with the Payload Passthru (PPT) scheme and cipher in use. +ppt_keyid: string (ppt_keyid); +``` + +with (in `autobahn/wamp/flatbuffers/types.fbs`): + +``` +///WAMP Payload Passthru (PPT) encryption key id. Define custom attribute to hint a string subtype: The value can be a hex-encoded string, URI, DNS name, Ethereum address, UUID identifier - any meaningful value which allows the target peer to choose a private key without guessing. The format of the value may depend on the ppt_scheme attribute. +attribute "ppt_keyid"; + +/// WAMP Payload Passthru (PPT) scheme (renamed from `Payload`). +enum PPTScheme: uint8 +{ + /// Unset (plain WAMP application payload) + NONE = 0, + + /// WAMP-cryptobox end-to-end encrypted application payload + CRYPTOBOX = 1, + + /// MQTT passthrough-mode application payload + MQTT = 2, + + /// XBR end-to-end encrypted end and Ethereum anchored application payload + XBR = 3, + + /// Raw pass-through of app payload, uninterpreted in any way. + OPAQUE = 4 +} + +/// WAMP Payload Passthru (PPT) application payload serializer (renamed from `Serializer`). +enum PPTSerializer: uint8 +{ + /// Use same serializer (dynamically or statically typed) for the application payload as used on the transport for the WAMP message envelope. + TRANSPORT = 0, + + /// Use JSON serializer for dynamically typed application payload. + JSON = 1, + + /// Use MsgPack serializer for dynamically typed application payload. + MSGPACK = 2, + + /// Use CBOR serializer for dynamically typed application payload. + CBOR = 3, + + /// Use UBJSON serializer for dynamically typed application payload. + UBJSON = 4, + + /// Raw pass-through of application payload, uninterpreted in any way. + OPAQUE = 5, + + /// Use FlatBuffers serializer for statically typed application payload (https://google.github.io/flatbuffers/index.html). + FLATBUFFERS = 6, + + /// Use FlexBuffers serializer dynamically typed application payload (https://google.github.io/flatbuffers/flexbuffers.html). + FLEXBUFFERS = 7 +} + +/// WAMP Payload Passthru (PPT) application payload cipher (NEW!). +enum PPTCipher: uint8 +{ + /// No valid cipher (unfilled) + NULL = 0, + + /// Particular combination of Salsa20 and Poly1305 specified in Daniel J. Bernstein, "Cryptography in NaCl" (https://cr.yp.to/highspeed/naclcrypto-20090310.pdf), see NaCl (https://nacl.cr.yp.to/) "crypto_secretbox_xsalsa20poly1305" (aka WAMP "cryptobox"). + XSALSA20POLY1305 = 1, + + /// Galois/Counter Mode (GCM), see https://en.wikipedia.org/wiki/Galois/Counter_Mode. + AES256GCM = 2 +} +``` + +### 5. 🟡 MEDIUM: Authentication Methods - NOT ENUMERATED + +yes, we need to add to `autobahn/wamp/flatbuffers/types.fbs`: + +``` +/// WAMP authentication method (see: WAMP protocol spec, "13. Authentication Methods"). +enum AuthMethod: uint8 +{ + /// Not set / not authenticated: `anonymous`. + NULL = 0, + + /// WAMP Ticket authentication: `ticket` (see: WAMP protocol spec, "13.1. Ticket-based Authentication"). + TICKET = 1, + + /// WAMP Challenge-Response authentication: `wampcra` (see: WAMP protocol spec, "13.2. Challenge Response Authentication"). + CRA = 1, + + /// WAMP Salted Challenge Response authentication: `wamp-scram` (see: WAMP protocol spec, "13.3. Salted Challenge Response Authentication"). + SCRAM = 1, + + /// WAMP Cryptosign authentication: `cryptosign` (see: WAMP protocol spec, "13.4. Cryptosign-based Authentication"). + CRYPTOSIGN = 1, +} +``` + +### 6. 🟢 LOW: Channel Binding Types - NOT ENUMERATED + +yes, we need to add to `autobahn/wamp/flatbuffers/types.fbs`: + +``` +/// TLS channel binding type (see: RFC5929 https://www.rfc-editor.org/rfc/rfc5929 and RFC9266 https://www.rfc-editor.org/rfc/rfc9266). +enum TLSChannelBinding: uint8 +{ + /// Not set / no channel binding. + NULL = 0, + + // RFC 5929 `tls-unique`. Available for TLS connections; historically used as the default for many SASL/SCRAM uses over TLS ≤ 1.2. Care needed with renegotiation and with TLS versions where the triple-handshake / EMS issues apply (see RFCs). + TLS_UNIQUE = 1, + + /// RFC 5929 `tls-unique-for-telnet`. Only relevant to TELNET / TELNET AUTH usage. See RFC 5929 for details and applicability guidance. + TLS_UNIQUE_TELNET = 2, + + /// RFC 5929 `tls-server-end-point`. Only available when a server certificate is used (i.e., cipher suites that include the Certificate handshake message / PKIX). Not applicable to OpenPGP server certificates. Recommended for situations where server-side proxies must interoperate without changes. + TLS_SERVER_ENDPOINT = 3, + + /// RFC 9266 `tls-exporter`. Defined to address TLS 1.3 (where the older tls-unique semantics are not reliably available). RFC 9266 updates defaults for TLS ≥ 1.3: when channel bindings are used for TLS 1.3, tls-exporter is the mandatory/expected mechanism. Not defined for connections where TLS renegotiation is enabled. + TLS_EXPORTER = 4, +} +``` + +### 7. 🟢 LOW: Key Derivation Functions (WAMP-SCRAM) - NOT ENUMERATED + +yes, we need to add to `autobahn/wamp/flatbuffers/types.fbs`: + +``` +/// Key Derivation Functions, e.g. WAMP SCRAM uses a password-based key derivation function (KDF) to hash user passwords. WAMP-SCRAM supports both Argon2 and PBKDF2 as the KDF (see: WAMP protocol specification, "SCRAM Algorithms"). +enum KDF: uint8 +{ + /// Not set / no TLS channel binding. + NULL = 0, + + /// Argon2id variant of Argon2, version 1.3 - `argon2id13`. + ARGON2ID13 = 1, + + /// PBKDF2 - `pbkdf2`. + PBKDF2 = 2, +} +``` + +### 8. 🟢 LOW: Transport Details - NOT IN FLATBUFFERS + +preface/note: "transport" here refers to the _WAMP_ transport as in: + +> WAMP can run over any Transport which is message-based, bidirectional, reliable and ordered. + +the _underlying_ communication "raw transport" or "channel" is different, and might not be message-based but streaming, and if so, requires a "framing" (into messages). + +this can be seen in the `WELCOME.Details.transport`, here is an example (from the WAMP spec): + +``` + +WAMP-Receive(-, -) << + WELCOME:: + [2, + 7325966140445461, + {'authextra': {'x_cb_node': 'intel-nuci7-49879', + 'x_cb_peer': 'tcp4:127.0.0.1:54046', + 'x_cb_pid': 49987, + 'x_cb_worker': 'worker001'}, + 'authid': 'client01@example.com', + 'authmethod': 'cryptosign', + 'authprovider': 'static', + 'authrole': 'device', + 'realm': 'devices', + 'roles': {'broker': {'features': {'event_retention': True, + 'pattern_based_subscription': True, + 'publisher_exclusion': True, + 'publisher_identification': True, + 'session_meta_api': True, + 'subscriber_blackwhite_listing': True, + 'subscription_meta_api': True, + 'subscription_revocation': True}}, + 'dealer': {'features': {'call_canceling': True, + 'caller_identification': True, + 'pattern_based_registration': True, + 'progressive_call_results': True, + 'registration_meta_api': True, + 'registration_revocation': True, + 'session_meta_api': True, + 'shared_registration': True, + 'testament_meta_api': True}}}, + 'x_cb_node': 'intel-nuci7-49879', + 'x_cb_peer': 'tcp4:127.0.0.1:54046', + 'x_cb_pid': 49987, + 'x_cb_worker': 'worker001'}] +<< +2022-07-13T17:38:29+0200 session joined: {'authextra': {'x_cb_node': 'intel-nuci7-49879', + 'x_cb_peer': 'tcp4:127.0.0.1:54046', + 'x_cb_pid': 49987, + 'x_cb_worker': 'worker001'}, + 'authid': 'client01@example.com', + 'authmethod': 'cryptosign', + 'authprovider': 'static', + 'authrole': 'device', + 'realm': 'devices', + 'resumable': False, + 'resume_token': None, + 'resumed': False, + 'serializer': 'cbor.batched', + 'session': 7325966140445461, + 'transport': {'channel_framing': 'websocket', + 'channel_id': {'tls-unique': b'\xe9s\xbe\xe2M\xce\xa9\xe2' + b'\x06%\xf9I\xc0\xe3\xcd(' + b'\xd62\xcc\xbe\xfeI\x07\xc2' + b'\xfa\xc2r\x87\x10\xf7\xb1`'}, + 'channel_serializer': None, + 'channel_type': 'tls', + 'http_cbtid': None, + 'http_headers_received': None, + 'http_headers_sent': None, + 'is_secure': True, + 'is_server': False, + 'own': None, + 'own_fd': -1, + 'own_pid': 50690, + 'own_tid': 50690, + 'peer': 'tcp4:127.0.0.1:8080', + 'peer_cert': None, + 'websocket_extensions_in_use': None, + 'websocket_protocol': None}} +``` + +since this is WAMP-over-WebSocket, `websocket_protocol` in above should actually be one of (see "2.3.1. WebSocket Transport" and "15.2. Message Batching"): + +- `wamp.2.json` +- `wamp.2.msgpack` +- `wamp.2.cbor` +- `wamp.2.json.batched` +- `wamp.2.msgpack.batched` +- `wamp.2.cbor.batched` + +and `websocket_extensions_in_use` should be e.g. `permessage-deflate` if WebSocket compression is active. + +Both of these _are_ supported in AutobahnPython _and_ Crossbar.io, e.g. look at: + +``` +oberstet@amd-ryzen5:~/work/wamp/crossbar$ find crossbar -name "*.py" -exec grep -Hi "websocket_extensions_in_use" {} \; +crossbar/router/service.py: 'websocket_extensions_in_use': [{'client_max_window_bits': 13, +crossbar/router/service.py: 'websocket_extensions_in_use': None, +crossbar/router/protocol.py: self._transport_details.websocket_extensions_in_use = None +crossbar/router/protocol.py: self._transport_details.websocket_extensions_in_use = [e.__json__() for e in self.websocket_extensions_in_use] +oberstet@amd-ryzen5:~/work/wamp/crossbar$ find crossbar -name "*.py" -exec grep -Hi "websocket_protocol" {} \; +crossbar/router/service.py: 'websocket_protocol': 'wamp.2.json'}, +crossbar/router/service.py: 'websocket_protocol': 'wamp.2.cbor'}} +crossbar/router/protocol.py: self._transport_details.websocket_protocol = protocol +crossbar/router/protocol.py: self._transport_details.websocket_protocol = "wamp.2.{}".format(self._serializer.SERIALIZER_ID) +oberstet@amd-ryzen5:~/work/wamp/crossbar$ +``` + +but yes, we should add to `autobahn/wamp/flatbuffers/types.fbs`: + +``` +/// WAMP transport channel type (`channel_type`). WAMP can run over any Transport which is message-based (requiring no WAMP transport channel framing) or stream-based (requiring WAMP transport channel framing), and bidirectional, reliable and ordered. +enum TransportChannelType: uint8 +{ + /// Not set or applicable. + NULL = 0, + + /// Host language native function call transport, e.g. inherently message-based already (function call). + FUNCTION = 1, + + /// Host run-time / OS level in-memory transport, e.g. (unframed / stream-based) memory buffer. + MEMORY = 2, + + /// Serial (UART) based transport (unframed / stream-based). + SERIAL = 3, + + /// TCP (non-TLS) based transport (unframed / stream-based). + TCP = 4, + + /// TLS (over TCP) based transport (unframed / stream-based). + TLS = 5, + + /// FUTURE (?): + /// - add WireGuard (over UDP) based transport. + /// - add QUIC (over UDP; using TLS 1.3 handshake messages; WebTransport browser API) based transport. + /// - add WebRTC data channels (SCTP over DTLS; RTCDataChannel browser API) based transport. + /// - add VirtIO (in-memory queues) based transport. +} +``` + +and + +``` +/// WAMP transport channel framing (`channel_framing`). +enum TransportChannelFraming: uint8 +{ + /// Not set. + NULL = 0, + + /// Raw transport itself is inherently message-based already (e.g. FUNCTION or VIRTIO). + NATIVE = 1, + + /// Raw transport itself (e.g. TCP) is stream-based and channel framing applied is WebSocket (RFC6455). + WEBSOCKET = 2, + + /// Raw transport itself (e.g. TCP) is stream-based and channel framing applied is RawSocket (WAMP). + RAWSOCKET = 3, +} +``` + +and + +``` +/// WAMP transport channel serializer (`channel_serializer`). +enum TransportChannelSerializer: uint8 +{ + /// Not set or applicable. + NULL = 0, + + /// Use JSON serializer (for dynamically typed app payload). + JSON = 1, + + /// Use MsgPack serializer (for dynamically typed app payload). + MSGPACK = 2, + + /// Use CBOR serializer (for dynamically typed app payload). + CBOR = 3, + + /// Use UBJSON serializer (for dynamically typed app payload). + UBJSON = 4, + + /// Raw pass-through of app payload, uninterpreted in any way. + OPAQUE = 5, + + /// Use FlatBuffers serialized (statically typed) payload (https://google.github.io/flatbuffers/index.html). + FLATBUFFERS = 6, + + /// Use FlexBuffers serialized (dynamically typed) payload (https://google.github.io/flatbuffers/flexbuffers.html). + FLEXBUFFERS = 7 +} +``` diff --git a/autobahn/asyncio/__init__.py b/autobahn/asyncio/__init__.py index 3d4ef1d32..ee96707cd 100644 --- a/autobahn/asyncio/__init__.py +++ b/autobahn/asyncio/__init__.py @@ -51,6 +51,7 @@ # Build identification string with optional NVX acceleration indicator if USES_NVX: import cffi + __ident__ = "Autobahn/{}-NVXCFFI/{}-asyncio-{}/{}".format( autobahn.__version__, cffi.__version__, @@ -59,7 +60,9 @@ ) else: __ident__ = "Autobahn/{}-asyncio-{}/{}".format( - autobahn.__version__, platform.python_implementation(), platform.python_version() + autobahn.__version__, + platform.python_implementation(), + platform.python_version(), ) """ diff --git a/autobahn/nvx/_xormasker.py b/autobahn/nvx/_xormasker.py index 76d4b838e..dfc30ee1c 100644 --- a/autobahn/nvx/_xormasker.py +++ b/autobahn/nvx/_xormasker.py @@ -106,8 +106,7 @@ def __init__(self, mask, use_simd=True): # Keep mask buffer alive for the lifetime of the masker self._mask_buffer = self.ffi.new("uint8_t[4]", mask) self._masker = self.ffi.gc( - self.lib.nvx_xormask_new(self._mask_buffer), - self.lib.nvx_xormask_free + self.lib.nvx_xormask_new(self._mask_buffer), self.lib.nvx_xormask_free ) # Set implementation: 1=Simple (scalar), 2=SSE2 (SIMD) @@ -140,6 +139,7 @@ class XorMaskerSimple(XorMaskerNvx): Simple scalar XOR masker for small payloads. :noindex: """ + def __init__(self, mask): super().__init__(mask, use_simd=False) @@ -149,6 +149,7 @@ class XorMaskerShifted1(XorMaskerNvx): SIMD-optimized XOR masker for large payloads. :noindex: """ + def __init__(self, mask): super().__init__(mask, use_simd=True) diff --git a/autobahn/nvx/test/test_nvx_utf8validator.py b/autobahn/nvx/test/test_nvx_utf8validator.py index 3cc675786..1c0da0491 100644 --- a/autobahn/nvx/test/test_nvx_utf8validator.py +++ b/autobahn/nvx/test/test_nvx_utf8validator.py @@ -327,6 +327,7 @@ def test_standard_utf8validator_incremental(self): information which is sufficient for real-world usage. """ from autobahn.websocket import USES_NVX + if USES_NVX: self.skipTest("NVX prioritizes performance over detailed position tracking") validator = StandardUtf8Validator() diff --git a/autobahn/twisted/__init__.py b/autobahn/twisted/__init__.py index 6642074cc..1a1e657ce 100644 --- a/autobahn/twisted/__init__.py +++ b/autobahn/twisted/__init__.py @@ -66,6 +66,7 @@ # Build identification string with optional NVX acceleration indicator if USES_NVX: import cffi + __ident__ = "Autobahn/{}-NVXCFFI/{}-Twisted/{}-{}/{}".format( autobahn.__version__, cffi.__version__, diff --git a/autobahn/wamp/flatbuffers/auth.fbs b/autobahn/wamp/flatbuffers/auth.fbs index 19e0d2019..503c4d12f 100644 --- a/autobahn/wamp/flatbuffers/auth.fbs +++ b/autobahn/wamp/flatbuffers/auth.fbs @@ -13,47 +13,57 @@ include "roles.fbs"; namespace wamp.proto; -// WAMP authentication method. +/// WAMP authentication method (see: WAMP protocol spec, "13. Authentication Methods"). enum AuthMethod: uint8 { - // Pseudo anonymous authentication. - ANONYMOUS = 0, + /// Not set / not authenticated: `anonymous`. + NULL = 0, - // Trnasport level authentication based on HTTP header cookie set. - COOKIE = 1, + /// WAMP Ticket authentication: `ticket` (see: WAMP protocol spec, "13.1. Ticket-based Authentication"). + TICKET = 1, - // Transport level authentication based on TLS client certificate presented. - TLS = 2, + /// WAMP Challenge-Response authentication: `wampcra` (see: WAMP protocol spec, "13.2. Challenge Response Authentication"). + CRA = 2, - // Authentication using WAMP-Ticket, a flexible one time token/password scheme.. - TICKET = 3, + /// WAMP Salted Challenge Response authentication: `wamp-scram` (see: WAMP protocol spec, "13.3. Salted Challenge Response Authentication"). + SCRAM = 3, - // Authentication using WAMP-CRA, a simple challenge-response scheme. - CRA = 4, - - // Authentication using WAMP-SCRAM, a sophisticated challenge-response scheme. - SCRAM = 5, - - // Authentication using WAMP-Cryptosign, a highly secure public-private-key scheme. - CRYPTOSIGN = 6 + /// WAMP Cryptosign authentication: `cryptosign` (see: WAMP protocol spec, "13.4. Cryptosign-based Authentication"). + CRYPTOSIGN = 4 } -enum ChannelBinding: uint8 +/// TLS channel binding type (see: RFC5929 https://www.rfc-editor.org/rfc/rfc5929 and RFC9266 https://www.rfc-editor.org/rfc/rfc9266). +enum TLSChannelBinding: uint8 { - NONE = 0, + /// Not set / no channel binding. + NULL = 0, + + // RFC 5929 `tls-unique`. Available for TLS connections; historically used as the default for many SASL/SCRAM uses over TLS ≤ 1.2. Care needed with renegotiation and with TLS versions where the triple-handshake / EMS issues apply (see RFCs). TLS_UNIQUE = 1, + + /// RFC 5929 `tls-unique-for-telnet`. Only relevant to TELNET / TELNET AUTH usage. See RFC 5929 for details and applicability guidance. + TLS_UNIQUE_TELNET = 2, + + /// RFC 5929 `tls-server-end-point`. Only available when a server certificate is used (i.e., cipher suites that include the Certificate handshake message / PKIX). Not applicable to OpenPGP server certificates. Recommended for situations where server-side proxies must interoperate without changes. + TLS_SERVER_ENDPOINT = 3, + + /// RFC 9266 `tls-exporter`. Defined to address TLS 1.3 (where the older tls-unique semantics are not reliably available). RFC 9266 updates defaults for TLS ≥ 1.3: when channel bindings are used for TLS 1.3, tls-exporter is the mandatory/expected mechanism. Not defined for connections where TLS renegotiation is enabled. + TLS_EXPORTER = 4 } -enum Kdf: uint8 +/// Key Derivation Functions, e.g. WAMP SCRAM uses a password-based key derivation function (KDF) to hash user passwords. WAMP-SCRAM supports both Argon2 and PBKDF2 as the KDF (see: WAMP protocol specification, "SCRAM Algorithms"). +enum KDF: uint8 { - NONE = 0, + /// Not set / no TLS channel binding. + NULL = 0, - PBKDF2 = 1, + /// Argon2id variant of Argon2, version 1.3 - `argon2id13`. + ARGON2ID13 = 1, - // Argon2id variant of Argon2, version 1.3 (`argon2id13`). - ARGON2 = 2 + /// PBKDF2 - `pbkdf2`. + PBKDF2 = 2 } @@ -115,23 +125,23 @@ table AuthScramRequest nonce: string (required, base64); // Optional requested channel binding type. - channel_binding: ChannelBinding; + channel_binding: TLSChannelBinding; } table AuthScramChallenge { // A server-generatated nonce that is appended to the client-generated - // nonce sent in the previous HELLO message. + // nonce sent in the previous HELLO message. nonce: string (required, base64); // The base64-encoded salt for this user, to be passed to the key // derivation function. This value is stored with each user record in - // the authentication database. + // the authentication database. salt: string (required, base64); // The key derivation function (KDF) used to hash the password. This - // value is stored with each user record in the authentication database. - kdf: Kdf = ARGON2; + // value is stored with each user record in the authentication database. + kdf: KDF = ARGON2ID13; // The execution time cost factor to use for generating the // SaltedPassword hash. This value is stored with each user record in @@ -144,7 +154,7 @@ table AuthScramChallenge memory: uint32; // Channel binding type, if channel binding was requested and is actually used. - channel_binding: ChannelBinding; + channel_binding: TLSChannelBinding; } table AuthScramWelcome @@ -164,14 +174,14 @@ table AuthCryptosignRequest pubkey: string (required, hex); // Optional requested channel binding type. - channel_binding: ChannelBinding; + channel_binding: TLSChannelBinding; } table AuthCryptosignChallenge { // Channel binding type, if channel binding was requested and is actually used. - channel_binding: ChannelBinding; + channel_binding: TLSChannelBinding; } table AuthCryptosignWelcome diff --git a/autobahn/wamp/flatbuffers/pubsub.fbs b/autobahn/wamp/flatbuffers/pubsub.fbs index c739fd985..9fe608944 100644 --- a/autobahn/wamp/flatbuffers/pubsub.fbs +++ b/autobahn/wamp/flatbuffers/pubsub.fbs @@ -12,7 +12,7 @@ include "types.fbs"; namespace wamp.proto; -// SUBSCRIBE message (message_type = 32): [SUBSCRIBE, Request|id, Options|dict, Topic|uri] +// [Category 1: Neither Payload nor Forwarding] SUBSCRIBE message (message_type = 32): Client subscribes to a topic. table Subscribe { // The WAMP session ID on the node this session is (directly) attached to. @@ -32,7 +32,7 @@ table Subscribe } -// SUBSCRIBED message (message_type = 33): [SUBSCRIBED, SUBSCRIBE.Request|id, Subscription|id] +// [Category 1: Neither Payload nor Forwarding] SUBSCRIBED message (message_type = 33): Router acknowledges subscription. table Subscribed { // The WAMP session ID on the node this session is (directly) attached to. @@ -46,7 +46,7 @@ table Subscribed } -// UNSUBSCRIBE message (message_type = 34): [UNSUBSCRIBE, Request|id, SUBSCRIBED.Subscription|id] +// [Category 1: Neither Payload nor Forwarding] UNSUBSCRIBE message (message_type = 34): Client unsubscribes from a topic. table Unsubscribe { // The WAMP session ID on the node this session is (directly) attached to. @@ -60,7 +60,7 @@ table Unsubscribe } -// UNSUBSCRIBED message (message_type = 35): [UNSUBSCRIBED, UNSUBSCRIBE.Request|id, Details|dict] +// [Category 1: Neither Payload nor Forwarding] UNSUBSCRIBED message (message_type = 35): Router acknowledges unsubscription. table Unsubscribed { // The WAMP session ID on the node this session is (directly) attached to. @@ -77,7 +77,7 @@ table Unsubscribed } -// PUBLISH message (message_type = 16): [PUBLISH, Request|id, Options|dict, Topic|uri, Payload|binary] +// [Category 4: Both Payload and Forwarding] PUBLISH message (message_type = 16): Client publishes an event with application payload. table Publish { // The WAMP session ID on the node this session is (directly) attached to. @@ -98,14 +98,17 @@ table Publish /// Alternative, transparent payload. If given, ``args`` and ``kwargs`` must be left unset. payload: [uint8]; - // The encoding algorithm that was used to encode the payload. - enc_algo: Payload; + /// The specific scheme in use with Payload Passthru (PPT) mode for the application payload. + ppt_scheme: PPTScheme; - // The payload object serializer that was used encoding the payload. - enc_serializer: Serializer; + /// The specific serializer encoding the application payload with the Payload Passthru (PPT) scheme in use. + ppt_serializer: PPTSerializer; - // When using Payload.CRYPTOBOX, the public Cryptobox key of the key pair used for encrypting the payload. - enc_key: [uint8]; + /// The cryptographic algorithm ("cipher") encrypting the application payload with the Payload Passthru (PPT) scheme in use. + ppt_cipher: PPTCipher; + + /// The identifier or reference to the encryption key that was used to encrypt the payload with the Payload Passthru (PPT) scheme and cipher in use. + ppt_keyid: string (ppt_keyid); // If true, acknowledge the publication with a success or error response. acknowledge: bool; @@ -142,7 +145,7 @@ table Publish } -// PUBLISHED message (message_type = 17): [PUBLISHED, PUBLISH.Request|id, Publication|id] +// [Category 1: Neither Payload nor Forwarding] PUBLISHED message (message_type = 17): Router acknowledges publication. table Published { // The WAMP session ID on the node this session is (directly) attached to. @@ -156,7 +159,7 @@ table Published } -// EVENT message (message_type = 36): [EVENT, SUBSCRIBED.Subscription|id, PUBLISHED.Publication|id, Details|dict, PUBLISH.Payload|binary] +// [Category 4: Both Payload and Forwarding] EVENT message (message_type = 36): Router dispatches event to subscriber with application payload. table Event { // The WAMP session ID on the node this session is (directly) attached to. @@ -177,14 +180,17 @@ table Event /// Alternative, transparent payload. If given, ``args`` and ``kwargs`` must be left unset. payload: [uint8]; - // The encoding algorithm that was used to encode the payload. - enc_algo: Payload; + /// The specific scheme in use with Payload Passthru (PPT) mode for the application payload. + ppt_scheme: PPTScheme; + + /// The specific serializer encoding the application payload with the Payload Passthru (PPT) scheme in use. + ppt_serializer: PPTSerializer; - // The payload object serializer that was used encoding the payload. - enc_serializer: Serializer; + /// The cryptographic algorithm ("cipher") encrypting the application payload with the Payload Passthru (PPT) scheme in use. + ppt_cipher: PPTCipher; - // When using Payload.CRYPTOBOX, the public Cryptobox key of the key pair used for encrypting the payload. - enc_key: [uint8]; + /// The identifier or reference to the encryption key that was used to encrypt the payload with the Payload Passthru (PPT) scheme and cipher in use. + ppt_keyid: string (ppt_keyid); // The WAMP session ID of the pubisher. Only filled when the publisher is disclosed. publisher: uint64; @@ -212,7 +218,7 @@ table Event } -// EVENT_RECEIVED message (message_type = 37): [EVENT_RECEIVED, EVENT.Publication|id, Details|dict, Payload|binary] +// [Category 3: Forwarding Only] EVENT_RECEIVED message (message_type = 37): Acknowledgment from subscriber to publisher for QoS level 2 ("exactly once delivery") and survey mode. table EventReceived { // The WAMP session ID on the node this session is (directly) attached to. @@ -221,15 +227,6 @@ table EventReceived // The publication ID of the event that was received, and that is acknowledged. publication: uint64; - // Raw application payload: error arguments. This might be encrypted (with Payload==Payload.CRYPTOBOX), and is serialized according to enc_serializer. - payload: [uint8]; - - // The encoding algorithm that was used to encode the payload. - enc_algo: Payload; - - // The payload object serializer that was used encoding the payload. - enc_serializer: Serializer; - - // When using Payload.CRYPTOBOX, the public Cryptobox key of the key pair used for encrypting the payload. - enc_key: [uint8]; + // When this message is forwarded in router-to-router traffic, the route of the acknowledgment message. This is the reversed forward_for from the originating EVENT message, so the acknowledgment travels back through the same router mesh path. + forward_for: [Principal]; } diff --git a/autobahn/wamp/flatbuffers/rpc.fbs b/autobahn/wamp/flatbuffers/rpc.fbs index 3794678a8..47fade74e 100644 --- a/autobahn/wamp/flatbuffers/rpc.fbs +++ b/autobahn/wamp/flatbuffers/rpc.fbs @@ -12,7 +12,7 @@ include "types.fbs"; namespace wamp.proto; -// CALL message (message_type = 48): [CALL, Request|id, Options|dict, Procedure|uri, Payload|binary] +// [Category 4: Both Payload and Forwarding] CALL message (message_type = 48): Client calls a procedure with application payload. table Call { // The WAMP session ID on the node this session is (directly) attached to. @@ -33,14 +33,17 @@ table Call /// Alternative, transparent payload. If given, ``args`` and ``kwargs`` must be left unset. payload: [uint8]; - // The encoding algorithm that was used to encode the payload. - enc_algo: Payload; + /// The specific scheme in use with Payload Passthru (PPT) mode for the application payload. + ppt_scheme: PPTScheme; - // The payload object serializer that was used encoding the payload. - enc_serializer: Serializer; + /// The specific serializer encoding the application payload with the Payload Passthru (PPT) scheme in use. + ppt_serializer: PPTSerializer; - // When using Payload.CRYPTOBOX, the public Cryptobox key of the key pair used for encrypting the payload. - enc_key: [uint8]; + /// The cryptographic algorithm ("cipher") encrypting the application payload with the Payload Passthru (PPT) scheme in use. + ppt_cipher: PPTCipher; + + /// The identifier or reference to the encryption key that was used to encrypt the payload with the Payload Passthru (PPT) scheme and cipher in use. + ppt_keyid: string (ppt_keyid); // If present, cancel the call after this duration in ms. timeout: uint32; @@ -65,7 +68,7 @@ table Call } -// CANCEL message (message_type = 49): [CANCEL, CALL.Request|id, Options|dict] +// [Category 3: Forwarding Only] CANCEL message (message_type = 49): Client cancels a pending call. table Cancel { // The WAMP session ID on the node this session is (directly) attached to. @@ -82,7 +85,7 @@ table Cancel } -// RESULT message (message_type = 50): [RESULT, CALL.Request|id, Details|dict, Payload|binary] +// [Category 4: Both Payload and Forwarding] RESULT message (message_type = 50): Callee returns call result with application payload. table Result { // The WAMP session ID on the node this session is (directly) attached to. @@ -100,14 +103,17 @@ table Result /// Alternative, transparent payload. If given, ``args`` and ``kwargs`` must be left unset. payload: [uint8]; - // The encoding algorithm that was used to encode the payload. - enc_algo: Payload; + /// The specific scheme in use with Payload Passthru (PPT) mode for the application payload. + ppt_scheme: PPTScheme; + + /// The specific serializer encoding the application payload with the Payload Passthru (PPT) scheme in use. + ppt_serializer: PPTSerializer; - // The payload object serializer that was used encoding the payload. - enc_serializer: Serializer; + /// The cryptographic algorithm ("cipher") encrypting the application payload with the Payload Passthru (PPT) scheme in use. + ppt_cipher: PPTCipher; - // When using Payload.CRYPTOBOX, the public Cryptobox key of the key pair used for encrypting the payload. - enc_key: [uint8]; + /// The identifier or reference to the encryption key that was used to encrypt the payload with the Payload Passthru (PPT) scheme and cipher in use. + ppt_keyid: string (ppt_keyid); // If true, this result is a progressive call result, and subsequent results (or a final error) will follow. progress: bool; @@ -126,7 +132,7 @@ table Result } -// REGISTER message (message_type = 64): [REGISTER, Request|id, Options|dict, Procedure|uri] +// [Category 1: Neither Payload nor Forwarding] REGISTER message (message_type = 64): Callee registers a procedure. table Register { // The WAMP session ID on the node this session is (directly) attached to. @@ -152,7 +158,7 @@ table Register } -// REGISTERED message (message_type = 65): [REGISTERED, REGISTER.Request|id, Registration|id] +// [Category 1: Neither Payload nor Forwarding] REGISTERED message (message_type = 65): Router acknowledges registration. table Registered { // The WAMP session ID on the node this session is (directly) attached to. @@ -166,7 +172,7 @@ table Registered } -// UNREGISTER message (message_type = 66): [UNREGISTER, Request|id, REGISTERED.Registration|id] +// [Category 1: Neither Payload nor Forwarding] UNREGISTER message (message_type = 66): Callee unregisters a procedure. table Unregister { // The WAMP session ID on the node this session is (directly) attached to. @@ -180,7 +186,7 @@ table Unregister } -// UNREGISTERED message (message_type = 67): [UNREGISTERED, UNREGISTER.Request|id, Details|dict] +// [Category 1: Neither Payload nor Forwarding] UNREGISTERED message (message_type = 67): Router acknowledges unregistration. table Unregistered { // The WAMP session ID on the node this session is (directly) attached to. @@ -197,7 +203,7 @@ table Unregistered } -// INVOCATION message (message_type = 68): [INVOCATION, Request|id, REGISTERED.Registration|id, Details|dict, Payload|binary] +// [Category 4: Both Payload and Forwarding] INVOCATION message (message_type = 68): Router invokes registered procedure with application payload. table Invocation { // The WAMP session ID on the node this session is (directly) attached to. @@ -218,14 +224,17 @@ table Invocation /// Alternative, transparent payload. If given, ``args`` and ``kwargs`` must be left unset. payload: [uint8]; - // The encoding algorithm that was used to encode the payload. - enc_algo: Payload; + /// The specific scheme in use with Payload Passthru (PPT) mode for the application payload. + ppt_scheme: PPTScheme; - // The payload object serializer that was used encoding the payload. - enc_serializer: Serializer; + /// The specific serializer encoding the application payload with the Payload Passthru (PPT) scheme in use. + ppt_serializer: PPTSerializer; - // When using Payload.CRYPTOBOX, the public Cryptobox key of the key pair used for encrypting the payload. - enc_key: [uint8]; + /// The cryptographic algorithm ("cipher") encrypting the application payload with the Payload Passthru (PPT) scheme in use. + ppt_cipher: PPTCipher; + + /// The identifier or reference to the encryption key that was used to encrypt the payload with the Payload Passthru (PPT) scheme and cipher in use. + ppt_keyid: string (ppt_keyid); // For pattern-based registrations (not for exact matching registrations, the invocation MUST include the actual procedure being called. procedure: string (uri); @@ -253,7 +262,7 @@ table Invocation } -// INTERRUPT message (message_type = 69): [INTERRUPT, INVOCATION.Request|id, Options|dict] +// [Category 3: Forwarding Only] INTERRUPT message (message_type = 69): Router interrupts a pending invocation. table Interrupt { // The WAMP session ID on the node this session is (directly) attached to. @@ -263,7 +272,7 @@ table Interrupt request: uint64 (key); // Specifies how to interrupt the invocation. - mode: CancelMode = ABORT; + mode: CancelMode = KILL; // The reason (an URI) for the invocation interrupt. reason: string (uri); @@ -273,7 +282,7 @@ table Interrupt } -// YIELD message (message_type = 70): [YIELD, INVOCATION.Request|id, Options|dict, Payload|binary] +// [Category 4: Both Payload and Forwarding] YIELD message (message_type = 70): Callee yields invocation result with application payload. table Yield { // The WAMP session ID on the node this session is (directly) attached to. @@ -291,14 +300,17 @@ table Yield /// Alternative, transparent payload. If given, ``args`` and ``kwargs`` must be left unset. payload: [uint8]; - // The encoding algorithm that was used to encode the payload. - enc_algo: Payload; + /// The specific scheme in use with Payload Passthru (PPT) mode for the application payload. + ppt_scheme: PPTScheme; + + /// The specific serializer encoding the application payload with the Payload Passthru (PPT) scheme in use. + ppt_serializer: PPTSerializer; - // The payload object serializer that was used encoding the payload. - enc_serializer: Serializer; + /// The cryptographic algorithm ("cipher") encrypting the application payload with the Payload Passthru (PPT) scheme in use. + ppt_cipher: PPTCipher; - // When using Payload.CRYPTOBOX, the public Cryptobox key of the key pair used for encrypting the payload. - enc_key: [uint8]; + /// The identifier or reference to the encryption key that was used to encrypt the payload with the Payload Passthru (PPT) scheme and cipher in use. + ppt_keyid: string (ppt_keyid); // If true, this result is a progressive invocation result, and subsequent results (or a final error) will follow. progress: bool; diff --git a/autobahn/wamp/flatbuffers/session.fbs b/autobahn/wamp/flatbuffers/session.fbs index ae80d8d8b..22890b40a 100644 --- a/autobahn/wamp/flatbuffers/session.fbs +++ b/autobahn/wamp/flatbuffers/session.fbs @@ -14,7 +14,7 @@ include "auth.fbs"; namespace wamp.proto; -// HELLO message (message_type = 1): [HELLO, Realm|uri, Details|dict] +// [Category 1: Neither Payload nor Forwarding] HELLO message (message_type = 1): Client initiates session. table Hello { // The WAMP session ID on the node this session is (directly) attached to. @@ -50,7 +50,7 @@ table Hello } -// WELCOME message (message_type = 2): [WELCOME, Session|id, Details|dict] +// [Category 1: Neither Payload nor Forwarding] WELCOME message (message_type = 2): Router accepts session. table Welcome { // The WAMP session ID on the node this session is (directly) attached to. @@ -88,7 +88,7 @@ table Welcome } -// ABORT message (message_type = 3): [ABORT, Details|dict, Reason|uri] +// [Category 1: Neither Payload nor Forwarding] ABORT message (message_type = 3): Session aborted during handshake. table Abort { // The WAMP session ID on the node this session is (directly) attached to. @@ -102,7 +102,7 @@ table Abort } -// CHALLENGE message (message_type = 4): [CHALLENGE, Method|string, Extra|dict] +// [Category 1: Neither Payload nor Forwarding] CHALLENGE message (message_type = 4): Router sends authentication challenge. table Challenge { // The WAMP session ID on the node this session is (directly) attached to. @@ -116,7 +116,7 @@ table Challenge } -// AUTHENTICATE message (message_type = 5): [AUTHENTICATE, Signature|string, Extra|dict] +// [Category 1: Neither Payload nor Forwarding] AUTHENTICATE message (message_type = 5): Client responds to authentication challenge. table Authenticate { // The WAMP session ID on the node this session is (directly) attached to. @@ -130,7 +130,7 @@ table Authenticate } -// GOODBYE message (message_type = 6): [GOODBYE, Details|dict, Reason|uri] +// [Category 1: Neither Payload nor Forwarding] GOODBYE message (message_type = 6): Session closed gracefully. table Goodbye { // The WAMP session ID on the node this session is (directly) attached to. @@ -147,7 +147,7 @@ table Goodbye } -// ERROR message (message_type = 8): [ERROR, REQUEST.Type|int, REQUEST.Request|id, Details|dict, Error|uri, Payload|binary] +// [Category 4: Both Payload and Forwarding] ERROR message (message_type = 8): Error response with optional application payload. table Error { // The WAMP session ID on the node this session is (directly) attached to. @@ -171,14 +171,17 @@ table Error /// Alternative, transparent payload. If given, ``args`` and ``kwargs`` must be left unset. payload: [uint8]; - // The encoding algorithm that was used to encode the payload. - enc_algo: Payload; + /// The specific scheme in use with Payload Passthru (PPT) mode for the application payload. + ppt_scheme: PPTScheme; - // The payload object serializer that was used encoding the payload. - enc_serializer: Serializer; + /// The specific serializer encoding the application payload with the Payload Passthru (PPT) scheme in use. + ppt_serializer: PPTSerializer; - // When using Payload.CRYPTOBOX, the public Cryptobox key of the key pair used for encrypting the payload. - enc_key: [uint8]; + /// The cryptographic algorithm ("cipher") encrypting the application payload with the Payload Passthru (PPT) scheme in use. + ppt_cipher: PPTCipher; + + /// The identifier or reference to the encryption key that was used to encrypt the payload with the Payload Passthru (PPT) scheme and cipher in use. + ppt_keyid: string (ppt_keyid); // R2R-link message forwarding route that was taken. forward_for: [Principal]; diff --git a/autobahn/wamp/flatbuffers/types.fbs b/autobahn/wamp/flatbuffers/types.fbs index 1b3e0b33b..c32b26261 100644 --- a/autobahn/wamp/flatbuffers/types.fbs +++ b/autobahn/wamp/flatbuffers/types.fbs @@ -7,46 +7,53 @@ // ////////////////////////////////////////////////////////////////////////////// -// Define custom attributes to hint a string subtypes: +/// Define custom attributes to hint a string subtypes: attribute "uri"; attribute "uri_pattern"; attribute "principal"; attribute "hex"; attribute "base64"; +/// WAMP Payload Passthru (PPT) encryption key id. The value can be a hex-encoded string, URI, DNS name, Ethereum address, UUID identifier - any meaningful value which allows the target peer to choose a private key without guessing. The format of the value may depend on the ppt_scheme attribute. +attribute "ppt_keyid"; -// Namespace for WAMP. + +/// Namespace for WAMP. namespace wamp; -// Simple mapping, from string keys to string values. +/// Simple mapping, from string keys to string values. table Map { - // Key, which must match the regular expression `[A-Za-z_][A-Za-z0-9_]*`. That - // makes keys a valid identified in FlatBuffers (and most programming languages) + /// Key, which must match the regular expression `[A-Za-z_][A-Za-z0-9_]*`. That + /// makes keys a valid identified in FlatBuffers (and most programming languages) key: string (required, key); - // Value, which may be any (UTF8 encoded Unicode) string. + /// Value, which may be any (UTF8 encoded Unicode) string. value: string; } -// Void type (table based) for use with services. +/// Void type (table based) for use with services. table Void { - // nothing here! + /// nothing here! } -// Namespace for WAMP protocol. +/// Namespace for WAMP protocol. namespace wamp.proto; -// WAMP message type IDs. +/// WAMP message type IDs organized by message categories: +/// - Category 1: Neither Payload nor Forwarding (12 messages) +/// - Category 2: Payload Only (0 messages) - architecturally empty +/// - Category 3: Forwarding Only (3 messages) +/// - Category 4: Both Payload and Forwarding (7 messages) enum MessageType: uint16 { - // No valid message type + /// No valid message type NULL = 0, - // Session opening, authentication and closing + /// [Category 1] Session opening, authentication and closing HELLO = 1, WELCOME = 2, ABORT = 3, @@ -54,147 +61,238 @@ enum MessageType: uint16 AUTHENTICATE = 5, GOODBYE = 6, - // Used in both PubSub and RPC + /// [Category 4] Used in both PubSub and RPC ERROR = 8, - // Publish & Subscribe (PubSub) - PUBLISH = 16, - PUBLISHED = 17, - SUBSCRIBE = 32, - SUBSCRIBED = 33, - UNSUBSCRIBE = 34, - UNSUBSCRIBED = 35, - EVENT = 36, - EVENT_RECEIVED = 37, // NEW: for QoS level 2 PubSub ("exactly once delivery") - // as well as "survey mode" PubSub response carrying - - // Remote Procedure Calls (RPC) - CALL = 48, - CANCEL = 49, - RESULT = 50, - REGISTER = 64, - REGISTERED = 65, - UNREGISTER = 66, - UNREGISTERED = 67, - INVOCATION = 68, - INTERRUPT = 69, - YIELD = 70 + /// Publish & Subscribe (PubSub) + PUBLISH = 16, // [Category 4] Publisher sends event with application payload + PUBLISHED = 17, // [Category 1] Router acknowledges publication + SUBSCRIBE = 32, // [Category 1] Client subscribes to topic + SUBSCRIBED = 33, // [Category 1] Router acknowledges subscription + UNSUBSCRIBE = 34, // [Category 1] Client unsubscribes from topic + UNSUBSCRIBED = 35, // [Category 1] Router acknowledges unsubscription + EVENT = 36, // [Category 4] Router dispatches event with application payload + EVENT_RECEIVED = 37, // [Category 3] Subscriber acknowledges event reception (QoS level 2) + + /// Remote Procedure Calls (RPC) + CALL = 48, // [Category 4] Caller invokes procedure with application payload + CANCEL = 49, // [Category 3] Caller cancels pending call + RESULT = 50, // [Category 4] Callee returns result with application payload + REGISTER = 64, // [Category 1] Callee registers procedure + REGISTERED = 65, // [Category 1] Router acknowledges registration + UNREGISTER = 66, // [Category 1] Callee unregisters procedure + UNREGISTERED = 67, // [Category 1] Router acknowledges unregistration + INVOCATION = 68, // [Category 4] Router invokes procedure with application payload + INTERRUPT = 69, // [Category 3] Router interrupts pending invocation + YIELD = 70 // [Category 4] Callee yields result with application payload } -// WAMP session identity information. -struct Principal +/// WAMP session identity information. +table Principal { - // WAMP session ID. + /// WAMP session ID. session: uint64; -// FIXME: error: structs may contain only scalar or struct fields -// -// // WAMP session authentication ID. -// authid: string (principal); -// -// // WAMP session authentication role. -// authrole: string (principal); + /// WAMP session authentication ID. + authid: string (principal); + + /// WAMP session authentication role. + authrole: string (principal); } -// Application payload type. -enum Payload: uint8 +/// WAMP Payload Passthru (PPT) scheme (renamed from `Payload`). +enum PPTScheme: uint8 { - // Plain WAMP application payload. - PLAIN = 0, + /// Unset (plain WAMP application payload) + NONE = 0, - // Encrypted WAMP application payload. This is using WAMP-cryptobox (Curve25519 / Cryptobox). + /// WAMP-cryptobox end-to-end encrypted application payload CRYPTOBOX = 1, - // Raw pass-through of app payload, uninterpreted in any way. - OPAQUE = 2 + /// MQTT passthrough-mode application payload + MQTT = 2, + + /// XBR end-to-end encrypted and Ethereum anchored application payload + XBR = 3, + + /// Raw pass-through of app payload, uninterpreted in any way. + OPAQUE = 4 } -// Application payload serializer types. -enum Serializer: uint8 +/// WAMP Payload Passthru (PPT) application payload serializer (renamed from `Serializer`). +enum PPTSerializer: uint8 { - // Use same (dynamic) serializer for the app payload as on the transport. - // This will be one of JSON, MSGPACK, CBOR or UBJSON. + /// Use same serializer (dynamically or statically typed) for the application payload as used on the transport for the WAMP message envelope. TRANSPORT = 0, - // Use JSON serializer (for dynamically typed app payload). + /// Use JSON serializer for dynamically typed application payload. JSON = 1, - // Use MsgPack serializer (for dynamically typed app payload). + /// Use MsgPack serializer for dynamically typed application payload. MSGPACK = 2, - // Use CBOR serializer (for dynamically typed app payload). + /// Use CBOR serializer for dynamically typed application payload. CBOR = 3, - // Use UBJSON serializer (for dynamically typed app payload). + /// Use UBJSON serializer for dynamically typed application payload. UBJSON = 4, - // Raw pass-through of app payload, uninterpreted in any way. + /// Raw pass-through of application payload, uninterpreted in any way. OPAQUE = 5, - // Use FlatBuffers serialized (statically typed) payload (https://google.github.io/flatbuffers/index.html). + /// Use FlatBuffers serializer for statically typed application payload (https://google.github.io/flatbuffers/index.html). FLATBUFFERS = 6, - // Use FlexBuffers serialized (dynamically typed) payload (https://google.github.io/flatbuffers/flexbuffers.html). + /// Use FlexBuffers serializer dynamically typed application payload (https://google.github.io/flatbuffers/flexbuffers.html). FLEXBUFFERS = 7 } -// Subscription topic matching method. +/// WAMP Payload Passthru (PPT) application payload cipher (NEW!). +enum PPTCipher: uint8 +{ + /// No valid cipher (unfilled) + NULL = 0, + + /// Particular combination of Salsa20 and Poly1305 specified in Daniel J. Bernstein, "Cryptography in NaCl" (https://cr.yp.to/highspeed/naclcrypto-20090310.pdf), see NaCl (https://nacl.cr.yp.to/) "crypto_secretbox_xsalsa20poly1305" (aka WAMP "cryptobox"). + XSALSA20POLY1305 = 1, + + /// Galois/Counter Mode (GCM), see https://en.wikipedia.org/wiki/Galois/Counter_Mode. + AES256GCM = 2 +} + + +/// Subscription topic matching method. enum Match: uint8 { - // Match URI exact. + /// Match URI exact. EXACT = 0, - // Match URI by prefix. + /// Match URI by prefix. PREFIX = 1, - // Match URI by wildcard. + /// Match URI by wildcard. WILDCARD = 2 } -// Remote procedure invocation policy, for use with shared registrations. +/// Remote procedure invocation policy, for use with shared registrations. enum InvocationPolicy: uint8 { - // Standard invocation policy: a procedure may only be registered by at most - // one callee at a time. This is the default. + /// Standard invocation policy: a procedure may only be registered by at most + /// one callee at a time. This is the default. SINGLE = 0, - // First callee invocation policy: all calls to a procedure are forwarded to - // the callee that first registered. Should that callee unregister, calls - // are forwarded to the callee that registered next after the former. + /// First callee invocation policy: all calls to a procedure are forwarded to + /// the callee that first registered. Should that callee unregister, calls + /// are forwarded to the callee that registered next after the former. FIRST = 1, - // Last callee invocation policy: all calls to a procedure are forwwrded to - // the callee that last registered. Should that callee unregister, calls - // are forwarded to the callee that registered next before the former. + /// Last callee invocation policy: all calls to a procedure are forwwrded to + /// the callee that last registered. Should that callee unregister, calls + /// are forwarded to the callee that registered next before the former. LAST = 2, - // Round-robin invocation policy: calls are forward to all registered callees - // in a round-robin fashion. Callees that unregister are removed from the - // round-robin list, and new callees registering will get calls immediately - // in the next dispatching round. + /// Round-robin invocation policy: calls are forward to all registered callees + /// in a round-robin fashion. Callees that unregister are removed from the + /// round-robin list, and new callees registering will get calls immediately + /// in the next dispatching round. ROUNDROBIN = 3, - // Random invocation policy: calls are forwarded to all registered callees - // in a random fashion. Callees that unregister are removed from the set - // of callees randomly drawn from to dispatch incoming calls. + /// Random invocation policy: calls are forwarded to all registered callees + /// in a random fashion. Callees that unregister are removed from the set + /// of callees randomly drawn from to dispatch incoming calls. RANDOM = 4 } -// Call cancel mode. +/// Call cancel mode. enum CancelMode: uint8 { - // Skip running invocation call. + /// Skip running invocation call. SKIP = 0, - // Abort running invocation and call. - ABORT = 1, + /// Kill running invocation and call. + KILL = 1, + + /// Kill running invocation and call without waiting. + KILLNOWAIT = 2 +} + + +/// WAMP transport channel type (`channel_type`). WAMP can run over any Transport which is message-based (requiring no WAMP transport channel framing) or stream-based (requiring WAMP transport channel framing), and bidirectional, reliable and ordered. +enum TransportChannelType: uint8 +{ + /// Not set or applicable. + NULL = 0, + + /// Host language native function call transport, e.g. inherently message-based already (function call). + FUNCTION = 1, + + /// Host run-time / OS level in-memory transport, e.g. (unframed / stream-based) memory buffer. + MEMORY = 2, + + /// Serial (UART) based transport (unframed / stream-based). + SERIAL = 3, + + /// TCP (non-TLS) based transport (unframed / stream-based). + TCP = 4, + + /// TLS (over TCP) based transport (unframed / stream-based). + TLS = 5 + + /// FUTURE (?): + /// - add WireGuard (over UDP) based transport. + /// - add QUIC (over UDP; using TLS 1.3 handshake messages; WebTransport browser API) based transport. + /// - add WebRTC data channels (SCTP over DTLS; RTCDataChannel browser API) based transport. + /// - add VirtIO (in-memory queues) based transport. +} - // Kill running invocation and call. - KILL = 2 + +/// WAMP transport channel framing (`channel_framing`). +enum TransportChannelFraming: uint8 +{ + /// Not set. + NULL = 0, + + /// Raw transport itself is inherently message-based already (e.g. FUNCTION or VIRTIO). + NATIVE = 1, + + /// Raw transport itself (e.g. TCP) is stream-based and channel framing applied is WebSocket (RFC6455). + WEBSOCKET = 2, + + /// Raw transport itself (e.g. TCP) is stream-based and channel framing applied is RawSocket (WAMP). + RAWSOCKET = 3 +} + + +/// WAMP transport channel serializer (`channel_serializer`). +enum TransportChannelSerializer: uint8 +{ + /// Not set or applicable. + NULL = 0, + + /// Use JSON serializer (for dynamically typed app payload). + JSON = 1, + + /// Use MsgPack serializer (for dynamically typed app payload). + MSGPACK = 2, + + /// Use CBOR serializer (for dynamically typed app payload). + CBOR = 3, + + /// Use UBJSON serializer (for dynamically typed app payload). + UBJSON = 4, + + /// Raw pass-through of app payload, uninterpreted in any way. + OPAQUE = 5, + + /// Use FlatBuffers serialized (statically typed) payload (https://google.github.io/flatbuffers/index.html). + FLATBUFFERS = 6, + + /// Use FlexBuffers serialized (dynamically typed) payload (https://google.github.io/flatbuffers/flexbuffers.html). + FLEXBUFFERS = 7 } diff --git a/autobahn/wamp/gen/schema/auth.bfbs b/autobahn/wamp/gen/schema/auth.bfbs index 7b991458b..f26535a5f 100644 Binary files a/autobahn/wamp/gen/schema/auth.bfbs and b/autobahn/wamp/gen/schema/auth.bfbs differ diff --git a/autobahn/wamp/gen/schema/pubsub.bfbs b/autobahn/wamp/gen/schema/pubsub.bfbs index dc90b5666..af29f7dcc 100644 Binary files a/autobahn/wamp/gen/schema/pubsub.bfbs and b/autobahn/wamp/gen/schema/pubsub.bfbs differ diff --git a/autobahn/wamp/gen/schema/roles.bfbs b/autobahn/wamp/gen/schema/roles.bfbs index e5a026aae..b4d7abf37 100644 Binary files a/autobahn/wamp/gen/schema/roles.bfbs and b/autobahn/wamp/gen/schema/roles.bfbs differ diff --git a/autobahn/wamp/gen/schema/rpc.bfbs b/autobahn/wamp/gen/schema/rpc.bfbs index 8f497a138..0ad5d13cf 100644 Binary files a/autobahn/wamp/gen/schema/rpc.bfbs and b/autobahn/wamp/gen/schema/rpc.bfbs differ diff --git a/autobahn/wamp/gen/schema/session.bfbs b/autobahn/wamp/gen/schema/session.bfbs index 7a98ce3a6..716b9365f 100644 Binary files a/autobahn/wamp/gen/schema/session.bfbs and b/autobahn/wamp/gen/schema/session.bfbs differ diff --git a/autobahn/wamp/gen/schema/types.bfbs b/autobahn/wamp/gen/schema/types.bfbs index 3db00ecbb..d958a5e16 100644 Binary files a/autobahn/wamp/gen/schema/types.bfbs and b/autobahn/wamp/gen/schema/types.bfbs differ diff --git a/autobahn/wamp/gen/schema/wamp.bfbs b/autobahn/wamp/gen/schema/wamp.bfbs index 6df7a9009..68fca07a6 100644 Binary files a/autobahn/wamp/gen/schema/wamp.bfbs and b/autobahn/wamp/gen/schema/wamp.bfbs differ diff --git a/autobahn/wamp/gen/wamp/Map.py b/autobahn/wamp/gen/wamp/Map.py index 7642c0a80..3097000d4 100644 --- a/autobahn/wamp/gen/wamp/Map.py +++ b/autobahn/wamp/gen/wamp/Map.py @@ -8,6 +8,7 @@ np = import_numpy() +# Simple mapping, from string keys to string values. class Map(object): __slots__ = ["_tab"] @@ -27,6 +28,8 @@ def GetRootAsMap(cls, buf, offset=0): def Init(self, buf, pos): self._tab = flatbuffers.table.Table(buf, pos) + # Key, which must match the regular expression `[A-Za-z_][A-Za-z0-9_]*`. That + # makes keys a valid identified in FlatBuffers (and most programming languages) # Map def Key(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4)) @@ -34,6 +37,7 @@ def Key(self): return self._tab.String(o + self._tab.Pos) return None + # Value, which may be any (UTF8 encoded Unicode) string. # Map def Value(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6)) diff --git a/autobahn/wamp/gen/wamp/Void.py b/autobahn/wamp/gen/wamp/Void.py index 3df3e6f9c..9c0754417 100644 --- a/autobahn/wamp/gen/wamp/Void.py +++ b/autobahn/wamp/gen/wamp/Void.py @@ -8,6 +8,7 @@ np = import_numpy() +# Void type (table based) for use with services. class Void(object): __slots__ = ["_tab"] diff --git a/autobahn/wamp/gen/wamp/proto/AuthMethod.py b/autobahn/wamp/gen/wamp/proto/AuthMethod.py index 6b198fb81..1148541fa 100644 --- a/autobahn/wamp/gen/wamp/proto/AuthMethod.py +++ b/autobahn/wamp/gen/wamp/proto/AuthMethod.py @@ -2,12 +2,15 @@ # namespace: proto - +# WAMP authentication method (see: WAMP protocol spec, "13. Authentication Methods"). class AuthMethod(object): - ANONYMOUS = 0 - COOKIE = 1 - TLS = 2 - TICKET = 3 - CRA = 4 - SCRAM = 5 - CRYPTOSIGN = 6 + # Not set / not authenticated: `anonymous`. + NULL = 0 + # WAMP Ticket authentication: `ticket` (see: WAMP protocol spec, "13.1. Ticket-based Authentication"). + TICKET = 1 + # WAMP Challenge-Response authentication: `wampcra` (see: WAMP protocol spec, "13.2. Challenge Response Authentication"). + CRA = 2 + # WAMP Salted Challenge Response authentication: `wamp-scram` (see: WAMP protocol spec, "13.3. Salted Challenge Response Authentication"). + SCRAM = 3 + # WAMP Cryptosign authentication: `cryptosign` (see: WAMP protocol spec, "13.4. Cryptosign-based Authentication"). + CRYPTOSIGN = 4 diff --git a/autobahn/wamp/gen/wamp/proto/AuthScramChallenge.py b/autobahn/wamp/gen/wamp/proto/AuthScramChallenge.py index 4dffaa14d..dd0b98871 100644 --- a/autobahn/wamp/gen/wamp/proto/AuthScramChallenge.py +++ b/autobahn/wamp/gen/wamp/proto/AuthScramChallenge.py @@ -46,7 +46,7 @@ def Kdf(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8)) if o != 0: return self._tab.Get(flatbuffers.number_types.Uint8Flags, o + self._tab.Pos) - return 2 + return 1 # AuthScramChallenge def Iterations(self): @@ -103,7 +103,7 @@ def AddSalt(builder, salt): def AuthScramChallengeAddKdf(builder, kdf): - builder.PrependUint8Slot(2, kdf, 2) + builder.PrependUint8Slot(2, kdf, 1) def AddKdf(builder, kdf): diff --git a/autobahn/wamp/gen/wamp/proto/Call.py b/autobahn/wamp/gen/wamp/proto/Call.py index 02fcc3b90..9d473cf57 100644 --- a/autobahn/wamp/gen/wamp/proto/Call.py +++ b/autobahn/wamp/gen/wamp/proto/Call.py @@ -145,53 +145,41 @@ def PayloadIsNone(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(14)) return o == 0 + # The specific scheme in use with Payload Passthru (PPT) mode for the application payload. # Call - def EncAlgo(self): + def PptScheme(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(16)) if o != 0: return self._tab.Get(flatbuffers.number_types.Uint8Flags, o + self._tab.Pos) return 0 + # The specific serializer encoding the application payload with the Payload Passthru (PPT) scheme in use. # Call - def EncSerializer(self): + def PptSerializer(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(18)) if o != 0: return self._tab.Get(flatbuffers.number_types.Uint8Flags, o + self._tab.Pos) return 0 + # The cryptographic algorithm ("cipher") encrypting the application payload with the Payload Passthru (PPT) scheme in use. # Call - def EncKey(self, j): + def PptCipher(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(20)) if o != 0: - a = self._tab.Vector(o) - return self._tab.Get( - flatbuffers.number_types.Uint8Flags, - a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 1), - ) - return 0 - - # Call - def EncKeyAsNumpy(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(20)) - if o != 0: - return self._tab.GetVectorAsNumpy(flatbuffers.number_types.Uint8Flags, o) + return self._tab.Get(flatbuffers.number_types.Uint8Flags, o + self._tab.Pos) return 0 + # The identifier or reference to the encryption key that was used to encrypt the payload with the Payload Passthru (PPT) scheme and cipher in use. # Call - def EncKeyLength(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(20)) + def PptKeyid(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(22)) if o != 0: - return self._tab.VectorLen(o) - return 0 - - # Call - def EncKeyIsNone(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(20)) - return o == 0 + return self._tab.String(o + self._tab.Pos) + return None # Call def Timeout(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(22)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(24)) if o != 0: return self._tab.Get( flatbuffers.number_types.Uint32Flags, o + self._tab.Pos @@ -200,7 +188,7 @@ def Timeout(self): # Call def ReceiveProgress(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(24)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(26)) if o != 0: return bool( self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos) @@ -209,14 +197,14 @@ def ReceiveProgress(self): # Call def TransactionHash(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(26)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(28)) if o != 0: return self._tab.String(o + self._tab.Pos) return None # Call def Caller(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(28)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(30)) if o != 0: return self._tab.Get( flatbuffers.number_types.Uint64Flags, o + self._tab.Pos @@ -225,25 +213,26 @@ def Caller(self): # Call def CallerAuthid(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(30)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(32)) if o != 0: return self._tab.String(o + self._tab.Pos) return None # Call def CallerAuthrole(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(32)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(34)) if o != 0: return self._tab.String(o + self._tab.Pos) return None # Call def ForwardFor(self, j): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(34)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(36)) if o != 0: x = self._tab.Vector(o) - x += flatbuffers.number_types.UOffsetTFlags.py_type(j) * 8 - from wamp.proto.Principal import Principal + x += flatbuffers.number_types.UOffsetTFlags.py_type(j) * 4 + x = self._tab.Indirect(x) + from autobahn.wamp.gen.wamp.proto.Principal import Principal obj = Principal() obj.Init(self._tab.Bytes, x) @@ -252,19 +241,19 @@ def ForwardFor(self, j): # Call def ForwardForLength(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(34)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(36)) if o != 0: return self._tab.VectorLen(o) return 0 # Call def ForwardForIsNone(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(34)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(36)) return o == 0 def CallStart(builder): - builder.StartObject(16) + builder.StartObject(17) def Start(builder): @@ -351,42 +340,42 @@ def StartPayloadVector(builder, numElems): return CallStartPayloadVector(builder, numElems) -def CallAddEncAlgo(builder, encAlgo): - builder.PrependUint8Slot(6, encAlgo, 0) +def CallAddPptScheme(builder, pptScheme): + builder.PrependUint8Slot(6, pptScheme, 0) -def AddEncAlgo(builder, encAlgo): - CallAddEncAlgo(builder, encAlgo) +def AddPptScheme(builder, pptScheme): + CallAddPptScheme(builder, pptScheme) -def CallAddEncSerializer(builder, encSerializer): - builder.PrependUint8Slot(7, encSerializer, 0) +def CallAddPptSerializer(builder, pptSerializer): + builder.PrependUint8Slot(7, pptSerializer, 0) -def AddEncSerializer(builder, encSerializer): - CallAddEncSerializer(builder, encSerializer) +def AddPptSerializer(builder, pptSerializer): + CallAddPptSerializer(builder, pptSerializer) -def CallAddEncKey(builder, encKey): - builder.PrependUOffsetTRelativeSlot( - 8, flatbuffers.number_types.UOffsetTFlags.py_type(encKey), 0 - ) +def CallAddPptCipher(builder, pptCipher): + builder.PrependUint8Slot(8, pptCipher, 0) -def AddEncKey(builder, encKey): - CallAddEncKey(builder, encKey) +def AddPptCipher(builder, pptCipher): + CallAddPptCipher(builder, pptCipher) -def CallStartEncKeyVector(builder, numElems): - return builder.StartVector(1, numElems, 1) +def CallAddPptKeyid(builder, pptKeyid): + builder.PrependUOffsetTRelativeSlot( + 9, flatbuffers.number_types.UOffsetTFlags.py_type(pptKeyid), 0 + ) -def StartEncKeyVector(builder, numElems): - return CallStartEncKeyVector(builder, numElems) +def AddPptKeyid(builder, pptKeyid): + CallAddPptKeyid(builder, pptKeyid) def CallAddTimeout(builder, timeout): - builder.PrependUint32Slot(9, timeout, 0) + builder.PrependUint32Slot(10, timeout, 0) def AddTimeout(builder, timeout): @@ -394,7 +383,7 @@ def AddTimeout(builder, timeout): def CallAddReceiveProgress(builder, receiveProgress): - builder.PrependBoolSlot(10, receiveProgress, 0) + builder.PrependBoolSlot(11, receiveProgress, 0) def AddReceiveProgress(builder, receiveProgress): @@ -403,7 +392,7 @@ def AddReceiveProgress(builder, receiveProgress): def CallAddTransactionHash(builder, transactionHash): builder.PrependUOffsetTRelativeSlot( - 11, flatbuffers.number_types.UOffsetTFlags.py_type(transactionHash), 0 + 12, flatbuffers.number_types.UOffsetTFlags.py_type(transactionHash), 0 ) @@ -412,7 +401,7 @@ def AddTransactionHash(builder, transactionHash): def CallAddCaller(builder, caller): - builder.PrependUint64Slot(12, caller, 0) + builder.PrependUint64Slot(13, caller, 0) def AddCaller(builder, caller): @@ -421,7 +410,7 @@ def AddCaller(builder, caller): def CallAddCallerAuthid(builder, callerAuthid): builder.PrependUOffsetTRelativeSlot( - 13, flatbuffers.number_types.UOffsetTFlags.py_type(callerAuthid), 0 + 14, flatbuffers.number_types.UOffsetTFlags.py_type(callerAuthid), 0 ) @@ -431,7 +420,7 @@ def AddCallerAuthid(builder, callerAuthid): def CallAddCallerAuthrole(builder, callerAuthrole): builder.PrependUOffsetTRelativeSlot( - 14, flatbuffers.number_types.UOffsetTFlags.py_type(callerAuthrole), 0 + 15, flatbuffers.number_types.UOffsetTFlags.py_type(callerAuthrole), 0 ) @@ -441,7 +430,7 @@ def AddCallerAuthrole(builder, callerAuthrole): def CallAddForwardFor(builder, forwardFor): builder.PrependUOffsetTRelativeSlot( - 15, flatbuffers.number_types.UOffsetTFlags.py_type(forwardFor), 0 + 16, flatbuffers.number_types.UOffsetTFlags.py_type(forwardFor), 0 ) @@ -450,7 +439,7 @@ def AddForwardFor(builder, forwardFor): def CallStartForwardForVector(builder, numElems): - return builder.StartVector(8, numElems, 8) + return builder.StartVector(4, numElems, 4) def StartForwardForVector(builder, numElems): diff --git a/autobahn/wamp/gen/wamp/proto/Cancel.py b/autobahn/wamp/gen/wamp/proto/Cancel.py index edb74e667..757c785b2 100644 --- a/autobahn/wamp/gen/wamp/proto/Cancel.py +++ b/autobahn/wamp/gen/wamp/proto/Cancel.py @@ -57,8 +57,9 @@ def ForwardFor(self, j): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10)) if o != 0: x = self._tab.Vector(o) - x += flatbuffers.number_types.UOffsetTFlags.py_type(j) * 8 - from wamp.proto.Principal import Principal + x += flatbuffers.number_types.UOffsetTFlags.py_type(j) * 4 + x = self._tab.Indirect(x) + from autobahn.wamp.gen.wamp.proto.Principal import Principal obj = Principal() obj.Init(self._tab.Bytes, x) @@ -121,7 +122,7 @@ def AddForwardFor(builder, forwardFor): def CancelStartForwardForVector(builder, numElems): - return builder.StartVector(8, numElems, 8) + return builder.StartVector(4, numElems, 4) def StartForwardForVector(builder, numElems): diff --git a/autobahn/wamp/gen/wamp/proto/CancelMode.py b/autobahn/wamp/gen/wamp/proto/CancelMode.py index c9876a37f..968e1f905 100644 --- a/autobahn/wamp/gen/wamp/proto/CancelMode.py +++ b/autobahn/wamp/gen/wamp/proto/CancelMode.py @@ -2,8 +2,11 @@ # namespace: proto - +# Call cancel mode. class CancelMode(object): + # Skip running invocation call. SKIP = 0 - ABORT = 1 - KILL = 2 + # Kill running invocation and call. + KILL = 1 + # Kill running invocation and call without waiting. + KILLNOWAIT = 2 diff --git a/autobahn/wamp/gen/wamp/proto/ClientRoles.py b/autobahn/wamp/gen/wamp/proto/ClientRoles.py index a83d0f6db..3f74676d8 100644 --- a/autobahn/wamp/gen/wamp/proto/ClientRoles.py +++ b/autobahn/wamp/gen/wamp/proto/ClientRoles.py @@ -32,7 +32,7 @@ def Publisher(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4)) if o != 0: x = self._tab.Indirect(o + self._tab.Pos) - from wamp.proto.PublisherFeatures import PublisherFeatures + from autobahn.wamp.gen.wamp.proto.PublisherFeatures import PublisherFeatures obj = PublisherFeatures() obj.Init(self._tab.Bytes, x) @@ -44,7 +44,9 @@ def Subscriber(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6)) if o != 0: x = self._tab.Indirect(o + self._tab.Pos) - from wamp.proto.SubscriberFeatures import SubscriberFeatures + from autobahn.wamp.gen.wamp.proto.SubscriberFeatures import ( + SubscriberFeatures, + ) obj = SubscriberFeatures() obj.Init(self._tab.Bytes, x) @@ -56,7 +58,7 @@ def Caller(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8)) if o != 0: x = self._tab.Indirect(o + self._tab.Pos) - from wamp.proto.CallerFeatures import CallerFeatures + from autobahn.wamp.gen.wamp.proto.CallerFeatures import CallerFeatures obj = CallerFeatures() obj.Init(self._tab.Bytes, x) @@ -68,7 +70,7 @@ def Callee(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10)) if o != 0: x = self._tab.Indirect(o + self._tab.Pos) - from wamp.proto.CalleeFeatures import CalleeFeatures + from autobahn.wamp.gen.wamp.proto.CalleeFeatures import CalleeFeatures obj = CalleeFeatures() obj.Init(self._tab.Bytes, x) diff --git a/autobahn/wamp/gen/wamp/proto/Error.py b/autobahn/wamp/gen/wamp/proto/Error.py index 62eb43e2d..c9a26c3b8 100644 --- a/autobahn/wamp/gen/wamp/proto/Error.py +++ b/autobahn/wamp/gen/wamp/proto/Error.py @@ -154,57 +154,46 @@ def PayloadIsNone(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(16)) return o == 0 + # The specific scheme in use with Payload Passthru (PPT) mode for the application payload. # Error - def EncAlgo(self): + def PptScheme(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(18)) if o != 0: return self._tab.Get(flatbuffers.number_types.Uint8Flags, o + self._tab.Pos) return 0 + # The specific serializer encoding the application payload with the Payload Passthru (PPT) scheme in use. # Error - def EncSerializer(self): + def PptSerializer(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(20)) if o != 0: return self._tab.Get(flatbuffers.number_types.Uint8Flags, o + self._tab.Pos) return 0 + # The cryptographic algorithm ("cipher") encrypting the application payload with the Payload Passthru (PPT) scheme in use. # Error - def EncKey(self, j): + def PptCipher(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(22)) if o != 0: - a = self._tab.Vector(o) - return self._tab.Get( - flatbuffers.number_types.Uint8Flags, - a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 1), - ) - return 0 - - # Error - def EncKeyAsNumpy(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(22)) - if o != 0: - return self._tab.GetVectorAsNumpy(flatbuffers.number_types.Uint8Flags, o) + return self._tab.Get(flatbuffers.number_types.Uint8Flags, o + self._tab.Pos) return 0 + # The identifier or reference to the encryption key that was used to encrypt the payload with the Payload Passthru (PPT) scheme and cipher in use. # Error - def EncKeyLength(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(22)) + def PptKeyid(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(24)) if o != 0: - return self._tab.VectorLen(o) - return 0 - - # Error - def EncKeyIsNone(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(22)) - return o == 0 + return self._tab.String(o + self._tab.Pos) + return None # Error def ForwardFor(self, j): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(24)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(26)) if o != 0: x = self._tab.Vector(o) - x += flatbuffers.number_types.UOffsetTFlags.py_type(j) * 8 - from wamp.proto.Principal import Principal + x += flatbuffers.number_types.UOffsetTFlags.py_type(j) * 4 + x = self._tab.Indirect(x) + from autobahn.wamp.gen.wamp.proto.Principal import Principal obj = Principal() obj.Init(self._tab.Bytes, x) @@ -213,19 +202,19 @@ def ForwardFor(self, j): # Error def ForwardForLength(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(24)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(26)) if o != 0: return self._tab.VectorLen(o) return 0 # Error def ForwardForIsNone(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(24)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(26)) return o == 0 def ErrorStart(builder): - builder.StartObject(11) + builder.StartObject(12) def Start(builder): @@ -320,43 +309,43 @@ def StartPayloadVector(builder, numElems): return ErrorStartPayloadVector(builder, numElems) -def ErrorAddEncAlgo(builder, encAlgo): - builder.PrependUint8Slot(7, encAlgo, 0) +def ErrorAddPptScheme(builder, pptScheme): + builder.PrependUint8Slot(7, pptScheme, 0) -def AddEncAlgo(builder, encAlgo): - ErrorAddEncAlgo(builder, encAlgo) +def AddPptScheme(builder, pptScheme): + ErrorAddPptScheme(builder, pptScheme) -def ErrorAddEncSerializer(builder, encSerializer): - builder.PrependUint8Slot(8, encSerializer, 0) +def ErrorAddPptSerializer(builder, pptSerializer): + builder.PrependUint8Slot(8, pptSerializer, 0) -def AddEncSerializer(builder, encSerializer): - ErrorAddEncSerializer(builder, encSerializer) +def AddPptSerializer(builder, pptSerializer): + ErrorAddPptSerializer(builder, pptSerializer) -def ErrorAddEncKey(builder, encKey): - builder.PrependUOffsetTRelativeSlot( - 9, flatbuffers.number_types.UOffsetTFlags.py_type(encKey), 0 - ) +def ErrorAddPptCipher(builder, pptCipher): + builder.PrependUint8Slot(9, pptCipher, 0) -def AddEncKey(builder, encKey): - ErrorAddEncKey(builder, encKey) +def AddPptCipher(builder, pptCipher): + ErrorAddPptCipher(builder, pptCipher) -def ErrorStartEncKeyVector(builder, numElems): - return builder.StartVector(1, numElems, 1) +def ErrorAddPptKeyid(builder, pptKeyid): + builder.PrependUOffsetTRelativeSlot( + 10, flatbuffers.number_types.UOffsetTFlags.py_type(pptKeyid), 0 + ) -def StartEncKeyVector(builder, numElems): - return ErrorStartEncKeyVector(builder, numElems) +def AddPptKeyid(builder, pptKeyid): + ErrorAddPptKeyid(builder, pptKeyid) def ErrorAddForwardFor(builder, forwardFor): builder.PrependUOffsetTRelativeSlot( - 10, flatbuffers.number_types.UOffsetTFlags.py_type(forwardFor), 0 + 11, flatbuffers.number_types.UOffsetTFlags.py_type(forwardFor), 0 ) @@ -365,7 +354,7 @@ def AddForwardFor(builder, forwardFor): def ErrorStartForwardForVector(builder, numElems): - return builder.StartVector(8, numElems, 8) + return builder.StartVector(4, numElems, 4) def StartForwardForVector(builder, numElems): diff --git a/autobahn/wamp/gen/wamp/proto/Event.py b/autobahn/wamp/gen/wamp/proto/Event.py index 3b4381c9d..41e1120dd 100644 --- a/autobahn/wamp/gen/wamp/proto/Event.py +++ b/autobahn/wamp/gen/wamp/proto/Event.py @@ -147,53 +147,41 @@ def PayloadIsNone(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(14)) return o == 0 + # The specific scheme in use with Payload Passthru (PPT) mode for the application payload. # Event - def EncAlgo(self): + def PptScheme(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(16)) if o != 0: return self._tab.Get(flatbuffers.number_types.Uint8Flags, o + self._tab.Pos) return 0 + # The specific serializer encoding the application payload with the Payload Passthru (PPT) scheme in use. # Event - def EncSerializer(self): + def PptSerializer(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(18)) if o != 0: return self._tab.Get(flatbuffers.number_types.Uint8Flags, o + self._tab.Pos) return 0 + # The cryptographic algorithm ("cipher") encrypting the application payload with the Payload Passthru (PPT) scheme in use. # Event - def EncKey(self, j): + def PptCipher(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(20)) if o != 0: - a = self._tab.Vector(o) - return self._tab.Get( - flatbuffers.number_types.Uint8Flags, - a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 1), - ) - return 0 - - # Event - def EncKeyAsNumpy(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(20)) - if o != 0: - return self._tab.GetVectorAsNumpy(flatbuffers.number_types.Uint8Flags, o) + return self._tab.Get(flatbuffers.number_types.Uint8Flags, o + self._tab.Pos) return 0 + # The identifier or reference to the encryption key that was used to encrypt the payload with the Payload Passthru (PPT) scheme and cipher in use. # Event - def EncKeyLength(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(20)) + def PptKeyid(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(22)) if o != 0: - return self._tab.VectorLen(o) - return 0 - - # Event - def EncKeyIsNone(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(20)) - return o == 0 + return self._tab.String(o + self._tab.Pos) + return None # Event def Publisher(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(22)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(24)) if o != 0: return self._tab.Get( flatbuffers.number_types.Uint64Flags, o + self._tab.Pos @@ -202,28 +190,28 @@ def Publisher(self): # Event def PublisherAuthid(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(24)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(26)) if o != 0: return self._tab.String(o + self._tab.Pos) return None # Event def PublisherAuthrole(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(26)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(28)) if o != 0: return self._tab.String(o + self._tab.Pos) return None # Event def Topic(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(28)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(30)) if o != 0: return self._tab.String(o + self._tab.Pos) return None # Event def Retained(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(30)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(32)) if o != 0: return bool( self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos) @@ -232,14 +220,14 @@ def Retained(self): # Event def TransactionHash(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(32)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(34)) if o != 0: return self._tab.String(o + self._tab.Pos) return None # Event def Acknowledge(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(34)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(36)) if o != 0: return bool( self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos) @@ -248,11 +236,12 @@ def Acknowledge(self): # Event def ForwardFor(self, j): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(36)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(38)) if o != 0: x = self._tab.Vector(o) - x += flatbuffers.number_types.UOffsetTFlags.py_type(j) * 8 - from wamp.proto.Principal import Principal + x += flatbuffers.number_types.UOffsetTFlags.py_type(j) * 4 + x = self._tab.Indirect(x) + from autobahn.wamp.gen.wamp.proto.Principal import Principal obj = Principal() obj.Init(self._tab.Bytes, x) @@ -261,19 +250,19 @@ def ForwardFor(self, j): # Event def ForwardForLength(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(36)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(38)) if o != 0: return self._tab.VectorLen(o) return 0 # Event def ForwardForIsNone(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(36)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(38)) return o == 0 def EventStart(builder): - builder.StartObject(17) + builder.StartObject(18) def Start(builder): @@ -358,42 +347,42 @@ def StartPayloadVector(builder, numElems): return EventStartPayloadVector(builder, numElems) -def EventAddEncAlgo(builder, encAlgo): - builder.PrependUint8Slot(6, encAlgo, 0) +def EventAddPptScheme(builder, pptScheme): + builder.PrependUint8Slot(6, pptScheme, 0) -def AddEncAlgo(builder, encAlgo): - EventAddEncAlgo(builder, encAlgo) +def AddPptScheme(builder, pptScheme): + EventAddPptScheme(builder, pptScheme) -def EventAddEncSerializer(builder, encSerializer): - builder.PrependUint8Slot(7, encSerializer, 0) +def EventAddPptSerializer(builder, pptSerializer): + builder.PrependUint8Slot(7, pptSerializer, 0) -def AddEncSerializer(builder, encSerializer): - EventAddEncSerializer(builder, encSerializer) +def AddPptSerializer(builder, pptSerializer): + EventAddPptSerializer(builder, pptSerializer) -def EventAddEncKey(builder, encKey): - builder.PrependUOffsetTRelativeSlot( - 8, flatbuffers.number_types.UOffsetTFlags.py_type(encKey), 0 - ) +def EventAddPptCipher(builder, pptCipher): + builder.PrependUint8Slot(8, pptCipher, 0) -def AddEncKey(builder, encKey): - EventAddEncKey(builder, encKey) +def AddPptCipher(builder, pptCipher): + EventAddPptCipher(builder, pptCipher) -def EventStartEncKeyVector(builder, numElems): - return builder.StartVector(1, numElems, 1) +def EventAddPptKeyid(builder, pptKeyid): + builder.PrependUOffsetTRelativeSlot( + 9, flatbuffers.number_types.UOffsetTFlags.py_type(pptKeyid), 0 + ) -def StartEncKeyVector(builder, numElems): - return EventStartEncKeyVector(builder, numElems) +def AddPptKeyid(builder, pptKeyid): + EventAddPptKeyid(builder, pptKeyid) def EventAddPublisher(builder, publisher): - builder.PrependUint64Slot(9, publisher, 0) + builder.PrependUint64Slot(10, publisher, 0) def AddPublisher(builder, publisher): @@ -402,7 +391,7 @@ def AddPublisher(builder, publisher): def EventAddPublisherAuthid(builder, publisherAuthid): builder.PrependUOffsetTRelativeSlot( - 10, flatbuffers.number_types.UOffsetTFlags.py_type(publisherAuthid), 0 + 11, flatbuffers.number_types.UOffsetTFlags.py_type(publisherAuthid), 0 ) @@ -412,7 +401,7 @@ def AddPublisherAuthid(builder, publisherAuthid): def EventAddPublisherAuthrole(builder, publisherAuthrole): builder.PrependUOffsetTRelativeSlot( - 11, flatbuffers.number_types.UOffsetTFlags.py_type(publisherAuthrole), 0 + 12, flatbuffers.number_types.UOffsetTFlags.py_type(publisherAuthrole), 0 ) @@ -422,7 +411,7 @@ def AddPublisherAuthrole(builder, publisherAuthrole): def EventAddTopic(builder, topic): builder.PrependUOffsetTRelativeSlot( - 12, flatbuffers.number_types.UOffsetTFlags.py_type(topic), 0 + 13, flatbuffers.number_types.UOffsetTFlags.py_type(topic), 0 ) @@ -431,7 +420,7 @@ def AddTopic(builder, topic): def EventAddRetained(builder, retained): - builder.PrependBoolSlot(13, retained, 0) + builder.PrependBoolSlot(14, retained, 0) def AddRetained(builder, retained): @@ -440,7 +429,7 @@ def AddRetained(builder, retained): def EventAddTransactionHash(builder, transactionHash): builder.PrependUOffsetTRelativeSlot( - 14, flatbuffers.number_types.UOffsetTFlags.py_type(transactionHash), 0 + 15, flatbuffers.number_types.UOffsetTFlags.py_type(transactionHash), 0 ) @@ -449,7 +438,7 @@ def AddTransactionHash(builder, transactionHash): def EventAddAcknowledge(builder, acknowledge): - builder.PrependBoolSlot(15, acknowledge, 0) + builder.PrependBoolSlot(16, acknowledge, 0) def AddAcknowledge(builder, acknowledge): @@ -458,7 +447,7 @@ def AddAcknowledge(builder, acknowledge): def EventAddForwardFor(builder, forwardFor): builder.PrependUOffsetTRelativeSlot( - 16, flatbuffers.number_types.UOffsetTFlags.py_type(forwardFor), 0 + 17, flatbuffers.number_types.UOffsetTFlags.py_type(forwardFor), 0 ) @@ -467,7 +456,7 @@ def AddForwardFor(builder, forwardFor): def EventStartForwardForVector(builder, numElems): - return builder.StartVector(8, numElems, 8) + return builder.StartVector(4, numElems, 4) def StartForwardForVector(builder, numElems): diff --git a/autobahn/wamp/gen/wamp/proto/EventReceived.py b/autobahn/wamp/gen/wamp/proto/EventReceived.py index f3014c5b1..5beba8f7e 100644 --- a/autobahn/wamp/gen/wamp/proto/EventReceived.py +++ b/autobahn/wamp/gen/wamp/proto/EventReceived.py @@ -46,82 +46,34 @@ def Publication(self): return 0 # EventReceived - def Payload(self, j): + def ForwardFor(self, j): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8)) if o != 0: - a = self._tab.Vector(o) - return self._tab.Get( - flatbuffers.number_types.Uint8Flags, - a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 1), - ) - return 0 + x = self._tab.Vector(o) + x += flatbuffers.number_types.UOffsetTFlags.py_type(j) * 4 + x = self._tab.Indirect(x) + from autobahn.wamp.gen.wamp.proto.Principal import Principal - # EventReceived - def PayloadAsNumpy(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8)) - if o != 0: - return self._tab.GetVectorAsNumpy(flatbuffers.number_types.Uint8Flags, o) - return 0 + obj = Principal() + obj.Init(self._tab.Bytes, x) + return obj + return None # EventReceived - def PayloadLength(self): + def ForwardForLength(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8)) if o != 0: return self._tab.VectorLen(o) return 0 # EventReceived - def PayloadIsNone(self): + def ForwardForIsNone(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8)) return o == 0 - # EventReceived - def EncAlgo(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10)) - if o != 0: - return self._tab.Get(flatbuffers.number_types.Uint8Flags, o + self._tab.Pos) - return 0 - - # EventReceived - def EncSerializer(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12)) - if o != 0: - return self._tab.Get(flatbuffers.number_types.Uint8Flags, o + self._tab.Pos) - return 0 - - # EventReceived - def EncKey(self, j): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(14)) - if o != 0: - a = self._tab.Vector(o) - return self._tab.Get( - flatbuffers.number_types.Uint8Flags, - a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 1), - ) - return 0 - - # EventReceived - def EncKeyAsNumpy(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(14)) - if o != 0: - return self._tab.GetVectorAsNumpy(flatbuffers.number_types.Uint8Flags, o) - return 0 - - # EventReceived - def EncKeyLength(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(14)) - if o != 0: - return self._tab.VectorLen(o) - return 0 - - # EventReceived - def EncKeyIsNone(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(14)) - return o == 0 - def EventReceivedStart(builder): - builder.StartObject(6) + builder.StartObject(3) def Start(builder): @@ -144,56 +96,22 @@ def AddPublication(builder, publication): EventReceivedAddPublication(builder, publication) -def EventReceivedAddPayload(builder, payload): - builder.PrependUOffsetTRelativeSlot( - 2, flatbuffers.number_types.UOffsetTFlags.py_type(payload), 0 - ) - - -def AddPayload(builder, payload): - EventReceivedAddPayload(builder, payload) - - -def EventReceivedStartPayloadVector(builder, numElems): - return builder.StartVector(1, numElems, 1) - - -def StartPayloadVector(builder, numElems): - return EventReceivedStartPayloadVector(builder, numElems) - - -def EventReceivedAddEncAlgo(builder, encAlgo): - builder.PrependUint8Slot(3, encAlgo, 0) - - -def AddEncAlgo(builder, encAlgo): - EventReceivedAddEncAlgo(builder, encAlgo) - - -def EventReceivedAddEncSerializer(builder, encSerializer): - builder.PrependUint8Slot(4, encSerializer, 0) - - -def AddEncSerializer(builder, encSerializer): - EventReceivedAddEncSerializer(builder, encSerializer) - - -def EventReceivedAddEncKey(builder, encKey): +def EventReceivedAddForwardFor(builder, forwardFor): builder.PrependUOffsetTRelativeSlot( - 5, flatbuffers.number_types.UOffsetTFlags.py_type(encKey), 0 + 2, flatbuffers.number_types.UOffsetTFlags.py_type(forwardFor), 0 ) -def AddEncKey(builder, encKey): - EventReceivedAddEncKey(builder, encKey) +def AddForwardFor(builder, forwardFor): + EventReceivedAddForwardFor(builder, forwardFor) -def EventReceivedStartEncKeyVector(builder, numElems): - return builder.StartVector(1, numElems, 1) +def EventReceivedStartForwardForVector(builder, numElems): + return builder.StartVector(4, numElems, 4) -def StartEncKeyVector(builder, numElems): - return EventReceivedStartEncKeyVector(builder, numElems) +def StartForwardForVector(builder, numElems): + return EventReceivedStartForwardForVector(builder, numElems) def EventReceivedEnd(builder): diff --git a/autobahn/wamp/gen/wamp/proto/Hello.py b/autobahn/wamp/gen/wamp/proto/Hello.py index 25cdcaab3..e663e34d0 100644 --- a/autobahn/wamp/gen/wamp/proto/Hello.py +++ b/autobahn/wamp/gen/wamp/proto/Hello.py @@ -41,7 +41,7 @@ def Roles(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6)) if o != 0: x = self._tab.Indirect(o + self._tab.Pos) - from wamp.proto.ClientRoles import ClientRoles + from autobahn.wamp.gen.wamp.proto.ClientRoles import ClientRoles obj = ClientRoles() obj.Init(self._tab.Bytes, x) diff --git a/autobahn/wamp/gen/wamp/proto/HelloNew.py b/autobahn/wamp/gen/wamp/proto/HelloNew.py index bf2fb02b5..48e093d87 100644 --- a/autobahn/wamp/gen/wamp/proto/HelloNew.py +++ b/autobahn/wamp/gen/wamp/proto/HelloNew.py @@ -32,7 +32,7 @@ def Roles(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4)) if o != 0: x = self._tab.Indirect(o + self._tab.Pos) - from wamp.proto.ClientRoles import ClientRoles + from autobahn.wamp.gen.wamp.proto.ClientRoles import ClientRoles obj = ClientRoles() obj.Init(self._tab.Bytes, x) diff --git a/autobahn/wamp/gen/wamp/proto/Interrupt.py b/autobahn/wamp/gen/wamp/proto/Interrupt.py index 337565be2..3bae86261 100644 --- a/autobahn/wamp/gen/wamp/proto/Interrupt.py +++ b/autobahn/wamp/gen/wamp/proto/Interrupt.py @@ -64,8 +64,9 @@ def ForwardFor(self, j): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12)) if o != 0: x = self._tab.Vector(o) - x += flatbuffers.number_types.UOffsetTFlags.py_type(j) * 8 - from wamp.proto.Principal import Principal + x += flatbuffers.number_types.UOffsetTFlags.py_type(j) * 4 + x = self._tab.Indirect(x) + from autobahn.wamp.gen.wamp.proto.Principal import Principal obj = Principal() obj.Init(self._tab.Bytes, x) @@ -138,7 +139,7 @@ def AddForwardFor(builder, forwardFor): def InterruptStartForwardForVector(builder, numElems): - return builder.StartVector(8, numElems, 8) + return builder.StartVector(4, numElems, 4) def StartForwardForVector(builder, numElems): diff --git a/autobahn/wamp/gen/wamp/proto/Invocation.py b/autobahn/wamp/gen/wamp/proto/Invocation.py index 4bc18a785..4e1afe63a 100644 --- a/autobahn/wamp/gen/wamp/proto/Invocation.py +++ b/autobahn/wamp/gen/wamp/proto/Invocation.py @@ -147,60 +147,48 @@ def PayloadIsNone(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(14)) return o == 0 + # The specific scheme in use with Payload Passthru (PPT) mode for the application payload. # Invocation - def EncAlgo(self): + def PptScheme(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(16)) if o != 0: return self._tab.Get(flatbuffers.number_types.Uint8Flags, o + self._tab.Pos) return 0 + # The specific serializer encoding the application payload with the Payload Passthru (PPT) scheme in use. # Invocation - def EncSerializer(self): + def PptSerializer(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(18)) if o != 0: return self._tab.Get(flatbuffers.number_types.Uint8Flags, o + self._tab.Pos) return 0 + # The cryptographic algorithm ("cipher") encrypting the application payload with the Payload Passthru (PPT) scheme in use. # Invocation - def EncKey(self, j): + def PptCipher(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(20)) if o != 0: - a = self._tab.Vector(o) - return self._tab.Get( - flatbuffers.number_types.Uint8Flags, - a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 1), - ) - return 0 - - # Invocation - def EncKeyAsNumpy(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(20)) - if o != 0: - return self._tab.GetVectorAsNumpy(flatbuffers.number_types.Uint8Flags, o) + return self._tab.Get(flatbuffers.number_types.Uint8Flags, o + self._tab.Pos) return 0 + # The identifier or reference to the encryption key that was used to encrypt the payload with the Payload Passthru (PPT) scheme and cipher in use. # Invocation - def EncKeyLength(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(20)) + def PptKeyid(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(22)) if o != 0: - return self._tab.VectorLen(o) - return 0 - - # Invocation - def EncKeyIsNone(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(20)) - return o == 0 + return self._tab.String(o + self._tab.Pos) + return None # Invocation def Procedure(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(22)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(24)) if o != 0: return self._tab.String(o + self._tab.Pos) return None # Invocation def Timeout(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(24)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(26)) if o != 0: return self._tab.Get( flatbuffers.number_types.Uint32Flags, o + self._tab.Pos @@ -209,7 +197,7 @@ def Timeout(self): # Invocation def ReceiveProgress(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(26)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(28)) if o != 0: return bool( self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos) @@ -218,14 +206,14 @@ def ReceiveProgress(self): # Invocation def TransactionHash(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(28)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(30)) if o != 0: return self._tab.String(o + self._tab.Pos) return None # Invocation def Caller(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(30)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(32)) if o != 0: return self._tab.Get( flatbuffers.number_types.Uint64Flags, o + self._tab.Pos @@ -234,25 +222,26 @@ def Caller(self): # Invocation def CallerAuthid(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(32)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(34)) if o != 0: return self._tab.String(o + self._tab.Pos) return None # Invocation def CallerAuthrole(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(34)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(36)) if o != 0: return self._tab.String(o + self._tab.Pos) return None # Invocation def ForwardFor(self, j): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(36)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(38)) if o != 0: x = self._tab.Vector(o) - x += flatbuffers.number_types.UOffsetTFlags.py_type(j) * 8 - from wamp.proto.Principal import Principal + x += flatbuffers.number_types.UOffsetTFlags.py_type(j) * 4 + x = self._tab.Indirect(x) + from autobahn.wamp.gen.wamp.proto.Principal import Principal obj = Principal() obj.Init(self._tab.Bytes, x) @@ -261,19 +250,19 @@ def ForwardFor(self, j): # Invocation def ForwardForLength(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(36)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(38)) if o != 0: return self._tab.VectorLen(o) return 0 # Invocation def ForwardForIsNone(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(36)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(38)) return o == 0 def InvocationStart(builder): - builder.StartObject(17) + builder.StartObject(18) def Start(builder): @@ -358,43 +347,43 @@ def StartPayloadVector(builder, numElems): return InvocationStartPayloadVector(builder, numElems) -def InvocationAddEncAlgo(builder, encAlgo): - builder.PrependUint8Slot(6, encAlgo, 0) +def InvocationAddPptScheme(builder, pptScheme): + builder.PrependUint8Slot(6, pptScheme, 0) -def AddEncAlgo(builder, encAlgo): - InvocationAddEncAlgo(builder, encAlgo) +def AddPptScheme(builder, pptScheme): + InvocationAddPptScheme(builder, pptScheme) -def InvocationAddEncSerializer(builder, encSerializer): - builder.PrependUint8Slot(7, encSerializer, 0) +def InvocationAddPptSerializer(builder, pptSerializer): + builder.PrependUint8Slot(7, pptSerializer, 0) -def AddEncSerializer(builder, encSerializer): - InvocationAddEncSerializer(builder, encSerializer) +def AddPptSerializer(builder, pptSerializer): + InvocationAddPptSerializer(builder, pptSerializer) -def InvocationAddEncKey(builder, encKey): - builder.PrependUOffsetTRelativeSlot( - 8, flatbuffers.number_types.UOffsetTFlags.py_type(encKey), 0 - ) +def InvocationAddPptCipher(builder, pptCipher): + builder.PrependUint8Slot(8, pptCipher, 0) -def AddEncKey(builder, encKey): - InvocationAddEncKey(builder, encKey) +def AddPptCipher(builder, pptCipher): + InvocationAddPptCipher(builder, pptCipher) -def InvocationStartEncKeyVector(builder, numElems): - return builder.StartVector(1, numElems, 1) +def InvocationAddPptKeyid(builder, pptKeyid): + builder.PrependUOffsetTRelativeSlot( + 9, flatbuffers.number_types.UOffsetTFlags.py_type(pptKeyid), 0 + ) -def StartEncKeyVector(builder, numElems): - return InvocationStartEncKeyVector(builder, numElems) +def AddPptKeyid(builder, pptKeyid): + InvocationAddPptKeyid(builder, pptKeyid) def InvocationAddProcedure(builder, procedure): builder.PrependUOffsetTRelativeSlot( - 9, flatbuffers.number_types.UOffsetTFlags.py_type(procedure), 0 + 10, flatbuffers.number_types.UOffsetTFlags.py_type(procedure), 0 ) @@ -403,7 +392,7 @@ def AddProcedure(builder, procedure): def InvocationAddTimeout(builder, timeout): - builder.PrependUint32Slot(10, timeout, 0) + builder.PrependUint32Slot(11, timeout, 0) def AddTimeout(builder, timeout): @@ -411,7 +400,7 @@ def AddTimeout(builder, timeout): def InvocationAddReceiveProgress(builder, receiveProgress): - builder.PrependBoolSlot(11, receiveProgress, 0) + builder.PrependBoolSlot(12, receiveProgress, 0) def AddReceiveProgress(builder, receiveProgress): @@ -420,7 +409,7 @@ def AddReceiveProgress(builder, receiveProgress): def InvocationAddTransactionHash(builder, transactionHash): builder.PrependUOffsetTRelativeSlot( - 12, flatbuffers.number_types.UOffsetTFlags.py_type(transactionHash), 0 + 13, flatbuffers.number_types.UOffsetTFlags.py_type(transactionHash), 0 ) @@ -429,7 +418,7 @@ def AddTransactionHash(builder, transactionHash): def InvocationAddCaller(builder, caller): - builder.PrependUint64Slot(13, caller, 0) + builder.PrependUint64Slot(14, caller, 0) def AddCaller(builder, caller): @@ -438,7 +427,7 @@ def AddCaller(builder, caller): def InvocationAddCallerAuthid(builder, callerAuthid): builder.PrependUOffsetTRelativeSlot( - 14, flatbuffers.number_types.UOffsetTFlags.py_type(callerAuthid), 0 + 15, flatbuffers.number_types.UOffsetTFlags.py_type(callerAuthid), 0 ) @@ -448,7 +437,7 @@ def AddCallerAuthid(builder, callerAuthid): def InvocationAddCallerAuthrole(builder, callerAuthrole): builder.PrependUOffsetTRelativeSlot( - 15, flatbuffers.number_types.UOffsetTFlags.py_type(callerAuthrole), 0 + 16, flatbuffers.number_types.UOffsetTFlags.py_type(callerAuthrole), 0 ) @@ -458,7 +447,7 @@ def AddCallerAuthrole(builder, callerAuthrole): def InvocationAddForwardFor(builder, forwardFor): builder.PrependUOffsetTRelativeSlot( - 16, flatbuffers.number_types.UOffsetTFlags.py_type(forwardFor), 0 + 17, flatbuffers.number_types.UOffsetTFlags.py_type(forwardFor), 0 ) @@ -467,7 +456,7 @@ def AddForwardFor(builder, forwardFor): def InvocationStartForwardForVector(builder, numElems): - return builder.StartVector(8, numElems, 8) + return builder.StartVector(4, numElems, 4) def StartForwardForVector(builder, numElems): diff --git a/autobahn/wamp/gen/wamp/proto/InvocationPolicy.py b/autobahn/wamp/gen/wamp/proto/InvocationPolicy.py index c7d05da2a..2ed299eec 100644 --- a/autobahn/wamp/gen/wamp/proto/InvocationPolicy.py +++ b/autobahn/wamp/gen/wamp/proto/InvocationPolicy.py @@ -2,10 +2,25 @@ # namespace: proto - +# Remote procedure invocation policy, for use with shared registrations. class InvocationPolicy(object): + # Standard invocation policy: a procedure may only be registered by at most + # one callee at a time. This is the default. SINGLE = 0 + # First callee invocation policy: all calls to a procedure are forwarded to + # the callee that first registered. Should that callee unregister, calls + # are forwarded to the callee that registered next after the former. FIRST = 1 + # Last callee invocation policy: all calls to a procedure are forwwrded to + # the callee that last registered. Should that callee unregister, calls + # are forwarded to the callee that registered next before the former. LAST = 2 + # Round-robin invocation policy: calls are forward to all registered callees + # in a round-robin fashion. Callees that unregister are removed from the + # round-robin list, and new callees registering will get calls immediately + # in the next dispatching round. ROUNDROBIN = 3 + # Random invocation policy: calls are forwarded to all registered callees + # in a random fashion. Callees that unregister are removed from the set + # of callees randomly drawn from to dispatch incoming calls. RANDOM = 4 diff --git a/autobahn/wamp/gen/wamp/proto/KDF.py b/autobahn/wamp/gen/wamp/proto/KDF.py new file mode 100644 index 000000000..0704be17b --- /dev/null +++ b/autobahn/wamp/gen/wamp/proto/KDF.py @@ -0,0 +1,12 @@ +# automatically generated by the FlatBuffers compiler, do not modify + +# namespace: proto + +# Key Derivation Functions, e.g. WAMP SCRAM uses a password-based key derivation function (KDF) to hash user passwords. WAMP-SCRAM supports both Argon2 and PBKDF2 as the KDF (see: WAMP protocol specification, "SCRAM Algorithms"). +class KDF(object): + # Not set / no TLS channel binding. + NULL = 0 + # Argon2id variant of Argon2, version 1.3 - `argon2id13`. + ARGON2ID13 = 1 + # PBKDF2 - `pbkdf2`. + PBKDF2 = 2 diff --git a/autobahn/wamp/gen/wamp/proto/Match.py b/autobahn/wamp/gen/wamp/proto/Match.py index c60ba4ce1..d0d0d1f09 100644 --- a/autobahn/wamp/gen/wamp/proto/Match.py +++ b/autobahn/wamp/gen/wamp/proto/Match.py @@ -2,8 +2,11 @@ # namespace: proto - +# Subscription topic matching method. class Match(object): + # Match URI exact. EXACT = 0 + # Match URI by prefix. PREFIX = 1 + # Match URI by wildcard. WILDCARD = 2 diff --git a/autobahn/wamp/gen/wamp/proto/MessageType.py b/autobahn/wamp/gen/wamp/proto/MessageType.py index 574b5b891..0a8cb2b62 100644 --- a/autobahn/wamp/gen/wamp/proto/MessageType.py +++ b/autobahn/wamp/gen/wamp/proto/MessageType.py @@ -2,16 +2,24 @@ # namespace: proto - +# WAMP message type IDs organized by message categories: +# - Category 1: Neither Payload nor Forwarding (12 messages) +# - Category 2: Payload Only (0 messages) - architecturally empty +# - Category 3: Forwarding Only (3 messages) +# - Category 4: Both Payload and Forwarding (7 messages) class MessageType(object): + # No valid message type NULL = 0 + # [Category 1] Session opening, authentication and closing HELLO = 1 WELCOME = 2 ABORT = 3 CHALLENGE = 4 AUTHENTICATE = 5 GOODBYE = 6 + # [Category 4] Used in both PubSub and RPC ERROR = 8 + # Publish & Subscribe (PubSub) PUBLISH = 16 PUBLISHED = 17 SUBSCRIBE = 32 @@ -20,6 +28,7 @@ class MessageType(object): UNSUBSCRIBED = 35 EVENT = 36 EVENT_RECEIVED = 37 + # Remote Procedure Calls (RPC) CALL = 48 CANCEL = 49 RESULT = 50 diff --git a/autobahn/wamp/gen/wamp/proto/PPTCipher.py b/autobahn/wamp/gen/wamp/proto/PPTCipher.py new file mode 100644 index 000000000..f6ed9bbf8 --- /dev/null +++ b/autobahn/wamp/gen/wamp/proto/PPTCipher.py @@ -0,0 +1,12 @@ +# automatically generated by the FlatBuffers compiler, do not modify + +# namespace: proto + +# WAMP Payload Passthru (PPT) application payload cipher (NEW!). +class PPTCipher(object): + # No valid cipher (unfilled) + NULL = 0 + # Particular combination of Salsa20 and Poly1305 specified in Daniel J. Bernstein, "Cryptography in NaCl" (https://cr.yp.to/highspeed/naclcrypto-20090310.pdf), see NaCl (https://nacl.cr.yp.to/) "crypto_secretbox_xsalsa20poly1305" (aka WAMP "cryptobox"). + XSALSA20POLY1305 = 1 + # Galois/Counter Mode (GCM), see https://en.wikipedia.org/wiki/Galois/Counter_Mode. + AES256GCM = 2 diff --git a/autobahn/wamp/gen/wamp/proto/PPTScheme.py b/autobahn/wamp/gen/wamp/proto/PPTScheme.py new file mode 100644 index 000000000..0441ab765 --- /dev/null +++ b/autobahn/wamp/gen/wamp/proto/PPTScheme.py @@ -0,0 +1,16 @@ +# automatically generated by the FlatBuffers compiler, do not modify + +# namespace: proto + +# WAMP Payload Passthru (PPT) scheme (renamed from `Payload`). +class PPTScheme(object): + # Unset (plain WAMP application payload) + NONE = 0 + # WAMP-cryptobox end-to-end encrypted application payload + CRYPTOBOX = 1 + # MQTT passthrough-mode application payload + MQTT = 2 + # XBR end-to-end encrypted and Ethereum anchored application payload + XBR = 3 + # Raw pass-through of app payload, uninterpreted in any way. + OPAQUE = 4 diff --git a/autobahn/wamp/gen/wamp/proto/PPTSerializer.py b/autobahn/wamp/gen/wamp/proto/PPTSerializer.py new file mode 100644 index 000000000..0c3995f7d --- /dev/null +++ b/autobahn/wamp/gen/wamp/proto/PPTSerializer.py @@ -0,0 +1,22 @@ +# automatically generated by the FlatBuffers compiler, do not modify + +# namespace: proto + +# WAMP Payload Passthru (PPT) application payload serializer (renamed from `Serializer`). +class PPTSerializer(object): + # Use same serializer (dynamically or statically typed) for the application payload as used on the transport for the WAMP message envelope. + TRANSPORT = 0 + # Use JSON serializer for dynamically typed application payload. + JSON = 1 + # Use MsgPack serializer for dynamically typed application payload. + MSGPACK = 2 + # Use CBOR serializer for dynamically typed application payload. + CBOR = 3 + # Use UBJSON serializer for dynamically typed application payload. + UBJSON = 4 + # Raw pass-through of application payload, uninterpreted in any way. + OPAQUE = 5 + # Use FlatBuffers serializer for statically typed application payload (https://google.github.io/flatbuffers/index.html). + FLATBUFFERS = 6 + # Use FlexBuffers serializer dynamically typed application payload (https://google.github.io/flatbuffers/flexbuffers.html). + FLEXBUFFERS = 7 diff --git a/autobahn/wamp/gen/wamp/proto/Payload.py b/autobahn/wamp/gen/wamp/proto/Payload.py deleted file mode 100644 index ec7d1b9e4..000000000 --- a/autobahn/wamp/gen/wamp/proto/Payload.py +++ /dev/null @@ -1,9 +0,0 @@ -# automatically generated by the FlatBuffers compiler, do not modify - -# namespace: proto - - -class Payload(object): - PLAIN = 0 - CRYPTOBOX = 1 - OPAQUE = 2 diff --git a/autobahn/wamp/gen/wamp/proto/Principal.py b/autobahn/wamp/gen/wamp/proto/Principal.py index 25f92cd21..9ead0ab19 100644 --- a/autobahn/wamp/gen/wamp/proto/Principal.py +++ b/autobahn/wamp/gen/wamp/proto/Principal.py @@ -8,26 +8,92 @@ np = import_numpy() +# WAMP session identity information. class Principal(object): __slots__ = ["_tab"] @classmethod - def SizeOf(cls): - return 8 + def GetRootAs(cls, buf, offset=0): + n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset) + x = Principal() + x.Init(buf, n + offset) + return x + + @classmethod + def GetRootAsPrincipal(cls, buf, offset=0): + """This method is deprecated. Please switch to GetRootAs.""" + return cls.GetRootAs(buf, offset) # Principal def Init(self, buf, pos): self._tab = flatbuffers.table.Table(buf, pos) + # WAMP session ID. # Principal def Session(self): - return self._tab.Get( - flatbuffers.number_types.Uint64Flags, - self._tab.Pos + flatbuffers.number_types.UOffsetTFlags.py_type(0), - ) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4)) + if o != 0: + return self._tab.Get( + flatbuffers.number_types.Uint64Flags, o + self._tab.Pos + ) + return 0 + + # WAMP session authentication ID. + # Principal + def Authid(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6)) + if o != 0: + return self._tab.String(o + self._tab.Pos) + return None + + # WAMP session authentication role. + # Principal + def Authrole(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8)) + if o != 0: + return self._tab.String(o + self._tab.Pos) + return None + + +def PrincipalStart(builder): + builder.StartObject(3) + + +def Start(builder): + PrincipalStart(builder) + + +def PrincipalAddSession(builder, session): + builder.PrependUint64Slot(0, session, 0) + + +def AddSession(builder, session): + PrincipalAddSession(builder, session) + + +def PrincipalAddAuthid(builder, authid): + builder.PrependUOffsetTRelativeSlot( + 1, flatbuffers.number_types.UOffsetTFlags.py_type(authid), 0 + ) + + +def AddAuthid(builder, authid): + PrincipalAddAuthid(builder, authid) + + +def PrincipalAddAuthrole(builder, authrole): + builder.PrependUOffsetTRelativeSlot( + 2, flatbuffers.number_types.UOffsetTFlags.py_type(authrole), 0 + ) + + +def AddAuthrole(builder, authrole): + PrincipalAddAuthrole(builder, authrole) + + +def PrincipalEnd(builder): + return builder.EndObject() -def CreatePrincipal(builder, session): - builder.Prep(8, 8) - builder.PrependUint64(session) - return builder.Offset() +def End(builder): + return PrincipalEnd(builder) diff --git a/autobahn/wamp/gen/wamp/proto/Publish.py b/autobahn/wamp/gen/wamp/proto/Publish.py index 397417079..417c96a76 100644 --- a/autobahn/wamp/gen/wamp/proto/Publish.py +++ b/autobahn/wamp/gen/wamp/proto/Publish.py @@ -145,53 +145,41 @@ def PayloadIsNone(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(14)) return o == 0 + # The specific scheme in use with Payload Passthru (PPT) mode for the application payload. # Publish - def EncAlgo(self): + def PptScheme(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(16)) if o != 0: return self._tab.Get(flatbuffers.number_types.Uint8Flags, o + self._tab.Pos) return 0 + # The specific serializer encoding the application payload with the Payload Passthru (PPT) scheme in use. # Publish - def EncSerializer(self): + def PptSerializer(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(18)) if o != 0: return self._tab.Get(flatbuffers.number_types.Uint8Flags, o + self._tab.Pos) return 0 + # The cryptographic algorithm ("cipher") encrypting the application payload with the Payload Passthru (PPT) scheme in use. # Publish - def EncKey(self, j): + def PptCipher(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(20)) if o != 0: - a = self._tab.Vector(o) - return self._tab.Get( - flatbuffers.number_types.Uint8Flags, - a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 1), - ) - return 0 - - # Publish - def EncKeyAsNumpy(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(20)) - if o != 0: - return self._tab.GetVectorAsNumpy(flatbuffers.number_types.Uint8Flags, o) + return self._tab.Get(flatbuffers.number_types.Uint8Flags, o + self._tab.Pos) return 0 + # The identifier or reference to the encryption key that was used to encrypt the payload with the Payload Passthru (PPT) scheme and cipher in use. # Publish - def EncKeyLength(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(20)) + def PptKeyid(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(22)) if o != 0: - return self._tab.VectorLen(o) - return 0 - - # Publish - def EncKeyIsNone(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(20)) - return o == 0 + return self._tab.String(o + self._tab.Pos) + return None # Publish def Acknowledge(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(22)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(24)) if o != 0: return bool( self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos) @@ -200,7 +188,7 @@ def Acknowledge(self): # Publish def ExcludeMe(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(24)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(26)) if o != 0: return bool( self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos) @@ -209,7 +197,7 @@ def ExcludeMe(self): # Publish def Exclude(self, j): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(26)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(28)) if o != 0: a = self._tab.Vector(o) return self._tab.Get( @@ -220,26 +208,26 @@ def Exclude(self, j): # Publish def ExcludeAsNumpy(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(26)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(28)) if o != 0: return self._tab.GetVectorAsNumpy(flatbuffers.number_types.Uint64Flags, o) return 0 # Publish def ExcludeLength(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(26)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(28)) if o != 0: return self._tab.VectorLen(o) return 0 # Publish def ExcludeIsNone(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(26)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(28)) return o == 0 # Publish def ExcludeAuthid(self, j): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(28)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(30)) if o != 0: a = self._tab.Vector(o) return self._tab.String( @@ -249,19 +237,19 @@ def ExcludeAuthid(self, j): # Publish def ExcludeAuthidLength(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(28)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(30)) if o != 0: return self._tab.VectorLen(o) return 0 # Publish def ExcludeAuthidIsNone(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(28)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(30)) return o == 0 # Publish def ExcludeAuthrole(self, j): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(30)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(32)) if o != 0: a = self._tab.Vector(o) return self._tab.String( @@ -271,19 +259,19 @@ def ExcludeAuthrole(self, j): # Publish def ExcludeAuthroleLength(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(30)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(32)) if o != 0: return self._tab.VectorLen(o) return 0 # Publish def ExcludeAuthroleIsNone(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(30)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(32)) return o == 0 # Publish def Eligible(self, j): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(32)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(34)) if o != 0: a = self._tab.Vector(o) return self._tab.Get( @@ -294,26 +282,26 @@ def Eligible(self, j): # Publish def EligibleAsNumpy(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(32)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(34)) if o != 0: return self._tab.GetVectorAsNumpy(flatbuffers.number_types.Uint64Flags, o) return 0 # Publish def EligibleLength(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(32)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(34)) if o != 0: return self._tab.VectorLen(o) return 0 # Publish def EligibleIsNone(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(32)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(34)) return o == 0 # Publish def EligibleAuthid(self, j): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(34)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(36)) if o != 0: a = self._tab.Vector(o) return self._tab.String( @@ -323,19 +311,19 @@ def EligibleAuthid(self, j): # Publish def EligibleAuthidLength(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(34)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(36)) if o != 0: return self._tab.VectorLen(o) return 0 # Publish def EligibleAuthidIsNone(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(34)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(36)) return o == 0 # Publish def EligibleAuthrole(self, j): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(36)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(38)) if o != 0: a = self._tab.Vector(o) return self._tab.String( @@ -345,19 +333,19 @@ def EligibleAuthrole(self, j): # Publish def EligibleAuthroleLength(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(36)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(38)) if o != 0: return self._tab.VectorLen(o) return 0 # Publish def EligibleAuthroleIsNone(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(36)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(38)) return o == 0 # Publish def Retain(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(38)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(40)) if o != 0: return bool( self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos) @@ -366,18 +354,19 @@ def Retain(self): # Publish def TransactionHash(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(40)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(42)) if o != 0: return self._tab.String(o + self._tab.Pos) return None # Publish def ForwardFor(self, j): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(42)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(44)) if o != 0: x = self._tab.Vector(o) - x += flatbuffers.number_types.UOffsetTFlags.py_type(j) * 8 - from wamp.proto.Principal import Principal + x += flatbuffers.number_types.UOffsetTFlags.py_type(j) * 4 + x = self._tab.Indirect(x) + from autobahn.wamp.gen.wamp.proto.Principal import Principal obj = Principal() obj.Init(self._tab.Bytes, x) @@ -386,19 +375,19 @@ def ForwardFor(self, j): # Publish def ForwardForLength(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(42)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(44)) if o != 0: return self._tab.VectorLen(o) return 0 # Publish def ForwardForIsNone(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(42)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(44)) return o == 0 def PublishStart(builder): - builder.StartObject(20) + builder.StartObject(21) def Start(builder): @@ -485,42 +474,42 @@ def StartPayloadVector(builder, numElems): return PublishStartPayloadVector(builder, numElems) -def PublishAddEncAlgo(builder, encAlgo): - builder.PrependUint8Slot(6, encAlgo, 0) +def PublishAddPptScheme(builder, pptScheme): + builder.PrependUint8Slot(6, pptScheme, 0) -def AddEncAlgo(builder, encAlgo): - PublishAddEncAlgo(builder, encAlgo) +def AddPptScheme(builder, pptScheme): + PublishAddPptScheme(builder, pptScheme) -def PublishAddEncSerializer(builder, encSerializer): - builder.PrependUint8Slot(7, encSerializer, 0) +def PublishAddPptSerializer(builder, pptSerializer): + builder.PrependUint8Slot(7, pptSerializer, 0) -def AddEncSerializer(builder, encSerializer): - PublishAddEncSerializer(builder, encSerializer) +def AddPptSerializer(builder, pptSerializer): + PublishAddPptSerializer(builder, pptSerializer) -def PublishAddEncKey(builder, encKey): - builder.PrependUOffsetTRelativeSlot( - 8, flatbuffers.number_types.UOffsetTFlags.py_type(encKey), 0 - ) +def PublishAddPptCipher(builder, pptCipher): + builder.PrependUint8Slot(8, pptCipher, 0) -def AddEncKey(builder, encKey): - PublishAddEncKey(builder, encKey) +def AddPptCipher(builder, pptCipher): + PublishAddPptCipher(builder, pptCipher) -def PublishStartEncKeyVector(builder, numElems): - return builder.StartVector(1, numElems, 1) +def PublishAddPptKeyid(builder, pptKeyid): + builder.PrependUOffsetTRelativeSlot( + 9, flatbuffers.number_types.UOffsetTFlags.py_type(pptKeyid), 0 + ) -def StartEncKeyVector(builder, numElems): - return PublishStartEncKeyVector(builder, numElems) +def AddPptKeyid(builder, pptKeyid): + PublishAddPptKeyid(builder, pptKeyid) def PublishAddAcknowledge(builder, acknowledge): - builder.PrependBoolSlot(9, acknowledge, 0) + builder.PrependBoolSlot(10, acknowledge, 0) def AddAcknowledge(builder, acknowledge): @@ -528,7 +517,7 @@ def AddAcknowledge(builder, acknowledge): def PublishAddExcludeMe(builder, excludeMe): - builder.PrependBoolSlot(10, excludeMe, 1) + builder.PrependBoolSlot(11, excludeMe, 1) def AddExcludeMe(builder, excludeMe): @@ -537,7 +526,7 @@ def AddExcludeMe(builder, excludeMe): def PublishAddExclude(builder, exclude): builder.PrependUOffsetTRelativeSlot( - 11, flatbuffers.number_types.UOffsetTFlags.py_type(exclude), 0 + 12, flatbuffers.number_types.UOffsetTFlags.py_type(exclude), 0 ) @@ -555,7 +544,7 @@ def StartExcludeVector(builder, numElems): def PublishAddExcludeAuthid(builder, excludeAuthid): builder.PrependUOffsetTRelativeSlot( - 12, flatbuffers.number_types.UOffsetTFlags.py_type(excludeAuthid), 0 + 13, flatbuffers.number_types.UOffsetTFlags.py_type(excludeAuthid), 0 ) @@ -573,7 +562,7 @@ def StartExcludeAuthidVector(builder, numElems): def PublishAddExcludeAuthrole(builder, excludeAuthrole): builder.PrependUOffsetTRelativeSlot( - 13, flatbuffers.number_types.UOffsetTFlags.py_type(excludeAuthrole), 0 + 14, flatbuffers.number_types.UOffsetTFlags.py_type(excludeAuthrole), 0 ) @@ -591,7 +580,7 @@ def StartExcludeAuthroleVector(builder, numElems): def PublishAddEligible(builder, eligible): builder.PrependUOffsetTRelativeSlot( - 14, flatbuffers.number_types.UOffsetTFlags.py_type(eligible), 0 + 15, flatbuffers.number_types.UOffsetTFlags.py_type(eligible), 0 ) @@ -609,7 +598,7 @@ def StartEligibleVector(builder, numElems): def PublishAddEligibleAuthid(builder, eligibleAuthid): builder.PrependUOffsetTRelativeSlot( - 15, flatbuffers.number_types.UOffsetTFlags.py_type(eligibleAuthid), 0 + 16, flatbuffers.number_types.UOffsetTFlags.py_type(eligibleAuthid), 0 ) @@ -627,7 +616,7 @@ def StartEligibleAuthidVector(builder, numElems): def PublishAddEligibleAuthrole(builder, eligibleAuthrole): builder.PrependUOffsetTRelativeSlot( - 16, flatbuffers.number_types.UOffsetTFlags.py_type(eligibleAuthrole), 0 + 17, flatbuffers.number_types.UOffsetTFlags.py_type(eligibleAuthrole), 0 ) @@ -644,7 +633,7 @@ def StartEligibleAuthroleVector(builder, numElems): def PublishAddRetain(builder, retain): - builder.PrependBoolSlot(17, retain, 0) + builder.PrependBoolSlot(18, retain, 0) def AddRetain(builder, retain): @@ -653,7 +642,7 @@ def AddRetain(builder, retain): def PublishAddTransactionHash(builder, transactionHash): builder.PrependUOffsetTRelativeSlot( - 18, flatbuffers.number_types.UOffsetTFlags.py_type(transactionHash), 0 + 19, flatbuffers.number_types.UOffsetTFlags.py_type(transactionHash), 0 ) @@ -663,7 +652,7 @@ def AddTransactionHash(builder, transactionHash): def PublishAddForwardFor(builder, forwardFor): builder.PrependUOffsetTRelativeSlot( - 19, flatbuffers.number_types.UOffsetTFlags.py_type(forwardFor), 0 + 20, flatbuffers.number_types.UOffsetTFlags.py_type(forwardFor), 0 ) @@ -672,7 +661,7 @@ def AddForwardFor(builder, forwardFor): def PublishStartForwardForVector(builder, numElems): - return builder.StartVector(8, numElems, 8) + return builder.StartVector(4, numElems, 4) def StartForwardForVector(builder, numElems): diff --git a/autobahn/wamp/gen/wamp/proto/Result.py b/autobahn/wamp/gen/wamp/proto/Result.py index 82d3133b8..25f9398c7 100644 --- a/autobahn/wamp/gen/wamp/proto/Result.py +++ b/autobahn/wamp/gen/wamp/proto/Result.py @@ -138,53 +138,41 @@ def PayloadIsNone(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12)) return o == 0 + # The specific scheme in use with Payload Passthru (PPT) mode for the application payload. # Result - def EncAlgo(self): + def PptScheme(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(14)) if o != 0: return self._tab.Get(flatbuffers.number_types.Uint8Flags, o + self._tab.Pos) return 0 + # The specific serializer encoding the application payload with the Payload Passthru (PPT) scheme in use. # Result - def EncSerializer(self): + def PptSerializer(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(16)) if o != 0: return self._tab.Get(flatbuffers.number_types.Uint8Flags, o + self._tab.Pos) return 0 + # The cryptographic algorithm ("cipher") encrypting the application payload with the Payload Passthru (PPT) scheme in use. # Result - def EncKey(self, j): + def PptCipher(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(18)) if o != 0: - a = self._tab.Vector(o) - return self._tab.Get( - flatbuffers.number_types.Uint8Flags, - a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 1), - ) - return 0 - - # Result - def EncKeyAsNumpy(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(18)) - if o != 0: - return self._tab.GetVectorAsNumpy(flatbuffers.number_types.Uint8Flags, o) + return self._tab.Get(flatbuffers.number_types.Uint8Flags, o + self._tab.Pos) return 0 + # The identifier or reference to the encryption key that was used to encrypt the payload with the Payload Passthru (PPT) scheme and cipher in use. # Result - def EncKeyLength(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(18)) + def PptKeyid(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(20)) if o != 0: - return self._tab.VectorLen(o) - return 0 - - # Result - def EncKeyIsNone(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(18)) - return o == 0 + return self._tab.String(o + self._tab.Pos) + return None # Result def Progress(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(20)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(22)) if o != 0: return bool( self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos) @@ -193,7 +181,7 @@ def Progress(self): # Result def Callee(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(22)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(24)) if o != 0: return self._tab.Get( flatbuffers.number_types.Uint64Flags, o + self._tab.Pos @@ -202,25 +190,26 @@ def Callee(self): # Result def CalleeAuthid(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(24)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(26)) if o != 0: return self._tab.String(o + self._tab.Pos) return None # Result def CalleeAuthrole(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(26)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(28)) if o != 0: return self._tab.String(o + self._tab.Pos) return None # Result def ForwardFor(self, j): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(28)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(30)) if o != 0: x = self._tab.Vector(o) - x += flatbuffers.number_types.UOffsetTFlags.py_type(j) * 8 - from wamp.proto.Principal import Principal + x += flatbuffers.number_types.UOffsetTFlags.py_type(j) * 4 + x = self._tab.Indirect(x) + from autobahn.wamp.gen.wamp.proto.Principal import Principal obj = Principal() obj.Init(self._tab.Bytes, x) @@ -229,19 +218,19 @@ def ForwardFor(self, j): # Result def ForwardForLength(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(28)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(30)) if o != 0: return self._tab.VectorLen(o) return 0 # Result def ForwardForIsNone(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(28)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(30)) return o == 0 def ResultStart(builder): - builder.StartObject(13) + builder.StartObject(14) def Start(builder): @@ -318,42 +307,42 @@ def StartPayloadVector(builder, numElems): return ResultStartPayloadVector(builder, numElems) -def ResultAddEncAlgo(builder, encAlgo): - builder.PrependUint8Slot(5, encAlgo, 0) +def ResultAddPptScheme(builder, pptScheme): + builder.PrependUint8Slot(5, pptScheme, 0) -def AddEncAlgo(builder, encAlgo): - ResultAddEncAlgo(builder, encAlgo) +def AddPptScheme(builder, pptScheme): + ResultAddPptScheme(builder, pptScheme) -def ResultAddEncSerializer(builder, encSerializer): - builder.PrependUint8Slot(6, encSerializer, 0) +def ResultAddPptSerializer(builder, pptSerializer): + builder.PrependUint8Slot(6, pptSerializer, 0) -def AddEncSerializer(builder, encSerializer): - ResultAddEncSerializer(builder, encSerializer) +def AddPptSerializer(builder, pptSerializer): + ResultAddPptSerializer(builder, pptSerializer) -def ResultAddEncKey(builder, encKey): - builder.PrependUOffsetTRelativeSlot( - 7, flatbuffers.number_types.UOffsetTFlags.py_type(encKey), 0 - ) +def ResultAddPptCipher(builder, pptCipher): + builder.PrependUint8Slot(7, pptCipher, 0) -def AddEncKey(builder, encKey): - ResultAddEncKey(builder, encKey) +def AddPptCipher(builder, pptCipher): + ResultAddPptCipher(builder, pptCipher) -def ResultStartEncKeyVector(builder, numElems): - return builder.StartVector(1, numElems, 1) +def ResultAddPptKeyid(builder, pptKeyid): + builder.PrependUOffsetTRelativeSlot( + 8, flatbuffers.number_types.UOffsetTFlags.py_type(pptKeyid), 0 + ) -def StartEncKeyVector(builder, numElems): - return ResultStartEncKeyVector(builder, numElems) +def AddPptKeyid(builder, pptKeyid): + ResultAddPptKeyid(builder, pptKeyid) def ResultAddProgress(builder, progress): - builder.PrependBoolSlot(8, progress, 0) + builder.PrependBoolSlot(9, progress, 0) def AddProgress(builder, progress): @@ -361,7 +350,7 @@ def AddProgress(builder, progress): def ResultAddCallee(builder, callee): - builder.PrependUint64Slot(9, callee, 0) + builder.PrependUint64Slot(10, callee, 0) def AddCallee(builder, callee): @@ -370,7 +359,7 @@ def AddCallee(builder, callee): def ResultAddCalleeAuthid(builder, calleeAuthid): builder.PrependUOffsetTRelativeSlot( - 10, flatbuffers.number_types.UOffsetTFlags.py_type(calleeAuthid), 0 + 11, flatbuffers.number_types.UOffsetTFlags.py_type(calleeAuthid), 0 ) @@ -380,7 +369,7 @@ def AddCalleeAuthid(builder, calleeAuthid): def ResultAddCalleeAuthrole(builder, calleeAuthrole): builder.PrependUOffsetTRelativeSlot( - 11, flatbuffers.number_types.UOffsetTFlags.py_type(calleeAuthrole), 0 + 12, flatbuffers.number_types.UOffsetTFlags.py_type(calleeAuthrole), 0 ) @@ -390,7 +379,7 @@ def AddCalleeAuthrole(builder, calleeAuthrole): def ResultAddForwardFor(builder, forwardFor): builder.PrependUOffsetTRelativeSlot( - 12, flatbuffers.number_types.UOffsetTFlags.py_type(forwardFor), 0 + 13, flatbuffers.number_types.UOffsetTFlags.py_type(forwardFor), 0 ) @@ -399,7 +388,7 @@ def AddForwardFor(builder, forwardFor): def ResultStartForwardForVector(builder, numElems): - return builder.StartVector(8, numElems, 8) + return builder.StartVector(4, numElems, 4) def StartForwardForVector(builder, numElems): diff --git a/autobahn/wamp/gen/wamp/proto/RouterRoles.py b/autobahn/wamp/gen/wamp/proto/RouterRoles.py index 1dccf987b..e06227e97 100644 --- a/autobahn/wamp/gen/wamp/proto/RouterRoles.py +++ b/autobahn/wamp/gen/wamp/proto/RouterRoles.py @@ -32,7 +32,7 @@ def Broker(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4)) if o != 0: x = self._tab.Indirect(o + self._tab.Pos) - from wamp.proto.BrokerFeatures import BrokerFeatures + from autobahn.wamp.gen.wamp.proto.BrokerFeatures import BrokerFeatures obj = BrokerFeatures() obj.Init(self._tab.Bytes, x) @@ -44,7 +44,7 @@ def Dealer(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6)) if o != 0: x = self._tab.Indirect(o + self._tab.Pos) - from wamp.proto.DealerFeatures import DealerFeatures + from autobahn.wamp.gen.wamp.proto.DealerFeatures import DealerFeatures obj = DealerFeatures() obj.Init(self._tab.Bytes, x) diff --git a/autobahn/wamp/gen/wamp/proto/Serializer.py b/autobahn/wamp/gen/wamp/proto/Serializer.py deleted file mode 100644 index ee44f9a31..000000000 --- a/autobahn/wamp/gen/wamp/proto/Serializer.py +++ /dev/null @@ -1,14 +0,0 @@ -# automatically generated by the FlatBuffers compiler, do not modify - -# namespace: proto - - -class Serializer(object): - TRANSPORT = 0 - JSON = 1 - MSGPACK = 2 - CBOR = 3 - UBJSON = 4 - OPAQUE = 5 - FLATBUFFERS = 6 - FLEXBUFFERS = 7 diff --git a/autobahn/wamp/gen/wamp/proto/TLSChannelBinding.py b/autobahn/wamp/gen/wamp/proto/TLSChannelBinding.py new file mode 100644 index 000000000..b4a717f24 --- /dev/null +++ b/autobahn/wamp/gen/wamp/proto/TLSChannelBinding.py @@ -0,0 +1,15 @@ +# automatically generated by the FlatBuffers compiler, do not modify + +# namespace: proto + +# TLS channel binding type (see: RFC5929 https://www.rfc-editor.org/rfc/rfc5929 and RFC9266 https://www.rfc-editor.org/rfc/rfc9266). +class TLSChannelBinding(object): + # Not set / no channel binding. + NULL = 0 + TLS_UNIQUE = 1 + # RFC 5929 `tls-unique-for-telnet`. Only relevant to TELNET / TELNET AUTH usage. See RFC 5929 for details and applicability guidance. + TLS_UNIQUE_TELNET = 2 + # RFC 5929 `tls-server-end-point`. Only available when a server certificate is used (i.e., cipher suites that include the Certificate handshake message / PKIX). Not applicable to OpenPGP server certificates. Recommended for situations where server-side proxies must interoperate without changes. + TLS_SERVER_ENDPOINT = 3 + # RFC 9266 `tls-exporter`. Defined to address TLS 1.3 (where the older tls-unique semantics are not reliably available). RFC 9266 updates defaults for TLS ≥ 1.3: when channel bindings are used for TLS 1.3, tls-exporter is the mandatory/expected mechanism. Not defined for connections where TLS renegotiation is enabled. + TLS_EXPORTER = 4 diff --git a/autobahn/wamp/gen/wamp/proto/TransportChannelFraming.py b/autobahn/wamp/gen/wamp/proto/TransportChannelFraming.py new file mode 100644 index 000000000..5d7c8a731 --- /dev/null +++ b/autobahn/wamp/gen/wamp/proto/TransportChannelFraming.py @@ -0,0 +1,14 @@ +# automatically generated by the FlatBuffers compiler, do not modify + +# namespace: proto + +# WAMP transport channel framing (`channel_framing`). +class TransportChannelFraming(object): + # Not set. + NULL = 0 + # Raw transport itself is inherently message-based already (e.g. FUNCTION or VIRTIO). + NATIVE = 1 + # Raw transport itself (e.g. TCP) is stream-based and channel framing applied is WebSocket (RFC6455). + WEBSOCKET = 2 + # Raw transport itself (e.g. TCP) is stream-based and channel framing applied is RawSocket (WAMP). + RAWSOCKET = 3 diff --git a/autobahn/wamp/gen/wamp/proto/TransportChannelSerializer.py b/autobahn/wamp/gen/wamp/proto/TransportChannelSerializer.py new file mode 100644 index 000000000..0c4c309bf --- /dev/null +++ b/autobahn/wamp/gen/wamp/proto/TransportChannelSerializer.py @@ -0,0 +1,22 @@ +# automatically generated by the FlatBuffers compiler, do not modify + +# namespace: proto + +# WAMP transport channel serializer (`channel_serializer`). +class TransportChannelSerializer(object): + # Not set or applicable. + NULL = 0 + # Use JSON serializer (for dynamically typed app payload). + JSON = 1 + # Use MsgPack serializer (for dynamically typed app payload). + MSGPACK = 2 + # Use CBOR serializer (for dynamically typed app payload). + CBOR = 3 + # Use UBJSON serializer (for dynamically typed app payload). + UBJSON = 4 + # Raw pass-through of app payload, uninterpreted in any way. + OPAQUE = 5 + # Use FlatBuffers serialized (statically typed) payload (https://google.github.io/flatbuffers/index.html). + FLATBUFFERS = 6 + # Use FlexBuffers serialized (dynamically typed) payload (https://google.github.io/flatbuffers/flexbuffers.html). + FLEXBUFFERS = 7 diff --git a/autobahn/wamp/gen/wamp/proto/TransportChannelType.py b/autobahn/wamp/gen/wamp/proto/TransportChannelType.py new file mode 100644 index 000000000..1731a7711 --- /dev/null +++ b/autobahn/wamp/gen/wamp/proto/TransportChannelType.py @@ -0,0 +1,18 @@ +# automatically generated by the FlatBuffers compiler, do not modify + +# namespace: proto + +# WAMP transport channel type (`channel_type`). WAMP can run over any Transport which is message-based (requiring no WAMP transport channel framing) or stream-based (requiring WAMP transport channel framing), and bidirectional, reliable and ordered. +class TransportChannelType(object): + # Not set or applicable. + NULL = 0 + # Host language native function call transport, e.g. inherently message-based already (function call). + FUNCTION = 1 + # Host run-time / OS level in-memory transport, e.g. (unframed / stream-based) memory buffer. + MEMORY = 2 + # Serial (UART) based transport (unframed / stream-based). + SERIAL = 3 + # TCP (non-TLS) based transport (unframed / stream-based). + TCP = 4 + # TLS (over TCP) based transport (unframed / stream-based). + TLS = 5 diff --git a/autobahn/wamp/gen/wamp/proto/Welcome.py b/autobahn/wamp/gen/wamp/proto/Welcome.py index 507ab5257..ba57532e1 100644 --- a/autobahn/wamp/gen/wamp/proto/Welcome.py +++ b/autobahn/wamp/gen/wamp/proto/Welcome.py @@ -41,7 +41,7 @@ def Roles(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6)) if o != 0: x = self._tab.Indirect(o + self._tab.Pos) - from wamp.proto.RouterRoles import RouterRoles + from autobahn.wamp.gen.wamp.proto.RouterRoles import RouterRoles obj = RouterRoles() obj.Init(self._tab.Bytes, x) diff --git a/autobahn/wamp/gen/wamp/proto/Yield.py b/autobahn/wamp/gen/wamp/proto/Yield.py index 30d0b1a43..ea5b49416 100644 --- a/autobahn/wamp/gen/wamp/proto/Yield.py +++ b/autobahn/wamp/gen/wamp/proto/Yield.py @@ -138,53 +138,41 @@ def PayloadIsNone(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12)) return o == 0 + # The specific scheme in use with Payload Passthru (PPT) mode for the application payload. # Yield - def EncAlgo(self): + def PptScheme(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(14)) if o != 0: return self._tab.Get(flatbuffers.number_types.Uint8Flags, o + self._tab.Pos) return 0 + # The specific serializer encoding the application payload with the Payload Passthru (PPT) scheme in use. # Yield - def EncSerializer(self): + def PptSerializer(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(16)) if o != 0: return self._tab.Get(flatbuffers.number_types.Uint8Flags, o + self._tab.Pos) return 0 + # The cryptographic algorithm ("cipher") encrypting the application payload with the Payload Passthru (PPT) scheme in use. # Yield - def EncKey(self, j): + def PptCipher(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(18)) if o != 0: - a = self._tab.Vector(o) - return self._tab.Get( - flatbuffers.number_types.Uint8Flags, - a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 1), - ) - return 0 - - # Yield - def EncKeyAsNumpy(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(18)) - if o != 0: - return self._tab.GetVectorAsNumpy(flatbuffers.number_types.Uint8Flags, o) + return self._tab.Get(flatbuffers.number_types.Uint8Flags, o + self._tab.Pos) return 0 + # The identifier or reference to the encryption key that was used to encrypt the payload with the Payload Passthru (PPT) scheme and cipher in use. # Yield - def EncKeyLength(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(18)) + def PptKeyid(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(20)) if o != 0: - return self._tab.VectorLen(o) - return 0 - - # Yield - def EncKeyIsNone(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(18)) - return o == 0 + return self._tab.String(o + self._tab.Pos) + return None # Yield def Progress(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(20)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(22)) if o != 0: return bool( self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos) @@ -193,7 +181,7 @@ def Progress(self): # Yield def Callee(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(22)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(24)) if o != 0: return self._tab.Get( flatbuffers.number_types.Uint64Flags, o + self._tab.Pos @@ -202,25 +190,26 @@ def Callee(self): # Yield def CalleeAuthid(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(24)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(26)) if o != 0: return self._tab.String(o + self._tab.Pos) return None # Yield def CalleeAuthrole(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(26)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(28)) if o != 0: return self._tab.String(o + self._tab.Pos) return None # Yield def ForwardFor(self, j): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(28)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(30)) if o != 0: x = self._tab.Vector(o) - x += flatbuffers.number_types.UOffsetTFlags.py_type(j) * 8 - from wamp.proto.Principal import Principal + x += flatbuffers.number_types.UOffsetTFlags.py_type(j) * 4 + x = self._tab.Indirect(x) + from autobahn.wamp.gen.wamp.proto.Principal import Principal obj = Principal() obj.Init(self._tab.Bytes, x) @@ -229,19 +218,19 @@ def ForwardFor(self, j): # Yield def ForwardForLength(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(28)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(30)) if o != 0: return self._tab.VectorLen(o) return 0 # Yield def ForwardForIsNone(self): - o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(28)) + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(30)) return o == 0 def YieldStart(builder): - builder.StartObject(13) + builder.StartObject(14) def Start(builder): @@ -318,42 +307,42 @@ def StartPayloadVector(builder, numElems): return YieldStartPayloadVector(builder, numElems) -def YieldAddEncAlgo(builder, encAlgo): - builder.PrependUint8Slot(5, encAlgo, 0) +def YieldAddPptScheme(builder, pptScheme): + builder.PrependUint8Slot(5, pptScheme, 0) -def AddEncAlgo(builder, encAlgo): - YieldAddEncAlgo(builder, encAlgo) +def AddPptScheme(builder, pptScheme): + YieldAddPptScheme(builder, pptScheme) -def YieldAddEncSerializer(builder, encSerializer): - builder.PrependUint8Slot(6, encSerializer, 0) +def YieldAddPptSerializer(builder, pptSerializer): + builder.PrependUint8Slot(6, pptSerializer, 0) -def AddEncSerializer(builder, encSerializer): - YieldAddEncSerializer(builder, encSerializer) +def AddPptSerializer(builder, pptSerializer): + YieldAddPptSerializer(builder, pptSerializer) -def YieldAddEncKey(builder, encKey): - builder.PrependUOffsetTRelativeSlot( - 7, flatbuffers.number_types.UOffsetTFlags.py_type(encKey), 0 - ) +def YieldAddPptCipher(builder, pptCipher): + builder.PrependUint8Slot(7, pptCipher, 0) -def AddEncKey(builder, encKey): - YieldAddEncKey(builder, encKey) +def AddPptCipher(builder, pptCipher): + YieldAddPptCipher(builder, pptCipher) -def YieldStartEncKeyVector(builder, numElems): - return builder.StartVector(1, numElems, 1) +def YieldAddPptKeyid(builder, pptKeyid): + builder.PrependUOffsetTRelativeSlot( + 8, flatbuffers.number_types.UOffsetTFlags.py_type(pptKeyid), 0 + ) -def StartEncKeyVector(builder, numElems): - return YieldStartEncKeyVector(builder, numElems) +def AddPptKeyid(builder, pptKeyid): + YieldAddPptKeyid(builder, pptKeyid) def YieldAddProgress(builder, progress): - builder.PrependBoolSlot(8, progress, 0) + builder.PrependBoolSlot(9, progress, 0) def AddProgress(builder, progress): @@ -361,7 +350,7 @@ def AddProgress(builder, progress): def YieldAddCallee(builder, callee): - builder.PrependUint64Slot(9, callee, 0) + builder.PrependUint64Slot(10, callee, 0) def AddCallee(builder, callee): @@ -370,7 +359,7 @@ def AddCallee(builder, callee): def YieldAddCalleeAuthid(builder, calleeAuthid): builder.PrependUOffsetTRelativeSlot( - 10, flatbuffers.number_types.UOffsetTFlags.py_type(calleeAuthid), 0 + 11, flatbuffers.number_types.UOffsetTFlags.py_type(calleeAuthid), 0 ) @@ -380,7 +369,7 @@ def AddCalleeAuthid(builder, calleeAuthid): def YieldAddCalleeAuthrole(builder, calleeAuthrole): builder.PrependUOffsetTRelativeSlot( - 11, flatbuffers.number_types.UOffsetTFlags.py_type(calleeAuthrole), 0 + 12, flatbuffers.number_types.UOffsetTFlags.py_type(calleeAuthrole), 0 ) @@ -390,7 +379,7 @@ def AddCalleeAuthrole(builder, calleeAuthrole): def YieldAddForwardFor(builder, forwardFor): builder.PrependUOffsetTRelativeSlot( - 12, flatbuffers.number_types.UOffsetTFlags.py_type(forwardFor), 0 + 13, flatbuffers.number_types.UOffsetTFlags.py_type(forwardFor), 0 ) @@ -399,7 +388,7 @@ def AddForwardFor(builder, forwardFor): def YieldStartForwardForVector(builder, numElems): - return builder.StartVector(8, numElems, 8) + return builder.StartVector(4, numElems, 4) def StartForwardForVector(builder, numElems): diff --git a/autobahn/wamp/message.py b/autobahn/wamp/message.py index 95d1d335f..a71e35437 100644 --- a/autobahn/wamp/message.py +++ b/autobahn/wamp/message.py @@ -61,6 +61,8 @@ "Interrupt", "Invocation", "Message", + "MessageWithAppPayload", + "MessageWithForwardFor", "Publish", "Published", "Register", @@ -653,7 +655,7 @@ def serialize(self, serializer): builder = flatbuffers.Builder(1024) # Get parent ISerializer to access payload serialization - parent_serializer = getattr(serializer, '_parent_serializer', None) + parent_serializer = getattr(serializer, "_parent_serializer", None) # this is the core method writing out this message (self) to a (new) flatbuffer # FIXME: implement this method for all classes derived from Message @@ -670,6 +672,318 @@ def serialize(self, serializer): return self._serialized[serializer] +class MessageWithAppPayload(object): + """ + Mixin for WAMP messages carrying application payload (Category 4). + + The 7 data plane messages: PUBLISH, EVENT, CALL, INVOCATION, YIELD, RESULT, ERROR + + Attributes (the "6-set"): + args, kwargs, payload, enc_algo, enc_key, enc_serializer + + These six attributes form an inseparable unit. In E2EE mode, attributes + enc_algo/enc_key/enc_serializer must all be present or all be None. + + Note on __slots__: + This mixin has __slots__ = () (empty tuple). This is REQUIRED for multiple + inheritance with __slots__. DO NOT REMOVE! Empty __slots__ means "I add no + new slots but allow derived classes to use slots". Without this, the class + would get a __dict__ and break the slots chain. See docs/wamp/message-design.rst + for detailed explanation. + + Note on initialization: + Uses _init_app_payload() method instead of __init__() to avoid complex super() + chains in multiple inheritance. Concrete classes call this method explicitly. + """ + + __slots__ = () # REQUIRED: Empty slots for mixin pattern. DO NOT REMOVE! + + def _init_app_payload( + self, + args=None, + kwargs=None, + payload=None, + enc_algo=None, + enc_key=None, + enc_serializer=None, + ): + """ + Initialize application payload attributes. + + Note: This is NOT __init__() to avoid super() complexity in multiple inheritance. + Concrete message classes call this method explicitly after Message.__init__(). + + :param args: Positional arguments (list/tuple) + :param kwargs: Keyword arguments (dict) + :param payload: Opaque payload bytes (for E2EE) + :param enc_algo: Encoding/encryption algorithm identifier + :param enc_key: Key identifier for decryption + :param enc_serializer: Payload serializer ID (e.g., "cbor", "json") + """ + self._args = args + self._kwargs = _validate_kwargs(kwargs) + self._payload = payload + self._enc_algo = enc_algo + self._enc_key = enc_key + self._enc_serializer = enc_serializer + + def _get_payload_serializer_id(self): + """ + Get the serializer ID to use for payload deserialization. + + Returns the enc_serializer if set, otherwise defaults to "cbor" + for backward compatibility. + """ + return self._enc_serializer if self._enc_serializer else "cbor" + + def _deserialize_payload(self, data_bytes, ser_id): + """ + Deserialize payload data using the specified serializer. + + Uses memoryview (zero-copy) where possible. Converts to bytes + only for JSON and FlexBuffers which don't support memoryview. + + :param data_bytes: memoryview of the serialized data + :param ser_id: Serializer ID string ("json", "cbor", "msgpack", etc.) + :return: Deserialized Python object (list, dict, etc.) + """ + # Special case: FlexBuffers (quasi-dynamic typing) + if ser_id == "flexbuffers": + import flatbuffers.flexbuffers as flexbuffers + + root = flexbuffers.GetRoot(bytes(data_bytes)) + return root + + # Import the appropriate deserializer + if ser_id == "json": + import json + + # JSON requires bytes() conversion + return json.loads(bytes(data_bytes)) + elif ser_id == "cbor": + import cbor2 + + # cbor2 supports memoryview (zero-copy) + return cbor2.loads(data_bytes) + elif ser_id == "msgpack": + import msgpack + + # msgpack supports memoryview (zero-copy) + return msgpack.unpackb(data_bytes) + elif ser_id == "ubjson": + import ubjson + + # ubjson supports memoryview (zero-copy) + return ubjson.loadb(data_bytes) + else: + # Fallback to CBOR for unknown serializers + import cbor2 + + return cbor2.loads(data_bytes) + + @property + def args(self): + """Lazy deserialization of args from FlatBuffers""" + if self._args is None and self._from_fbs: + if self._from_fbs.ArgsLength(): + ser_id = self._get_payload_serializer_id() + args_bytes = self._from_fbs.ArgsAsBytes() # Returns memoryview + + if ser_id == "flexbuffers": + root = self._deserialize_payload(args_bytes, ser_id) + self._args = root.AsVector.Value # Returns Python list + else: + self._args = self._deserialize_payload(args_bytes, ser_id) + return self._args + + @args.setter + def args(self, value): + assert value is None or type(value) in [list, tuple] + self._args = value + + @property + def kwargs(self): + """Lazy deserialization of kwargs from FlatBuffers""" + if self._kwargs is None and self._from_fbs: + if self._from_fbs.KwargsLength(): + ser_id = self._get_payload_serializer_id() + kwargs_bytes = self._from_fbs.KwargsAsBytes() # Returns memoryview + + if ser_id == "flexbuffers": + root = self._deserialize_payload(kwargs_bytes, ser_id) + self._kwargs = root.AsMap.Value # Returns Python dict + else: + self._kwargs = self._deserialize_payload(kwargs_bytes, ser_id) + return self._kwargs + + @kwargs.setter + def kwargs(self, value): + assert value is None or type(value) == dict + self._kwargs = value + + @property + def payload(self): + """Lazy deserialization of payload from FlatBuffers""" + if self._payload is None and self._from_fbs: + if self._from_fbs.PayloadLength(): + self._payload = self._from_fbs.PayloadAsBytes() + return self._payload + + @payload.setter + def payload(self, value): + assert value is None or type(value) == bytes + self._payload = value + + @property + def enc_algo(self): + """Lazy deserialization of enc_algo from FlatBuffers""" + if self._enc_algo is None and self._from_fbs: + enc_algo = self._from_fbs.PptScheme() + if enc_algo: + # Convert FlatBuffers enum integer to string + self._enc_algo = ENC_ALGOS.get(enc_algo) + return self._enc_algo + + @enc_algo.setter + def enc_algo(self, value): + assert value is None or is_valid_enc_algo(value) + self._enc_algo = value + + @property + def enc_key(self): + """Lazy deserialization of enc_key from FlatBuffers""" + if self._enc_key is None and self._from_fbs: + self._enc_key = self._from_fbs.PptKeyid() + return self._enc_key + + @enc_key.setter + def enc_key(self, value): + assert value is None or type(value) == str + self._enc_key = value + + @property + def enc_serializer(self): + """Lazy deserialization of enc_serializer from FlatBuffers""" + if self._enc_serializer is None and self._from_fbs: + enc_serializer = self._from_fbs.PptSerializer() + if enc_serializer: + # Convert FlatBuffers enum integer to string + self._enc_serializer = ENC_SERS.get(enc_serializer) + return self._enc_serializer + + @enc_serializer.setter + def enc_serializer(self, value): + assert value is None or is_valid_enc_serializer(value) + self._enc_serializer = value + + +class MessageWithForwardFor(object): + """ + Mixin for WAMP messages with forward_for (Category 3 & 4). + + Category 3: Subscribe, Unsubscribe, Register, Unregister, Cancel, Interrupt + Category 4: PUBLISH, EVENT, CALL, INVOCATION, YIELD, RESULT, ERROR + + Note on __slots__: + This mixin has __slots__ = () (empty tuple). This is REQUIRED for multiple + inheritance with __slots__. DO NOT REMOVE! Empty __slots__ means "I add no + new slots but allow derived classes to use slots". Without this, the class + would get a __dict__ and break the slots chain. See docs/wamp/message-design.rst + for detailed explanation. + + Note on initialization: + Uses _init_forward_for() method instead of __init__() to avoid complex super() + chains in multiple inheritance. Concrete classes call this method explicitly. + """ + + __slots__ = () # REQUIRED: Empty slots for mixin pattern. DO NOT REMOVE! + + def _init_forward_for(self, forward_for=None): + """ + Initialize forwarding attributes. + + Note: This is NOT __init__() to avoid super() complexity in multiple inheritance. + Concrete message classes call this method explicitly after Message.__init__(). + + :param forward_for: Forwarding chain metadata (list of dicts) + """ + self._forward_for = forward_for + + @property + def forward_for(self): + """ + Property-based access to WAMP message forward_for attribute. + + Primary purpose: Provides property-based access to the forward_for attribute + for ALL WAMP serializers (JSON, MessagePack, CBOR, UBJSON, FlatBuffers). + + FlatBuffers detail: For FlatBuffers serialization specifically, this property + performs lazy deserialization - the forward_for list is only deserialized from + the underlying FlatBuffers Principal objects when first accessed. For other + serializers (JSON, CBOR, etc.), the entire WAMP message is deserialized in one + go during message parsing, so this property simply returns the pre-parsed value. + + :return: List of forwarding chain entries, each a dict with keys: + - 'session' (int): WAMP session ID + - 'authid' (str or None): Authentication ID + - 'authrole' (str): Authentication role + :rtype: list[dict] or None + """ + if self._forward_for is None and self._from_fbs: + # Check if this message type has forward_for in FlatBuffers schema + # Category 1 messages don't have forward_for + if hasattr(self._from_fbs, 'ForwardForLength') and self._from_fbs.ForwardForLength(): + forward_for = [] + for j in range(self._from_fbs.ForwardForLength()): + principal = self._from_fbs.ForwardFor(j) + # Principal is now a table and supports authid/authrole + authid = principal.Authid() + if authid: + authid = ( + authid.decode("utf-8") + if isinstance(authid, bytes) + else authid + ) + authrole = principal.Authrole() + if authrole: + authrole = ( + authrole.decode("utf-8") + if isinstance(authrole, bytes) + else authrole + ) + forward_for.append( + { + "session": principal.Session(), + "authid": authid, + "authrole": authrole, + } + ) + self._forward_for = forward_for + return self._forward_for + + @forward_for.setter + def forward_for(self, value): + """ + Set the forward_for attribute. + + :param value: List of forwarding chain entries, each a dict with keys: + - 'session' (int): WAMP session ID + - 'authid' (str or None): Authentication ID + - 'authrole' (str): Authentication role + :type value: list[dict] or None + """ + assert value is None or type(value) == list + if value: + for ff in value: + assert type(ff) == dict + assert "session" in ff and type(ff["session"]) == int + assert "authid" in ff and ( + ff["authid"] is None or type(ff["authid"]) == str + ) + assert "authrole" in ff and type(ff["authrole"]) == str + self._forward_for = value + + class Hello(Message): """ A WAMP ``HELLO`` message. @@ -751,7 +1065,9 @@ def __init__( assert len(roles) > 0 for role in roles: assert role in ["subscriber", "publisher", "caller", "callee"] - assert isinstance(roles[role], autobahn.wamp.role.ROLE_NAME_TO_CLASS[role]) + assert isinstance( + roles[role], autobahn.wamp.role.ROLE_NAME_TO_CLASS[role] + ) if authmethods: assert type(authmethods) == list for authmethod in authmethods: @@ -963,8 +1279,9 @@ def build(self, builder, serializer=None): message_fbs.HelloGen.HelloStart(builder) # Add fields - if self.session: - message_fbs.HelloGen.HelloAddSession(builder, self.session) + session = getattr(self, 'session', None) + if session: + message_fbs.HelloGen.HelloAddSession(builder, session) if realm: message_fbs.HelloGen.HelloAddRealm(builder, realm) if authid: @@ -1297,7 +1614,9 @@ def __init__( assert len(roles) > 0 for role in roles: assert role in ["broker", "dealer"] - assert isinstance(roles[role], autobahn.wamp.role.ROLE_NAME_TO_CLASS[role]) + assert isinstance( + roles[role], autobahn.wamp.role.ROLE_NAME_TO_CLASS[role] + ) assert realm is None or type(realm) == str assert authid is None or type(authid) == str assert authrole is None or type(authrole) == str @@ -1882,8 +2201,9 @@ def build(self, builder, serializer=None): message_fbs.AbortGen.AbortStart(builder) # Add fields - if self.session: - message_fbs.AbortGen.AbortAddSession(builder, self.session) + session = getattr(self, 'session', None) + if session: + message_fbs.AbortGen.AbortAddSession(builder, session) if reason: message_fbs.AbortGen.AbortAddReason(builder, reason) if message: @@ -1891,7 +2211,14 @@ def build(self, builder, serializer=None): # End and return msg = message_fbs.AbortGen.AbortEnd(builder) - return msg + + # Wrap in Message union with type + message_fbs.Message.MessageStart(builder) + message_fbs.Message.MessageAddMsgType(builder, message_fbs.MessageType.ABORT) + message_fbs.Message.MessageAddMsg(builder, msg) + union_msg = message_fbs.Message.MessageEnd(builder) + + return union_msg @staticmethod def parse(wmsg): @@ -1999,10 +2326,15 @@ def method(self): if self._method is None and self._from_fbs: method_val = self._from_fbs.Method() # Map AuthMethod enum to string - # Note: FlatBuffers uses AuthMethod enum, Python uses string - # For now, return a default string representation - # Full enum mapping would require auth method lookup table - self._method = f"authmethod_{method_val}" if method_val else None + # AuthMethod: NULL=0, TICKET=1, CRA=2, SCRAM=3, CRYPTOSIGN=4 + AUTH_METHOD_MAP = { + 0: None, # NULL/anonymous + 1: "ticket", # TICKET + 2: "wampcra", # CRA (Challenge-Response Authentication) + 3: "wamp-scram", # SCRAM + 4: "cryptosign", # CRYPTOSIGN + } + self._method = AUTH_METHOD_MAP.get(method_val) return self._method @method.setter @@ -2056,14 +2388,22 @@ def build(self, builder, serializer=None): message_fbs.ChallengeGen.ChallengeStart(builder) # Add fields - if self.session: - message_fbs.ChallengeGen.ChallengeAddSession(builder, self.session) + session = getattr(self, 'session', None) + if session: + message_fbs.ChallengeGen.ChallengeAddSession(builder, session) # Method: Map string to AuthMethod enum - # For simplicity, default to ANONYMOUS (0) for now - # Full implementation would require reverse lookup table + # AuthMethod: NULL=0, TICKET=1, CRA=2, SCRAM=3, CRYPTOSIGN=4 if self.method: - message_fbs.ChallengeGen.ChallengeAddMethod(builder, 0) # Default to ANONYMOUS + STRING_TO_AUTH_METHOD = { + "anonymous": 0, # NULL + "ticket": 1, # TICKET + "wampcra": 2, # CRA (Challenge-Response Authentication) + "wamp-scram": 3, # SCRAM + "cryptosign": 4, # CRYPTOSIGN + } + method_enum = STRING_TO_AUTH_METHOD.get(self.method, 0) + message_fbs.ChallengeGen.ChallengeAddMethod(builder, method_enum) # TODO: Add proper Map serialization for extra field # if self.extra: @@ -2072,7 +2412,14 @@ def build(self, builder, serializer=None): # End and return msg = message_fbs.ChallengeGen.ChallengeEnd(builder) - return msg + + # Wrap in Message union with type + message_fbs.Message.MessageStart(builder) + message_fbs.Message.MessageAddMsgType(builder, message_fbs.MessageType.CHALLENGE) + message_fbs.Message.MessageAddMsg(builder, msg) + union_msg = message_fbs.Message.MessageEnd(builder) + + return union_msg @staticmethod def parse(wmsg): @@ -2200,7 +2547,9 @@ def cast(buf): :returns: An instance of this class. """ - return Authenticate(from_fbs=message_fbs.Authenticate.GetRootAsAuthenticate(buf, 0)) + return Authenticate( + from_fbs=message_fbs.Authenticate.GetRootAsAuthenticate(buf, 0) + ) def build(self, builder, serializer=None): """ @@ -2227,8 +2576,9 @@ def build(self, builder, serializer=None): message_fbs.AuthenticateGen.AuthenticateStart(builder) # Add fields - if self.session: - message_fbs.AuthenticateGen.AuthenticateAddSession(builder, self.session) + session = getattr(self, 'session', None) + if session: + message_fbs.AuthenticateGen.AuthenticateAddSession(builder, session) if signature: message_fbs.AuthenticateGen.AuthenticateAddSignature(builder, signature) @@ -2239,7 +2589,14 @@ def build(self, builder, serializer=None): # End and return msg = message_fbs.AuthenticateGen.AuthenticateEnd(builder) - return msg + + # Wrap in Message union with type + message_fbs.Message.MessageStart(builder) + message_fbs.Message.MessageAddMsgType(builder, message_fbs.MessageType.AUTHENTICATE) + message_fbs.Message.MessageAddMsg(builder, msg) + union_msg = message_fbs.Message.MessageEnd(builder) + + return union_msg @staticmethod def parse(wmsg): @@ -2309,7 +2666,9 @@ class Goodbye(Message): "_resumable", ) - def __init__(self, reason=DEFAULT_REASON, message=None, resumable=None, from_fbs=None): + def __init__( + self, reason=DEFAULT_REASON, message=None, resumable=None, from_fbs=None + ): """ :param reason: Optional WAMP or application error URI for closing reason. @@ -2420,8 +2779,9 @@ def build(self, builder, serializer=None): message_fbs.GoodbyeGen.GoodbyeStart(builder) # Add fields - if self.session: - message_fbs.GoodbyeGen.GoodbyeAddSession(builder, self.session) + session = getattr(self, 'session', None) + if session: + message_fbs.GoodbyeGen.GoodbyeAddSession(builder, session) if reason: message_fbs.GoodbyeGen.GoodbyeAddReason(builder, reason) if message: @@ -2431,7 +2791,14 @@ def build(self, builder, serializer=None): # End and return msg = message_fbs.GoodbyeGen.GoodbyeEnd(builder) - return msg + + # Wrap in Message union with type + message_fbs.Message.MessageStart(builder) + message_fbs.Message.MessageAddMsgType(builder, message_fbs.MessageType.GOODBYE) + message_fbs.Message.MessageAddMsg(builder, msg) + union_msg = message_fbs.Message.MessageEnd(builder) + + return union_msg @staticmethod def parse(wmsg): @@ -2498,7 +2865,7 @@ def marshal(self): return [Goodbye.MESSAGE_TYPE, details, self.reason] -class Error(Message): +class Error(MessageWithAppPayload, MessageWithForwardFor, Message): """ A WAMP ``ERROR`` message. @@ -2515,27 +2882,31 @@ class Error(Message): The WAMP message code for this type of message. """ + # Note: Slots from Message base class (_from_fbs) are inherited, not redefined here __slots__ = ( - "request_type", - "request", - "error", - "args", - "kwargs", - "payload", - "enc_algo", - "enc_key", - "enc_serializer", - "callee", - "callee_authid", - "callee_authrole", - "forward_for", + # Error-specific slots (FlatBuffers schema types in comments) + "_request_type", # uint8 (message type) + "_request", # uint64 (key) + "_error", # string (required, uri) + "_callee", # uint64 (session id) + "_callee_authid", # string (principal) + "_callee_authrole", # string (principal) + # From MessageWithAppPayload mixin + "_args", # [uint8] - serialized args + "_kwargs", # [uint8] - serialized kwargs + "_payload", # [uint8] - opaque payload + "_enc_algo", # Payload (enum) - encryption algorithm + "_enc_key", # [uint8] - encryption key + "_enc_serializer", # Serializer (enum) - payload serializer + # From MessageWithForwardFor mixin + "_forward_for", # [Principal] - forwarding chain ) def __init__( self, - request_type, - request, - error, + request_type=None, + request=None, + error=None, args=None, kwargs=None, payload=None, @@ -2546,6 +2917,7 @@ def __init__( callee_authid=None, callee_authrole=None, forward_for=None, + from_fbs=None, ): """ @@ -2590,11 +2962,11 @@ def __init__( :param forward_for: When this Error is forwarded for a client/callee (or from an intermediary router). :type forward_for: list[dict] """ - assert type(request_type) == int - assert type(request) == int - assert type(error) == str - assert args is None or type(args) in [list, tuple] - assert kwargs is None or type(kwargs) == dict + assert request_type is None or type(request_type) == int + assert request is None or type(request) == int + assert error is None or type(error) == str + assert args is None or type(args) in [list, tuple, str, bytes] + assert kwargs is None or type(kwargs) in [dict, str, bytes] assert payload is None or type(payload) == bytes assert payload is None or ( payload is not None and args is None and kwargs is None @@ -2621,26 +2993,99 @@ def __init__( ) assert "authrole" in ff and type(ff["authrole"]) == str - Message.__init__(self) - self.request_type = request_type - self.request = request - self.error = error - self.args = args - self.kwargs = _validate_kwargs(kwargs) - self.payload = payload + # Initialize Message base class + Message.__init__(self, from_fbs=from_fbs) - # payload transparency related knobs - self.enc_algo = enc_algo - self.enc_key = enc_key - self.enc_serializer = enc_serializer + # Initialize mixin attributes + self._init_app_payload( + args=args, + kwargs=kwargs, + payload=payload, + enc_algo=enc_algo, + enc_key=enc_key, + enc_serializer=enc_serializer, + ) + self._init_forward_for(forward_for=forward_for) + + # Initialize Error-specific attributes + self._request_type = request_type + self._request = request + self._error = error # effective callee that responded with the error - self.callee = callee - self.callee_authid = callee_authid - self.callee_authrole = callee_authrole + self._callee = callee + self._callee_authid = callee_authid + self._callee_authrole = callee_authrole + + @property + def request_type(self): + if self._request_type is None and self._from_fbs: + self._request_type = self._from_fbs.RequestType() + return self._request_type + + @request_type.setter + def request_type(self, value): + assert value is None or type(value) == int + self._request_type = value + + @property + def request(self): + if self._request is None and self._from_fbs: + self._request = self._from_fbs.Request() + return self._request + + @request.setter + def request(self, value): + assert value is None or type(value) == int + self._request = value + + @property + def error(self): + if self._error is None and self._from_fbs: + s = self._from_fbs.Error() + if s: + self._error = s.decode("utf8") + return self._error + + @error.setter + def error(self, value): + assert value is None or type(value) == str + self._error = value + + # NOTE: args, kwargs, payload properties are provided by MessageWithAppPayload mixin + + @property + def callee(self): + # Note: Error FlatBuffers schema doesn't include callee fields yet + return self._callee - # message forwarding - self.forward_for = forward_for + @callee.setter + def callee(self, value): + assert value is None or type(value) == int + self._callee = value + + @property + def callee_authid(self): + # Note: Error FlatBuffers schema doesn't include callee fields yet + return self._callee_authid + + @callee_authid.setter + def callee_authid(self, value): + assert value is None or type(value) == str + self._callee_authid = value + + @property + def callee_authrole(self): + # Note: Error FlatBuffers schema doesn't include callee fields yet + return self._callee_authrole + + @callee_authrole.setter + def callee_authrole(self, value): + assert value is None or type(value) == str + self._callee_authrole = value + + # NOTE: enc_algo, enc_key, enc_serializer properties are provided by MessageWithAppPayload mixin + # NOTE: forward_for property is provided by MessageWithForwardFor mixin @staticmethod def cast(buf): @@ -2671,7 +3116,7 @@ def build(self, builder, serializer=None): enc_key = self.enc_key if enc_key: - enc_key = builder.CreateByteVector(enc_key) + enc_key = builder.CreateString(enc_key) callee_authid = self.callee_authid if callee_authid: @@ -2724,21 +3169,28 @@ def build(self, builder, serializer=None): if payload: message_fbs.ErrorGen.ErrorAddPayload(builder, payload) if self.enc_algo: - message_fbs.ErrorGen.ErrorAddEncAlgo(builder, self.enc_algo) + message_fbs.ErrorGen.ErrorAddPptScheme(builder, self.enc_algo) if self.enc_serializer: - message_fbs.ErrorGen.ErrorAddEncSerializer(builder, self.enc_serializer) + message_fbs.ErrorGen.ErrorAddPptSerializer(builder, self.enc_serializer) if enc_key: - message_fbs.ErrorGen.ErrorAddEncKey(builder, enc_key) + message_fbs.ErrorGen.ErrorAddPptKeyid(builder, enc_key) if forward_for: message_fbs.ErrorGen.ErrorAddForwardFor(builder, forward_for) msg = message_fbs.ErrorGen.ErrorEnd(builder) - return msg - - @staticmethod - def parse(wmsg): - """ - Verifies and parses an unserialized raw message into an actual WAMP message instance. + + # Wrap in Message union with type + message_fbs.Message.MessageStart(builder) + message_fbs.Message.MessageAddMsgType(builder, message_fbs.MessageType.ERROR) + message_fbs.Message.MessageAddMsg(builder, msg) + union_msg = message_fbs.Message.MessageEnd(builder) + + return union_msg + + @staticmethod + def parse(wmsg): + """ + Verifies and parses an unserialized raw message into an actual WAMP message instance. :param wmsg: The unserialized raw message. :type wmsg: list @@ -2961,7 +3413,7 @@ def marshal(self): ] -class Publish(Message): +class Publish(MessageWithAppPayload, MessageWithForwardFor, Message): """ A WAMP ``PUBLISH`` message. @@ -2978,45 +3430,30 @@ class Publish(Message): The WAMP message code for this type of message. """ + # Note: Slots from Message base class (_from_fbs) are inherited, not redefined here __slots__ = ( - # uint64 (key) - "_request", - # string (required, uri) - "_topic", - # [uint8] - "_args", - # [uint8] - "_kwargs", - # [uint8] - "_payload", - # Payload => uint8 - "_enc_algo", - # Serializer => uint8 - "_enc_serializer", - # [uint8] - "_enc_key", - # bool - "_acknowledge", - # bool - "_exclude_me", - # [uint64] - "_exclude", - # [string] (principal) - "_exclude_authid", - # [string] (principal) - "_exclude_authrole", - # [uint64] - "_eligible", - # [string] (principal) - "_eligible_authid", - # [string] (principal) - "_eligible_authrole", - # bool - "_retain", - # string - "_transaction_hash", - # [Principal] - "_forward_for", + # Publish-specific slots (FlatBuffers schema types in comments) + "_request", # uint64 (key) + "_topic", # string (required, uri) + "_acknowledge", # bool + "_exclude_me", # bool + "_exclude", # [uint64] + "_exclude_authid", # [string] (principal) + "_exclude_authrole", # [string] (principal) + "_eligible", # [uint64] + "_eligible_authid", # [string] (principal) + "_eligible_authrole", # [string] (principal) + "_retain", # bool + "_transaction_hash", # string + # From MessageWithAppPayload mixin + "_args", # [uint8] - serialized args + "_kwargs", # [uint8] - serialized kwargs + "_payload", # [uint8] - opaque payload + "_enc_algo", # Payload (enum) - encryption algorithm + "_enc_key", # [uint8] - encryption key + "_enc_serializer", # Serializer (enum) - payload serializer + # From MessageWithForwardFor mixin + "_forward_for", # [Principal] - forwarding chain ) def __init__( @@ -3170,12 +3607,23 @@ def __init__( ) assert "authrole" in ff and type(ff["authrole"]) == str + # Initialize Message base class Message.__init__(self, from_fbs=from_fbs) + + # Initialize mixin attributes + self._init_app_payload( + args=args, + kwargs=kwargs, + payload=payload, + enc_algo=enc_algo, + enc_key=enc_key, + enc_serializer=enc_serializer, + ) + self._init_forward_for(forward_for=forward_for) + + # Initialize Publish-specific attributes self._request = request self._topic = topic - self._args = args - self._kwargs = _validate_kwargs(kwargs) - self._payload = payload self._acknowledge = acknowledge # publisher exlusion and black-/whitelisting @@ -3193,14 +3641,6 @@ def __init__( # application provided transaction hash for event self._transaction_hash = transaction_hash - # payload transparency related knobs - self._enc_algo = enc_algo - self._enc_key = enc_key - self._enc_serializer = enc_serializer - - # message forwarding - self._forward_for = forward_for - def __eq__(self, other): if not isinstance(other, self.__class__): return False @@ -3273,41 +3713,7 @@ def topic(self, value): assert value is None or type(value) == str self._topic = value - @property - def args(self): - if self._args is None and self._from_fbs: - if self._from_fbs.ArgsLength(): - self._args = cbor2.loads(bytes(self._from_fbs.ArgsAsBytes())) - return self._args - - @args.setter - def args(self, value): - assert value is None or type(value) in [list, tuple] - self._args = value - - @property - def kwargs(self): - if self._kwargs is None and self._from_fbs: - if self._from_fbs.KwargsLength(): - self._kwargs = cbor2.loads(bytes(self._from_fbs.KwargsAsBytes())) - return self._kwargs - - @kwargs.setter - def kwargs(self, value): - assert value is None or type(value) == dict - self._kwargs = value - - @property - def payload(self): - if self._payload is None and self._from_fbs: - if self._from_fbs.PayloadLength(): - self._payload = self._from_fbs.PayloadAsBytes() - return self._payload - - @payload.setter - def payload(self, value): - assert value is None or type(value) == bytes - self._payload = value + # NOTE: args, kwargs, payload properties are provided by MessageWithAppPayload mixin @property def acknowledge(self): @@ -3477,82 +3883,8 @@ def transaction_hash(self, value): assert value is None or type(value) == str self._transaction_hash = value - @property - def enc_algo(self): - if self._enc_algo is None and self._from_fbs: - enc_algo = self._from_fbs.EncAlgo() - if enc_algo: - self._enc_algo = enc_algo - return self._enc_algo - - @enc_algo.setter - def enc_algo(self, value): - assert value is None or value in [ - ENC_ALGO_CRYPTOBOX, - ENC_ALGO_MQTT, - ENC_ALGO_XBR, - ] - self._enc_algo = value - - @property - def enc_key(self): - if self._enc_key is None and self._from_fbs: - if self._from_fbs.EncKeyLength(): - self._enc_key = self._from_fbs.EncKeyAsBytes() - return self._enc_key - - @enc_key.setter - def enc_key(self, value): - assert value is None or type(value) == bytes - self._enc_key = value - - @property - def enc_serializer(self): - if self._enc_serializer is None and self._from_fbs: - enc_serializer = self._from_fbs.EncSerializer() - if enc_serializer: - self._enc_serializer = enc_serializer - return self._enc_serializer - - @enc_serializer.setter - def enc_serializer(self, value): - assert value is None or value in [ - ENC_SER_JSON, - ENC_SER_MSGPACK, - ENC_SER_CBOR, - ENC_SER_UBJSON, - ] - self._enc_serializer = value - - @property - def forward_for(self): - if self._forward_for is None and self._from_fbs: - if self._from_fbs.ForwardForLength(): - forward_for = [] - for j in range(self._from_fbs.ForwardForLength()): - principal = self._from_fbs.ForwardFor(j) - # Principal struct currently only has session field - # (authid/authrole are commented out in schema due to FlatBuffers struct limitations) - forward_for.append({ - 'session': principal.Session(), - 'authid': None, - 'authrole': None, - }) - self._forward_for = forward_for - return self._forward_for - - @forward_for.setter - def forward_for(self, value): - assert value is None or type(value) == list - if value: - for ff in value: - assert type(ff) == dict - assert "session" in ff and type(ff["session"]) == int - assert "authid" in ff and ( - ff["authid"] is None or type(ff["authid"]) == str - ) - assert "authrole" in ff and type(ff["authrole"]) == str - self._forward_for = value + # NOTE: enc_algo, enc_key, enc_serializer properties are provided by MessageWithAppPayload mixin + # NOTE: forward_for property is provided by MessageWithForwardFor mixin @staticmethod def cast(buf): @@ -3589,7 +3921,7 @@ def build(self, builder, serializer=None): enc_key = self.enc_key if enc_key: - enc_key = builder.CreateByteVector(enc_key) + enc_key = builder.CreateString(enc_key) # exclude: [int] exclude = self.exclude @@ -3663,6 +3995,43 @@ def build(self, builder, serializer=None): builder.PrependUOffsetTRelative(o) eligible_authrole = builder.EndVector(len(_eligible_authrole)) + # forward_for: [Principal] + forward_for = None + if self.forward_for: + from autobahn.wamp.gen.wamp.proto.Principal import ( + PrincipalStart, + PrincipalAddSession, + PrincipalAddAuthid, + PrincipalAddAuthrole, + PrincipalEnd, + ) + + _forward_for = [] + for ff in self.forward_for: + # Build Principal table + authid = ( + builder.CreateString(ff["authid"]) if ff.get("authid") else None + ) + authrole = ( + builder.CreateString(ff["authrole"]) if ff.get("authrole") else None + ) + + PrincipalStart(builder) + PrincipalAddSession(builder, ff["session"]) + if authid: + PrincipalAddAuthid(builder, authid) + if authrole: + PrincipalAddAuthrole(builder, authrole) + _forward_for.append(PrincipalEnd(builder)) + + # Create vector of Principal tables + message_fbs.PublishGen.PublishStartForwardForVector( + builder, len(_forward_for) + ) + for o in reversed(_forward_for): + builder.PrependUOffsetTRelative(o) + forward_for = builder.EndVector(len(_forward_for)) + # now start and build a new object .. message_fbs.PublishGen.PublishStart(builder) @@ -3680,11 +4049,15 @@ def build(self, builder, serializer=None): message_fbs.PublishGen.PublishAddPayload(builder, payload) if self.enc_algo: - message_fbs.PublishGen.PublishAddEncAlgo(builder, self.enc_algo) + # Convert string enc_algo to FlatBuffers enum value + enc_algo_int = ENC_ALGOS_FROMSTR.get(self.enc_algo, 0) + message_fbs.PublishGen.PublishAddPptScheme(builder, enc_algo_int) if self.enc_serializer: - message_fbs.PublishGen.PublishAddEncSerializer(builder, self.enc_serializer) + # Convert string enc_serializer to FlatBuffers enum value + enc_serializer_int = ENC_SERS_FROMSTR.get(self.enc_serializer, 0) + message_fbs.PublishGen.PublishAddPptSerializer(builder, enc_serializer_int) if enc_key: - message_fbs.PublishGen.PublishAddEncKey(builder, enc_key) + message_fbs.PublishGen.PublishAddPptKeyid(builder, enc_key) if self.acknowledge is not None: message_fbs.PublishGen.PublishAddAcknowledge(builder, self.acknowledge) @@ -3710,11 +4083,10 @@ def build(self, builder, serializer=None): if self.retain is not None: message_fbs.PublishGen.PublishAddRetain(builder, self.retain) if transaction_hash is not None: - message_fbs.PublishGen.PublishAddTransactionHash( - builder, self.transaction_hash - ) + message_fbs.PublishGen.PublishAddTransactionHash(builder, transaction_hash) - # FIXME: add forward_for + if forward_for: + message_fbs.PublishGen.PublishAddForwardFor(builder, forward_for) msg = message_fbs.PublishGen.PublishEnd(builder) @@ -4055,12 +4427,18 @@ def marshal(self): options = self.marshal_options() if self.payload: + # Convert memoryview to bytes for non-FlatBuffers serializers + payload = ( + bytes(self.payload) + if isinstance(self.payload, memoryview) + else self.payload + ) return [ Publish.MESSAGE_TYPE, self.request, options, self.topic, - self.payload, + payload, ] else: if self.kwargs: @@ -4170,18 +4548,26 @@ def cast(buf): def build(self, builder, serializer=None): message_fbs.PublishedGen.PublishedStart(builder) - if self.session: - message_fbs.PublishedGen.PublishedAddSession(builder, self.session) + session = getattr(self, 'session', None) + if session: + message_fbs.PublishedGen.PublishedAddSession(builder, session) if self.request: message_fbs.PublishedGen.PublishedAddRequest(builder, self.request) if self.publication: message_fbs.PublishedGen.PublishedAddPublication(builder, self.publication) msg = message_fbs.PublishedGen.PublishedEnd(builder) - return msg + + # Wrap in Message union with type + message_fbs.Message.MessageStart(builder) + message_fbs.Message.MessageAddMsgType(builder, message_fbs.MessageType.PUBLISHED) + message_fbs.Message.MessageAddMsg(builder, msg) + union_msg = message_fbs.Message.MessageEnd(builder) + + return union_msg -class Subscribe(Message): +class Subscribe(MessageWithForwardFor, Message): """ A WAMP ``SUBSCRIBE`` message. @@ -4197,20 +4583,26 @@ class Subscribe(Message): MATCH_PREFIX = "prefix" MATCH_WILDCARD = "wildcard" + # Note: Slots from Message base class (_from_fbs) are inherited, not redefined here __slots__ = ( - # uint64 (key) - "_request", - # string (required, uri_pattern) - "_topic", - # Match (enum) - "_match", - # bool - "_get_retained", - # [Principal] - "_forward_for", + # Subscribe-specific slots (FlatBuffers schema types in comments) + "_request", # uint64 (key) + "_topic", # string (required, uri_pattern) + "_match", # Match (enum) + "_get_retained", # bool + # From MessageWithForwardFor mixin + "_forward_for", # [Principal] ) - def __init__(self, request=None, topic=None, match=None, get_retained=None, forward_for=None, from_fbs=None): + def __init__( + self, + request=None, + topic=None, + match=None, + get_retained=None, + forward_for=None, + from_fbs=None, + ): """ :param request: The WAMP request ID of this request. @@ -4248,12 +4640,17 @@ def __init__(self, request=None, topic=None, match=None, get_retained=None, forw ) assert "authrole" in ff and type(ff["authrole"]) == str + # Initialize Message base class Message.__init__(self, from_fbs=from_fbs) + + # Initialize mixin attributes + self._init_forward_for(forward_for=forward_for) + + # Initialize Subscribe-specific attributes self._request = request self._topic = topic self._match = match or Subscribe.MATCH_EXACT self._get_retained = get_retained - self._forward_for = forward_for def __eq__(self, other): if not isinstance(other, self.__class__): @@ -4329,15 +4726,7 @@ def get_retained(self, value): assert value is None or type(value) == bool self._get_retained = value - @property - def forward_for(self): - # forward_for is not in FlatBuffers schema yet - return self._forward_for - - @forward_for.setter - def forward_for(self, value): - assert value is None or type(value) == list - self._forward_for = value + # Note: forward_for property is provided by MessageWithForwardFor mixin @staticmethod def parse(wmsg): @@ -4471,8 +4860,9 @@ def build(self, builder, serializer=None): message_fbs.SubscribeGen.SubscribeStart(builder) # Add fields - if self.session: - message_fbs.SubscribeGen.SubscribeAddSession(builder, self.session) + session = getattr(self, 'session', None) + if session: + message_fbs.SubscribeGen.SubscribeAddSession(builder, session) if self.request: message_fbs.SubscribeGen.SubscribeAddRequest(builder, self.request) if topic: @@ -4493,7 +4883,14 @@ def build(self, builder, serializer=None): # End and return msg = message_fbs.SubscribeGen.SubscribeEnd(builder) - return msg + + # Wrap in Message union with type + message_fbs.Message.MessageStart(builder) + message_fbs.Message.MessageAddMsgType(builder, message_fbs.MessageType.SUBSCRIBE) + message_fbs.Message.MessageAddMsg(builder, msg) + union_msg = message_fbs.Message.MessageEnd(builder) + + return union_msg class Subscribed(Message): @@ -4582,18 +4979,28 @@ def cast(buf): def build(self, builder, serializer=None): message_fbs.SubscribedGen.SubscribedStart(builder) - if self.session: - message_fbs.SubscribedGen.SubscribedAddSession(builder, self.session) + session = getattr(self, 'session', None) + if session: + message_fbs.SubscribedGen.SubscribedAddSession(builder, session) if self.request: message_fbs.SubscribedGen.SubscribedAddRequest(builder, self.request) if self.subscription: - message_fbs.SubscribedGen.SubscribedAddSubscription(builder, self.subscription) + message_fbs.SubscribedGen.SubscribedAddSubscription( + builder, self.subscription + ) msg = message_fbs.SubscribedGen.SubscribedEnd(builder) - return msg + + # Wrap in Message union with type + message_fbs.Message.MessageStart(builder) + message_fbs.Message.MessageAddMsgType(builder, message_fbs.MessageType.SUBSCRIBED) + message_fbs.Message.MessageAddMsg(builder, msg) + union_msg = message_fbs.Message.MessageEnd(builder) + + return union_msg -class Unsubscribe(Message): +class Unsubscribe(MessageWithForwardFor, Message): """ A WAMP ``UNSUBSCRIBE`` message. @@ -4608,16 +5015,18 @@ class Unsubscribe(Message): The WAMP message code for this type of message. """ + # Note: Slots from Message base class (_from_fbs) are inherited, not redefined here __slots__ = ( - # uint64 (key) - "_request", - # uint64 - "_subscription", - # [Principal] - "_forward_for", + # Unsubscribe-specific slots (FlatBuffers schema types in comments) + "_request", # uint64 (key) + "_subscription", # uint64 + # From MessageWithForwardFor mixin + "_forward_for", # [Principal] ) - def __init__(self, request=None, subscription=None, forward_for=None, from_fbs=None): + def __init__( + self, request=None, subscription=None, forward_for=None, from_fbs=None + ): """ :param request: The WAMP request ID of this request. @@ -4641,10 +5050,15 @@ def __init__(self, request=None, subscription=None, forward_for=None, from_fbs=N ) assert "authrole" in ff and type(ff["authrole"]) == str + # Initialize Message base class Message.__init__(self, from_fbs=from_fbs) + + # Initialize mixin attributes + self._init_forward_for(forward_for=forward_for) + + # Initialize Unsubscribe-specific attributes self._request = request self._subscription = subscription - self._forward_for = forward_for def __eq__(self, other): if not isinstance(other, self.__class__): @@ -4684,15 +5098,7 @@ def subscription(self, value): assert value is None or type(value) == int self._subscription = value - @property - def forward_for(self): - # forward_for is not in FlatBuffers schema yet - return self._forward_for - - @forward_for.setter - def forward_for(self, value): - assert value is None or type(value) == list - self._forward_for = value + # Note: forward_for property is provided by MessageWithForwardFor mixin @staticmethod def parse(wmsg): @@ -4768,8 +5174,9 @@ def cast(buf): def build(self, builder, serializer=None): message_fbs.UnsubscribeGen.UnsubscribeStart(builder) - if self.session: - message_fbs.UnsubscribeGen.UnsubscribeAddSession(builder, self.session) + session = getattr(self, 'session', None) + if session: + message_fbs.UnsubscribeGen.UnsubscribeAddSession(builder, session) if self.request: message_fbs.UnsubscribeGen.UnsubscribeAddRequest(builder, self.request) if self.subscription: @@ -4778,7 +5185,14 @@ def build(self, builder, serializer=None): ) msg = message_fbs.UnsubscribeGen.UnsubscribeEnd(builder) - return msg + + # Wrap in Message union with type + message_fbs.Message.MessageStart(builder) + message_fbs.Message.MessageAddMsgType(builder, message_fbs.MessageType.UNSUBSCRIBE) + message_fbs.Message.MessageAddMsg(builder, msg) + union_msg = message_fbs.Message.MessageEnd(builder) + + return union_msg class Unsubscribed(Message): @@ -4961,8 +5375,9 @@ def build(self, builder, serializer=None): message_fbs.UnsubscribedGen.UnsubscribedStart(builder) # Add fields - if self.session: - message_fbs.UnsubscribedGen.UnsubscribedAddSession(builder, self.session) + session = getattr(self, 'session', None) + if session: + message_fbs.UnsubscribedGen.UnsubscribedAddSession(builder, session) if self.request: message_fbs.UnsubscribedGen.UnsubscribedAddRequest(builder, self.request) if self.subscription: @@ -4974,10 +5389,17 @@ def build(self, builder, serializer=None): # End and return msg = message_fbs.UnsubscribedGen.UnsubscribedEnd(builder) - return msg + + # Wrap in Message union with type + message_fbs.Message.MessageStart(builder) + message_fbs.Message.MessageAddMsgType(builder, message_fbs.MessageType.UNSUBSCRIBED) + message_fbs.Message.MessageAddMsg(builder, msg) + union_msg = message_fbs.Message.MessageEnd(builder) + + return union_msg -class Event(Message): +class Event(MessageWithAppPayload, MessageWithForwardFor, Message): """ A WAMP ``EVENT`` message. @@ -4994,39 +5416,27 @@ class Event(Message): The WAMP message code for this type of message. """ + # Note: Slots from Message base class (_from_fbs) are inherited, not redefined here __slots__ = ( - # uint64 - "_subscription", - # uint64 - "_publication", - # [uint8] - "_args", - # [uint8] - "_kwargs", - # [uint8] - "_payload", - # Payload => uint8 - "_enc_algo", - # Serializer => uint8 - "_enc_serializer", - # [uint8] - "_enc_key", - # uint64 - "_publisher", - # string (principal) - "_publisher_authid", - # string (principal) - "_publisher_authrole", - # string (uri) - "_topic", - # bool - "_retained", - # string - "_transaction_hash", - # bool - FIXME: rename to "acknowledge" - "_x_acknowledged_delivery", - # [Principal] - "_forward_for", + # Event-specific slots (FlatBuffers schema types in comments) + "_subscription", # uint64 + "_publication", # uint64 + "_publisher", # uint64 + "_publisher_authid", # string (principal) + "_publisher_authrole", # string (principal) + "_topic", # string (uri) + "_retained", # bool + "_transaction_hash", # string + "_x_acknowledged_delivery", # bool - FIXME: rename to "acknowledge" + # From MessageWithAppPayload mixin + "_args", # [uint8] - serialized args + "_kwargs", # [uint8] - serialized kwargs + "_payload", # [uint8] - opaque payload + "_enc_algo", # Payload (enum) - encryption algorithm + "_enc_key", # [uint8] - encryption key + "_enc_serializer", # Serializer (enum) - payload serializer + # From MessageWithForwardFor mixin + "_forward_for", # [Principal] - forwarding chain ) def __init__( @@ -5135,12 +5545,23 @@ def __init__( ) assert "authrole" in ff and type(ff["authrole"]) == str + # Initialize Message base class Message.__init__(self, from_fbs=from_fbs) + + # Initialize mixin attributes + self._init_app_payload( + args=args, + kwargs=kwargs, + payload=payload, + enc_algo=enc_algo, + enc_key=enc_key, + enc_serializer=enc_serializer, + ) + self._init_forward_for(forward_for=forward_for) + + # Initialize Event-specific attributes self._subscription = subscription self._publication = publication - self._args = args - self._kwargs = _validate_kwargs(kwargs) - self._payload = payload self._publisher = publisher self._publisher_authid = publisher_authid self._publisher_authrole = publisher_authrole @@ -5148,10 +5569,6 @@ def __init__( self._retained = retained self._transaction_hash = transaction_hash self._x_acknowledged_delivery = x_acknowledged_delivery - self._enc_algo = enc_algo - self._enc_key = enc_key - self._enc_serializer = enc_serializer - self._forward_for = forward_for def __eq__(self, other): if not isinstance(other, self.__class__): @@ -5217,41 +5634,7 @@ def publication(self, value): assert value is None or type(value) == int self._publication = value - @property - def args(self): - if self._args is None and self._from_fbs: - if self._from_fbs.ArgsLength(): - self._args = cbor2.loads(bytes(self._from_fbs.ArgsAsBytes())) - return self._args - - @args.setter - def args(self, value): - assert value is None or type(value) in [list, tuple] - self._args = value - - @property - def kwargs(self): - if self._kwargs is None and self._from_fbs: - if self._from_fbs.KwargsLength(): - self._kwargs = cbor2.loads(bytes(self._from_fbs.KwargsAsBytes())) - return self._kwargs - - @kwargs.setter - def kwargs(self, value): - assert value is None or type(value) == dict - self._kwargs = value - - @property - def payload(self): - if self._payload is None and self._from_fbs: - if self._from_fbs.PayloadLength(): - self._payload = self._from_fbs.PayloadAsBytes() - return self._payload - - @payload.setter - def payload(self, value): - assert value is None or type(value) == bytes - self._payload = value + # Note: args, kwargs, payload properties are provided by MessageWithAppPayload mixin @property def publisher(self): @@ -5308,7 +5691,10 @@ def topic(self, value): @property def retained(self): if self._retained is None and self._from_fbs: - self._retained = self._from_fbs.Retained() + # Only set if non-default (True). FlatBuffers returns False for unset booleans. + val = self._from_fbs.Retained() + if val: # Only set if True (non-default) + self._retained = val return self._retained @retained.setter @@ -5342,62 +5728,7 @@ def x_acknowledged_delivery(self, value): assert value is None or type(value) == bool self._x_acknowledged_delivery = value - @property - def enc_algo(self): - if self._enc_algo is None and self._from_fbs: - enc_algo = self._from_fbs.EncAlgo() - if enc_algo: - self._enc_algo = enc_algo - return self._enc_algo - - @enc_algo.setter - def enc_algo(self, value): - assert value is None or value in [ - ENC_ALGO_CRYPTOBOX, - ENC_ALGO_MQTT, - ENC_ALGO_XBR, - ] - self._enc_algo = value - - @property - def enc_key(self): - if self._enc_key is None and self._from_fbs: - if self._from_fbs.EncKeyLength(): - self._enc_key = self._from_fbs.EncKeyAsBytes() - return self._enc_key - - @enc_key.setter - def enc_key(self, value): - assert value is None or type(value) == bytes - self._enc_key = value - - @property - def enc_serializer(self): - if self._enc_serializer is None and self._from_fbs: - enc_serializer = self._from_fbs.EncSerializer() - if enc_serializer: - self._enc_serializer = enc_serializer - return self._enc_serializer - - @enc_serializer.setter - def enc_serializer(self, value): - assert value is None or value in [ - ENC_SER_JSON, - ENC_SER_MSGPACK, - ENC_SER_CBOR, - ENC_SER_UBJSON, - ] - self._enc_serializer = value - - @property - def forward_for(self): - # FIXME - return self._forward_for - - @forward_for.setter - def forward_for(self, value): - # FIXME - self._forward_for = value + # Note: enc_algo, enc_key, enc_serializer, forward_for properties are provided by mixins @staticmethod def cast(buf): @@ -5442,7 +5773,42 @@ def build(self, builder, serializer=None): enc_key = self.enc_key if enc_key: - enc_key = builder.CreateByteVector(enc_key) + enc_key = builder.CreateString(enc_key) + + # forward_for: [Principal] + forward_for = None + if self.forward_for: + from autobahn.wamp.gen.wamp.proto.Principal import ( + PrincipalStart, + PrincipalAddSession, + PrincipalAddAuthid, + PrincipalAddAuthrole, + PrincipalEnd, + ) + + _forward_for = [] + for ff in self.forward_for: + # Build Principal table + authid = ( + builder.CreateString(ff["authid"]) if ff.get("authid") else None + ) + authrole = ( + builder.CreateString(ff["authrole"]) if ff.get("authrole") else None + ) + + PrincipalStart(builder) + PrincipalAddSession(builder, ff["session"]) + if authid: + PrincipalAddAuthid(builder, authid) + if authrole: + PrincipalAddAuthrole(builder, authrole) + _forward_for.append(PrincipalEnd(builder)) + + # Create vector of Principal tables + message_fbs.EventGen.EventStartForwardForVector(builder, len(_forward_for)) + for o in reversed(_forward_for): + builder.PrependUOffsetTRelative(o) + forward_for = builder.EndVector(len(_forward_for)) message_fbs.EventGen.EventStart(builder) @@ -5477,13 +5843,18 @@ def build(self, builder, serializer=None): ) if self.enc_algo: - message_fbs.EventGen.EventAddEncAlgo(builder, self.enc_algo) + # Convert string enc_algo to FlatBuffers enum value + enc_algo_int = ENC_ALGOS_FROMSTR.get(self.enc_algo, 0) + message_fbs.EventGen.EventAddPptScheme(builder, enc_algo_int) if enc_key: - message_fbs.EventGen.EventAddEncKey(builder, enc_key) + message_fbs.EventGen.EventAddPptKeyid(builder, enc_key) if self.enc_serializer: - message_fbs.EventGen.EventAddEncSerializer(builder, self.enc_serializer) + # Convert string enc_serializer to FlatBuffers enum value + enc_serializer_int = ENC_SERS_FROMSTR.get(self.enc_serializer, 0) + message_fbs.EventGen.EventAddPptSerializer(builder, enc_serializer_int) - # FIXME: add forward_for + if forward_for: + message_fbs.EventGen.EventAddForwardFor(builder, forward_for) msg = message_fbs.EventGen.EventEnd(builder) @@ -5725,12 +6096,18 @@ def marshal(self): details["enc_key"] = self.enc_key if self.enc_serializer is not None: details["enc_serializer"] = self.enc_serializer + # Convert memoryview to bytes for non-FlatBuffers serializers + payload = ( + bytes(self.payload) + if isinstance(self.payload, memoryview) + else self.payload + ) return [ Event.MESSAGE_TYPE, self.subscription, self.publication, details, - self.payload, + payload, ] else: if self.kwargs: @@ -5861,10 +6238,17 @@ def build(self, builder, serializer=None): ) msg = message_fbs.EventReceivedGen.EventReceivedEnd(builder) - return msg + + # Wrap in Message union with type + message_fbs.Message.MessageStart(builder) + message_fbs.Message.MessageAddMsgType(builder, message_fbs.MessageType.EVENT_RECEIVED) + message_fbs.Message.MessageAddMsg(builder, msg) + union_msg = message_fbs.Message.MessageEnd(builder) + + return union_msg -class Call(Message): +class Call(MessageWithAppPayload, MessageWithForwardFor, Message): """ A WAMP ``CALL`` message. @@ -5881,28 +6265,32 @@ class Call(Message): The WAMP message code for this type of message. """ + # Note: Slots from Message base class (_from_fbs) are inherited, not redefined here __slots__ = ( - "_request", - "_procedure", - "_args", - "_kwargs", - "_payload", - "_timeout", - "_receive_progress", - "_transaction_hash", - "_enc_algo", - "_enc_key", - "_enc_serializer", - "_caller", - "_caller_authid", - "_caller_authrole", - "_forward_for", + # Call-specific slots (FlatBuffers schema types in comments) + "_request", # uint64 (key) + "_procedure", # string (required, uri) + "_timeout", # uint32 + "_receive_progress", # bool + "_transaction_hash", # string + "_caller", # uint64 + "_caller_authid", # string (principal) + "_caller_authrole", # string (principal) + # From MessageWithAppPayload mixin + "_args", # [uint8] - serialized args + "_kwargs", # [uint8] - serialized kwargs + "_payload", # [uint8] - opaque payload + "_enc_algo", # Payload (enum) - encryption algorithm + "_enc_key", # [uint8] - encryption key + "_enc_serializer", # Serializer (enum) - payload serializer + # From MessageWithForwardFor mixin + "_forward_for", # [Principal] - forwarding chain ) def __init__( self, - request, - procedure, + request=None, + procedure=None, args=None, kwargs=None, payload=None, @@ -5971,8 +6359,9 @@ def __init__( :param forward_for: When this Publish is forwarded for a client (or from an intermediary router). :type forward_for: list[dict] """ - assert type(request) == int - assert type(procedure) == str + if from_fbs is None: + assert type(request) == int + assert type(procedure) == str assert args is None or type(args) in [list, tuple] assert kwargs is None or type(kwargs) == dict assert payload is None or type(payload) == bytes @@ -6005,26 +6394,29 @@ def __init__( ) assert "authrole" in ff and type(ff["authrole"]) == str + # Initialize Message base class Message.__init__(self, from_fbs=from_fbs) + + # Initialize mixin attributes + self._init_app_payload( + args=args, + kwargs=kwargs, + payload=payload, + enc_algo=enc_algo, + enc_key=enc_key, + enc_serializer=enc_serializer, + ) + self._init_forward_for(forward_for=forward_for) + + # Initialize Call-specific attributes self._request = request self._procedure = procedure - self._args = args - self._kwargs = _validate_kwargs(kwargs) - self._payload = payload self._timeout = timeout self._receive_progress = receive_progress self._transaction_hash = transaction_hash - - # payload transparency related knobs - self._enc_algo = enc_algo - self._enc_key = enc_key - self._enc_serializer = enc_serializer - - # message forwarding self._caller = caller self._caller_authid = caller_authid self._caller_authrole = caller_authrole - self._forward_for = forward_for def __eq__(self, other): if not isinstance(other, self.__class__): @@ -6090,41 +6482,7 @@ def procedure(self, value): assert value is None or type(value) == str self._procedure = value - @property - def args(self): - if self._args is None and self._from_fbs: - if self._from_fbs.ArgsLength(): - self._args = cbor2.loads(bytes(self._from_fbs.ArgsAsBytes())) - return self._args - - @args.setter - def args(self, value): - assert value is None or type(value) in [list, tuple] - self._args = value - - @property - def kwargs(self): - if self._kwargs is None and self._from_fbs: - if self._from_fbs.KwargsLength(): - self._kwargs = cbor2.loads(bytes(self._from_fbs.KwargsAsBytes())) - return self._kwargs - - @kwargs.setter - def kwargs(self, value): - assert value is None or type(value) == dict - self._kwargs = value - - @property - def payload(self): - if self._payload is None and self._from_fbs: - if self._from_fbs.PayloadLength(): - self._payload = self._from_fbs.PayloadAsBytes() - return self._payload - - @payload.setter - def payload(self, value): - assert value is None or type(value) == bytes - self._payload = value + # Note: args, kwargs, payload properties are provided by MessageWithAppPayload mixin @property def timeout(self): @@ -6165,44 +6523,7 @@ def transaction_hash(self, value): assert value is None or type(value) == str self._transaction_hash = value - @property - def enc_algo(self): - if self._enc_algo is None and self._from_fbs: - s = self._from_fbs.EncAlgo() - if s: - self._enc_algo = s.decode("utf8") - return self._enc_algo - - @enc_algo.setter - def enc_algo(self, value): - assert value is None or is_valid_enc_algo(value) - self._enc_algo = value - - @property - def enc_key(self): - if self._enc_key is None and self._from_fbs: - s = self._from_fbs.EncKey() - if s: - self._enc_key = s.decode("utf8") - return self._enc_key - - @enc_key.setter - def enc_key(self, value): - assert value is None or type(value) == str - self._enc_key = value - - @property - def enc_serializer(self): - if self._enc_serializer is None and self._from_fbs: - s = self._from_fbs.EncSerializer() - if s: - self._enc_serializer = s.decode("utf8") - return self._enc_serializer - - @enc_serializer.setter - def enc_serializer(self, value): - assert value is None or is_valid_enc_serializer(value) - self._enc_serializer = value + # Note: enc_algo, enc_key, enc_serializer properties are provided by MessageWithAppPayload mixin @property def caller(self): @@ -6243,35 +6564,7 @@ def caller_authrole(self, value): assert value is None or type(value) == str self._caller_authrole = value - @property - def forward_for(self): - if self._forward_for is None and self._from_fbs: - if self._from_fbs.ForwardForLength(): - forward_for = [] - for j in range(self._from_fbs.ForwardForLength()): - principal = self._from_fbs.ForwardFor(j) - # Principal struct currently only has session field - # (authid/authrole are commented out in schema due to FlatBuffers struct limitations) - forward_for.append({ - 'session': principal.Session(), - 'authid': None, - 'authrole': None, - }) - self._forward_for = forward_for - return self._forward_for - - @forward_for.setter - def forward_for(self, value): - assert value is None or type(value) == list - if value: - for ff in value: - assert type(ff) == dict - assert "session" in ff and type(ff["session"]) == int - assert "authid" in ff and ( - ff["authid"] is None or type(ff["authid"]) == str - ) - assert "authrole" in ff and type(ff["authrole"]) == str - self._forward_for = value + # Note: forward_for property is provided by MessageWithForwardFor mixin @staticmethod def cast(buf): @@ -6316,7 +6609,7 @@ def build(self, builder, serializer=None): enc_key = self.enc_key if enc_key: - enc_key = builder.CreateByteVector(enc_key) + enc_key = builder.CreateString(enc_key) # forward_for: [Principal] forward_for = self.forward_for @@ -6348,8 +6641,6 @@ def build(self, builder, serializer=None): # build CallGen message_fbs.CallGen.CallStart(builder) - if self.session: - message_fbs.CallGen.CallAddSession(builder, self.session) if self.request: message_fbs.CallGen.CallAddRequest(builder, self.request) if procedure: @@ -6361,11 +6652,11 @@ def build(self, builder, serializer=None): if payload: message_fbs.CallGen.CallAddPayload(builder, payload) if self.enc_algo: - message_fbs.CallGen.CallAddEncAlgo(builder, self.enc_algo) + message_fbs.CallGen.CallAddPptScheme(builder, self.enc_algo) if self.enc_serializer: - message_fbs.CallGen.CallAddEncSerializer(builder, self.enc_serializer) + message_fbs.CallGen.CallAddPptSerializer(builder, self.enc_serializer) if enc_key: - message_fbs.CallGen.CallAddEncKey(builder, enc_key) + message_fbs.CallGen.CallAddPptKeyid(builder, enc_key) if self.timeout: message_fbs.CallGen.CallAddTimeout(builder, self.timeout) if self.receive_progress: @@ -6382,7 +6673,14 @@ def build(self, builder, serializer=None): message_fbs.CallGen.CallAddForwardFor(builder, forward_for) msg = message_fbs.CallGen.CallEnd(builder) - return msg + + # Wrap in Message union with type + message_fbs.Message.MessageStart(builder) + message_fbs.Message.MessageAddMsgType(builder, message_fbs.MessageType.CALL) + message_fbs.Message.MessageAddMsg(builder, msg) + union_msg = message_fbs.Message.MessageEnd(builder) + + return union_msg @staticmethod def parse(wmsg): @@ -6643,7 +6941,7 @@ def marshal(self): return [Call.MESSAGE_TYPE, self.request, options, self.procedure] -class Cancel(Message): +class Cancel(MessageWithForwardFor, Message): """ A WAMP ``CANCEL`` message. @@ -6661,13 +6959,13 @@ class Cancel(Message): KILL = "kill" KILLNOWAIT = "killnowait" + # Note: Slots from Message base class (_from_fbs) are inherited, not redefined here __slots__ = ( - # uint64 (key) - "_request", - # CancelMode (enum) - "_mode", - # [Principal] - "_forward_for", + # Cancel-specific slots (FlatBuffers schema types in comments) + "_request", # uint64 (key) + "_mode", # CancelMode (enum) + # From MessageWithForwardFor mixin + "_forward_for", # [Principal] ) def __init__(self, request=None, mode=None, forward_for=None, from_fbs=None): @@ -6696,10 +6994,15 @@ def __init__(self, request=None, mode=None, forward_for=None, from_fbs=None): ) assert "authrole" in ff and type(ff["authrole"]) == str + # Initialize Message base class Message.__init__(self, from_fbs=from_fbs) + + # Initialize mixin attributes + self._init_forward_for(forward_for=forward_for) + + # Initialize Cancel-specific attributes self._request = request self._mode = mode - self._forward_for = forward_for def __eq__(self, other): if not isinstance(other, self.__class__): @@ -6744,15 +7047,7 @@ def mode(self, value): assert value is None or type(value) == str self._mode = value - @property - def forward_for(self): - # forward_for in FlatBuffers uses Principal struct, complex to deserialize - return self._forward_for - - @forward_for.setter - def forward_for(self, value): - assert value is None or type(value) == list - self._forward_for = value + # Note: forward_for property is provided by MessageWithForwardFor mixin @staticmethod def parse(wmsg): @@ -6862,10 +7157,17 @@ def build(self, builder, serializer=None): message_fbs.CancelGen.CancelAddMode(builder, mode_val) msg = message_fbs.CancelGen.CancelEnd(builder) - return msg + + # Wrap in Message union with type + message_fbs.Message.MessageStart(builder) + message_fbs.Message.MessageAddMsgType(builder, message_fbs.MessageType.CANCEL) + message_fbs.Message.MessageAddMsg(builder, msg) + union_msg = message_fbs.Message.MessageEnd(builder) + + return union_msg -class Result(Message): +class Result(MessageWithAppPayload, MessageWithForwardFor, Message): """ A WAMP ``RESULT`` message. @@ -6882,24 +7184,28 @@ class Result(Message): The WAMP message code for this type of message. """ + # Note: Slots from Message base class (_from_fbs) are inherited, not redefined here __slots__ = ( - "request", - "args", - "kwargs", - "payload", - "progress", - "enc_algo", - "enc_key", - "enc_serializer", - "callee", - "callee_authid", - "callee_authrole", - "forward_for", + # Result-specific slots (FlatBuffers schema types in comments) + "_request", # uint64 (key) + "_progress", # bool + "_callee", # uint64 (session id) + "_callee_authid", # string (principal) + "_callee_authrole", # string (principal) + # From MessageWithAppPayload mixin + "_args", # [uint8] - serialized args + "_kwargs", # [uint8] - serialized kwargs + "_payload", # [uint8] - opaque payload + "_enc_algo", # Payload (enum) - encryption algorithm + "_enc_key", # [uint8] - encryption key + "_enc_serializer", # Serializer (enum) - payload serializer + # From MessageWithForwardFor mixin + "_forward_for", # [Principal] - forwarding chain ) def __init__( self, - request, + request=None, args=None, kwargs=None, payload=None, @@ -6911,6 +7217,7 @@ def __init__( callee_authid=None, callee_authrole=None, forward_for=None, + from_fbs=None, ): """ @@ -6953,9 +7260,9 @@ def __init__( :param forward_for: When this Result is forwarded for a client/callee (or from an intermediary router). :type forward_for: list[dict] """ - assert type(request) == int - assert args is None or type(args) in [list, tuple] - assert kwargs is None or type(kwargs) == dict + assert request is None or type(request) == int + assert args is None or type(args) in [list, tuple, str, bytes] + assert kwargs is None or type(kwargs) in [dict, str, bytes] assert payload is None or type(payload) == bytes assert payload is None or ( payload is not None and args is None and kwargs is None @@ -6983,44 +7290,115 @@ def __init__( ) assert "authrole" in ff and type(ff["authrole"]) == str - Message.__init__(self) - self.request = request - self.args = args - self.kwargs = _validate_kwargs(kwargs) - self.payload = payload - self.progress = progress + # Initialize Message base class + Message.__init__(self, from_fbs=from_fbs) - # payload transparency related knobs - self.enc_algo = enc_algo - self.enc_key = enc_key - self.enc_serializer = enc_serializer + # Initialize mixin attributes + self._init_app_payload( + args=args, + kwargs=kwargs, + payload=payload, + enc_algo=enc_algo, + enc_key=enc_key, + enc_serializer=enc_serializer, + ) + self._init_forward_for(forward_for=forward_for) + + # Initialize Result-specific attributes + self._request = request + self._progress = progress # effective callee that responded with the result - self.callee = callee - self.callee_authid = callee_authid - self.callee_authrole = callee_authrole + self._callee = callee + self._callee_authid = callee_authid + self._callee_authrole = callee_authrole - # message forwarding - self.forward_for = forward_for + @property + def request(self): + if self._request is None and self._from_fbs: + self._request = self._from_fbs.Request() + return self._request - @staticmethod - def cast(buf): - return Result(from_fbs=message_fbs.Result.GetRootAsResult(buf, 0)) + @request.setter + def request(self, value): + assert value is None or type(value) == int + self._request = value - def build(self, builder, serializer=None): - args = self.args - if args: - if serializer: - args = builder.CreateByteVector(serializer.serialize_payload(args)) - else: - args = builder.CreateByteVector(cbor2.dumps(args)) + # NOTE: args, kwargs, payload properties are provided by MessageWithAppPayload mixin - kwargs = self.kwargs - if kwargs: - if serializer: - kwargs = builder.CreateByteVector(serializer.serialize_payload(kwargs)) - else: - kwargs = builder.CreateByteVector(cbor2.dumps(kwargs)) + @property + def progress(self): + if self._progress is None and self._from_fbs: + progress = self._from_fbs.Progress() + if progress: + self._progress = progress + return self._progress + + @progress.setter + def progress(self, value): + assert value is None or type(value) == bool + self._progress = value + + @property + def callee(self): + if self._callee is None and self._from_fbs: + callee = self._from_fbs.Callee() + if callee: + self._callee = callee + return self._callee + + @callee.setter + def callee(self, value): + assert value is None or type(value) == int + self._callee = value + + @property + def callee_authid(self): + if self._callee_authid is None and self._from_fbs: + s = self._from_fbs.CalleeAuthid() + if s: + self._callee_authid = s.decode("utf8") + return self._callee_authid + + @callee_authid.setter + def callee_authid(self, value): + assert value is None or type(value) == str + self._callee_authid = value + + @property + def callee_authrole(self): + if self._callee_authrole is None and self._from_fbs: + s = self._from_fbs.CalleeAuthrole() + if s: + self._callee_authrole = s.decode("utf8") + return self._callee_authrole + + @callee_authrole.setter + def callee_authrole(self, value): + assert value is None or type(value) == str + self._callee_authrole = value + + # NOTE: enc_algo, enc_key, enc_serializer properties are provided by MessageWithAppPayload mixin + # NOTE: forward_for property is provided by MessageWithForwardFor mixin + + @staticmethod + def cast(buf): + return Result(from_fbs=message_fbs.Result.GetRootAsResult(buf, 0)) + + def build(self, builder, serializer=None): + args = self.args + if args: + if serializer: + args = builder.CreateByteVector(serializer.serialize_payload(args)) + else: + args = builder.CreateByteVector(cbor2.dumps(args)) + + kwargs = self.kwargs + if kwargs: + if serializer: + kwargs = builder.CreateByteVector(serializer.serialize_payload(kwargs)) + else: + kwargs = builder.CreateByteVector(cbor2.dumps(kwargs)) payload = self.payload if payload: @@ -7028,7 +7406,7 @@ def build(self, builder, serializer=None): enc_key = self.enc_key if enc_key: - enc_key = builder.CreateByteVector(enc_key) + enc_key = builder.CreateString(enc_key) callee_authid = self.callee_authid if callee_authid: @@ -7060,7 +7438,9 @@ def build(self, builder, serializer=None): PrincipalGen.AddAuthrole(builder, _authrole) _forward_for.append(PrincipalGen.End(builder)) - message_fbs.ResultGen.ResultStartForwardForVector(builder, len(_forward_for)) + message_fbs.ResultGen.ResultStartForwardForVector( + builder, len(_forward_for) + ) for principal in reversed(_forward_for): builder.PrependUOffsetTRelative(principal) forward_for = builder.EndVector() @@ -7077,11 +7457,11 @@ def build(self, builder, serializer=None): if payload: message_fbs.ResultGen.ResultAddPayload(builder, payload) if self.enc_algo: - message_fbs.ResultGen.ResultAddEncAlgo(builder, self.enc_algo) + message_fbs.ResultGen.ResultAddPptScheme(builder, self.enc_algo) if self.enc_serializer: - message_fbs.ResultGen.ResultAddEncSerializer(builder, self.enc_serializer) + message_fbs.ResultGen.ResultAddPptSerializer(builder, self.enc_serializer) if enc_key: - message_fbs.ResultGen.ResultAddEncKey(builder, enc_key) + message_fbs.ResultGen.ResultAddPptKeyid(builder, enc_key) if self.progress: message_fbs.ResultGen.ResultAddProgress(builder, self.progress) if self.callee: @@ -7094,7 +7474,14 @@ def build(self, builder, serializer=None): message_fbs.ResultGen.ResultAddForwardFor(builder, forward_for) msg = message_fbs.ResultGen.ResultEnd(builder) - return msg + + # Wrap in Message union with type + message_fbs.Message.MessageStart(builder) + message_fbs.Message.MessageAddMsgType(builder, message_fbs.MessageType.RESULT) + message_fbs.Message.MessageAddMsg(builder, msg) + union_msg = message_fbs.Message.MessageEnd(builder) + + return union_msg @staticmethod def parse(wmsg): @@ -7294,7 +7681,7 @@ def marshal(self): return [Result.MESSAGE_TYPE, self.request, details] -class Register(Message): +class Register(MessageWithForwardFor, Message): """ A WAMP ``REGISTER`` message. @@ -7315,23 +7702,18 @@ class Register(Message): INVOKE_LAST = "last" INVOKE_ROUNDROBIN = "roundrobin" INVOKE_RANDOM = "random" - INVOKE_ALL = "all" + # Note: Slots from Message base class (_from_fbs) are inherited, not redefined here __slots__ = ( - # uint64 (key) - "_request", - # string (required, uri_pattern) - "_procedure", - # Match (enum) - "_match", - # InvocationPolicy (enum) - "_invoke", - # uint16 - "_concurrency", - # bool - "_force_reregister", - # [Principal] - "_forward_for", + # Register-specific slots (FlatBuffers schema types in comments) + "_request", # uint64 (key) + "_procedure", # string (required, uri_pattern) + "_match", # Match (enum) + "_invoke", # InvocationPolicy (enum) + "_concurrency", # uint16 + "_force_reregister", # bool + # From MessageWithForwardFor mixin + "_forward_for", # [Principal] ) def __init__( @@ -7394,14 +7776,19 @@ def __init__( ) assert "authrole" in ff and type(ff["authrole"]) == str + # Initialize Message base class Message.__init__(self, from_fbs=from_fbs) + + # Initialize mixin attributes + self._init_forward_for(forward_for=forward_for) + + # Initialize Register-specific attributes self._request = request self._procedure = procedure self._match = match or Register.MATCH_EXACT self._invoke = invoke or Register.INVOKE_SINGLE self._concurrency = concurrency self._force_reregister = force_reregister - self._forward_for = forward_for def __eq__(self, other): if not isinstance(other, self.__class__): @@ -7511,15 +7898,7 @@ def force_reregister(self, value): assert value is None or type(value) == bool self._force_reregister = value - @property - def forward_for(self): - # forward_for is not in FlatBuffers schema yet - return self._forward_for - - @forward_for.setter - def forward_for(self, value): - assert value is None or type(value) == list - self._forward_for = value + # Note: forward_for property is provided by MessageWithForwardFor mixin @staticmethod def parse(wmsg): @@ -7724,8 +8103,9 @@ def build(self, builder, serializer=None): message_fbs.RegisterGen.RegisterStart(builder) # Add fields - if self.session: - message_fbs.RegisterGen.RegisterAddSession(builder, self.session) + session = getattr(self, 'session', None) + if session: + message_fbs.RegisterGen.RegisterAddSession(builder, session) if self.request: message_fbs.RegisterGen.RegisterAddRequest(builder, self.request) if procedure: @@ -7765,7 +8145,14 @@ def build(self, builder, serializer=None): # End and return msg = message_fbs.RegisterGen.RegisterEnd(builder) - return msg + + # Wrap in Message union with type + message_fbs.Message.MessageStart(builder) + message_fbs.Message.MessageAddMsgType(builder, message_fbs.MessageType.REGISTER) + message_fbs.Message.MessageAddMsg(builder, msg) + union_msg = message_fbs.Message.MessageEnd(builder) + + return union_msg class Registered(Message): @@ -7880,8 +8267,9 @@ def cast(buf): def build(self, builder, serializer=None): message_fbs.RegisteredGen.RegisteredStart(builder) - if self.session: - message_fbs.RegisteredGen.RegisteredAddSession(builder, self.session) + session = getattr(self, 'session', None) + if session: + message_fbs.RegisteredGen.RegisteredAddSession(builder, session) if self.request: message_fbs.RegisteredGen.RegisteredAddRequest(builder, self.request) if self.registration: @@ -7890,10 +8278,17 @@ def build(self, builder, serializer=None): ) msg = message_fbs.RegisteredGen.RegisteredEnd(builder) - return msg + # Wrap in Message union with type + message_fbs.Message.MessageStart(builder) + message_fbs.Message.MessageAddMsgType(builder, message_fbs.MessageType.REGISTERED) + message_fbs.Message.MessageAddMsg(builder, msg) + union_msg = message_fbs.Message.MessageEnd(builder) + + return union_msg -class Unregister(Message): + +class Unregister(MessageWithForwardFor, Message): """ A WAMP `UNREGISTER` message. @@ -7908,13 +8303,18 @@ class Unregister(Message): The WAMP message code for this type of message. """ + # Note: Slots from Message base class (_from_fbs) are inherited, not redefined here __slots__ = ( - "request", - "registration", - "forward_for", + # Unregister-specific slots (FlatBuffers schema types in comments) + "_request", # uint64 (key) + "_registration", # uint64 + # From MessageWithForwardFor mixin + "_forward_for", # [Principal] ) - def __init__(self, request, registration, forward_for=None): + def __init__( + self, request=None, registration=None, forward_for=None, from_fbs=None + ): """ :param request: The WAMP request ID of this request. @@ -7927,13 +8327,42 @@ def __init__(self, request, registration, forward_for=None): or via an intermediary router. :type forward_for: list[dict] """ - assert type(request) == int - assert type(registration) == int + assert request is None or type(request) == int + assert registration is None or type(registration) == int - Message.__init__(self) - self.request = request - self.registration = registration - self.forward_for = forward_for + # Initialize Message base class + Message.__init__(self, from_fbs=from_fbs) + + # Initialize mixin attributes + self._init_forward_for(forward_for=forward_for) + + # Initialize Unregister-specific attributes + self._request = request + self._registration = registration + + @property + def request(self): + if self._request is None and self._from_fbs: + self._request = self._from_fbs.Request() + return self._request + + @request.setter + def request(self, value): + assert value is None or type(value) == int + self._request = value + + @property + def registration(self): + if self._registration is None and self._from_fbs: + self._registration = self._from_fbs.Registration() + return self._registration + + @registration.setter + def registration(self, value): + assert value is None or type(value) == int + self._registration = value + + # Note: forward_for property is provided by MessageWithForwardFor mixin @staticmethod def parse(wmsg): @@ -8180,8 +8609,9 @@ def build(self, builder, serializer=None): message_fbs.UnregisteredGen.UnregisteredStart(builder) # Add fields - if self.session: - message_fbs.UnregisteredGen.UnregisteredAddSession(builder, self.session) + session = getattr(self, 'session', None) + if session: + message_fbs.UnregisteredGen.UnregisteredAddSession(builder, session) if self.request: message_fbs.UnregisteredGen.UnregisteredAddRequest(builder, self.request) if self.registration: @@ -8193,10 +8623,17 @@ def build(self, builder, serializer=None): # End and return msg = message_fbs.UnregisteredGen.UnregisteredEnd(builder) - return msg + # Wrap in Message union with type + message_fbs.Message.MessageStart(builder) + message_fbs.Message.MessageAddMsgType(builder, message_fbs.MessageType.UNREGISTERED) + message_fbs.Message.MessageAddMsg(builder, msg) + union_msg = message_fbs.Message.MessageEnd(builder) + + return union_msg -class Invocation(Message): + +class Invocation(MessageWithAppPayload, MessageWithForwardFor, Message): """ A WAMP ``INVOCATION`` message. @@ -8213,29 +8650,33 @@ class Invocation(Message): The WAMP message code for this type of message. """ + # Note: Slots from Message base class (_from_fbs) are inherited, not redefined here __slots__ = ( - "request", - "registration", - "args", - "kwargs", - "payload", - "timeout", - "receive_progress", - "caller", - "caller_authid", - "caller_authrole", - "procedure", - "transaction_hash", - "enc_algo", - "enc_key", - "enc_serializer", - "forward_for", + # Invocation-specific slots (FlatBuffers schema types in comments) + "_request", # uint64 (key) + "_registration", # uint64 (key) + "_timeout", # uint32 + "_receive_progress", # bool + "_caller", # uint64 (session id) + "_caller_authid", # string (principal) + "_caller_authrole", # string (principal) + "_procedure", # string (uri) + "_transaction_hash", # string + # From MessageWithAppPayload mixin + "_args", # [uint8] - serialized args + "_kwargs", # [uint8] - serialized kwargs + "_payload", # [uint8] - opaque payload + "_enc_algo", # Payload (enum) - encryption algorithm + "_enc_key", # [uint8] - encryption key + "_enc_serializer", # Serializer (enum) - payload serializer + # From MessageWithForwardFor mixin + "_forward_for", # [Principal] - forwarding chain ) def __init__( self, - request, - registration, + request=None, + registration=None, args=None, kwargs=None, payload=None, @@ -8250,6 +8691,7 @@ def __init__( enc_key=None, enc_serializer=None, forward_for=None, + from_fbs=None, ): """ @@ -8306,10 +8748,10 @@ def __init__( :param forward_for: When this Call is forwarded for a client (or from an intermediary router). :type forward_for: list[dict] """ - assert type(request) == int - assert type(registration) == int - assert args is None or type(args) in [list, tuple] - assert kwargs is None or type(kwargs) == dict + assert request is None or type(request) == int + assert registration is None or type(registration) == int + assert args is None or type(args) in [list, tuple, str, bytes] + assert kwargs is None or type(kwargs) in [dict, str, bytes] assert payload is None or type(payload) == bytes assert payload is None or ( payload is not None and args is None and kwargs is None @@ -8338,25 +8780,148 @@ def __init__( ) assert "authrole" in ff and type(ff["authrole"]) == str - Message.__init__(self) - self.request = request - self.registration = registration - self.args = args - self.kwargs = _validate_kwargs(kwargs) - self.payload = payload - self.timeout = timeout - self.receive_progress = receive_progress - self.caller = caller - self.caller_authid = caller_authid - self.caller_authrole = caller_authrole - self.procedure = procedure - self.transaction_hash = transaction_hash - self.enc_algo = enc_algo - self.enc_key = enc_key - self.enc_serializer = enc_serializer - - # message forwarding - self.forward_for = forward_for + # Initialize Message base class + Message.__init__(self, from_fbs=from_fbs) + + # Initialize mixin attributes + self._init_app_payload( + args=args, + kwargs=kwargs, + payload=payload, + enc_algo=enc_algo, + enc_key=enc_key, + enc_serializer=enc_serializer, + ) + self._init_forward_for(forward_for=forward_for) + + # Initialize Invocation-specific attributes + self._request = request + self._registration = registration + self._timeout = timeout + self._receive_progress = receive_progress + self._caller = caller + self._caller_authid = caller_authid + self._caller_authrole = caller_authrole + self._procedure = procedure + self._transaction_hash = transaction_hash + + @property + def request(self): + if self._request is None and self._from_fbs: + self._request = self._from_fbs.Request() + return self._request + + @request.setter + def request(self, value): + assert value is None or type(value) == int + self._request = value + + @property + def registration(self): + if self._registration is None and self._from_fbs: + self._registration = self._from_fbs.Registration() + return self._registration + + @registration.setter + def registration(self, value): + assert value is None or type(value) == int + self._registration = value + + # NOTE: args, kwargs, payload properties are provided by MessageWithAppPayload mixin + + @property + def timeout(self): + if self._timeout is None and self._from_fbs: + timeout = self._from_fbs.Timeout() + if timeout: + self._timeout = timeout + return self._timeout + + @timeout.setter + def timeout(self, value): + assert value is None or type(value) == int + self._timeout = value + + @property + def receive_progress(self): + if self._receive_progress is None and self._from_fbs: + receive_progress = self._from_fbs.ReceiveProgress() + if receive_progress: + self._receive_progress = receive_progress + return self._receive_progress + + @receive_progress.setter + def receive_progress(self, value): + assert value is None or type(value) == bool + self._receive_progress = value + + @property + def caller(self): + if self._caller is None and self._from_fbs: + caller = self._from_fbs.Caller() + if caller: + self._caller = caller + return self._caller + + @caller.setter + def caller(self, value): + assert value is None or type(value) == int + self._caller = value + + @property + def caller_authid(self): + if self._caller_authid is None and self._from_fbs: + s = self._from_fbs.CallerAuthid() + if s: + self._caller_authid = s.decode("utf8") + return self._caller_authid + + @caller_authid.setter + def caller_authid(self, value): + assert value is None or type(value) == str + self._caller_authid = value + + @property + def caller_authrole(self): + if self._caller_authrole is None and self._from_fbs: + s = self._from_fbs.CallerAuthrole() + if s: + self._caller_authrole = s.decode("utf8") + return self._caller_authrole + + @caller_authrole.setter + def caller_authrole(self, value): + assert value is None or type(value) == str + self._caller_authrole = value + + @property + def procedure(self): + if self._procedure is None and self._from_fbs: + s = self._from_fbs.Procedure() + if s: + self._procedure = s.decode("utf8") + return self._procedure + + @procedure.setter + def procedure(self, value): + assert value is None or type(value) == str + self._procedure = value + + @property + def transaction_hash(self): + if self._transaction_hash is None and self._from_fbs: + s = self._from_fbs.TransactionHash() + if s: + self._transaction_hash = s.decode("utf8") + return self._transaction_hash + + @transaction_hash.setter + def transaction_hash(self, value): + assert value is None or type(value) == str + self._transaction_hash = value + + # NOTE: enc_algo, enc_key, enc_serializer properties are provided by MessageWithAppPayload mixin + # NOTE: forward_for property is provided by MessageWithForwardFor mixin @staticmethod def cast(buf): @@ -8399,7 +8964,7 @@ def build(self, builder, serializer=None): enc_key = self.enc_key if enc_key: - enc_key = builder.CreateByteVector(enc_key) + enc_key = builder.CreateString(enc_key) # forward_for: [Principal] forward_for = self.forward_for @@ -8423,7 +8988,9 @@ def build(self, builder, serializer=None): PrincipalGen.AddAuthrole(builder, _authrole) _forward_for.append(PrincipalGen.End(builder)) - message_fbs.InvocationGen.InvocationStartForwardForVector(builder, len(_forward_for)) + message_fbs.InvocationGen.InvocationStartForwardForVector( + builder, len(_forward_for) + ) for principal in reversed(_forward_for): builder.PrependUOffsetTRelative(principal) forward_for = builder.EndVector() @@ -8434,7 +9001,9 @@ def build(self, builder, serializer=None): if self.request: message_fbs.InvocationGen.InvocationAddRequest(builder, self.request) if self.registration: - message_fbs.InvocationGen.InvocationAddRegistration(builder, self.registration) + message_fbs.InvocationGen.InvocationAddRegistration( + builder, self.registration + ) if args: message_fbs.InvocationGen.InvocationAddArgs(builder, args) if kwargs: @@ -8444,28 +9013,43 @@ def build(self, builder, serializer=None): if self.timeout: message_fbs.InvocationGen.InvocationAddTimeout(builder, self.timeout) if self.receive_progress: - message_fbs.InvocationGen.InvocationAddReceiveProgress(builder, self.receive_progress) + message_fbs.InvocationGen.InvocationAddReceiveProgress( + builder, self.receive_progress + ) if self.caller: message_fbs.InvocationGen.InvocationAddCaller(builder, self.caller) if caller_authid: message_fbs.InvocationGen.InvocationAddCallerAuthid(builder, caller_authid) if caller_authrole: - message_fbs.InvocationGen.InvocationAddCallerAuthrole(builder, caller_authrole) + message_fbs.InvocationGen.InvocationAddCallerAuthrole( + builder, caller_authrole + ) if procedure: message_fbs.InvocationGen.InvocationAddProcedure(builder, procedure) if self.enc_algo: - message_fbs.InvocationGen.InvocationAddEncAlgo(builder, self.enc_algo) + message_fbs.InvocationGen.InvocationAddPptScheme(builder, self.enc_algo) if self.enc_serializer: - message_fbs.InvocationGen.InvocationAddEncSerializer(builder, self.enc_serializer) + message_fbs.InvocationGen.InvocationAddPptSerializer( + builder, self.enc_serializer + ) if enc_key: - message_fbs.InvocationGen.InvocationAddEncKey(builder, enc_key) + message_fbs.InvocationGen.InvocationAddPptKeyid(builder, enc_key) if transaction_hash: - message_fbs.InvocationGen.InvocationAddTransactionHash(builder, transaction_hash) + message_fbs.InvocationGen.InvocationAddTransactionHash( + builder, transaction_hash + ) if forward_for: message_fbs.InvocationGen.InvocationAddForwardFor(builder, forward_for) msg = message_fbs.InvocationGen.InvocationEnd(builder) - return msg + + # Wrap in Message union with type + message_fbs.Message.MessageStart(builder) + message_fbs.Message.MessageAddMsgType(builder, message_fbs.MessageType.INVOCATION) + message_fbs.Message.MessageAddMsg(builder, msg) + union_msg = message_fbs.Message.MessageEnd(builder) + + return union_msg @staticmethod def parse(wmsg): @@ -8748,7 +9332,7 @@ def marshal(self): ] -class Interrupt(Message): +class Interrupt(MessageWithForwardFor, Message): """ A WAMP ``INTERRUPT`` message. @@ -8765,18 +9349,19 @@ class Interrupt(Message): KILL = "kill" KILLNOWAIT = "killnowait" + # Note: Slots from Message base class (_from_fbs) are inherited, not redefined here __slots__ = ( - # uint64 (key) - "_request", - # CancelMode (enum) - "_mode", - # string (uri) - "_reason", - # [Principal] - "_forward_for", + # Interrupt-specific slots (FlatBuffers schema types in comments) + "_request", # uint64 (key) + "_mode", # CancelMode (enum) + "_reason", # string (uri) + # From MessageWithForwardFor mixin + "_forward_for", # [Principal] ) - def __init__(self, request=None, mode=None, reason=None, forward_for=None, from_fbs=None): + def __init__( + self, request=None, mode=None, reason=None, forward_for=None, from_fbs=None + ): """ :param request: The WAMP request ID of the original ``INVOCATION`` to interrupt. @@ -8813,11 +9398,16 @@ def __init__(self, request=None, mode=None, reason=None, forward_for=None, from_ ) assert "authrole" in ff and type(ff["authrole"]) == str + # Initialize Message base class Message.__init__(self, from_fbs=from_fbs) + + # Initialize mixin attributes + self._init_forward_for(forward_for=forward_for) + + # Initialize Interrupt-specific attributes self._request = request self._mode = mode self._reason = reason - self._forward_for = forward_for def __eq__(self, other): if not isinstance(other, self.__class__): @@ -8876,15 +9466,7 @@ def reason(self, value): assert value is None or type(value) == str self._reason = value - @property - def forward_for(self): - # forward_for in FlatBuffers uses Principal struct, complex to deserialize - return self._forward_for - - @forward_for.setter - def forward_for(self, value): - assert value is None or type(value) == list - self._forward_for = value + # Note: forward_for property is provided by MessageWithForwardFor mixin @staticmethod def parse(wmsg): @@ -9010,10 +9592,17 @@ def build(self, builder, serializer=None): message_fbs.InterruptGen.InterruptAddReason(builder, reason) msg = message_fbs.InterruptGen.InterruptEnd(builder) - return msg + + # Wrap in Message union with type + message_fbs.Message.MessageStart(builder) + message_fbs.Message.MessageAddMsgType(builder, message_fbs.MessageType.INTERRUPT) + message_fbs.Message.MessageAddMsg(builder, msg) + union_msg = message_fbs.Message.MessageEnd(builder) + + return union_msg -class Yield(Message): +class Yield(MessageWithAppPayload, MessageWithForwardFor, Message): """ A WAMP ``YIELD`` message. @@ -9030,24 +9619,28 @@ class Yield(Message): The WAMP message code for this type of message. """ + # Note: Slots from Message base class (_from_fbs) are inherited, not redefined here __slots__ = ( - "request", - "args", - "kwargs", - "payload", - "progress", - "enc_algo", - "enc_key", - "enc_serializer", - "callee", - "callee_authid", - "callee_authrole", - "forward_for", + # Yield-specific slots (FlatBuffers schema types in comments) + "_request", # uint64 (key) + "_progress", # bool + "_callee", # uint64 (session id) + "_callee_authid", # string (principal) + "_callee_authrole", # string (principal) + # From MessageWithAppPayload mixin + "_args", # [uint8] - serialized args + "_kwargs", # [uint8] - serialized kwargs + "_payload", # [uint8] - opaque payload + "_enc_algo", # Payload (enum) - encryption algorithm + "_enc_key", # [uint8] - encryption key + "_enc_serializer", # Serializer (enum) - payload serializer + # From MessageWithForwardFor mixin + "_forward_for", # [Principal] - forwarding chain ) def __init__( self, - request, + request=None, args=None, kwargs=None, payload=None, @@ -9059,6 +9652,7 @@ def __init__( callee_authid=None, callee_authrole=None, forward_for=None, + from_fbs=None, ): """ @@ -9101,9 +9695,9 @@ def __init__( :param forward_for: When this Call is forwarded for a client (or from an intermediary router). :type forward_for: list[dict] """ - assert type(request) == int - assert args is None or type(args) in [list, tuple] - assert kwargs is None or type(kwargs) == dict + assert request is None or type(request) == int + assert args is None or type(args) in [list, tuple, str, bytes] + assert kwargs is None or type(kwargs) in [dict, str, bytes] assert payload is None or type(payload) == bytes assert payload is None or ( payload is not None and args is None and kwargs is None @@ -9130,23 +9724,96 @@ def __init__( ) assert "authrole" in ff and type(ff["authrole"]) == str - Message.__init__(self) - self.request = request - self.args = args - self.kwargs = _validate_kwargs(kwargs) - self.payload = payload - self.progress = progress - self.enc_algo = enc_algo - self.enc_key = enc_key - self.enc_serializer = enc_serializer + # Initialize Message base class + Message.__init__(self, from_fbs=from_fbs) + + # Initialize mixin attributes + self._init_app_payload( + args=args, + kwargs=kwargs, + payload=payload, + enc_algo=enc_algo, + enc_key=enc_key, + enc_serializer=enc_serializer, + ) + self._init_forward_for(forward_for=forward_for) + + # Initialize Yield-specific attributes + self._request = request + self._progress = progress # effective callee that responded with the result - self.callee = callee - self.callee_authid = callee_authid - self.callee_authrole = callee_authrole + self._callee = callee + self._callee_authid = callee_authid + self._callee_authrole = callee_authrole + + @property + def request(self): + if self._request is None and self._from_fbs: + self._request = self._from_fbs.Request() + return self._request - # message forwarding - self.forward_for = forward_for + @request.setter + def request(self, value): + assert value is None or type(value) == int + self._request = value + + # NOTE: args, kwargs, payload properties are provided by MessageWithAppPayload mixin + + @property + def progress(self): + if self._progress is None and self._from_fbs: + progress = self._from_fbs.Progress() + if progress: + self._progress = progress + return self._progress + + @progress.setter + def progress(self, value): + assert value is None or type(value) == bool + self._progress = value + + @property + def callee(self): + if self._callee is None and self._from_fbs: + callee = self._from_fbs.Callee() + if callee: + self._callee = callee + return self._callee + + @callee.setter + def callee(self, value): + assert value is None or type(value) == int + self._callee = value + + @property + def callee_authid(self): + if self._callee_authid is None and self._from_fbs: + s = self._from_fbs.CalleeAuthid() + if s: + self._callee_authid = s.decode("utf8") + return self._callee_authid + + @callee_authid.setter + def callee_authid(self, value): + assert value is None or type(value) == str + self._callee_authid = value + + @property + def callee_authrole(self): + if self._callee_authrole is None and self._from_fbs: + s = self._from_fbs.CalleeAuthrole() + if s: + self._callee_authrole = s.decode("utf8") + return self._callee_authrole + + @callee_authrole.setter + def callee_authrole(self, value): + assert value is None or type(value) == str + self._callee_authrole = value + + # NOTE: enc_algo, enc_key, enc_serializer properties are provided by MessageWithAppPayload mixin + # NOTE: forward_for property is provided by MessageWithForwardFor mixin @staticmethod def cast(buf): @@ -9173,7 +9840,7 @@ def build(self, builder, serializer=None): enc_key = self.enc_key if enc_key: - enc_key = builder.CreateByteVector(enc_key) + enc_key = builder.CreateString(enc_key) callee_authid = self.callee_authid if callee_authid: @@ -9224,11 +9891,11 @@ def build(self, builder, serializer=None): if self.progress: message_fbs.YieldGen.YieldAddProgress(builder, self.progress) if self.enc_algo: - message_fbs.YieldGen.YieldAddEncAlgo(builder, self.enc_algo) + message_fbs.YieldGen.YieldAddPptScheme(builder, self.enc_algo) if self.enc_serializer: - message_fbs.YieldGen.YieldAddEncSerializer(builder, self.enc_serializer) + message_fbs.YieldGen.YieldAddPptSerializer(builder, self.enc_serializer) if enc_key: - message_fbs.YieldGen.YieldAddEncKey(builder, enc_key) + message_fbs.YieldGen.YieldAddPptKeyid(builder, enc_key) if self.callee: message_fbs.YieldGen.YieldAddCallee(builder, self.callee) if callee_authid: @@ -9239,7 +9906,14 @@ def build(self, builder, serializer=None): message_fbs.YieldGen.YieldAddForwardFor(builder, forward_for) msg = message_fbs.YieldGen.YieldEnd(builder) - return msg + + # Wrap in Message union with type + message_fbs.Message.MessageStart(builder) + message_fbs.Message.MessageAddMsgType(builder, message_fbs.MessageType.YIELD) + message_fbs.Message.MessageAddMsg(builder, msg) + union_msg = message_fbs.Message.MessageEnd(builder) + + return union_msg @staticmethod def parse(wmsg): diff --git a/autobahn/wamp/message_fbs.py b/autobahn/wamp/message_fbs.py index 305691185..4963aa539 100644 --- a/autobahn/wamp/message_fbs.py +++ b/autobahn/wamp/message_fbs.py @@ -25,16 +25,88 @@ ############################################################################### import flatbuffers -from autobahn.wamp.gen.wamp.proto import Event as EventGen -from autobahn.wamp.gen.wamp.proto import Publish as PublishGen +# Message type and enums from autobahn.wamp.gen.wamp.proto import Message from autobahn.wamp.gen.wamp.proto.MessageType import MessageType +# Enums used by message build() methods +from autobahn.wamp.gen.wamp.proto.Match import Match +from autobahn.wamp.gen.wamp.proto.InvocationPolicy import InvocationPolicy +from autobahn.wamp.gen.wamp.proto.CancelMode import CancelMode + +# Category 1: Session lifecycle messages (neither payload nor forwarding) +from autobahn.wamp.gen.wamp.proto import Hello as HelloGen +from autobahn.wamp.gen.wamp.proto import Welcome as WelcomeGen +from autobahn.wamp.gen.wamp.proto import Abort as AbortGen +from autobahn.wamp.gen.wamp.proto import Challenge as ChallengeGen +from autobahn.wamp.gen.wamp.proto import Authenticate as AuthenticateGen +from autobahn.wamp.gen.wamp.proto import Goodbye as GoodbyeGen + +# Category 1: PubSub messages (neither payload nor forwarding) +from autobahn.wamp.gen.wamp.proto import Subscribe as SubscribeGen +from autobahn.wamp.gen.wamp.proto import Subscribed as SubscribedGen +from autobahn.wamp.gen.wamp.proto import Unsubscribe as UnsubscribeGen +from autobahn.wamp.gen.wamp.proto import Unsubscribed as UnsubscribedGen +from autobahn.wamp.gen.wamp.proto import Published as PublishedGen + +# Category 1: RPC messages (neither payload nor forwarding) +from autobahn.wamp.gen.wamp.proto import Register as RegisterGen +from autobahn.wamp.gen.wamp.proto import Registered as RegisteredGen +from autobahn.wamp.gen.wamp.proto import Unregister as UnregisterGen +from autobahn.wamp.gen.wamp.proto import Unregistered as UnregisteredGen + +# Category 3: Forwarding only messages +from autobahn.wamp.gen.wamp.proto import EventReceived as EventReceivedGen +from autobahn.wamp.gen.wamp.proto import Cancel as CancelGen +from autobahn.wamp.gen.wamp.proto import Interrupt as InterruptGen + +# Category 4: Both Payload and Forwarding messages +from autobahn.wamp.gen.wamp.proto import Error as ErrorGen +from autobahn.wamp.gen.wamp.proto import Event as EventGen +from autobahn.wamp.gen.wamp.proto import Publish as PublishGen +from autobahn.wamp.gen.wamp.proto import Call as CallGen +from autobahn.wamp.gen.wamp.proto import Result as ResultGen +from autobahn.wamp.gen.wamp.proto import Invocation as InvocationGen +from autobahn.wamp.gen.wamp.proto import Yield as YieldGen + __all__ = ( "Event", + "Publish", + "Error", + "Call", + "Result", + "Invocation", + "Yield", "Message", "MessageType", + "Match", + "InvocationPolicy", + "CancelMode", + "HelloGen", + "WelcomeGen", + "AbortGen", + "ChallengeGen", + "AuthenticateGen", + "GoodbyeGen", + "SubscribeGen", + "SubscribedGen", + "UnsubscribeGen", + "UnsubscribedGen", + "PublishedGen", + "RegisterGen", + "RegisteredGen", + "UnregisterGen", + "UnregisteredGen", + "EventReceivedGen", + "CancelGen", + "InterruptGen", + "ErrorGen", + "CallGen", + "ResultGen", + "InvocationGen", + "YieldGen", + "PublishGen", ) @@ -124,3 +196,223 @@ def EncKeyAsBytes(self): _len = self._tab.VectorLen(o) return memoryview(self._tab.Bytes)[_off : _off + _len] return None + + +class Error(ErrorGen.Error): + @classmethod + def GetRootAsError(cls, buf, offset): + n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset) + x = Error() + x.Init(buf, n + offset) + return x + + def Init(self, buf, pos): + self._tab = flatbuffers.table.Table(buf, pos) + + def ArgsAsBytes(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10)) + if o != 0: + _off = self._tab.Vector(o) + _len = self._tab.VectorLen(o) + return memoryview(self._tab.Bytes)[_off : _off + _len] + return None + + def KwargsAsBytes(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12)) + if o != 0: + _off = self._tab.Vector(o) + _len = self._tab.VectorLen(o) + return memoryview(self._tab.Bytes)[_off : _off + _len] + return None + + def PayloadAsBytes(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(14)) + if o != 0: + _off = self._tab.Vector(o) + _len = self._tab.VectorLen(o) + return memoryview(self._tab.Bytes)[_off : _off + _len] + return None + + def EncKeyAsBytes(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(22)) + if o != 0: + _off = self._tab.Vector(o) + _len = self._tab.VectorLen(o) + return memoryview(self._tab.Bytes)[_off : _off + _len] + return None + + +class Call(CallGen.Call): + @classmethod + def GetRootAsCall(cls, buf, offset): + n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset) + x = Call() + x.Init(buf, n + offset) + return x + + def Init(self, buf, pos): + self._tab = flatbuffers.table.Table(buf, pos) + + def ArgsAsBytes(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10)) + if o != 0: + _off = self._tab.Vector(o) + _len = self._tab.VectorLen(o) + return memoryview(self._tab.Bytes)[_off : _off + _len] + return None + + def KwargsAsBytes(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12)) + if o != 0: + _off = self._tab.Vector(o) + _len = self._tab.VectorLen(o) + return memoryview(self._tab.Bytes)[_off : _off + _len] + return None + + def PayloadAsBytes(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(14)) + if o != 0: + _off = self._tab.Vector(o) + _len = self._tab.VectorLen(o) + return memoryview(self._tab.Bytes)[_off : _off + _len] + return None + + def EncKeyAsBytes(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(22)) + if o != 0: + _off = self._tab.Vector(o) + _len = self._tab.VectorLen(o) + return memoryview(self._tab.Bytes)[_off : _off + _len] + return None + + +class Result(ResultGen.Result): + @classmethod + def GetRootAsResult(cls, buf, offset): + n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset) + x = Result() + x.Init(buf, n + offset) + return x + + def Init(self, buf, pos): + self._tab = flatbuffers.table.Table(buf, pos) + + def ArgsAsBytes(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8)) + if o != 0: + _off = self._tab.Vector(o) + _len = self._tab.VectorLen(o) + return memoryview(self._tab.Bytes)[_off : _off + _len] + return None + + def KwargsAsBytes(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10)) + if o != 0: + _off = self._tab.Vector(o) + _len = self._tab.VectorLen(o) + return memoryview(self._tab.Bytes)[_off : _off + _len] + return None + + def PayloadAsBytes(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12)) + if o != 0: + _off = self._tab.Vector(o) + _len = self._tab.VectorLen(o) + return memoryview(self._tab.Bytes)[_off : _off + _len] + return None + + def EncKeyAsBytes(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(22)) + if o != 0: + _off = self._tab.Vector(o) + _len = self._tab.VectorLen(o) + return memoryview(self._tab.Bytes)[_off : _off + _len] + return None + + +class Invocation(InvocationGen.Invocation): + @classmethod + def GetRootAsInvocation(cls, buf, offset): + n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset) + x = Invocation() + x.Init(buf, n + offset) + return x + + def Init(self, buf, pos): + self._tab = flatbuffers.table.Table(buf, pos) + + def ArgsAsBytes(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10)) + if o != 0: + _off = self._tab.Vector(o) + _len = self._tab.VectorLen(o) + return memoryview(self._tab.Bytes)[_off : _off + _len] + return None + + def KwargsAsBytes(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12)) + if o != 0: + _off = self._tab.Vector(o) + _len = self._tab.VectorLen(o) + return memoryview(self._tab.Bytes)[_off : _off + _len] + return None + + def PayloadAsBytes(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(14)) + if o != 0: + _off = self._tab.Vector(o) + _len = self._tab.VectorLen(o) + return memoryview(self._tab.Bytes)[_off : _off + _len] + return None + + def EncKeyAsBytes(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(26)) + if o != 0: + _off = self._tab.Vector(o) + _len = self._tab.VectorLen(o) + return memoryview(self._tab.Bytes)[_off : _off + _len] + return None + + +class Yield(YieldGen.Yield): + @classmethod + def GetRootAsYield(cls, buf, offset): + n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset) + x = Yield() + x.Init(buf, n + offset) + return x + + def Init(self, buf, pos): + self._tab = flatbuffers.table.Table(buf, pos) + + def ArgsAsBytes(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8)) + if o != 0: + _off = self._tab.Vector(o) + _len = self._tab.VectorLen(o) + return memoryview(self._tab.Bytes)[_off : _off + _len] + return None + + def KwargsAsBytes(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10)) + if o != 0: + _off = self._tab.Vector(o) + _len = self._tab.VectorLen(o) + return memoryview(self._tab.Bytes)[_off : _off + _len] + return None + + def PayloadAsBytes(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12)) + if o != 0: + _off = self._tab.Vector(o) + _len = self._tab.VectorLen(o) + return memoryview(self._tab.Bytes)[_off : _off + _len] + return None + + def EncKeyAsBytes(self): + o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(20)) + if o != 0: + _off = self._tab.Vector(o) + _len = self._tab.VectorLen(o) + return memoryview(self._tab.Bytes)[_off : _off + _len] + return None diff --git a/autobahn/wamp/serializer.py b/autobahn/wamp/serializer.py index 79487f89c..56245a21e 100644 --- a/autobahn/wamp/serializer.py +++ b/autobahn/wamp/serializer.py @@ -294,7 +294,7 @@ def serialize_payload(self, data): """ # FlatBuffersSerializer has _payload_serializer (separate from envelope) # Traditional serializers use _serializer (same for envelope and payload) - payload_ser = getattr(self, '_payload_serializer', self._serializer) + payload_ser = getattr(self, "_payload_serializer", self._serializer) return payload_ser.serialize(data) def unserialize( @@ -1054,8 +1054,38 @@ class FlatBuffersObjectSerializer(object): """ MESSAGE_TYPE_MAP = { + # Category 4: Both Payload and Forwarding + message_fbs.MessageType.ERROR: (message_fbs.Error, message.Error), message_fbs.MessageType.EVENT: (message_fbs.Event, message.Event), message_fbs.MessageType.PUBLISH: (message_fbs.Publish, message.Publish), + message_fbs.MessageType.CALL: (message_fbs.Call, message.Call), + message_fbs.MessageType.RESULT: (message_fbs.Result, message.Result), + message_fbs.MessageType.INVOCATION: (message_fbs.Invocation, message.Invocation), + message_fbs.MessageType.YIELD: (message_fbs.Yield, message.Yield), + + # Category 1: Session lifecycle messages + message_fbs.MessageType.ABORT: (message_fbs.AbortGen.Abort, message.Abort), + message_fbs.MessageType.CHALLENGE: (message_fbs.ChallengeGen.Challenge, message.Challenge), + message_fbs.MessageType.AUTHENTICATE: (message_fbs.AuthenticateGen.Authenticate, message.Authenticate), + message_fbs.MessageType.GOODBYE: (message_fbs.GoodbyeGen.Goodbye, message.Goodbye), + + # Category 1: PubSub messages + message_fbs.MessageType.SUBSCRIBE: (message_fbs.SubscribeGen.Subscribe, message.Subscribe), + message_fbs.MessageType.SUBSCRIBED: (message_fbs.SubscribedGen.Subscribed, message.Subscribed), + message_fbs.MessageType.PUBLISHED: (message_fbs.PublishedGen.Published, message.Published), + message_fbs.MessageType.UNSUBSCRIBE: (message_fbs.UnsubscribeGen.Unsubscribe, message.Unsubscribe), + message_fbs.MessageType.UNSUBSCRIBED: (message_fbs.UnsubscribedGen.Unsubscribed, message.Unsubscribed), + + # Category 1: RPC messages + message_fbs.MessageType.REGISTER: (message_fbs.RegisterGen.Register, message.Register), + message_fbs.MessageType.REGISTERED: (message_fbs.RegisteredGen.Registered, message.Registered), + message_fbs.MessageType.UNREGISTER: (message_fbs.UnregisterGen.Unregister, message.Unregister), + message_fbs.MessageType.UNREGISTERED: (message_fbs.UnregisteredGen.Unregistered, message.Unregistered), + + # Category 3: Forwarding Only messages + message_fbs.MessageType.EVENT_RECEIVED: (message_fbs.EventReceivedGen.EventReceived, message.EventReceived), + message_fbs.MessageType.CANCEL: (message_fbs.CancelGen.Cancel, message.Cancel), + message_fbs.MessageType.INTERRUPT: (message_fbs.InterruptGen.Interrupt, message.Interrupt), } def __init__(self, batched=False): diff --git a/autobahn/websocket/compress.py b/autobahn/websocket/compress.py index b52ae8011..f2c8bada9 100644 --- a/autobahn/websocket/compress.py +++ b/autobahn/websocket/compress.py @@ -144,7 +144,8 @@ # Use 'brotli' on CPython (CPyExt), 'brotlicffi' on PyPy (CFFI) try: import platform - if platform.python_implementation() == 'PyPy': + + if platform.python_implementation() == "PyPy": # noinspection PyPackageRequirements import brotlicffi as brotli else: diff --git a/docs/index.rst b/docs/index.rst index 4d4e8b260..1fff4a98a 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -17,6 +17,7 @@ Contents websocket/conformance wamp/programming wamp/flatbuffers-schema + wamp/message-design wamp/aio-repl wamp/examples environments/index diff --git a/docs/wamp/message-design.rst b/docs/wamp/message-design.rst new file mode 100644 index 000000000..356b717cd --- /dev/null +++ b/docs/wamp/message-design.rst @@ -0,0 +1,725 @@ +.. _wamp-message-design: + +WAMP Message Class Design +========================== + +This document describes the architectural design of WAMP message classes in Autobahn|Python, including the rationale for using multiple inheritance with mixin classes to handle application payload and router forwarding attributes. + +Overview +-------- + +Autobahn|Python implements all 25 WAMP message types defined in the `WAMP specification `_. Analysis of these message classes reveals a **perfect architectural pattern** governing two orthogonal concerns: + +1. **Application Payload** - Messages carrying application data (args/kwargs) with optional E2E encryption +2. **Router Forwarding** - Messages that can traverse router-to-router links in distributed fabrics + +This pattern is implemented using **multiple inheritance with mixin classes**, providing a clean separation of concerns that maps directly to the WAMP protocol architecture. + +The Four Message Categories +---------------------------- + +Based on analysis documented in `WAMP Message Attributes: E2E Encryption & Router-to-Router Links `_, all WAMP messages fall into exactly **four categories**: + +Category 1: Neither Payload nor Forwarding (12 messages) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**Messages**: Session lifecycle and acknowledgments + +- ``Abort``, ``Authenticate``, ``Challenge`` +- ``EventReceived`` +- ``Goodbye``, ``Hello`` +- ``Published``, ``Registered``, ``Subscribed`` +- ``Unregistered``, ``Unsubscribed`` +- ``Welcome`` + +**Characteristics**: + +- Local to router-client connection +- Never forwarded across router boundaries +- No application payload (or payload not encryptable) +- Session management only + +**Implementation**: Derive directly from ``Message`` base class + +Category 2: Payload Only (0 messages) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**Messages**: NONE - This category is **architecturally empty** + +**Why Empty**: Messages with application payload that needs encryption (payload transparency) **must also** support forwarding (``forward_for``) because: + +- E2E encrypted payloads are meant to cross router boundaries +- Multi-router topologies are a primary use case for E2EE +- Encrypted messages must be routable in distributed fabrics + +**Architectural Insight**: Payload transparency without router forwarding is **meaningless** - if you're encrypting end-to-end, you inherently need multi-hop routing. + +Category 3: Forwarding Only (6 messages) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**Messages**: Control/management messages without application payload + +- ``Cancel``, ``Interrupt`` +- ``Register``, ``Unregister`` +- ``Subscribe``, ``Unsubscribe`` + +**Characteristics**: + +- Control plane operations +- No application payload to encrypt +- Must be forwarded across routers (R-Links) +- Carry metadata only (URIs, IDs, options) + +**Implementation**: Derive from ``MessageWithForwardFor`` mixin + +**Example**: A ``SUBSCRIBE`` message from Client A connected to Router 1 for a topic handled by Router 2: + +.. code-block:: text + + Client A → Router 1 → Router 2 + +Router 1 forwards ``SUBSCRIBE`` to Router 2, adding itself to ``forward_for``. When an ``EVENT`` comes back, Router 2 knows to send it to Router 1 (following the ``forward_for`` chain in reverse). + +Category 4: Both Payload and Forwarding (7 messages) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**Messages**: Application data plane - ALL messages carrying application payload + +- **PubSub**: ``PUBLISH``, ``EVENT`` +- **RPC**: ``CALL``, ``RESULT``, ``INVOCATION``, ``YIELD`` +- **Errors**: ``ERROR`` (can carry payload in args/kwargs) + +**Characteristics**: + +- Carry application payload (args/kwargs or opaque bytes) +- Support E2E encryption (payload transparency) +- Can be routed across multiple routers (R-Links) +- The **only** messages users interact with for data exchange + +**Implementation**: Derive from both ``MessageWithAppPayload`` and ``MessageWithForwardFor`` mixins (multiple inheritance) + +**Why**: These messages are the **core data plane** of WAMP. They: + +1. Carry user application data that may need encryption +2. Must traverse router-to-router links in distributed topologies +3. Require both E2EE and R-Link capabilities + +Architectural Design: Multiple Inheritance with Mixins +------------------------------------------------------- + +The Problem: Code Duplication +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Without mixins, the original design duplicated attribute handling across multiple message classes: + +- **Application payload attributes** (``args``, ``kwargs``, ``payload``, ``enc_algo``, ``enc_key``, ``enc_serializer``) duplicated across 7 Category 4 messages +- **Forwarding attribute** (``forward_for``) duplicated across 13 messages (6 Category 3 + 7 Category 4) +- Lazy FlatBuffers deserialization logic duplicated in each message class + +This violated the **DRY principle** (Don't Repeat Yourself) and made maintenance difficult. + +The Solution: Mixin Classes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +We introduce two mixin classes that encapsulate orthogonal concerns: + +1. ``MessageWithAppPayload`` - Handles application payload attributes +2. ``MessageWithForwardFor`` - Handles router forwarding attributes + +These mixins are **orthogonal** (no overlapping attributes) and can be composed using multiple inheritance. + +Class Hierarchy +~~~~~~~~~~~~~~~~ + +.. code-block:: text + + Message (base) + │ + ├─ MessageWithAppPayload (mixin) + │ ├─ args + │ ├─ kwargs + │ ├─ payload + │ ├─ enc_algo + │ ├─ enc_key + │ └─ enc_serializer + │ + ├─ MessageWithForwardFor (mixin) + │ └─ forward_for + │ + └─ Concrete message classes: + ├─ Category 1 (12): Message only + │ └─ Hello, Welcome, Abort, ... + ├─ Category 3 (6): MessageWithForwardFor only + │ └─ Subscribe, Register, Cancel, ... + └─ Category 4 (7): MessageWithAppPayload + MessageWithForwardFor + └─ Publish, Event, Call, Result, ... + +Why Multiple Inheritance? +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**Advantages**: + +1. **Separation of Concerns**: Each mixin handles exactly one orthogonal concern +2. **DRY Principle**: Payload logic in one place, forwarding logic in one place +3. **Type Safety**: Mixins can be type-hinted for better IDE support +4. **Direct Mapping**: Maps perfectly to the 4 architectural categories +5. **Pythonic Pattern**: Standard approach in frameworks like Django, Flask +6. **Maintainability**: Changes to payload handling affect only ``MessageWithAppPayload`` + +**Method Resolution Order (MRO)**: + +Python's C3 linearization ensures predictable method resolution: + +.. code-block:: python + + class Publish(MessageWithAppPayload, MessageWithForwardFor): + pass + + # MRO: Publish → MessageWithAppPayload → MessageWithForwardFor → Message → object + +Since mixins are orthogonal (no overlapping methods), MRO conflicts cannot occur. + +Technical Implementation: ``__slots__`` and Initialization +----------------------------------------------------------- + +The implementation uses sophisticated Python patterns to combine multiple inheritance with ``__slots__`` for memory efficiency. + +The ``__slots__`` Pattern +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**Challenge**: Python's ``__slots__`` mechanism has strict rules for multiple inheritance. + +**Rule**: You cannot have multiple base classes that both define non-empty, non-overlapping ``__slots__``. + +Attempting this causes: ``TypeError: multiple bases have instance lay-out conflict`` + +**Solution**: Mixins use **empty** ``__slots__ = ()`` while the concrete message class defines all slots. + +Empty ``__slots__ = ()`` vs No ``__slots__`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This distinction is **critical**: + +**Empty slots** (``__slots__ = ()``): + +.. code-block:: python + + class MessageWithAppPayload(object): + __slots__ = () # ✓ "I add no new slots, but use slots from derived classes" + +**Effect**: + +- Class has NO ``__dict__`` (memory efficient) +- Class adds NO new slot attributes +- Derived classes can still use ``__slots__`` +- Acts as a "pure mixin" for methods only + +**No slots defined**: + +.. code-block:: python + + class MessageWithAppPayload(object): + # No __slots__ defined # ✗ "I use __dict__ for attributes" + +**Effect**: + +- Class gets a ``__dict__`` for dynamic attributes +- Breaks the ``__slots__`` chain +- Derived classes can't benefit from slots +- Wastes memory with unnecessary ``__dict__`` + +**DO NOT REMOVE** the ``__slots__ = ()`` from mixin classes! + +How It Works +^^^^^^^^^^^^ + +The pattern combines three elements: + +1. **Message base class**: Defines ``__slots__`` for base attributes (``_from_fbs``, ``_serialized``, etc.) +2. **Mixin classes**: Have ``__slots__ = ()`` (empty) and provide methods only +3. **Concrete classes**: Define ``__slots__`` for their own attributes PLUS mixin attributes + +Example with ``Publish``: + +.. code-block:: python + + class Message(object): + __slots__ = ('_from_fbs', '_serialized', ...) # Base slots + + class MessageWithAppPayload(object): + __slots__ = () # Empty - adds no storage + + class MessageWithForwardFor(object): + __slots__ = () # Empty - adds no storage + + class Publish(MessageWithAppPayload, MessageWithForwardFor, Message): + __slots__ = ( + # From Message base (inherited, not redefined) + # Publish-specific slots + '_request', + '_topic', + '_acknowledge', + # ... other Publish attributes + # From MessageWithAppPayload mixin (storage defined here, methods in mixin) + '_args', + '_kwargs', + '_payload', + '_enc_algo', + '_enc_key', + '_enc_serializer', + # From MessageWithForwardFor mixin (storage defined here, methods in mixin) + '_forward_for', + ) + +**Memory layout**: All slots from ``Message`` and ``Publish`` are allocated in the instance. The mixins provide the **methods** (properties) to access their logical slots, but the **storage** is defined in the concrete class. + +**Why this works**: + +- Only ONE class in the inheritance chain (``Message``) defines actual base slots +- Mixins have empty ``__slots__``, so no conflict +- ``Publish`` adds more slots for itself and the mixins +- Mixins provide property accessors for "their" slots +- No ``__dict__`` anywhere - pure slot-based storage + +Method Resolution Order (MRO): + +.. code-block:: python + + >>> Publish.__mro__ + (, + , # ← Provides args/kwargs/payload properties + , # ← Provides forward_for property + , # ← Base functionality + ) + +When accessing ``publish_instance.args``: + +1. Python looks in ``Publish`` → not found +2. Looks in ``MessageWithAppPayload`` → **found** (the ``@property`` method) +3. That property accesses ``self._args`` which exists in ``Publish.__slots__`` + +Initialization Pattern +~~~~~~~~~~~~~~~~~~~~~~ + +**Challenge**: Cannot use ``__init__()`` in mixins with multiple inheritance. + +**Problem with mixin ``__init__()``**: + +.. code-block:: python + + class MessageWithAppPayload(object): + def __init__(self, args=None, kwargs=None, ...): + super().__init__(...) # ✗ Complicated MRO chain! + self._args = args + +Issues: + +- ``super().__init__()`` follows MRO, causing ``Message.__init__()`` to be called multiple times +- Confusing initialization order +- Hard to debug and reason about + +**Solution: Explicit initialization methods**: + +.. code-block:: python + + class MessageWithAppPayload(object): + def _init_app_payload(self, args=None, kwargs=None, ...): + """Initialize application payload attributes (no super() call).""" + self._args = args + self._kwargs = kwargs + # ... + + class Publish(...): + def __init__(self, request, topic, args=None, ...): + # Call Message.__init__() exactly once + Message.__init__(self, from_fbs=from_fbs) + + # Call mixin initialization methods explicitly + self._init_app_payload(args=args, kwargs=kwargs, ...) + self._init_forward_for(forward_for=forward_for) + + # Initialize Publish-specific attributes + self._request = request + self._topic = topic + +**Benefits**: + +- Clear, explicit initialization order +- ``Message.__init__()`` called exactly once +- No confusing ``super()`` chains +- Easy to debug and understand +- Each initialization step is explicit and traceable + +**Why not use ``super()``?** + +With multiple inheritance, ``super()`` follows the MRO chain. For ``Publish``: + +.. code-block:: python + + # If mixins used __init__ with super(): + Publish.__init__() + → super().__init__() # Calls MessageWithAppPayload.__init__() + → super().__init__() # Calls MessageWithForwardFor.__init__() + → super().__init__() # Calls Message.__init__() + +This works, but requires ALL classes in the chain to properly use ``super()`` and accept ``**kwargs`` to pass along unknown arguments. It's fragile and hard to maintain. + +Our explicit pattern is more verbose but much clearer: + +.. code-block:: python + + Publish.__init__() + → Message.__init__(from_fbs=from_fbs) # Explicit, called once + → self._init_app_payload(...) # Explicit + → self._init_forward_for(...) # Explicit + +The MessageWithAppPayload Mixin +-------------------------------- + +Purpose +~~~~~~~ + +Encapsulates the "6-set" of application payload attributes that **always co-occur** in Category 4 messages. + +The "6-set" Attributes +~~~~~~~~~~~~~~~~~~~~~~ + +These six attributes form an **inseparable unit**: + +1. ``args`` (list) - Positional arguments for procedures/events +2. ``kwargs`` (dict) - Keyword arguments for procedures/events +3. ``payload`` (bytes) - Opaque encrypted payload (for E2EE) +4. ``enc_algo`` (str) - Encryption/encoding algorithm identifier +5. ``enc_key`` (str) - Key identifier for decryption +6. ``enc_serializer`` (str) - Payload serializer ID + +**Co-occurrence Rule**: In E2EE mode, attributes 3-6 **must all be present** or **all be None**. In normal mode, all are None except ``args``/``kwargs``. + +Payload Serialization Architecture +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The mixin handles a **three-level architecture**: + +1. **Transport Serializer** - Serializes the WAMP message envelope (JSON/msgpack/CBOR/ubjson/FlatBuffers) +2. **Payload Mode** - Normal (args/kwargs) vs Transparent (E2EE with opaque payload) +3. **Payload Serializer** - Serializes application data within the envelope + +**Supported Payload Serializers**: + +Per ``enum Serializer`` in `types.fbs `_: + +- ``TRANSPORT = 0`` - Use same serializer as transport (default for FlatBuffers) +- ``JSON = 1`` - Explicit JSON serialization +- ``MSGPACK = 2`` - Explicit MessagePack serialization +- ``CBOR = 3`` - Explicit CBOR serialization (default) +- ``UBJSON = 4`` - Explicit UBJSON serialization +- ``OPAQUE = 5`` - Raw pass-through (no serialization) +- ``FLATBUFFERS = 6`` - FlatBuffers with static schema +- ``FLEXBUFFERS = 7`` - FlexBuffers with quasi-dynamic typing + +**Important**: ``enc_serializer`` specifies **payload** serialization, not transport serialization. The name "enc" stands for **encoding**, not encryption (though it's also used in E2EE contexts). + +Implementation Details +~~~~~~~~~~~~~~~~~~~~~~ + +The mixin provides: + +**Lazy Deserialization**: + +When a FlatBuffers-serialized message is received, ``args``/``kwargs`` are deserialized on first access using the serializer specified by ``enc_serializer``. + +.. code-block:: python + + @property + def args(self): + """Lazy deserialization of args from FlatBuffers""" + if self._args is None and self._from_fbs: + if self._from_fbs.ArgsLength(): + ser_id = self.enc_serializer or "cbor" # Default to CBOR + args_bytes = self._from_fbs.ArgsAsBytes() # Returns memoryview! + self._args = self._deserialize_with_memoryview(args_bytes, ser_id) + return self._args + +**Zero-Copy Optimization**: + +The helper ``_deserialize_with_memoryview()`` uses memoryview (zero-copy) for serializers that support it: + +- ✓ **Zero-copy**: CBOR (``cbor2``), MessagePack (``msgpack``), UBJSON (``ubjson``) +- ✗ **Requires bytes()**: JSON (``json``), FlexBuffers (``flatbuffers.flexbuffers``) + +.. code-block:: python + + def _deserialize_with_memoryview(self, data_bytes, ser_id): + """ + Deserialize with memoryview where possible for zero-copy efficiency. + Converts to bytes only for JSON/FlexBuffers. + """ + if ser_id == "json" or ser_id == "flexbuffers": + data = bytes(data_bytes) # One copy needed + else: + data = data_bytes # Keep as memoryview (zero-copy) + + serializer = self._get_payload_serializer(ser_id) + return serializer.unserialize(data)[0] + +**FlexBuffers Support**: + +FlexBuffers provides schema-less, quasi-dynamic typing using FlatBuffers infrastructure: + +.. code-block:: python + + if ser_id == "flexbuffers": + import flatbuffers.flexbuffers as flexbuffers + root = flexbuffers.GetRoot(bytes(args_bytes)) + self._args = root.AsVector.Value # Returns Python list + # For kwargs: root.AsMap.Value returns Python dict + +The MessageWithForwardFor Mixin +-------------------------------- + +Purpose +~~~~~~~ + +Encapsulates router-to-router forwarding metadata for messages that can traverse distributed router fabrics. + +The forward_for Attribute +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**Type**: ``list[dict]`` - Forwarding chain metadata + +**Structure**: Each entry is a dictionary with: + +.. code-block:: python + + { + 'session': int, # WAMP session ID + 'authid': str, # Authentication ID + 'authrole': str # Authentication role + } + +**Semantics**: + +- Each router that forwards a message adds its own entry to ``forward_for`` +- The chain tracks the complete routing path +- Prevents routing loops (routers check if they're already in the chain) +- Enables reverse routing (responses follow the chain backward) + +Implementation Details +~~~~~~~~~~~~~~~~~~~~~~ + +The mixin provides: + +**Lazy Deserialization**: + +When a FlatBuffers-serialized message is received, ``forward_for`` is deserialized on first access from the FlatBuffers ``Principal`` objects: + +.. code-block:: python + + @property + def forward_for(self): + """Lazy deserialization of forward_for from FlatBuffers""" + if self._forward_for is None and self._from_fbs: + if self._from_fbs.ForwardForLength(): + forward_for = [] + for j in range(self._from_fbs.ForwardForLength()): + principal = self._from_fbs.ForwardFor(j) + authid = principal.Authid() + if authid: + authid = authid.decode('utf-8') + authrole = principal.Authrole() + if authrole: + authrole = authrole.decode('utf-8') + forward_for.append({ + 'session': principal.Session(), + 'authid': authid, + 'authrole': authrole, + }) + self._forward_for = forward_for + return self._forward_for + +**Helper Methods** (future): + +The mixin could provide utility methods like: + +- ``_validate_forward_chain()`` - Check for routing loops +- ``_add_hop(session, authid, authrole)`` - Append router to chain +- ``_reverse_chain()`` - Get return path for responses + +Concrete Message Classes +------------------------- + +Category 4 Example: Publish +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``Publish`` message demonstrates multiple inheritance combining both mixins: + +.. code-block:: python + + class Publish(MessageWithAppPayload, MessageWithForwardFor): + """ + WAMP PUBLISH message. + + Combines: + - Application payload (args/kwargs/payload/enc_*) + - Router forwarding (forward_for) + - Publish-specific attributes (request, topic, options) + """ + MESSAGE_TYPE = 16 + + def __init__(self, request, topic, args=None, kwargs=None, + payload=None, enc_algo=None, enc_key=None, + enc_serializer=None, acknowledge=None, + retain=None, exclude_me=None, exclude=None, + exclude_authid=None, exclude_authrole=None, + eligible=None, eligible_authid=None, + eligible_authrole=None, forward_for=None): + # Initialize mixins + MessageWithAppPayload.__init__( + self, args=args, kwargs=kwargs, payload=payload, + enc_algo=enc_algo, enc_key=enc_key, + enc_serializer=enc_serializer + ) + MessageWithForwardFor.__init__(self, forward_for=forward_for) + + # Publish-specific attributes + self.request = request + self.topic = topic + self.acknowledge = acknowledge + self.retain = retain + # ... other options + +**Method Resolution Order**: + +.. code-block:: python + + >>> Publish.__mro__ + (, + , + , + , + ) + +Category 3 Example: Subscribe +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``Subscribe`` message uses only the forwarding mixin: + +.. code-block:: python + + class Subscribe(MessageWithForwardFor): + """ + WAMP SUBSCRIBE message. + + Combines: + - Router forwarding (forward_for) + - Subscribe-specific attributes (request, topic, match) + """ + MESSAGE_TYPE = 32 + + def __init__(self, request, topic, match=None, + get_retained=None, forward_for=None): + MessageWithForwardFor.__init__(self, forward_for=forward_for) + + self.request = request + self.topic = topic + self.match = match + self.get_retained = get_retained + +Validation Rules +---------------- + +The "6-set" Co-occurrence Validation +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +**Rule**: In messages with application payload, the E2EE attributes must follow all-or-none semantics: + +- If ``payload`` is not None, then ``enc_algo``, ``enc_key``, and ``enc_serializer`` **must all be present** +- If ``payload`` is None, then ``enc_algo``, ``enc_key``, and ``enc_serializer`` **must all be None** + +**Mutual Exclusivity**: + +- If ``payload`` is not None (E2EE mode), then ``args`` and ``kwargs`` **must be None** +- If ``args`` or ``kwargs`` is not None (normal mode), then ``payload`` **must be None** + +**Future Implementation**: + +These validation rules should be implemented in ``MessageWithAppPayload.__init__()`` or as a separate validation method: + +.. code-block:: python + + def _validate_payload_attributes(self): + """Validate "6-set" co-occurrence rules""" + has_payload = self._payload is not None + has_enc_algo = self._enc_algo is not None + has_enc_key = self._enc_key is not None + has_enc_serializer = self._enc_serializer is not None + + # All-or-none for E2EE attributes + if has_payload: + if not (has_enc_algo and has_enc_key and has_enc_serializer): + raise ValueError( + "E2EE mode: payload requires enc_algo, enc_key, " + "and enc_serializer" + ) + else: + if has_enc_algo or has_enc_key or has_enc_serializer: + raise ValueError( + "Normal mode: enc_algo/enc_key/enc_serializer " + "require payload" + ) + + # Mutual exclusivity + has_args = self._args is not None + has_kwargs = self._kwargs is not None + + if has_payload and (has_args or has_kwargs): + raise ValueError( + "Cannot have both payload (E2EE) and args/kwargs (normal)" + ) + +Migration Notes +--------------- + +API Compatibility +~~~~~~~~~~~~~~~~~ + +This refactoring is **internal only** - the public API remains unchanged: + +- All message classes still have the same ``__init__()`` signatures +- All attributes remain accessible with the same names +- Serialization/deserialization behavior is identical + +No user code changes are required. + +Internal Changes +~~~~~~~~~~~~~~~~ + +For Autobahn|Python developers: + +1. Message classes now use multiple inheritance (Category 3 & 4 only) +2. Attribute initialization delegates to mixin ``__init__()`` methods +3. Lazy deserialization logic moved to mixins (DRY) +4. FlatBuffers ``build()`` methods will use mixin helpers + +Benefits +~~~~~~~~ + +- **Maintainability**: Changes to payload/forwarding logic in one place +- **Testability**: Mixins can be unit-tested independently +- **Extensibility**: New payload serializers added in one place +- **Type Safety**: Better IDE support with mixin type hints +- **Performance**: Zero-copy optimizations benefit all messages automatically + +References +---------- + +- `WAMP Specification `_ +- `WAMP Message Attributes: E2E Encryption & Router-to-Router Links `_ +- `WEP006 - Zero-copy WAMP Serialization with FlatBuffers `_ +- `FlatBuffers Schema Reference `_ +- :mod:`autobahn.wamp.message` - Message class implementations +- :mod:`autobahn.wamp.serializer` - Serializer implementations + +--- + +*This architectural design ensures Autobahn|Python's WAMP message implementation remains maintainable, performant, and aligned with the WAMP protocol specification.* diff --git a/examples/benchmarks/serialization/loader.py b/examples/benchmarks/serialization/loader.py index c58ba940d..7e5c2af62 100644 --- a/examples/benchmarks/serialization/loader.py +++ b/examples/benchmarks/serialization/loader.py @@ -47,29 +47,29 @@ from sample import JSON_DATA1, JSON_DATA2, JSON_DATA3 __all__ = [ - 'PAYLOAD_MODE_NORMAL', - 'PAYLOAD_MODE_TRANSPARENT', - 'PAYLOAD_SIZE_EMPTY', - 'PAYLOAD_SIZE_SMALL', - 'PAYLOAD_SIZE_MEDIUM', - 'PAYLOAD_SIZE_LARGE', - 'PAYLOAD_SIZE_XL', - 'PAYLOAD_SIZE_XXL', - 'VehicleEvent', - 'load', + "PAYLOAD_MODE_NORMAL", + "PAYLOAD_MODE_TRANSPARENT", + "PAYLOAD_SIZE_EMPTY", + "PAYLOAD_SIZE_SMALL", + "PAYLOAD_SIZE_MEDIUM", + "PAYLOAD_SIZE_LARGE", + "PAYLOAD_SIZE_XL", + "PAYLOAD_SIZE_XXL", + "VehicleEvent", + "load", ] # Payload modes -PAYLOAD_MODE_NORMAL = 'normal' -PAYLOAD_MODE_TRANSPARENT = 'transparent' +PAYLOAD_MODE_NORMAL = "normal" +PAYLOAD_MODE_TRANSPARENT = "transparent" # Payload sizes -PAYLOAD_SIZE_EMPTY = 'empty' -PAYLOAD_SIZE_SMALL = 'small' -PAYLOAD_SIZE_MEDIUM = 'medium' -PAYLOAD_SIZE_LARGE = 'large' -PAYLOAD_SIZE_XL = 'xl' -PAYLOAD_SIZE_XXL = 'xxl' +PAYLOAD_SIZE_EMPTY = "empty" +PAYLOAD_SIZE_SMALL = "small" +PAYLOAD_SIZE_MEDIUM = "medium" +PAYLOAD_SIZE_LARGE = "large" +PAYLOAD_SIZE_XL = "xl" +PAYLOAD_SIZE_XXL = "xxl" def deg2num(lat_deg: float, lon_deg: float, zoom: int) -> Tuple[int, int]: @@ -85,9 +85,13 @@ def deg2num(lat_deg: float, lon_deg: float, zoom: int) -> Tuple[int, int]: Tuple of (xtile, ytile) coordinates """ lat_rad = math.radians(lat_deg) - n = 2.0 ** zoom + n = 2.0**zoom xtile = int((lon_deg + 180.0) / 360.0 * n) - ytile = int((1.0 - math.log(math.tan(lat_rad) + (1 / math.cos(lat_rad))) / math.pi) / 2.0 * n) + ytile = int( + (1.0 - math.log(math.tan(lat_rad) + (1 / math.cos(lat_rad))) / math.pi) + / 2.0 + * n + ) return (xtile, ytile) @@ -113,13 +117,13 @@ def __init__( self, fleet: Optional[str] = None, zoom: int = 18, - size: str = PAYLOAD_SIZE_SMALL + size: str = PAYLOAD_SIZE_SMALL, ): self._fleet = fleet self._zoom = zoom self._size = size - self.vehicle_id = 'unknown' + self.vehicle_id = "unknown" self.timestamp: Optional[datetime] = None self.lng: Optional[float] = None self.lat: Optional[float] = None @@ -140,7 +144,7 @@ def __init__( self._frame_data = os.urandom(128 * 1024) # 128KB binary frame @staticmethod - def from_row(row: Dict[str, str], fleet: str, size: str) -> 'VehicleEvent': + def from_row(row: Dict[str, str], fleet: str, size: str) -> "VehicleEvent": """ Create VehicleEvent from CSV row. @@ -156,22 +160,26 @@ def from_row(row: Dict[str, str], fleet: str, size: str) -> 'VehicleEvent': if size == PAYLOAD_SIZE_EMPTY: return obj - obj.vehicle_id = f'vehicle{row["vehicleID"]}' - obj.timestamp = datetime.strptime(row['ts'], '%Y-%m-%d %H:%M:%S') - obj.lng = float(row['lon']) - obj.lat = float(row['lat']) + obj.vehicle_id = f"vehicle{row['vehicleID']}" + obj.timestamp = datetime.strptime(row["ts"], "%Y-%m-%d %H:%M:%S") + obj.lng = float(row["lon"]) + obj.lat = float(row["lat"]) if obj.lng and obj.lat: obj.xtile, obj.ytile = deg2num(obj.lat, obj.lng, obj._zoom) - obj.speed = float(row['speed']) + obj.speed = float(row["speed"]) # Available only in some files - obj.rain = float(row['rain']) - obj.wiper = str(row['dyn_wiper']) + obj.rain = float(row["rain"]) + obj.wiper = str(row["dyn_wiper"]) # Use pseudo-random but deterministic value based on vehicle_id and timestamp - random_in = hashlib.sha256(f'{obj.vehicle_id}:{obj.timestamp}'.encode()).digest() - obj.pothole_depth = float(struct.unpack('>L', random_in[:4])[0]) / 2 ** 32 - obj.pothole_type = random.choice(['type-a', 'type-b', 'type-c', 'type-d', 'type-e', 'type-f']) + random_in = hashlib.sha256( + f"{obj.vehicle_id}:{obj.timestamp}".encode() + ).digest() + obj.pothole_depth = float(struct.unpack(">L", random_in[:4])[0]) / 2**32 + obj.pothole_type = random.choice( + ["type-a", "type-b", "type-c", "type-d", "type-e", "type-f"] + ) return obj @@ -186,25 +194,25 @@ def marshal(self) -> Optional[Dict[str, Any]]: return None obj: Dict[str, Any] = { - 'ts': utcnow(), - 'vehicle_id': self.vehicle_id, - 'timestamp': utcstr(self.timestamp), - 'gps_location': { - 'lng': self.lng, - 'lat': self.lat, - 'speed': self.speed, - 'xtile': self.xtile, - 'ytile': self.ytile, - 'zoom': self._zoom + "ts": utcnow(), + "vehicle_id": self.vehicle_id, + "timestamp": utcstr(self.timestamp), + "gps_location": { + "lng": self.lng, + "lat": self.lat, + "speed": self.speed, + "xtile": self.xtile, + "ytile": self.ytile, + "zoom": self._zoom, }, - 'rain_sensor': { - 'rain': self.rain, - 'wiper': self.wiper, + "rain_sensor": { + "rain": self.rain, + "wiper": self.wiper, + }, + "pothole_sensor": { + "depth": self.pothole_depth, + "type": self.pothole_type, }, - 'pothole_sensor': { - 'depth': self.pothole_depth, - 'type': self.pothole_type, - } } # Add additional data for larger payloads @@ -217,15 +225,13 @@ def marshal(self) -> Optional[Dict[str, Any]]: # Use cached frame data for xl/xxl payloads (pre-generated in __init__) if self._frame_data is not None: - obj['frame'] = self._frame_data + obj["frame"] = self._frame_data return obj def load_dataset( - files: Dict[str, Tuple[Callable, str]], - payload_mode: str, - payload_size: str + files: Dict[str, Tuple[Callable, str]], payload_mode: str, payload_size: str ) -> Tuple[Optional[VehicleEvent], Dict[str, List[Tuple[str, Any]]]]: """ Load vehicle telemetry dataset from CSV files. @@ -255,26 +261,32 @@ def load_dataset( fn = os.path.abspath(os.path.join(os.path.dirname(__file__), filename)) vehicles: Dict[str, List[Tuple[str, Any]]] = {} - with open(fn, newline='') as csvfile: + with open(fn, newline="") as csvfile: reader = csv.DictReader(csvfile) # Limit events for large payloads to prevent memory issues if max_events: - vehicle_events = [from_row(row, fleet, size=payload_size) for i, row in enumerate(reader) if i < max_events] + vehicle_events = [ + from_row(row, fleet, size=payload_size) + for i, row in enumerate(reader) + if i < max_events + ] else: - vehicle_events = [from_row(row, fleet, size=payload_size) for row in reader] + vehicle_events = [ + from_row(row, fleet, size=payload_size) for row in reader + ] for evt in vehicle_events: if not sample: sample = evt - vehicle_id = f'{fleet}-{evt.vehicle_id}' - topic = f'com.example.vehicle.{evt.vehicle_id}.xtile.{evt.xtile}.ytile.{evt.ytile}' + vehicle_id = f"{fleet}-{evt.vehicle_id}" + topic = f"com.example.vehicle.{evt.vehicle_id}.xtile.{evt.xtile}.ytile.{evt.ytile}" if payload_mode == PAYLOAD_MODE_NORMAL: evt_data = evt.marshal() if payload_size != PAYLOAD_SIZE_EMPTY else None elif payload_mode == PAYLOAD_MODE_TRANSPARENT: if payload_size == PAYLOAD_SIZE_EMPTY: - evt_data = b'' + evt_data = b"" else: evt_data = cbor2.dumps(evt.marshal()) else: @@ -291,8 +303,7 @@ def load_dataset( def load( - payload_mode: str = PAYLOAD_MODE_NORMAL, - payload_size: str = PAYLOAD_SIZE_SMALL + payload_mode: str = PAYLOAD_MODE_NORMAL, payload_size: str = PAYLOAD_SIZE_SMALL ) -> Tuple[Optional[VehicleEvent], Dict[str, List[Tuple[str, Any]]]]: """ Load default vehicle telemetry datasets. @@ -305,15 +316,15 @@ def load( Tuple of (sample_event, vehicles_dict) """ files = { - 'data/dataset1.csv': (VehicleEvent.from_row, 'fleet1'), - 'data/dataset2.csv': (VehicleEvent.from_row, 'fleet2'), + "data/dataset1.csv": (VehicleEvent.from_row, "fleet1"), + "data/dataset2.csv": (VehicleEvent.from_row, "fleet2"), } return load_dataset(files, payload_mode=payload_mode, payload_size=payload_size) -if __name__ == '__main__': +if __name__ == "__main__": sample_evt, vehicles = load() - print(f'Ok, data loaded from {len(vehicles)} vehicles:') + print(f"Ok, data loaded from {len(vehicles)} vehicles:") for vehicle in vehicles: cnt = len(vehicles[vehicle]) - print(f'{vehicle}: {cnt} events') + print(f"{vehicle}: {cnt} events") diff --git a/examples/benchmarks/serialization/main.py b/examples/benchmarks/serialization/main.py index 59f2d9789..07e84d048 100644 --- a/examples/benchmarks/serialization/main.py +++ b/examples/benchmarks/serialization/main.py @@ -69,7 +69,7 @@ load, ) -__all__ = ['main_run', 'main_index'] +__all__ = ["main_run", "main_index"] def main_run(args: argparse.Namespace) -> None: @@ -83,27 +83,27 @@ def main_run(args: argparse.Namespace) -> None: payload_mode = args.payload_mode payload_size = args.payload_size - python = 'cpy' if platform.python_implementation() == 'CPython' else 'pypy' + python = "cpy" if platform.python_implementation() == "CPython" else "pypy" filename_profile = args.profile # Detect actual serializer implementation (e.g., ujson vs json, cbor2 vs cbor) - if args.serializer == 'json' and 'AUTOBAHN_USE_UJSON' in os.environ: - _serializer = 'ujson' - elif args.serializer == 'cbor' and 'AUTOBAHN_USE_CBOR2' in os.environ: - _serializer = 'cbor2' + if args.serializer == "json" and "AUTOBAHN_USE_UJSON" in os.environ: + _serializer = "ujson" + elif args.serializer == "cbor" and "AUTOBAHN_USE_CBOR2" in os.environ: + _serializer = "cbor2" else: _serializer = args.serializer filename_results = os.path.join( args.results, - f'results_{python}_{_serializer}_{payload_mode}_{payload_size}.json' + f"results_{python}_{_serializer}_{payload_mode}_{payload_size}.json", ) # Create serializer factory ser = create_transport_serializer(args.serializer) - print('Preparing benchmarking sample data ..') + print("Preparing benchmarking sample data ..") sample, vehicles = load(payload_mode=payload_mode, payload_size=payload_size) # Prepare sample for display @@ -111,17 +111,21 @@ def main_run(args: argparse.Namespace) -> None: sample_display: Any = len(sample) elif isinstance(sample, VehicleEvent): sample_dict = sample.marshal() - if sample_dict and 'frame' in sample_dict: - sample_dict['frame'] = f'<<<<<<<<< BINARY data, {len(sample_dict["frame"])} bytes >>>>>>>>>>' + if sample_dict and "frame" in sample_dict: + sample_dict["frame"] = ( + f"<<<<<<<<< BINARY data, {len(sample_dict['frame'])} bytes >>>>>>>>>>" + ) sample_display = sample_dict else: - raise RuntimeError(f'unexpected type {type(sample)}') + raise RuntimeError(f"unexpected type {type(sample)}") total_events = sum(len(events) for events in vehicles.values()) - print(f'Ok, data loaded from {len(vehicles)} vehicles, {total_events} events in total.') - print(f'Sample:\n{sample_display}') - print(f'Message serialization test starting with {ser.SERIALIZER_ID}-serializer ..') + print( + f"Ok, data loaded from {len(vehicles)} vehicles, {total_events} events in total." + ) + print(f"Sample:\n{sample_display}") + print(f"Message serialization test starting with {ser.SERIALIZER_ID}-serializer ..") def loop(results: Optional[Dict[str, Any]] = None) -> None: """Inner benchmark loop.""" @@ -162,27 +166,27 @@ def loop(results: Optional[Dict[str, Any]] = None) -> None: bytes_per_sec = int(round(float(total_bytes) / secs, 0)) print( - f'Serialized {total_cnt} messages, {total_bytes} bytes in total, ' - f'{total_bytes // total_cnt} bytes/msg, {msg_per_sec} msgs/sec, ' - f'{bytes_per_sec} bytes/sec' + f"Serialized {total_cnt} messages, {total_bytes} bytes in total, " + f"{total_bytes // total_cnt} bytes/msg, {msg_per_sec} msgs/sec, " + f"{bytes_per_sec} bytes/sec" ) if results is not None: - results['msg_bytes'] = int(round(total_bytes / total_cnt)) - if 'msgs_per_sec' not in results: - results['msgs_per_sec'] = [] - results['msgs_per_sec'].append(msg_per_sec) - if 'bytes_per_sec' not in results: - results['bytes_per_sec'] = [] - results['bytes_per_sec'].append(bytes_per_sec) + results["msg_bytes"] = int(round(total_bytes / total_cnt)) + if "msgs_per_sec" not in results: + results["msgs_per_sec"] = [] + results["msgs_per_sec"].append(msg_per_sec) + if "bytes_per_sec" not in results: + results["bytes_per_sec"] = [] + results["bytes_per_sec"].append(bytes_per_sec) # Warm-up phase - print(f'Warming up {ser.SERIALIZER_ID}-serializer for {iterations} iterations ..') + print(f"Warming up {ser.SERIALIZER_ID}-serializer for {iterations} iterations ..") t = Timer(lambda: loop()) t.timeit(number=iterations) # Measurement phase with profiling - print(f'Measuring {ser.SERIALIZER_ID}-serializer {iterations} iterations ..') + print(f"Measuring {ser.SERIALIZER_ID}-serializer {iterations} iterations ..") results: Dict[str, Any] = {} fd = os.open(filename_profile, os.O_RDWR | os.O_CREAT | os.O_TRUNC, 0o644) @@ -194,24 +198,28 @@ def loop(results: Optional[Dict[str, Any]] = None) -> None: os.close(fd) # Calculate averages - msgs_per_sec = int(round(sum(results['msgs_per_sec']) / len(results['msgs_per_sec']))) - bytes_per_sec = int(round(sum(results['bytes_per_sec']) / len(results['bytes_per_sec']))) + msgs_per_sec = int( + round(sum(results["msgs_per_sec"]) / len(results["msgs_per_sec"])) + ) + bytes_per_sec = int( + round(sum(results["bytes_per_sec"]) / len(results["bytes_per_sec"])) + ) # Save results - with open(filename_results, 'w') as f: + with open(filename_results, "w") as f: obj = { - 'python_version': sys.version, - 'python': python, - 'events': total_events, - 'sample': sample_display, - 'iterations': iterations, - 'msg_bytes': results['msg_bytes'], - 'msgs_per_sec': msgs_per_sec, - 'bytes_per_sec': bytes_per_sec, + "python_version": sys.version, + "python": python, + "events": total_events, + "sample": sample_display, + "iterations": iterations, + "msg_bytes": results["msg_bytes"], + "msgs_per_sec": msgs_per_sec, + "bytes_per_sec": bytes_per_sec, } json.dump(obj, f) - print(f'Done: {msgs_per_sec} msgs/sec, {bytes_per_sec} bytes/sec') + print(f"Done: {msgs_per_sec} msgs/sec, {bytes_per_sec} bytes/sec") def main_index(args: argparse.Namespace) -> None: @@ -224,166 +232,162 @@ def main_index(args: argparse.Namespace) -> None: output = args.output templates = jinja2.Environment( - loader=jinja2.FileSystemLoader('templates'), + loader=jinja2.FileSystemLoader("templates"), keep_trailing_newline=True, - autoescape=True + autoescape=True, ) - template_index = templates.get_template('index.html') - template_flamegraph = templates.get_template('flamegraph.html') + template_index = templates.get_template("index.html") + template_flamegraph = templates.get_template("flamegraph.html") report_data: Dict[str, Any] = { - 'generated': util.utcnow(), - 'results': { - 'cpy': {}, - 'pypy': {}, - } + "generated": util.utcnow(), + "results": { + "cpy": {}, + "pypy": {}, + }, } # All serializers and configurations - serializers = ['json', 'ujson', 'msgpack', 'cbor', 'cbor2', 'ubjson', 'flatbuffers'] - payload_modes = ['normal', 'transparent'] - payload_sizes = ['empty', 'small', 'medium', 'large', 'xl', 'xxl'] + serializers = ["json", "ujson", "msgpack", "cbor", "cbor2", "ubjson", "flatbuffers"] + payload_modes = ["normal", "transparent"] + payload_sizes = ["empty", "small", "medium", "large", "xl", "xxl"] - for _python in report_data['results']: + for _python in report_data["results"]: for _ser in serializers: - if _ser not in report_data['results'][_python]: - report_data['results'][_python][_ser] = {} + if _ser not in report_data["results"][_python]: + report_data["results"][_python][_ser] = {} for _payload_mode in payload_modes: - if _payload_mode not in report_data['results'][_python][_ser]: - report_data['results'][_python][_ser][_payload_mode] = {} + if _payload_mode not in report_data["results"][_python][_ser]: + report_data["results"][_python][_ser][_payload_mode] = {} for _payload_size in payload_sizes: fn = os.path.join( output, - f'results_{_python}_{_ser}_{_payload_mode}_{_payload_size}.json' + f"results_{_python}_{_ser}_{_payload_mode}_{_payload_size}.json", ) if os.path.isfile(fn): with open(fn) as f: data = json.load(f) - report_data['results'][_python][_ser][_payload_mode][_payload_size] = data - print(f'File added : {fn}') + report_data["results"][_python][_ser][_payload_mode][ + _payload_size + ] = data + print(f"File added : {fn}") # Generate flamegraph HTML fn_svg = os.path.join( output, - f'vmprof_{_python}_{_ser}_{_payload_mode}_{_payload_size}.html' + f"vmprof_{_python}_{_ser}_{_payload_mode}_{_payload_size}.html", ) - with open(fn_svg, 'w') as f: - data['python'] = _python - data['serializer'] = _ser - data['payload_mode'] = _payload_mode - data['payload_size'] = _payload_size + with open(fn_svg, "w") as f: + data["python"] = _python + data["serializer"] = _ser + data["payload_mode"] = _payload_mode + data["payload_size"] = _payload_size s = template_flamegraph.render( naturalsize=humanize.naturalsize, intword=humanize.intword, intcomma=humanize.intcomma, sorted=sorted, - **data + **data, ) f.write(s) else: - print(f'File not found: {fn}') + print(f"File not found: {fn}") # Clean up empty configurations - if not report_data['results'][_python][_ser][_payload_mode]: - del report_data['results'][_python][_ser][_payload_mode] - if not report_data['results'][_python][_ser]: - del report_data['results'][_python][_ser] + if not report_data["results"][_python][_ser][_payload_mode]: + del report_data["results"][_python][_ser][_payload_mode] + if not report_data["results"][_python][_ser]: + del report_data["results"][_python][_ser] # Generate index HTML - with open(os.path.join(output, 'index.html'), 'w') as f: + with open(os.path.join(output, "index.html"), "w") as f: s = template_index.render( naturalsize=humanize.naturalsize, intword=humanize.intword, intcomma=humanize.intcomma, sorted=sorted, - **report_data + **report_data, ) f.write(s) - print(f'Report generated: {os.path.join(output, "index.html")}') + print(f"Report generated: {os.path.join(output, 'index.html')}") -if __name__ == '__main__': +if __name__ == "__main__": parser = argparse.ArgumentParser( - description='WAMP Message Serialization Benchmarks' + description="WAMP Message Serialization Benchmarks" ) subparsers = parser.add_subparsers( - dest='command', - title='commands', - help='Command to run (required)' + dest="command", title="commands", help="Command to run (required)" ) subparsers.required = True # Run benchmark subcommand - parser_run = subparsers.add_parser( - 'run', - help='Run serialization benchmark' - ) + parser_run = subparsers.add_parser("run", help="Run serialization benchmark") parser_run.add_argument( - '--iterations', - dest='iterations', + "--iterations", + dest="iterations", type=int, default=10, - help='Number of iterations in the benchmarking loop (default: 10)' + help="Number of iterations in the benchmarking loop (default: 10)", ) parser_run.add_argument( - '--serializer', - dest='serializer', - choices=['json', 'cbor', 'msgpack', 'ubjson', 'flatbuffers'], - default='cbor', - help='Serializer to use (implementation variants like ujson/json or cbor2/cbor ' - 'can be selected via AUTOBAHN_USE_UJSON or AUTOBAHN_USE_CBOR2 env vars)' + "--serializer", + dest="serializer", + choices=["json", "cbor", "msgpack", "ubjson", "flatbuffers"], + default="cbor", + help="Serializer to use (implementation variants like ujson/json or cbor2/cbor " + "can be selected via AUTOBAHN_USE_UJSON or AUTOBAHN_USE_CBOR2 env vars)", ) parser_run.add_argument( - '--payload_mode', - dest='payload_mode', - choices=['normal', 'transparent'], - default='normal', - help='WAMP payload mode: normal (args) or transparent (payload)' + "--payload_mode", + dest="payload_mode", + choices=["normal", "transparent"], + default="normal", + help="WAMP payload mode: normal (args) or transparent (payload)", ) parser_run.add_argument( - '--payload_size', - dest='payload_size', - choices=['empty', 'small', 'medium', 'large', 'xl', 'xxl'], - default='small', - help='Payload size category' + "--payload_size", + dest="payload_size", + choices=["empty", "small", "medium", "large", "xl", "xxl"], + default="small", + help="Payload size category", ) parser_run.add_argument( - '--profile', - dest='profile', + "--profile", + dest="profile", type=str, required=True, - help='vmprof profile output filename (.dat)' + help="vmprof profile output filename (.dat)", ) parser_run.add_argument( - '--results', - dest='results', + "--results", + dest="results", type=str, required=True, - help='Results output directory' + help="Results output directory", ) parser_run.set_defaults(func=main_run) # Index generation subcommand parser_index = subparsers.add_parser( - 'index', - help='Generate HTML report index from benchmark results' + "index", help="Generate HTML report index from benchmark results" ) parser_index.add_argument( - '--output', - dest='output', + "--output", + dest="output", type=str, required=True, - help='Output directory for HTML report' + help="Output directory for HTML report", ) parser_index.set_defaults(func=main_index) diff --git a/examples/benchmarks/serialization/sample.py b/examples/benchmarks/serialization/sample.py index ee987824d..83f6ee807 100644 --- a/examples/benchmarks/serialization/sample.py +++ b/examples/benchmarks/serialization/sample.py @@ -37,60 +37,56 @@ from typing import Any, Dict, List -__all__ = ['JSON_DATA1', 'JSON_DATA2', 'JSON_DATA3'] +__all__ = ["JSON_DATA1", "JSON_DATA2", "JSON_DATA3"] JSON_DATA1: Dict[str, Any] = { - 'data': [ + "data": [ { - 'attributes': { - 'body': 'The shortest article. Ever.', - 'created': '2015-05-22T14:56:29.000Z', - 'title': 'JSON:API paints my bikeshed!', - 'updated': '2015-05-22T14:56:28.000Z' + "attributes": { + "body": "The shortest article. Ever.", + "created": "2015-05-22T14:56:29.000Z", + "title": "JSON:API paints my bikeshed!", + "updated": "2015-05-22T14:56:28.000Z", }, - 'id': '1', - 'relationships': { - 'author': { - 'data': {'id': '42', 'type': 'people'} - } - }, - 'type': 'articles' + "id": "1", + "relationships": {"author": {"data": {"id": "42", "type": "people"}}}, + "type": "articles", } ], - 'included': [ + "included": [ { - 'attributes': {'age': 80, 'gender': 'male', 'name': 'John'}, - 'id': '42', - 'type': 'people' + "attributes": {"age": 80, "gender": "male", "name": "John"}, + "id": "42", + "type": "people", } ], - 'widget': { - 'debug': 'on', - 'image': { - 'alignment': 'center', - 'hOffset': 250, - 'name': 'sun1', - 'src': 'Images/Sun.png', - 'vOffset': 250 + "widget": { + "debug": "on", + "image": { + "alignment": "center", + "hOffset": 250, + "name": "sun1", + "src": "Images/Sun.png", + "vOffset": 250, }, - 'text': { - 'alignment': 'center', - 'data': 'Click Here', - 'hOffset': 250, - 'name': 'text1', - 'onMouseUp': 'sun1.opacity = (sun1.opacity / 100) * 90;', - 'size': 36, - 'style': 'bold', - 'vOffset': 100 + "text": { + "alignment": "center", + "data": "Click Here", + "hOffset": 250, + "name": "text1", + "onMouseUp": "sun1.opacity = (sun1.opacity / 100) * 90;", + "size": 36, + "style": "bold", + "vOffset": 100, }, - 'window': { - 'height': 500, - 'name': 'main_window', - 'title': 'Sample Konfabulator Widget', - 'width': 500 - } - } + "window": { + "height": 500, + "name": "main_window", + "title": "Sample Konfabulator Widget", + "width": 500, + }, + }, } @@ -106,7 +102,7 @@ "weight": 67.5, "hasChildren": True, "hasGreyHair": False, - "children": ["Suri", "Isabella Jane", "Connor"] + "children": ["Suri", "Isabella Jane", "Connor"], }, { "name": "Robert Downey Jr.", @@ -118,8 +114,8 @@ "weight": 77.1, "hasChildren": True, "hasGreyHair": False, - "children": ["Indio Falconer", "Avri Roel", "Exton Elias"] - } + "children": ["Indio Falconer", "Avri Roel", "Exton Elias"], + }, ] } @@ -136,7 +132,7 @@ {"id": "1001", "type": "Regular"}, {"id": "1002", "type": "Chocolate"}, {"id": "1003", "type": "Blueberry"}, - {"id": "1004", "type": "Devil's Food"} + {"id": "1004", "type": "Devil's Food"}, ] }, "topping": [ @@ -146,24 +142,22 @@ {"id": "5007", "type": "Powdered Sugar"}, {"id": "5006", "type": "Chocolate with Sprinkles"}, {"id": "5003", "type": "Chocolate"}, - {"id": "5004", "type": "Maple"} - ] + {"id": "5004", "type": "Maple"}, + ], }, { "id": "0002", "type": "donut", "name": "Raised", "ppu": 0.55, - "batters": { - "batter": [{"id": "1001", "type": "Regular"}] - }, + "batters": {"batter": [{"id": "1001", "type": "Regular"}]}, "topping": [ {"id": "5001", "type": "None"}, {"id": "5002", "type": "Glazed"}, {"id": "5005", "type": "Sugar"}, {"id": "5003", "type": "Chocolate"}, - {"id": "5004", "type": "Maple"} - ] + {"id": "5004", "type": "Maple"}, + ], }, { "id": "0003", @@ -173,15 +167,15 @@ "batters": { "batter": [ {"id": "1001", "type": "Regular"}, - {"id": "1002", "type": "Chocolate"} + {"id": "1002", "type": "Chocolate"}, ] }, "topping": [ {"id": "5001", "type": "None"}, {"id": "5002", "type": "Glazed"}, {"id": "5003", "type": "Chocolate"}, - {"id": "5004", "type": "Maple"} - ] - } + {"id": "5004", "type": "Maple"}, + ], + }, ] } diff --git a/examples/run-all-examples.py b/examples/run-all-examples.py index aa4c89be9..3e1f3b80b 100755 --- a/examples/run-all-examples.py +++ b/examples/run-all-examples.py @@ -137,10 +137,10 @@ def categorize_example(example_path): """Categorize an example by backend and feature type.""" if example_path.startswith("./twisted/"): backend = "twisted" - path = example_path[len("./twisted/"):] + path = example_path[len("./twisted/") :] elif example_path.startswith("./asyncio/"): backend = "asyncio" - path = example_path[len("./asyncio/"):] + path = example_path[len("./asyncio/") :] else: backend = "unknown" path = example_path @@ -176,7 +176,9 @@ def print_summary(results): for result in results: status_symbol = "✓" if result["passed"] else "✗" status_color = Fore.GREEN if result["passed"] else Fore.RED - print(f"{status_color}{status_symbol}{Fore.RESET} {result['backend']:8s} {result['feature']:8s} {result['path']}") + print( + f"{status_color}{status_symbol}{Fore.RESET} {result['backend']:8s} {result['feature']:8s} {result['path']}" + ) print() # Aggregate by backend @@ -190,7 +192,9 @@ def print_summary(results): backend_failed = backend_total - backend_passed status_symbol = "✓" if backend_failed == 0 else "✗" status_color = Fore.GREEN if backend_failed == 0 else Fore.RED - print(f"{status_color}{status_symbol}{Fore.RESET} {backend:8s}: {backend_passed}/{backend_total} passed, {backend_failed} failed") + print( + f"{status_color}{status_symbol}{Fore.RESET} {backend:8s}: {backend_passed}/{backend_total} passed, {backend_failed} failed" + ) print() # Aggregate by feature @@ -204,7 +208,9 @@ def print_summary(results): feature_failed = feature_total - feature_passed status_symbol = "✓" if feature_failed == 0 else "✗" status_color = Fore.GREEN if feature_failed == 0 else Fore.RED - print(f"{status_color}{status_symbol}{Fore.RESET} {feature:8s}: {feature_passed}/{feature_total} passed, {feature_failed} failed") + print( + f"{status_color}{status_symbol}{Fore.RESET} {feature:8s}: {feature_passed}/{feature_total} passed, {feature_failed} failed" + ) print() # Overall summary @@ -311,12 +317,14 @@ def main(reactor): # Record result backend_type, feature_type = categorize_example(exdir) - results.append({ - "path": exdir, - "backend": backend_type, - "feature": feature_type, - "passed": example_passed - }) + results.append( + { + "path": exdir, + "backend": backend_type, + "feature": feature_type, + "passed": example_passed, + } + ) if not overall_success: break diff --git a/examples/serdes/gen_flatbuffers_testvectors.py b/examples/serdes/gen_flatbuffers_testvectors.py new file mode 100644 index 000000000..36b038429 --- /dev/null +++ b/examples/serdes/gen_flatbuffers_testvectors.py @@ -0,0 +1,440 @@ +#!/usr/bin/env python3 +""" +Generate FlatBuffers test vectors for WAMP messages. + +This script: +1. Loads test vector JSON files from wamp-proto/testsuite +2. For each sample, creates a WAMP message object +3. Serializes it to FlatBuffers +4. Adds the bytes_hex to the test vector +5. Saves the updated JSON back to wamp-proto + +Usage: + python gen_flatbuffers_testvectors.py + +Requirements: + - autobahn-python with FlatBuffers support installed + - wamp-proto repo in sibling directory: ../../../wamp-proto +""" + +import json +import sys +from binascii import hexlify +from pathlib import Path + +# Add autobahn to path +sys.path.insert(0, str(Path(__file__).parent.parent.parent)) + +# Initialize txaio before importing autobahn modules +import txaio + +txaio.use_asyncio() + +from autobahn.wamp import message as wamp_messages +from autobahn.wamp.gen.wamp.proto.PPTScheme import PPTScheme +from autobahn.wamp.gen.wamp.proto.PPTSerializer import PPTSerializer +from autobahn.wamp.serializer import FlatBuffersSerializer + +# Enum mappings for E2EE payloads (renamed from Payload -> PPTScheme) +PAYLOAD_ALGO_MAP = { + "none": PPTScheme.NONE, + "cryptobox": PPTScheme.CRYPTOBOX, + "mqtt": PPTScheme.MQTT, + "xbr": PPTScheme.XBR, + "opaque": PPTScheme.OPAQUE, +} + +# Enum mappings for serializers (renamed from Serializer -> PPTSerializer) +SERIALIZER_MAP = { + "transport": PPTSerializer.TRANSPORT, + "json": PPTSerializer.JSON, + "msgpack": PPTSerializer.MSGPACK, + "cbor": PPTSerializer.CBOR, + "ubjson": PPTSerializer.UBJSON, + "opaque": PPTSerializer.OPAQUE, + "flatbuffers": PPTSerializer.FLATBUFFERS, + "flexbuffers": PPTSerializer.FLEXBUFFERS, +} + + +# Message type mapping +MESSAGE_TYPE_MAP = { + "HELLO": (wamp_messages.Hello, 1), + "WELCOME": (wamp_messages.Welcome, 2), + "ABORT": (wamp_messages.Abort, 3), + "CHALLENGE": (wamp_messages.Challenge, 4), + "AUTHENTICATE": (wamp_messages.Authenticate, 5), + "GOODBYE": (wamp_messages.Goodbye, 6), + "ERROR": (wamp_messages.Error, 8), + "PUBLISH": (wamp_messages.Publish, 16), + "PUBLISHED": (wamp_messages.Published, 17), + "SUBSCRIBE": (wamp_messages.Subscribe, 32), + "SUBSCRIBED": (wamp_messages.Subscribed, 33), + "UNSUBSCRIBE": (wamp_messages.Unsubscribe, 34), + "UNSUBSCRIBED": (wamp_messages.Unsubscribed, 35), + "EVENT": (wamp_messages.Event, 36), + "CALL": (wamp_messages.Call, 48), + "CANCEL": (wamp_messages.Cancel, 49), + "RESULT": (wamp_messages.Result, 50), + "REGISTER": (wamp_messages.Register, 64), + "REGISTERED": (wamp_messages.Registered, 65), + "UNREGISTER": (wamp_messages.Unregister, 66), + "UNREGISTERED": (wamp_messages.Unregistered, 67), + "INVOCATION": (wamp_messages.Invocation, 68), + "INTERRUPT": (wamp_messages.Interrupt, 69), + "YIELD": (wamp_messages.Yield, 70), +} + + +def create_message_from_attributes(message_type_name, attributes): + """ + Create a WAMP message object from expected_attributes. + + :param message_type_name: WAMP message type name (e.g., "PUBLISHED") + :param attributes: Expected attributes dict from test vector + :return: WAMP message object + """ + message_class, _ = MESSAGE_TYPE_MAP[message_type_name] + + # Session establishment messages + if message_type_name == "HELLO": + return message_class(realm=attributes["realm"], roles=attributes["roles"]) + elif message_type_name == "WELCOME": + return message_class( + session=attributes["session_id"], roles=attributes["roles"] + ) + elif message_type_name == "ABORT": + return message_class( + reason=attributes["reason"], message=attributes.get("message") + ) + elif message_type_name == "CHALLENGE": + return message_class( + method=attributes["method"], extra=attributes.get("extra", {}) + ) + elif message_type_name == "AUTHENTICATE": + return message_class( + signature=attributes["signature"], extra=attributes.get("extra", {}) + ) + elif message_type_name == "GOODBYE": + return message_class( + reason=attributes.get("reason", "wamp.close.normal"), + message=attributes.get("message"), + ) + + # Error message + elif message_type_name == "ERROR": + return message_class( + request_type=attributes["request_type"], + request=attributes["request_id"], + error=attributes["error"], + args=attributes.get("args"), + kwargs=attributes.get("kwargs"), + payload=bytes.fromhex(attributes["payload"]) + if attributes.get("payload") + else None, + ) + + # PubSub messages + elif message_type_name == "PUBLISH": + # Keep enc_algo and enc_serializer as strings (build() converts to enums) + return message_class( + request=attributes["request_id"], + topic=attributes["topic"], + args=attributes.get("args"), + kwargs=attributes.get("kwargs"), + payload=bytes.fromhex(attributes["payload"]) + if attributes.get("payload") + else None, + acknowledge=attributes.get("options", {}).get("acknowledge"), + exclude_me=attributes.get("options", {}).get("exclude_me"), + retain=attributes.get("options", {}).get("retain"), + forward_for=attributes.get("options", {}).get("forward_for"), + enc_algo=attributes.get("options", {}).get("enc_algo"), + enc_serializer=attributes.get("options", {}).get("enc_serializer"), + ) + elif message_type_name == "PUBLISHED": + return message_class( + request=attributes["request_id"], publication=attributes["publication_id"] + ) + elif message_type_name == "SUBSCRIBE": + return message_class( + request=attributes["request_id"], + topic=attributes["topic"], + match=attributes.get("options", {}).get("match"), + get_retained=attributes.get("options", {}).get("get_retained"), + forward_for=attributes.get("options", {}).get("forward_for"), + ) + elif message_type_name == "SUBSCRIBED": + return message_class( + request=attributes["request_id"], subscription=attributes["subscription_id"] + ) + elif message_type_name == "UNSUBSCRIBE": + return message_class( + request=attributes["request_id"], + subscription=attributes["subscription_id"], + forward_for=attributes.get("options", {}).get("forward_for"), + ) + elif message_type_name == "UNSUBSCRIBED": + return message_class(request=attributes["request_id"]) + elif message_type_name == "EVENT": + # Keep enc_algo and enc_serializer as strings (build() converts to enums) + return message_class( + subscription=attributes["subscription"], + publication=attributes["publication"], + args=attributes.get("args"), + kwargs=attributes.get("kwargs"), + payload=bytes.fromhex(attributes["payload"]) + if attributes.get("payload") + else None, + publisher=attributes.get("details", {}).get("publisher"), + publisher_authid=attributes.get("details", {}).get("publisher_authid"), + publisher_authrole=attributes.get("details", {}).get("publisher_authrole"), + topic=attributes.get("details", {}).get("topic"), + retained=attributes.get("details", {}).get("retained"), + forward_for=attributes.get("details", {}).get("forward_for"), + enc_algo=attributes.get("details", {}).get("enc_algo"), + enc_serializer=attributes.get("details", {}).get("enc_serializer"), + ) + + # RPC messages + elif message_type_name == "CALL": + return message_class( + request=attributes["request_id"], + procedure=attributes["procedure"], + args=attributes.get("args"), + kwargs=attributes.get("kwargs"), + payload=bytes.fromhex(attributes["payload"]) + if attributes.get("payload") + else None, + timeout=attributes.get("options", {}).get("timeout"), + receive_progress=attributes.get("options", {}).get("receive_progress"), + forward_for=attributes.get("options", {}).get("forward_for"), + enc_algo=attributes.get("options", {}).get("enc_algo"), + enc_serializer=attributes.get("options", {}).get("enc_serializer"), + ) + elif message_type_name == "CANCEL": + return message_class( + request=attributes["request_id"], + mode=attributes.get("options", {}).get("mode"), + forward_for=attributes.get("options", {}).get("forward_for"), + ) + elif message_type_name == "RESULT": + return message_class( + request=attributes["request_id"], + args=attributes.get("args"), + kwargs=attributes.get("kwargs"), + payload=bytes.fromhex(attributes["payload"]) + if attributes.get("payload") + else None, + progress=attributes.get("details", {}).get("progress"), + enc_algo=attributes.get("details", {}).get("enc_algo"), + enc_serializer=attributes.get("details", {}).get("enc_serializer"), + callee=attributes.get("details", {}).get("callee"), + callee_authid=attributes.get("details", {}).get("callee_authid"), + callee_authrole=attributes.get("details", {}).get("callee_authrole"), + forward_for=attributes.get("details", {}).get("forward_for"), + ) + elif message_type_name == "REGISTER": + return message_class( + request=attributes["request_id"], + procedure=attributes["procedure"], + match=attributes.get("options", {}).get("match"), + invoke=attributes.get("options", {}).get("invoke"), + concurrency=attributes.get("options", {}).get("concurrency"), + force_reregister=attributes.get("options", {}).get("force_reregister"), + forward_for=attributes.get("options", {}).get("forward_for"), + ) + elif message_type_name == "REGISTERED": + return message_class( + request=attributes["request_id"], registration=attributes["registration_id"] + ) + elif message_type_name == "UNREGISTER": + return message_class( + request=attributes["request_id"], + registration=attributes["registration_id"], + forward_for=attributes.get("options", {}).get("forward_for"), + ) + elif message_type_name == "UNREGISTERED": + return message_class(request=attributes["request_id"]) + elif message_type_name == "INVOCATION": + return message_class( + request=attributes["request_id"], + registration=attributes["registration_id"], + args=attributes.get("args"), + kwargs=attributes.get("kwargs"), + payload=bytes.fromhex(attributes["payload"]) + if attributes.get("payload") + else None, + timeout=attributes.get("details", {}).get("timeout"), + receive_progress=attributes.get("details", {}).get("receive_progress"), + caller=attributes.get("details", {}).get("caller"), + caller_authid=attributes.get("details", {}).get("caller_authid"), + caller_authrole=attributes.get("details", {}).get("caller_authrole"), + procedure=attributes.get("details", {}).get("procedure"), + enc_algo=attributes.get("details", {}).get("enc_algo"), + enc_serializer=attributes.get("details", {}).get("enc_serializer"), + forward_for=attributes.get("details", {}).get("forward_for"), + ) + elif message_type_name == "INTERRUPT": + return message_class( + request=attributes["request_id"], + mode=attributes.get("options", {}).get("mode"), + reason=attributes.get("options", {}).get("reason"), + forward_for=attributes.get("options", {}).get("forward_for"), + ) + elif message_type_name == "YIELD": + return message_class( + request=attributes["request_id"], + args=attributes.get("args"), + kwargs=attributes.get("kwargs"), + payload=bytes.fromhex(attributes["payload"]) + if attributes.get("payload") + else None, + progress=attributes.get("options", {}).get("progress"), + enc_algo=attributes.get("options", {}).get("enc_algo"), + enc_serializer=attributes.get("options", {}).get("enc_serializer"), + forward_for=attributes.get("options", {}).get("forward_for"), + ) + + else: + raise NotImplementedError( + f"Message creation not implemented for {message_type_name}" + ) + + +def generate_flatbuffers_bytes(message_obj): + """ + Serialize a WAMP message to FlatBuffers and return hex string. + + :param message_obj: WAMP message object + :return: Hex string of serialized bytes + """ + import flatbuffers + + # Create a FlatBuffers builder + builder = flatbuffers.Builder(1024) + + # Use the message's build() method to build the FlatBuffers message + msg_offset = message_obj.build(builder) + + # Finish the buffer + builder.Finish(msg_offset) + + # Get the serialized bytes + serialized_bytes = bytes(builder.Output()) + + return hexlify(serialized_bytes).decode("ascii") + + +def process_test_vector_file(json_path): + """ + Process a single test vector JSON file and add FlatBuffers entries. + + :param json_path: Path to test vector JSON file + """ + print(f"\nProcessing: {json_path.name}") + + # Load test vector + with open(json_path, "r") as f: + test_vector = json.load(f) + + message_type_name = test_vector["wamp_message_type"] + + if message_type_name not in MESSAGE_TYPE_MAP: + print(f" ⚠ Skipping {message_type_name} - not in MESSAGE_TYPE_MAP") + return False + + modified = False + + # Process each sample + for i, sample in enumerate(test_vector.get("samples", [])): + if "serializers" not in sample: + continue + + # Check if flatbuffers already exists - we'll update it + updating = "flatbuffers" in sample["serializers"] + if updating: + print(f" ↻ Sample {i + 1}: updating existing flatbuffers entry") + + # Check if we have expected_attributes + if "expected_attributes" not in sample: + print(f" ⚠ Sample {i + 1}: no expected_attributes, skipping") + continue + + try: + # Create message object + message_obj = create_message_from_attributes( + message_type_name, sample["expected_attributes"] + ) + + # Generate FlatBuffers bytes + bytes_hex = generate_flatbuffers_bytes(message_obj) + + # Add to test vector + sample["serializers"]["flatbuffers"] = [ + { + "bytes_hex": bytes_hex, + "note": "Generated by autobahn-python FlatBuffers serializer", + } + ] + + modified = True + action = "updated" if updating else "added" + print( + f" ✓ Sample {i + 1}: {action} flatbuffers ({len(bytes_hex) // 2} bytes)" + ) + + except NotImplementedError as e: + print(f" ⚠ Sample {i + 1}: {e}") + continue + except Exception as e: + print(f" ✗ Sample {i + 1}: Error - {e}") + continue + + # Save if modified + if modified: + with open(json_path, "w") as f: + json.dump(test_vector, f, indent=2) + f.write("\n") # Add trailing newline + print(" 💾 Saved updated test vector") + return True + + return False + + +def main(): + """Main entry point""" + print("=" * 70) + print("FlatBuffers Test Vector Generator") + print("=" * 70) + + # Find wamp-proto testsuite directory + autobahn_root = Path(__file__).parent.parent.parent + wamp_proto_root = autobahn_root / ".proto" + testsuite_dir = wamp_proto_root / "testsuite" / "singlemessage" / "basic" + + if not testsuite_dir.exists(): + print(f"\n❌ Error: testsuite directory not found: {testsuite_dir}") + print(" Expected: .proto/testsuite/singlemessage/basic/") + return 1 + + print(f"\nTestsuite directory: {testsuite_dir}") + + # Process all JSON files + json_files = sorted(testsuite_dir.glob("*.json")) + print(f"Found {len(json_files)} test vector files") + + modified_count = 0 + for json_path in json_files: + if process_test_vector_file(json_path): + modified_count += 1 + + print("\n" + "=" * 70) + print(f"✅ Complete: {modified_count}/{len(json_files)} files modified") + print("=" * 70) + + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/examples/serdes/generate_abort_bytes.py b/examples/serdes/generate_abort_bytes.py index 7a8ed297c..57d0b24d6 100644 --- a/examples/serdes/generate_abort_bytes.py +++ b/examples/serdes/generate_abort_bytes.py @@ -2,8 +2,10 @@ """ Generate serialized bytes for ABORT message test vectors. """ + import binascii import txaio + txaio.use_asyncio() from autobahn.wamp.message import Abort diff --git a/examples/serdes/generate_authenticate_bytes.py b/examples/serdes/generate_authenticate_bytes.py index a125d116d..27663cd02 100644 --- a/examples/serdes/generate_authenticate_bytes.py +++ b/examples/serdes/generate_authenticate_bytes.py @@ -2,8 +2,10 @@ """ Generate serialized bytes for AUTHENTICATE message test vectors. """ + import binascii import txaio + txaio.use_asyncio() from autobahn.wamp.message import Authenticate diff --git a/examples/serdes/generate_cancel_bytes.py b/examples/serdes/generate_cancel_bytes.py index b82cf2bd3..e769bc3f9 100644 --- a/examples/serdes/generate_cancel_bytes.py +++ b/examples/serdes/generate_cancel_bytes.py @@ -2,8 +2,10 @@ """ Generate serialized bytes for CANCEL message test vectors. """ + import binascii import txaio + txaio.use_asyncio() from autobahn.wamp.message import Cancel diff --git a/examples/serdes/generate_challenge_bytes.py b/examples/serdes/generate_challenge_bytes.py index 95276c916..404bb84e6 100644 --- a/examples/serdes/generate_challenge_bytes.py +++ b/examples/serdes/generate_challenge_bytes.py @@ -2,8 +2,10 @@ """ Generate serialized bytes for CHALLENGE message test vectors. """ + import binascii import txaio + txaio.use_asyncio() from autobahn.wamp.message import Challenge diff --git a/examples/serdes/generate_eventreceived_bytes.py b/examples/serdes/generate_eventreceived_bytes.py index 61e2b6132..887c1e8ac 100644 --- a/examples/serdes/generate_eventreceived_bytes.py +++ b/examples/serdes/generate_eventreceived_bytes.py @@ -2,8 +2,10 @@ """ Generate serialized bytes for EVENT_RECEIVED message test vectors. """ + import binascii import txaio + txaio.use_asyncio() from autobahn.wamp.message import EventReceived diff --git a/examples/serdes/generate_goodbye_bytes.py b/examples/serdes/generate_goodbye_bytes.py index 920b8a5cc..d0a591835 100644 --- a/examples/serdes/generate_goodbye_bytes.py +++ b/examples/serdes/generate_goodbye_bytes.py @@ -2,8 +2,10 @@ """ Generate serialized bytes for GOODBYE message test vectors. """ + import binascii import txaio + txaio.use_asyncio() from autobahn.wamp.message import Goodbye diff --git a/examples/serdes/generate_hello_bytes.py b/examples/serdes/generate_hello_bytes.py index 98f414fc1..230fbd813 100644 --- a/examples/serdes/generate_hello_bytes.py +++ b/examples/serdes/generate_hello_bytes.py @@ -2,8 +2,10 @@ """ Generate serialized bytes for HELLO message test vectors. """ + import binascii import txaio + txaio.use_asyncio() from autobahn.wamp.message import Hello @@ -15,8 +17,8 @@ realm="com.example.realm", roles={ "subscriber": RoleSubscriberFeatures(), - "publisher": RolePublisherFeatures() - } + "publisher": RolePublisherFeatures(), + }, ) print(f"Message: {msg}") diff --git a/examples/serdes/generate_interrupt_bytes.py b/examples/serdes/generate_interrupt_bytes.py index 5d181f607..dfb094fce 100644 --- a/examples/serdes/generate_interrupt_bytes.py +++ b/examples/serdes/generate_interrupt_bytes.py @@ -2,8 +2,10 @@ """ Generate serialized bytes for INTERRUPT message test vectors. """ + import binascii import txaio + txaio.use_asyncio() from autobahn.wamp.message import Interrupt diff --git a/examples/serdes/generate_welcome_bytes.py b/examples/serdes/generate_welcome_bytes.py index 8b53bcd59..d1527f677 100644 --- a/examples/serdes/generate_welcome_bytes.py +++ b/examples/serdes/generate_welcome_bytes.py @@ -2,8 +2,10 @@ """ Generate serialized bytes for WELCOME message test vectors. """ + import binascii import txaio + txaio.use_asyncio() from autobahn.wamp.message import Welcome @@ -13,10 +15,7 @@ # Create a basic WELCOME message msg = Welcome( session=9129137332, - roles={ - "broker": RoleBrokerFeatures(), - "dealer": RoleDealerFeatures() - } + roles={"broker": RoleBrokerFeatures(), "dealer": RoleDealerFeatures()}, ) print(f"Message: {msg}") diff --git a/examples/serdes/tests/conftest.py b/examples/serdes/tests/conftest.py index 2e780f7f3..34832742e 100644 --- a/examples/serdes/tests/conftest.py +++ b/examples/serdes/tests/conftest.py @@ -1,6 +1,7 @@ """ Pytest configuration and fixtures for WAMP serdes tests """ + import pytest import txaio @@ -32,8 +33,10 @@ def all_serializer_ids(): @pytest.fixture def create_serializer(): """Factory fixture to create serializers by ID""" + def _create(serializer_id: str): return create_transport_serializer(serializer_id) + return _create @@ -53,6 +56,6 @@ def pytest_generate_tests(metafunc): serializer_ids = get_serializer_ids() pairs = [] for i, ser1 in enumerate(serializer_ids): - for ser2 in serializer_ids[i+1:]: + for ser2 in serializer_ids[i + 1 :]: pairs.append((ser1, ser2)) metafunc.parametrize("serializer_pair", pairs) diff --git a/examples/serdes/tests/test_abort.py b/examples/serdes/tests/test_abort.py index 0509c66fe..cab25cd02 100644 --- a/examples/serdes/tests/test_abort.py +++ b/examples/serdes/tests/test_abort.py @@ -8,6 +8,7 @@ Uses test vectors from: wamp-proto/testsuite/singlemessage/basic/abort.json """ + import pytest from autobahn.wamp.message import Abort from autobahn.wamp.serializer import create_transport_serializer @@ -23,6 +24,7 @@ # Test Vector Loading # ============================================================================= + @pytest.fixture(scope="module") def abort_test_vector(): """Load ABORT test vector from wamp-proto""" @@ -39,6 +41,7 @@ def abort_samples(abort_test_vector): # SerDes Tests # ============================================================================= + def test_abort_deserialize_from_bytes(serializer_id, abort_samples, create_serializer): """ Test ABORT deserialization from canonical bytes. @@ -64,7 +67,7 @@ def test_abort_deserialize_from_bytes(serializer_id, abort_samples, create_seria if "bytes_hex" in variant: test_bytes = bytes_from_hex(variant["bytes_hex"]) elif "bytes" in variant: - test_bytes = variant["bytes"].encode('utf-8') + test_bytes = variant["bytes"].encode("utf-8") else: continue @@ -130,7 +133,7 @@ def test_abort_roundtrip(serializer_id, abort_samples, create_serializer): if "bytes_hex" in variant: original_bytes = bytes_from_hex(variant["bytes_hex"]) elif "bytes" in variant: - original_bytes = variant["bytes"].encode('utf-8') + original_bytes = variant["bytes"].encode("utf-8") else: continue diff --git a/examples/serdes/tests/test_authenticate.py b/examples/serdes/tests/test_authenticate.py index 255ce39eb..097b8dfc5 100644 --- a/examples/serdes/tests/test_authenticate.py +++ b/examples/serdes/tests/test_authenticate.py @@ -8,6 +8,7 @@ Uses test vectors from: wamp-proto/testsuite/singlemessage/basic/authenticate.json """ + import pytest from autobahn.wamp.message import Authenticate from autobahn.wamp.serializer import create_transport_serializer @@ -23,6 +24,7 @@ # Test Vector Loading # ============================================================================= + @pytest.fixture(scope="module") def authenticate_test_vector(): """Load AUTHENTICATE test vector from wamp-proto""" @@ -39,7 +41,10 @@ def authenticate_samples(authenticate_test_vector): # SerDes Tests # ============================================================================= -def test_authenticate_deserialize_from_bytes(serializer_id, authenticate_samples, create_serializer): + +def test_authenticate_deserialize_from_bytes( + serializer_id, authenticate_samples, create_serializer +): """ Test AUTHENTICATE deserialization from canonical bytes. @@ -64,7 +69,7 @@ def test_authenticate_deserialize_from_bytes(serializer_id, authenticate_samples if "bytes_hex" in variant: test_bytes = bytes_from_hex(variant["bytes_hex"]) elif "bytes" in variant: - test_bytes = variant["bytes"].encode('utf-8') + test_bytes = variant["bytes"].encode("utf-8") else: continue @@ -76,7 +81,9 @@ def test_authenticate_deserialize_from_bytes(serializer_id, authenticate_samples assert msg.signature == sample["expected_attributes"]["signature"] -def test_authenticate_serialize_to_bytes(serializer_id, authenticate_samples, create_serializer): +def test_authenticate_serialize_to_bytes( + serializer_id, authenticate_samples, create_serializer +): """ Test AUTHENTICATE serialization to bytes. @@ -130,7 +137,7 @@ def test_authenticate_roundtrip(serializer_id, authenticate_samples, create_seri if "bytes_hex" in variant: original_bytes = bytes_from_hex(variant["bytes_hex"]) elif "bytes" in variant: - original_bytes = variant["bytes"].encode('utf-8') + original_bytes = variant["bytes"].encode("utf-8") else: continue diff --git a/examples/serdes/tests/test_call.py b/examples/serdes/tests/test_call.py index 2b7f9cba5..d33f1ec48 100644 --- a/examples/serdes/tests/test_call.py +++ b/examples/serdes/tests/test_call.py @@ -8,6 +8,7 @@ Uses test vectors from: wamp-proto/testsuite/singlemessage/basic/call.json """ + import pytest from autobahn.wamp.message import Call from autobahn.wamp.serializer import create_transport_serializer @@ -23,6 +24,7 @@ # Test Vector Loading # ============================================================================= + @pytest.fixture(scope="module") def call_test_vector(): """Load CALL test vector from wamp-proto""" @@ -39,6 +41,7 @@ def call_samples(call_test_vector): # SerDes Tests # ============================================================================= + def test_call_deserialize_from_bytes(serializer_id, call_samples, create_serializer): """ Test CALL deserialization from canonical bytes. @@ -64,7 +67,7 @@ def test_call_deserialize_from_bytes(serializer_id, call_samples, create_seriali if "bytes_hex" in variant: test_bytes = bytes_from_hex(variant["bytes_hex"]) elif "bytes" in variant: - test_bytes = variant["bytes"].encode('utf-8') + test_bytes = variant["bytes"].encode("utf-8") else: continue @@ -113,7 +116,7 @@ def test_call_serialize_to_bytes(serializer_id, call_samples, create_serializer) request=attrs["request_id"], procedure=attrs["procedure"], args=attrs.get("args"), - kwargs=attrs.get("kwargs") + kwargs=attrs.get("kwargs"), ) # Serialize @@ -150,7 +153,7 @@ def test_call_roundtrip(serializer_id, call_samples, create_serializer): if "bytes_hex" in variant: original_bytes = bytes_from_hex(variant["bytes_hex"]) elif "bytes" in variant: - original_bytes = variant["bytes"].encode('utf-8') + original_bytes = variant["bytes"].encode("utf-8") else: continue diff --git a/examples/serdes/tests/test_cancel.py b/examples/serdes/tests/test_cancel.py index ea62d862e..39a8dfe29 100644 --- a/examples/serdes/tests/test_cancel.py +++ b/examples/serdes/tests/test_cancel.py @@ -8,6 +8,7 @@ Uses test vectors from: wamp-proto/testsuite/singlemessage/advanced/cancel.json """ + import pytest from autobahn.wamp.message import Cancel from autobahn.wamp.serializer import create_transport_serializer @@ -23,6 +24,7 @@ # Test Vector Loading # ============================================================================= + @pytest.fixture(scope="module") def cancel_test_vector(): """Load CANCEL test vector from wamp-proto""" @@ -39,7 +41,10 @@ def cancel_samples(cancel_test_vector): # SerDes Tests # ============================================================================= -def test_cancel_deserialize_from_bytes(serializer_id, cancel_samples, create_serializer): + +def test_cancel_deserialize_from_bytes( + serializer_id, cancel_samples, create_serializer +): """ Test CANCEL deserialization from canonical bytes. @@ -64,7 +69,7 @@ def test_cancel_deserialize_from_bytes(serializer_id, cancel_samples, create_ser if "bytes_hex" in variant: test_bytes = bytes_from_hex(variant["bytes_hex"]) elif "bytes" in variant: - test_bytes = variant["bytes"].encode('utf-8') + test_bytes = variant["bytes"].encode("utf-8") else: continue @@ -130,7 +135,7 @@ def test_cancel_roundtrip(serializer_id, cancel_samples, create_serializer): if "bytes_hex" in variant: original_bytes = bytes_from_hex(variant["bytes_hex"]) elif "bytes" in variant: - original_bytes = variant["bytes"].encode('utf-8') + original_bytes = variant["bytes"].encode("utf-8") else: continue diff --git a/examples/serdes/tests/test_challenge.py b/examples/serdes/tests/test_challenge.py index a52ab76ed..a642d6bb5 100644 --- a/examples/serdes/tests/test_challenge.py +++ b/examples/serdes/tests/test_challenge.py @@ -8,6 +8,7 @@ Uses test vectors from: wamp-proto/testsuite/singlemessage/basic/challenge.json """ + import pytest from autobahn.wamp.message import Challenge from autobahn.wamp.serializer import create_transport_serializer @@ -23,6 +24,7 @@ # Test Vector Loading # ============================================================================= + @pytest.fixture(scope="module") def challenge_test_vector(): """Load CHALLENGE test vector from wamp-proto""" @@ -39,7 +41,10 @@ def challenge_samples(challenge_test_vector): # SerDes Tests # ============================================================================= -def test_challenge_deserialize_from_bytes(serializer_id, challenge_samples, create_serializer): + +def test_challenge_deserialize_from_bytes( + serializer_id, challenge_samples, create_serializer +): """ Test CHALLENGE deserialization from canonical bytes. @@ -64,7 +69,7 @@ def test_challenge_deserialize_from_bytes(serializer_id, challenge_samples, crea if "bytes_hex" in variant: test_bytes = bytes_from_hex(variant["bytes_hex"]) elif "bytes" in variant: - test_bytes = variant["bytes"].encode('utf-8') + test_bytes = variant["bytes"].encode("utf-8") else: continue @@ -76,7 +81,9 @@ def test_challenge_deserialize_from_bytes(serializer_id, challenge_samples, crea assert msg.method == sample["expected_attributes"]["method"] -def test_challenge_serialize_to_bytes(serializer_id, challenge_samples, create_serializer): +def test_challenge_serialize_to_bytes( + serializer_id, challenge_samples, create_serializer +): """ Test CHALLENGE serialization to bytes. @@ -130,7 +137,7 @@ def test_challenge_roundtrip(serializer_id, challenge_samples, create_serializer if "bytes_hex" in variant: original_bytes = bytes_from_hex(variant["bytes_hex"]) elif "bytes" in variant: - original_bytes = variant["bytes"].encode('utf-8') + original_bytes = variant["bytes"].encode("utf-8") else: continue diff --git a/examples/serdes/tests/test_error.py b/examples/serdes/tests/test_error.py index 680941337..33a60905f 100644 --- a/examples/serdes/tests/test_error.py +++ b/examples/serdes/tests/test_error.py @@ -8,6 +8,7 @@ Uses test vectors from: wamp-proto/testsuite/singlemessage/basic/error.json """ + import pytest from autobahn.wamp.message import Error from autobahn.wamp.serializer import create_transport_serializer @@ -23,6 +24,7 @@ # Test Vector Loading # ============================================================================= + @pytest.fixture(scope="module") def error_test_vector(): """Load ERROR test vector from wamp-proto""" @@ -39,6 +41,7 @@ def error_samples(error_test_vector): # SerDes Tests # ============================================================================= + def test_error_deserialize_from_bytes(serializer_id, error_samples, create_serializer): """ Test ERROR deserialization from canonical bytes. @@ -64,7 +67,7 @@ def test_error_deserialize_from_bytes(serializer_id, error_samples, create_seria if "bytes_hex" in variant: test_bytes = bytes_from_hex(variant["bytes_hex"]) elif "bytes" in variant: - test_bytes = variant["bytes"].encode('utf-8') + test_bytes = variant["bytes"].encode("utf-8") else: continue @@ -99,7 +102,7 @@ def test_error_serialize_to_bytes(serializer_id, error_samples, create_serialize msg = Error( request_type=attrs["request_type"], request=attrs["request_id"], - error=attrs["error"] + error=attrs["error"], ) # Serialize @@ -136,7 +139,7 @@ def test_error_roundtrip(serializer_id, error_samples, create_serializer): if "bytes_hex" in variant: original_bytes = bytes_from_hex(variant["bytes_hex"]) elif "bytes" in variant: - original_bytes = variant["bytes"].encode('utf-8') + original_bytes = variant["bytes"].encode("utf-8") else: continue diff --git a/examples/serdes/tests/test_event.py b/examples/serdes/tests/test_event.py index 811527c37..1294f4aee 100644 --- a/examples/serdes/tests/test_event.py +++ b/examples/serdes/tests/test_event.py @@ -8,6 +8,7 @@ Uses test vectors from: wamp-proto/testsuite/singlemessage/basic/event.json """ + import pytest from autobahn.wamp.message import Event from autobahn.wamp.serializer import create_transport_serializer @@ -26,6 +27,7 @@ # Test Vector Loading # ============================================================================= + @pytest.fixture(scope="module") def event_test_vector(): """Load EVENT test vector from wamp-proto""" @@ -50,6 +52,7 @@ def event_samples(event_test_vector): # Dimension 2: Single-Serializer Roundtrip Correctness # ============================================================================= + def test_event_deserialize_from_bytes(serializer_id, event_samples, create_serializer): """ Test EVENT deserialization from canonical bytes. @@ -75,7 +78,7 @@ def test_event_deserialize_from_bytes(serializer_id, event_samples, create_seria if "bytes_hex" in variant: test_bytes = bytes_from_hex(variant["bytes_hex"]) elif "bytes" in variant: - test_bytes = variant["bytes"].encode('utf-8') + test_bytes = variant["bytes"].encode("utf-8") else: continue @@ -92,8 +95,10 @@ def test_event_deserialize_from_bytes(serializer_id, event_samples, create_seria error = validates_with_any_code(msg, validation_codes) if error: # Debug: print sample description - sample_desc = sample.get('description', 'unknown') - pytest.fail(f"Validation failed for {serializer_id} on sample '{sample_desc}': {error}") + sample_desc = sample.get("description", "unknown") + pytest.fail( + f"Validation failed for {serializer_id} on sample '{sample_desc}': {error}" + ) def test_event_serialize_to_bytes(serializer_id, event_samples, create_serializer): @@ -144,11 +149,6 @@ def test_event_roundtrip(serializer_id, event_samples, create_serializer): serializer = create_serializer(serializer_id) for sample in event_samples: - # Skip flatbuffers with transparent payload mode due to autobahn bug - # See: https://github.com/crossbario/autobahn-python/issues/1766 - # flatbuffers serializer incorrectly handles enc_algo (expects uint8, gets string) - if serializer_id == 'flatbuffers' and sample.get('expected_attributes', {}).get('payload') is not None: - pytest.skip("Flatbuffers with transparent payload not supported (issue #1766)") # Skip if no construction code construction_code = sample["construction"].get("autobahn-python") if not construction_code: @@ -177,15 +177,17 @@ def test_event_roundtrip(serializer_id, event_samples, create_serializer): # 5. Check equality (if message class implements __eq__) # Note: Skip equality check for flatbuffers due to known __eq__ issues - if hasattr(msg_original, '__eq__') and serializer_id != 'flatbuffers': - assert msg_original == msg_roundtrip, \ + if hasattr(msg_original, "__eq__") and serializer_id != "flatbuffers": + assert msg_original == msg_roundtrip, ( f"Roundtrip message not equal to original for {serializer_id}" + ) # ============================================================================= # Dimension 3: Cross-Serializer Preservation # ============================================================================= + def test_event_cross_serializer_preservation(serializer_pair, event_samples): """ Test that EVENT message attributes are preserved across different serializers. @@ -205,13 +207,6 @@ def test_event_cross_serializer_preservation(serializer_pair, event_samples): ser2 = create_transport_serializer(ser2_id) for sample in event_samples: - # Skip flatbuffers with transparent payload mode due to autobahn bug - # See: https://github.com/crossbario/autobahn-python/issues/1766 - # flatbuffers serializer incorrectly handles enc_algo (expects uint8, gets string) - has_payload = sample.get('expected_attributes', {}).get('payload') is not None - if has_payload and ('flatbuffers' in [ser1_id, ser2_id]): - pytest.skip("Flatbuffers with transparent payload not supported (issue #1766)") - # Skip if ser1 not in test vector if ser1_id not in sample["serializers"]: pytest.skip(f"Serializer {ser1_id} not in test vector") @@ -229,10 +224,10 @@ def test_event_cross_serializer_preservation(serializer_pair, event_samples): # Take the first canonical byte representation from ser1 variant1 = ser1_variants[0] - if 'bytes_hex' in variant1: - bytes1 = bytes_from_hex(variant1['bytes_hex']) - elif 'bytes' in variant1: - bytes1 = variant1['bytes'].encode('utf-8') + if "bytes_hex" in variant1: + bytes1 = bytes_from_hex(variant1["bytes_hex"]) + elif "bytes" in variant1: + bytes1 = variant1["bytes"].encode("utf-8") else: pytest.skip(f"No bytes representation in {ser1_id} variant") @@ -259,6 +254,7 @@ def test_event_cross_serializer_preservation(serializer_pair, event_samples): # Expected Attributes Validation # ============================================================================= + def test_event_expected_attributes(event_samples): """ Test that deserialized EVENT message has expected attributes. @@ -281,7 +277,7 @@ def test_event_expected_attributes(event_samples): # Deserialize from first JSON variant variant = json_variants[0] if "bytes" in variant: - test_bytes = variant["bytes"].encode('utf-8') + test_bytes = variant["bytes"].encode("utf-8") elif "bytes_hex" in variant: test_bytes = bytes_from_hex(variant["bytes_hex"]) else: @@ -300,7 +296,9 @@ def test_event_expected_attributes(event_samples): # kwargs can be None or {} depending on implementation if expected["kwargs"] is None: - assert msg.kwargs is None or msg.kwargs == {}, "kwargs should be None or empty" + assert msg.kwargs is None or msg.kwargs == {}, ( + "kwargs should be None or empty" + ) else: assert msg.kwargs == expected["kwargs"], "kwargs mismatch" @@ -309,6 +307,7 @@ def test_event_expected_attributes(event_samples): # Payload Mode Tests # ============================================================================= + def test_event_normal_mode(event_samples): """ Test EVENT in normal payload mode. @@ -317,15 +316,20 @@ def test_event_normal_mode(event_samples): Router deserializes and can inspect args/kwargs. """ # Find the normal mode sample (has args, not payload) - normal_samples = [s for s in event_samples if 'args' in s['expected_attributes'] and s['expected_attributes']['args'] is not None] + normal_samples = [ + s + for s in event_samples + if "args" in s["expected_attributes"] + and s["expected_attributes"]["args"] is not None + ] assert len(normal_samples) > 0, "Should have at least one normal mode sample" for sample in normal_samples: # Verify expected attributes show normal mode - expected = sample['expected_attributes'] - assert expected.get('args') is not None or expected.get('kwargs') is not None - assert expected.get('payload') is None or expected['payload'] is None + expected = sample["expected_attributes"] + assert expected.get("args") is not None or expected.get("kwargs") is not None + assert expected.get("payload") is None or expected["payload"] is None def test_event_transparent_mode(event_samples, create_serializer): @@ -336,24 +340,29 @@ def test_event_transparent_mode(event_samples, create_serializer): Router does NOT deserialize payload - enables E2E encryption. """ # Find the transparent mode sample (has payload, not args/kwargs) - transparent_samples = [s for s in event_samples if 'payload' in s['expected_attributes'] and s['expected_attributes']['payload'] is not None] + transparent_samples = [ + s + for s in event_samples + if "payload" in s["expected_attributes"] + and s["expected_attributes"]["payload"] is not None + ] if not transparent_samples: pytest.skip("No transparent payload mode samples in test vector") for sample in transparent_samples: # Verify expected attributes show transparent mode - expected = sample['expected_attributes'] - assert expected.get('payload') is not None - assert expected.get('args') is None - assert expected.get('kwargs') is None + expected = sample["expected_attributes"] + assert expected.get("payload") is not None + assert expected.get("args") is None + assert expected.get("kwargs") is None # Test roundtrip for each serializer - construction_code = sample['construction'].get('autobahn-python') + construction_code = sample["construction"].get("autobahn-python") if not construction_code: continue - validation_codes = sample['validation'].get('autobahn-python', []) + validation_codes = sample["validation"].get("autobahn-python", []) if not validation_codes: continue @@ -367,7 +376,7 @@ def test_event_transparent_mode(event_samples, create_serializer): assert msg_original.kwargs is None # Test with JSON serializer (most common) - serializer = create_serializer('json') + serializer = create_serializer("json") serialized, is_binary = serializer.serialize(msg_original) msgs = serializer.unserialize(serialized) assert len(msgs) == 1 @@ -379,8 +388,9 @@ def test_event_transparent_mode(event_samples, create_serializer): pytest.fail(f"Transparent mode validation failed: {error}") # Critical: payload bytes must be preserved exactly (byte-for-byte) - assert msg_roundtrip.payload == msg_original.payload, \ + assert msg_roundtrip.payload == msg_original.payload, ( "Transparent payload must be preserved byte-for-byte through serialization" + ) # ============================================================================= @@ -400,6 +410,7 @@ def test_event_transparent_mode(event_samples, create_serializer): # publisher|int - Advanced Profile # ----------------------------------------------------------------------------- + def test_event_details_publisher_valid(): """Test EVENT.Details.publisher with valid int values""" valid_values = [123, 456, 999999] @@ -428,6 +439,7 @@ def test_event_details_publisher_invalid_type(): # publisher_authid|str - Advanced Profile # ----------------------------------------------------------------------------- + def test_event_details_publisher_authid_valid(): """Test EVENT.Details.publisher_authid with valid str values""" valid_values = ["alice", "bob", "user123", ""] @@ -455,6 +467,7 @@ def test_event_details_publisher_authid_invalid_type(): # publisher_authrole|str - Advanced Profile # ----------------------------------------------------------------------------- + def test_event_details_publisher_authrole_valid(): """Test EVENT.Details.publisher_authrole with valid str values""" valid_values = ["user", "admin", "manager", ""] @@ -482,6 +495,7 @@ def test_event_details_publisher_authrole_invalid_type(): # topic|str - Advanced Profile (pattern-based subscriptions) # ----------------------------------------------------------------------------- + def test_event_details_topic_valid(): """Test EVENT.Details.topic with valid str values""" valid_values = [ @@ -513,6 +527,7 @@ def test_event_details_topic_invalid_type(): # retained|bool - Advanced Profile (event retention) # ----------------------------------------------------------------------------- + def test_event_details_retained_valid(): """Test EVENT.Details.retained with valid bool values""" for value in [True, False]: @@ -539,6 +554,7 @@ def test_event_details_retained_invalid_type(): # transaction_hash|str - Implementation-Only # ----------------------------------------------------------------------------- + def test_event_details_transaction_hash_valid(): """Test EVENT.Details.transaction_hash with valid str values""" valid_values = [ @@ -570,6 +586,7 @@ def test_event_details_transaction_hash_invalid_type(): # x_acknowledged_delivery|bool - Implementation-Only # ----------------------------------------------------------------------------- + def test_event_details_x_acknowledged_delivery_valid(): """Test EVENT.Details.x_acknowledged_delivery with valid bool values""" for value in [True, False]: @@ -595,6 +612,7 @@ def test_event_details_x_acknowledged_delivery_invalid_type(): # forward_for|list[dict] - Implementation-Only # ----------------------------------------------------------------------------- + def test_event_details_forward_for_valid(): """Test EVENT.Details.forward_for with valid list[dict] values""" valid_values = [ @@ -633,8 +651,14 @@ def test_event_details_forward_for_invalid_values(): """ invalid_lists = [ # Multi-item lists where validation fails - ([{"session": 123, "authid": "alice", "authrole": "user"}, 123], "second item is int"), - ([{"session": 123, "authid": "alice", "authrole": "user"}, {"bad": "dict"}], "second item missing required fields"), + ( + [{"session": 123, "authid": "alice", "authrole": "user"}, 123], + "second item is int", + ), + ( + [{"session": 123, "authid": "alice", "authrole": "user"}, {"bad": "dict"}], + "second item missing required fields", + ), ] for value, description in invalid_lists: wmsg = [36, 5512315355, 4429313566, {"forward_for": value}] @@ -648,6 +672,7 @@ def test_event_details_forward_for_invalid_values(): # These are only valid with transparent payload mode (variant 4) # ----------------------------------------------------------------------------- + def test_event_details_enc_algo_valid(): """Test EVENT.Details.enc_algo with valid values (transparent payload mode)""" # enc_algo is only parsed when message has transparent payload @@ -690,7 +715,13 @@ def test_event_details_enc_key_valid(): "", ] for value in valid_values: - wmsg = [36, 5512315355, 4429313566, {"enc_algo": "cryptobox", "enc_key": value}, payload] + wmsg = [ + 36, + 5512315355, + 4429313566, + {"enc_algo": "cryptobox", "enc_key": value}, + payload, + ] msg = Event.parse(wmsg) assert msg.enc_key == value @@ -707,7 +738,13 @@ def test_event_details_enc_key_invalid_type(): ([1, 2], "non-empty list instead of str"), ] for value, description in invalid_values: - wmsg = [36, 5512315355, 4429313566, {"enc_algo": "cryptobox", "enc_key": value}, payload] + wmsg = [ + 36, + 5512315355, + 4429313566, + {"enc_algo": "cryptobox", "enc_key": value}, + payload, + ] with pytest.raises((ProtocolError, AssertionError)) as exc_info: Event.parse(wmsg) @@ -725,7 +762,13 @@ def test_event_details_enc_serializer_valid(): "flatbuffers", ] for value in valid_values: - wmsg = [36, 5512315355, 4429313566, {"enc_algo": "cryptobox", "enc_serializer": value}, payload] + wmsg = [ + 36, + 5512315355, + 4429313566, + {"enc_algo": "cryptobox", "enc_serializer": value}, + payload, + ] msg = Event.parse(wmsg) assert msg.enc_serializer == value @@ -739,7 +782,13 @@ def test_event_details_enc_serializer_invalid_value(): "yaml", ] for value in invalid_values: - wmsg = [36, 5512315355, 4429313566, {"enc_algo": "cryptobox", "enc_serializer": value}, payload] + wmsg = [ + 36, + 5512315355, + 4429313566, + {"enc_algo": "cryptobox", "enc_serializer": value}, + payload, + ] with pytest.raises(ProtocolError) as exc_info: Event.parse(wmsg) assert "enc_serializer" in str(exc_info.value).lower() @@ -749,17 +798,24 @@ def test_event_details_enc_serializer_invalid_value(): # Dimension 5: Details Validation (Language-Agnostic JSON Test Vectors) # ============================================================================= + @pytest.fixture(scope="module") def event_validation_samples(event_test_vector): """Extract validation samples from EVENT test vector""" - return [s for s in event_test_vector["samples"] if s.get("test_category") == "details_validation"] + return [ + s + for s in event_test_vector["samples"] + if s.get("test_category") == "details_validation" + ] def test_event_details_validation_sample_count(event_validation_samples): """Verify we have the expected number of validation samples""" # We should have validation samples for all EVENT.Details attributes assert len(event_validation_samples) > 0, "No validation samples found" - print(f"\nLoaded {len(event_validation_samples)} EVENT.Details validation samples from JSON") + print( + f"\nLoaded {len(event_validation_samples)} EVENT.Details validation samples from JSON" + ) @pytest.mark.parametrize("sample_index", range(21)) # We have 21 validation samples @@ -797,12 +853,15 @@ def test_event_details_validation_from_json(event_validation_samples, sample_ind # Verify the error message contains the expected attribute name error_msg = str(exc_info.value).lower() - assert expected_error["contains"].lower() in error_msg, \ + assert expected_error["contains"].lower() in error_msg, ( f"{description}: Expected error message to contain '{expected_error['contains']}', got: {exc_info.value}" + ) else: # This sample should parse successfully msg = Event.parse(wmsg) - assert isinstance(msg, Event), f"{description}: Failed to parse as Event message" + assert isinstance(msg, Event), ( + f"{description}: Failed to parse as Event message" + ) # Verify the message type assert msg.MESSAGE_TYPE == 36, f"{description}: Wrong message type" diff --git a/examples/serdes/tests/test_eventreceived.py b/examples/serdes/tests/test_eventreceived.py index 7c0a02b27..684a01ed4 100644 --- a/examples/serdes/tests/test_eventreceived.py +++ b/examples/serdes/tests/test_eventreceived.py @@ -8,6 +8,7 @@ Uses test vectors from: wamp-proto/testsuite/singlemessage/advanced/eventreceived.json """ + import pytest from autobahn.wamp.message import EventReceived from autobahn.wamp.serializer import create_transport_serializer @@ -23,6 +24,7 @@ # Test Vector Loading # ============================================================================= + @pytest.fixture(scope="module") def eventreceived_test_vector(): """Load EVENT_RECEIVED test vector from wamp-proto""" @@ -39,7 +41,10 @@ def eventreceived_samples(eventreceived_test_vector): # SerDes Tests # ============================================================================= -def test_eventreceived_deserialize_from_bytes(serializer_id, eventreceived_samples, create_serializer): + +def test_eventreceived_deserialize_from_bytes( + serializer_id, eventreceived_samples, create_serializer +): """ Test EVENT_RECEIVED deserialization from canonical bytes. @@ -64,7 +69,7 @@ def test_eventreceived_deserialize_from_bytes(serializer_id, eventreceived_sampl if "bytes_hex" in variant: test_bytes = bytes_from_hex(variant["bytes_hex"]) elif "bytes" in variant: - test_bytes = variant["bytes"].encode('utf-8') + test_bytes = variant["bytes"].encode("utf-8") else: continue @@ -76,7 +81,9 @@ def test_eventreceived_deserialize_from_bytes(serializer_id, eventreceived_sampl assert msg.publication == sample["expected_attributes"]["publication_id"] -def test_eventreceived_serialize_to_bytes(serializer_id, eventreceived_samples, create_serializer): +def test_eventreceived_serialize_to_bytes( + serializer_id, eventreceived_samples, create_serializer +): """ Test EVENT_RECEIVED serialization to bytes. @@ -105,7 +112,9 @@ def test_eventreceived_serialize_to_bytes(serializer_id, eventreceived_samples, ), f"Serialized bytes don't match any valid representation for {serializer_id}" -def test_eventreceived_roundtrip(serializer_id, eventreceived_samples, create_serializer): +def test_eventreceived_roundtrip( + serializer_id, eventreceived_samples, create_serializer +): """ Test EVENT_RECEIVED roundtrip: deserialize → serialize → deserialize. @@ -130,7 +139,7 @@ def test_eventreceived_roundtrip(serializer_id, eventreceived_samples, create_se if "bytes_hex" in variant: original_bytes = bytes_from_hex(variant["bytes_hex"]) elif "bytes" in variant: - original_bytes = variant["bytes"].encode('utf-8') + original_bytes = variant["bytes"].encode("utf-8") else: continue diff --git a/examples/serdes/tests/test_goodbye.py b/examples/serdes/tests/test_goodbye.py index b3eb3d1ab..65cb9faad 100644 --- a/examples/serdes/tests/test_goodbye.py +++ b/examples/serdes/tests/test_goodbye.py @@ -8,6 +8,7 @@ Uses test vectors from: wamp-proto/testsuite/singlemessage/basic/goodbye.json """ + import pytest from autobahn.wamp.message import Goodbye from autobahn.wamp.serializer import create_transport_serializer @@ -23,6 +24,7 @@ # Test Vector Loading # ============================================================================= + @pytest.fixture(scope="module") def goodbye_test_vector(): """Load GOODBYE test vector from wamp-proto""" @@ -39,7 +41,10 @@ def goodbye_samples(goodbye_test_vector): # SerDes Tests # ============================================================================= -def test_goodbye_deserialize_from_bytes(serializer_id, goodbye_samples, create_serializer): + +def test_goodbye_deserialize_from_bytes( + serializer_id, goodbye_samples, create_serializer +): """ Test GOODBYE deserialization from canonical bytes. @@ -64,7 +69,7 @@ def test_goodbye_deserialize_from_bytes(serializer_id, goodbye_samples, create_s if "bytes_hex" in variant: test_bytes = bytes_from_hex(variant["bytes_hex"]) elif "bytes" in variant: - test_bytes = variant["bytes"].encode('utf-8') + test_bytes = variant["bytes"].encode("utf-8") else: continue @@ -130,7 +135,7 @@ def test_goodbye_roundtrip(serializer_id, goodbye_samples, create_serializer): if "bytes_hex" in variant: original_bytes = bytes_from_hex(variant["bytes_hex"]) elif "bytes" in variant: - original_bytes = variant["bytes"].encode('utf-8') + original_bytes = variant["bytes"].encode("utf-8") else: continue diff --git a/examples/serdes/tests/test_hello.py b/examples/serdes/tests/test_hello.py index b48942f0c..99d52886d 100644 --- a/examples/serdes/tests/test_hello.py +++ b/examples/serdes/tests/test_hello.py @@ -8,6 +8,7 @@ Uses test vectors from: wamp-proto/testsuite/singlemessage/basic/hello.json """ + import pytest from autobahn.wamp.message import Hello from autobahn.wamp.serializer import create_transport_serializer @@ -15,7 +16,7 @@ RoleSubscriberFeatures, RolePublisherFeatures, RoleCallerFeatures, - RoleCalleeFeatures + RoleCalleeFeatures, ) from .utils import ( @@ -29,6 +30,7 @@ # Test Vector Loading # ============================================================================= + @pytest.fixture(scope="module") def hello_test_vector(): """Load HELLO test vector from wamp-proto""" @@ -45,6 +47,7 @@ def hello_samples(hello_test_vector): # Helper Functions # ============================================================================= + def _convert_roles_dict_to_objects(roles_dict): """ Convert a plain dict of roles to Role objects. @@ -71,6 +74,7 @@ def _convert_roles_dict_to_objects(roles_dict): # SerDes Tests # ============================================================================= + def test_hello_deserialize_from_bytes(serializer_id, hello_samples, create_serializer): """ Test HELLO deserialization from canonical bytes. @@ -96,7 +100,7 @@ def test_hello_deserialize_from_bytes(serializer_id, hello_samples, create_seria if "bytes_hex" in variant: test_bytes = bytes_from_hex(variant["bytes_hex"]) elif "bytes" in variant: - test_bytes = variant["bytes"].encode('utf-8') + test_bytes = variant["bytes"].encode("utf-8") else: continue @@ -128,8 +132,7 @@ def test_hello_serialize_to_bytes(serializer_id, hello_samples, create_serialize # Construct message attrs = sample["expected_attributes"] msg = Hello( - realm=attrs["realm"], - roles=_convert_roles_dict_to_objects(attrs["roles"]) + realm=attrs["realm"], roles=_convert_roles_dict_to_objects(attrs["roles"]) ) # Serialize @@ -166,7 +169,7 @@ def test_hello_roundtrip(serializer_id, hello_samples, create_serializer): if "bytes_hex" in variant: original_bytes = bytes_from_hex(variant["bytes_hex"]) elif "bytes" in variant: - original_bytes = variant["bytes"].encode('utf-8') + original_bytes = variant["bytes"].encode("utf-8") else: continue diff --git a/examples/serdes/tests/test_interrupt.py b/examples/serdes/tests/test_interrupt.py index 1bfe85a03..751dc2981 100644 --- a/examples/serdes/tests/test_interrupt.py +++ b/examples/serdes/tests/test_interrupt.py @@ -8,6 +8,7 @@ Uses test vectors from: wamp-proto/testsuite/singlemessage/advanced/interrupt.json """ + import pytest from autobahn.wamp.message import Interrupt from autobahn.wamp.serializer import create_transport_serializer @@ -23,6 +24,7 @@ # Test Vector Loading # ============================================================================= + @pytest.fixture(scope="module") def interrupt_test_vector(): """Load INTERRUPT test vector from wamp-proto""" @@ -39,7 +41,10 @@ def interrupt_samples(interrupt_test_vector): # SerDes Tests # ============================================================================= -def test_interrupt_deserialize_from_bytes(serializer_id, interrupt_samples, create_serializer): + +def test_interrupt_deserialize_from_bytes( + serializer_id, interrupt_samples, create_serializer +): """ Test INTERRUPT deserialization from canonical bytes. @@ -64,7 +69,7 @@ def test_interrupt_deserialize_from_bytes(serializer_id, interrupt_samples, crea if "bytes_hex" in variant: test_bytes = bytes_from_hex(variant["bytes_hex"]) elif "bytes" in variant: - test_bytes = variant["bytes"].encode('utf-8') + test_bytes = variant["bytes"].encode("utf-8") else: continue @@ -76,7 +81,9 @@ def test_interrupt_deserialize_from_bytes(serializer_id, interrupt_samples, crea assert msg.request == sample["expected_attributes"]["request_id"] -def test_interrupt_serialize_to_bytes(serializer_id, interrupt_samples, create_serializer): +def test_interrupt_serialize_to_bytes( + serializer_id, interrupt_samples, create_serializer +): """ Test INTERRUPT serialization to bytes. @@ -130,7 +137,7 @@ def test_interrupt_roundtrip(serializer_id, interrupt_samples, create_serializer if "bytes_hex" in variant: original_bytes = bytes_from_hex(variant["bytes_hex"]) elif "bytes" in variant: - original_bytes = variant["bytes"].encode('utf-8') + original_bytes = variant["bytes"].encode("utf-8") else: continue diff --git a/examples/serdes/tests/test_invocation.py b/examples/serdes/tests/test_invocation.py index e139c0d99..712d4b42e 100644 --- a/examples/serdes/tests/test_invocation.py +++ b/examples/serdes/tests/test_invocation.py @@ -8,6 +8,7 @@ Uses test vectors from: wamp-proto/testsuite/singlemessage/basic/invocation.json """ + import pytest from autobahn.wamp.message import Invocation from autobahn.wamp.serializer import create_transport_serializer @@ -23,6 +24,7 @@ # Test Vector Loading # ============================================================================= + @pytest.fixture(scope="module") def invocation_test_vector(): """Load INVOCATION test vector from wamp-proto""" @@ -39,7 +41,10 @@ def invocation_samples(invocation_test_vector): # SerDes Tests # ============================================================================= -def test_invocation_deserialize_from_bytes(serializer_id, invocation_samples, create_serializer): + +def test_invocation_deserialize_from_bytes( + serializer_id, invocation_samples, create_serializer +): """ Test INVOCATION deserialization from canonical bytes. @@ -64,7 +69,7 @@ def test_invocation_deserialize_from_bytes(serializer_id, invocation_samples, cr if "bytes_hex" in variant: test_bytes = bytes_from_hex(variant["bytes_hex"]) elif "bytes" in variant: - test_bytes = variant["bytes"].encode('utf-8') + test_bytes = variant["bytes"].encode("utf-8") else: continue @@ -77,7 +82,9 @@ def test_invocation_deserialize_from_bytes(serializer_id, invocation_samples, cr assert msg.registration == sample["expected_attributes"]["registration_id"] -def test_invocation_serialize_to_bytes(serializer_id, invocation_samples, create_serializer): +def test_invocation_serialize_to_bytes( + serializer_id, invocation_samples, create_serializer +): """ Test INVOCATION serialization to bytes. @@ -96,8 +103,7 @@ def test_invocation_serialize_to_bytes(serializer_id, invocation_samples, create # Construct message attrs = sample["expected_attributes"] msg = Invocation( - request=attrs["request_id"], - registration=attrs["registration_id"] + request=attrs["request_id"], registration=attrs["registration_id"] ) # Serialize @@ -134,7 +140,7 @@ def test_invocation_roundtrip(serializer_id, invocation_samples, create_serializ if "bytes_hex" in variant: original_bytes = bytes_from_hex(variant["bytes_hex"]) elif "bytes" in variant: - original_bytes = variant["bytes"].encode('utf-8') + original_bytes = variant["bytes"].encode("utf-8") else: continue diff --git a/examples/serdes/tests/test_publish.py b/examples/serdes/tests/test_publish.py index fcec6ecd1..7c75b2adf 100644 --- a/examples/serdes/tests/test_publish.py +++ b/examples/serdes/tests/test_publish.py @@ -8,6 +8,7 @@ Uses test vectors from: wamp-proto/testsuite/singlemessage/basic/publish.json """ + import pytest from autobahn.wamp.message import Publish from autobahn.wamp.serializer import create_transport_serializer @@ -26,6 +27,7 @@ # Test Vector Loading # ============================================================================= + @pytest.fixture(scope="module") def publish_test_vector(): """Load PUBLISH test vector from wamp-proto""" @@ -50,7 +52,10 @@ def publish_samples(publish_test_vector): # Dimension 2: Single-Serializer Roundtrip Correctness # ============================================================================= -def test_publish_deserialize_from_bytes(serializer_id, publish_samples, create_serializer): + +def test_publish_deserialize_from_bytes( + serializer_id, publish_samples, create_serializer +): """ Test PUBLISH deserialization from canonical bytes. @@ -75,7 +80,7 @@ def test_publish_deserialize_from_bytes(serializer_id, publish_samples, create_s if "bytes_hex" in variant: test_bytes = bytes_from_hex(variant["bytes_hex"]) elif "bytes" in variant: - test_bytes = variant["bytes"].encode('utf-8') + test_bytes = variant["bytes"].encode("utf-8") else: continue @@ -92,8 +97,10 @@ def test_publish_deserialize_from_bytes(serializer_id, publish_samples, create_s error = validates_with_any_code(msg, validation_codes) if error: # Debug: print sample description - sample_desc = sample.get('description', 'unknown') - pytest.fail(f"Validation failed for {serializer_id} on sample '{sample_desc}': {error}") + sample_desc = sample.get("description", "unknown") + pytest.fail( + f"Validation failed for {serializer_id} on sample '{sample_desc}': {error}" + ) def test_publish_serialize_to_bytes(serializer_id, publish_samples, create_serializer): @@ -144,11 +151,6 @@ def test_publish_roundtrip(serializer_id, publish_samples, create_serializer): serializer = create_serializer(serializer_id) for sample in publish_samples: - # Skip flatbuffers with transparent payload mode due to autobahn bug - # See: https://github.com/crossbario/autobahn-python/issues/1766 - # flatbuffers serializer incorrectly handles enc_algo (expects uint8, gets string) - if serializer_id == 'flatbuffers' and sample.get('expected_attributes', {}).get('payload') is not None: - pytest.skip("Flatbuffers with transparent payload not supported (issue #1766)") # Skip if no construction code construction_code = sample["construction"].get("autobahn-python") if not construction_code: @@ -176,15 +178,17 @@ def test_publish_roundtrip(serializer_id, publish_samples, create_serializer): pytest.fail(f"Roundtrip validation failed for {serializer_id}: {error}") # 5. Check equality (if message class implements __eq__) - if hasattr(msg_original, '__eq__'): - assert msg_original == msg_roundtrip, \ + if hasattr(msg_original, "__eq__"): + assert msg_original == msg_roundtrip, ( f"Roundtrip message not equal to original for {serializer_id}" + ) # ============================================================================= # Dimension 3: Cross-Serializer Preservation # ============================================================================= + def test_publish_cross_serializer_preservation(serializer_pair, publish_samples): """ Test that PUBLISH message attributes are preserved across different serializers. @@ -204,13 +208,6 @@ def test_publish_cross_serializer_preservation(serializer_pair, publish_samples) ser2 = create_transport_serializer(ser2_id) for sample in publish_samples: - # Skip flatbuffers with transparent payload mode due to autobahn bug - # See: https://github.com/crossbario/autobahn-python/issues/1766 - # flatbuffers serializer incorrectly handles enc_algo (expects uint8, gets string) - has_payload = sample.get('expected_attributes', {}).get('payload') is not None - if has_payload and ('flatbuffers' in [ser1_id, ser2_id]): - pytest.skip("Flatbuffers with transparent payload not supported (issue #1766)") - # Skip if ser1 not in test vector if ser1_id not in sample["serializers"]: pytest.skip(f"Serializer {ser1_id} not in test vector") @@ -228,10 +225,10 @@ def test_publish_cross_serializer_preservation(serializer_pair, publish_samples) # Take the first canonical byte representation from ser1 variant1 = ser1_variants[0] - if 'bytes_hex' in variant1: - bytes1 = bytes_from_hex(variant1['bytes_hex']) - elif 'bytes' in variant1: - bytes1 = variant1['bytes'].encode('utf-8') + if "bytes_hex" in variant1: + bytes1 = bytes_from_hex(variant1["bytes_hex"]) + elif "bytes" in variant1: + bytes1 = variant1["bytes"].encode("utf-8") else: pytest.skip(f"No bytes representation in {ser1_id} variant") @@ -258,6 +255,7 @@ def test_publish_cross_serializer_preservation(serializer_pair, publish_samples) # Expected Attributes Validation # ============================================================================= + def test_publish_expected_attributes(publish_samples): """ Test that deserialized PUBLISH message has expected attributes. @@ -280,7 +278,7 @@ def test_publish_expected_attributes(publish_samples): # Deserialize from first JSON variant variant = json_variants[0] if "bytes" in variant: - test_bytes = variant["bytes"].encode('utf-8') + test_bytes = variant["bytes"].encode("utf-8") elif "bytes_hex" in variant: test_bytes = bytes_from_hex(variant["bytes_hex"]) else: @@ -299,7 +297,9 @@ def test_publish_expected_attributes(publish_samples): # kwargs can be None or {} depending on implementation if expected["kwargs"] is None: - assert msg.kwargs is None or msg.kwargs == {}, "kwargs should be None or empty" + assert msg.kwargs is None or msg.kwargs == {}, ( + "kwargs should be None or empty" + ) else: assert msg.kwargs == expected["kwargs"], "kwargs mismatch" @@ -308,6 +308,7 @@ def test_publish_expected_attributes(publish_samples): # Payload Mode Tests # ============================================================================= + def test_publish_normal_mode(publish_samples): """ Test PUBLISH in normal payload mode. @@ -316,15 +317,20 @@ def test_publish_normal_mode(publish_samples): Router deserializes and can inspect args/kwargs. """ # Find the normal mode sample (has args, not payload) - normal_samples = [s for s in publish_samples if 'args' in s['expected_attributes'] and s['expected_attributes']['args'] is not None] + normal_samples = [ + s + for s in publish_samples + if "args" in s["expected_attributes"] + and s["expected_attributes"]["args"] is not None + ] assert len(normal_samples) > 0, "Should have at least one normal mode sample" for sample in normal_samples: # Verify expected attributes show normal mode - expected = sample['expected_attributes'] - assert expected.get('args') is not None or expected.get('kwargs') is not None - assert expected.get('payload') is None or expected['payload'] is None + expected = sample["expected_attributes"] + assert expected.get("args") is not None or expected.get("kwargs") is not None + assert expected.get("payload") is None or expected["payload"] is None def test_publish_transparent_mode(publish_samples, create_serializer): @@ -335,24 +341,29 @@ def test_publish_transparent_mode(publish_samples, create_serializer): Router does NOT deserialize payload - enables E2E encryption. """ # Find the transparent mode sample (has payload, not args/kwargs) - transparent_samples = [s for s in publish_samples if 'payload' in s['expected_attributes'] and s['expected_attributes']['payload'] is not None] + transparent_samples = [ + s + for s in publish_samples + if "payload" in s["expected_attributes"] + and s["expected_attributes"]["payload"] is not None + ] if not transparent_samples: pytest.skip("No transparent payload mode samples in test vector") for sample in transparent_samples: # Verify expected attributes show transparent mode - expected = sample['expected_attributes'] - assert expected.get('payload') is not None - assert expected.get('args') is None - assert expected.get('kwargs') is None + expected = sample["expected_attributes"] + assert expected.get("payload") is not None + assert expected.get("args") is None + assert expected.get("kwargs") is None # Test roundtrip for each serializer - construction_code = sample['construction'].get('autobahn-python') + construction_code = sample["construction"].get("autobahn-python") if not construction_code: continue - validation_codes = sample['validation'].get('autobahn-python', []) + validation_codes = sample["validation"].get("autobahn-python", []) if not validation_codes: continue @@ -366,7 +377,7 @@ def test_publish_transparent_mode(publish_samples, create_serializer): assert msg_original.kwargs is None # Test with JSON serializer (most common) - serializer = create_serializer('json') + serializer = create_serializer("json") serialized, is_binary = serializer.serialize(msg_original) msgs = serializer.unserialize(serialized) assert len(msgs) == 1 @@ -378,8 +389,9 @@ def test_publish_transparent_mode(publish_samples, create_serializer): pytest.fail(f"Transparent mode validation failed: {error}") # Critical: payload bytes must be preserved exactly (byte-for-byte) - assert msg_roundtrip.payload == msg_original.payload, \ + assert msg_roundtrip.payload == msg_original.payload, ( "Transparent payload must be preserved byte-for-byte through serialization" + ) # ============================================================================= @@ -399,6 +411,7 @@ def test_publish_transparent_mode(publish_samples, create_serializer): # acknowledge|bool - Basic Profile # ----------------------------------------------------------------------------- + def test_publish_options_acknowledge_valid(): """Test PUBLISH.Options.acknowledge with valid bool values""" for value in [True, False]: @@ -421,14 +434,16 @@ def test_publish_options_acknowledge_invalid_type(): wmsg = [16, 123, {"acknowledge": value}, "com.example.topic"] with pytest.raises(ProtocolError) as exc_info: Publish.parse(wmsg) - assert "acknowledge" in str(exc_info.value).lower(), \ + assert "acknowledge" in str(exc_info.value).lower(), ( f"ProtocolError should mention 'acknowledge' for {description}" + ) # ----------------------------------------------------------------------------- # exclude_me|bool - Advanced Profile # ----------------------------------------------------------------------------- + def test_publish_options_exclude_me_valid(): """Test PUBLISH.Options.exclude_me with valid bool values""" for value in [True, False]: @@ -454,6 +469,7 @@ def test_publish_options_exclude_me_invalid_type(): # exclude|list[int] - Advanced Profile # ----------------------------------------------------------------------------- + def test_publish_options_exclude_valid(): """Test PUBLISH.Options.exclude with valid list[int] values""" valid_values = [ @@ -502,6 +518,7 @@ def test_publish_options_exclude_invalid_values(): # exclude_authid|list[str] - Advanced Profile # ----------------------------------------------------------------------------- + def test_publish_options_exclude_authid_valid(): """Test PUBLISH.Options.exclude_authid with valid list[str] values""" valid_values = [ @@ -546,6 +563,7 @@ def test_publish_options_exclude_authid_invalid_values(): # exclude_authrole|list[str] - Advanced Profile # ----------------------------------------------------------------------------- + def test_publish_options_exclude_authrole_valid(): """Test PUBLISH.Options.exclude_authrole with valid list[str] values""" valid_values = [ @@ -589,6 +607,7 @@ def test_publish_options_exclude_authrole_invalid_values(): # eligible|list[int] - Advanced Profile # ----------------------------------------------------------------------------- + def test_publish_options_eligible_valid(): """Test PUBLISH.Options.eligible with valid list[int] values""" valid_values = [ @@ -632,6 +651,7 @@ def test_publish_options_eligible_invalid_values(): # eligible_authid|list[str] - Advanced Profile # ----------------------------------------------------------------------------- + def test_publish_options_eligible_authid_valid(): """Test PUBLISH.Options.eligible_authid with valid list[str] values""" valid_values = [ @@ -675,6 +695,7 @@ def test_publish_options_eligible_authid_invalid_values(): # eligible_authrole|list[str] - Advanced Profile # ----------------------------------------------------------------------------- + def test_publish_options_eligible_authrole_valid(): """Test PUBLISH.Options.eligible_authrole with valid list[str] values""" valid_values = [ @@ -718,6 +739,7 @@ def test_publish_options_eligible_authrole_invalid_values(): # retain|bool - Advanced Profile # ----------------------------------------------------------------------------- + def test_publish_options_retain_valid(): """Test PUBLISH.Options.retain with valid bool values""" for value in [True, False]: @@ -743,6 +765,7 @@ def test_publish_options_retain_invalid_type(): # transaction_hash|str - Implementation-Only # ----------------------------------------------------------------------------- + def test_publish_options_transaction_hash_valid(): """Test PUBLISH.Options.transaction_hash with valid str values""" valid_values = [ @@ -774,6 +797,7 @@ def test_publish_options_transaction_hash_invalid_type(): # forward_for|list[dict] - Implementation-Only # ----------------------------------------------------------------------------- + def test_publish_options_forward_for_valid(): """Test PUBLISH.Options.forward_for with valid list[dict] values""" valid_values = [ @@ -815,8 +839,14 @@ def test_publish_options_forward_for_invalid_values(): """ invalid_lists = [ # Multi-item lists where validation fails - ([{"session": 123, "authid": "alice", "authrole": "user"}, 123], "second item is int"), - ([{"session": 123, "authid": "alice", "authrole": "user"}, {"bad": "dict"}], "second item missing required fields"), + ( + [{"session": 123, "authid": "alice", "authrole": "user"}, 123], + "second item is int", + ), + ( + [{"session": 123, "authid": "alice", "authrole": "user"}, {"bad": "dict"}], + "second item missing required fields", + ), ] for value, description in invalid_lists: wmsg = [16, 123, {"forward_for": value}, "com.example.topic"] @@ -831,6 +861,7 @@ def test_publish_options_forward_for_invalid_values(): # These are only valid with transparent payload mode (variant 4) # ----------------------------------------------------------------------------- + def test_publish_options_enc_algo_valid(): """Test PUBLISH.Options.enc_algo with valid values (transparent payload mode)""" # enc_algo is only parsed when message has transparent payload @@ -874,7 +905,13 @@ def test_publish_options_enc_key_valid(): "", ] for value in valid_values: - wmsg = [16, 123, {"enc_algo": "cryptobox", "enc_key": value}, "com.example.topic", payload] + wmsg = [ + 16, + 123, + {"enc_algo": "cryptobox", "enc_key": value}, + "com.example.topic", + payload, + ] msg = Publish.parse(wmsg) assert msg.enc_key == value @@ -893,7 +930,13 @@ def test_publish_options_enc_key_invalid_type(): ([1, 2], "non-empty list instead of str"), # truthy, will be caught ] for value, description in invalid_values: - wmsg = [16, 123, {"enc_algo": "cryptobox", "enc_key": value}, "com.example.topic", payload] + wmsg = [ + 16, + 123, + {"enc_algo": "cryptobox", "enc_key": value}, + "com.example.topic", + payload, + ] # Due to validation bug with falsy values, might hit assert in __init__ with pytest.raises((ProtocolError, AssertionError)) as exc_info: Publish.parse(wmsg) @@ -912,7 +955,13 @@ def test_publish_options_enc_serializer_valid(): "flatbuffers", ] for value in valid_values: - wmsg = [16, 123, {"enc_algo": "cryptobox", "enc_serializer": value}, "com.example.topic", payload] + wmsg = [ + 16, + 123, + {"enc_algo": "cryptobox", "enc_serializer": value}, + "com.example.topic", + payload, + ] msg = Publish.parse(wmsg) assert msg.enc_serializer == value @@ -926,7 +975,13 @@ def test_publish_options_enc_serializer_invalid_value(): "yaml", ] for value in invalid_values: - wmsg = [16, 123, {"enc_algo": "cryptobox", "enc_serializer": value}, "com.example.topic", payload] + wmsg = [ + 16, + 123, + {"enc_algo": "cryptobox", "enc_serializer": value}, + "com.example.topic", + payload, + ] with pytest.raises(ProtocolError) as exc_info: Publish.parse(wmsg) assert "enc_serializer" in str(exc_info.value).lower() @@ -936,17 +991,24 @@ def test_publish_options_enc_serializer_invalid_value(): # Dimension 5: Options Validation (Language-Agnostic JSON Test Vectors) # ============================================================================= + @pytest.fixture(scope="module") def publish_validation_samples(publish_test_vector): """Extract validation samples from PUBLISH test vector""" - return [s for s in publish_test_vector["samples"] if s.get("test_category") == "options_validation"] + return [ + s + for s in publish_test_vector["samples"] + if s.get("test_category") == "options_validation" + ] def test_publish_options_validation_sample_count(publish_validation_samples): """Verify we have the expected number of validation samples""" # We should have validation samples for all PUBLISH.Options attributes assert len(publish_validation_samples) > 0, "No validation samples found" - print(f"\nLoaded {len(publish_validation_samples)} PUBLISH.Options validation samples from JSON") + print( + f"\nLoaded {len(publish_validation_samples)} PUBLISH.Options validation samples from JSON" + ) @pytest.mark.parametrize("sample_index", range(35)) # We have 35 validation samples @@ -984,12 +1046,15 @@ def test_publish_options_validation_from_json(publish_validation_samples, sample # Verify the error message contains the expected attribute name error_msg = str(exc_info.value).lower() - assert expected_error["contains"].lower() in error_msg, \ + assert expected_error["contains"].lower() in error_msg, ( f"{description}: Expected error message to contain '{expected_error['contains']}', got: {exc_info.value}" + ) else: # This sample should parse successfully msg = Publish.parse(wmsg) - assert isinstance(msg, Publish), f"{description}: Failed to parse as Publish message" + assert isinstance(msg, Publish), ( + f"{description}: Failed to parse as Publish message" + ) # Verify the message type assert msg.MESSAGE_TYPE == 16, f"{description}: Wrong message type" diff --git a/examples/serdes/tests/test_published.py b/examples/serdes/tests/test_published.py index 14f766a55..10629a428 100644 --- a/examples/serdes/tests/test_published.py +++ b/examples/serdes/tests/test_published.py @@ -9,6 +9,7 @@ Uses test vectors from: wamp-proto/testsuite/singlemessage/basic/published.json """ + import pytest from autobahn.wamp.message import Published from autobahn.wamp.serializer import create_transport_serializer @@ -24,6 +25,7 @@ # Test Vector Loading # ============================================================================= + @pytest.fixture(scope="module") def published_test_vector(): """Load PUBLISHED test vector from wamp-proto""" @@ -40,7 +42,10 @@ def published_samples(published_test_vector): # SerDes Tests # ============================================================================= -def test_published_deserialize_from_bytes(serializer_id, published_samples, create_serializer): + +def test_published_deserialize_from_bytes( + serializer_id, published_samples, create_serializer +): """ Test PUBLISHED deserialization from canonical bytes. @@ -65,7 +70,7 @@ def test_published_deserialize_from_bytes(serializer_id, published_samples, crea if "bytes_hex" in variant: test_bytes = bytes_from_hex(variant["bytes_hex"]) elif "bytes" in variant: - test_bytes = variant["bytes"].encode('utf-8') + test_bytes = variant["bytes"].encode("utf-8") else: continue @@ -78,7 +83,9 @@ def test_published_deserialize_from_bytes(serializer_id, published_samples, crea assert msg.publication == sample["expected_attributes"]["publication_id"] -def test_published_serialize_to_bytes(serializer_id, published_samples, create_serializer): +def test_published_serialize_to_bytes( + serializer_id, published_samples, create_serializer +): """ Test PUBLISHED serialization to bytes. @@ -97,8 +104,7 @@ def test_published_serialize_to_bytes(serializer_id, published_samples, create_s # Construct message attrs = sample["expected_attributes"] msg = Published( - request=attrs["request_id"], - publication=attrs["publication_id"] + request=attrs["request_id"], publication=attrs["publication_id"] ) # Serialize @@ -135,7 +141,7 @@ def test_published_roundtrip(serializer_id, published_samples, create_serializer if "bytes_hex" in variant: original_bytes = bytes_from_hex(variant["bytes_hex"]) elif "bytes" in variant: - original_bytes = variant["bytes"].encode('utf-8') + original_bytes = variant["bytes"].encode("utf-8") else: continue diff --git a/examples/serdes/tests/test_register.py b/examples/serdes/tests/test_register.py index d6d54d8e4..897229456 100644 --- a/examples/serdes/tests/test_register.py +++ b/examples/serdes/tests/test_register.py @@ -8,6 +8,7 @@ Uses test vectors from: wamp-proto/testsuite/singlemessage/basic/register.json """ + import pytest from autobahn.wamp.message import Register from autobahn.wamp.serializer import create_transport_serializer @@ -23,6 +24,7 @@ # Test Vector Loading # ============================================================================= + @pytest.fixture(scope="module") def register_test_vector(): """Load REGISTER test vector from wamp-proto""" @@ -39,7 +41,10 @@ def register_samples(register_test_vector): # SerDes Tests # ============================================================================= -def test_register_deserialize_from_bytes(serializer_id, register_samples, create_serializer): + +def test_register_deserialize_from_bytes( + serializer_id, register_samples, create_serializer +): """ Test REGISTER deserialization from canonical bytes. @@ -64,7 +69,7 @@ def test_register_deserialize_from_bytes(serializer_id, register_samples, create if "bytes_hex" in variant: test_bytes = bytes_from_hex(variant["bytes_hex"]) elif "bytes" in variant: - test_bytes = variant["bytes"].encode('utf-8') + test_bytes = variant["bytes"].encode("utf-8") else: continue @@ -77,7 +82,9 @@ def test_register_deserialize_from_bytes(serializer_id, register_samples, create assert msg.procedure == sample["expected_attributes"]["procedure"] -def test_register_serialize_to_bytes(serializer_id, register_samples, create_serializer): +def test_register_serialize_to_bytes( + serializer_id, register_samples, create_serializer +): """ Test REGISTER serialization to bytes. @@ -95,10 +102,7 @@ def test_register_serialize_to_bytes(serializer_id, register_samples, create_ser # Construct message attrs = sample["expected_attributes"] - msg = Register( - request=attrs["request_id"], - procedure=attrs["procedure"] - ) + msg = Register(request=attrs["request_id"], procedure=attrs["procedure"]) # Serialize serialized_bytes, is_binary = serializer.serialize(msg) @@ -134,7 +138,7 @@ def test_register_roundtrip(serializer_id, register_samples, create_serializer): if "bytes_hex" in variant: original_bytes = bytes_from_hex(variant["bytes_hex"]) elif "bytes" in variant: - original_bytes = variant["bytes"].encode('utf-8') + original_bytes = variant["bytes"].encode("utf-8") else: continue diff --git a/examples/serdes/tests/test_registered.py b/examples/serdes/tests/test_registered.py index 19b8e8755..189448cfd 100644 --- a/examples/serdes/tests/test_registered.py +++ b/examples/serdes/tests/test_registered.py @@ -9,6 +9,7 @@ Uses test vectors from: wamp-proto/testsuite/singlemessage/basic/registered.json """ + import pytest from autobahn.wamp.message import Registered from autobahn.wamp.serializer import create_transport_serializer @@ -24,6 +25,7 @@ # Test Vector Loading # ============================================================================= + @pytest.fixture(scope="module") def registered_test_vector(): """Load REGISTERED test vector from wamp-proto""" @@ -40,7 +42,10 @@ def registered_samples(registered_test_vector): # SerDes Tests # ============================================================================= -def test_registered_deserialize_from_bytes(serializer_id, registered_samples, create_serializer): + +def test_registered_deserialize_from_bytes( + serializer_id, registered_samples, create_serializer +): """ Test REGISTERED deserialization from canonical bytes. @@ -65,7 +70,7 @@ def test_registered_deserialize_from_bytes(serializer_id, registered_samples, cr if "bytes_hex" in variant: test_bytes = bytes_from_hex(variant["bytes_hex"]) elif "bytes" in variant: - test_bytes = variant["bytes"].encode('utf-8') + test_bytes = variant["bytes"].encode("utf-8") else: continue @@ -78,7 +83,9 @@ def test_registered_deserialize_from_bytes(serializer_id, registered_samples, cr assert msg.registration == sample["expected_attributes"]["registration_id"] -def test_registered_serialize_to_bytes(serializer_id, registered_samples, create_serializer): +def test_registered_serialize_to_bytes( + serializer_id, registered_samples, create_serializer +): """ Test REGISTERED serialization to bytes. @@ -97,8 +104,7 @@ def test_registered_serialize_to_bytes(serializer_id, registered_samples, create # Construct message attrs = sample["expected_attributes"] msg = Registered( - request=attrs["request_id"], - registration=attrs["registration_id"] + request=attrs["request_id"], registration=attrs["registration_id"] ) # Serialize @@ -135,7 +141,7 @@ def test_registered_roundtrip(serializer_id, registered_samples, create_serializ if "bytes_hex" in variant: original_bytes = bytes_from_hex(variant["bytes_hex"]) elif "bytes" in variant: - original_bytes = variant["bytes"].encode('utf-8') + original_bytes = variant["bytes"].encode("utf-8") else: continue diff --git a/examples/serdes/tests/test_result.py b/examples/serdes/tests/test_result.py index 1c4c8224b..3d2d99cff 100644 --- a/examples/serdes/tests/test_result.py +++ b/examples/serdes/tests/test_result.py @@ -8,6 +8,7 @@ Uses test vectors from: wamp-proto/testsuite/singlemessage/basic/result.json """ + import pytest from autobahn.wamp.message import Result from autobahn.wamp.serializer import create_transport_serializer @@ -23,6 +24,7 @@ # Test Vector Loading # ============================================================================= + @pytest.fixture(scope="module") def result_test_vector(): """Load RESULT test vector from wamp-proto""" @@ -39,7 +41,10 @@ def result_samples(result_test_vector): # SerDes Tests # ============================================================================= -def test_result_deserialize_from_bytes(serializer_id, result_samples, create_serializer): + +def test_result_deserialize_from_bytes( + serializer_id, result_samples, create_serializer +): """ Test RESULT deserialization from canonical bytes. @@ -64,7 +69,7 @@ def test_result_deserialize_from_bytes(serializer_id, result_samples, create_ser if "bytes_hex" in variant: test_bytes = bytes_from_hex(variant["bytes_hex"]) elif "bytes" in variant: - test_bytes = variant["bytes"].encode('utf-8') + test_bytes = variant["bytes"].encode("utf-8") else: continue @@ -111,7 +116,7 @@ def test_result_serialize_to_bytes(serializer_id, result_samples, create_seriali msg = Result( request=attrs["request_id"], args=attrs.get("args"), - kwargs=attrs.get("kwargs") + kwargs=attrs.get("kwargs"), ) # Serialize @@ -148,7 +153,7 @@ def test_result_roundtrip(serializer_id, result_samples, create_serializer): if "bytes_hex" in variant: original_bytes = bytes_from_hex(variant["bytes_hex"]) elif "bytes" in variant: - original_bytes = variant["bytes"].encode('utf-8') + original_bytes = variant["bytes"].encode("utf-8") else: continue diff --git a/examples/serdes/tests/test_subscribe.py b/examples/serdes/tests/test_subscribe.py index a2e13e982..a377fba89 100644 --- a/examples/serdes/tests/test_subscribe.py +++ b/examples/serdes/tests/test_subscribe.py @@ -8,6 +8,7 @@ Uses test vectors from: wamp-proto/testsuite/singlemessage/basic/subscribe.json """ + import pytest from autobahn.wamp.message import Subscribe from autobahn.wamp.serializer import create_transport_serializer @@ -26,6 +27,7 @@ # Test Vector Loading # ============================================================================= + @pytest.fixture(scope="module") def subscribe_test_vector(): """Load SUBSCRIBE test vector from wamp-proto""" @@ -49,7 +51,10 @@ def subscribe_validation_samples(subscribe_test_vector): # Dimension 1: Single-Serializer Roundtrip Correctness # ============================================================================= -def test_subscribe_deserialize_from_bytes(serializer_id, subscribe_samples, create_serializer): + +def test_subscribe_deserialize_from_bytes( + serializer_id, subscribe_samples, create_serializer +): """ Test SUBSCRIBE deserialization from canonical bytes. @@ -74,7 +79,7 @@ def test_subscribe_deserialize_from_bytes(serializer_id, subscribe_samples, crea if "bytes_hex" in variant: test_bytes = bytes_from_hex(variant["bytes_hex"]) elif "bytes" in variant: - test_bytes = variant["bytes"].encode('utf-8') + test_bytes = variant["bytes"].encode("utf-8") else: continue @@ -96,7 +101,9 @@ def test_subscribe_deserialize_from_bytes(serializer_id, subscribe_samples, crea assert msg.forward_for == expected_options["forward_for"] -def test_subscribe_serialize_to_bytes(serializer_id, subscribe_samples, create_serializer): +def test_subscribe_serialize_to_bytes( + serializer_id, subscribe_samples, create_serializer +): """ Test SUBSCRIBE serialization to bytes. @@ -158,7 +165,7 @@ def test_subscribe_roundtrip(serializer_id, subscribe_samples, create_serializer if "bytes_hex" in variant: original_bytes = bytes_from_hex(variant["bytes_hex"]) elif "bytes" in variant: - original_bytes = variant["bytes"].encode('utf-8') + original_bytes = variant["bytes"].encode("utf-8") else: continue @@ -185,14 +192,19 @@ def test_subscribe_roundtrip(serializer_id, subscribe_samples, create_serializer # Dimension 2: Options Validation # ============================================================================= + def test_subscribe_options_validation_sample_count(subscribe_validation_samples): """Verify we have the expected number of validation samples""" assert len(subscribe_validation_samples) > 0, "No validation samples found" - print(f"\nLoaded {len(subscribe_validation_samples)} SUBSCRIBE.Options validation samples from JSON") + print( + f"\nLoaded {len(subscribe_validation_samples)} SUBSCRIBE.Options validation samples from JSON" + ) @pytest.mark.parametrize("sample_index", range(11)) # We have 11 validation samples -def test_subscribe_options_validation_from_json(subscribe_validation_samples, sample_index): +def test_subscribe_options_validation_from_json( + subscribe_validation_samples, sample_index +): """Test SUBSCRIBE.Options validation using language-agnostic JSON test vectors This test loads validation samples from wamp-proto/testsuite/singlemessage/basic/subscribe.json @@ -231,8 +243,12 @@ def test_subscribe_options_validation_from_json(subscribe_validation_samples, sa # This sample should parse successfully try: msg = Subscribe.parse(wmsg) - assert isinstance(msg, Subscribe), f"parse() should return Subscribe instance for: {description}" + assert isinstance(msg, Subscribe), ( + f"parse() should return Subscribe instance for: {description}" + ) assert msg.request == wmsg[1], f"request mismatch for: {description}" assert msg.topic == wmsg[3], f"topic mismatch for: {description}" except Exception as e: - pytest.fail(f"Expected successful parse, but got {type(e).__name__}: {e}\nTest: {description}") + pytest.fail( + f"Expected successful parse, but got {type(e).__name__}: {e}\nTest: {description}" + ) diff --git a/examples/serdes/tests/test_subscribed.py b/examples/serdes/tests/test_subscribed.py index f59a131a0..463130d87 100644 --- a/examples/serdes/tests/test_subscribed.py +++ b/examples/serdes/tests/test_subscribed.py @@ -9,6 +9,7 @@ Uses test vectors from: wamp-proto/testsuite/singlemessage/basic/subscribed.json """ + import pytest from autobahn.wamp.message import Subscribed from autobahn.wamp.serializer import create_transport_serializer @@ -24,6 +25,7 @@ # Test Vector Loading # ============================================================================= + @pytest.fixture(scope="module") def subscribed_test_vector(): """Load SUBSCRIBED test vector from wamp-proto""" @@ -40,7 +42,10 @@ def subscribed_samples(subscribed_test_vector): # SerDes Tests # ============================================================================= -def test_subscribed_deserialize_from_bytes(serializer_id, subscribed_samples, create_serializer): + +def test_subscribed_deserialize_from_bytes( + serializer_id, subscribed_samples, create_serializer +): """ Test SUBSCRIBED deserialization from canonical bytes. @@ -65,7 +70,7 @@ def test_subscribed_deserialize_from_bytes(serializer_id, subscribed_samples, cr if "bytes_hex" in variant: test_bytes = bytes_from_hex(variant["bytes_hex"]) elif "bytes" in variant: - test_bytes = variant["bytes"].encode('utf-8') + test_bytes = variant["bytes"].encode("utf-8") else: continue @@ -78,7 +83,9 @@ def test_subscribed_deserialize_from_bytes(serializer_id, subscribed_samples, cr assert msg.subscription == sample["expected_attributes"]["subscription_id"] -def test_subscribed_serialize_to_bytes(serializer_id, subscribed_samples, create_serializer): +def test_subscribed_serialize_to_bytes( + serializer_id, subscribed_samples, create_serializer +): """ Test SUBSCRIBED serialization to bytes. @@ -97,8 +104,7 @@ def test_subscribed_serialize_to_bytes(serializer_id, subscribed_samples, create # Construct message attrs = sample["expected_attributes"] msg = Subscribed( - request=attrs["request_id"], - subscription=attrs["subscription_id"] + request=attrs["request_id"], subscription=attrs["subscription_id"] ) # Serialize @@ -135,7 +141,7 @@ def test_subscribed_roundtrip(serializer_id, subscribed_samples, create_serializ if "bytes_hex" in variant: original_bytes = bytes_from_hex(variant["bytes_hex"]) elif "bytes" in variant: - original_bytes = variant["bytes"].encode('utf-8') + original_bytes = variant["bytes"].encode("utf-8") else: continue diff --git a/examples/serdes/tests/test_unregister.py b/examples/serdes/tests/test_unregister.py index 67d101ad2..302aaefea 100644 --- a/examples/serdes/tests/test_unregister.py +++ b/examples/serdes/tests/test_unregister.py @@ -8,6 +8,7 @@ Uses test vectors from: wamp-proto/testsuite/singlemessage/basic/unregister.json """ + import pytest from autobahn.wamp.message import Unregister from autobahn.wamp.serializer import create_transport_serializer @@ -23,6 +24,7 @@ # Test Vector Loading # ============================================================================= + @pytest.fixture(scope="module") def unregister_test_vector(): """Load UNREGISTER test vector from wamp-proto""" @@ -39,7 +41,10 @@ def unregister_samples(unregister_test_vector): # SerDes Tests # ============================================================================= -def test_unregister_deserialize_from_bytes(serializer_id, unregister_samples, create_serializer): + +def test_unregister_deserialize_from_bytes( + serializer_id, unregister_samples, create_serializer +): """ Test UNREGISTER deserialization from canonical bytes. @@ -64,7 +69,7 @@ def test_unregister_deserialize_from_bytes(serializer_id, unregister_samples, cr if "bytes_hex" in variant: test_bytes = bytes_from_hex(variant["bytes_hex"]) elif "bytes" in variant: - test_bytes = variant["bytes"].encode('utf-8') + test_bytes = variant["bytes"].encode("utf-8") else: continue @@ -77,7 +82,9 @@ def test_unregister_deserialize_from_bytes(serializer_id, unregister_samples, cr assert msg.registration == sample["expected_attributes"]["registration_id"] -def test_unregister_serialize_to_bytes(serializer_id, unregister_samples, create_serializer): +def test_unregister_serialize_to_bytes( + serializer_id, unregister_samples, create_serializer +): """ Test UNREGISTER serialization to bytes. @@ -96,8 +103,7 @@ def test_unregister_serialize_to_bytes(serializer_id, unregister_samples, create # Construct message attrs = sample["expected_attributes"] msg = Unregister( - request=attrs["request_id"], - registration=attrs["registration_id"] + request=attrs["request_id"], registration=attrs["registration_id"] ) # Serialize @@ -134,7 +140,7 @@ def test_unregister_roundtrip(serializer_id, unregister_samples, create_serializ if "bytes_hex" in variant: original_bytes = bytes_from_hex(variant["bytes_hex"]) elif "bytes" in variant: - original_bytes = variant["bytes"].encode('utf-8') + original_bytes = variant["bytes"].encode("utf-8") else: continue diff --git a/examples/serdes/tests/test_unregistered.py b/examples/serdes/tests/test_unregistered.py index 5eaa18032..5350d97d2 100644 --- a/examples/serdes/tests/test_unregistered.py +++ b/examples/serdes/tests/test_unregistered.py @@ -9,6 +9,7 @@ Uses test vectors from: wamp-proto/testsuite/singlemessage/basic/unregistered.json """ + import pytest from autobahn.wamp.message import Unregistered from autobahn.wamp.serializer import create_transport_serializer @@ -24,6 +25,7 @@ # Test Vector Loading # ============================================================================= + @pytest.fixture(scope="module") def unregistered_test_vector(): """Load UNREGISTERED test vector from wamp-proto""" @@ -40,7 +42,10 @@ def unregistered_samples(unregistered_test_vector): # SerDes Tests # ============================================================================= -def test_unregistered_deserialize_from_bytes(serializer_id, unregistered_samples, create_serializer): + +def test_unregistered_deserialize_from_bytes( + serializer_id, unregistered_samples, create_serializer +): """ Test UNREGISTERED deserialization from canonical bytes. @@ -65,7 +70,7 @@ def test_unregistered_deserialize_from_bytes(serializer_id, unregistered_samples if "bytes_hex" in variant: test_bytes = bytes_from_hex(variant["bytes_hex"]) elif "bytes" in variant: - test_bytes = variant["bytes"].encode('utf-8') + test_bytes = variant["bytes"].encode("utf-8") else: continue @@ -77,7 +82,9 @@ def test_unregistered_deserialize_from_bytes(serializer_id, unregistered_samples assert msg.request == sample["expected_attributes"]["request_id"] -def test_unregistered_serialize_to_bytes(serializer_id, unregistered_samples, create_serializer): +def test_unregistered_serialize_to_bytes( + serializer_id, unregistered_samples, create_serializer +): """ Test UNREGISTERED serialization to bytes. @@ -131,7 +138,7 @@ def test_unregistered_roundtrip(serializer_id, unregistered_samples, create_seri if "bytes_hex" in variant: original_bytes = bytes_from_hex(variant["bytes_hex"]) elif "bytes" in variant: - original_bytes = variant["bytes"].encode('utf-8') + original_bytes = variant["bytes"].encode("utf-8") else: continue diff --git a/examples/serdes/tests/test_unsubscribe.py b/examples/serdes/tests/test_unsubscribe.py index dd2b1233c..ac43750f2 100644 --- a/examples/serdes/tests/test_unsubscribe.py +++ b/examples/serdes/tests/test_unsubscribe.py @@ -9,6 +9,7 @@ Uses test vectors from: wamp-proto/testsuite/singlemessage/basic/unsubscribe.json """ + import pytest from autobahn.wamp.message import Unsubscribe from autobahn.wamp.serializer import create_transport_serializer @@ -24,6 +25,7 @@ # Test Vector Loading # ============================================================================= + @pytest.fixture(scope="module") def unsubscribe_test_vector(): """Load UNSUBSCRIBE test vector from wamp-proto""" @@ -40,7 +42,10 @@ def unsubscribe_samples(unsubscribe_test_vector): # SerDes Tests # ============================================================================= -def test_unsubscribe_deserialize_from_bytes(serializer_id, unsubscribe_samples, create_serializer): + +def test_unsubscribe_deserialize_from_bytes( + serializer_id, unsubscribe_samples, create_serializer +): """ Test UNSUBSCRIBE deserialization from canonical bytes. @@ -65,7 +70,7 @@ def test_unsubscribe_deserialize_from_bytes(serializer_id, unsubscribe_samples, if "bytes_hex" in variant: test_bytes = bytes_from_hex(variant["bytes_hex"]) elif "bytes" in variant: - test_bytes = variant["bytes"].encode('utf-8') + test_bytes = variant["bytes"].encode("utf-8") else: continue @@ -78,7 +83,9 @@ def test_unsubscribe_deserialize_from_bytes(serializer_id, unsubscribe_samples, assert msg.subscription == sample["expected_attributes"]["subscription_id"] -def test_unsubscribe_serialize_to_bytes(serializer_id, unsubscribe_samples, create_serializer): +def test_unsubscribe_serialize_to_bytes( + serializer_id, unsubscribe_samples, create_serializer +): """ Test UNSUBSCRIBE serialization to bytes. @@ -97,8 +104,7 @@ def test_unsubscribe_serialize_to_bytes(serializer_id, unsubscribe_samples, crea # Construct message attrs = sample["expected_attributes"] msg = Unsubscribe( - request=attrs["request_id"], - subscription=attrs["subscription_id"] + request=attrs["request_id"], subscription=attrs["subscription_id"] ) # Serialize @@ -135,7 +141,7 @@ def test_unsubscribe_roundtrip(serializer_id, unsubscribe_samples, create_serial if "bytes_hex" in variant: original_bytes = bytes_from_hex(variant["bytes_hex"]) elif "bytes" in variant: - original_bytes = variant["bytes"].encode('utf-8') + original_bytes = variant["bytes"].encode("utf-8") else: continue diff --git a/examples/serdes/tests/test_unsubscribed.py b/examples/serdes/tests/test_unsubscribed.py index bc5a11b94..a674baae8 100644 --- a/examples/serdes/tests/test_unsubscribed.py +++ b/examples/serdes/tests/test_unsubscribed.py @@ -9,6 +9,7 @@ Uses test vectors from: wamp-proto/testsuite/singlemessage/basic/unsubscribed.json """ + import pytest from autobahn.wamp.message import Unsubscribed from autobahn.wamp.serializer import create_transport_serializer @@ -24,6 +25,7 @@ # Test Vector Loading # ============================================================================= + @pytest.fixture(scope="module") def unsubscribed_test_vector(): """Load UNSUBSCRIBED test vector from wamp-proto""" @@ -40,7 +42,10 @@ def unsubscribed_samples(unsubscribed_test_vector): # SerDes Tests # ============================================================================= -def test_unsubscribed_deserialize_from_bytes(serializer_id, unsubscribed_samples, create_serializer): + +def test_unsubscribed_deserialize_from_bytes( + serializer_id, unsubscribed_samples, create_serializer +): """ Test UNSUBSCRIBED deserialization from canonical bytes. @@ -65,7 +70,7 @@ def test_unsubscribed_deserialize_from_bytes(serializer_id, unsubscribed_samples if "bytes_hex" in variant: test_bytes = bytes_from_hex(variant["bytes_hex"]) elif "bytes" in variant: - test_bytes = variant["bytes"].encode('utf-8') + test_bytes = variant["bytes"].encode("utf-8") else: continue @@ -77,7 +82,9 @@ def test_unsubscribed_deserialize_from_bytes(serializer_id, unsubscribed_samples assert msg.request == sample["expected_attributes"]["request_id"] -def test_unsubscribed_serialize_to_bytes(serializer_id, unsubscribed_samples, create_serializer): +def test_unsubscribed_serialize_to_bytes( + serializer_id, unsubscribed_samples, create_serializer +): """ Test UNSUBSCRIBED serialization to bytes. @@ -95,9 +102,7 @@ def test_unsubscribed_serialize_to_bytes(serializer_id, unsubscribed_samples, cr # Construct message attrs = sample["expected_attributes"] - msg = Unsubscribed( - request=attrs["request_id"] - ) + msg = Unsubscribed(request=attrs["request_id"]) # Serialize serialized_bytes, is_binary = serializer.serialize(msg) @@ -133,7 +138,7 @@ def test_unsubscribed_roundtrip(serializer_id, unsubscribed_samples, create_seri if "bytes_hex" in variant: original_bytes = bytes_from_hex(variant["bytes_hex"]) elif "bytes" in variant: - original_bytes = variant["bytes"].encode('utf-8') + original_bytes = variant["bytes"].encode("utf-8") else: continue diff --git a/examples/serdes/tests/test_welcome.py b/examples/serdes/tests/test_welcome.py index ac99fe019..92e408853 100644 --- a/examples/serdes/tests/test_welcome.py +++ b/examples/serdes/tests/test_welcome.py @@ -8,13 +8,11 @@ Uses test vectors from: wamp-proto/testsuite/singlemessage/basic/welcome.json """ + import pytest from autobahn.wamp.message import Welcome from autobahn.wamp.serializer import create_transport_serializer -from autobahn.wamp.role import ( - RoleBrokerFeatures, - RoleDealerFeatures -) +from autobahn.wamp.role import RoleBrokerFeatures, RoleDealerFeatures from .utils import ( load_test_vector, @@ -27,6 +25,7 @@ # Test Vector Loading # ============================================================================= + @pytest.fixture(scope="module") def welcome_test_vector(): """Load WELCOME test vector from wamp-proto""" @@ -43,6 +42,7 @@ def welcome_samples(welcome_test_vector): # Helper Functions # ============================================================================= + def _convert_roles_dict_to_objects(roles_dict): """ Convert a plain dict of roles to Role objects. @@ -67,7 +67,10 @@ def _convert_roles_dict_to_objects(roles_dict): # SerDes Tests # ============================================================================= -def test_welcome_deserialize_from_bytes(serializer_id, welcome_samples, create_serializer): + +def test_welcome_deserialize_from_bytes( + serializer_id, welcome_samples, create_serializer +): """ Test WELCOME deserialization from canonical bytes. @@ -92,7 +95,7 @@ def test_welcome_deserialize_from_bytes(serializer_id, welcome_samples, create_s if "bytes_hex" in variant: test_bytes = bytes_from_hex(variant["bytes_hex"]) elif "bytes" in variant: - test_bytes = variant["bytes"].encode('utf-8') + test_bytes = variant["bytes"].encode("utf-8") else: continue @@ -125,7 +128,7 @@ def test_welcome_serialize_to_bytes(serializer_id, welcome_samples, create_seria attrs = sample["expected_attributes"] msg = Welcome( session=attrs["session_id"], - roles=_convert_roles_dict_to_objects(attrs["roles"]) + roles=_convert_roles_dict_to_objects(attrs["roles"]), ) # Serialize @@ -162,7 +165,7 @@ def test_welcome_roundtrip(serializer_id, welcome_samples, create_serializer): if "bytes_hex" in variant: original_bytes = bytes_from_hex(variant["bytes_hex"]) elif "bytes" in variant: - original_bytes = variant["bytes"].encode('utf-8') + original_bytes = variant["bytes"].encode("utf-8") else: continue diff --git a/examples/serdes/tests/test_yield.py b/examples/serdes/tests/test_yield.py index dcfca4c48..2585ece57 100644 --- a/examples/serdes/tests/test_yield.py +++ b/examples/serdes/tests/test_yield.py @@ -8,6 +8,7 @@ Uses test vectors from: wamp-proto/testsuite/singlemessage/basic/yield.json """ + import pytest from autobahn.wamp.message import Yield from autobahn.wamp.serializer import create_transport_serializer @@ -23,6 +24,7 @@ # Test Vector Loading # ============================================================================= + @pytest.fixture(scope="module") def yield_test_vector(): """Load YIELD test vector from wamp-proto""" @@ -39,6 +41,7 @@ def yield_samples(yield_test_vector): # SerDes Tests # ============================================================================= + def test_yield_deserialize_from_bytes(serializer_id, yield_samples, create_serializer): """ Test YIELD deserialization from canonical bytes. @@ -64,7 +67,7 @@ def test_yield_deserialize_from_bytes(serializer_id, yield_samples, create_seria if "bytes_hex" in variant: test_bytes = bytes_from_hex(variant["bytes_hex"]) elif "bytes" in variant: - test_bytes = variant["bytes"].encode('utf-8') + test_bytes = variant["bytes"].encode("utf-8") else: continue @@ -130,7 +133,7 @@ def test_yield_roundtrip(serializer_id, yield_samples, create_serializer): if "bytes_hex" in variant: original_bytes = bytes_from_hex(variant["bytes_hex"]) elif "bytes" in variant: - original_bytes = variant["bytes"].encode('utf-8') + original_bytes = variant["bytes"].encode("utf-8") else: continue diff --git a/examples/serdes/tests/utils.py b/examples/serdes/tests/utils.py index 6d9cc17b3..de08e2b31 100644 --- a/examples/serdes/tests/utils.py +++ b/examples/serdes/tests/utils.py @@ -1,6 +1,7 @@ """ Utility functions for WAMP serdes tests """ + import json import os from pathlib import Path @@ -69,14 +70,13 @@ def load_test_vector(relative_path: str) -> Dict[str, Any]: for test_vector_path in locations: if test_vector_path.exists(): - with open(test_vector_path, 'r') as f: + with open(test_vector_path, "r") as f: return json.load(f) # Neither location has the file raise FileNotFoundError( f"Test vector not found: {relative_path}\n" - f"Tried locations:\n" + - "\n".join(f" - {loc}" for loc in locations) + f"Tried locations:\n" + "\n".join(f" - {loc}" for loc in locations) ) @@ -88,6 +88,7 @@ def get_serializer_ids() -> List[str]: List of serializer IDs (e.g., ["json", "msgpack", "cbor", ...]) """ from autobahn.wamp.serializer import SERID_TO_OBJSER + return sorted(SERID_TO_OBJSER.keys()) @@ -113,7 +114,7 @@ def validate_message_with_code(msg: Any, validation_code: str) -> None: validation_code: Python code string with assertions """ # Create namespace with msg available - namespace = {'msg': msg} + namespace = {"msg": msg} # Execute validation code try: @@ -123,10 +124,11 @@ def validate_message_with_code(msg: Any, validation_code: str) -> None: if not str(e): # Empty assertion - add context import traceback - tb_lines = traceback.format_exc().split('\n') + + tb_lines = traceback.format_exc().split("\n") # Find the line that failed for line in tb_lines: - if 'assert' in line.lower(): + if "assert" in line.lower(): raise AssertionError(f"Assertion failed: {line.strip()}") from e raise @@ -149,15 +151,14 @@ def construct_message_with_code(construction_code: str) -> Any: exec(construction_code, namespace) # Extract the constructed message - if 'msg' not in namespace: + if "msg" not in namespace: raise ValueError("Construction code must create a variable named 'msg'") - return namespace['msg'] + return namespace["msg"] def matches_any_byte_representation( - actual_bytes: bytes, - expected_variants: List[Dict[str, str]] + actual_bytes: bytes, expected_variants: List[Dict[str, str]] ) -> bool: """ Check if actual bytes match any of the expected byte representations. @@ -172,25 +173,22 @@ def matches_any_byte_representation( True if actual_bytes matches at least one expected variant """ for variant in expected_variants: - if 'bytes_hex' in variant: - expected = bytes_from_hex(variant['bytes_hex']) + if "bytes_hex" in variant: + expected = bytes_from_hex(variant["bytes_hex"]) if actual_bytes == expected: return True - if 'bytes' in variant: + if "bytes" in variant: # For JSON serializer, the 'bytes' field is the string representation # We need to encode it to bytes for comparison - expected = variant['bytes'].encode('utf-8') + expected = variant["bytes"].encode("utf-8") if actual_bytes == expected: return True return False -def validates_with_any_code( - msg: Any, - validation_codes: List[str] -) -> Optional[str]: +def validates_with_any_code(msg: Any, validation_codes: List[str]) -> Optional[str]: """ Try to validate message with any of the validation code blocks. diff --git a/flatbuffers/builder.py b/flatbuffers/builder.py index 71d0eba75..85e863693 100644 --- a/flatbuffers/builder.py +++ b/flatbuffers/builder.py @@ -21,7 +21,7 @@ from .compat import memoryview_type from .compat import NumpyRequiredForThisFeature, import_numpy from .compat import range_func -from .number_types import (SOffsetTFlags, UOffsetTFlags, VOffsetTFlags) +from .number_types import SOffsetTFlags, UOffsetTFlags, VOffsetTFlags np = import_numpy() ## @file @@ -31,64 +31,64 @@ ## @cond FLATBUFFERS_INTERNAL class OffsetArithmeticError(RuntimeError): - """Error caused by an Offset arithmetic error. + """Error caused by an Offset arithmetic error. - Probably caused by bad writing of fields. This is considered an unreachable - situation in normal circumstances. - """ + Probably caused by bad writing of fields. This is considered an unreachable + situation in normal circumstances. + """ - pass + pass class IsNotNestedError(RuntimeError): - """Error caused by using a Builder to write Object data when not inside + """Error caused by using a Builder to write Object data when not inside - an Object. - """ + an Object. + """ - pass + pass class IsNestedError(RuntimeError): - """Error caused by using a Builder to begin an Object when an Object is + """Error caused by using a Builder to begin an Object when an Object is - already being built. - """ + already being built. + """ - pass + pass class StructIsNotInlineError(RuntimeError): - """Error caused by using a Builder to write a Struct at a location that + """Error caused by using a Builder to write a Struct at a location that - is not the current Offset. - """ + is not the current Offset. + """ - pass + pass class BuilderSizeError(RuntimeError): - """Error caused by causing a Builder to exceed the hardcoded limit of 2 + """Error caused by causing a Builder to exceed the hardcoded limit of 2 - gigabytes. - """ + gigabytes. + """ - pass + pass class BuilderNotFinishedError(RuntimeError): - """Error caused by not calling `Finish` before calling `Output`.""" + """Error caused by not calling `Finish` before calling `Output`.""" - pass + pass class EndVectorLengthMismatched(RuntimeError): - """The number of elements passed to EndVector does not match the number + """The number of elements passed to EndVector does not match the number - specified in StartVector. - """ + specified in StartVector. + """ - pass + pass # VtableMetadataFields is the count of metadata fields in each vtable. @@ -97,773 +97,773 @@ class EndVectorLengthMismatched(RuntimeError): class Builder(object): - """A Builder is used to construct one or more FlatBuffers. - - Typically, Builder objects will be used from code generated by the `flatc` - compiler. - - A Builder constructs byte buffers in a last-first manner for simplicity and - performance during reading. - - Internally, a Builder is a state machine for creating FlatBuffer objects. - - It holds the following internal state: - - Bytes: an array of bytes. - - current_vtable: a list of integers. - - vtables: a hash of vtable entries. - - Attributes: - Bytes: The internal `bytearray` for the Builder. - finished: A boolean determining if the Builder has been finalized. - """ - - ## @cond FLATBUFFERS_INTENRAL - __slots__ = ( - "Bytes", - "current_vtable", - "head", - "minalign", - "objectEnd", - "vtables", - "nested", - "forceDefaults", - "finished", - "vectorNumElems", - "sharedStrings", - ) - - """Maximum buffer size constant, in bytes. + """A Builder is used to construct one or more FlatBuffers. - Builder will never allow it's buffer grow over this size. - Currently equals 2Gb. - """ - MAX_BUFFER_SIZE = 2**31 - ## @endcond + Typically, Builder objects will be used from code generated by the `flatc` + compiler. - def __init__(self, initialSize=1024): - """Initializes a Builder of size `initial_size`. + A Builder constructs byte buffers in a last-first manner for simplicity and + performance during reading. - The internal buffer is grown as needed. - """ + Internally, a Builder is a state machine for creating FlatBuffer objects. - if not (0 <= initialSize <= Builder.MAX_BUFFER_SIZE): - msg = "flatbuffers: Cannot create Builder larger than 2 gigabytes." - raise BuilderSizeError(msg) + It holds the following internal state: + - Bytes: an array of bytes. + - current_vtable: a list of integers. + - vtables: a hash of vtable entries. - self.Bytes = bytearray(initialSize) - ## @cond FLATBUFFERS_INTERNAL - self.current_vtable = None - self.head = UOffsetTFlags.py_type(initialSize) - self.minalign = 1 - self.objectEnd = None - self.vtables = {} - self.nested = False - self.forceDefaults = False - self.sharedStrings = {} - ## @endcond - self.finished = False - - def Clear(self) -> None: - ## @cond FLATBUFFERS_INTERNAL - self.current_vtable = None - self.head = UOffsetTFlags.py_type(len(self.Bytes)) - self.minalign = 1 - self.objectEnd = None - self.vtables = {} - self.nested = False - self.forceDefaults = False - self.sharedStrings = {} - self.vectorNumElems = None - ## @endcond - self.finished = False - - def Output(self): - """Return the portion of the buffer that has been used for writing data. - - This is the typical way to access the FlatBuffer data inside the - builder. If you try to access `Builder.Bytes` directly, you would need - to manually index it with `Head()`, since the buffer is constructed - backwards. - - It raises BuilderNotFinishedError if the buffer has not been finished - with `Finish`. + Attributes: + Bytes: The internal `bytearray` for the Builder. + finished: A boolean determining if the Builder has been finalized. """ - if not self.finished: - raise BuilderNotFinishedError() - - return self.Bytes[self.Head() :] - - ## @cond FLATBUFFERS_INTERNAL - def StartObject(self, numfields): - """StartObject initializes bookkeeping for writing a new object.""" - - self.assertNotNested() - - # use 32-bit offsets so that arithmetic doesn't overflow. - self.current_vtable = [0 for _ in range_func(numfields)] - self.objectEnd = self.Offset() - self.nested = True - - def WriteVtable(self): - """WriteVtable serializes the vtable for the current object, if needed. + ## @cond FLATBUFFERS_INTENRAL + __slots__ = ( + "Bytes", + "current_vtable", + "head", + "minalign", + "objectEnd", + "vtables", + "nested", + "forceDefaults", + "finished", + "vectorNumElems", + "sharedStrings", + ) - Before writing out the vtable, this checks pre-existing vtables for - equality to this one. If an equal vtable is found, point the object to - the existing vtable and return. + """Maximum buffer size constant, in bytes. - Because vtable values are sensitive to alignment of object data, not - all logically-equal vtables will be deduplicated. - - A vtable has the following format: - - - * N, where N is the number of fields - in the schema for this type. Includes deprecated fields. - Thus, a vtable is made of 2 + N elements, each VOffsetT bytes wide. - - An object has the following format: - - + - """ - - # Prepend a zero scalar to the object. Later in this function we'll - # write an offset here that points to the object's vtable: - self.PrependSOffsetTRelative(0) - - objectOffset = self.Offset() - - vtKey = [] - trim = True - for elem in reversed(self.current_vtable): - if elem == 0: - if trim: - continue - else: - elem = objectOffset - elem - trim = False - - vtKey.append(elem) - - vtKey = tuple(vtKey) - vt2Offset = self.vtables.get(vtKey) - if vt2Offset is None: - # Did not find a vtable, so write this one to the buffer. - - # Write out the current vtable in reverse , because - # serialization occurs in last-first order: - i = len(self.current_vtable) - 1 - trailing = 0 - trim = True - while i >= 0: - off = 0 - elem = self.current_vtable[i] - i -= 1 - - if elem == 0: - if trim: - trailing += 1 - continue - else: - # Forward reference to field; - # use 32bit number to ensure no overflow: - off = objectOffset - elem - trim = False - - self.PrependVOffsetT(off) - - # The two metadata fields are written last. - - # First, store the object bytesize: - objectSize = UOffsetTFlags.py_type(objectOffset - self.objectEnd) - self.PrependVOffsetT(VOffsetTFlags.py_type(objectSize)) - - # Second, store the vtable bytesize: - vBytes = len(self.current_vtable) - trailing + VtableMetadataFields - vBytes *= N.VOffsetTFlags.bytewidth - self.PrependVOffsetT(VOffsetTFlags.py_type(vBytes)) - - # Next, write the offset to the new vtable in the - # already-allocated SOffsetT at the beginning of this object: - objectStart = SOffsetTFlags.py_type(len(self.Bytes) - objectOffset) - encode.Write( - packer.soffset, - self.Bytes, - objectStart, - SOffsetTFlags.py_type(self.Offset() - objectOffset), - ) - - # Finally, store this vtable in memory for future - # deduplication: - self.vtables[vtKey] = self.Offset() - else: - # Found a duplicate vtable. - objectStart = SOffsetTFlags.py_type(len(self.Bytes) - objectOffset) - self.head = UOffsetTFlags.py_type(objectStart) - - # Write the offset to the found vtable in the - # already-allocated SOffsetT at the beginning of this object: - encode.Write( - packer.soffset, - self.Bytes, - self.Head(), - SOffsetTFlags.py_type(vt2Offset - objectOffset), - ) - - self.current_vtable = None - return objectOffset - - def EndObject(self): - """EndObject writes data necessary to finish object construction.""" - self.assertNested() - self.nested = False - return self.WriteVtable() - - def growByteBuffer(self): - """Doubles the size of the byteslice, and copies the old data towards - - the end of the new buffer (since we build the buffer backwards). + Builder will never allow it's buffer grow over this size. + Currently equals 2Gb. """ - if len(self.Bytes) == Builder.MAX_BUFFER_SIZE: - msg = "flatbuffers: cannot grow buffer beyond 2 gigabytes" - raise BuilderSizeError(msg) - - newSize = min(len(self.Bytes) * 2, Builder.MAX_BUFFER_SIZE) - if newSize == 0: - newSize = 1 - bytes2 = bytearray(newSize) - bytes2[newSize - len(self.Bytes) :] = self.Bytes - self.Bytes = bytes2 - - ## @endcond + MAX_BUFFER_SIZE = 2**31 + ## @endcond - def Head(self): - """Get the start of useful data in the underlying byte buffer. + def __init__(self, initialSize=1024): + """Initializes a Builder of size `initial_size`. + + The internal buffer is grown as needed. + """ + + if not (0 <= initialSize <= Builder.MAX_BUFFER_SIZE): + msg = "flatbuffers: Cannot create Builder larger than 2 gigabytes." + raise BuilderSizeError(msg) + + self.Bytes = bytearray(initialSize) + ## @cond FLATBUFFERS_INTERNAL + self.current_vtable = None + self.head = UOffsetTFlags.py_type(initialSize) + self.minalign = 1 + self.objectEnd = None + self.vtables = {} + self.nested = False + self.forceDefaults = False + self.sharedStrings = {} + ## @endcond + self.finished = False + + def Clear(self) -> None: + ## @cond FLATBUFFERS_INTERNAL + self.current_vtable = None + self.head = UOffsetTFlags.py_type(len(self.Bytes)) + self.minalign = 1 + self.objectEnd = None + self.vtables = {} + self.nested = False + self.forceDefaults = False + self.sharedStrings = {} + self.vectorNumElems = None + ## @endcond + self.finished = False + + def Output(self): + """Return the portion of the buffer that has been used for writing data. + + This is the typical way to access the FlatBuffer data inside the + builder. If you try to access `Builder.Bytes` directly, you would need + to manually index it with `Head()`, since the buffer is constructed + backwards. + + It raises BuilderNotFinishedError if the buffer has not been finished + with `Finish`. + """ + + if not self.finished: + raise BuilderNotFinishedError() + + return self.Bytes[self.Head() :] - Note: unlike other functions, this value is interpreted as from the - left. - """ ## @cond FLATBUFFERS_INTERNAL - return self.head - ## @endcond + def StartObject(self, numfields): + """StartObject initializes bookkeeping for writing a new object.""" + + self.assertNotNested() + + # use 32-bit offsets so that arithmetic doesn't overflow. + self.current_vtable = [0 for _ in range_func(numfields)] + self.objectEnd = self.Offset() + self.nested = True + + def WriteVtable(self): + """WriteVtable serializes the vtable for the current object, if needed. + + Before writing out the vtable, this checks pre-existing vtables for + equality to this one. If an equal vtable is found, point the object to + the existing vtable and return. + + Because vtable values are sensitive to alignment of object data, not + all logically-equal vtables will be deduplicated. + + A vtable has the following format: + + + * N, where N is the number of fields + in the schema for this type. Includes deprecated fields. + Thus, a vtable is made of 2 + N elements, each VOffsetT bytes wide. + + An object has the following format: + + + + """ + + # Prepend a zero scalar to the object. Later in this function we'll + # write an offset here that points to the object's vtable: + self.PrependSOffsetTRelative(0) + + objectOffset = self.Offset() + + vtKey = [] + trim = True + for elem in reversed(self.current_vtable): + if elem == 0: + if trim: + continue + else: + elem = objectOffset - elem + trim = False + + vtKey.append(elem) + + vtKey = tuple(vtKey) + vt2Offset = self.vtables.get(vtKey) + if vt2Offset is None: + # Did not find a vtable, so write this one to the buffer. + + # Write out the current vtable in reverse , because + # serialization occurs in last-first order: + i = len(self.current_vtable) - 1 + trailing = 0 + trim = True + while i >= 0: + off = 0 + elem = self.current_vtable[i] + i -= 1 + + if elem == 0: + if trim: + trailing += 1 + continue + else: + # Forward reference to field; + # use 32bit number to ensure no overflow: + off = objectOffset - elem + trim = False + + self.PrependVOffsetT(off) + + # The two metadata fields are written last. + + # First, store the object bytesize: + objectSize = UOffsetTFlags.py_type(objectOffset - self.objectEnd) + self.PrependVOffsetT(VOffsetTFlags.py_type(objectSize)) + + # Second, store the vtable bytesize: + vBytes = len(self.current_vtable) - trailing + VtableMetadataFields + vBytes *= N.VOffsetTFlags.bytewidth + self.PrependVOffsetT(VOffsetTFlags.py_type(vBytes)) + + # Next, write the offset to the new vtable in the + # already-allocated SOffsetT at the beginning of this object: + objectStart = SOffsetTFlags.py_type(len(self.Bytes) - objectOffset) + encode.Write( + packer.soffset, + self.Bytes, + objectStart, + SOffsetTFlags.py_type(self.Offset() - objectOffset), + ) + + # Finally, store this vtable in memory for future + # deduplication: + self.vtables[vtKey] = self.Offset() + else: + # Found a duplicate vtable. + objectStart = SOffsetTFlags.py_type(len(self.Bytes) - objectOffset) + self.head = UOffsetTFlags.py_type(objectStart) + + # Write the offset to the found vtable in the + # already-allocated SOffsetT at the beginning of this object: + encode.Write( + packer.soffset, + self.Bytes, + self.Head(), + SOffsetTFlags.py_type(vt2Offset - objectOffset), + ) + + self.current_vtable = None + return objectOffset + + def EndObject(self): + """EndObject writes data necessary to finish object construction.""" + self.assertNested() + self.nested = False + return self.WriteVtable() + + def growByteBuffer(self): + """Doubles the size of the byteslice, and copies the old data towards + + the end of the new buffer (since we build the buffer backwards). + """ + if len(self.Bytes) == Builder.MAX_BUFFER_SIZE: + msg = "flatbuffers: cannot grow buffer beyond 2 gigabytes" + raise BuilderSizeError(msg) + + newSize = min(len(self.Bytes) * 2, Builder.MAX_BUFFER_SIZE) + if newSize == 0: + newSize = 1 + bytes2 = bytearray(newSize) + bytes2[newSize - len(self.Bytes) :] = self.Bytes + self.Bytes = bytes2 - ## @cond FLATBUFFERS_INTERNAL - def Offset(self): - """Offset relative to the end of the buffer.""" - return UOffsetTFlags.py_type(len(self.Bytes) - self.Head()) + ## @endcond - def Pad(self, n): - """Pad places zeros at the current offset.""" - for i in range_func(n): - self.Place(0, N.Uint8Flags) + def Head(self): + """Get the start of useful data in the underlying byte buffer. - def Prep(self, size, additionalBytes): - """Prep prepares to write an element of `size` after `additional_bytes` + Note: unlike other functions, this value is interpreted as from the + left. + """ + ## @cond FLATBUFFERS_INTERNAL + return self.head + ## @endcond - have been written, e.g. if you write a string, you need to align - such the int length field is aligned to SizeInt32, and the string - data follows it directly. - If all you need to do is align, `additionalBytes` will be 0. - """ + ## @cond FLATBUFFERS_INTERNAL + def Offset(self): + """Offset relative to the end of the buffer.""" + return UOffsetTFlags.py_type(len(self.Bytes) - self.Head()) + + def Pad(self, n): + """Pad places zeros at the current offset.""" + for i in range_func(n): + self.Place(0, N.Uint8Flags) + + def Prep(self, size, additionalBytes): + """Prep prepares to write an element of `size` after `additional_bytes` + + have been written, e.g. if you write a string, you need to align + such the int length field is aligned to SizeInt32, and the string + data follows it directly. + If all you need to do is align, `additionalBytes` will be 0. + """ + + # Track the biggest thing we've ever aligned to. + if size > self.minalign: + self.minalign = size + + # Find the amount of alignment needed such that `size` is properly + # aligned after `additionalBytes`: + alignSize = (~(len(self.Bytes) - self.Head() + additionalBytes)) + 1 + alignSize &= size - 1 + + # Reallocate the buffer if needed: + while self.Head() < alignSize + size + additionalBytes: + oldBufSize = len(self.Bytes) + self.growByteBuffer() + updated_head = self.head + len(self.Bytes) - oldBufSize + self.head = UOffsetTFlags.py_type(updated_head) + self.Pad(alignSize) + + def PrependSOffsetTRelative(self, off): + """PrependSOffsetTRelative prepends an SOffsetT, relative to where it + + will be written. + """ + + # Ensure alignment is already done: + self.Prep(N.SOffsetTFlags.bytewidth, 0) + if not (off <= self.Offset()): + msg = "flatbuffers: Offset arithmetic error." + raise OffsetArithmeticError(msg) + off2 = self.Offset() - off + N.SOffsetTFlags.bytewidth + self.PlaceSOffsetT(off2) - # Track the biggest thing we've ever aligned to. - if size > self.minalign: - self.minalign = size + ## @endcond - # Find the amount of alignment needed such that `size` is properly - # aligned after `additionalBytes`: - alignSize = (~(len(self.Bytes) - self.Head() + additionalBytes)) + 1 - alignSize &= size - 1 + def PrependUOffsetTRelative(self, off): + """Prepends an unsigned offset into vector data, relative to where it - # Reallocate the buffer if needed: - while self.Head() < alignSize + size + additionalBytes: - oldBufSize = len(self.Bytes) - self.growByteBuffer() - updated_head = self.head + len(self.Bytes) - oldBufSize - self.head = UOffsetTFlags.py_type(updated_head) - self.Pad(alignSize) + will be written. + """ - def PrependSOffsetTRelative(self, off): - """PrependSOffsetTRelative prepends an SOffsetT, relative to where it + # Ensure alignment is already done: + self.Prep(N.UOffsetTFlags.bytewidth, 0) + if not (off <= self.Offset()): + msg = "flatbuffers: Offset arithmetic error." + raise OffsetArithmeticError(msg) + off2 = self.Offset() - off + N.UOffsetTFlags.bytewidth + self.PlaceUOffsetT(off2) - will be written. - """ - - # Ensure alignment is already done: - self.Prep(N.SOffsetTFlags.bytewidth, 0) - if not (off <= self.Offset()): - msg = "flatbuffers: Offset arithmetic error." - raise OffsetArithmeticError(msg) - off2 = self.Offset() - off + N.SOffsetTFlags.bytewidth - self.PlaceSOffsetT(off2) + ## @cond FLATBUFFERS_INTERNAL + def StartVector(self, elemSize, numElems, alignment): + """StartVector initializes bookkeeping for writing a new vector. - ## @endcond + A vector has the following format: + - + - +, where T is the type of elements of this vector. + """ - def PrependUOffsetTRelative(self, off): - """Prepends an unsigned offset into vector data, relative to where it + self.assertNotNested() + self.nested = True + self.vectorNumElems = numElems + self.Prep(N.Uint32Flags.bytewidth, elemSize * numElems) + self.Prep(alignment, elemSize * numElems) # In case alignment > int. + return self.Offset() - will be written. - """ + ## @endcond - # Ensure alignment is already done: - self.Prep(N.UOffsetTFlags.bytewidth, 0) - if not (off <= self.Offset()): - msg = "flatbuffers: Offset arithmetic error." - raise OffsetArithmeticError(msg) - off2 = self.Offset() - off + N.UOffsetTFlags.bytewidth - self.PlaceUOffsetT(off2) - - ## @cond FLATBUFFERS_INTERNAL - def StartVector(self, elemSize, numElems, alignment): - """StartVector initializes bookkeeping for writing a new vector. - - A vector has the following format: - - - - +, where T is the type of elements of this vector. - """ + def EndVector(self, numElems=None): + """EndVector writes data necessary to finish vector construction.""" - self.assertNotNested() - self.nested = True - self.vectorNumElems = numElems - self.Prep(N.Uint32Flags.bytewidth, elemSize * numElems) - self.Prep(alignment, elemSize * numElems) # In case alignment > int. - return self.Offset() + self.assertNested() + ## @cond FLATBUFFERS_INTERNAL + self.nested = False + ## @endcond - ## @endcond + if numElems: + warnings.warn("numElems is deprecated.", DeprecationWarning, stacklevel=2) + if numElems != self.vectorNumElems: + raise EndVectorLengthMismatched() - def EndVector(self, numElems=None): - """EndVector writes data necessary to finish vector construction.""" + # we already made space for this, so write without PrependUint32 + self.PlaceUOffsetT(self.vectorNumElems) + self.vectorNumElems = None + return self.Offset() - self.assertNested() - ## @cond FLATBUFFERS_INTERNAL - self.nested = False - ## @endcond + def CreateSharedString(self, s, encoding="utf-8", errors="strict"): + """CreateSharedString checks if the string is already written to the buffer - if numElems: - warnings.warn("numElems is deprecated.", DeprecationWarning, stacklevel=2) - if numElems != self.vectorNumElems: - raise EndVectorLengthMismatched() + before calling CreateString. + """ - # we already made space for this, so write without PrependUint32 - self.PlaceUOffsetT(self.vectorNumElems) - self.vectorNumElems = None - return self.Offset() + if s in self.sharedStrings: + return self.sharedStrings[s] - def CreateSharedString(self, s, encoding="utf-8", errors="strict"): - """CreateSharedString checks if the string is already written to the buffer + off = self.CreateString(s, encoding, errors) + self.sharedStrings[s] = off - before calling CreateString. - """ + return off - if s in self.sharedStrings: - return self.sharedStrings[s] + def CreateString(self, s, encoding="utf-8", errors="strict"): + """CreateString writes a null-terminated byte string as a vector.""" - off = self.CreateString(s, encoding, errors) - self.sharedStrings[s] = off + self.assertNotNested() + ## @cond FLATBUFFERS_INTERNAL + self.nested = True + ## @endcond - return off + if isinstance(s, compat.string_types): + x = s.encode(encoding, errors) + elif isinstance(s, compat.binary_types): + x = s + else: + raise TypeError("non-string passed to CreateString") - def CreateString(self, s, encoding="utf-8", errors="strict"): - """CreateString writes a null-terminated byte string as a vector.""" + self.Prep(N.UOffsetTFlags.bytewidth, (len(x) + 1) * N.Uint8Flags.bytewidth) + self.Place(0, N.Uint8Flags) - self.assertNotNested() - ## @cond FLATBUFFERS_INTERNAL - self.nested = True - ## @endcond + l = UOffsetTFlags.py_type(len(s)) + ## @cond FLATBUFFERS_INTERNAL + self.head = UOffsetTFlags.py_type(self.Head() - l) + ## @endcond + self.Bytes[self.Head() : self.Head() + l] = x - if isinstance(s, compat.string_types): - x = s.encode(encoding, errors) - elif isinstance(s, compat.binary_types): - x = s - else: - raise TypeError("non-string passed to CreateString") + self.vectorNumElems = len(x) + return self.EndVector() - self.Prep(N.UOffsetTFlags.bytewidth, (len(x) + 1) * N.Uint8Flags.bytewidth) - self.Place(0, N.Uint8Flags) + def CreateByteVector(self, x): + """CreateString writes a byte vector.""" - l = UOffsetTFlags.py_type(len(s)) - ## @cond FLATBUFFERS_INTERNAL - self.head = UOffsetTFlags.py_type(self.Head() - l) - ## @endcond - self.Bytes[self.Head() : self.Head() + l] = x + self.assertNotNested() + ## @cond FLATBUFFERS_INTERNAL + self.nested = True + ## @endcond - self.vectorNumElems = len(x) - return self.EndVector() + if not isinstance(x, compat.binary_types): + raise TypeError("non-byte vector passed to CreateByteVector") - def CreateByteVector(self, x): - """CreateString writes a byte vector.""" + self.Prep(N.UOffsetTFlags.bytewidth, len(x) * N.Uint8Flags.bytewidth) - self.assertNotNested() - ## @cond FLATBUFFERS_INTERNAL - self.nested = True - ## @endcond + l = UOffsetTFlags.py_type(len(x)) + ## @cond FLATBUFFERS_INTERNAL + self.head = UOffsetTFlags.py_type(self.Head() - l) + ## @endcond + self.Bytes[self.Head() : self.Head() + l] = x - if not isinstance(x, compat.binary_types): - raise TypeError("non-byte vector passed to CreateByteVector") + self.vectorNumElems = len(x) + return self.EndVector() - self.Prep(N.UOffsetTFlags.bytewidth, len(x) * N.Uint8Flags.bytewidth) + def CreateNumpyVector(self, x): + """CreateNumpyVector writes a numpy array into the buffer.""" - l = UOffsetTFlags.py_type(len(x)) - ## @cond FLATBUFFERS_INTERNAL - self.head = UOffsetTFlags.py_type(self.Head() - l) - ## @endcond - self.Bytes[self.Head() : self.Head() + l] = x + if np is None: + # Numpy is required for this feature + raise NumpyRequiredForThisFeature("Numpy was not found.") - self.vectorNumElems = len(x) - return self.EndVector() + if not isinstance(x, np.ndarray): + raise TypeError("non-numpy-ndarray passed to CreateNumpyVector") - def CreateNumpyVector(self, x): - """CreateNumpyVector writes a numpy array into the buffer.""" + if x.dtype.kind not in ["b", "i", "u", "f"]: + raise TypeError("numpy-ndarray holds elements of unsupported datatype") - if np is None: - # Numpy is required for this feature - raise NumpyRequiredForThisFeature("Numpy was not found.") + if x.ndim > 1: + raise TypeError("multidimensional-ndarray passed to CreateNumpyVector") - if not isinstance(x, np.ndarray): - raise TypeError("non-numpy-ndarray passed to CreateNumpyVector") + self.StartVector(x.itemsize, x.size, x.dtype.alignment) - if x.dtype.kind not in ["b", "i", "u", "f"]: - raise TypeError("numpy-ndarray holds elements of unsupported datatype") + # Ensure little endian byte ordering + if x.dtype.str[0] == "<": + x_lend = x + else: + x_lend = x.byteswap(inplace=False) - if x.ndim > 1: - raise TypeError("multidimensional-ndarray passed to CreateNumpyVector") + # Calculate total length + l = UOffsetTFlags.py_type(x_lend.itemsize * x_lend.size) + ## @cond FLATBUFFERS_INTERNAL + self.head = UOffsetTFlags.py_type(self.Head() - l) + ## @endcond - self.StartVector(x.itemsize, x.size, x.dtype.alignment) + # tobytes ensures c_contiguous ordering + self.Bytes[self.Head() : self.Head() + l] = x_lend.tobytes(order="C") - # Ensure little endian byte ordering - if x.dtype.str[0] == "<": - x_lend = x - else: - x_lend = x.byteswap(inplace=False) + self.vectorNumElems = x.size + return self.EndVector() - # Calculate total length - l = UOffsetTFlags.py_type(x_lend.itemsize * x_lend.size) ## @cond FLATBUFFERS_INTERNAL - self.head = UOffsetTFlags.py_type(self.Head() - l) - ## @endcond + def assertNested(self): + """Check that we are in the process of building an object.""" - # tobytes ensures c_contiguous ordering - self.Bytes[self.Head() : self.Head() + l] = x_lend.tobytes(order="C") + if not self.nested: + raise IsNotNestedError() - self.vectorNumElems = x.size - return self.EndVector() + def assertNotNested(self): + """Check that no other objects are being built while making this object. - ## @cond FLATBUFFERS_INTERNAL - def assertNested(self): - """Check that we are in the process of building an object.""" + If not, raise an exception. + """ - if not self.nested: - raise IsNotNestedError() + if self.nested: + raise IsNestedError() - def assertNotNested(self): - """Check that no other objects are being built while making this object. + def assertStructIsInline(self, obj): + """Structs are always stored inline, so need to be created right - If not, raise an exception. - """ + where they are used. You'll get this error if you created it + elsewhere. + """ - if self.nested: - raise IsNestedError() + N.enforce_number(obj, N.UOffsetTFlags) + if obj != self.Offset(): + msg = ( + "flatbuffers: Tried to write a Struct at an Offset that " + "is different from the current Offset of the Builder." + ) + raise StructIsNotInlineError(msg) - def assertStructIsInline(self, obj): - """Structs are always stored inline, so need to be created right + def Slot(self, slotnum): + """Slot sets the vtable key `voffset` to the current location in the - where they are used. You'll get this error if you created it - elsewhere. - """ + buffer. + """ + self.assertNested() + self.current_vtable[slotnum] = self.Offset() - N.enforce_number(obj, N.UOffsetTFlags) - if obj != self.Offset(): - msg = ( - "flatbuffers: Tried to write a Struct at an Offset that " - "is different from the current Offset of the Builder." - ) - raise StructIsNotInlineError(msg) + ## @endcond - def Slot(self, slotnum): - """Slot sets the vtable key `voffset` to the current location in the + def __Finish(self, rootTable, sizePrefix, file_identifier=None): + """Finish finalizes a buffer, pointing to the given `rootTable`.""" + N.enforce_number(rootTable, N.UOffsetTFlags) + + prepSize = N.UOffsetTFlags.bytewidth + if file_identifier is not None: + prepSize += N.Int32Flags.bytewidth + if sizePrefix: + prepSize += N.Int32Flags.bytewidth + self.Prep(self.minalign, prepSize) + + if file_identifier is not None: + self.Prep(N.UOffsetTFlags.bytewidth, encode.FILE_IDENTIFIER_LENGTH) + + # Convert bytes object file_identifier to an array of 4 8-bit integers, + # and use big-endian to enforce size compliance. + # https://docs.python.org/2/library/struct.html#format-characters + file_identifier = N.struct.unpack(">BBBB", file_identifier) + for i in range(encode.FILE_IDENTIFIER_LENGTH - 1, -1, -1): + # Place the bytes of the file_identifer in reverse order: + self.Place(file_identifier[i], N.Uint8Flags) + + self.PrependUOffsetTRelative(rootTable) + if sizePrefix: + size = len(self.Bytes) - self.Head() + N.enforce_number(size, N.Int32Flags) + self.PrependInt32(size) + self.finished = True + return self.Head() + + def Finish(self, rootTable, file_identifier=None): + """Finish finalizes a buffer, pointing to the given `rootTable`.""" + return self.__Finish(rootTable, False, file_identifier=file_identifier) + + def FinishSizePrefixed(self, rootTable, file_identifier=None): + """Finish finalizes a buffer, pointing to the given `rootTable`, + + with the size prefixed. + """ + return self.__Finish(rootTable, True, file_identifier=file_identifier) - buffer. - """ - self.assertNested() - self.current_vtable[slotnum] = self.Offset() - - ## @endcond - - def __Finish(self, rootTable, sizePrefix, file_identifier=None): - """Finish finalizes a buffer, pointing to the given `rootTable`.""" - N.enforce_number(rootTable, N.UOffsetTFlags) - - prepSize = N.UOffsetTFlags.bytewidth - if file_identifier is not None: - prepSize += N.Int32Flags.bytewidth - if sizePrefix: - prepSize += N.Int32Flags.bytewidth - self.Prep(self.minalign, prepSize) - - if file_identifier is not None: - self.Prep(N.UOffsetTFlags.bytewidth, encode.FILE_IDENTIFIER_LENGTH) - - # Convert bytes object file_identifier to an array of 4 8-bit integers, - # and use big-endian to enforce size compliance. - # https://docs.python.org/2/library/struct.html#format-characters - file_identifier = N.struct.unpack(">BBBB", file_identifier) - for i in range(encode.FILE_IDENTIFIER_LENGTH - 1, -1, -1): - # Place the bytes of the file_identifer in reverse order: - self.Place(file_identifier[i], N.Uint8Flags) - - self.PrependUOffsetTRelative(rootTable) - if sizePrefix: - size = len(self.Bytes) - self.Head() - N.enforce_number(size, N.Int32Flags) - self.PrependInt32(size) - self.finished = True - return self.Head() - - def Finish(self, rootTable, file_identifier=None): - """Finish finalizes a buffer, pointing to the given `rootTable`.""" - return self.__Finish(rootTable, False, file_identifier=file_identifier) - - def FinishSizePrefixed(self, rootTable, file_identifier=None): - """Finish finalizes a buffer, pointing to the given `rootTable`, - - with the size prefixed. - """ - return self.__Finish(rootTable, True, file_identifier=file_identifier) + ## @cond FLATBUFFERS_INTERNAL + def Prepend(self, flags, off): + self.Prep(flags.bytewidth, 0) + self.Place(off, flags) - ## @cond FLATBUFFERS_INTERNAL - def Prepend(self, flags, off): - self.Prep(flags.bytewidth, 0) - self.Place(off, flags) + def PrependSlot(self, flags, o, x, d): + if x is not None: + N.enforce_number(x, flags) + if d is not None: + N.enforce_number(d, flags) + if x != d or (self.forceDefaults and d is not None): + self.Prepend(flags, x) + self.Slot(o) - def PrependSlot(self, flags, o, x, d): - if x is not None: - N.enforce_number(x, flags) - if d is not None: - N.enforce_number(d, flags) - if x != d or (self.forceDefaults and d is not None): - self.Prepend(flags, x) - self.Slot(o) + def PrependBoolSlot(self, *args): + self.PrependSlot(N.BoolFlags, *args) - def PrependBoolSlot(self, *args): - self.PrependSlot(N.BoolFlags, *args) + def PrependByteSlot(self, *args): + self.PrependSlot(N.Uint8Flags, *args) - def PrependByteSlot(self, *args): - self.PrependSlot(N.Uint8Flags, *args) + def PrependUint8Slot(self, *args): + self.PrependSlot(N.Uint8Flags, *args) - def PrependUint8Slot(self, *args): - self.PrependSlot(N.Uint8Flags, *args) + def PrependUint16Slot(self, *args): + self.PrependSlot(N.Uint16Flags, *args) - def PrependUint16Slot(self, *args): - self.PrependSlot(N.Uint16Flags, *args) + def PrependUint32Slot(self, *args): + self.PrependSlot(N.Uint32Flags, *args) - def PrependUint32Slot(self, *args): - self.PrependSlot(N.Uint32Flags, *args) + def PrependUint64Slot(self, *args): + self.PrependSlot(N.Uint64Flags, *args) - def PrependUint64Slot(self, *args): - self.PrependSlot(N.Uint64Flags, *args) + def PrependInt8Slot(self, *args): + self.PrependSlot(N.Int8Flags, *args) - def PrependInt8Slot(self, *args): - self.PrependSlot(N.Int8Flags, *args) + def PrependInt16Slot(self, *args): + self.PrependSlot(N.Int16Flags, *args) - def PrependInt16Slot(self, *args): - self.PrependSlot(N.Int16Flags, *args) + def PrependInt32Slot(self, *args): + self.PrependSlot(N.Int32Flags, *args) - def PrependInt32Slot(self, *args): - self.PrependSlot(N.Int32Flags, *args) + def PrependInt64Slot(self, *args): + self.PrependSlot(N.Int64Flags, *args) - def PrependInt64Slot(self, *args): - self.PrependSlot(N.Int64Flags, *args) + def PrependFloat32Slot(self, *args): + self.PrependSlot(N.Float32Flags, *args) - def PrependFloat32Slot(self, *args): - self.PrependSlot(N.Float32Flags, *args) + def PrependFloat64Slot(self, *args): + self.PrependSlot(N.Float64Flags, *args) - def PrependFloat64Slot(self, *args): - self.PrependSlot(N.Float64Flags, *args) + def PrependUOffsetTRelativeSlot(self, o, x, d): + """PrependUOffsetTRelativeSlot prepends an UOffsetT onto the object at - def PrependUOffsetTRelativeSlot(self, o, x, d): - """PrependUOffsetTRelativeSlot prepends an UOffsetT onto the object at + vtable slot `o`. If value `x` equals default `d`, then the slot will + be set to zero and no other data will be written. + """ - vtable slot `o`. If value `x` equals default `d`, then the slot will - be set to zero and no other data will be written. - """ + if x != d or self.forceDefaults: + self.PrependUOffsetTRelative(x) + self.Slot(o) - if x != d or self.forceDefaults: - self.PrependUOffsetTRelative(x) - self.Slot(o) + def PrependStructSlot(self, v, x, d): + """PrependStructSlot prepends a struct onto the object at vtable slot `o`. - def PrependStructSlot(self, v, x, d): - """PrependStructSlot prepends a struct onto the object at vtable slot `o`. - - Structs are stored inline, so nothing additional is being added. In - generated code, `d` is always 0. - """ + Structs are stored inline, so nothing additional is being added. In + generated code, `d` is always 0. + """ - N.enforce_number(d, N.UOffsetTFlags) - if x != d: - self.assertStructIsInline(x) - self.Slot(v) + N.enforce_number(d, N.UOffsetTFlags) + if x != d: + self.assertStructIsInline(x) + self.Slot(v) - ## @endcond + ## @endcond - def PrependBool(self, x): - """Prepend a `bool` to the Builder buffer. + def PrependBool(self, x): + """Prepend a `bool` to the Builder buffer. - Note: aligns and checks for space. - """ - self.Prepend(N.BoolFlags, x) + Note: aligns and checks for space. + """ + self.Prepend(N.BoolFlags, x) - def PrependByte(self, x): - """Prepend a `byte` to the Builder buffer. + def PrependByte(self, x): + """Prepend a `byte` to the Builder buffer. - Note: aligns and checks for space. - """ - self.Prepend(N.Uint8Flags, x) + Note: aligns and checks for space. + """ + self.Prepend(N.Uint8Flags, x) - def PrependUint8(self, x): - """Prepend an `uint8` to the Builder buffer. + def PrependUint8(self, x): + """Prepend an `uint8` to the Builder buffer. - Note: aligns and checks for space. - """ - self.Prepend(N.Uint8Flags, x) + Note: aligns and checks for space. + """ + self.Prepend(N.Uint8Flags, x) - def PrependUint16(self, x): - """Prepend an `uint16` to the Builder buffer. + def PrependUint16(self, x): + """Prepend an `uint16` to the Builder buffer. - Note: aligns and checks for space. - """ - self.Prepend(N.Uint16Flags, x) + Note: aligns and checks for space. + """ + self.Prepend(N.Uint16Flags, x) - def PrependUint32(self, x): - """Prepend an `uint32` to the Builder buffer. + def PrependUint32(self, x): + """Prepend an `uint32` to the Builder buffer. - Note: aligns and checks for space. - """ - self.Prepend(N.Uint32Flags, x) + Note: aligns and checks for space. + """ + self.Prepend(N.Uint32Flags, x) - def PrependUint64(self, x): - """Prepend an `uint64` to the Builder buffer. + def PrependUint64(self, x): + """Prepend an `uint64` to the Builder buffer. - Note: aligns and checks for space. - """ - self.Prepend(N.Uint64Flags, x) + Note: aligns and checks for space. + """ + self.Prepend(N.Uint64Flags, x) - def PrependInt8(self, x): - """Prepend an `int8` to the Builder buffer. + def PrependInt8(self, x): + """Prepend an `int8` to the Builder buffer. - Note: aligns and checks for space. - """ - self.Prepend(N.Int8Flags, x) + Note: aligns and checks for space. + """ + self.Prepend(N.Int8Flags, x) - def PrependInt16(self, x): - """Prepend an `int16` to the Builder buffer. + def PrependInt16(self, x): + """Prepend an `int16` to the Builder buffer. - Note: aligns and checks for space. - """ - self.Prepend(N.Int16Flags, x) + Note: aligns and checks for space. + """ + self.Prepend(N.Int16Flags, x) - def PrependInt32(self, x): - """Prepend an `int32` to the Builder buffer. + def PrependInt32(self, x): + """Prepend an `int32` to the Builder buffer. - Note: aligns and checks for space. - """ - self.Prepend(N.Int32Flags, x) + Note: aligns and checks for space. + """ + self.Prepend(N.Int32Flags, x) - def PrependInt64(self, x): - """Prepend an `int64` to the Builder buffer. + def PrependInt64(self, x): + """Prepend an `int64` to the Builder buffer. - Note: aligns and checks for space. - """ - self.Prepend(N.Int64Flags, x) + Note: aligns and checks for space. + """ + self.Prepend(N.Int64Flags, x) - def PrependFloat32(self, x): - """Prepend a `float32` to the Builder buffer. + def PrependFloat32(self, x): + """Prepend a `float32` to the Builder buffer. - Note: aligns and checks for space. - """ - self.Prepend(N.Float32Flags, x) + Note: aligns and checks for space. + """ + self.Prepend(N.Float32Flags, x) - def PrependFloat64(self, x): - """Prepend a `float64` to the Builder buffer. + def PrependFloat64(self, x): + """Prepend a `float64` to the Builder buffer. - Note: aligns and checks for space. - """ - self.Prepend(N.Float64Flags, x) + Note: aligns and checks for space. + """ + self.Prepend(N.Float64Flags, x) - def ForceDefaults(self, forceDefaults): - """In order to save space, fields that are set to their default value + def ForceDefaults(self, forceDefaults): + """In order to save space, fields that are set to their default value - don't get serialized into the buffer. Forcing defaults provides a - way to manually disable this optimization. When set to `True`, will - always serialize default values. - """ - self.forceDefaults = forceDefaults + don't get serialized into the buffer. Forcing defaults provides a + way to manually disable this optimization. When set to `True`, will + always serialize default values. + """ + self.forceDefaults = forceDefaults - ############################################################## + ############################################################## - ## @cond FLATBUFFERS_INTERNAL - def PrependVOffsetT(self, x): - self.Prepend(N.VOffsetTFlags, x) + ## @cond FLATBUFFERS_INTERNAL + def PrependVOffsetT(self, x): + self.Prepend(N.VOffsetTFlags, x) - def Place(self, x, flags): - """Place prepends a value specified by `flags` to the Builder, + def Place(self, x, flags): + """Place prepends a value specified by `flags` to the Builder, - without checking for available space. - """ + without checking for available space. + """ - N.enforce_number(x, flags) - self.head = self.head - flags.bytewidth - encode.Write(flags.packer_type, self.Bytes, self.Head(), x) + N.enforce_number(x, flags) + self.head = self.head - flags.bytewidth + encode.Write(flags.packer_type, self.Bytes, self.Head(), x) - def PlaceVOffsetT(self, x): - """PlaceVOffsetT prepends a VOffsetT to the Builder, without checking + def PlaceVOffsetT(self, x): + """PlaceVOffsetT prepends a VOffsetT to the Builder, without checking - for space. - """ - N.enforce_number(x, N.VOffsetTFlags) - self.head = self.head - N.VOffsetTFlags.bytewidth - encode.Write(packer.voffset, self.Bytes, self.Head(), x) + for space. + """ + N.enforce_number(x, N.VOffsetTFlags) + self.head = self.head - N.VOffsetTFlags.bytewidth + encode.Write(packer.voffset, self.Bytes, self.Head(), x) - def PlaceSOffsetT(self, x): - """PlaceSOffsetT prepends a SOffsetT to the Builder, without checking + def PlaceSOffsetT(self, x): + """PlaceSOffsetT prepends a SOffsetT to the Builder, without checking - for space. - """ - N.enforce_number(x, N.SOffsetTFlags) - self.head = self.head - N.SOffsetTFlags.bytewidth - encode.Write(packer.soffset, self.Bytes, self.Head(), x) + for space. + """ + N.enforce_number(x, N.SOffsetTFlags) + self.head = self.head - N.SOffsetTFlags.bytewidth + encode.Write(packer.soffset, self.Bytes, self.Head(), x) - def PlaceUOffsetT(self, x): - """PlaceUOffsetT prepends a UOffsetT to the Builder, without checking + def PlaceUOffsetT(self, x): + """PlaceUOffsetT prepends a UOffsetT to the Builder, without checking - for space. - """ - N.enforce_number(x, N.UOffsetTFlags) - self.head = self.head - N.UOffsetTFlags.bytewidth - encode.Write(packer.uoffset, self.Bytes, self.Head(), x) + for space. + """ + N.enforce_number(x, N.UOffsetTFlags) + self.head = self.head - N.UOffsetTFlags.bytewidth + encode.Write(packer.uoffset, self.Bytes, self.Head(), x) - ## @endcond + ## @endcond ## @cond FLATBUFFERS_INTERNAL def vtableEqual(a, objectStart, b): - """vtableEqual compares an unwritten vtable to a written vtable.""" + """vtableEqual compares an unwritten vtable to a written vtable.""" - N.enforce_number(objectStart, N.UOffsetTFlags) + N.enforce_number(objectStart, N.UOffsetTFlags) - if len(a) * N.VOffsetTFlags.bytewidth != len(b): - return False + if len(a) * N.VOffsetTFlags.bytewidth != len(b): + return False - for i, elem in enumerate(a): - x = encode.Get(packer.voffset, b, i * N.VOffsetTFlags.bytewidth) + for i, elem in enumerate(a): + x = encode.Get(packer.voffset, b, i * N.VOffsetTFlags.bytewidth) - # Skip vtable entries that indicate a default value. - if x == 0 and elem == 0: - pass - else: - y = objectStart - elem - if x != y: - return False - return True + # Skip vtable entries that indicate a default value. + if x == 0 and elem == 0: + pass + else: + y = objectStart - elem + if x != y: + return False + return True ## @endcond diff --git a/flatbuffers/compat.py b/flatbuffers/compat.py index 5668ad70f..97e06d5c9 100644 --- a/flatbuffers/compat.py +++ b/flatbuffers/compat.py @@ -27,65 +27,65 @@ PY34 = sys.version_info[0:2] >= (3, 4) if PY3: - import importlib.machinery + import importlib.machinery - string_types = (str,) - binary_types = (bytes, bytearray) - range_func = range - memoryview_type = memoryview - struct_bool_decl = "?" -else: - import imp - - string_types = (unicode,) - if PY26 or PY27: - binary_types = (str, bytearray) - else: - binary_types = (str,) - range_func = xrange - if PY26 or (PY27 and not PY275): - memoryview_type = buffer - struct_bool_decl = "= 0 - - if value < (1 << 8): - return BitWidth.W8 - elif value < (1 << 16): - return BitWidth.W16 - elif value < (1 << 32): - return BitWidth.W32 - elif value < (1 << 64): - return BitWidth.W64 - else: - raise ValueError('value is too big to encode: %s' % value) - - @staticmethod - def I(value): - """Returns the minimum `BitWidth` to encode signed integer value.""" - # -2^(n-1) <= value < 2^(n-1) - # -2^n <= 2 * value < 2^n - # 2 * value < 2^n, when value >= 0 or 2 * (-value) <= 2^n, when value < 0 - # 2 * value < 2^n, when value >= 0 or 2 * (-value) - 1 < 2^n, when value < 0 - # - # if value >= 0: - # return BitWidth.U(2 * value) - # else: - # return BitWidth.U(2 * (-value) - 1) # ~x = -x - 1 - value *= 2 - return BitWidth.U(value if value >= 0 else ~value) - - @staticmethod - def F(value): - """Returns the `BitWidth` to encode floating point value.""" - if struct.unpack('= 0 + + if value < (1 << 8): + return BitWidth.W8 + elif value < (1 << 16): + return BitWidth.W16 + elif value < (1 << 32): + return BitWidth.W32 + elif value < (1 << 64): + return BitWidth.W64 + else: + raise ValueError("value is too big to encode: %s" % value) + + @staticmethod + def I(value): + """Returns the minimum `BitWidth` to encode signed integer value.""" + # -2^(n-1) <= value < 2^(n-1) + # -2^n <= 2 * value < 2^n + # 2 * value < 2^n, when value >= 0 or 2 * (-value) <= 2^n, when value < 0 + # 2 * value < 2^n, when value >= 0 or 2 * (-value) - 1 < 2^n, when value < 0 + # + # if value >= 0: + # return BitWidth.U(2 * value) + # else: + # return BitWidth.U(2 * (-value) - 1) # ~x = -x - 1 + value *= 2 + return BitWidth.U(value if value >= 0 else ~value) + + @staticmethod + def F(value): + """Returns the `BitWidth` to encode floating point value.""" + if struct.unpack(" 0: - i = first - step = count // 2 - i += step - if pred(values[i], value): - i += 1 - first = i - count -= step + 1 - else: - count = step - return first + """Implementation of C++ std::lower_bound() algorithm.""" + first, last = 0, len(values) + count = last - first + while count > 0: + i = first + step = count // 2 + i += step + if pred(values[i], value): + i += 1 + first = i + count -= step + 1 + else: + count = step + return first # https://en.cppreference.com/w/cpp/algorithm/binary_search def _BinarySearch(values, value, pred=lambda x, y: x < y): - """Implementation of C++ std::binary_search() algorithm.""" - index = _LowerBound(values, value, pred) - if index != len(values) and not pred(value, values[index]): - return index - return -1 + """Implementation of C++ std::binary_search() algorithm.""" + index = _LowerBound(values, value, pred) + if index != len(values) and not pred(value, values[index]): + return index + return -1 class Type(enum.IntEnum): - """Supported types of encoded data. - - These are used as the upper 6 bits of a type field to indicate the actual - type. - """ - - NULL = 0 - INT = 1 - UINT = 2 - FLOAT = 3 - # Types above stored inline, types below store an offset. - KEY = 4 - STRING = 5 - INDIRECT_INT = 6 - INDIRECT_UINT = 7 - INDIRECT_FLOAT = 8 - MAP = 9 - VECTOR = 10 # Untyped. - - VECTOR_INT = 11 # Typed any size (stores no type table). - VECTOR_UINT = 12 - VECTOR_FLOAT = 13 - VECTOR_KEY = 14 - # DEPRECATED, use VECTOR or VECTOR_KEY instead. - # Read test.cpp/FlexBuffersDeprecatedTest() for details on why. - VECTOR_STRING_DEPRECATED = 15 - - VECTOR_INT2 = 16 # Typed tuple (no type table, no size field). - VECTOR_UINT2 = 17 - VECTOR_FLOAT2 = 18 - VECTOR_INT3 = 19 # Typed triple (no type table, no size field). - VECTOR_UINT3 = 20 - VECTOR_FLOAT3 = 21 - VECTOR_INT4 = 22 # Typed quad (no type table, no size field). - VECTOR_UINT4 = 23 - VECTOR_FLOAT4 = 24 - - BLOB = 25 - BOOL = 26 - VECTOR_BOOL = 36 # To do the same type of conversion of type to vector type - - @staticmethod - def Pack(type_, bit_width): - return (int(type_) << 2) | bit_width - - @staticmethod - def Unpack(packed_type): - return 1 << (packed_type & 0b11), Type(packed_type >> 2) - - @staticmethod - def IsInline(type_): - return type_ <= Type.FLOAT or type_ == Type.BOOL - - @staticmethod - def IsTypedVector(type_): - return ( - Type.VECTOR_INT <= type_ <= Type.VECTOR_STRING_DEPRECATED - or type_ == Type.VECTOR_BOOL - ) - - @staticmethod - def IsTypedVectorElementType(type_): - return Type.INT <= type_ <= Type.STRING or type_ == Type.BOOL - - @staticmethod - def ToTypedVectorElementType(type_): - if not Type.IsTypedVector(type_): - raise ValueError('must be typed vector type') - - return Type(type_ - Type.VECTOR_INT + Type.INT) - - @staticmethod - def IsFixedTypedVector(type_): - return Type.VECTOR_INT2 <= type_ <= Type.VECTOR_FLOAT4 - - @staticmethod - def IsFixedTypedVectorElementType(type_): - return Type.INT <= type_ <= Type.FLOAT - - @staticmethod - def ToFixedTypedVectorElementType(type_): - if not Type.IsFixedTypedVector(type_): - raise ValueError('must be fixed typed vector type') - - # 3 types each, starting from length 2. - fixed_type = type_ - Type.VECTOR_INT2 - return Type(fixed_type % 3 + Type.INT), fixed_type // 3 + 2 - - @staticmethod - def ToTypedVector(element_type, fixed_len=0): - """Converts element type to corresponding vector type. - - Args: - element_type: vector element type - fixed_len: number of elements: 0 for typed vector; 2, 3, or 4 for fixed - typed vector. - - Returns: - Typed vector type or fixed typed vector type. + """Supported types of encoded data. + + These are used as the upper 6 bits of a type field to indicate the actual + type. """ - if fixed_len == 0: - if not Type.IsTypedVectorElementType(element_type): - raise ValueError('must be typed vector element type') - else: - if not Type.IsFixedTypedVectorElementType(element_type): - raise ValueError('must be fixed typed vector element type') - - offset = element_type - Type.INT - if fixed_len == 0: - return Type(offset + Type.VECTOR_INT) # TypedVector - elif fixed_len == 2: - return Type(offset + Type.VECTOR_INT2) # FixedTypedVector - elif fixed_len == 3: - return Type(offset + Type.VECTOR_INT3) # FixedTypedVector - elif fixed_len == 4: - return Type(offset + Type.VECTOR_INT4) # FixedTypedVector - else: - raise ValueError('unsupported fixed_len: %s' % fixed_len) + NULL = 0 + INT = 1 + UINT = 2 + FLOAT = 3 + # Types above stored inline, types below store an offset. + KEY = 4 + STRING = 5 + INDIRECT_INT = 6 + INDIRECT_UINT = 7 + INDIRECT_FLOAT = 8 + MAP = 9 + VECTOR = 10 # Untyped. + + VECTOR_INT = 11 # Typed any size (stores no type table). + VECTOR_UINT = 12 + VECTOR_FLOAT = 13 + VECTOR_KEY = 14 + # DEPRECATED, use VECTOR or VECTOR_KEY instead. + # Read test.cpp/FlexBuffersDeprecatedTest() for details on why. + VECTOR_STRING_DEPRECATED = 15 + + VECTOR_INT2 = 16 # Typed tuple (no type table, no size field). + VECTOR_UINT2 = 17 + VECTOR_FLOAT2 = 18 + VECTOR_INT3 = 19 # Typed triple (no type table, no size field). + VECTOR_UINT3 = 20 + VECTOR_FLOAT3 = 21 + VECTOR_INT4 = 22 # Typed quad (no type table, no size field). + VECTOR_UINT4 = 23 + VECTOR_FLOAT4 = 24 + + BLOB = 25 + BOOL = 26 + VECTOR_BOOL = 36 # To do the same type of conversion of type to vector type + + @staticmethod + def Pack(type_, bit_width): + return (int(type_) << 2) | bit_width + + @staticmethod + def Unpack(packed_type): + return 1 << (packed_type & 0b11), Type(packed_type >> 2) + + @staticmethod + def IsInline(type_): + return type_ <= Type.FLOAT or type_ == Type.BOOL + + @staticmethod + def IsTypedVector(type_): + return ( + Type.VECTOR_INT <= type_ <= Type.VECTOR_STRING_DEPRECATED + or type_ == Type.VECTOR_BOOL + ) -class Buf: - """Class to access underlying buffer object starting from the given offset.""" + @staticmethod + def IsTypedVectorElementType(type_): + return Type.INT <= type_ <= Type.STRING or type_ == Type.BOOL + + @staticmethod + def ToTypedVectorElementType(type_): + if not Type.IsTypedVector(type_): + raise ValueError("must be typed vector type") + + return Type(type_ - Type.VECTOR_INT + Type.INT) + + @staticmethod + def IsFixedTypedVector(type_): + return Type.VECTOR_INT2 <= type_ <= Type.VECTOR_FLOAT4 + + @staticmethod + def IsFixedTypedVectorElementType(type_): + return Type.INT <= type_ <= Type.FLOAT + + @staticmethod + def ToFixedTypedVectorElementType(type_): + if not Type.IsFixedTypedVector(type_): + raise ValueError("must be fixed typed vector type") + + # 3 types each, starting from length 2. + fixed_type = type_ - Type.VECTOR_INT2 + return Type(fixed_type % 3 + Type.INT), fixed_type // 3 + 2 + + @staticmethod + def ToTypedVector(element_type, fixed_len=0): + """Converts element type to corresponding vector type. + + Args: + element_type: vector element type + fixed_len: number of elements: 0 for typed vector; 2, 3, or 4 for fixed + typed vector. + + Returns: + Typed vector type or fixed typed vector type. + """ + if fixed_len == 0: + if not Type.IsTypedVectorElementType(element_type): + raise ValueError("must be typed vector element type") + else: + if not Type.IsFixedTypedVectorElementType(element_type): + raise ValueError("must be fixed typed vector element type") + + offset = element_type - Type.INT + if fixed_len == 0: + return Type(offset + Type.VECTOR_INT) # TypedVector + elif fixed_len == 2: + return Type(offset + Type.VECTOR_INT2) # FixedTypedVector + elif fixed_len == 3: + return Type(offset + Type.VECTOR_INT3) # FixedTypedVector + elif fixed_len == 4: + return Type(offset + Type.VECTOR_INT4) # FixedTypedVector + else: + raise ValueError("unsupported fixed_len: %s" % fixed_len) - def __init__(self, buf, offset): - self._buf = buf - self._offset = offset if offset >= 0 else len(buf) + offset - self._length = len(buf) - self._offset - def __getitem__(self, key): - if isinstance(key, slice): - return self._buf[_ShiftSlice(key, self._offset, self._length)] - elif isinstance(key, int): - return self._buf[self._offset + key] - else: - raise TypeError('invalid key type') +class Buf: + """Class to access underlying buffer object starting from the given offset.""" + + def __init__(self, buf, offset): + self._buf = buf + self._offset = offset if offset >= 0 else len(buf) + offset + self._length = len(buf) - self._offset + + def __getitem__(self, key): + if isinstance(key, slice): + return self._buf[_ShiftSlice(key, self._offset, self._length)] + elif isinstance(key, int): + return self._buf[self._offset + key] + else: + raise TypeError("invalid key type") - def __setitem__(self, key, value): - if isinstance(key, slice): - self._buf[_ShiftSlice(key, self._offset, self._length)] = value - elif isinstance(key, int): - self._buf[self._offset + key] = key - else: - raise TypeError('invalid key type') + def __setitem__(self, key, value): + if isinstance(key, slice): + self._buf[_ShiftSlice(key, self._offset, self._length)] = value + elif isinstance(key, int): + self._buf[self._offset + key] = key + else: + raise TypeError("invalid key type") - def __repr__(self): - return 'buf[%d:]' % self._offset + def __repr__(self): + return "buf[%d:]" % self._offset - def Find(self, sub): - """Returns the lowest index where the sub subsequence is found.""" - return self._buf[self._offset :].find(sub) + def Find(self, sub): + """Returns the lowest index where the sub subsequence is found.""" + return self._buf[self._offset :].find(sub) - def Slice(self, offset): - """Returns new `Buf` which starts from the given offset.""" - return Buf(self._buf, self._offset + offset) + def Slice(self, offset): + """Returns new `Buf` which starts from the given offset.""" + return Buf(self._buf, self._offset + offset) - def Indirect(self, offset, byte_width): - """Return new `Buf` based on the encoded offset (indirect encoding).""" - return self.Slice(offset - _Unpack(U, self[offset : offset + byte_width])) + def Indirect(self, offset, byte_width): + """Return new `Buf` based on the encoded offset (indirect encoding).""" + return self.Slice(offset - _Unpack(U, self[offset : offset + byte_width])) class Object: - """Base class for all non-trivial data accessors.""" + """Base class for all non-trivial data accessors.""" - __slots__ = '_buf', '_byte_width' + __slots__ = "_buf", "_byte_width" - def __init__(self, buf, byte_width): - self._buf = buf - self._byte_width = byte_width + def __init__(self, buf, byte_width): + self._buf = buf + self._byte_width = byte_width - @property - def ByteWidth(self): - return self._byte_width + @property + def ByteWidth(self): + return self._byte_width class Sized(Object): - """Base class for all data accessors which need to read encoded size.""" + """Base class for all data accessors which need to read encoded size.""" - __slots__ = ('_size',) + __slots__ = ("_size",) - def __init__(self, buf, byte_width, size=0): - super().__init__(buf, byte_width) - if size == 0: - self._size = _Unpack(U, self.SizeBytes) - else: - self._size = size + def __init__(self, buf, byte_width, size=0): + super().__init__(buf, byte_width) + if size == 0: + self._size = _Unpack(U, self.SizeBytes) + else: + self._size = size - @property - def SizeBytes(self): - return self._buf[-self._byte_width : 0] + @property + def SizeBytes(self): + return self._buf[-self._byte_width : 0] - def __len__(self): - return self._size + def __len__(self): + return self._size class Blob(Sized): - """Data accessor for the encoded blob bytes.""" + """Data accessor for the encoded blob bytes.""" - __slots__ = () + __slots__ = () - @property - def Bytes(self): - return self._buf[0 : len(self)] + @property + def Bytes(self): + return self._buf[0 : len(self)] - def __repr__(self): - return 'Blob(%s, size=%d)' % (self._buf, len(self)) + def __repr__(self): + return "Blob(%s, size=%d)" % (self._buf, len(self)) class String(Sized): - """Data accessor for the encoded string bytes.""" + """Data accessor for the encoded string bytes.""" - __slots__ = () + __slots__ = () - @property - def Bytes(self): - return self._buf[0 : len(self)] + @property + def Bytes(self): + return self._buf[0 : len(self)] - def Mutate(self, value): - """Mutates underlying string bytes in place. + def Mutate(self, value): + """Mutates underlying string bytes in place. - Args: - value: New string to replace the existing one. New string must have less - or equal UTF-8-encoded bytes than the existing one to successfully - mutate underlying byte buffer. + Args: + value: New string to replace the existing one. New string must have less + or equal UTF-8-encoded bytes than the existing one to successfully + mutate underlying byte buffer. - Returns: - Whether the value was mutated or not. - """ - encoded = value.encode('utf-8') - n = len(encoded) - if n <= len(self): - self._buf[-self._byte_width : 0] = _Pack(U, n, self._byte_width) - self._buf[0:n] = encoded - self._buf[n : len(self)] = bytearray(len(self) - n) - return True - return False + Returns: + Whether the value was mutated or not. + """ + encoded = value.encode("utf-8") + n = len(encoded) + if n <= len(self): + self._buf[-self._byte_width : 0] = _Pack(U, n, self._byte_width) + self._buf[0:n] = encoded + self._buf[n : len(self)] = bytearray(len(self) - n) + return True + return False - def __str__(self): - return self.Bytes.decode('utf-8') + def __str__(self): + return self.Bytes.decode("utf-8") - def __repr__(self): - return 'String(%s, size=%d)' % (self._buf, len(self)) + def __repr__(self): + return "String(%s, size=%d)" % (self._buf, len(self)) class Key(Object): - """Data accessor for the encoded key bytes.""" + """Data accessor for the encoded key bytes.""" - __slots__ = () + __slots__ = () - def __init__(self, buf, byte_width): - assert byte_width == 1 - super().__init__(buf, byte_width) + def __init__(self, buf, byte_width): + assert byte_width == 1 + super().__init__(buf, byte_width) - @property - def Bytes(self): - return self._buf[0 : len(self)] + @property + def Bytes(self): + return self._buf[0 : len(self)] - def __len__(self): - return self._buf.Find(0) + def __len__(self): + return self._buf.Find(0) - def __str__(self): - return self.Bytes.decode('ascii') + def __str__(self): + return self.Bytes.decode("ascii") - def __repr__(self): - return 'Key(%s, size=%d)' % (self._buf, len(self)) + def __repr__(self): + return "Key(%s, size=%d)" % (self._buf, len(self)) class Vector(Sized): - """Data accessor for the encoded vector bytes.""" - - __slots__ = () - - def __getitem__(self, index): - if index < 0 or index >= len(self): - raise IndexError( - 'vector index %s is out of [0, %d) range' % (index, len(self)) - ) - - packed_type = self._buf[len(self) * self._byte_width + index] - buf = self._buf.Slice(index * self._byte_width) - return Ref.PackedType(buf, self._byte_width, packed_type) - - @property - def Value(self): - """Returns the underlying encoded data as a list object.""" - return [e.Value for e in self] - - def __repr__(self): - return 'Vector(%s, byte_width=%d, size=%d)' % ( - self._buf, - self._byte_width, - self._size, - ) + """Data accessor for the encoded vector bytes.""" + + __slots__ = () + + def __getitem__(self, index): + if index < 0 or index >= len(self): + raise IndexError( + "vector index %s is out of [0, %d) range" % (index, len(self)) + ) + + packed_type = self._buf[len(self) * self._byte_width + index] + buf = self._buf.Slice(index * self._byte_width) + return Ref.PackedType(buf, self._byte_width, packed_type) + + @property + def Value(self): + """Returns the underlying encoded data as a list object.""" + return [e.Value for e in self] + + def __repr__(self): + return "Vector(%s, byte_width=%d, size=%d)" % ( + self._buf, + self._byte_width, + self._size, + ) class TypedVector(Sized): - """Data accessor for the encoded typed vector or fixed typed vector bytes.""" - - __slots__ = '_element_type', '_size' - - def __init__(self, buf, byte_width, element_type, size=0): - super().__init__(buf, byte_width, size) - - if element_type == Type.STRING: - # These can't be accessed as strings, since we don't know the bit-width - # of the size field, see the declaration of - # FBT_VECTOR_STRING_DEPRECATED above for details. - # We change the type here to be keys, which are a subtype of strings, - # and will ignore the size field. This will truncate strings with - # embedded nulls. - element_type = Type.KEY - - self._element_type = element_type - - @property - def Bytes(self): - return self._buf[: self._byte_width * len(self)] - - @property - def ElementType(self): - return self._element_type - - def __getitem__(self, index): - if index < 0 or index >= len(self): - raise IndexError( - 'vector index %s is out of [0, %d) range' % (index, len(self)) - ) - - buf = self._buf.Slice(index * self._byte_width) - return Ref(buf, self._byte_width, 1, self._element_type) - - @property - def Value(self): - """Returns underlying data as list object.""" - if not self: - return [] - - if self._element_type is Type.BOOL: - return [bool(e) for e in _UnpackVector(U, self.Bytes, len(self))] - elif self._element_type is Type.INT: - return list(_UnpackVector(I, self.Bytes, len(self))) - elif self._element_type is Type.UINT: - return list(_UnpackVector(U, self.Bytes, len(self))) - elif self._element_type is Type.FLOAT: - return list(_UnpackVector(F, self.Bytes, len(self))) - elif self._element_type is Type.KEY: - return [e.AsKey for e in self] - elif self._element_type is Type.STRING: - return [e.AsString for e in self] - else: - raise TypeError('unsupported element_type: %s' % self._element_type) - - def __repr__(self): - return 'TypedVector(%s, byte_width=%d, element_type=%s, size=%d)' % ( - self._buf, - self._byte_width, - self._element_type, - self._size, - ) + """Data accessor for the encoded typed vector or fixed typed vector bytes.""" + + __slots__ = "_element_type", "_size" + + def __init__(self, buf, byte_width, element_type, size=0): + super().__init__(buf, byte_width, size) + + if element_type == Type.STRING: + # These can't be accessed as strings, since we don't know the bit-width + # of the size field, see the declaration of + # FBT_VECTOR_STRING_DEPRECATED above for details. + # We change the type here to be keys, which are a subtype of strings, + # and will ignore the size field. This will truncate strings with + # embedded nulls. + element_type = Type.KEY + + self._element_type = element_type + + @property + def Bytes(self): + return self._buf[: self._byte_width * len(self)] + + @property + def ElementType(self): + return self._element_type + + def __getitem__(self, index): + if index < 0 or index >= len(self): + raise IndexError( + "vector index %s is out of [0, %d) range" % (index, len(self)) + ) + + buf = self._buf.Slice(index * self._byte_width) + return Ref(buf, self._byte_width, 1, self._element_type) + + @property + def Value(self): + """Returns underlying data as list object.""" + if not self: + return [] + + if self._element_type is Type.BOOL: + return [bool(e) for e in _UnpackVector(U, self.Bytes, len(self))] + elif self._element_type is Type.INT: + return list(_UnpackVector(I, self.Bytes, len(self))) + elif self._element_type is Type.UINT: + return list(_UnpackVector(U, self.Bytes, len(self))) + elif self._element_type is Type.FLOAT: + return list(_UnpackVector(F, self.Bytes, len(self))) + elif self._element_type is Type.KEY: + return [e.AsKey for e in self] + elif self._element_type is Type.STRING: + return [e.AsString for e in self] + else: + raise TypeError("unsupported element_type: %s" % self._element_type) + + def __repr__(self): + return "TypedVector(%s, byte_width=%d, element_type=%s, size=%d)" % ( + self._buf, + self._byte_width, + self._element_type, + self._size, + ) class Map(Vector): - """Data accessor for the encoded map bytes.""" + """Data accessor for the encoded map bytes.""" - @staticmethod - def CompareKeys(a, b): - if isinstance(a, Ref): - a = a.AsKeyBytes - if isinstance(b, Ref): - b = b.AsKeyBytes - return a < b + @staticmethod + def CompareKeys(a, b): + if isinstance(a, Ref): + a = a.AsKeyBytes + if isinstance(b, Ref): + b = b.AsKeyBytes + return a < b - def __getitem__(self, key): - if isinstance(key, int): - return super().__getitem__(key) + def __getitem__(self, key): + if isinstance(key, int): + return super().__getitem__(key) - index = _BinarySearch(self.Keys, key.encode('ascii'), self.CompareKeys) - if index != -1: - return super().__getitem__(index) + index = _BinarySearch(self.Keys, key.encode("ascii"), self.CompareKeys) + if index != -1: + return super().__getitem__(index) - raise KeyError(key) + raise KeyError(key) - @property - def Keys(self): - byte_width = _Unpack( - U, self._buf[-2 * self._byte_width : -self._byte_width] - ) - buf = self._buf.Indirect(-3 * self._byte_width, self._byte_width) - return TypedVector(buf, byte_width, Type.KEY) + @property + def Keys(self): + byte_width = _Unpack(U, self._buf[-2 * self._byte_width : -self._byte_width]) + buf = self._buf.Indirect(-3 * self._byte_width, self._byte_width) + return TypedVector(buf, byte_width, Type.KEY) - @property - def Values(self): - return Vector(self._buf, self._byte_width) + @property + def Values(self): + return Vector(self._buf, self._byte_width) - @property - def Value(self): - return {k.Value: v.Value for k, v in zip(self.Keys, self.Values)} + @property + def Value(self): + return {k.Value: v.Value for k, v in zip(self.Keys, self.Values)} - def __repr__(self): - return 'Map(%s, size=%d)' % (self._buf, len(self)) + def __repr__(self): + return "Map(%s, size=%d)" % (self._buf, len(self)) class Ref: - """Data accessor for the encoded data bytes.""" - - __slots__ = '_buf', '_parent_width', '_byte_width', '_type' - - @staticmethod - def PackedType(buf, parent_width, packed_type): - byte_width, type_ = Type.Unpack(packed_type) - return Ref(buf, parent_width, byte_width, type_) - - def __init__(self, buf, parent_width, byte_width, type_): - self._buf = buf - self._parent_width = parent_width - self._byte_width = byte_width - self._type = type_ - - def __repr__(self): - return 'Ref(%s, parent_width=%d, byte_width=%d, type_=%s)' % ( - self._buf, - self._parent_width, - self._byte_width, - self._type, - ) - - @property - def _Bytes(self): - return self._buf[: self._parent_width] - - def _ConvertError(self, target_type): - raise TypeError('cannot convert %s to %s' % (self._type, target_type)) - - def _Indirect(self): - return self._buf.Indirect(0, self._parent_width) - - @property - def IsNull(self): - return self._type is Type.NULL - - @property - def IsBool(self): - return self._type is Type.BOOL - - @property - def AsBool(self): - if self._type is Type.BOOL: - return bool(_Unpack(U, self._Bytes)) - else: - return self.AsInt != 0 - - def MutateBool(self, value): - """Mutates underlying boolean value bytes in place. - - Args: - value: New boolean value. - - Returns: - Whether the value was mutated or not. - """ - return self.IsBool and _Mutate( - U, self._buf, value, self._parent_width, BitWidth.W8 - ) - - @property - def IsNumeric(self): - return self.IsInt or self.IsFloat - - @property - def IsInt(self): - return self._type in ( - Type.INT, - Type.INDIRECT_INT, - Type.UINT, - Type.INDIRECT_UINT, - ) - - @property - def AsInt(self): - """Returns current reference as integer value.""" - if self.IsNull: - return 0 - elif self.IsBool: - return int(self.AsBool) - elif self._type is Type.INT: - return _Unpack(I, self._Bytes) - elif self._type is Type.INDIRECT_INT: - return _Unpack(I, self._Indirect()[: self._byte_width]) - if self._type is Type.UINT: - return _Unpack(U, self._Bytes) - elif self._type is Type.INDIRECT_UINT: - return _Unpack(U, self._Indirect()[: self._byte_width]) - elif self.IsString: - return len(self.AsString) - elif self.IsKey: - return len(self.AsKey) - elif self.IsBlob: - return len(self.AsBlob) - elif self.IsVector: - return len(self.AsVector) - elif self.IsTypedVector: - return len(self.AsTypedVector) - elif self.IsFixedTypedVector: - return len(self.AsFixedTypedVector) - else: - raise self._ConvertError(Type.INT) - - def MutateInt(self, value): - """Mutates underlying integer value bytes in place. - - Args: - value: New integer value. It must fit to the byte size of the existing - encoded value. - - Returns: - Whether the value was mutated or not. - """ - if self._type is Type.INT: - return _Mutate(I, self._buf, value, self._parent_width, BitWidth.I(value)) - elif self._type is Type.INDIRECT_INT: - return _Mutate( - I, self._Indirect(), value, self._byte_width, BitWidth.I(value) - ) - elif self._type is Type.UINT: - return _Mutate(U, self._buf, value, self._parent_width, BitWidth.U(value)) - elif self._type is Type.INDIRECT_UINT: - return _Mutate( - U, self._Indirect(), value, self._byte_width, BitWidth.U(value) - ) - else: - return False - - @property - def IsFloat(self): - return self._type in (Type.FLOAT, Type.INDIRECT_FLOAT) - - @property - def AsFloat(self): - """Returns current reference as floating point value.""" - if self.IsNull: - return 0.0 - elif self.IsBool: - return float(self.AsBool) - elif self.IsInt: - return float(self.AsInt) - elif self._type is Type.FLOAT: - return _Unpack(F, self._Bytes) - elif self._type is Type.INDIRECT_FLOAT: - return _Unpack(F, self._Indirect()[: self._byte_width]) - elif self.IsString: - return float(self.AsString) - elif self.IsVector: - return float(len(self.AsVector)) - elif self.IsTypedVector(): - return float(len(self.AsTypedVector)) - elif self.IsFixedTypedVector(): - return float(len(self.FixedTypedVector)) - else: - raise self._ConvertError(Type.FLOAT) - - def MutateFloat(self, value): - """Mutates underlying floating point value bytes in place. - - Args: - value: New float value. It must fit to the byte size of the existing - encoded value. - - Returns: - Whether the value was mutated or not. - """ - if self._type is Type.FLOAT: - return _Mutate( - F, - self._buf, - value, - self._parent_width, - BitWidth.B(self._parent_width), - ) - elif self._type is Type.INDIRECT_FLOAT: - return _Mutate( - F, - self._Indirect(), - value, - self._byte_width, - BitWidth.B(self._byte_width), - ) - else: - return False - - @property - def IsKey(self): - return self._type is Type.KEY - - @property - def AsKeyBytes(self): - if self.IsKey: - return Key(self._Indirect(), self._byte_width).Bytes - else: - raise self._ConvertError(Type.KEY) - - @property - def AsKey(self): - if self.IsKey: - return str(Key(self._Indirect(), self._byte_width)) - else: - raise self._ConvertError(Type.KEY) - - @property - def IsString(self): - return self._type is Type.STRING - - @property - def AsStringBytes(self): - if self.IsString: - return String(self._Indirect(), self._byte_width).Bytes - elif self.IsKey: - return self.AsKeyBytes - else: - raise self._ConvertError(Type.STRING) - - @property - def AsString(self): - if self.IsString: - return str(String(self._Indirect(), self._byte_width)) - elif self.IsKey: - return self.AsKey - else: - raise self._ConvertError(Type.STRING) - - def MutateString(self, value): - return String(self._Indirect(), self._byte_width).Mutate(value) - - @property - def IsBlob(self): - return self._type is Type.BLOB - - @property - def AsBlob(self): - if self.IsBlob: - return Blob(self._Indirect(), self._byte_width).Bytes - else: - raise self._ConvertError(Type.BLOB) - - @property - def IsAnyVector(self): - return self.IsVector or self.IsTypedVector or self.IsFixedTypedVector() - - @property - def IsVector(self): - return self._type in (Type.VECTOR, Type.MAP) - - @property - def AsVector(self): - if self.IsVector: - return Vector(self._Indirect(), self._byte_width) - else: - raise self._ConvertError(Type.VECTOR) - - @property - def IsTypedVector(self): - return Type.IsTypedVector(self._type) - - @property - def AsTypedVector(self): - if self.IsTypedVector: - return TypedVector( - self._Indirect(), - self._byte_width, - Type.ToTypedVectorElementType(self._type), - ) - else: - raise self._ConvertError('TYPED_VECTOR') - - @property - def IsFixedTypedVector(self): - return Type.IsFixedTypedVector(self._type) - - @property - def AsFixedTypedVector(self): - if self.IsFixedTypedVector: - element_type, size = Type.ToFixedTypedVectorElementType(self._type) - return TypedVector(self._Indirect(), self._byte_width, element_type, size) - else: - raise self._ConvertError('FIXED_TYPED_VECTOR') - - @property - def IsMap(self): - return self._type is Type.MAP - - @property - def AsMap(self): - if self.IsMap: - return Map(self._Indirect(), self._byte_width) - else: - raise self._ConvertError(Type.MAP) - - @property - def Value(self): - """Converts current reference to value of corresponding type. - - This is equivalent to calling `AsInt` for integer values, `AsFloat` for - floating point values, etc. - - Returns: - Value of corresponding type. - """ - if self.IsNull: - return None - elif self.IsBool: - return self.AsBool - elif self.IsInt: - return self.AsInt - elif self.IsFloat: - return self.AsFloat - elif self.IsString: - return self.AsString - elif self.IsKey: - return self.AsKey - elif self.IsBlob: - return self.AsBlob - elif self.IsMap: - return self.AsMap.Value - elif self.IsVector: - return self.AsVector.Value - elif self.IsTypedVector: - return self.AsTypedVector.Value - elif self.IsFixedTypedVector: - return self.AsFixedTypedVector.Value - else: - raise TypeError('cannot convert %r to value' % self) - - -def _IsIterable(obj): - try: - iter(obj) - return True - except TypeError: - return False - - -class Value: - """Class to represent given value during the encoding process.""" - - @staticmethod - def Null(): - return Value(0, Type.NULL, BitWidth.W8) - - @staticmethod - def Bool(value): - return Value(value, Type.BOOL, BitWidth.W8) - - @staticmethod - def Int(value, bit_width): - return Value(value, Type.INT, bit_width) - - @staticmethod - def UInt(value, bit_width): - return Value(value, Type.UINT, bit_width) - - @staticmethod - def Float(value, bit_width): - return Value(value, Type.FLOAT, bit_width) - - @staticmethod - def Key(offset): - return Value(offset, Type.KEY, BitWidth.W8) - - def __init__(self, value, type_, min_bit_width): - self._value = value - self._type = type_ - - # For scalars: of itself, for vector: of its elements, for string: length. - self._min_bit_width = min_bit_width - - @property - def Value(self): - return self._value - - @property - def Type(self): - return self._type - - @property - def MinBitWidth(self): - return self._min_bit_width - - def StoredPackedType(self, parent_bit_width=BitWidth.W8): - return Type.Pack(self._type, self.StoredWidth(parent_bit_width)) - - # We have an absolute offset, but want to store a relative offset - # elem_index elements beyond the current buffer end. Since whether - # the relative offset fits in a certain byte_width depends on - # the size of the elements before it (and their alignment), we have - # to test for each size in turn. - def ElemWidth(self, buf_size, elem_index=0): - if Type.IsInline(self._type): - return self._min_bit_width - for byte_width in 1, 2, 4, 8: - offset_loc = ( - buf_size - + _PaddingBytes(buf_size, byte_width) - + elem_index * byte_width - ) - bit_width = BitWidth.U(offset_loc - self._value) - if byte_width == (1 << bit_width): - return bit_width - raise ValueError('relative offset is too big') - - def StoredWidth(self, parent_bit_width=BitWidth.W8): - if Type.IsInline(self._type): - return max(self._min_bit_width, parent_bit_width) - return self._min_bit_width - - def __repr__(self): - return 'Value(%s, %s, %s)' % (self._value, self._type, self._min_bit_width) - - def __str__(self): - return str(self._value) - - -def InMap(func): - def wrapper(self, *args, **kwargs): - if isinstance(args[0], str): - self.Key(args[0]) - func(self, *args[1:], **kwargs) - else: - func(self, *args, **kwargs) - - return wrapper + """Data accessor for the encoded data bytes.""" + + __slots__ = "_buf", "_parent_width", "_byte_width", "_type" + + @staticmethod + def PackedType(buf, parent_width, packed_type): + byte_width, type_ = Type.Unpack(packed_type) + return Ref(buf, parent_width, byte_width, type_) + + def __init__(self, buf, parent_width, byte_width, type_): + self._buf = buf + self._parent_width = parent_width + self._byte_width = byte_width + self._type = type_ + + def __repr__(self): + return "Ref(%s, parent_width=%d, byte_width=%d, type_=%s)" % ( + self._buf, + self._parent_width, + self._byte_width, + self._type, + ) + @property + def _Bytes(self): + return self._buf[: self._parent_width] -def InMapForString(func): - def wrapper(self, *args): - if len(args) == 1: - func(self, args[0]) - elif len(args) == 2: - self.Key(args[0]) - func(self, args[1]) - else: - raise ValueError('invalid number of arguments') + def _ConvertError(self, target_type): + raise TypeError("cannot convert %s to %s" % (self._type, target_type)) - return wrapper + def _Indirect(self): + return self._buf.Indirect(0, self._parent_width) + @property + def IsNull(self): + return self._type is Type.NULL -class Pool: - """Collection of (data, offset) pairs sorted by data for quick access.""" + @property + def IsBool(self): + return self._type is Type.BOOL - def __init__(self): - self._pool = [] # sorted list of (data, offset) tuples + @property + def AsBool(self): + if self._type is Type.BOOL: + return bool(_Unpack(U, self._Bytes)) + else: + return self.AsInt != 0 - def FindOrInsert(self, data, offset): - do = data, offset - index = _BinarySearch(self._pool, do, lambda a, b: a[0] < b[0]) - if index != -1: - _, offset = self._pool[index] - return offset - self._pool.insert(index, do) - return None + def MutateBool(self, value): + """Mutates underlying boolean value bytes in place. - def Clear(self): - self._pool = [] + Args: + value: New boolean value. - @property - def Elements(self): - return [data for data, _ in self._pool] + Returns: + Whether the value was mutated or not. + """ + return self.IsBool and _Mutate( + U, self._buf, value, self._parent_width, BitWidth.W8 + ) + @property + def IsNumeric(self): + return self.IsInt or self.IsFloat + + @property + def IsInt(self): + return self._type in ( + Type.INT, + Type.INDIRECT_INT, + Type.UINT, + Type.INDIRECT_UINT, + ) -class Builder: - """Helper class to encode structural data into flexbuffers format.""" - - def __init__( - self, - share_strings=False, - share_keys=True, - force_min_bit_width=BitWidth.W8, - ): - self._share_strings = share_strings - self._share_keys = share_keys - self._force_min_bit_width = force_min_bit_width - - self._string_pool = Pool() - self._key_pool = Pool() - - self._finished = False - self._buf = bytearray() - self._stack = [] - - def __len__(self): - return len(self._buf) - - @property - def StringPool(self): - return self._string_pool - - @property - def KeyPool(self): - return self._key_pool - - def Clear(self): - self._string_pool.Clear() - self._key_pool.Clear() - self._finished = False - self._buf = bytearray() - self._stack = [] - - def Finish(self): - """Finishes encoding process and returns underlying buffer.""" - if self._finished: - raise RuntimeError('builder has been already finished') - - # If you hit this exception, you likely have objects that were never - # included in a parent. You need to have exactly one root to finish a - # buffer. Check your Start/End calls are matched, and all objects are inside - # some other object. - if len(self._stack) != 1: - raise RuntimeError('internal stack size must be one') - - value = self._stack[0] - byte_width = self._Align(value.ElemWidth(len(self._buf))) - self._WriteAny(value, byte_width=byte_width) # Root value - self._Write(U, value.StoredPackedType(), byte_width=1) # Root type - self._Write(U, byte_width, byte_width=1) # Root size - - self.finished = True - return self._buf - - def _ReadKey(self, offset): - key = self._buf[offset:] - return key[: key.find(0)] - - def _Align(self, alignment): - byte_width = 1 << alignment - self._buf.extend(b'\x00' * _PaddingBytes(len(self._buf), byte_width)) - return byte_width - - def _Write(self, fmt, value, byte_width): - self._buf.extend(_Pack(fmt, value, byte_width)) - - def _WriteVector(self, fmt, values, byte_width): - self._buf.extend(_PackVector(fmt, values, byte_width)) - - def _WriteOffset(self, offset, byte_width): - relative_offset = len(self._buf) - offset - assert byte_width == 8 or relative_offset < (1 << (8 * byte_width)) - self._Write(U, relative_offset, byte_width) - - def _WriteAny(self, value, byte_width): - fmt = { - Type.NULL: U, - Type.BOOL: U, - Type.INT: I, - Type.UINT: U, - Type.FLOAT: F, - }.get(value.Type) - if fmt: - self._Write(fmt, value.Value, byte_width) - else: - self._WriteOffset(value.Value, byte_width) - - def _WriteBlob(self, data, append_zero, type_): - bit_width = BitWidth.U(len(data)) - byte_width = self._Align(bit_width) - self._Write(U, len(data), byte_width) - loc = len(self._buf) - self._buf.extend(data) - if append_zero: - self._buf.append(0) - self._stack.append(Value(loc, type_, bit_width)) - return loc - - def _WriteScalarVector(self, element_type, byte_width, elements, fixed): - """Writes scalar vector elements to the underlying buffer.""" - bit_width = BitWidth.B(byte_width) - # If you get this exception, you're trying to write a vector with a size - # field that is bigger than the scalars you're trying to write (e.g. a - # byte vector > 255 elements). For such types, write a "blob" instead. - if BitWidth.U(len(elements)) > bit_width: - raise ValueError('too many elements for the given byte_width') - - self._Align(bit_width) - if not fixed: - self._Write(U, len(elements), byte_width) - - loc = len(self._buf) - - fmt = {Type.INT: I, Type.UINT: U, Type.FLOAT: F}.get(element_type) - if not fmt: - raise TypeError('unsupported element_type') - self._WriteVector(fmt, elements, byte_width) - - type_ = Type.ToTypedVector(element_type, len(elements) if fixed else 0) - self._stack.append(Value(loc, type_, bit_width)) - return loc - - def _CreateVector(self, elements, typed, fixed, keys=None): - """Writes vector elements to the underlying buffer.""" - length = len(elements) - - if fixed and not typed: - raise ValueError('fixed vector must be typed') - - # Figure out smallest bit width we can store this vector with. - bit_width = max(self._force_min_bit_width, BitWidth.U(length)) - prefix_elems = 1 # Vector size - if keys: - bit_width = max(bit_width, keys.ElemWidth(len(self._buf))) - prefix_elems += 2 # Offset to the keys vector and its byte width. - - vector_type = Type.KEY - # Check bit widths and types for all elements. - for i, e in enumerate(elements): - bit_width = max(bit_width, e.ElemWidth(len(self._buf), prefix_elems + i)) - - if typed: - if i == 0: - vector_type = e.Type + @property + def AsInt(self): + """Returns current reference as integer value.""" + if self.IsNull: + return 0 + elif self.IsBool: + return int(self.AsBool) + elif self._type is Type.INT: + return _Unpack(I, self._Bytes) + elif self._type is Type.INDIRECT_INT: + return _Unpack(I, self._Indirect()[: self._byte_width]) + if self._type is Type.UINT: + return _Unpack(U, self._Bytes) + elif self._type is Type.INDIRECT_UINT: + return _Unpack(U, self._Indirect()[: self._byte_width]) + elif self.IsString: + return len(self.AsString) + elif self.IsKey: + return len(self.AsKey) + elif self.IsBlob: + return len(self.AsBlob) + elif self.IsVector: + return len(self.AsVector) + elif self.IsTypedVector: + return len(self.AsTypedVector) + elif self.IsFixedTypedVector: + return len(self.AsFixedTypedVector) else: - if vector_type != e.Type: - raise RuntimeError('typed vector elements must be of the same type') - - if fixed and not Type.IsFixedTypedVectorElementType(vector_type): - raise RuntimeError('must be fixed typed vector element type') - - byte_width = self._Align(bit_width) - # Write vector. First the keys width/offset if available, and size. - if keys: - self._WriteOffset(keys.Value, byte_width) - self._Write(U, 1 << keys.MinBitWidth, byte_width) - - if not fixed: - self._Write(U, length, byte_width) - - # Then the actual data. - loc = len(self._buf) - for e in elements: - self._WriteAny(e, byte_width) - - # Then the types. - if not typed: - for e in elements: - self._buf.append(e.StoredPackedType(bit_width)) - - if keys: - type_ = Type.MAP - else: - if typed: - type_ = Type.ToTypedVector(vector_type, length if fixed else 0) - else: - type_ = Type.VECTOR - - return Value(loc, type_, bit_width) - - def _PushIndirect(self, value, type_, bit_width): - byte_width = self._Align(bit_width) - loc = len(self._buf) - fmt = {Type.INDIRECT_INT: I, Type.INDIRECT_UINT: U, Type.INDIRECT_FLOAT: F}[ - type_ - ] - self._Write(fmt, value, byte_width) - self._stack.append(Value(loc, type_, bit_width)) - - @InMapForString - def String(self, value): - """Encodes string value.""" - reset_to = len(self._buf) - encoded = value.encode('utf-8') - loc = self._WriteBlob(encoded, append_zero=True, type_=Type.STRING) - if self._share_strings: - prev_loc = self._string_pool.FindOrInsert(encoded, loc) - if prev_loc is not None: - del self._buf[reset_to:] - self._stack[-1]._value = loc = prev_loc # pylint: disable=protected-access - - return loc - - @InMap - def Blob(self, value): - """Encodes binary blob value. - - Args: - value: A byte/bytearray value to encode - - Returns: - Offset of the encoded value in underlying the byte buffer. - """ - return self._WriteBlob(value, append_zero=False, type_=Type.BLOB) - - def Key(self, value): - """Encodes key value. - - Args: - value: A byte/bytearray/str value to encode. Byte object must not contain - zero bytes. String object must be convertible to ASCII. + raise self._ConvertError(Type.INT) + + def MutateInt(self, value): + """Mutates underlying integer value bytes in place. + + Args: + value: New integer value. It must fit to the byte size of the existing + encoded value. + + Returns: + Whether the value was mutated or not. + """ + if self._type is Type.INT: + return _Mutate(I, self._buf, value, self._parent_width, BitWidth.I(value)) + elif self._type is Type.INDIRECT_INT: + return _Mutate( + I, self._Indirect(), value, self._byte_width, BitWidth.I(value) + ) + elif self._type is Type.UINT: + return _Mutate(U, self._buf, value, self._parent_width, BitWidth.U(value)) + elif self._type is Type.INDIRECT_UINT: + return _Mutate( + U, self._Indirect(), value, self._byte_width, BitWidth.U(value) + ) + else: + return False + + @property + def IsFloat(self): + return self._type in (Type.FLOAT, Type.INDIRECT_FLOAT) + + @property + def AsFloat(self): + """Returns current reference as floating point value.""" + if self.IsNull: + return 0.0 + elif self.IsBool: + return float(self.AsBool) + elif self.IsInt: + return float(self.AsInt) + elif self._type is Type.FLOAT: + return _Unpack(F, self._Bytes) + elif self._type is Type.INDIRECT_FLOAT: + return _Unpack(F, self._Indirect()[: self._byte_width]) + elif self.IsString: + return float(self.AsString) + elif self.IsVector: + return float(len(self.AsVector)) + elif self.IsTypedVector(): + return float(len(self.AsTypedVector)) + elif self.IsFixedTypedVector(): + return float(len(self.FixedTypedVector)) + else: + raise self._ConvertError(Type.FLOAT) + + def MutateFloat(self, value): + """Mutates underlying floating point value bytes in place. + + Args: + value: New float value. It must fit to the byte size of the existing + encoded value. + + Returns: + Whether the value was mutated or not. + """ + if self._type is Type.FLOAT: + return _Mutate( + F, + self._buf, + value, + self._parent_width, + BitWidth.B(self._parent_width), + ) + elif self._type is Type.INDIRECT_FLOAT: + return _Mutate( + F, + self._Indirect(), + value, + self._byte_width, + BitWidth.B(self._byte_width), + ) + else: + return False - Returns: - Offset of the encoded value in the underlying byte buffer. - """ - if isinstance(value, (bytes, bytearray)): - encoded = value - else: - encoded = value.encode('ascii') - - if 0 in encoded: - raise ValueError('key contains zero byte') - - loc = len(self._buf) - self._buf.extend(encoded) - self._buf.append(0) - if self._share_keys: - prev_loc = self._key_pool.FindOrInsert(encoded, loc) - if prev_loc is not None: - del self._buf[loc:] - loc = prev_loc - - self._stack.append(Value.Key(loc)) - return loc - - def Null(self, key=None): - """Encodes None value.""" - if key: - self.Key(key) - self._stack.append(Value.Null()) - - @InMap - def Bool(self, value): - """Encodes boolean value. - - Args: - value: A boolean value. - """ - self._stack.append(Value.Bool(value)) + @property + def IsKey(self): + return self._type is Type.KEY - @InMap - def Int(self, value, byte_width=0): - """Encodes signed integer value. + @property + def AsKeyBytes(self): + if self.IsKey: + return Key(self._Indirect(), self._byte_width).Bytes + else: + raise self._ConvertError(Type.KEY) - Args: - value: A signed integer value. - byte_width: Number of bytes to use: 1, 2, 4, or 8. - """ - bit_width = BitWidth.I(value) if byte_width == 0 else BitWidth.B(byte_width) - self._stack.append(Value.Int(value, bit_width)) + @property + def AsKey(self): + if self.IsKey: + return str(Key(self._Indirect(), self._byte_width)) + else: + raise self._ConvertError(Type.KEY) + + @property + def IsString(self): + return self._type is Type.STRING + + @property + def AsStringBytes(self): + if self.IsString: + return String(self._Indirect(), self._byte_width).Bytes + elif self.IsKey: + return self.AsKeyBytes + else: + raise self._ConvertError(Type.STRING) + + @property + def AsString(self): + if self.IsString: + return str(String(self._Indirect(), self._byte_width)) + elif self.IsKey: + return self.AsKey + else: + raise self._ConvertError(Type.STRING) - @InMap - def IndirectInt(self, value, byte_width=0): - """Encodes signed integer value indirectly. + def MutateString(self, value): + return String(self._Indirect(), self._byte_width).Mutate(value) - Args: - value: A signed integer value. - byte_width: Number of bytes to use: 1, 2, 4, or 8. - """ - bit_width = BitWidth.I(value) if byte_width == 0 else BitWidth.B(byte_width) - self._PushIndirect(value, Type.INDIRECT_INT, bit_width) + @property + def IsBlob(self): + return self._type is Type.BLOB - @InMap - def UInt(self, value, byte_width=0): - """Encodes unsigned integer value. + @property + def AsBlob(self): + if self.IsBlob: + return Blob(self._Indirect(), self._byte_width).Bytes + else: + raise self._ConvertError(Type.BLOB) - Args: - value: An unsigned integer value. - byte_width: Number of bytes to use: 1, 2, 4, or 8. - """ - bit_width = BitWidth.U(value) if byte_width == 0 else BitWidth.B(byte_width) - self._stack.append(Value.UInt(value, bit_width)) + @property + def IsAnyVector(self): + return self.IsVector or self.IsTypedVector or self.IsFixedTypedVector() - @InMap - def IndirectUInt(self, value, byte_width=0): - """Encodes unsigned integer value indirectly. + @property + def IsVector(self): + return self._type in (Type.VECTOR, Type.MAP) - Args: - value: An unsigned integer value. - byte_width: Number of bytes to use: 1, 2, 4, or 8. - """ - bit_width = BitWidth.U(value) if byte_width == 0 else BitWidth.B(byte_width) - self._PushIndirect(value, Type.INDIRECT_UINT, bit_width) + @property + def AsVector(self): + if self.IsVector: + return Vector(self._Indirect(), self._byte_width) + else: + raise self._ConvertError(Type.VECTOR) + + @property + def IsTypedVector(self): + return Type.IsTypedVector(self._type) + + @property + def AsTypedVector(self): + if self.IsTypedVector: + return TypedVector( + self._Indirect(), + self._byte_width, + Type.ToTypedVectorElementType(self._type), + ) + else: + raise self._ConvertError("TYPED_VECTOR") - @InMap - def Float(self, value, byte_width=0): - """Encodes floating point value. + @property + def IsFixedTypedVector(self): + return Type.IsFixedTypedVector(self._type) - Args: - value: A floating point value. - byte_width: Number of bytes to use: 4 or 8. - """ - bit_width = BitWidth.F(value) if byte_width == 0 else BitWidth.B(byte_width) - self._stack.append(Value.Float(value, bit_width)) + @property + def AsFixedTypedVector(self): + if self.IsFixedTypedVector: + element_type, size = Type.ToFixedTypedVectorElementType(self._type) + return TypedVector(self._Indirect(), self._byte_width, element_type, size) + else: + raise self._ConvertError("FIXED_TYPED_VECTOR") - @InMap - def IndirectFloat(self, value, byte_width=0): - """Encodes floating point value indirectly. + @property + def IsMap(self): + return self._type is Type.MAP - Args: - value: A floating point value. - byte_width: Number of bytes to use: 4 or 8. - """ - bit_width = BitWidth.F(value) if byte_width == 0 else BitWidth.B(byte_width) - self._PushIndirect(value, Type.INDIRECT_FLOAT, bit_width) + @property + def AsMap(self): + if self.IsMap: + return Map(self._Indirect(), self._byte_width) + else: + raise self._ConvertError(Type.MAP) + + @property + def Value(self): + """Converts current reference to value of corresponding type. + + This is equivalent to calling `AsInt` for integer values, `AsFloat` for + floating point values, etc. + + Returns: + Value of corresponding type. + """ + if self.IsNull: + return None + elif self.IsBool: + return self.AsBool + elif self.IsInt: + return self.AsInt + elif self.IsFloat: + return self.AsFloat + elif self.IsString: + return self.AsString + elif self.IsKey: + return self.AsKey + elif self.IsBlob: + return self.AsBlob + elif self.IsMap: + return self.AsMap.Value + elif self.IsVector: + return self.AsVector.Value + elif self.IsTypedVector: + return self.AsTypedVector.Value + elif self.IsFixedTypedVector: + return self.AsFixedTypedVector.Value + else: + raise TypeError("cannot convert %r to value" % self) - def _StartVector(self): - """Starts vector construction.""" - return len(self._stack) - def _EndVector(self, start, typed, fixed): - """Finishes vector construction by encodung its elements.""" - vec = self._CreateVector(self._stack[start:], typed, fixed) - del self._stack[start:] - self._stack.append(vec) - return vec.Value +def _IsIterable(obj): + try: + iter(obj) + return True + except TypeError: + return False - @contextlib.contextmanager - def Vector(self, key=None): - if key: - self.Key(key) - try: - start = self._StartVector() - yield self - finally: - self._EndVector(start, typed=False, fixed=False) +class Value: + """Class to represent given value during the encoding process.""" + + @staticmethod + def Null(): + return Value(0, Type.NULL, BitWidth.W8) + + @staticmethod + def Bool(value): + return Value(value, Type.BOOL, BitWidth.W8) + + @staticmethod + def Int(value, bit_width): + return Value(value, Type.INT, bit_width) + + @staticmethod + def UInt(value, bit_width): + return Value(value, Type.UINT, bit_width) + + @staticmethod + def Float(value, bit_width): + return Value(value, Type.FLOAT, bit_width) + + @staticmethod + def Key(offset): + return Value(offset, Type.KEY, BitWidth.W8) + + def __init__(self, value, type_, min_bit_width): + self._value = value + self._type = type_ + + # For scalars: of itself, for vector: of its elements, for string: length. + self._min_bit_width = min_bit_width + + @property + def Value(self): + return self._value + + @property + def Type(self): + return self._type + + @property + def MinBitWidth(self): + return self._min_bit_width + + def StoredPackedType(self, parent_bit_width=BitWidth.W8): + return Type.Pack(self._type, self.StoredWidth(parent_bit_width)) + + # We have an absolute offset, but want to store a relative offset + # elem_index elements beyond the current buffer end. Since whether + # the relative offset fits in a certain byte_width depends on + # the size of the elements before it (and their alignment), we have + # to test for each size in turn. + def ElemWidth(self, buf_size, elem_index=0): + if Type.IsInline(self._type): + return self._min_bit_width + for byte_width in 1, 2, 4, 8: + offset_loc = ( + buf_size + _PaddingBytes(buf_size, byte_width) + elem_index * byte_width + ) + bit_width = BitWidth.U(offset_loc - self._value) + if byte_width == (1 << bit_width): + return bit_width + raise ValueError("relative offset is too big") + + def StoredWidth(self, parent_bit_width=BitWidth.W8): + if Type.IsInline(self._type): + return max(self._min_bit_width, parent_bit_width) + return self._min_bit_width + + def __repr__(self): + return "Value(%s, %s, %s)" % (self._value, self._type, self._min_bit_width) + + def __str__(self): + return str(self._value) - @InMap - def VectorFromElements(self, elements): - """Encodes sequence of any elements as a vector. - Args: - elements: sequence of elements, they may have different types. - """ - with self.Vector(): - for e in elements: - self.Add(e) +def InMap(func): + def wrapper(self, *args, **kwargs): + if isinstance(args[0], str): + self.Key(args[0]) + func(self, *args[1:], **kwargs) + else: + func(self, *args, **kwargs) - @contextlib.contextmanager - def TypedVector(self, key=None): - if key: - self.Key(key) + return wrapper - try: - start = self._StartVector() - yield self - finally: - self._EndVector(start, typed=True, fixed=False) - - @InMap - def TypedVectorFromElements(self, elements, element_type=None): - """Encodes sequence of elements of the same type as typed vector. - - Args: - elements: Sequence of elements, they must be of the same type. - element_type: Suggested element type. Setting it to None means determining - correct value automatically based on the given elements. - """ - if isinstance(elements, array.array): - if elements.typecode == 'f': - self._WriteScalarVector(Type.FLOAT, 4, elements, fixed=False) - elif elements.typecode == 'd': - self._WriteScalarVector(Type.FLOAT, 8, elements, fixed=False) - elif elements.typecode in ('b', 'h', 'i', 'l', 'q'): - self._WriteScalarVector( - Type.INT, elements.itemsize, elements, fixed=False - ) - elif elements.typecode in ('B', 'H', 'I', 'L', 'Q'): - self._WriteScalarVector( - Type.UINT, elements.itemsize, elements, fixed=False - ) - else: - raise ValueError('unsupported array typecode: %s' % elements.typecode) - else: - add = self.Add if element_type is None else self.Adder(element_type) - with self.TypedVector(): - for e in elements: - add(e) - - @InMap - def FixedTypedVectorFromElements( - self, elements, element_type=None, byte_width=0 - ): - """Encodes sequence of elements of the same type as fixed typed vector. - - Args: - elements: Sequence of elements, they must be of the same type. Allowed - types are `Type.INT`, `Type.UINT`, `Type.FLOAT`. Allowed number of - elements are 2, 3, or 4. - element_type: Suggested element type. Setting it to None means determining - correct value automatically based on the given elements. - byte_width: Number of bytes to use per element. For `Type.INT` and - `Type.UINT`: 1, 2, 4, or 8. For `Type.FLOAT`: 4 or 8. Setting it to 0 - means determining correct value automatically based on the given - elements. - """ - if not 2 <= len(elements) <= 4: - raise ValueError('only 2, 3, or 4 elements are supported') - types = {type(e) for e in elements} - if len(types) != 1: - raise TypeError('all elements must be of the same type') +def InMapForString(func): + def wrapper(self, *args): + if len(args) == 1: + func(self, args[0]) + elif len(args) == 2: + self.Key(args[0]) + func(self, args[1]) + else: + raise ValueError("invalid number of arguments") - (type_,) = types + return wrapper - if element_type is None: - element_type = {int: Type.INT, float: Type.FLOAT}.get(type_) - if not element_type: - raise TypeError('unsupported element_type: %s' % type_) - if byte_width == 0: - width = { - Type.UINT: BitWidth.U, - Type.INT: BitWidth.I, - Type.FLOAT: BitWidth.F, - }[element_type] - byte_width = 1 << max(width(e) for e in elements) +class Pool: + """Collection of (data, offset) pairs sorted by data for quick access.""" - self._WriteScalarVector(element_type, byte_width, elements, fixed=True) + def __init__(self): + self._pool = [] # sorted list of (data, offset) tuples - def _StartMap(self): - """Starts map construction.""" - return len(self._stack) + def FindOrInsert(self, data, offset): + do = data, offset + index = _BinarySearch(self._pool, do, lambda a, b: a[0] < b[0]) + if index != -1: + _, offset = self._pool[index] + return offset + self._pool.insert(index, do) + return None - def _EndMap(self, start): - """Finishes map construction by encodung its elements.""" - # Interleaved keys and values on the stack. - stack = self._stack[start:] + def Clear(self): + self._pool = [] - if len(stack) % 2 != 0: - raise RuntimeError('must be even number of keys and values') + @property + def Elements(self): + return [data for data, _ in self._pool] - for key in stack[::2]: - if key.Type is not Type.KEY: - raise RuntimeError('all map keys must be of %s type' % Type.KEY) - pairs = zip(stack[::2], stack[1::2]) # [(key, value), ...] - pairs = sorted(pairs, key=lambda pair: self._ReadKey(pair[0].Value)) +class Builder: + """Helper class to encode structural data into flexbuffers format.""" + + def __init__( + self, + share_strings=False, + share_keys=True, + force_min_bit_width=BitWidth.W8, + ): + self._share_strings = share_strings + self._share_keys = share_keys + self._force_min_bit_width = force_min_bit_width + + self._string_pool = Pool() + self._key_pool = Pool() + + self._finished = False + self._buf = bytearray() + self._stack = [] + + def __len__(self): + return len(self._buf) + + @property + def StringPool(self): + return self._string_pool + + @property + def KeyPool(self): + return self._key_pool + + def Clear(self): + self._string_pool.Clear() + self._key_pool.Clear() + self._finished = False + self._buf = bytearray() + self._stack = [] + + def Finish(self): + """Finishes encoding process and returns underlying buffer.""" + if self._finished: + raise RuntimeError("builder has been already finished") + + # If you hit this exception, you likely have objects that were never + # included in a parent. You need to have exactly one root to finish a + # buffer. Check your Start/End calls are matched, and all objects are inside + # some other object. + if len(self._stack) != 1: + raise RuntimeError("internal stack size must be one") + + value = self._stack[0] + byte_width = self._Align(value.ElemWidth(len(self._buf))) + self._WriteAny(value, byte_width=byte_width) # Root value + self._Write(U, value.StoredPackedType(), byte_width=1) # Root type + self._Write(U, byte_width, byte_width=1) # Root size + + self.finished = True + return self._buf + + def _ReadKey(self, offset): + key = self._buf[offset:] + return key[: key.find(0)] + + def _Align(self, alignment): + byte_width = 1 << alignment + self._buf.extend(b"\x00" * _PaddingBytes(len(self._buf), byte_width)) + return byte_width + + def _Write(self, fmt, value, byte_width): + self._buf.extend(_Pack(fmt, value, byte_width)) + + def _WriteVector(self, fmt, values, byte_width): + self._buf.extend(_PackVector(fmt, values, byte_width)) + + def _WriteOffset(self, offset, byte_width): + relative_offset = len(self._buf) - offset + assert byte_width == 8 or relative_offset < (1 << (8 * byte_width)) + self._Write(U, relative_offset, byte_width) + + def _WriteAny(self, value, byte_width): + fmt = { + Type.NULL: U, + Type.BOOL: U, + Type.INT: I, + Type.UINT: U, + Type.FLOAT: F, + }.get(value.Type) + if fmt: + self._Write(fmt, value.Value, byte_width) + else: + self._WriteOffset(value.Value, byte_width) + + def _WriteBlob(self, data, append_zero, type_): + bit_width = BitWidth.U(len(data)) + byte_width = self._Align(bit_width) + self._Write(U, len(data), byte_width) + loc = len(self._buf) + self._buf.extend(data) + if append_zero: + self._buf.append(0) + self._stack.append(Value(loc, type_, bit_width)) + return loc + + def _WriteScalarVector(self, element_type, byte_width, elements, fixed): + """Writes scalar vector elements to the underlying buffer.""" + bit_width = BitWidth.B(byte_width) + # If you get this exception, you're trying to write a vector with a size + # field that is bigger than the scalars you're trying to write (e.g. a + # byte vector > 255 elements). For such types, write a "blob" instead. + if BitWidth.U(len(elements)) > bit_width: + raise ValueError("too many elements for the given byte_width") + + self._Align(bit_width) + if not fixed: + self._Write(U, len(elements), byte_width) + + loc = len(self._buf) + + fmt = {Type.INT: I, Type.UINT: U, Type.FLOAT: F}.get(element_type) + if not fmt: + raise TypeError("unsupported element_type") + self._WriteVector(fmt, elements, byte_width) + + type_ = Type.ToTypedVector(element_type, len(elements) if fixed else 0) + self._stack.append(Value(loc, type_, bit_width)) + return loc + + def _CreateVector(self, elements, typed, fixed, keys=None): + """Writes vector elements to the underlying buffer.""" + length = len(elements) + + if fixed and not typed: + raise ValueError("fixed vector must be typed") + + # Figure out smallest bit width we can store this vector with. + bit_width = max(self._force_min_bit_width, BitWidth.U(length)) + prefix_elems = 1 # Vector size + if keys: + bit_width = max(bit_width, keys.ElemWidth(len(self._buf))) + prefix_elems += 2 # Offset to the keys vector and its byte width. + + vector_type = Type.KEY + # Check bit widths and types for all elements. + for i, e in enumerate(elements): + bit_width = max(bit_width, e.ElemWidth(len(self._buf), prefix_elems + i)) + + if typed: + if i == 0: + vector_type = e.Type + else: + if vector_type != e.Type: + raise RuntimeError( + "typed vector elements must be of the same type" + ) + + if fixed and not Type.IsFixedTypedVectorElementType(vector_type): + raise RuntimeError("must be fixed typed vector element type") + + byte_width = self._Align(bit_width) + # Write vector. First the keys width/offset if available, and size. + if keys: + self._WriteOffset(keys.Value, byte_width) + self._Write(U, 1 << keys.MinBitWidth, byte_width) + + if not fixed: + self._Write(U, length, byte_width) + + # Then the actual data. + loc = len(self._buf) + for e in elements: + self._WriteAny(e, byte_width) - del self._stack[start:] - for pair in pairs: - self._stack.extend(pair) + # Then the types. + if not typed: + for e in elements: + self._buf.append(e.StoredPackedType(bit_width)) - keys = self._CreateVector(self._stack[start::2], typed=True, fixed=False) - values = self._CreateVector( - self._stack[start + 1 :: 2], typed=False, fixed=False, keys=keys - ) + if keys: + type_ = Type.MAP + else: + if typed: + type_ = Type.ToTypedVector(vector_type, length if fixed else 0) + else: + type_ = Type.VECTOR + + return Value(loc, type_, bit_width) + + def _PushIndirect(self, value, type_, bit_width): + byte_width = self._Align(bit_width) + loc = len(self._buf) + fmt = {Type.INDIRECT_INT: I, Type.INDIRECT_UINT: U, Type.INDIRECT_FLOAT: F}[ + type_ + ] + self._Write(fmt, value, byte_width) + self._stack.append(Value(loc, type_, bit_width)) + + @InMapForString + def String(self, value): + """Encodes string value.""" + reset_to = len(self._buf) + encoded = value.encode("utf-8") + loc = self._WriteBlob(encoded, append_zero=True, type_=Type.STRING) + if self._share_strings: + prev_loc = self._string_pool.FindOrInsert(encoded, loc) + if prev_loc is not None: + del self._buf[reset_to:] + self._stack[-1]._value = loc = prev_loc # pylint: disable=protected-access + + return loc + + @InMap + def Blob(self, value): + """Encodes binary blob value. + + Args: + value: A byte/bytearray value to encode + + Returns: + Offset of the encoded value in underlying the byte buffer. + """ + return self._WriteBlob(value, append_zero=False, type_=Type.BLOB) + + def Key(self, value): + """Encodes key value. + + Args: + value: A byte/bytearray/str value to encode. Byte object must not contain + zero bytes. String object must be convertible to ASCII. + + Returns: + Offset of the encoded value in the underlying byte buffer. + """ + if isinstance(value, (bytes, bytearray)): + encoded = value + else: + encoded = value.encode("ascii") + + if 0 in encoded: + raise ValueError("key contains zero byte") + + loc = len(self._buf) + self._buf.extend(encoded) + self._buf.append(0) + if self._share_keys: + prev_loc = self._key_pool.FindOrInsert(encoded, loc) + if prev_loc is not None: + del self._buf[loc:] + loc = prev_loc + + self._stack.append(Value.Key(loc)) + return loc + + def Null(self, key=None): + """Encodes None value.""" + if key: + self.Key(key) + self._stack.append(Value.Null()) + + @InMap + def Bool(self, value): + """Encodes boolean value. + + Args: + value: A boolean value. + """ + self._stack.append(Value.Bool(value)) + + @InMap + def Int(self, value, byte_width=0): + """Encodes signed integer value. + + Args: + value: A signed integer value. + byte_width: Number of bytes to use: 1, 2, 4, or 8. + """ + bit_width = BitWidth.I(value) if byte_width == 0 else BitWidth.B(byte_width) + self._stack.append(Value.Int(value, bit_width)) + + @InMap + def IndirectInt(self, value, byte_width=0): + """Encodes signed integer value indirectly. + + Args: + value: A signed integer value. + byte_width: Number of bytes to use: 1, 2, 4, or 8. + """ + bit_width = BitWidth.I(value) if byte_width == 0 else BitWidth.B(byte_width) + self._PushIndirect(value, Type.INDIRECT_INT, bit_width) + + @InMap + def UInt(self, value, byte_width=0): + """Encodes unsigned integer value. + + Args: + value: An unsigned integer value. + byte_width: Number of bytes to use: 1, 2, 4, or 8. + """ + bit_width = BitWidth.U(value) if byte_width == 0 else BitWidth.B(byte_width) + self._stack.append(Value.UInt(value, bit_width)) + + @InMap + def IndirectUInt(self, value, byte_width=0): + """Encodes unsigned integer value indirectly. + + Args: + value: An unsigned integer value. + byte_width: Number of bytes to use: 1, 2, 4, or 8. + """ + bit_width = BitWidth.U(value) if byte_width == 0 else BitWidth.B(byte_width) + self._PushIndirect(value, Type.INDIRECT_UINT, bit_width) + + @InMap + def Float(self, value, byte_width=0): + """Encodes floating point value. + + Args: + value: A floating point value. + byte_width: Number of bytes to use: 4 or 8. + """ + bit_width = BitWidth.F(value) if byte_width == 0 else BitWidth.B(byte_width) + self._stack.append(Value.Float(value, bit_width)) + + @InMap + def IndirectFloat(self, value, byte_width=0): + """Encodes floating point value indirectly. + + Args: + value: A floating point value. + byte_width: Number of bytes to use: 4 or 8. + """ + bit_width = BitWidth.F(value) if byte_width == 0 else BitWidth.B(byte_width) + self._PushIndirect(value, Type.INDIRECT_FLOAT, bit_width) + + def _StartVector(self): + """Starts vector construction.""" + return len(self._stack) + + def _EndVector(self, start, typed, fixed): + """Finishes vector construction by encodung its elements.""" + vec = self._CreateVector(self._stack[start:], typed, fixed) + del self._stack[start:] + self._stack.append(vec) + return vec.Value + + @contextlib.contextmanager + def Vector(self, key=None): + if key: + self.Key(key) + + try: + start = self._StartVector() + yield self + finally: + self._EndVector(start, typed=False, fixed=False) + + @InMap + def VectorFromElements(self, elements): + """Encodes sequence of any elements as a vector. + + Args: + elements: sequence of elements, they may have different types. + """ + with self.Vector(): + for e in elements: + self.Add(e) + + @contextlib.contextmanager + def TypedVector(self, key=None): + if key: + self.Key(key) + + try: + start = self._StartVector() + yield self + finally: + self._EndVector(start, typed=True, fixed=False) + + @InMap + def TypedVectorFromElements(self, elements, element_type=None): + """Encodes sequence of elements of the same type as typed vector. + + Args: + elements: Sequence of elements, they must be of the same type. + element_type: Suggested element type. Setting it to None means determining + correct value automatically based on the given elements. + """ + if isinstance(elements, array.array): + if elements.typecode == "f": + self._WriteScalarVector(Type.FLOAT, 4, elements, fixed=False) + elif elements.typecode == "d": + self._WriteScalarVector(Type.FLOAT, 8, elements, fixed=False) + elif elements.typecode in ("b", "h", "i", "l", "q"): + self._WriteScalarVector( + Type.INT, elements.itemsize, elements, fixed=False + ) + elif elements.typecode in ("B", "H", "I", "L", "Q"): + self._WriteScalarVector( + Type.UINT, elements.itemsize, elements, fixed=False + ) + else: + raise ValueError("unsupported array typecode: %s" % elements.typecode) + else: + add = self.Add if element_type is None else self.Adder(element_type) + with self.TypedVector(): + for e in elements: + add(e) + + @InMap + def FixedTypedVectorFromElements(self, elements, element_type=None, byte_width=0): + """Encodes sequence of elements of the same type as fixed typed vector. + + Args: + elements: Sequence of elements, they must be of the same type. Allowed + types are `Type.INT`, `Type.UINT`, `Type.FLOAT`. Allowed number of + elements are 2, 3, or 4. + element_type: Suggested element type. Setting it to None means determining + correct value automatically based on the given elements. + byte_width: Number of bytes to use per element. For `Type.INT` and + `Type.UINT`: 1, 2, 4, or 8. For `Type.FLOAT`: 4 or 8. Setting it to 0 + means determining correct value automatically based on the given + elements. + """ + if not 2 <= len(elements) <= 4: + raise ValueError("only 2, 3, or 4 elements are supported") + + types = {type(e) for e in elements} + if len(types) != 1: + raise TypeError("all elements must be of the same type") + + (type_,) = types + + if element_type is None: + element_type = {int: Type.INT, float: Type.FLOAT}.get(type_) + if not element_type: + raise TypeError("unsupported element_type: %s" % type_) + + if byte_width == 0: + width = { + Type.UINT: BitWidth.U, + Type.INT: BitWidth.I, + Type.FLOAT: BitWidth.F, + }[element_type] + byte_width = 1 << max(width(e) for e in elements) + + self._WriteScalarVector(element_type, byte_width, elements, fixed=True) + + def _StartMap(self): + """Starts map construction.""" + return len(self._stack) + + def _EndMap(self, start): + """Finishes map construction by encodung its elements.""" + # Interleaved keys and values on the stack. + stack = self._stack[start:] + + if len(stack) % 2 != 0: + raise RuntimeError("must be even number of keys and values") + + for key in stack[::2]: + if key.Type is not Type.KEY: + raise RuntimeError("all map keys must be of %s type" % Type.KEY) + + pairs = zip(stack[::2], stack[1::2]) # [(key, value), ...] + pairs = sorted(pairs, key=lambda pair: self._ReadKey(pair[0].Value)) + + del self._stack[start:] + for pair in pairs: + self._stack.extend(pair) + + keys = self._CreateVector(self._stack[start::2], typed=True, fixed=False) + values = self._CreateVector( + self._stack[start + 1 :: 2], typed=False, fixed=False, keys=keys + ) - del self._stack[start:] - self._stack.append(values) - return values.Value + del self._stack[start:] + self._stack.append(values) + return values.Value + + @contextlib.contextmanager + def Map(self, key=None): + if key: + self.Key(key) + + try: + start = self._StartMap() + yield self + finally: + self._EndMap(start) + + def MapFromElements(self, elements): + start = self._StartMap() + for k, v in elements.items(): + self.Key(k) + self.Add(v) + self._EndMap(start) + + def Adder(self, type_): + return { + Type.BOOL: self.Bool, + Type.INT: self.Int, + Type.INDIRECT_INT: self.IndirectInt, + Type.UINT: self.UInt, + Type.INDIRECT_UINT: self.IndirectUInt, + Type.FLOAT: self.Float, + Type.INDIRECT_FLOAT: self.IndirectFloat, + Type.KEY: self.Key, + Type.BLOB: self.Blob, + Type.STRING: self.String, + }[type_] + + @InMapForString + def Add(self, value): + """Encodes value of any supported type.""" + if value is None: + self.Null() + elif isinstance(value, bool): + self.Bool(value) + elif isinstance(value, int): + self.Int(value) + elif isinstance(value, float): + self.Float(value) + elif isinstance(value, str): + self.String(value) + elif isinstance(value, (bytes, bytearray)): + self.Blob(value) + elif isinstance(value, dict): + with self.Map(): + for k, v in value.items(): + self.Key(k) + self.Add(v) + elif isinstance(value, array.array): + self.TypedVectorFromElements(value) + elif _IsIterable(value): + self.VectorFromElements(value) + else: + raise TypeError("unsupported python type: %s" % type(value)) - @contextlib.contextmanager - def Map(self, key=None): - if key: - self.Key(key) + @property + def LastValue(self): + return self._stack[-1] - try: - start = self._StartMap() - yield self - finally: - self._EndMap(start) - - def MapFromElements(self, elements): - start = self._StartMap() - for k, v in elements.items(): - self.Key(k) - self.Add(v) - self._EndMap(start) - - def Adder(self, type_): - return { - Type.BOOL: self.Bool, - Type.INT: self.Int, - Type.INDIRECT_INT: self.IndirectInt, - Type.UINT: self.UInt, - Type.INDIRECT_UINT: self.IndirectUInt, - Type.FLOAT: self.Float, - Type.INDIRECT_FLOAT: self.IndirectFloat, - Type.KEY: self.Key, - Type.BLOB: self.Blob, - Type.STRING: self.String, - }[type_] - - @InMapForString - def Add(self, value): - """Encodes value of any supported type.""" - if value is None: - self.Null() - elif isinstance(value, bool): - self.Bool(value) - elif isinstance(value, int): - self.Int(value) - elif isinstance(value, float): - self.Float(value) - elif isinstance(value, str): - self.String(value) - elif isinstance(value, (bytes, bytearray)): - self.Blob(value) - elif isinstance(value, dict): - with self.Map(): - for k, v in value.items(): - self.Key(k) - self.Add(v) - elif isinstance(value, array.array): - self.TypedVectorFromElements(value) - elif _IsIterable(value): - self.VectorFromElements(value) - else: - raise TypeError('unsupported python type: %s' % type(value)) - - @property - def LastValue(self): - return self._stack[-1] - - @InMap - def ReuseValue(self, value): - self._stack.append(value) + @InMap + def ReuseValue(self, value): + self._stack.append(value) def GetRoot(buf): - """Returns root `Ref` object for the given buffer.""" - if len(buf) < 3: - raise ValueError('buffer is too small') - byte_width = buf[-1] - return Ref.PackedType( - Buf(buf, -(2 + byte_width)), byte_width, packed_type=buf[-2] - ) + """Returns root `Ref` object for the given buffer.""" + if len(buf) < 3: + raise ValueError("buffer is too small") + byte_width = buf[-1] + return Ref.PackedType(Buf(buf, -(2 + byte_width)), byte_width, packed_type=buf[-2]) def Dumps(obj): - """Returns bytearray with the encoded python object.""" - fbb = Builder() - fbb.Add(obj) - return fbb.Finish() + """Returns bytearray with the encoded python object.""" + fbb = Builder() + fbb.Add(obj) + return fbb.Finish() def Loads(buf): - """Returns python object decoded from the buffer.""" - return GetRoot(buf).Value + """Returns python object decoded from the buffer.""" + return GetRoot(buf).Value diff --git a/flatbuffers/number_types.py b/flatbuffers/number_types.py index e47f66f12..e55a46a64 100644 --- a/flatbuffers/number_types.py +++ b/flatbuffers/number_types.py @@ -28,155 +28,155 @@ class BoolFlags(object): - bytewidth = 1 - min_val = False - max_val = True - py_type = bool - name = "bool" - packer_type = packer.boolean + bytewidth = 1 + min_val = False + max_val = True + py_type = bool + name = "bool" + packer_type = packer.boolean class Uint8Flags(object): - bytewidth = 1 - min_val = 0 - max_val = (2**8) - 1 - py_type = int - name = "uint8" - packer_type = packer.uint8 + bytewidth = 1 + min_val = 0 + max_val = (2**8) - 1 + py_type = int + name = "uint8" + packer_type = packer.uint8 class Uint16Flags(object): - bytewidth = 2 - min_val = 0 - max_val = (2**16) - 1 - py_type = int - name = "uint16" - packer_type = packer.uint16 + bytewidth = 2 + min_val = 0 + max_val = (2**16) - 1 + py_type = int + name = "uint16" + packer_type = packer.uint16 class Uint32Flags(object): - bytewidth = 4 - min_val = 0 - max_val = (2**32) - 1 - py_type = int - name = "uint32" - packer_type = packer.uint32 + bytewidth = 4 + min_val = 0 + max_val = (2**32) - 1 + py_type = int + name = "uint32" + packer_type = packer.uint32 class Uint64Flags(object): - bytewidth = 8 - min_val = 0 - max_val = (2**64) - 1 - py_type = int - name = "uint64" - packer_type = packer.uint64 + bytewidth = 8 + min_val = 0 + max_val = (2**64) - 1 + py_type = int + name = "uint64" + packer_type = packer.uint64 class Int8Flags(object): - bytewidth = 1 - min_val = -(2**7) - max_val = (2**7) - 1 - py_type = int - name = "int8" - packer_type = packer.int8 + bytewidth = 1 + min_val = -(2**7) + max_val = (2**7) - 1 + py_type = int + name = "int8" + packer_type = packer.int8 class Int16Flags(object): - bytewidth = 2 - min_val = -(2**15) - max_val = (2**15) - 1 - py_type = int - name = "int16" - packer_type = packer.int16 + bytewidth = 2 + min_val = -(2**15) + max_val = (2**15) - 1 + py_type = int + name = "int16" + packer_type = packer.int16 class Int32Flags(object): - bytewidth = 4 - min_val = -(2**31) - max_val = (2**31) - 1 - py_type = int - name = "int32" - packer_type = packer.int32 + bytewidth = 4 + min_val = -(2**31) + max_val = (2**31) - 1 + py_type = int + name = "int32" + packer_type = packer.int32 class Int64Flags(object): - bytewidth = 8 - min_val = -(2**63) - max_val = (2**63) - 1 - py_type = int - name = "int64" - packer_type = packer.int64 + bytewidth = 8 + min_val = -(2**63) + max_val = (2**63) - 1 + py_type = int + name = "int64" + packer_type = packer.int64 class Float32Flags(object): - bytewidth = 4 - min_val = None - max_val = None - py_type = float - name = "float32" - packer_type = packer.float32 + bytewidth = 4 + min_val = None + max_val = None + py_type = float + name = "float32" + packer_type = packer.float32 class Float64Flags(object): - bytewidth = 8 - min_val = None - max_val = None - py_type = float - name = "float64" - packer_type = packer.float64 + bytewidth = 8 + min_val = None + max_val = None + py_type = float + name = "float64" + packer_type = packer.float64 class SOffsetTFlags(Int32Flags): - pass + pass class UOffsetTFlags(Uint32Flags): - pass + pass class VOffsetTFlags(Uint16Flags): - pass + pass def valid_number(n, flags): - if flags.min_val is None and flags.max_val is None: - return True - return flags.min_val <= n <= flags.max_val + if flags.min_val is None and flags.max_val is None: + return True + return flags.min_val <= n <= flags.max_val def enforce_number(n, flags): - if flags.min_val is None and flags.max_val is None: - return - if not flags.min_val <= n <= flags.max_val: - raise TypeError("bad number %s for type %s" % (str(n), flags.name)) + if flags.min_val is None and flags.max_val is None: + return + if not flags.min_val <= n <= flags.max_val: + raise TypeError("bad number %s for type %s" % (str(n), flags.name)) def float32_to_uint32(n): - packed = struct.pack("<1f", n) - (converted,) = struct.unpack("<1L", packed) - return converted + packed = struct.pack("<1f", n) + (converted,) = struct.unpack("<1L", packed) + return converted def uint32_to_float32(n): - packed = struct.pack("<1L", n) - (unpacked,) = struct.unpack("<1f", packed) - return unpacked + packed = struct.pack("<1L", n) + (unpacked,) = struct.unpack("<1f", packed) + return unpacked def float64_to_uint64(n): - packed = struct.pack("<1d", n) - (converted,) = struct.unpack("<1Q", packed) - return converted + packed = struct.pack("<1d", n) + (converted,) = struct.unpack("<1Q", packed) + return converted def uint64_to_float64(n): - packed = struct.pack("<1Q", n) - (unpacked,) = struct.unpack("<1d", packed) - return unpacked + packed = struct.pack("<1Q", n) + (unpacked,) = struct.unpack("<1d", packed) + return unpacked def to_numpy_type(number_type): - if np is not None: - return np.dtype(number_type.name).newbyteorder("<") - else: - raise NumpyRequiredForThisFeature("Numpy was not found.") + if np is not None: + return np.dtype(number_type.name).newbyteorder("<") + else: + raise NumpyRequiredForThisFeature("Numpy was not found.") diff --git a/flatbuffers/reflection/BaseType.py b/flatbuffers/reflection/BaseType.py index a64a3eb4f..7c1bec0b0 100644 --- a/flatbuffers/reflection/BaseType.py +++ b/flatbuffers/reflection/BaseType.py @@ -2,6 +2,7 @@ # namespace: reflection + class BaseType(object): None_ = 0 UType = 1 diff --git a/flatbuffers/reflection/Enum.py b/flatbuffers/reflection/Enum.py index 2c366d1cb..5a04e54c3 100644 --- a/flatbuffers/reflection/Enum.py +++ b/flatbuffers/reflection/Enum.py @@ -4,10 +4,12 @@ import flatbuffers from flatbuffers.compat import import_numpy + np = import_numpy() + class Enum(object): - __slots__ = ['_tab'] + __slots__ = ["_tab"] @classmethod def GetRootAs(cls, buf, offset=0): @@ -20,9 +22,12 @@ def GetRootAs(cls, buf, offset=0): def GetRootAsEnum(cls, buf, offset=0): """This method is deprecated. Please switch to GetRootAs.""" return cls.GetRootAs(buf, offset) + @classmethod def EnumBufferHasIdentifier(cls, buf, offset, size_prefixed=False): - return flatbuffers.util.BufferHasIdentifier(buf, offset, b"\x42\x46\x42\x53", size_prefixed=size_prefixed) + return flatbuffers.util.BufferHasIdentifier( + buf, offset, b"\x42\x46\x42\x53", size_prefixed=size_prefixed + ) # Enum def Init(self, buf, pos): @@ -43,6 +48,7 @@ def Values(self, j): x += flatbuffers.number_types.UOffsetTFlags.py_type(j) * 4 x = self._tab.Indirect(x) from reflection.EnumVal import EnumVal + obj = EnumVal() obj.Init(self._tab.Bytes, x) return obj @@ -64,7 +70,9 @@ def ValuesIsNone(self): def IsUnion(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8)) if o != 0: - return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos)) + return bool( + self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos) + ) return False # Enum @@ -73,6 +81,7 @@ def UnderlyingType(self): if o != 0: x = self._tab.Indirect(o + self._tab.Pos) from reflection.Type import Type + obj = Type() obj.Init(self._tab.Bytes, x) return obj @@ -86,6 +95,7 @@ def Attributes(self, j): x += flatbuffers.number_types.UOffsetTFlags.py_type(j) * 4 x = self._tab.Indirect(x) from reflection.KeyValue import KeyValue + obj = KeyValue() obj.Init(self._tab.Bytes, x) return obj @@ -108,7 +118,9 @@ def Documentation(self, j): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(14)) if o != 0: a = self._tab.Vector(o) - return self._tab.String(a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 4)) + return self._tab.String( + a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 4) + ) return "" # Enum @@ -131,74 +143,110 @@ def DeclarationFile(self): return self._tab.String(o + self._tab.Pos) return None + def EnumStart(builder): builder.StartObject(7) + def Start(builder): EnumStart(builder) + def EnumAddName(builder, name): - builder.PrependUOffsetTRelativeSlot(0, flatbuffers.number_types.UOffsetTFlags.py_type(name), 0) + builder.PrependUOffsetTRelativeSlot( + 0, flatbuffers.number_types.UOffsetTFlags.py_type(name), 0 + ) + def AddName(builder, name): EnumAddName(builder, name) + def EnumAddValues(builder, values): - builder.PrependUOffsetTRelativeSlot(1, flatbuffers.number_types.UOffsetTFlags.py_type(values), 0) + builder.PrependUOffsetTRelativeSlot( + 1, flatbuffers.number_types.UOffsetTFlags.py_type(values), 0 + ) + def AddValues(builder, values): EnumAddValues(builder, values) + def EnumStartValuesVector(builder, numElems): return builder.StartVector(4, numElems, 4) + def StartValuesVector(builder, numElems): return EnumStartValuesVector(builder, numElems) + def EnumAddIsUnion(builder, isUnion): builder.PrependBoolSlot(2, isUnion, 0) + def AddIsUnion(builder, isUnion): EnumAddIsUnion(builder, isUnion) + def EnumAddUnderlyingType(builder, underlyingType): - builder.PrependUOffsetTRelativeSlot(3, flatbuffers.number_types.UOffsetTFlags.py_type(underlyingType), 0) + builder.PrependUOffsetTRelativeSlot( + 3, flatbuffers.number_types.UOffsetTFlags.py_type(underlyingType), 0 + ) + def AddUnderlyingType(builder, underlyingType): EnumAddUnderlyingType(builder, underlyingType) + def EnumAddAttributes(builder, attributes): - builder.PrependUOffsetTRelativeSlot(4, flatbuffers.number_types.UOffsetTFlags.py_type(attributes), 0) + builder.PrependUOffsetTRelativeSlot( + 4, flatbuffers.number_types.UOffsetTFlags.py_type(attributes), 0 + ) + def AddAttributes(builder, attributes): EnumAddAttributes(builder, attributes) + def EnumStartAttributesVector(builder, numElems): return builder.StartVector(4, numElems, 4) + def StartAttributesVector(builder, numElems): return EnumStartAttributesVector(builder, numElems) + def EnumAddDocumentation(builder, documentation): - builder.PrependUOffsetTRelativeSlot(5, flatbuffers.number_types.UOffsetTFlags.py_type(documentation), 0) + builder.PrependUOffsetTRelativeSlot( + 5, flatbuffers.number_types.UOffsetTFlags.py_type(documentation), 0 + ) + def AddDocumentation(builder, documentation): EnumAddDocumentation(builder, documentation) + def EnumStartDocumentationVector(builder, numElems): return builder.StartVector(4, numElems, 4) + def StartDocumentationVector(builder, numElems): return EnumStartDocumentationVector(builder, numElems) + def EnumAddDeclarationFile(builder, declarationFile): - builder.PrependUOffsetTRelativeSlot(6, flatbuffers.number_types.UOffsetTFlags.py_type(declarationFile), 0) + builder.PrependUOffsetTRelativeSlot( + 6, flatbuffers.number_types.UOffsetTFlags.py_type(declarationFile), 0 + ) + def AddDeclarationFile(builder, declarationFile): EnumAddDeclarationFile(builder, declarationFile) + def EnumEnd(builder): return builder.EndObject() + def End(builder): return EnumEnd(builder) diff --git a/flatbuffers/reflection/EnumVal.py b/flatbuffers/reflection/EnumVal.py index 3789a16e4..fb1719c73 100644 --- a/flatbuffers/reflection/EnumVal.py +++ b/flatbuffers/reflection/EnumVal.py @@ -4,10 +4,12 @@ import flatbuffers from flatbuffers.compat import import_numpy + np = import_numpy() + class EnumVal(object): - __slots__ = ['_tab'] + __slots__ = ["_tab"] @classmethod def GetRootAs(cls, buf, offset=0): @@ -20,9 +22,12 @@ def GetRootAs(cls, buf, offset=0): def GetRootAsEnumVal(cls, buf, offset=0): """This method is deprecated. Please switch to GetRootAs.""" return cls.GetRootAs(buf, offset) + @classmethod def EnumValBufferHasIdentifier(cls, buf, offset, size_prefixed=False): - return flatbuffers.util.BufferHasIdentifier(buf, offset, b"\x42\x46\x42\x53", size_prefixed=size_prefixed) + return flatbuffers.util.BufferHasIdentifier( + buf, offset, b"\x42\x46\x42\x53", size_prefixed=size_prefixed + ) # EnumVal def Init(self, buf, pos): @@ -48,6 +53,7 @@ def UnionType(self): if o != 0: x = self._tab.Indirect(o + self._tab.Pos) from reflection.Type import Type + obj = Type() obj.Init(self._tab.Bytes, x) return obj @@ -58,7 +64,9 @@ def Documentation(self, j): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12)) if o != 0: a = self._tab.Vector(o) - return self._tab.String(a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 4)) + return self._tab.String( + a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 4) + ) return "" # EnumVal @@ -81,6 +89,7 @@ def Attributes(self, j): x += flatbuffers.number_types.UOffsetTFlags.py_type(j) * 4 x = self._tab.Indirect(x) from reflection.KeyValue import KeyValue + obj = KeyValue() obj.Init(self._tab.Bytes, x) return obj @@ -98,56 +107,82 @@ def AttributesIsNone(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(14)) return o == 0 + def EnumValStart(builder): builder.StartObject(6) + def Start(builder): EnumValStart(builder) + def EnumValAddName(builder, name): - builder.PrependUOffsetTRelativeSlot(0, flatbuffers.number_types.UOffsetTFlags.py_type(name), 0) + builder.PrependUOffsetTRelativeSlot( + 0, flatbuffers.number_types.UOffsetTFlags.py_type(name), 0 + ) + def AddName(builder, name): EnumValAddName(builder, name) + def EnumValAddValue(builder, value): builder.PrependInt64Slot(1, value, 0) + def AddValue(builder, value): EnumValAddValue(builder, value) + def EnumValAddUnionType(builder, unionType): - builder.PrependUOffsetTRelativeSlot(3, flatbuffers.number_types.UOffsetTFlags.py_type(unionType), 0) + builder.PrependUOffsetTRelativeSlot( + 3, flatbuffers.number_types.UOffsetTFlags.py_type(unionType), 0 + ) + def AddUnionType(builder, unionType): EnumValAddUnionType(builder, unionType) + def EnumValAddDocumentation(builder, documentation): - builder.PrependUOffsetTRelativeSlot(4, flatbuffers.number_types.UOffsetTFlags.py_type(documentation), 0) + builder.PrependUOffsetTRelativeSlot( + 4, flatbuffers.number_types.UOffsetTFlags.py_type(documentation), 0 + ) + def AddDocumentation(builder, documentation): EnumValAddDocumentation(builder, documentation) + def EnumValStartDocumentationVector(builder, numElems): return builder.StartVector(4, numElems, 4) + def StartDocumentationVector(builder, numElems): return EnumValStartDocumentationVector(builder, numElems) + def EnumValAddAttributes(builder, attributes): - builder.PrependUOffsetTRelativeSlot(5, flatbuffers.number_types.UOffsetTFlags.py_type(attributes), 0) + builder.PrependUOffsetTRelativeSlot( + 5, flatbuffers.number_types.UOffsetTFlags.py_type(attributes), 0 + ) + def AddAttributes(builder, attributes): EnumValAddAttributes(builder, attributes) + def EnumValStartAttributesVector(builder, numElems): return builder.StartVector(4, numElems, 4) + def StartAttributesVector(builder, numElems): return EnumValStartAttributesVector(builder, numElems) + def EnumValEnd(builder): return builder.EndObject() + def End(builder): return EnumValEnd(builder) diff --git a/flatbuffers/reflection/Field.py b/flatbuffers/reflection/Field.py index 2cce39203..e70ebdc21 100644 --- a/flatbuffers/reflection/Field.py +++ b/flatbuffers/reflection/Field.py @@ -4,10 +4,12 @@ import flatbuffers from flatbuffers.compat import import_numpy + np = import_numpy() + class Field(object): - __slots__ = ['_tab'] + __slots__ = ["_tab"] @classmethod def GetRootAs(cls, buf, offset=0): @@ -20,9 +22,12 @@ def GetRootAs(cls, buf, offset=0): def GetRootAsField(cls, buf, offset=0): """This method is deprecated. Please switch to GetRootAs.""" return cls.GetRootAs(buf, offset) + @classmethod def FieldBufferHasIdentifier(cls, buf, offset, size_prefixed=False): - return flatbuffers.util.BufferHasIdentifier(buf, offset, b"\x42\x46\x42\x53", size_prefixed=size_prefixed) + return flatbuffers.util.BufferHasIdentifier( + buf, offset, b"\x42\x46\x42\x53", size_prefixed=size_prefixed + ) # Field def Init(self, buf, pos): @@ -41,6 +46,7 @@ def Type(self): if o != 0: x = self._tab.Indirect(o + self._tab.Pos) from reflection.Type import Type + obj = Type() obj.Init(self._tab.Bytes, x) return obj @@ -50,14 +56,18 @@ def Type(self): def Id(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8)) if o != 0: - return self._tab.Get(flatbuffers.number_types.Uint16Flags, o + self._tab.Pos) + return self._tab.Get( + flatbuffers.number_types.Uint16Flags, o + self._tab.Pos + ) return 0 # Field def Offset(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10)) if o != 0: - return self._tab.Get(flatbuffers.number_types.Uint16Flags, o + self._tab.Pos) + return self._tab.Get( + flatbuffers.number_types.Uint16Flags, o + self._tab.Pos + ) return 0 # Field @@ -71,28 +81,36 @@ def DefaultInteger(self): def DefaultReal(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(14)) if o != 0: - return self._tab.Get(flatbuffers.number_types.Float64Flags, o + self._tab.Pos) + return self._tab.Get( + flatbuffers.number_types.Float64Flags, o + self._tab.Pos + ) return 0.0 # Field def Deprecated(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(16)) if o != 0: - return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos)) + return bool( + self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos) + ) return False # Field def Required(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(18)) if o != 0: - return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos)) + return bool( + self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos) + ) return False # Field def Key(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(20)) if o != 0: - return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos)) + return bool( + self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos) + ) return False # Field @@ -103,6 +121,7 @@ def Attributes(self, j): x += flatbuffers.number_types.UOffsetTFlags.py_type(j) * 4 x = self._tab.Indirect(x) from reflection.KeyValue import KeyValue + obj = KeyValue() obj.Init(self._tab.Bytes, x) return obj @@ -125,7 +144,9 @@ def Documentation(self, j): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(24)) if o != 0: a = self._tab.Vector(o) - return self._tab.String(a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 4)) + return self._tab.String( + a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 4) + ) return "" # Field @@ -144,7 +165,9 @@ def DocumentationIsNone(self): def Optional(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(26)) if o != 0: - return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos)) + return bool( + self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos) + ) return False # Number of padding octets to always add after this field. Structs only. @@ -152,7 +175,9 @@ def Optional(self): def Padding(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(28)) if o != 0: - return self._tab.Get(flatbuffers.number_types.Uint16Flags, o + self._tab.Pos) + return self._tab.Get( + flatbuffers.number_types.Uint16Flags, o + self._tab.Pos + ) return 0 # If the field uses 64-bit offsets. @@ -160,113 +185,159 @@ def Padding(self): def Offset64(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(30)) if o != 0: - return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos)) + return bool( + self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos) + ) return False + def FieldStart(builder): builder.StartObject(14) + def Start(builder): FieldStart(builder) + def FieldAddName(builder, name): - builder.PrependUOffsetTRelativeSlot(0, flatbuffers.number_types.UOffsetTFlags.py_type(name), 0) + builder.PrependUOffsetTRelativeSlot( + 0, flatbuffers.number_types.UOffsetTFlags.py_type(name), 0 + ) + def AddName(builder, name): FieldAddName(builder, name) + def FieldAddType(builder, type): - builder.PrependUOffsetTRelativeSlot(1, flatbuffers.number_types.UOffsetTFlags.py_type(type), 0) + builder.PrependUOffsetTRelativeSlot( + 1, flatbuffers.number_types.UOffsetTFlags.py_type(type), 0 + ) + def AddType(builder, type): FieldAddType(builder, type) + def FieldAddId(builder, id): builder.PrependUint16Slot(2, id, 0) + def AddId(builder, id): FieldAddId(builder, id) + def FieldAddOffset(builder, offset): builder.PrependUint16Slot(3, offset, 0) + def AddOffset(builder, offset): FieldAddOffset(builder, offset) + def FieldAddDefaultInteger(builder, defaultInteger): builder.PrependInt64Slot(4, defaultInteger, 0) + def AddDefaultInteger(builder, defaultInteger): FieldAddDefaultInteger(builder, defaultInteger) + def FieldAddDefaultReal(builder, defaultReal): builder.PrependFloat64Slot(5, defaultReal, 0.0) + def AddDefaultReal(builder, defaultReal): FieldAddDefaultReal(builder, defaultReal) + def FieldAddDeprecated(builder, deprecated): builder.PrependBoolSlot(6, deprecated, 0) + def AddDeprecated(builder, deprecated): FieldAddDeprecated(builder, deprecated) + def FieldAddRequired(builder, required): builder.PrependBoolSlot(7, required, 0) + def AddRequired(builder, required): FieldAddRequired(builder, required) + def FieldAddKey(builder, key): builder.PrependBoolSlot(8, key, 0) + def AddKey(builder, key): FieldAddKey(builder, key) + def FieldAddAttributes(builder, attributes): - builder.PrependUOffsetTRelativeSlot(9, flatbuffers.number_types.UOffsetTFlags.py_type(attributes), 0) + builder.PrependUOffsetTRelativeSlot( + 9, flatbuffers.number_types.UOffsetTFlags.py_type(attributes), 0 + ) + def AddAttributes(builder, attributes): FieldAddAttributes(builder, attributes) + def FieldStartAttributesVector(builder, numElems): return builder.StartVector(4, numElems, 4) + def StartAttributesVector(builder, numElems): return FieldStartAttributesVector(builder, numElems) + def FieldAddDocumentation(builder, documentation): - builder.PrependUOffsetTRelativeSlot(10, flatbuffers.number_types.UOffsetTFlags.py_type(documentation), 0) + builder.PrependUOffsetTRelativeSlot( + 10, flatbuffers.number_types.UOffsetTFlags.py_type(documentation), 0 + ) + def AddDocumentation(builder, documentation): FieldAddDocumentation(builder, documentation) + def FieldStartDocumentationVector(builder, numElems): return builder.StartVector(4, numElems, 4) + def StartDocumentationVector(builder, numElems): return FieldStartDocumentationVector(builder, numElems) + def FieldAddOptional(builder, optional): builder.PrependBoolSlot(11, optional, 0) + def AddOptional(builder, optional): FieldAddOptional(builder, optional) + def FieldAddPadding(builder, padding): builder.PrependUint16Slot(12, padding, 0) + def AddPadding(builder, padding): FieldAddPadding(builder, padding) + def FieldAddOffset64(builder, offset64): builder.PrependBoolSlot(13, offset64, 0) + def AddOffset64(builder, offset64): FieldAddOffset64(builder, offset64) + def FieldEnd(builder): return builder.EndObject() + def End(builder): return FieldEnd(builder) diff --git a/flatbuffers/reflection/KeyValue.py b/flatbuffers/reflection/KeyValue.py index 585f99aad..e000dd3d4 100644 --- a/flatbuffers/reflection/KeyValue.py +++ b/flatbuffers/reflection/KeyValue.py @@ -4,10 +4,12 @@ import flatbuffers from flatbuffers.compat import import_numpy + np = import_numpy() + class KeyValue(object): - __slots__ = ['_tab'] + __slots__ = ["_tab"] @classmethod def GetRootAs(cls, buf, offset=0): @@ -20,9 +22,12 @@ def GetRootAs(cls, buf, offset=0): def GetRootAsKeyValue(cls, buf, offset=0): """This method is deprecated. Please switch to GetRootAs.""" return cls.GetRootAs(buf, offset) + @classmethod def KeyValueBufferHasIdentifier(cls, buf, offset, size_prefixed=False): - return flatbuffers.util.BufferHasIdentifier(buf, offset, b"\x42\x46\x42\x53", size_prefixed=size_prefixed) + return flatbuffers.util.BufferHasIdentifier( + buf, offset, b"\x42\x46\x42\x53", size_prefixed=size_prefixed + ) # KeyValue def Init(self, buf, pos): @@ -42,26 +47,38 @@ def Value(self): return self._tab.String(o + self._tab.Pos) return None + def KeyValueStart(builder): builder.StartObject(2) + def Start(builder): KeyValueStart(builder) + def KeyValueAddKey(builder, key): - builder.PrependUOffsetTRelativeSlot(0, flatbuffers.number_types.UOffsetTFlags.py_type(key), 0) + builder.PrependUOffsetTRelativeSlot( + 0, flatbuffers.number_types.UOffsetTFlags.py_type(key), 0 + ) + def AddKey(builder, key): KeyValueAddKey(builder, key) + def KeyValueAddValue(builder, value): - builder.PrependUOffsetTRelativeSlot(1, flatbuffers.number_types.UOffsetTFlags.py_type(value), 0) + builder.PrependUOffsetTRelativeSlot( + 1, flatbuffers.number_types.UOffsetTFlags.py_type(value), 0 + ) + def AddValue(builder, value): KeyValueAddValue(builder, value) + def KeyValueEnd(builder): return builder.EndObject() + def End(builder): return KeyValueEnd(builder) diff --git a/flatbuffers/reflection/Object.py b/flatbuffers/reflection/Object.py index 41fbc09ab..a2e834279 100644 --- a/flatbuffers/reflection/Object.py +++ b/flatbuffers/reflection/Object.py @@ -4,10 +4,12 @@ import flatbuffers from flatbuffers.compat import import_numpy + np = import_numpy() + class Object(object): - __slots__ = ['_tab'] + __slots__ = ["_tab"] @classmethod def GetRootAs(cls, buf, offset=0): @@ -20,9 +22,12 @@ def GetRootAs(cls, buf, offset=0): def GetRootAsObject(cls, buf, offset=0): """This method is deprecated. Please switch to GetRootAs.""" return cls.GetRootAs(buf, offset) + @classmethod def ObjectBufferHasIdentifier(cls, buf, offset, size_prefixed=False): - return flatbuffers.util.BufferHasIdentifier(buf, offset, b"\x42\x46\x42\x53", size_prefixed=size_prefixed) + return flatbuffers.util.BufferHasIdentifier( + buf, offset, b"\x42\x46\x42\x53", size_prefixed=size_prefixed + ) # Object def Init(self, buf, pos): @@ -43,6 +48,7 @@ def Fields(self, j): x += flatbuffers.number_types.UOffsetTFlags.py_type(j) * 4 x = self._tab.Indirect(x) from reflection.Field import Field + obj = Field() obj.Init(self._tab.Bytes, x) return obj @@ -64,7 +70,9 @@ def FieldsIsNone(self): def IsStruct(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8)) if o != 0: - return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos)) + return bool( + self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos) + ) return False # Object @@ -89,6 +97,7 @@ def Attributes(self, j): x += flatbuffers.number_types.UOffsetTFlags.py_type(j) * 4 x = self._tab.Indirect(x) from reflection.KeyValue import KeyValue + obj = KeyValue() obj.Init(self._tab.Bytes, x) return obj @@ -111,7 +120,9 @@ def Documentation(self, j): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(16)) if o != 0: a = self._tab.Vector(o) - return self._tab.String(a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 4)) + return self._tab.String( + a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 4) + ) return "" # Object @@ -134,80 +145,116 @@ def DeclarationFile(self): return self._tab.String(o + self._tab.Pos) return None + def ObjectStart(builder): builder.StartObject(8) + def Start(builder): ObjectStart(builder) + def ObjectAddName(builder, name): - builder.PrependUOffsetTRelativeSlot(0, flatbuffers.number_types.UOffsetTFlags.py_type(name), 0) + builder.PrependUOffsetTRelativeSlot( + 0, flatbuffers.number_types.UOffsetTFlags.py_type(name), 0 + ) + def AddName(builder, name): ObjectAddName(builder, name) + def ObjectAddFields(builder, fields): - builder.PrependUOffsetTRelativeSlot(1, flatbuffers.number_types.UOffsetTFlags.py_type(fields), 0) + builder.PrependUOffsetTRelativeSlot( + 1, flatbuffers.number_types.UOffsetTFlags.py_type(fields), 0 + ) + def AddFields(builder, fields): ObjectAddFields(builder, fields) + def ObjectStartFieldsVector(builder, numElems): return builder.StartVector(4, numElems, 4) + def StartFieldsVector(builder, numElems): return ObjectStartFieldsVector(builder, numElems) + def ObjectAddIsStruct(builder, isStruct): builder.PrependBoolSlot(2, isStruct, 0) + def AddIsStruct(builder, isStruct): ObjectAddIsStruct(builder, isStruct) + def ObjectAddMinalign(builder, minalign): builder.PrependInt32Slot(3, minalign, 0) + def AddMinalign(builder, minalign): ObjectAddMinalign(builder, minalign) + def ObjectAddBytesize(builder, bytesize): builder.PrependInt32Slot(4, bytesize, 0) + def AddBytesize(builder, bytesize): ObjectAddBytesize(builder, bytesize) + def ObjectAddAttributes(builder, attributes): - builder.PrependUOffsetTRelativeSlot(5, flatbuffers.number_types.UOffsetTFlags.py_type(attributes), 0) + builder.PrependUOffsetTRelativeSlot( + 5, flatbuffers.number_types.UOffsetTFlags.py_type(attributes), 0 + ) + def AddAttributes(builder, attributes): ObjectAddAttributes(builder, attributes) + def ObjectStartAttributesVector(builder, numElems): return builder.StartVector(4, numElems, 4) + def StartAttributesVector(builder, numElems): return ObjectStartAttributesVector(builder, numElems) + def ObjectAddDocumentation(builder, documentation): - builder.PrependUOffsetTRelativeSlot(6, flatbuffers.number_types.UOffsetTFlags.py_type(documentation), 0) + builder.PrependUOffsetTRelativeSlot( + 6, flatbuffers.number_types.UOffsetTFlags.py_type(documentation), 0 + ) + def AddDocumentation(builder, documentation): ObjectAddDocumentation(builder, documentation) + def ObjectStartDocumentationVector(builder, numElems): return builder.StartVector(4, numElems, 4) + def StartDocumentationVector(builder, numElems): return ObjectStartDocumentationVector(builder, numElems) + def ObjectAddDeclarationFile(builder, declarationFile): - builder.PrependUOffsetTRelativeSlot(7, flatbuffers.number_types.UOffsetTFlags.py_type(declarationFile), 0) + builder.PrependUOffsetTRelativeSlot( + 7, flatbuffers.number_types.UOffsetTFlags.py_type(declarationFile), 0 + ) + def AddDeclarationFile(builder, declarationFile): ObjectAddDeclarationFile(builder, declarationFile) + def ObjectEnd(builder): return builder.EndObject() + def End(builder): return ObjectEnd(builder) diff --git a/flatbuffers/reflection/RPCCall.py b/flatbuffers/reflection/RPCCall.py index b88c64b77..2068ead81 100644 --- a/flatbuffers/reflection/RPCCall.py +++ b/flatbuffers/reflection/RPCCall.py @@ -4,10 +4,12 @@ import flatbuffers from flatbuffers.compat import import_numpy + np = import_numpy() + class RPCCall(object): - __slots__ = ['_tab'] + __slots__ = ["_tab"] @classmethod def GetRootAs(cls, buf, offset=0): @@ -20,9 +22,12 @@ def GetRootAs(cls, buf, offset=0): def GetRootAsRPCCall(cls, buf, offset=0): """This method is deprecated. Please switch to GetRootAs.""" return cls.GetRootAs(buf, offset) + @classmethod def RPCCallBufferHasIdentifier(cls, buf, offset, size_prefixed=False): - return flatbuffers.util.BufferHasIdentifier(buf, offset, b"\x42\x46\x42\x53", size_prefixed=size_prefixed) + return flatbuffers.util.BufferHasIdentifier( + buf, offset, b"\x42\x46\x42\x53", size_prefixed=size_prefixed + ) # RPCCall def Init(self, buf, pos): @@ -41,6 +46,7 @@ def Request(self): if o != 0: x = self._tab.Indirect(o + self._tab.Pos) from reflection.Object import Object + obj = Object() obj.Init(self._tab.Bytes, x) return obj @@ -52,6 +58,7 @@ def Response(self): if o != 0: x = self._tab.Indirect(o + self._tab.Pos) from reflection.Object import Object + obj = Object() obj.Init(self._tab.Bytes, x) return obj @@ -65,6 +72,7 @@ def Attributes(self, j): x += flatbuffers.number_types.UOffsetTFlags.py_type(j) * 4 x = self._tab.Indirect(x) from reflection.KeyValue import KeyValue + obj = KeyValue() obj.Init(self._tab.Bytes, x) return obj @@ -87,7 +95,9 @@ def Documentation(self, j): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12)) if o != 0: a = self._tab.Vector(o) - return self._tab.String(a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 4)) + return self._tab.String( + a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 4) + ) return "" # RPCCall @@ -102,56 +112,84 @@ def DocumentationIsNone(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12)) return o == 0 + def RPCCallStart(builder): builder.StartObject(5) + def Start(builder): RPCCallStart(builder) + def RPCCallAddName(builder, name): - builder.PrependUOffsetTRelativeSlot(0, flatbuffers.number_types.UOffsetTFlags.py_type(name), 0) + builder.PrependUOffsetTRelativeSlot( + 0, flatbuffers.number_types.UOffsetTFlags.py_type(name), 0 + ) + def AddName(builder, name): RPCCallAddName(builder, name) + def RPCCallAddRequest(builder, request): - builder.PrependUOffsetTRelativeSlot(1, flatbuffers.number_types.UOffsetTFlags.py_type(request), 0) + builder.PrependUOffsetTRelativeSlot( + 1, flatbuffers.number_types.UOffsetTFlags.py_type(request), 0 + ) + def AddRequest(builder, request): RPCCallAddRequest(builder, request) + def RPCCallAddResponse(builder, response): - builder.PrependUOffsetTRelativeSlot(2, flatbuffers.number_types.UOffsetTFlags.py_type(response), 0) + builder.PrependUOffsetTRelativeSlot( + 2, flatbuffers.number_types.UOffsetTFlags.py_type(response), 0 + ) + def AddResponse(builder, response): RPCCallAddResponse(builder, response) + def RPCCallAddAttributes(builder, attributes): - builder.PrependUOffsetTRelativeSlot(3, flatbuffers.number_types.UOffsetTFlags.py_type(attributes), 0) + builder.PrependUOffsetTRelativeSlot( + 3, flatbuffers.number_types.UOffsetTFlags.py_type(attributes), 0 + ) + def AddAttributes(builder, attributes): RPCCallAddAttributes(builder, attributes) + def RPCCallStartAttributesVector(builder, numElems): return builder.StartVector(4, numElems, 4) + def StartAttributesVector(builder, numElems): return RPCCallStartAttributesVector(builder, numElems) + def RPCCallAddDocumentation(builder, documentation): - builder.PrependUOffsetTRelativeSlot(4, flatbuffers.number_types.UOffsetTFlags.py_type(documentation), 0) + builder.PrependUOffsetTRelativeSlot( + 4, flatbuffers.number_types.UOffsetTFlags.py_type(documentation), 0 + ) + def AddDocumentation(builder, documentation): RPCCallAddDocumentation(builder, documentation) + def RPCCallStartDocumentationVector(builder, numElems): return builder.StartVector(4, numElems, 4) + def StartDocumentationVector(builder, numElems): return RPCCallStartDocumentationVector(builder, numElems) + def RPCCallEnd(builder): return builder.EndObject() + def End(builder): return RPCCallEnd(builder) diff --git a/flatbuffers/reflection/Schema.py b/flatbuffers/reflection/Schema.py index 357e6ea0e..eef6e7b97 100644 --- a/flatbuffers/reflection/Schema.py +++ b/flatbuffers/reflection/Schema.py @@ -4,10 +4,12 @@ import flatbuffers from flatbuffers.compat import import_numpy + np = import_numpy() + class Schema(object): - __slots__ = ['_tab'] + __slots__ = ["_tab"] @classmethod def GetRootAs(cls, buf, offset=0): @@ -20,9 +22,12 @@ def GetRootAs(cls, buf, offset=0): def GetRootAsSchema(cls, buf, offset=0): """This method is deprecated. Please switch to GetRootAs.""" return cls.GetRootAs(buf, offset) + @classmethod def SchemaBufferHasIdentifier(cls, buf, offset, size_prefixed=False): - return flatbuffers.util.BufferHasIdentifier(buf, offset, b"\x42\x46\x42\x53", size_prefixed=size_prefixed) + return flatbuffers.util.BufferHasIdentifier( + buf, offset, b"\x42\x46\x42\x53", size_prefixed=size_prefixed + ) # Schema def Init(self, buf, pos): @@ -36,6 +41,7 @@ def Objects(self, j): x += flatbuffers.number_types.UOffsetTFlags.py_type(j) * 4 x = self._tab.Indirect(x) from reflection.Object import Object + obj = Object() obj.Init(self._tab.Bytes, x) return obj @@ -61,6 +67,7 @@ def Enums(self, j): x += flatbuffers.number_types.UOffsetTFlags.py_type(j) * 4 x = self._tab.Indirect(x) from reflection.Enum import Enum + obj = Enum() obj.Init(self._tab.Bytes, x) return obj @@ -98,6 +105,7 @@ def RootTable(self): if o != 0: x = self._tab.Indirect(o + self._tab.Pos) from reflection.Object import Object + obj = Object() obj.Init(self._tab.Bytes, x) return obj @@ -111,6 +119,7 @@ def Services(self, j): x += flatbuffers.number_types.UOffsetTFlags.py_type(j) * 4 x = self._tab.Indirect(x) from reflection.Service import Service + obj = Service() obj.Init(self._tab.Bytes, x) return obj @@ -132,7 +141,9 @@ def ServicesIsNone(self): def AdvancedFeatures(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(16)) if o != 0: - return self._tab.Get(flatbuffers.number_types.Uint64Flags, o + self._tab.Pos) + return self._tab.Get( + flatbuffers.number_types.Uint64Flags, o + self._tab.Pos + ) return 0 # All the files used in this compilation. Files are relative to where @@ -145,6 +156,7 @@ def FbsFiles(self, j): x += flatbuffers.number_types.UOffsetTFlags.py_type(j) * 4 x = self._tab.Indirect(x) from reflection.SchemaFile import SchemaFile + obj = SchemaFile() obj.Init(self._tab.Bytes, x) return obj @@ -162,86 +174,128 @@ def FbsFilesIsNone(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(18)) return o == 0 + def SchemaStart(builder): builder.StartObject(8) + def Start(builder): SchemaStart(builder) + def SchemaAddObjects(builder, objects): - builder.PrependUOffsetTRelativeSlot(0, flatbuffers.number_types.UOffsetTFlags.py_type(objects), 0) + builder.PrependUOffsetTRelativeSlot( + 0, flatbuffers.number_types.UOffsetTFlags.py_type(objects), 0 + ) + def AddObjects(builder, objects): SchemaAddObjects(builder, objects) + def SchemaStartObjectsVector(builder, numElems): return builder.StartVector(4, numElems, 4) + def StartObjectsVector(builder, numElems): return SchemaStartObjectsVector(builder, numElems) + def SchemaAddEnums(builder, enums): - builder.PrependUOffsetTRelativeSlot(1, flatbuffers.number_types.UOffsetTFlags.py_type(enums), 0) + builder.PrependUOffsetTRelativeSlot( + 1, flatbuffers.number_types.UOffsetTFlags.py_type(enums), 0 + ) + def AddEnums(builder, enums): SchemaAddEnums(builder, enums) + def SchemaStartEnumsVector(builder, numElems): return builder.StartVector(4, numElems, 4) + def StartEnumsVector(builder, numElems): return SchemaStartEnumsVector(builder, numElems) + def SchemaAddFileIdent(builder, fileIdent): - builder.PrependUOffsetTRelativeSlot(2, flatbuffers.number_types.UOffsetTFlags.py_type(fileIdent), 0) + builder.PrependUOffsetTRelativeSlot( + 2, flatbuffers.number_types.UOffsetTFlags.py_type(fileIdent), 0 + ) + def AddFileIdent(builder, fileIdent): SchemaAddFileIdent(builder, fileIdent) + def SchemaAddFileExt(builder, fileExt): - builder.PrependUOffsetTRelativeSlot(3, flatbuffers.number_types.UOffsetTFlags.py_type(fileExt), 0) + builder.PrependUOffsetTRelativeSlot( + 3, flatbuffers.number_types.UOffsetTFlags.py_type(fileExt), 0 + ) + def AddFileExt(builder, fileExt): SchemaAddFileExt(builder, fileExt) + def SchemaAddRootTable(builder, rootTable): - builder.PrependUOffsetTRelativeSlot(4, flatbuffers.number_types.UOffsetTFlags.py_type(rootTable), 0) + builder.PrependUOffsetTRelativeSlot( + 4, flatbuffers.number_types.UOffsetTFlags.py_type(rootTable), 0 + ) + def AddRootTable(builder, rootTable): SchemaAddRootTable(builder, rootTable) + def SchemaAddServices(builder, services): - builder.PrependUOffsetTRelativeSlot(5, flatbuffers.number_types.UOffsetTFlags.py_type(services), 0) + builder.PrependUOffsetTRelativeSlot( + 5, flatbuffers.number_types.UOffsetTFlags.py_type(services), 0 + ) + def AddServices(builder, services): SchemaAddServices(builder, services) + def SchemaStartServicesVector(builder, numElems): return builder.StartVector(4, numElems, 4) + def StartServicesVector(builder, numElems): return SchemaStartServicesVector(builder, numElems) + def SchemaAddAdvancedFeatures(builder, advancedFeatures): builder.PrependUint64Slot(6, advancedFeatures, 0) + def AddAdvancedFeatures(builder, advancedFeatures): SchemaAddAdvancedFeatures(builder, advancedFeatures) + def SchemaAddFbsFiles(builder, fbsFiles): - builder.PrependUOffsetTRelativeSlot(7, flatbuffers.number_types.UOffsetTFlags.py_type(fbsFiles), 0) + builder.PrependUOffsetTRelativeSlot( + 7, flatbuffers.number_types.UOffsetTFlags.py_type(fbsFiles), 0 + ) + def AddFbsFiles(builder, fbsFiles): SchemaAddFbsFiles(builder, fbsFiles) + def SchemaStartFbsFilesVector(builder, numElems): return builder.StartVector(4, numElems, 4) + def StartFbsFilesVector(builder, numElems): return SchemaStartFbsFilesVector(builder, numElems) + def SchemaEnd(builder): return builder.EndObject() + def End(builder): return SchemaEnd(builder) diff --git a/flatbuffers/reflection/SchemaFile.py b/flatbuffers/reflection/SchemaFile.py index db4e47f1a..400e1c479 100644 --- a/flatbuffers/reflection/SchemaFile.py +++ b/flatbuffers/reflection/SchemaFile.py @@ -4,13 +4,15 @@ import flatbuffers from flatbuffers.compat import import_numpy + np = import_numpy() + # File specific information. # Symbols declared within a file may be recovered by iterating over all # symbols and examining the `declaration_file` field. class SchemaFile(object): - __slots__ = ['_tab'] + __slots__ = ["_tab"] @classmethod def GetRootAs(cls, buf, offset=0): @@ -23,9 +25,12 @@ def GetRootAs(cls, buf, offset=0): def GetRootAsSchemaFile(cls, buf, offset=0): """This method is deprecated. Please switch to GetRootAs.""" return cls.GetRootAs(buf, offset) + @classmethod def SchemaFileBufferHasIdentifier(cls, buf, offset, size_prefixed=False): - return flatbuffers.util.BufferHasIdentifier(buf, offset, b"\x42\x46\x42\x53", size_prefixed=size_prefixed) + return flatbuffers.util.BufferHasIdentifier( + buf, offset, b"\x42\x46\x42\x53", size_prefixed=size_prefixed + ) # SchemaFile def Init(self, buf, pos): @@ -45,7 +50,9 @@ def IncludedFilenames(self, j): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6)) if o != 0: a = self._tab.Vector(o) - return self._tab.String(a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 4)) + return self._tab.String( + a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 4) + ) return "" # SchemaFile @@ -60,32 +67,46 @@ def IncludedFilenamesIsNone(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6)) return o == 0 + def SchemaFileStart(builder): builder.StartObject(2) + def Start(builder): SchemaFileStart(builder) + def SchemaFileAddFilename(builder, filename): - builder.PrependUOffsetTRelativeSlot(0, flatbuffers.number_types.UOffsetTFlags.py_type(filename), 0) + builder.PrependUOffsetTRelativeSlot( + 0, flatbuffers.number_types.UOffsetTFlags.py_type(filename), 0 + ) + def AddFilename(builder, filename): SchemaFileAddFilename(builder, filename) + def SchemaFileAddIncludedFilenames(builder, includedFilenames): - builder.PrependUOffsetTRelativeSlot(1, flatbuffers.number_types.UOffsetTFlags.py_type(includedFilenames), 0) + builder.PrependUOffsetTRelativeSlot( + 1, flatbuffers.number_types.UOffsetTFlags.py_type(includedFilenames), 0 + ) + def AddIncludedFilenames(builder, includedFilenames): SchemaFileAddIncludedFilenames(builder, includedFilenames) + def SchemaFileStartIncludedFilenamesVector(builder, numElems): return builder.StartVector(4, numElems, 4) + def StartIncludedFilenamesVector(builder, numElems): return SchemaFileStartIncludedFilenamesVector(builder, numElems) + def SchemaFileEnd(builder): return builder.EndObject() + def End(builder): return SchemaFileEnd(builder) diff --git a/flatbuffers/reflection/Service.py b/flatbuffers/reflection/Service.py index cbd1b6992..1d2e18916 100644 --- a/flatbuffers/reflection/Service.py +++ b/flatbuffers/reflection/Service.py @@ -4,10 +4,12 @@ import flatbuffers from flatbuffers.compat import import_numpy + np = import_numpy() + class Service(object): - __slots__ = ['_tab'] + __slots__ = ["_tab"] @classmethod def GetRootAs(cls, buf, offset=0): @@ -20,9 +22,12 @@ def GetRootAs(cls, buf, offset=0): def GetRootAsService(cls, buf, offset=0): """This method is deprecated. Please switch to GetRootAs.""" return cls.GetRootAs(buf, offset) + @classmethod def ServiceBufferHasIdentifier(cls, buf, offset, size_prefixed=False): - return flatbuffers.util.BufferHasIdentifier(buf, offset, b"\x42\x46\x42\x53", size_prefixed=size_prefixed) + return flatbuffers.util.BufferHasIdentifier( + buf, offset, b"\x42\x46\x42\x53", size_prefixed=size_prefixed + ) # Service def Init(self, buf, pos): @@ -43,6 +48,7 @@ def Calls(self, j): x += flatbuffers.number_types.UOffsetTFlags.py_type(j) * 4 x = self._tab.Indirect(x) from reflection.RPCCall import RPCCall + obj = RPCCall() obj.Init(self._tab.Bytes, x) return obj @@ -68,6 +74,7 @@ def Attributes(self, j): x += flatbuffers.number_types.UOffsetTFlags.py_type(j) * 4 x = self._tab.Indirect(x) from reflection.KeyValue import KeyValue + obj = KeyValue() obj.Init(self._tab.Bytes, x) return obj @@ -90,7 +97,9 @@ def Documentation(self, j): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10)) if o != 0: a = self._tab.Vector(o) - return self._tab.String(a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 4)) + return self._tab.String( + a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 4) + ) return "" # Service @@ -113,62 +122,92 @@ def DeclarationFile(self): return self._tab.String(o + self._tab.Pos) return None + def ServiceStart(builder): builder.StartObject(5) + def Start(builder): ServiceStart(builder) + def ServiceAddName(builder, name): - builder.PrependUOffsetTRelativeSlot(0, flatbuffers.number_types.UOffsetTFlags.py_type(name), 0) + builder.PrependUOffsetTRelativeSlot( + 0, flatbuffers.number_types.UOffsetTFlags.py_type(name), 0 + ) + def AddName(builder, name): ServiceAddName(builder, name) + def ServiceAddCalls(builder, calls): - builder.PrependUOffsetTRelativeSlot(1, flatbuffers.number_types.UOffsetTFlags.py_type(calls), 0) + builder.PrependUOffsetTRelativeSlot( + 1, flatbuffers.number_types.UOffsetTFlags.py_type(calls), 0 + ) + def AddCalls(builder, calls): ServiceAddCalls(builder, calls) + def ServiceStartCallsVector(builder, numElems): return builder.StartVector(4, numElems, 4) + def StartCallsVector(builder, numElems): return ServiceStartCallsVector(builder, numElems) + def ServiceAddAttributes(builder, attributes): - builder.PrependUOffsetTRelativeSlot(2, flatbuffers.number_types.UOffsetTFlags.py_type(attributes), 0) + builder.PrependUOffsetTRelativeSlot( + 2, flatbuffers.number_types.UOffsetTFlags.py_type(attributes), 0 + ) + def AddAttributes(builder, attributes): ServiceAddAttributes(builder, attributes) + def ServiceStartAttributesVector(builder, numElems): return builder.StartVector(4, numElems, 4) + def StartAttributesVector(builder, numElems): return ServiceStartAttributesVector(builder, numElems) + def ServiceAddDocumentation(builder, documentation): - builder.PrependUOffsetTRelativeSlot(3, flatbuffers.number_types.UOffsetTFlags.py_type(documentation), 0) + builder.PrependUOffsetTRelativeSlot( + 3, flatbuffers.number_types.UOffsetTFlags.py_type(documentation), 0 + ) + def AddDocumentation(builder, documentation): ServiceAddDocumentation(builder, documentation) + def ServiceStartDocumentationVector(builder, numElems): return builder.StartVector(4, numElems, 4) + def StartDocumentationVector(builder, numElems): return ServiceStartDocumentationVector(builder, numElems) + def ServiceAddDeclarationFile(builder, declarationFile): - builder.PrependUOffsetTRelativeSlot(4, flatbuffers.number_types.UOffsetTFlags.py_type(declarationFile), 0) + builder.PrependUOffsetTRelativeSlot( + 4, flatbuffers.number_types.UOffsetTFlags.py_type(declarationFile), 0 + ) + def AddDeclarationFile(builder, declarationFile): ServiceAddDeclarationFile(builder, declarationFile) + def ServiceEnd(builder): return builder.EndObject() + def End(builder): return ServiceEnd(builder) diff --git a/flatbuffers/reflection/Type.py b/flatbuffers/reflection/Type.py index 49c78a3d9..c851b19ae 100644 --- a/flatbuffers/reflection/Type.py +++ b/flatbuffers/reflection/Type.py @@ -4,10 +4,12 @@ import flatbuffers from flatbuffers.compat import import_numpy + np = import_numpy() + class Type(object): - __slots__ = ['_tab'] + __slots__ = ["_tab"] @classmethod def GetRootAs(cls, buf, offset=0): @@ -20,9 +22,12 @@ def GetRootAs(cls, buf, offset=0): def GetRootAsType(cls, buf, offset=0): """This method is deprecated. Please switch to GetRootAs.""" return cls.GetRootAs(buf, offset) + @classmethod def TypeBufferHasIdentifier(cls, buf, offset, size_prefixed=False): - return flatbuffers.util.BufferHasIdentifier(buf, offset, b"\x42\x46\x42\x53", size_prefixed=size_prefixed) + return flatbuffers.util.BufferHasIdentifier( + buf, offset, b"\x42\x46\x42\x53", size_prefixed=size_prefixed + ) # Type def Init(self, buf, pos): @@ -53,7 +58,9 @@ def Index(self): def FixedLength(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10)) if o != 0: - return self._tab.Get(flatbuffers.number_types.Uint16Flags, o + self._tab.Pos) + return self._tab.Get( + flatbuffers.number_types.Uint16Flags, o + self._tab.Pos + ) return 0 # The size (octets) of the `base_type` field. @@ -61,7 +68,9 @@ def FixedLength(self): def BaseSize(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(12)) if o != 0: - return self._tab.Get(flatbuffers.number_types.Uint32Flags, o + self._tab.Pos) + return self._tab.Get( + flatbuffers.number_types.Uint32Flags, o + self._tab.Pos + ) return 4 # The size (octets) of the `element` field, if present. @@ -69,53 +78,71 @@ def BaseSize(self): def ElementSize(self): o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(14)) if o != 0: - return self._tab.Get(flatbuffers.number_types.Uint32Flags, o + self._tab.Pos) + return self._tab.Get( + flatbuffers.number_types.Uint32Flags, o + self._tab.Pos + ) return 0 + def TypeStart(builder): builder.StartObject(6) + def Start(builder): TypeStart(builder) + def TypeAddBaseType(builder, baseType): builder.PrependInt8Slot(0, baseType, 0) + def AddBaseType(builder, baseType): TypeAddBaseType(builder, baseType) + def TypeAddElement(builder, element): builder.PrependInt8Slot(1, element, 0) + def AddElement(builder, element): TypeAddElement(builder, element) + def TypeAddIndex(builder, index): builder.PrependInt32Slot(2, index, -1) + def AddIndex(builder, index): TypeAddIndex(builder, index) + def TypeAddFixedLength(builder, fixedLength): builder.PrependUint16Slot(3, fixedLength, 0) + def AddFixedLength(builder, fixedLength): TypeAddFixedLength(builder, fixedLength) + def TypeAddBaseSize(builder, baseSize): builder.PrependUint32Slot(4, baseSize, 4) + def AddBaseSize(builder, baseSize): TypeAddBaseSize(builder, baseSize) + def TypeAddElementSize(builder, elementSize): builder.PrependUint32Slot(5, elementSize, 0) + def AddElementSize(builder, elementSize): TypeAddElementSize(builder, elementSize) + def TypeEnd(builder): return builder.EndObject() + def End(builder): return TypeEnd(builder) diff --git a/flatbuffers/table.py b/flatbuffers/table.py index 97f95a7a3..e01757991 100644 --- a/flatbuffers/table.py +++ b/flatbuffers/table.py @@ -17,132 +17,132 @@ class Table(object): - """Table wraps a byte slice and provides read access to its data. + """Table wraps a byte slice and provides read access to its data. - The variable `Pos` indicates the root of the FlatBuffers object therein. - """ - - __slots__ = ("Bytes", "Pos") - - def __init__(self, buf, pos): - N.enforce_number(pos, N.UOffsetTFlags) - - self.Bytes = buf - self.Pos = pos - - def Offset(self, vtableOffset): - """Offset provides access into the Table's vtable. - - Deprecated fields are ignored by checking the vtable's length. - """ - - vtable = self.Pos - self.Get(N.SOffsetTFlags, self.Pos) - vtableEnd = self.Get(N.VOffsetTFlags, vtable) - if vtableOffset < vtableEnd: - return self.Get(N.VOffsetTFlags, vtable + vtableOffset) - return 0 - - def Indirect(self, off): - """Indirect retrieves the relative offset stored at `offset`.""" - N.enforce_number(off, N.UOffsetTFlags) - return off + encode.Get(N.UOffsetTFlags.packer_type, self.Bytes, off) - - def String(self, off): - """String gets a string from data stored inside the flatbuffer.""" - N.enforce_number(off, N.UOffsetTFlags) - off += encode.Get(N.UOffsetTFlags.packer_type, self.Bytes, off) - start = off + N.UOffsetTFlags.bytewidth - length = encode.Get(N.UOffsetTFlags.packer_type, self.Bytes, off) - return bytes(self.Bytes[start : start + length]) - - def VectorLen(self, off): - """VectorLen retrieves the length of the vector whose offset is stored - - at "off" in this object. + The variable `Pos` indicates the root of the FlatBuffers object therein. """ - N.enforce_number(off, N.UOffsetTFlags) - - off += self.Pos - off += encode.Get(N.UOffsetTFlags.packer_type, self.Bytes, off) - ret = encode.Get(N.UOffsetTFlags.packer_type, self.Bytes, off) - return ret - - def Vector(self, off): - """Vector retrieves the start of data of the vector whose offset is - - stored at "off" in this object. - """ - N.enforce_number(off, N.UOffsetTFlags) - - off += self.Pos - x = off + self.Get(N.UOffsetTFlags, off) - # data starts after metadata containing the vector length - x += N.UOffsetTFlags.bytewidth - return x - - def Union(self, t2, off): - """Union initializes any Table-derived type to point to the union at - - the given offset. - """ - assert type(t2) is Table - N.enforce_number(off, N.UOffsetTFlags) - - off += self.Pos - t2.Pos = off + self.Get(N.UOffsetTFlags, off) - t2.Bytes = self.Bytes - - def Get(self, flags, off): - """Get retrieves a value of the type specified by `flags` at the - - given offset. - """ - N.enforce_number(off, N.UOffsetTFlags) - return flags.py_type(encode.Get(flags.packer_type, self.Bytes, off)) - - def GetSlot(self, slot, d, validator_flags): - N.enforce_number(slot, N.VOffsetTFlags) - if validator_flags is not None: - N.enforce_number(d, validator_flags) - off = self.Offset(slot) - if off == 0: - return d - return self.Get(validator_flags, self.Pos + off) - - def GetVectorAsNumpy(self, flags, off): - """GetVectorAsNumpy returns the vector that starts at `Vector(off)` - - as a numpy array with the type specified by `flags`. The array is - a `view` into Bytes, so modifying the returned array will - modify Bytes in place. - """ - offset = self.Vector(off) - length = self.VectorLen(off) # TODO: length accounts for bytewidth, right? - numpy_dtype = N.to_numpy_type(flags) - return encode.GetVectorAsNumpy(numpy_dtype, self.Bytes, length, offset) - - def GetArrayAsNumpy(self, flags, off, length): - """GetArrayAsNumpy returns the array with fixed width that starts at `Vector(offset)` - - with length `length` as a numpy array with the type specified by `flags`. - The - array is a `view` into Bytes so modifying the returned will modify Bytes in - place. - """ - numpy_dtype = N.to_numpy_type(flags) - return encode.GetVectorAsNumpy(numpy_dtype, self.Bytes, length, off) - - def GetVOffsetTSlot(self, slot, d): - """GetVOffsetTSlot retrieves the VOffsetT that the given vtable location - - points to. If the vtable value is zero, the default value `d` - will be returned. - """ - - N.enforce_number(slot, N.VOffsetTFlags) - N.enforce_number(d, N.VOffsetTFlags) - off = self.Offset(slot) - if off == 0: - return d - return off + __slots__ = ("Bytes", "Pos") + + def __init__(self, buf, pos): + N.enforce_number(pos, N.UOffsetTFlags) + + self.Bytes = buf + self.Pos = pos + + def Offset(self, vtableOffset): + """Offset provides access into the Table's vtable. + + Deprecated fields are ignored by checking the vtable's length. + """ + + vtable = self.Pos - self.Get(N.SOffsetTFlags, self.Pos) + vtableEnd = self.Get(N.VOffsetTFlags, vtable) + if vtableOffset < vtableEnd: + return self.Get(N.VOffsetTFlags, vtable + vtableOffset) + return 0 + + def Indirect(self, off): + """Indirect retrieves the relative offset stored at `offset`.""" + N.enforce_number(off, N.UOffsetTFlags) + return off + encode.Get(N.UOffsetTFlags.packer_type, self.Bytes, off) + + def String(self, off): + """String gets a string from data stored inside the flatbuffer.""" + N.enforce_number(off, N.UOffsetTFlags) + off += encode.Get(N.UOffsetTFlags.packer_type, self.Bytes, off) + start = off + N.UOffsetTFlags.bytewidth + length = encode.Get(N.UOffsetTFlags.packer_type, self.Bytes, off) + return bytes(self.Bytes[start : start + length]) + + def VectorLen(self, off): + """VectorLen retrieves the length of the vector whose offset is stored + + at "off" in this object. + """ + N.enforce_number(off, N.UOffsetTFlags) + + off += self.Pos + off += encode.Get(N.UOffsetTFlags.packer_type, self.Bytes, off) + ret = encode.Get(N.UOffsetTFlags.packer_type, self.Bytes, off) + return ret + + def Vector(self, off): + """Vector retrieves the start of data of the vector whose offset is + + stored at "off" in this object. + """ + N.enforce_number(off, N.UOffsetTFlags) + + off += self.Pos + x = off + self.Get(N.UOffsetTFlags, off) + # data starts after metadata containing the vector length + x += N.UOffsetTFlags.bytewidth + return x + + def Union(self, t2, off): + """Union initializes any Table-derived type to point to the union at + + the given offset. + """ + assert type(t2) is Table + N.enforce_number(off, N.UOffsetTFlags) + + off += self.Pos + t2.Pos = off + self.Get(N.UOffsetTFlags, off) + t2.Bytes = self.Bytes + + def Get(self, flags, off): + """Get retrieves a value of the type specified by `flags` at the + + given offset. + """ + N.enforce_number(off, N.UOffsetTFlags) + return flags.py_type(encode.Get(flags.packer_type, self.Bytes, off)) + + def GetSlot(self, slot, d, validator_flags): + N.enforce_number(slot, N.VOffsetTFlags) + if validator_flags is not None: + N.enforce_number(d, validator_flags) + off = self.Offset(slot) + if off == 0: + return d + return self.Get(validator_flags, self.Pos + off) + + def GetVectorAsNumpy(self, flags, off): + """GetVectorAsNumpy returns the vector that starts at `Vector(off)` + + as a numpy array with the type specified by `flags`. The array is + a `view` into Bytes, so modifying the returned array will + modify Bytes in place. + """ + offset = self.Vector(off) + length = self.VectorLen(off) # TODO: length accounts for bytewidth, right? + numpy_dtype = N.to_numpy_type(flags) + return encode.GetVectorAsNumpy(numpy_dtype, self.Bytes, length, offset) + + def GetArrayAsNumpy(self, flags, off, length): + """GetArrayAsNumpy returns the array with fixed width that starts at `Vector(offset)` + + with length `length` as a numpy array with the type specified by `flags`. + The + array is a `view` into Bytes so modifying the returned will modify Bytes in + place. + """ + numpy_dtype = N.to_numpy_type(flags) + return encode.GetVectorAsNumpy(numpy_dtype, self.Bytes, length, off) + + def GetVOffsetTSlot(self, slot, d): + """GetVOffsetTSlot retrieves the VOffsetT that the given vtable location + + points to. If the vtable value is zero, the default value `d` + will be returned. + """ + + N.enforce_number(slot, N.VOffsetTFlags) + N.enforce_number(d, N.VOffsetTFlags) + + off = self.Offset(slot) + if off == 0: + return d + return off diff --git a/flatbuffers/util.py b/flatbuffers/util.py index 03feb2679..cda1b1c91 100644 --- a/flatbuffers/util.py +++ b/flatbuffers/util.py @@ -18,30 +18,30 @@ def GetSizePrefix(buf, offset): - """Extract the size prefix from a buffer.""" - return encode.Get(packer.int32, buf, offset) + """Extract the size prefix from a buffer.""" + return encode.Get(packer.int32, buf, offset) def GetBufferIdentifier(buf, offset, size_prefixed=False): - """Extract the file_identifier from a buffer""" - if size_prefixed: - # increase offset by size of UOffsetTFlags + """Extract the file_identifier from a buffer""" + if size_prefixed: + # increase offset by size of UOffsetTFlags + offset += number_types.UOffsetTFlags.bytewidth + # increase offset by size of root table pointer offset += number_types.UOffsetTFlags.bytewidth - # increase offset by size of root table pointer - offset += number_types.UOffsetTFlags.bytewidth - # end of FILE_IDENTIFIER - end = offset + encode.FILE_IDENTIFIER_LENGTH - return buf[offset:end] + # end of FILE_IDENTIFIER + end = offset + encode.FILE_IDENTIFIER_LENGTH + return buf[offset:end] def BufferHasIdentifier(buf, offset, file_identifier, size_prefixed=False): - got = GetBufferIdentifier(buf, offset, size_prefixed=size_prefixed) - return got == file_identifier + got = GetBufferIdentifier(buf, offset, size_prefixed=size_prefixed) + return got == file_identifier def RemoveSizePrefix(buf, offset): - """Create a slice of a size-prefixed buffer that has + """Create a slice of a size-prefixed buffer that has - its position advanced just past the size prefix. - """ - return buf, offset + number_types.Int32Flags.bytewidth + its position advanced just past the size prefix. + """ + return buf, offset + number_types.Int32Flags.bytewidth diff --git a/justfile b/justfile index 4f6693503..a6a9ed700 100644 --- a/justfile +++ b/justfile @@ -529,11 +529,13 @@ autoformat venv="": (install-tools venv) echo "==> Automatically formatting code with ${VENV_NAME}..." # 1. Run the FORMATTER first. This will handle line lengths, quotes, etc. - "${VENV_PATH}/bin/ruff" format --exclude ./tests ./autobahn + # Uses exclude list from pyproject.toml (includes autobahn/wamp/gen/*, tests, etc.) + "${VENV_PATH}/bin/ruff" format . # 2. Run the LINTER'S FIXER second. This will handle things like # removing unused imports, sorting __all__, etc. - "${VENV_PATH}/bin/ruff" check --fix --exclude ./tests ./autobahn + # Uses exclude list from pyproject.toml (includes autobahn/wamp/gen/*, tests, etc.) + "${VENV_PATH}/bin/ruff" check --fix . echo "--> Formatting complete." # Lint code using Ruff in a single environment @@ -1272,6 +1274,12 @@ build-fbs venv="": (install-tools venv) touch ./autobahn/wamp/gen/__init__.py echo "--> Generated $(find ./autobahn/wamp/gen/ -name '*.py' | wc -l) .py files" + # Fix import paths in generated files (flatc generates relative imports) + # Change: from wamp.proto.X import X + # To: from autobahn.wamp.gen.wamp.proto.X import X + find ./autobahn/wamp/gen/wamp/proto/ -name "*.py" -exec sed -i 's/from wamp\.proto\./from autobahn.wamp.gen.wamp.proto./g' {} + + echo "--> Fixed import paths in generated files" + echo "Auto-formatting code using ruff after flatc code generation .." "${VENV_PATH}/bin/ruff" format ./autobahn/wamp/gen/ "${VENV_PATH}/bin/ruff" check --fix ./autobahn/wamp/gen/ diff --git a/setup.py b/setup.py index d87b8de88..689ae133c 100644 --- a/setup.py +++ b/setup.py @@ -23,4 +23,4 @@ setup( cffi_modules=cffi_modules, include_package_data=include_package_data, -) \ No newline at end of file +)