Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement a device-host messaging service #11

Merged
merged 3 commits into from
Nov 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/workflows/build_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@ jobs:
cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release ..
make -j4

- name: Build and run unit tests
run: |
export CXX=clang++
mkdir build_unittest
cd build_unittest
cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=./ ..
make install -j4
./bin/unittest_comms

build-ubuntu-x64-gcc:
name: Ubuntu x64 GCC
runs-on: ubuntu-22.04
Expand Down
6 changes: 4 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
.vs
.vscode

# Python directories
__pycache__

# CMake build directories
build
build_arm64
build*

# Build and debug output files
/.cache
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@
[submodule "khronos/egl"]
path = khronos/egl
url = https://github.com/KhronosGroup/EGL-Registry
[submodule "source_third_party/gtest"]
path = source_third_party/gtest
url = https://github.com/google/googletest
41 changes: 41 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# SPDX-License-Identifier: MIT
# -----------------------------------------------------------------------------
# Copyright (c) 2024 Arm Limited
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
# -----------------------------------------------------------------------------

cmake_minimum_required(VERSION 3.17)

set(CMAKE_CXX_STANDARD 20)

project(libGPULayers_UnitTests VERSION 1.0.0)

# Build steps
set(LGL_UNITTEST ON)

# Build GoogleTest framework
set(INSTALL_GTEST OFF CACHE BOOL "" FORCE)
add_subdirectory(source_third_party/gtest)

# Enable ctest
enable_testing()

# Build unit tests
add_subdirectory(source_common)
54 changes: 54 additions & 0 deletions lgl_host_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# SPDX-License-Identifier: MIT
# -----------------------------------------------------------------------------
# Copyright (c) 2024 Arm Limited
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
# -----------------------------------------------------------------------------

# This module implements a host server that provides services over the network
# to a layer running on a remote device.

import sys
import lglpy.server
import lglpy.service_test
import lglpy.service_log

def main():
# Create a server instance
server = lglpy.server.CommsServer(63412)

# Register all the services with it
print(f'Registering host services:')
test_service = lglpy.service_test.TestService()
endpoint_id = server.register_endpoint(test_service)
print(f' - [{endpoint_id}] = {test_service.get_service_name()}')

log_service = lglpy.service_log.LogService()
endpoint_id = server.register_endpoint(log_service)
print(f' - [{endpoint_id}] = {log_service.get_service_name()}')
print()

# Start it running
server.run()

return 0


if __name__ == '__main__':
sys.exit(main())
Empty file added lglpy/__init__.py
Empty file.
175 changes: 175 additions & 0 deletions lglpy/server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
# SPDX-License-Identifier: MIT
# -----------------------------------------------------------------------------
# Copyright (c) 2024 Arm Limited
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
# -----------------------------------------------------------------------------

# This module implements the server-side communications module that can
# accept client connections from the layer drivers, and dispatch them to
# handlers in the server.
#
# This module currently only accepts a single connection from a layer at a time
# and runs in the context of the calling thread, so if it needs to run in the
# background the user must create a new thread to contain it. It is therefore
# not possible to implement pseudo-host-driven event loops if the layer is
# using multiple services concurrently - this needs threads per service.

import enum
import socket
import struct


class MessageType(enum.Enum):
'''
The received message type.
'''
TX_ASYNC = 0
TX = 1
TX_RX = 2


class Message:
'''
A decoded message header packet.

See the MessageHeader struct in comms_message.hpp for binary layout.
'''

def __init__(self, header):
assert len(header) == 14, 'Header length is incorrect'

fields = struct.unpack('<BBQL', header)

self.message_type = MessageType(fields[0])
self.endpoint_id = fields[1]
self.message_id = fields[2]
self.payload_size = fields[3]
self.payload = b''

def add_payload(self, data):
self.payload = data

class Response:
'''
An encoded message header packet.

See the MessageHeader struct in comms_message.hpp for binary layout.
'''

def __init__(self, message, data):

self.message_type = message.message_type
self.message_id = message.message_id
self.payload_size = len(data)

def get_header(self):
data = struct.pack('<BBQL', self.message_type.value, 0,
self.message_id, self.payload_size)
return data

class CommsServer:

def __init__(self, port: int):
self.port = port
self.endpoints = {}
self.register_endpoint(self)

def get_service_name(self) -> str:
return 'registry'

def register_endpoint(self, endpoint) -> int:
endpoint_id = len(self.endpoints)
self.endpoints[endpoint_id] = endpoint
return endpoint_id

def handle_message(self, message: Message):
data = []
for endpoint_id, endpoint in self.endpoints.items():
name = endpoint.get_service_name().encode('utf-8')
data.append(struct.pack('<BL', endpoint_id, len(name)))
data.append(name)

return b''.join(data)

def run(self):
listen_sockfd = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
listen_sockfd.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

listen_sockfd.bind(('localhost', self.port))
listen_sockfd.listen(1)

# Accept connections from outside
while True:
print('Waiting for connection')
sockfd, _ = listen_sockfd.accept()
print(' + Client connected')

# TODO: Add shutdown code to the loop
while True:
# Read the header
data = self.receive_data(sockfd, 14)
if not data:
break
message = Message(data)

if message.payload_size:
# Read the payload
data = self.receive_data(sockfd, message.payload_size)
if not data:
break
message.add_payload(data)

# Dispatch to a handler
endpoint = self.endpoints[message.endpoint_id]
response = endpoint.handle_message(message)

# Send a response for all TX_RX messages
if message.message_type == MessageType.TX_RX:
header = Response(message, response)
sent = self.send_data(sockfd, header.get_header())
if not sent:
break
sent = self.send_data(sockfd, response)
if not sent:
break

listen_sockfd.close()

def receive_data(self, sockfd, byte_count):
data = b''

while len(data) < byte_count:
new_data = sockfd.recv(byte_count - len(data))
if not new_data:
print(" - Client disconnected")
return None
data = data + new_data

return data

def send_data(self, sockfd, data):
while len(data):
sent_bytes = sockfd.send(data)
if not sent_bytes:
print(" - Client disconnected")
return False
data = data[sent_bytes:]

return True
42 changes: 42 additions & 0 deletions lglpy/service_log.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# SPDX-License-Identifier: MIT
# -----------------------------------------------------------------------------
# Copyright (c) 2024 Arm Limited
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
# -----------------------------------------------------------------------------

# This module implements the server-side communications module service that
# implements basic logging.

class LogService:
'''
A decoded message header packet.

See the MessageHeader struct in comms_message.hpp for binary layout.
'''

def __init__(self):
pass

def get_service_name(self):
return 'log'

def handle_message(self, message):
log_entry = payload.decode(encoding='utf-8')
print(log_entry)
Loading