styly-netsync-server 0.13.0__tar.gz → 0.15.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {styly_netsync_server-0.13.0 → styly_netsync_server-0.15.0}/PKG-INFO +11 -8
- {styly_netsync_server-0.13.0 → styly_netsync_server-0.15.0}/README.md +10 -7
- {styly_netsync_server-0.13.0 → styly_netsync_server-0.15.0}/pyproject.toml +1 -1
- {styly_netsync_server-0.13.0 → styly_netsync_server-0.15.0}/src/styly_netsync/__init__.py +1 -1
- {styly_netsync_server-0.13.0 → styly_netsync_server-0.15.0}/src/styly_netsync/binary_serializer.py +49 -33
- {styly_netsync_server-0.13.0 → styly_netsync_server-0.15.0}/src/styly_netsync/client.py +116 -73
- {styly_netsync_server-0.13.0 → styly_netsync_server-0.15.0}/src/styly_netsync/client_simulator.py +101 -17
- {styly_netsync_server-0.13.0 → styly_netsync_server-0.15.0}/src/styly_netsync/config.py +52 -4
- {styly_netsync_server-0.13.0 → styly_netsync_server-0.15.0}/src/styly_netsync/default.toml +7 -1
- {styly_netsync_server-0.13.0 → styly_netsync_server-0.15.0}/src/styly_netsync/rest_bridge.py +37 -6
- {styly_netsync_server-0.13.0 → styly_netsync_server-0.15.0}/src/styly_netsync/server.py +481 -252
- {styly_netsync_server-0.13.0 → styly_netsync_server-0.15.0}/src/styly_netsync_server.egg-info/PKG-INFO +11 -8
- {styly_netsync_server-0.13.0 → styly_netsync_server-0.15.0}/src/styly_netsync_server.egg-info/SOURCES.txt +3 -1
- {styly_netsync_server-0.13.0 → styly_netsync_server-0.15.0}/tests/test_binary_serializer.py +33 -7
- {styly_netsync_server-0.13.0 → styly_netsync_server-0.15.0}/tests/test_client_variable_device_store.py +16 -13
- {styly_netsync_server-0.13.0 → styly_netsync_server-0.15.0}/tests/test_config.py +52 -9
- {styly_netsync_server-0.13.0 → styly_netsync_server-0.15.0}/tests/test_discovery_probe.py +1 -1
- {styly_netsync_server-0.13.0 → styly_netsync_server-0.15.0}/tests/test_multi_nic.py +35 -4
- styly_netsync_server-0.15.0/tests/test_nv_server_ordering.py +147 -0
- {styly_netsync_server-0.13.0 → styly_netsync_server-0.15.0}/tests/test_object_sync.py +9 -2
- {styly_netsync_server-0.13.0 → styly_netsync_server-0.15.0}/tests/test_python_client.py +98 -0
- {styly_netsync_server-0.13.0 → styly_netsync_server-0.15.0}/tests/test_reconnect_identity.py +3 -3
- {styly_netsync_server-0.13.0 → styly_netsync_server-0.15.0}/tests/test_stealth_heartbeat.py +20 -0
- styly_netsync_server-0.15.0/tests/test_transport_split.py +330 -0
- {styly_netsync_server-0.13.0 → styly_netsync_server-0.15.0}/LICENSE +0 -0
- {styly_netsync_server-0.13.0 → styly_netsync_server-0.15.0}/setup.cfg +0 -0
- {styly_netsync_server-0.13.0 → styly_netsync_server-0.15.0}/src/styly_netsync/__main__.py +0 -0
- {styly_netsync_server-0.13.0 → styly_netsync_server-0.15.0}/src/styly_netsync/adapters.py +0 -0
- {styly_netsync_server-0.13.0 → styly_netsync_server-0.15.0}/src/styly_netsync/cli.py +0 -0
- {styly_netsync_server-0.13.0 → styly_netsync_server-0.15.0}/src/styly_netsync/events.py +0 -0
- {styly_netsync_server-0.13.0 → styly_netsync_server-0.15.0}/src/styly_netsync/logging_utils.py +0 -0
- {styly_netsync_server-0.13.0 → styly_netsync_server-0.15.0}/src/styly_netsync/network_utils.py +0 -0
- {styly_netsync_server-0.13.0 → styly_netsync_server-0.15.0}/src/styly_netsync/nv_sync.py +0 -0
- {styly_netsync_server-0.13.0 → styly_netsync_server-0.15.0}/src/styly_netsync/types.py +0 -0
- {styly_netsync_server-0.13.0 → styly_netsync_server-0.15.0}/src/styly_netsync_server.egg-info/dependency_links.txt +0 -0
- {styly_netsync_server-0.13.0 → styly_netsync_server-0.15.0}/src/styly_netsync_server.egg-info/entry_points.txt +0 -0
- {styly_netsync_server-0.13.0 → styly_netsync_server-0.15.0}/src/styly_netsync_server.egg-info/requires.txt +0 -0
- {styly_netsync_server-0.13.0 → styly_netsync_server-0.15.0}/src/styly_netsync_server.egg-info/top_level.txt +0 -0
- {styly_netsync_server-0.13.0 → styly_netsync_server-0.15.0}/tests/test_all_run_methods.py +0 -0
- {styly_netsync_server-0.13.0 → styly_netsync_server-0.15.0}/tests/test_logging_cli.py +0 -0
- {styly_netsync_server-0.13.0 → styly_netsync_server-0.15.0}/tests/test_nv_protocol.py +0 -0
- {styly_netsync_server-0.13.0 → styly_netsync_server-0.15.0}/tests/test_port_error_message.py +0 -0
- {styly_netsync_server-0.13.0 → styly_netsync_server-0.15.0}/tests/test_rest_bridge.py +0 -0
- {styly_netsync_server-0.13.0 → styly_netsync_server-0.15.0}/tests/test_room_expiry.py +0 -0
- {styly_netsync_server-0.13.0 → styly_netsync_server-0.15.0}/tests/test_timing_monotonic.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: styly-netsync-server
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.15.0
|
|
4
4
|
Summary: STYLY NetSync Server - Multiplayer framework for Location-Based Entertainment VR/MR experiences
|
|
5
5
|
Author-email: "STYLY, Inc." <info@styly.inc>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -81,13 +81,16 @@ styly-netsync-simulator --server tcp://localhost --room my_room --clients 50
|
|
|
81
81
|
|
|
82
82
|
## Wire protocol compatibility
|
|
83
83
|
|
|
84
|
-
- Current
|
|
85
|
-
-
|
|
86
|
-
-
|
|
87
|
-
-
|
|
84
|
+
- Current wire protocol is `protocolVersion = 7`.
|
|
85
|
+
- Transport uses three sockets: control (`control_port`, default `5555`) for RPC, Network Variables, ownership, ID mapping, and client hello; transform uplink (`transform_port`, default `5557`) for client/object poses; PUB/SUB (`pub_port`, default `5556`) for room pose and room object downlink.
|
|
86
|
+
- Discovery responses use `STYLY-NETSYNC2|controlPort|transformPort|pubPort|serverName`; legacy `STYLY-NETSYNC|...` responses are explicitly incompatible.
|
|
87
|
+
- `dealer_port` / `--dealer-port` remain a one-release compatibility alias for `control_port` / `--control-port`.
|
|
88
|
+
- Transform messages use `MSG_CLIENT_POSE` (11) and `MSG_ROOM_POSE` (12) with the compact pose body. Clients register control identity with `MSG_CLIENT_HELLO` (19).
|
|
89
|
+
- Moving-floor-local poses set the `MovingFloorLocal` pose flag. Bound avatars send head, hands, and virtual transforms in the registered moving floor's local coordinates, and reuse the existing 8-byte physical slot as direct physical position/yaw.
|
|
90
|
+
- Unbound poses keep the `xrOriginDelta` semantics: `xrOriginDelta` carries a Y component as a 4th `int16` (`dx, dy, dz, dyaw` = 8 bytes), so receivers can reconstruct the sender's rig-Y motion.
|
|
88
91
|
- Legacy transform protocols (v2/v3) and JSON transform fallback are not supported.
|
|
89
92
|
- Deploy Unity and Python updates together when changing transform protocol behavior.
|
|
90
|
-
- Protocol
|
|
93
|
+
- Protocol v7 position quantization ranges:
|
|
91
94
|
- Absolute (`headPosAbs` only): signed `int24` at `0.01 m` per unit, per-axis range `[-83,886.08 m, 83,886.07 m]`.
|
|
92
95
|
- XROrigin locomotion delta for unbound poses (`xrOriginDelta`, 4×`int16`: `dx, dy, dz, dyaw`): `0.01 m` per unit for translation, `0.1°` for yaw. Receivers reconstruct `physicalPos = invDeltaRot * (headPos − deltaPos)`; it is not on the wire as a separate absolute field.
|
|
93
96
|
- Direct physical payload for moving-floor-local poses (`physical`, 4×`int16`: `x, y, z, yaw`): `0.01 m` per unit for translation, `0.1°` for yaw.
|
|
@@ -98,7 +101,7 @@ styly-netsync-simulator --server tcp://localhost --room my_room --clients 50
|
|
|
98
101
|
|
|
99
102
|
The following options summarize trade-offs when expanding absolute-position range.
|
|
100
103
|
|
|
101
|
-
Assumed unbound baseline (`protocolVersion=
|
|
104
|
+
Assumed unbound baseline (`protocolVersion=7`, `MovingFloorLocal` off):
|
|
102
105
|
- Client pose body with `Physical+Head+Right+Left` valid and `virtualCount=0`: `46 bytes` (matches `test_client_body_size_with_full_pose_no_virtuals`).
|
|
103
106
|
- Room per-client entry (`clientNo + poseTime + clientBody`): `56 bytes`.
|
|
104
107
|
|
|
@@ -267,4 +270,4 @@ The response includes the room ID and whether each key was `"applied"`, `"queued
|
|
|
267
270
|
|
|
268
271
|
### Read consistency
|
|
269
272
|
|
|
270
|
-
GET endpoints return a snapshot of the REST bridge's in-process cache, which is populated by
|
|
273
|
+
GET endpoints return a snapshot of the REST bridge's in-process cache, which is populated by control-lane sync messages from the server. The first request to a room lazily creates a bridge and may return an empty snapshot until the initial sync arrives — retry after a short delay if needed.
|
|
@@ -42,13 +42,16 @@ styly-netsync-simulator --server tcp://localhost --room my_room --clients 50
|
|
|
42
42
|
|
|
43
43
|
## Wire protocol compatibility
|
|
44
44
|
|
|
45
|
-
- Current
|
|
46
|
-
-
|
|
47
|
-
-
|
|
48
|
-
-
|
|
45
|
+
- Current wire protocol is `protocolVersion = 7`.
|
|
46
|
+
- Transport uses three sockets: control (`control_port`, default `5555`) for RPC, Network Variables, ownership, ID mapping, and client hello; transform uplink (`transform_port`, default `5557`) for client/object poses; PUB/SUB (`pub_port`, default `5556`) for room pose and room object downlink.
|
|
47
|
+
- Discovery responses use `STYLY-NETSYNC2|controlPort|transformPort|pubPort|serverName`; legacy `STYLY-NETSYNC|...` responses are explicitly incompatible.
|
|
48
|
+
- `dealer_port` / `--dealer-port` remain a one-release compatibility alias for `control_port` / `--control-port`.
|
|
49
|
+
- Transform messages use `MSG_CLIENT_POSE` (11) and `MSG_ROOM_POSE` (12) with the compact pose body. Clients register control identity with `MSG_CLIENT_HELLO` (19).
|
|
50
|
+
- Moving-floor-local poses set the `MovingFloorLocal` pose flag. Bound avatars send head, hands, and virtual transforms in the registered moving floor's local coordinates, and reuse the existing 8-byte physical slot as direct physical position/yaw.
|
|
51
|
+
- Unbound poses keep the `xrOriginDelta` semantics: `xrOriginDelta` carries a Y component as a 4th `int16` (`dx, dy, dz, dyaw` = 8 bytes), so receivers can reconstruct the sender's rig-Y motion.
|
|
49
52
|
- Legacy transform protocols (v2/v3) and JSON transform fallback are not supported.
|
|
50
53
|
- Deploy Unity and Python updates together when changing transform protocol behavior.
|
|
51
|
-
- Protocol
|
|
54
|
+
- Protocol v7 position quantization ranges:
|
|
52
55
|
- Absolute (`headPosAbs` only): signed `int24` at `0.01 m` per unit, per-axis range `[-83,886.08 m, 83,886.07 m]`.
|
|
53
56
|
- XROrigin locomotion delta for unbound poses (`xrOriginDelta`, 4×`int16`: `dx, dy, dz, dyaw`): `0.01 m` per unit for translation, `0.1°` for yaw. Receivers reconstruct `physicalPos = invDeltaRot * (headPos − deltaPos)`; it is not on the wire as a separate absolute field.
|
|
54
57
|
- Direct physical payload for moving-floor-local poses (`physical`, 4×`int16`: `x, y, z, yaw`): `0.01 m` per unit for translation, `0.1°` for yaw.
|
|
@@ -59,7 +62,7 @@ styly-netsync-simulator --server tcp://localhost --room my_room --clients 50
|
|
|
59
62
|
|
|
60
63
|
The following options summarize trade-offs when expanding absolute-position range.
|
|
61
64
|
|
|
62
|
-
Assumed unbound baseline (`protocolVersion=
|
|
65
|
+
Assumed unbound baseline (`protocolVersion=7`, `MovingFloorLocal` off):
|
|
63
66
|
- Client pose body with `Physical+Head+Right+Left` valid and `virtualCount=0`: `46 bytes` (matches `test_client_body_size_with_full_pose_no_virtuals`).
|
|
64
67
|
- Room per-client entry (`clientNo + poseTime + clientBody`): `56 bytes`.
|
|
65
68
|
|
|
@@ -228,4 +231,4 @@ The response includes the room ID and whether each key was `"applied"`, `"queued
|
|
|
228
231
|
|
|
229
232
|
### Read consistency
|
|
230
233
|
|
|
231
|
-
GET endpoints return a snapshot of the REST bridge's in-process cache, which is populated by
|
|
234
|
+
GET endpoints return a snapshot of the REST bridge's in-process cache, which is populated by control-lane sync messages from the server. The first request to a room lazily creates a bridge and may return an empty snapshot until the initial sync arrives — retry after a short delay if needed.
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "styly-netsync-server"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.15.0"
|
|
8
8
|
description = "STYLY NetSync Server - Multiplayer framework for Location-Based Entertainment VR/MR experiences"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -16,7 +16,7 @@ Examples:
|
|
|
16
16
|
|
|
17
17
|
# Use server programmatically
|
|
18
18
|
from styly_netsync import NetSyncServer
|
|
19
|
-
server = NetSyncServer(
|
|
19
|
+
server = NetSyncServer(control_port=5555, transform_port=5557, pub_port=5556)
|
|
20
20
|
server.start()
|
|
21
21
|
|
|
22
22
|
# Use client programmatically
|
{styly_netsync_server-0.13.0 → styly_netsync_server-0.15.0}/src/styly_netsync/binary_serializer.py
RENAMED
|
@@ -6,7 +6,11 @@ from typing import Any
|
|
|
6
6
|
logger = logging.getLogger(__name__)
|
|
7
7
|
|
|
8
8
|
# Message type identifiers
|
|
9
|
-
|
|
9
|
+
# v7: Control and transform traffic use separate sockets, and clients register
|
|
10
|
+
# their control identity with MSG_CLIENT_HELLO.
|
|
11
|
+
# Bumped so mixed old/new builds fail the handshake instead of silently
|
|
12
|
+
# misparsing NV traffic (the version byte rides on transform/object messages).
|
|
13
|
+
PROTOCOL_VERSION = 7
|
|
10
14
|
MSG_CLIENT_TRANSFORM = 1
|
|
11
15
|
MSG_ROOM_TRANSFORM = 2 # Legacy room transform with short IDs only
|
|
12
16
|
MSG_RPC = 3 # Remote procedure call
|
|
@@ -25,6 +29,9 @@ MSG_OBJECT_OWNERSHIP_REQUEST = 15 # Client → Server: RequestOwnership/Release
|
|
|
25
29
|
MSG_OBJECT_OWNERSHIP_CHANGED = 16 # Server → Clients (ROUTER): ownership changed
|
|
26
30
|
MSG_OBJECT_OWNERSHIP_REJECTED = 17 # Server → Client (ROUTER): request rejected
|
|
27
31
|
MSG_CLIENT_VAR_CLEAR = 18 # Clear all client variables for the sender
|
|
32
|
+
MSG_CLIENT_HELLO = 19 # Client → Server: bind control identity to device ID
|
|
33
|
+
|
|
34
|
+
CLIENT_HELLO_FLAG_STEALTH = 0x01
|
|
28
35
|
|
|
29
36
|
# Transform data type identifiers (deprecated - kept for reference)
|
|
30
37
|
|
|
@@ -33,7 +40,7 @@ MSG_CLIENT_VAR_CLEAR = 18 # Clear all client variables for the sender
|
|
|
33
40
|
_max_virtual_transforms = 50
|
|
34
41
|
MAX_VIRTUAL_TRANSFORMS = _max_virtual_transforms # Legacy alias for backward compat
|
|
35
42
|
|
|
36
|
-
# Protocol
|
|
43
|
+
# Protocol v7 pose encoding constants
|
|
37
44
|
ABS_POS_SCALE = 0.01
|
|
38
45
|
LOCO_POS_SCALE = 0.01
|
|
39
46
|
REL_POS_SCALE = 0.005
|
|
@@ -629,6 +636,17 @@ def serialize_rpc_message(data: dict[str, Any]) -> bytes:
|
|
|
629
636
|
return bytes(buffer)
|
|
630
637
|
|
|
631
638
|
|
|
639
|
+
def serialize_client_hello(device_id: str, is_stealth: bool = False) -> bytes:
|
|
640
|
+
"""Serialize client hello for control-lane identity registration."""
|
|
641
|
+
buffer = bytearray()
|
|
642
|
+
buffer.append(MSG_CLIENT_HELLO)
|
|
643
|
+
buffer.append(PROTOCOL_VERSION)
|
|
644
|
+
flags = CLIENT_HELLO_FLAG_STEALTH if is_stealth else 0
|
|
645
|
+
buffer.append(flags)
|
|
646
|
+
_pack_string(buffer, device_id)
|
|
647
|
+
return bytes(buffer)
|
|
648
|
+
|
|
649
|
+
|
|
632
650
|
def parse_version(version_str: str) -> tuple[int, int, int]:
|
|
633
651
|
"""Parse semantic version string into (major, minor, patch) tuple.
|
|
634
652
|
|
|
@@ -686,7 +704,7 @@ def serialize_global_var_set(data: dict[str, Any]) -> bytes:
|
|
|
686
704
|
"""Serialize global variable set message
|
|
687
705
|
|
|
688
706
|
Args:
|
|
689
|
-
data: Dictionary with senderClientNo, variableName, variableValue
|
|
707
|
+
data: Dictionary with senderClientNo, variableName, variableValue
|
|
690
708
|
"""
|
|
691
709
|
buffer = bytearray()
|
|
692
710
|
|
|
@@ -704,9 +722,6 @@ def serialize_global_var_set(data: dict[str, Any]) -> bytes:
|
|
|
704
722
|
value = data.get("variableValue", "")[:1024]
|
|
705
723
|
_pack_string(buffer, value, use_ushort=True)
|
|
706
724
|
|
|
707
|
-
# Timestamp (8 bytes double)
|
|
708
|
-
buffer.extend(struct.pack("<d", data.get("timestamp", 0.0)))
|
|
709
|
-
|
|
710
725
|
return bytes(buffer)
|
|
711
726
|
|
|
712
727
|
|
|
@@ -729,7 +744,6 @@ def serialize_global_var_sync(data: dict[str, Any]) -> bytes:
|
|
|
729
744
|
for var in variables:
|
|
730
745
|
_pack_string(buffer, var.get("name", "")[:64])
|
|
731
746
|
_pack_string(buffer, var.get("value", "")[:1024], use_ushort=True)
|
|
732
|
-
buffer.extend(struct.pack("<d", var.get("timestamp", 0.0)))
|
|
733
747
|
buffer.extend(struct.pack("<H", var.get("lastWriterClientNo", 0)))
|
|
734
748
|
|
|
735
749
|
return bytes(buffer)
|
|
@@ -739,7 +753,8 @@ def serialize_client_var_set(data: dict[str, Any]) -> bytes:
|
|
|
739
753
|
"""Serialize client variable set message
|
|
740
754
|
|
|
741
755
|
Args:
|
|
742
|
-
data: Dictionary with senderClientNo, targetClientNo, variableName,
|
|
756
|
+
data: Dictionary with senderClientNo, targetClientNo, variableName,
|
|
757
|
+
variableValue
|
|
743
758
|
"""
|
|
744
759
|
buffer = bytearray()
|
|
745
760
|
|
|
@@ -760,9 +775,6 @@ def serialize_client_var_set(data: dict[str, Any]) -> bytes:
|
|
|
760
775
|
value = data.get("variableValue", "")[:1024]
|
|
761
776
|
_pack_string(buffer, value, use_ushort=True)
|
|
762
777
|
|
|
763
|
-
# Timestamp (8 bytes double)
|
|
764
|
-
buffer.extend(struct.pack("<d", data.get("timestamp", 0.0)))
|
|
765
|
-
|
|
766
778
|
return bytes(buffer)
|
|
767
779
|
|
|
768
780
|
|
|
@@ -770,7 +782,7 @@ def serialize_client_var_clear(data: dict[str, Any]) -> bytes:
|
|
|
770
782
|
"""Serialize client variable clear message.
|
|
771
783
|
|
|
772
784
|
Args:
|
|
773
|
-
data: Dictionary with senderClientNo
|
|
785
|
+
data: Dictionary with senderClientNo.
|
|
774
786
|
"""
|
|
775
787
|
buffer = bytearray()
|
|
776
788
|
|
|
@@ -780,9 +792,6 @@ def serialize_client_var_clear(data: dict[str, Any]) -> bytes:
|
|
|
780
792
|
# Sender client number (2 bytes)
|
|
781
793
|
buffer.extend(struct.pack("<H", data.get("senderClientNo", 0)))
|
|
782
794
|
|
|
783
|
-
# Timestamp (8 bytes double)
|
|
784
|
-
buffer.extend(struct.pack("<d", data.get("timestamp", 0.0)))
|
|
785
|
-
|
|
786
795
|
return bytes(buffer)
|
|
787
796
|
|
|
788
797
|
|
|
@@ -811,7 +820,6 @@ def serialize_client_var_sync(data: dict[str, Any]) -> bytes:
|
|
|
811
820
|
for var in variables:
|
|
812
821
|
_pack_string(buffer, var.get("name", "")[:64])
|
|
813
822
|
_pack_string(buffer, var.get("value", "")[:1024], use_ushort=True)
|
|
814
|
-
buffer.extend(struct.pack("<d", var.get("timestamp", 0.0)))
|
|
815
823
|
buffer.extend(struct.pack("<H", var.get("lastWriterClientNo", 0)))
|
|
816
824
|
|
|
817
825
|
return bytes(buffer)
|
|
@@ -832,7 +840,7 @@ def deserialize(data: bytes) -> tuple[int, dict[str, Any] | None, bytes]:
|
|
|
832
840
|
offset += 1
|
|
833
841
|
|
|
834
842
|
# Validate message type is within valid range
|
|
835
|
-
if message_type < MSG_CLIENT_TRANSFORM or message_type >
|
|
843
|
+
if message_type < MSG_CLIENT_TRANSFORM or message_type > MSG_CLIENT_HELLO:
|
|
836
844
|
# Return invalid message type with None data instead of raising exception
|
|
837
845
|
return message_type, None, b""
|
|
838
846
|
|
|
@@ -863,6 +871,8 @@ def deserialize(data: bytes) -> tuple[int, dict[str, Any] | None, bytes]:
|
|
|
863
871
|
return message_type, _deserialize_client_var_sync(data, offset), b""
|
|
864
872
|
elif message_type == MSG_CLIENT_VAR_CLEAR:
|
|
865
873
|
return message_type, _deserialize_client_var_clear(data, offset), b""
|
|
874
|
+
elif message_type == MSG_CLIENT_HELLO:
|
|
875
|
+
return message_type, _deserialize_client_hello(data, offset), b""
|
|
866
876
|
elif message_type == MSG_OBJECT_POSE:
|
|
867
877
|
return message_type, _deserialize_object_pose(data, offset), b""
|
|
868
878
|
elif message_type == MSG_ROOM_OBJECTS:
|
|
@@ -897,6 +907,22 @@ def deserialize(data: bytes) -> tuple[int, dict[str, Any] | None, bytes]:
|
|
|
897
907
|
return message_type, None, b""
|
|
898
908
|
|
|
899
909
|
|
|
910
|
+
def _deserialize_client_hello(data: bytes, offset: int) -> dict[str, Any]:
|
|
911
|
+
"""Deserialize client hello control message."""
|
|
912
|
+
protocol_version = data[offset]
|
|
913
|
+
offset += 1
|
|
914
|
+
if protocol_version != PROTOCOL_VERSION:
|
|
915
|
+
raise ValueError(f"Unsupported protocol version: {protocol_version}")
|
|
916
|
+
flags = data[offset]
|
|
917
|
+
offset += 1
|
|
918
|
+
device_id, offset = _unpack_string(data, offset)
|
|
919
|
+
return {
|
|
920
|
+
"deviceId": device_id,
|
|
921
|
+
"flags": flags,
|
|
922
|
+
"isStealthMode": bool(flags & CLIENT_HELLO_FLAG_STEALTH),
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
|
|
900
926
|
def _deserialize_client_body(data: bytes, offset: int) -> tuple[dict[str, Any], int]:
|
|
901
927
|
"""Deserialize protocol v5 compact pose body."""
|
|
902
928
|
result: dict[str, Any] = {}
|
|
@@ -1247,10 +1273,6 @@ def _deserialize_global_var_set(data: bytes, offset: int) -> dict[str, Any]:
|
|
|
1247
1273
|
# Variable value
|
|
1248
1274
|
result["variableValue"], offset = _unpack_string(data, offset, use_ushort=True)
|
|
1249
1275
|
|
|
1250
|
-
# Timestamp (8 bytes double)
|
|
1251
|
-
result["timestamp"] = struct.unpack("<d", data[offset : offset + 8])[0]
|
|
1252
|
-
offset += 8
|
|
1253
|
-
|
|
1254
1276
|
return result
|
|
1255
1277
|
|
|
1256
1278
|
|
|
@@ -1267,8 +1289,6 @@ def _deserialize_global_var_sync(data: bytes, offset: int) -> dict[str, Any]:
|
|
|
1267
1289
|
var = {}
|
|
1268
1290
|
var["name"], offset = _unpack_string(data, offset)
|
|
1269
1291
|
var["value"], offset = _unpack_string(data, offset, use_ushort=True)
|
|
1270
|
-
var["timestamp"] = struct.unpack("<d", data[offset : offset + 8])[0]
|
|
1271
|
-
offset += 8
|
|
1272
1292
|
var["lastWriterClientNo"] = struct.unpack("<H", data[offset : offset + 2])[0]
|
|
1273
1293
|
offset += 2
|
|
1274
1294
|
result["variables"].append(var)
|
|
@@ -1294,10 +1314,6 @@ def _deserialize_client_var_set(data: bytes, offset: int) -> dict[str, Any]:
|
|
|
1294
1314
|
# Variable value
|
|
1295
1315
|
result["variableValue"], offset = _unpack_string(data, offset, use_ushort=True)
|
|
1296
1316
|
|
|
1297
|
-
# Timestamp (8 bytes double)
|
|
1298
|
-
result["timestamp"] = struct.unpack("<d", data[offset : offset + 8])[0]
|
|
1299
|
-
offset += 8
|
|
1300
|
-
|
|
1301
1317
|
return result
|
|
1302
1318
|
|
|
1303
1319
|
|
|
@@ -1307,10 +1323,6 @@ def _deserialize_client_var_clear(data: bytes, offset: int) -> dict[str, Any]:
|
|
|
1307
1323
|
|
|
1308
1324
|
# Sender client number (2 bytes)
|
|
1309
1325
|
result["senderClientNo"] = struct.unpack("<H", data[offset : offset + 2])[0]
|
|
1310
|
-
offset += 2
|
|
1311
|
-
|
|
1312
|
-
# Timestamp (8 bytes double)
|
|
1313
|
-
result["timestamp"] = struct.unpack("<d", data[offset : offset + 8])[0]
|
|
1314
1326
|
|
|
1315
1327
|
return result
|
|
1316
1328
|
|
|
@@ -1336,8 +1348,6 @@ def _deserialize_client_var_sync(data: bytes, offset: int) -> dict[str, Any]:
|
|
|
1336
1348
|
var = {}
|
|
1337
1349
|
var["name"], offset = _unpack_string(data, offset)
|
|
1338
1350
|
var["value"], offset = _unpack_string(data, offset, use_ushort=True)
|
|
1339
|
-
var["timestamp"] = struct.unpack("<d", data[offset : offset + 8])[0]
|
|
1340
|
-
offset += 8
|
|
1341
1351
|
var["lastWriterClientNo"] = struct.unpack("<H", data[offset : offset + 2])[
|
|
1342
1352
|
0
|
|
1343
1353
|
]
|
|
@@ -1359,6 +1369,10 @@ def serialize_object_pose(data: dict[str, Any]) -> bytes:
|
|
|
1359
1369
|
buffer = bytearray()
|
|
1360
1370
|
buffer.append(MSG_OBJECT_POSE)
|
|
1361
1371
|
buffer.append(PROTOCOL_VERSION)
|
|
1372
|
+
# Sender device ID: lets the server attribute the pose by identity carried in
|
|
1373
|
+
# the payload rather than the transform-lane socket identity (which a stealth
|
|
1374
|
+
# owner never binds because it sends no MSG_CLIENT_POSE).
|
|
1375
|
+
_pack_string(buffer, str(data.get("deviceId", "")))
|
|
1362
1376
|
object_id = int(data.get("objectId", 0)) & 0xFFFFFFFF
|
|
1363
1377
|
buffer.extend(struct.pack("<I", object_id))
|
|
1364
1378
|
buffer.extend(struct.pack("<H", int(data.get("poseSeq", 0)) & 0xFFFF))
|
|
@@ -1456,6 +1470,8 @@ def _deserialize_object_pose(data: bytes, offset: int) -> dict[str, Any]:
|
|
|
1456
1470
|
offset += 1
|
|
1457
1471
|
if protocol_version != PROTOCOL_VERSION:
|
|
1458
1472
|
raise ValueError(f"Unsupported protocol version: {protocol_version}")
|
|
1473
|
+
device_id, offset = _unpack_string(data, offset)
|
|
1474
|
+
result["deviceId"] = device_id
|
|
1459
1475
|
result["objectId"] = struct.unpack("<I", data[offset : offset + 4])[0]
|
|
1460
1476
|
offset += 4
|
|
1461
1477
|
result["poseSeq"] = struct.unpack("<H", data[offset : offset + 2])[0]
|