Skip to content

Commit 1681c82

Browse files
authored
Implement a device-host messaging service (#11)
Add a basic building block that can be used to let layers act as a network client to a server running on the host. The design for this module exposes the server as a number of message-oriented endpoint services which are multiplexed on a single socket communications channel. Support message types are synchronous send (tx), asynchronous send (tx_async), and synchronous send-receive (tx_rx). On Android the device-side socket can use an abstract Unix domain sockets, which can connect to a TCP/IP socket on the host when using "adb reverse" to proxy the connection. On Linux the device-side socket can directly use TCP/IP. The default host server, which provides some basic services out-of-the-box, is implemented in Python. Client-side implementations of higher-level services, such as a virtual file system, will be provided in follow-on PRs.
1 parent 156305b commit 1681c82

28 files changed

+2970
-4
lines changed

.github/workflows/build_test.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,15 @@ jobs:
3030
cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release ..
3131
make -j4
3232
33+
- name: Build and run unit tests
34+
run: |
35+
export CXX=clang++
36+
mkdir build_unittest
37+
cd build_unittest
38+
cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=./ ..
39+
make install -j4
40+
./bin/unittest_comms
41+
3342
build-ubuntu-x64-gcc:
3443
name: Ubuntu x64 GCC
3544
runs-on: ubuntu-22.04

.gitignore

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
.vs
33
.vscode
44

5+
# Python directories
6+
__pycache__
7+
58
# CMake build directories
6-
build
7-
build_arm64
9+
build*
810

911
# Build and debug output files
1012
/.cache

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,6 @@
77
[submodule "khronos/egl"]
88
path = khronos/egl
99
url = https://github.com/KhronosGroup/EGL-Registry
10+
[submodule "source_third_party/gtest"]
11+
path = source_third_party/gtest
12+
url = https://github.com/google/googletest

CMakeLists.txt

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# SPDX-License-Identifier: MIT
2+
# -----------------------------------------------------------------------------
3+
# Copyright (c) 2024 Arm Limited
4+
#
5+
# Permission is hereby granted, free of charge, to any person obtaining a copy
6+
# of this software and associated documentation files (the "Software"), to
7+
# deal in the Software without restriction, including without limitation the
8+
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9+
# sell copies of the Software, and to permit persons to whom the Software is
10+
# furnished to do so, subject to the following conditions:
11+
#
12+
# The above copyright notice and this permission notice shall be included in
13+
# all copies or substantial portions of the Software.
14+
#
15+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
# SOFTWARE.
22+
# -----------------------------------------------------------------------------
23+
24+
cmake_minimum_required(VERSION 3.17)
25+
26+
set(CMAKE_CXX_STANDARD 20)
27+
28+
project(libGPULayers_UnitTests VERSION 1.0.0)
29+
30+
# Build steps
31+
set(LGL_UNITTEST ON)
32+
33+
# Build GoogleTest framework
34+
set(INSTALL_GTEST OFF CACHE BOOL "" FORCE)
35+
add_subdirectory(source_third_party/gtest)
36+
37+
# Enable ctest
38+
enable_testing()
39+
40+
# Build unit tests
41+
add_subdirectory(source_common)

lgl_host_server.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# SPDX-License-Identifier: MIT
2+
# -----------------------------------------------------------------------------
3+
# Copyright (c) 2024 Arm Limited
4+
#
5+
# Permission is hereby granted, free of charge, to any person obtaining a copy
6+
# of this software and associated documentation files (the "Software"), to
7+
# deal in the Software without restriction, including without limitation the
8+
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9+
# sell copies of the Software, and to permit persons to whom the Software is
10+
# furnished to do so, subject to the following conditions:
11+
#
12+
# The above copyright notice and this permission notice shall be included in
13+
# all copies or substantial portions of the Software.
14+
#
15+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
# SOFTWARE.
22+
# -----------------------------------------------------------------------------
23+
24+
# This module implements a host server that provides services over the network
25+
# to a layer running on a remote device.
26+
27+
import sys
28+
import lglpy.server
29+
import lglpy.service_test
30+
import lglpy.service_log
31+
32+
def main():
33+
# Create a server instance
34+
server = lglpy.server.CommsServer(63412)
35+
36+
# Register all the services with it
37+
print(f'Registering host services:')
38+
test_service = lglpy.service_test.TestService()
39+
endpoint_id = server.register_endpoint(test_service)
40+
print(f' - [{endpoint_id}] = {test_service.get_service_name()}')
41+
42+
log_service = lglpy.service_log.LogService()
43+
endpoint_id = server.register_endpoint(log_service)
44+
print(f' - [{endpoint_id}] = {log_service.get_service_name()}')
45+
print()
46+
47+
# Start it running
48+
server.run()
49+
50+
return 0
51+
52+
53+
if __name__ == '__main__':
54+
sys.exit(main())

lglpy/__init__.py

Whitespace-only changes.

lglpy/server.py

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
# SPDX-License-Identifier: MIT
2+
# -----------------------------------------------------------------------------
3+
# Copyright (c) 2024 Arm Limited
4+
#
5+
# Permission is hereby granted, free of charge, to any person obtaining a copy
6+
# of this software and associated documentation files (the "Software"), to
7+
# deal in the Software without restriction, including without limitation the
8+
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9+
# sell copies of the Software, and to permit persons to whom the Software is
10+
# furnished to do so, subject to the following conditions:
11+
#
12+
# The above copyright notice and this permission notice shall be included in
13+
# all copies or substantial portions of the Software.
14+
#
15+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
# SOFTWARE.
22+
# -----------------------------------------------------------------------------
23+
24+
# This module implements the server-side communications module that can
25+
# accept client connections from the layer drivers, and dispatch them to
26+
# handlers in the server.
27+
#
28+
# This module currently only accepts a single connection from a layer at a time
29+
# and runs in the context of the calling thread, so if it needs to run in the
30+
# background the user must create a new thread to contain it. It is therefore
31+
# not possible to implement pseudo-host-driven event loops if the layer is
32+
# using multiple services concurrently - this needs threads per service.
33+
34+
import enum
35+
import socket
36+
import struct
37+
38+
39+
class MessageType(enum.Enum):
40+
'''
41+
The received message type.
42+
'''
43+
TX_ASYNC = 0
44+
TX = 1
45+
TX_RX = 2
46+
47+
48+
class Message:
49+
'''
50+
A decoded message header packet.
51+
52+
See the MessageHeader struct in comms_message.hpp for binary layout.
53+
'''
54+
55+
def __init__(self, header):
56+
assert len(header) == 14, 'Header length is incorrect'
57+
58+
fields = struct.unpack('<BBQL', header)
59+
60+
self.message_type = MessageType(fields[0])
61+
self.endpoint_id = fields[1]
62+
self.message_id = fields[2]
63+
self.payload_size = fields[3]
64+
self.payload = b''
65+
66+
def add_payload(self, data):
67+
self.payload = data
68+
69+
class Response:
70+
'''
71+
An encoded message header packet.
72+
73+
See the MessageHeader struct in comms_message.hpp for binary layout.
74+
'''
75+
76+
def __init__(self, message, data):
77+
78+
self.message_type = message.message_type
79+
self.message_id = message.message_id
80+
self.payload_size = len(data)
81+
82+
def get_header(self):
83+
data = struct.pack('<BBQL', self.message_type.value, 0,
84+
self.message_id, self.payload_size)
85+
return data
86+
87+
class CommsServer:
88+
89+
def __init__(self, port: int):
90+
self.port = port
91+
self.endpoints = {}
92+
self.register_endpoint(self)
93+
94+
def get_service_name(self) -> str:
95+
return 'registry'
96+
97+
def register_endpoint(self, endpoint) -> int:
98+
endpoint_id = len(self.endpoints)
99+
self.endpoints[endpoint_id] = endpoint
100+
return endpoint_id
101+
102+
def handle_message(self, message: Message):
103+
data = []
104+
for endpoint_id, endpoint in self.endpoints.items():
105+
name = endpoint.get_service_name().encode('utf-8')
106+
data.append(struct.pack('<BL', endpoint_id, len(name)))
107+
data.append(name)
108+
109+
return b''.join(data)
110+
111+
def run(self):
112+
listen_sockfd = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
113+
listen_sockfd.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
114+
115+
listen_sockfd.bind(('localhost', self.port))
116+
listen_sockfd.listen(1)
117+
118+
# Accept connections from outside
119+
while True:
120+
print('Waiting for connection')
121+
sockfd, _ = listen_sockfd.accept()
122+
print(' + Client connected')
123+
124+
# TODO: Add shutdown code to the loop
125+
while True:
126+
# Read the header
127+
data = self.receive_data(sockfd, 14)
128+
if not data:
129+
break
130+
message = Message(data)
131+
132+
if message.payload_size:
133+
# Read the payload
134+
data = self.receive_data(sockfd, message.payload_size)
135+
if not data:
136+
break
137+
message.add_payload(data)
138+
139+
# Dispatch to a handler
140+
endpoint = self.endpoints[message.endpoint_id]
141+
response = endpoint.handle_message(message)
142+
143+
# Send a response for all TX_RX messages
144+
if message.message_type == MessageType.TX_RX:
145+
header = Response(message, response)
146+
sent = self.send_data(sockfd, header.get_header())
147+
if not sent:
148+
break
149+
sent = self.send_data(sockfd, response)
150+
if not sent:
151+
break
152+
153+
listen_sockfd.close()
154+
155+
def receive_data(self, sockfd, byte_count):
156+
data = b''
157+
158+
while len(data) < byte_count:
159+
new_data = sockfd.recv(byte_count - len(data))
160+
if not new_data:
161+
print(" - Client disconnected")
162+
return None
163+
data = data + new_data
164+
165+
return data
166+
167+
def send_data(self, sockfd, data):
168+
while len(data):
169+
sent_bytes = sockfd.send(data)
170+
if not sent_bytes:
171+
print(" - Client disconnected")
172+
return False
173+
data = data[sent_bytes:]
174+
175+
return True

lglpy/service_log.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# SPDX-License-Identifier: MIT
2+
# -----------------------------------------------------------------------------
3+
# Copyright (c) 2024 Arm Limited
4+
#
5+
# Permission is hereby granted, free of charge, to any person obtaining a copy
6+
# of this software and associated documentation files (the "Software"), to
7+
# deal in the Software without restriction, including without limitation the
8+
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9+
# sell copies of the Software, and to permit persons to whom the Software is
10+
# furnished to do so, subject to the following conditions:
11+
#
12+
# The above copyright notice and this permission notice shall be included in
13+
# all copies or substantial portions of the Software.
14+
#
15+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
# SOFTWARE.
22+
# -----------------------------------------------------------------------------
23+
24+
# This module implements the server-side communications module service that
25+
# implements basic logging.
26+
27+
class LogService:
28+
'''
29+
A decoded message header packet.
30+
31+
See the MessageHeader struct in comms_message.hpp for binary layout.
32+
'''
33+
34+
def __init__(self):
35+
pass
36+
37+
def get_service_name(self):
38+
return 'log'
39+
40+
def handle_message(self, message):
41+
log_entry = payload.decode(encoding='utf-8')
42+
print(log_entry)

0 commit comments

Comments
 (0)