Skip to content

Commit

Permalink
Add hal transmitter and socket support for remote hal
Browse files Browse the repository at this point in the history
Add base class hal_transmitter which can be used for simple send/
receive operations of data. This can be used by client and servers
for different models of communication.

Also added a socket version which will be the default use case.
  • Loading branch information
coldav committed Apr 11, 2024
1 parent e43ab34 commit adf7ac5
Show file tree
Hide file tree
Showing 5 changed files with 358 additions and 0 deletions.
1 change: 1 addition & 0 deletions hal/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,5 @@ target_compile_definitions(hal_common PRIVATE

target_link_libraries(hal_common PUBLIC $<$<PLATFORM_ID:Linux>:dl>)

add_subdirectory(hal_remote)
add_subdirectory(source/hal_null)
33 changes: 33 additions & 0 deletions hal/hal_remote/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Copyright (C) Codeplay Software Limited
#
# Licensed under the Apache License, Version 2.0 (the "License") with LLVM
# Exceptions; you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://github.com/codeplaysoftware/oneapi-construction-kit/blob/main/LICENSE.txt
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

set(HAL_SOURCE
${CMAKE_CURRENT_SOURCE_DIR}/include/hal_remote/hal_transmitter.h
${CMAKE_CURRENT_SOURCE_DIR}/source/hal_socket_transmitter.cpp
)

if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
list (APPEND HAL_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/include/hal_remote/hal_socket_transmitter.h)
endif()

add_library(
hal_remote STATIC ${HAL_SOURCE})
target_include_directories(
hal_remote PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
set_target_properties(
hal_remote PROPERTIES LINKER_LANGUAGE CXX)

target_link_libraries(hal_remote PUBLIC hal_common)
117 changes: 117 additions & 0 deletions hal/hal_remote/include/hal_remote/hal_socket_transmitter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// Copyright (C) Codeplay Software Limited
//
// Licensed under the Apache License, Version 2.0 (the "License") with LLVM
// Exceptions; you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://github.com/codeplaysoftware/oneapi-construction-kit/blob/main/LICENSE.txt
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
//
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

#ifndef _HAL_SOCKET_TRANSMITTER_H
#define _HAL_SOCKET_TRANSMITTER_H

#include <hal_remote/hal_transmitter.h>
#include <netinet/in.h>

namespace hal {

/// @brief A very simple socket based version of a hal_transmitter
/// @note This supports both client and server mode, and the sure should use
/// start_server() or make_connection() as appropriate. This supports the
/// required port being 0 for a server allows it to find a free port. For
/// some operations error_code enum will be used, but for `send` and `receive`
/// these are derived functions, so get_last_error() can be used.
class hal_socket_transmitter : public hal_transmitter {
public:
hal_socket_transmitter(uint16_t port = 0) : port_requested(port) {}
~hal_socket_transmitter();

enum error_code {
success,
bind_failed,
connect_failed,
connection_closed,
listen_failed,
accept_failed,
send_error,
recv_error,
getsockname_failed
};

/// @brief set port we wish to request on. This must be done before any calls
/// to start_server or make_connection.
/// @param port we wish to make request on
/// @note This duplicates the constructor argument but makes it easier
/// in some cases to default it initially and then set it later.
void set_port(uint16_t port) { port_requested = port; }

/// @brief Start the server end
/// @param print_port optionally print out that we are listening on a
/// particular port
/// @return success if successful. If unsuccessful last_error also will
/// contain error
error_code start_server(bool print_port);

/// @brief make a connection to a server
/// @return success if successful. If unsuccessful last_error also will
/// contain error
error_code make_connection();

/// @brief Indicates that a connection is live (either as server or as a
/// client)
/// @note it may still be the case that the last send/receive was in error
/// but we keep the connected flag live and report false in that case.
/// If the connection was dropped, recv can receive 0 bytes which is
/// what is used to set this to false.
/// @return true if connected.
bool connected() const { return is_connected; }

/// @brief returns the last error from socket operations
/// @return last error reported from the socket
int get_last_error() const { return last_error; }

/// @brief Receive `size` bytes of data into `data`
/// @return true if was able to receive the data. Note that false may
/// indicate that the connection was dropped or there were errors. In this
/// case check last_error for information (connection_closed if dropped)
bool receive(void *data, uint32_t size) override;

/// @brief Send `size` bytes of `data` with an optional flush
bool send(const void *data, uint32_t size, bool flush);

private:
/// @brief connect to the remote server
/// @return error returned by connect socket function
error_code connect();

/// @brief set up a connection basic ready for connect() or listen/accept
/// optionally adding a bind if being used as server and getting the
/// port number bound to.
/// @param server in server mode, requiring a bind() call
/// @return 0 for success, anything else represents the error from bind or
/// getsockname
error_code setup_connection(bool server);

int listen();
int accept();

int get_port() { return current_port; }

uint16_t port_requested;
uint16_t current_port = 0;
sockaddr_in server_address;
int sock = 0;
int fd_to_use = 0;
bool setup_connection_done = false;
error_code last_error = error_code::success;
bool is_connected = false;
};
} // namespace hal
#endif
44 changes: 44 additions & 0 deletions hal/hal_remote/include/hal_remote/hal_transmitter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright (C) Codeplay Software Limited
//
// Licensed under the Apache License, Version 2.0 (the "License") with LLVM
// Exceptions; you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://github.com/codeplaysoftware/oneapi-construction-kit/blob/main/LICENSE.txt
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
//
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

#include <cstdint>

#ifndef _HAL_TRANSMITTER_H
#define _HAL_TRANSMITTER_H

namespace hal {

/// @brief A simple class interface that can be used for transmitting and
/// sending data - derive from this to support sockets, file descriptors etc.
class hal_transmitter {
public:
/// @brief Send `size` bytes of `data` with an optional flush
/// @return true if the send succeeds
virtual bool send(const void *data, uint32_t size, bool flush) = 0;

/// Receive `size` bytes of data into `data`
/// @return true if the receive succeeds
virtual bool receive(void *data, uint32_t size) = 0;
virtual ~hal_transmitter(){};

void enable_debug(bool debug_enabled) { debug = debug_enabled; }
bool debug_enabled() { return debug; }

private:
bool debug = false;
};
} // namespace hal
#endif
163 changes: 163 additions & 0 deletions hal/hal_remote/source/hal_socket_transmitter.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
// Copyright (C) Codeplay Software Limited
//
// Licensed under the Apache License, Version 2.0 (the "License") with LLVM
// Exceptions; you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://github.com/codeplaysoftware/oneapi-construction-kit/blob/main/LICENSE.txt
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
//
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

#include <hal_remote/hal_socket_transmitter.h>
#include <hal_remote/hal_transmitter.h>
#include <sys/types.h>
#include <unistd.h>

#include <cstdio>

namespace hal {
hal_socket_transmitter::~hal_socket_transmitter() {
if (sock) {
close(sock);
}
if (fd_to_use && fd_to_use != sock) {
close(fd_to_use);
}
}

hal_socket_transmitter::error_code hal_socket_transmitter::start_server(
bool print_port) {
if (!setup_connection_done) {
hal_socket_transmitter::error_code res = setup_connection(true);
if (res != 0) {
return res;
}
setup_connection_done = true;
}
if (listen() != 0) {
last_error = hal_socket_transmitter::listen_failed;
return last_error;
}
if (print_port) {
printf("Listening on port %d\n", get_port());
fflush(stdout);
}
if (accept() == -1) {
last_error = hal_socket_transmitter::accept_failed;
return last_error;
}
last_error = hal_socket_transmitter::success;
return last_error;
}

/// @brief make a connection to a server
/// @return true if successful, otherwise will set last_error to errno
hal_socket_transmitter::error_code hal_socket_transmitter::make_connection() {
if (!setup_connection_done) {
last_error = setup_connection(false);
if (last_error != hal_socket_transmitter::success) {
return last_error;
}
setup_connection_done = true;
}
last_error = connect();
return last_error;
}

bool hal_socket_transmitter::receive(void *data, uint32_t size) {
uint32_t data_to_read = size;
uint32_t offset = 0;

// repeatedly recv until `size` bytes is read.
do {
int res = recv(fd_to_use, ((char *)data) + offset, data_to_read, 0);
// If recv returns 0, this indicates the connection has been dropped
// It's not an error as such but we are not able to continue
if (res == 0) {
is_connected = false;
fd_to_use = 0;
last_error = hal_socket_transmitter::connection_closed;
return false;
}
if (res == -1) {
last_error = hal_socket_transmitter::recv_error;
return false;
}
data_to_read = data_to_read - res;
offset += res;
} while (data_to_read);
last_error = hal_socket_transmitter::success;
return true;
}

/// @brief Send `size` bytes of `data` with an optional flush
bool hal_socket_transmitter::send(const void *data, uint32_t size, bool flush) {
const int ret = ::send(fd_to_use, data, size, 0);
if (ret == -1) {
last_error = hal_socket_transmitter::send_error;
return false;
}
last_error = hal_socket_transmitter::success;
return true;
}

hal_socket_transmitter::error_code hal_socket_transmitter::connect() {
int res = ::connect(sock, (struct sockaddr *)&server_address,
sizeof(server_address));
if (res != -1) {
fd_to_use = sock;
is_connected = true;
return hal_socket_transmitter::success;
} else {
return hal_socket_transmitter::connect_failed;
}
}

hal_socket_transmitter::error_code hal_socket_transmitter::setup_connection(
bool server) {
// @return 0 if success, otherwise -1 and errno set
sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
server_address.sin_family = AF_INET;
server_address.sin_port = htons(port_requested);
server_address.sin_addr.s_addr = INADDR_ANY;
if (server) {
if (int res = bind(sock, (struct sockaddr *)&server_address,
sizeof(server_address)) != 0) {
return hal_socket_transmitter::bind_failed;
}

struct sockaddr_in local_addr;
socklen_t len = sizeof(local_addr);
if (int res =
getsockname(sock, (struct sockaddr *)&local_addr, &len) != 0) {
return hal_socket_transmitter::getsockname_failed;
}
current_port = ntohs(local_addr.sin_port);
} else {
current_port = port_requested;
}
setup_connection_done = true;
return hal_socket_transmitter::success;
}

int hal_socket_transmitter::listen() {
if (int res = ::listen(sock, 1) != 0) {
return res;
}
return 0;
}
int hal_socket_transmitter::accept() {
int res = ::accept(sock, nullptr, nullptr);
if (res != -1) {
fd_to_use = res;
}
return res;
}

} // namespace hal

0 comments on commit adf7ac5

Please sign in to comment.