diff --git a/.github/workflows/build_binary.yml b/.github/workflows/build_binary.yml index e1afc4f..ae204fa 100644 --- a/.github/workflows/build_binary.yml +++ b/.github/workflows/build_binary.yml @@ -39,7 +39,7 @@ jobs: needs: ['setup-matrix'] runs-on: ${{ matrix.runner }} env: - ASTREIN_VERSION: '2.0.0' + ASTREIN_VERSION: '3.0.0' strategy: fail-fast: false @@ -178,6 +178,11 @@ jobs: with: fetch-depth: 0 + - name: 'Enable Git metadata in the build container' + run: | + git config --global --add safe.directory "${GITHUB_WORKSPACE}" + git rev-parse --verify HEAD + - name: 'Configure portable release' run: | cmake -S . \ diff --git a/CMakeLists.txt b/CMakeLists.txt index 783329d..d95e7b0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -65,7 +65,7 @@ set( CPMAddPackage( NAME cpp_core GITHUB_REPOSITORY Serial-IO/cpp-core - GIT_TAG v2.0.1 + GIT_TAG v3.0.0 OPTIONS "CMAKE_EXPORT_COMPILE_COMMANDS OFF" ) @@ -257,6 +257,9 @@ if(TEST_SOURCES) ) target_compile_features(cpp_bindings_linux_tests PRIVATE cxx_std_26) + target_compile_definitions(cpp_bindings_linux_tests PRIVATE + CPP_BINDINGS_LINUX_TEST_VERSION="${GIT_DESCRIBE}" + ) include(GoogleTest) gtest_discover_tests(cpp_bindings_linux_tests) diff --git a/README.md b/README.md index 823ab76..e75d8e5 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,8 @@ API documentation. - Ninja - A compiler with sufficient C++26 support -CMake downloads `cpp-core` and GoogleTest automatically during configuration. +CMake downloads `cpp-core` **v3.0.0** and GoogleTest automatically during +configuration. ## Build @@ -37,10 +38,10 @@ The shared library is written to `build/libcpp_bindings_linux.so`. Official release and JSR artifacts are built for these GNU/Linux targets: -| Target | CPU baseline | Minimum glibc | -| --- | --- | --- | -| `x86_64-linux-gnu` | generic x86-64 | 2.28 | -| `aarch64-linux-gnu` | ARMv8-A | 2.28 | +| Target | CPU baseline | Minimum glibc | +| ------------------- | -------------- | ------------- | +| `x86_64-linux-gnu` | generic x86-64 | 2.28 | +| `aarch64-linux-gnu` | ARMv8-A | 2.28 | ### Binary compatibility @@ -48,13 +49,13 @@ The prebuilt binaries require **glibc 2.28 or newer**. Compatibility depends on the installed glibc version rather than the distribution name. Common release baselines are shown below for orientation: -| Distribution | Release baseline | -| --- | --- | -| Debian | 10+ | -| Ubuntu | 20.04 LTS+ | -| RHEL / Rocky Linux / AlmaLinux | 8+ | -| Fedora | 29+ | -| openSUSE Leap | 15.x (not compatible by default) | +| Distribution | Release baseline | +| ------------------------------ | -------------------------------- | +| Debian | 10+ | +| Ubuntu | 20.04 LTS+ | +| RHEL / Rocky Linux / AlmaLinux | 8+ | +| Fedora | 29+ | +| openSUSE Leap | 15.x (not compatible by default) | Check the installed version with: @@ -79,8 +80,9 @@ cmake -S . -B build -G Ninja \ cmake --build build --target cpp_bindings_linux ``` -The CI release builds use pinned `manylinux_2_28` images with GCC 14. GCC 16 -is used separately to generate the ASTrein FFI metadata. +The CI release builds use pinned `manylinux_2_28` images with GCC 14. The FFI +metadata is generated separately with ASTrein 3.0.0 and a GCC 14 compile +context. To select a specific compiler, add it while configuring, for example: @@ -99,7 +101,8 @@ cmake --build --preset linux-gcc-release --target cpp_bindings_linux_tests ctest --test-dir build --output-on-failure ``` -Tests that require a serial device use `SERIAL_TEST_PORT`. They are skipped when no suitable device is available. +Tests that require a serial device use `SERIAL_TEST_PORT`. They are skipped when +no suitable device is available. The optional runtime integration smoke tests currently use Deno 2 as their FFI test harness and require a built library. Deno is not required to consume the @@ -112,4 +115,5 @@ deno task test ## License -This project is licensed under the [GNU Lesser General Public License v3.0](LICENSE). +This project is licensed under the +[GNU Lesser General Public License v3.0](LICENSE). diff --git a/integration_tests/ffi_bindings.ts b/integration_tests/ffi_bindings.ts index 6060b81..6ca6e45 100644 --- a/integration_tests/ffi_bindings.ts +++ b/integration_tests/ffi_bindings.ts @@ -7,8 +7,24 @@ export type LoadedLibrary = Deno.DynamicLibrary; export type SerialLib = LoadedLibrary["symbols"]; const symbols = { + meta: { + parameters: ["pointer"] as const, + result: "void" as const, + }, + serialReadUntilSequence: { + parameters: ["i64", "pointer", "i32", "pointer", "pointer", "i32", "pointer"] as const, + result: "i32" as const, + }, + serialWaitForDrain: { + parameters: ["i64", "pointer"] as const, + result: "i32" as const, + }, + serialSetEventCallback: { + parameters: ["pointer", "pointer"] as const, + result: "i32" as const, + }, serialOpen: { - parameters: ["pointer", "i32", "i32", "i32", "i32", "pointer"] as const, + parameters: ["pointer", "pointer", "pointer"] as const, result: "i64" as const, }, serialClose: { @@ -16,11 +32,11 @@ const symbols = { result: "i32" as const, }, serialRead: { - parameters: ["i64", "pointer", "i32", "i32", "i32", "pointer"] as const, + parameters: ["i64", "pointer", "i32", "pointer", "pointer"] as const, result: "i32" as const, }, serialWrite: { - parameters: ["i64", "pointer", "i32", "i32", "i32", "pointer"] as const, + parameters: ["i64", "pointer", "i32", "pointer", "pointer"] as const, result: "i32" as const, }, }; @@ -38,7 +54,7 @@ export async function loadSerialLib( // Try to find the library in common build locations const possiblePaths = [ - libraryPath, + libraryPath ?? Deno.env.get("SERIAL_LIBRARY_PATH"), "../build/libcpp_bindings_linux.so", "../build/libcpp_bindings_linux.so.0", "../build/libcpp_bindings_linux.so.0.0.0", diff --git a/integration_tests/integration_test.ts b/integration_tests/integration_test.ts index 806235f..f5d8045 100644 --- a/integration_tests/integration_test.ts +++ b/integration_tests/integration_test.ts @@ -4,7 +4,7 @@ * - verify that it can be cleanly unloaded again */ -import { assertExists } from "@std/assert"; +import { assertEquals, assertExists } from "@std/assert"; import { type LoadedLibrary, loadSerialLib, type SerialLib } from "./ffi_bindings.ts"; let lib: SerialLib | null = null; @@ -30,6 +30,33 @@ Deno.test({ sanitizeOps: false, }); +Deno.test({ + name: "cpp-core v3 configuration ABI", + fn() { + assertExists(lib); + const config = new Int32Array([9600, 8, 0, 0, 0]); + const timeout = new Int32Array([10, 1]); + const invalidTimeout = new Int32Array([-1, 1]); + const port = new TextEncoder().encode("/dev/ttyNONEXISTENT99999\0"); + const buffer = new Uint8Array(4); + const pointer = Deno.UnsafePointer.of; + + assertEquals(Number(lib.serialOpen(pointer(port), pointer(config), null)), -200); + assertEquals(Number(lib.serialOpen(pointer(port), null, null)), -405); + assertEquals(lib.serialRead(-1n, pointer(buffer), 4, pointer(timeout), null), -201); + assertEquals(lib.serialWrite(-1n, pointer(buffer), 4, pointer(timeout), null), -201); + assertEquals(lib.serialRead(-1n, pointer(buffer), 4, null, null), -105); + assertEquals(lib.serialWrite(-1n, pointer(buffer), 4, pointer(invalidTimeout), null), -105); + assertEquals( + lib.serialReadUntilSequence(-1n, pointer(buffer), 4, pointer(timeout), pointer(buffer), 0, null), + -304, + ); + assertEquals(lib.serialWaitForDrain(-1n, null), -201); + assertEquals(lib.serialSetEventCallback(null, null), 0); + lib.meta(null); + }, +}); + Deno.test({ name: "Unload cpp-bindings-linux library", async fn() { diff --git a/jsr/README.md b/jsr/README.md index 322e815..5a4b99b 100644 --- a/jsr/README.md +++ b/jsr/README.md @@ -42,8 +42,9 @@ distribution releases are still supported by their vendors. ## FFI metadata It also includes cpp-core FFI API metadata generated with -[ASTrein](https://github.com/Katze719/ASTrein) at `bin/x86_64/ffi.json` and -`bin/aarch64/ffi.json`. It describes the exported C symbols, parameter and +[ASTrein 3.0.0](https://github.com/Katze719/ASTrein/releases/tag/v3.0.0) at `bin/x86_64/ffi.json` and +`bin/aarch64/ffi.json`, using the `astrein_ffi_api` schema version 3. +It describes the exported C symbols, parameter and return types, callbacks, structs, default values, and API documentation used by runtime-specific FFI adapter generators. diff --git a/src/detail/parse_stop_bits.hpp b/src/detail/parse_stop_bits.hpp index 32b456e..5ad5a4b 100644 --- a/src/detail/parse_stop_bits.hpp +++ b/src/detail/parse_stop_bits.hpp @@ -12,7 +12,6 @@ inline auto parseStopBits(int stop_bits, ErrorCallbackT error_callback, StatusCo switch (stop_bits) { case 0: - case 1: return StopBits::kOne; case 2: return StopBits::kTwo; diff --git a/src/detail/read_impl.hpp b/src/detail/read_impl.hpp index 3df7e6a..19297ee 100644 --- a/src/detail/read_impl.hpp +++ b/src/detail/read_impl.hpp @@ -17,10 +17,19 @@ namespace cpp_bindings_linux::detail { -inline auto readImpl(int64_t handle, void *buffer, int buffer_size, int timeout_ms, int multiplier, - const unsigned char *terminator, int terminator_size, ErrorCallbackT error_callback) -> int +inline auto readImpl(int64_t handle, std::uint8_t *buffer, int buffer_size, + const cpp_core::SerialTimeoutConfig *timeout_config, const unsigned char *terminator, + int terminator_size, ErrorCallbackT error_callback) -> int { const auto callback = effectiveErrorCallback(error_callback); + const auto timeout_status = cpp_core::validateTimeoutConfig(timeout_config, callback); + if (timeout_status < 0) + { + return timeout_status; + } + const int timeout_ms = timeout_config->timeout_ms; + const int multiplier = timeout_config->multiplier; + const auto buffer_status = cpp_core::validateBuffer(buffer, buffer_size, callback); if (buffer_status < 0) { diff --git a/src/detail/termios2.hpp b/src/detail/termios2.hpp index e6d92ad..d8aee80 100644 --- a/src/detail/termios2.hpp +++ b/src/detail/termios2.hpp @@ -8,7 +8,8 @@ #endif #ifndef BOTHER -#define BOTHER 0x010000 +// Linux asm-generic/termbits.h: select the explicit c_ispeed/c_ospeed values. +#define BOTHER 0x00001000 #endif #ifndef CRTSCTS diff --git a/src/detail/validate_baudrate_value.hpp b/src/detail/validate_baudrate_value.hpp index 3cfe08e..39e41b8 100644 --- a/src/detail/validate_baudrate_value.hpp +++ b/src/detail/validate_baudrate_value.hpp @@ -11,7 +11,7 @@ namespace cpp_bindings_linux::detail { inline auto validateBaudrateValue(int baudrate) -> cpp_core::Status { - if (cpp_core::SerialConfig::tryMake(baudrate, 8)) + if (cpp_core::SerialConfig::tryMake(baudrate, cpp_core::DataBits::kEight)) { return cpp_core::ok(); } diff --git a/src/detail/validate_data_bits_value.hpp b/src/detail/validate_data_bits_value.hpp index b040e5c..b25186c 100644 --- a/src/detail/validate_data_bits_value.hpp +++ b/src/detail/validate_data_bits_value.hpp @@ -11,7 +11,7 @@ namespace cpp_bindings_linux::detail { inline auto validateDataBitsValue(int data_bits) -> cpp_core::Status { - if (cpp_core::SerialConfig::tryMake(300, data_bits)) + if (cpp_core::SerialConfig::tryMake(300, static_cast(data_bits))) { return cpp_core::ok(); } diff --git a/src/detail/write_impl.hpp b/src/detail/write_impl.hpp index 87283d1..57d005f 100644 --- a/src/detail/write_impl.hpp +++ b/src/detail/write_impl.hpp @@ -16,10 +16,18 @@ namespace cpp_bindings_linux::detail { -inline auto writeImpl(int64_t handle, const void *buffer, int buffer_size, int timeout_ms, int multiplier, - ErrorCallbackT error_callback) -> int +inline auto writeImpl(int64_t handle, const std::uint8_t *buffer, int buffer_size, + const cpp_core::SerialTimeoutConfig *timeout_config, ErrorCallbackT error_callback) -> int { const auto callback = effectiveErrorCallback(error_callback); + const auto timeout_status = cpp_core::validateTimeoutConfig(timeout_config, callback); + if (timeout_status < 0) + { + return timeout_status; + } + const int timeout_ms = timeout_config->timeout_ms; + const int multiplier = timeout_config->multiplier; + const auto buffer_status = cpp_core::validateBuffer(buffer, buffer_size, callback); if (buffer_status < 0) { diff --git a/src/meta.cpp b/src/meta.cpp new file mode 100644 index 0000000..01ec4c6 --- /dev/null +++ b/src/meta.cpp @@ -0,0 +1,29 @@ +// Load the binding's generated version before cpp-core's identically guarded header. +#include "version.hpp" + +#include + +MODULE_API void meta(cpp_core::Meta *out) +{ + if (out != nullptr) + { + *out = cpp_core::Meta{ + .major = version::MAJOR, + .minor = version::MINOR, + .patch = version::PATCH, + .commits_since_tag = version::GIT_COMMIT_COUNT, + .is_dirty = version::GIT_IS_DIRTY ? 1 : 0, + .version_string = version::VERSION, + .prerelease = version::PRERELEASE, + .prerelease_type = version::PRERELEASE_TYPE, + .prerelease_number = version::PRERELEASE_NUMBER, + .git_tag = version::GIT_TAG, + .git_describe_hash = version::GIT_DESCRIBE_HASH, + .git_commit_hash_short = version::GIT_COMMIT_HASH_SHORT, + .git_commit_hash_full = version::GIT_COMMIT_HASH_FULL, + .git_commit_date = version::GIT_COMMIT_DATE, + .git_branch = version::GIT_BRANCH, + .git_dirty_suffix = version::GIT_DIRTY_SUFFIX, + }; + } +} diff --git a/src/serial_extended_api.test.cpp b/src/serial_extended_api.test.cpp index 6e2487d..36ff1d2 100644 --- a/src/serial_extended_api.test.cpp +++ b/src/serial_extended_api.test.cpp @@ -4,8 +4,6 @@ #include #include #include -#include -#include #include #include #include @@ -77,7 +75,10 @@ TEST_F(SerialExtendedApiTest, GlobalErrorCallbackActsAsFallback) serialSetErrorCallback(globalErrorCallback); std::array buffer{}; - EXPECT_EQ(serialRead(-1, buffer.data(), static_cast(buffer.size()), 10, 1, nullptr), kInvalidHandleError); + const cpp_core::SerialTimeoutConfig timeout_config0{10, 1}; + EXPECT_EQ(serialRead(-1, reinterpret_cast(buffer.data()), static_cast(buffer.size()), + &timeout_config0, nullptr), + kInvalidHandleError); EXPECT_EQ(g_last_error_code.load(std::memory_order_relaxed), kInvalidHandleError); } @@ -92,11 +93,13 @@ TEST_F(SerialExtendedApiTest, ReadWriteCallbacksAndTotalsTrackPipeHandles) serialSetWriteCallback(globalWriteCallback); const char *message = "hello"; - ASSERT_EQ(serialWrite(pipefd[1], message, 5, 100, 1, nullptr), 5); + const cpp_core::SerialTimeoutConfig timeout_config1{100, 1}; + ASSERT_EQ(serialWrite(pipefd[1], reinterpret_cast(message), 5, &timeout_config1, nullptr), 5); std::array buffer{}; ASSERT_EQ(serialInBytesWaiting(pipefd[0], nullptr), 5); - ASSERT_EQ(serialRead(pipefd[0], buffer.data(), 5, 100, 1, nullptr), 5); + const cpp_core::SerialTimeoutConfig timeout_config2{100, 1}; + ASSERT_EQ(serialRead(pipefd[0], reinterpret_cast(buffer.data()), 5, &timeout_config2, nullptr), 5); EXPECT_EQ(std::string(buffer.data(), 5), "hello"); EXPECT_EQ(g_last_write_callback.load(std::memory_order_relaxed), 5); @@ -120,7 +123,10 @@ TEST_F(SerialExtendedApiTest, ReadHelpersStopAtRequestedTerminator) ASSERT_EQ(write(pipefd[1], line, std::strlen(line)), static_cast(std::strlen(line))); std::array line_buffer{}; - ASSERT_EQ(serialReadLine(pipefd[0], line_buffer.data(), static_cast(line_buffer.size()), 100, 1, nullptr), + const cpp_core::SerialTimeoutConfig timeout_config3{100, 1}; + ASSERT_EQ(serialReadUntilSequence(pipefd[0], reinterpret_cast(line_buffer.data()), + static_cast(line_buffer.size()), &timeout_config3, + reinterpret_cast("\n"), 1, nullptr), 6); EXPECT_EQ(std::string(line_buffer.data(), 6), "alpha\n"); @@ -139,8 +145,10 @@ TEST_F(SerialExtendedApiTest, ReadHelpersStopAtRequestedTerminator) std::array until_buffer{}; unsigned char dash = '-'; - ASSERT_EQ(serialReadUntil(pipefd[0], until_buffer.data(), static_cast(until_buffer.size()), 100, 1, &dash, - nullptr), + const cpp_core::SerialTimeoutConfig timeout_config4{100, 1}; + ASSERT_EQ(serialReadUntilSequence(pipefd[0], reinterpret_cast(until_buffer.data()), + static_cast(until_buffer.size()), &timeout_config4, + reinterpret_cast(&dash), 1, nullptr), 7); EXPECT_EQ(std::string(until_buffer.data(), 7), "prefix-"); @@ -158,8 +166,11 @@ TEST_F(SerialExtendedApiTest, ReadHelpersStopAtRequestedTerminator) ASSERT_EQ(write(pipefd[1], payload, std::strlen(payload)), static_cast(std::strlen(payload))); std::array sequence_buffer{}; - ASSERT_EQ(serialReadUntilSequence(pipefd[0], sequence_buffer.data(), static_cast(sequence_buffer.size()), - 100, 1, const_cast("END"), nullptr), + const cpp_core::SerialTimeoutConfig timeout_config5{100, 1}; + ASSERT_EQ(serialReadUntilSequence(pipefd[0], reinterpret_cast(sequence_buffer.data()), + static_cast(sequence_buffer.size()), &timeout_config5, + reinterpret_cast(const_cast("END")), 3, + nullptr), 8); EXPECT_EQ(std::string(sequence_buffer.data(), 8), "more-END"); @@ -178,7 +189,9 @@ TEST_F(SerialExtendedApiTest, AbortReadInterruptsWaitingOperation) std::array buffer{}; int read_result = 0; std::thread reader([&] { - read_result = serialRead(pipefd[0], buffer.data(), static_cast(buffer.size()), 2000, 1, nullptr); + const cpp_core::SerialTimeoutConfig timeout_config6{2000, 1}; + read_result = serialRead(pipefd[0], reinterpret_cast(buffer.data()), + static_cast(buffer.size()), &timeout_config6, nullptr); }); std::this_thread::sleep_for(std::chrono::milliseconds(100)); diff --git a/src/serial_get_data_bits.cpp b/src/serial_get_data_bits.cpp index b3c8852..dce6194 100644 --- a/src/serial_get_data_bits.cpp +++ b/src/serial_get_data_bits.cpp @@ -10,34 +10,35 @@ extern "C" { - MODULE_API auto serialGetDataBits(int64_t handle, ErrorCallbackT error_callback) -> int + MODULE_API auto serialGetDataBits(int64_t handle, ErrorCallbackT error_callback) -> cpp_core::DataBits { cpp_bindings_linux::detail::HandleContext handle_context; const auto status = cpp_bindings_linux::detail::acquireHandleContext(handle, error_callback, &handle_context); if (status < 0) { - return status; + return static_cast(status); } termios2 serial_settings{}; if (ioctl(handle_context.file_descriptor, TCGETS2, &serial_settings) != 0) { - return cpp_bindings_linux::detail::failErrno( - error_callback, cpp_bindings_linux::detail::statusValue(cpp_core::StatusCode::Control::kGetStateError)); + return static_cast(cpp_bindings_linux::detail::failErrno( + error_callback, + cpp_bindings_linux::detail::statusValue(cpp_core::StatusCode::Control::kGetStateError))); } switch (serial_settings.c_cflag & CSIZE) { case CS5: - return 5; + return cpp_core::DataBits::kFive; case CS6: - return 6; + return cpp_core::DataBits::kSix; case CS7: - return 7; + return cpp_core::DataBits::kSeven; case CS8: default: - return 8; + return cpp_core::DataBits::kEight; } } diff --git a/src/serial_get_flow_control.cpp b/src/serial_get_flow_control.cpp index 2477734..4cee295 100644 --- a/src/serial_get_flow_control.cpp +++ b/src/serial_get_flow_control.cpp @@ -10,32 +10,33 @@ extern "C" { - MODULE_API auto serialGetFlowControl(int64_t handle, ErrorCallbackT error_callback) -> int + MODULE_API auto serialGetFlowControl(int64_t handle, ErrorCallbackT error_callback) -> cpp_core::FlowControl { cpp_bindings_linux::detail::HandleContext handle_context; const auto status = cpp_bindings_linux::detail::acquireHandleContext(handle, error_callback, &handle_context); if (status < 0) { - return status; + return static_cast(status); } termios2 serial_settings{}; if (ioctl(handle_context.file_descriptor, TCGETS2, &serial_settings) != 0) { - return cpp_bindings_linux::detail::failErrno( - error_callback, cpp_bindings_linux::detail::statusValue(cpp_core::StatusCode::Control::kGetStateError)); + return static_cast(cpp_bindings_linux::detail::failErrno( + error_callback, + cpp_bindings_linux::detail::statusValue(cpp_core::StatusCode::Control::kGetStateError))); } if ((serial_settings.c_cflag & CRTSCTS) != 0) { - return 1; + return cpp_core::FlowControl::kRtsCts; } if ((serial_settings.c_iflag & (IXON | IXOFF)) != 0) { - return 2; + return cpp_core::FlowControl::kXonXoff; } - return 0; + return cpp_core::FlowControl::kNone; } } // extern "C" diff --git a/src/serial_get_parity.cpp b/src/serial_get_parity.cpp index a79c4cb..19940d4 100644 --- a/src/serial_get_parity.cpp +++ b/src/serial_get_parity.cpp @@ -10,28 +10,29 @@ extern "C" { - MODULE_API auto serialGetParity(int64_t handle, ErrorCallbackT error_callback) -> int + MODULE_API auto serialGetParity(int64_t handle, ErrorCallbackT error_callback) -> cpp_core::Parity { cpp_bindings_linux::detail::HandleContext handle_context; const auto status = cpp_bindings_linux::detail::acquireHandleContext(handle, error_callback, &handle_context); if (status < 0) { - return status; + return static_cast(status); } termios2 serial_settings{}; if (ioctl(handle_context.file_descriptor, TCGETS2, &serial_settings) != 0) { - return cpp_bindings_linux::detail::failErrno( - error_callback, cpp_bindings_linux::detail::statusValue(cpp_core::StatusCode::Control::kGetStateError)); + return static_cast(cpp_bindings_linux::detail::failErrno( + error_callback, + cpp_bindings_linux::detail::statusValue(cpp_core::StatusCode::Control::kGetStateError))); } if ((serial_settings.c_cflag & PARENB) == 0) { - return 0; + return cpp_core::Parity::kNone; } - return (serial_settings.c_cflag & PARODD) != 0 ? 2 : 1; + return (serial_settings.c_cflag & PARODD) != 0 ? cpp_core::Parity::kOdd : cpp_core::Parity::kEven; } } // extern "C" diff --git a/src/serial_get_stop_bits.cpp b/src/serial_get_stop_bits.cpp index e23d85b..f052ef9 100644 --- a/src/serial_get_stop_bits.cpp +++ b/src/serial_get_stop_bits.cpp @@ -10,24 +10,25 @@ extern "C" { - MODULE_API auto serialGetStopBits(int64_t handle, ErrorCallbackT error_callback) -> int + MODULE_API auto serialGetStopBits(int64_t handle, ErrorCallbackT error_callback) -> cpp_core::StopBits { cpp_bindings_linux::detail::HandleContext handle_context; const auto status = cpp_bindings_linux::detail::acquireHandleContext(handle, error_callback, &handle_context); if (status < 0) { - return status; + return static_cast(status); } termios2 serial_settings{}; if (ioctl(handle_context.file_descriptor, TCGETS2, &serial_settings) != 0) { - return cpp_bindings_linux::detail::failErrno( - error_callback, cpp_bindings_linux::detail::statusValue(cpp_core::StatusCode::Control::kGetStateError)); + return static_cast(cpp_bindings_linux::detail::failErrno( + error_callback, + cpp_bindings_linux::detail::statusValue(cpp_core::StatusCode::Control::kGetStateError))); } - return (serial_settings.c_cflag & CSTOPB) != 0 ? 2 : 0; + return (serial_settings.c_cflag & CSTOPB) != 0 ? cpp_core::StopBits::kTwo : cpp_core::StopBits::kOne; } } // extern "C" diff --git a/src/serial_monitor_ports.cpp b/src/serial_monitor_ports.cpp deleted file mode 100644 index d5bce10..0000000 --- a/src/serial_monitor_ports.cpp +++ /dev/null @@ -1,146 +0,0 @@ -#include - -#include "detail/fail_errno.hpp" -#include "detail/is_serial_device_name.hpp" -#include "detail/status_value.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace -{ - -std::mutex g_monitor_mutex; -std::thread g_monitor_thread; -int g_inotify_fd = -1; -int g_stop_fd = -1; -std::atomic g_running{false}; - -void monitorLoop(void (*callback)(int event, const char *port)) -{ - (void)inotify_add_watch(g_inotify_fd, "/dev/", IN_CREATE | IN_DELETE); - - constexpr std::size_t kBufferLength = 4096; - alignas(inotify_event) char buffer[kBufferLength]; - - while (g_running.load(std::memory_order_relaxed)) - { - pollfd fds[2]{}; - fds[0].fd = g_inotify_fd; - fds[0].events = POLLIN; - fds[1].fd = g_stop_fd; - fds[1].events = POLLIN; - - const int ready = poll(fds, 2, 1000); - if (ready <= 0) - { - continue; - } - - if ((fds[1].revents & POLLIN) != 0) - { - break; - } - - if ((fds[0].revents & POLLIN) == 0) - { - continue; - } - - const ssize_t bytes_read = read(g_inotify_fd, buffer, kBufferLength); - if (bytes_read <= 0) - { - continue; - } - - const char *cursor = buffer; - while (cursor < buffer + bytes_read) - { - const auto *event = reinterpret_cast(cursor); - if (event->len > 0 && cpp_bindings_linux::detail::isSerialDeviceName(event->name)) - { - const std::string device_path = std::string("/dev/") + event->name; - callback((event->mask & IN_CREATE) != 0 ? 1 : 0, device_path.c_str()); - } - - cursor += sizeof(inotify_event) + event->len; - } - } -} - -void stopMonitor() -{ - if (!g_running.load(std::memory_order_relaxed)) - { - return; - } - - g_running.store(false, std::memory_order_relaxed); - if (g_stop_fd >= 0) - { - std::uint64_t value = 1; - (void)write(g_stop_fd, &value, sizeof(value)); - } - - if (g_monitor_thread.joinable()) - { - g_monitor_thread.join(); - } - - if (g_inotify_fd >= 0) - { - close(g_inotify_fd); - g_inotify_fd = -1; - } - if (g_stop_fd >= 0) - { - close(g_stop_fd); - g_stop_fd = -1; - } -} - -} // namespace - -extern "C" -{ - - MODULE_API auto serialMonitorPorts(void (*callback_fn)(int event, const char *port), - ErrorCallbackT error_callback) -> int - { - std::lock_guard lock(g_monitor_mutex); - stopMonitor(); - - if (callback_fn == nullptr) - { - return static_cast(cpp_core::StatusCode::kSuccess); - } - - g_inotify_fd = inotify_init1(IN_CLOEXEC | IN_NONBLOCK); - if (g_inotify_fd < 0) - { - return cpp_bindings_linux::detail::failErrno( - error_callback, cpp_bindings_linux::detail::statusValue(cpp_core::StatusCode::Monitor::kMonitorError)); - } - - g_stop_fd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); - if (g_stop_fd < 0) - { - close(g_inotify_fd); - g_inotify_fd = -1; - return cpp_bindings_linux::detail::failErrno( - error_callback, cpp_bindings_linux::detail::statusValue(cpp_core::StatusCode::Monitor::kMonitorError)); - } - - g_running.store(true, std::memory_order_relaxed); - g_monitor_thread = std::thread(monitorLoop, callback_fn); - return static_cast(cpp_core::StatusCode::kSuccess); - } - -} // extern "C" diff --git a/src/serial_open.cpp b/src/serial_open.cpp index d6591e3..91fcd47 100644 --- a/src/serial_open.cpp +++ b/src/serial_open.cpp @@ -3,18 +3,17 @@ #include "detail/apply_baudrate.hpp" #include "detail/apply_data_bits.hpp" +#include "detail/apply_flow_control.hpp" #include "detail/apply_parity.hpp" #include "detail/apply_stop_bits.hpp" #include "detail/effective_error_callback.hpp" #include "detail/fail_errno.hpp" #include "detail/handle_types.hpp" -#include "detail/parse_parity.hpp" -#include "detail/parse_stop_bits.hpp" #include "detail/read_termios2.hpp" #include "detail/register_opened_handle.hpp" #include "detail/status_value.hpp" -#include "detail/write_termios2.hpp" #include "detail/termios2.hpp" +#include "detail/write_termios2.hpp" #include #include @@ -24,36 +23,22 @@ extern "C" { - MODULE_API auto serialOpen(void *port, int baudrate, int data_bits, int parity, int stop_bits, - ErrorCallbackT error_callback) -> intptr_t + MODULE_API auto serialOpen(const char *port, const cpp_core::SerialConfig *config, ErrorCallbackT error_callback) + -> intptr_t { const auto callback = cpp_bindings_linux::detail::effectiveErrorCallback(error_callback); - const auto validation_rc = cpp_core::validateOpenParams(port, baudrate, data_bits, callback); + const auto validation_rc = cpp_core::validateOpenParams(port, config, callback); if (validation_rc < 0) { return validation_rc; } - const auto parity_value = cpp_bindings_linux::detail::parseParity( - parity, error_callback, cpp_bindings_linux::detail::statusValue(cpp_core::StatusCode::Control::kSetStateError)); - if (!parity_value.has_value()) - { - return static_cast(cpp_core::StatusCode::Control::kSetStateError); - } - - const auto stop_bits_value = cpp_bindings_linux::detail::parseStopBits( - stop_bits, error_callback, cpp_bindings_linux::detail::statusValue(cpp_core::StatusCode::Control::kSetStateError)); - if (!stop_bits_value.has_value()) - { - return static_cast(cpp_core::StatusCode::Control::kSetStateError); - } - - const char *port_path = static_cast(port); - cpp_bindings_linux::detail::UniqueFd handle(open(port_path, O_RDWR | O_NOCTTY | O_NONBLOCK)); + cpp_bindings_linux::detail::UniqueFd handle(open(port, O_RDWR | O_NOCTTY | O_NONBLOCK)); if (!handle.valid()) { return cpp_bindings_linux::detail::failErrno( - error_callback, cpp_bindings_linux::detail::statusValue(cpp_core::StatusCode::Connection::kNotFoundError)); + error_callback, + cpp_bindings_linux::detail::statusValue(cpp_core::StatusCode::Connection::kNotFoundError)); } termios2 serial_settings{}; @@ -62,14 +47,15 @@ extern "C" return static_cast(cpp_core::StatusCode::Control::kGetStateError); } - cpp_bindings_linux::detail::applyBaudrate(&serial_settings, baudrate); - cpp_bindings_linux::detail::applyDataBits(&serial_settings, data_bits); - cpp_bindings_linux::detail::applyParity(&serial_settings, *parity_value); - cpp_bindings_linux::detail::applyStopBits(&serial_settings, *stop_bits_value); + cpp_bindings_linux::detail::applyBaudrate(&serial_settings, config->baudrate); + cpp_bindings_linux::detail::applyDataBits(&serial_settings, cpp_core::toInt(config->data_bits)); + cpp_bindings_linux::detail::applyParity(&serial_settings, config->parity); + cpp_bindings_linux::detail::applyStopBits(&serial_settings, config->stop_bits); serial_settings.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); serial_settings.c_iflag &= ~(IXON | IXOFF | IXANY | INLCR | IGNCR | ICRNL); serial_settings.c_oflag &= ~OPOST; + cpp_bindings_linux::detail::applyFlowControl(&serial_settings, config->flow_mode); serial_settings.c_cc[VMIN] = 0; serial_settings.c_cc[VTIME] = 0; diff --git a/src/serial_open.test.cpp b/src/serial_open.test.cpp index 8dfe1a0..ccbb1d9 100644 --- a/src/serial_open.test.cpp +++ b/src/serial_open.test.cpp @@ -35,7 +35,9 @@ class SerialOpenTest : public ::testing::Test TEST_F(SerialOpenTest, NullPortParameter) { - intptr_t result = serialOpen(nullptr, 9600, 8, 0, 1, error_callback); + const cpp_core::SerialConfig config{9600, cpp_core::DataBits::kEight, cpp_core::Parity::kNone, + cpp_core::StopBits::kOne, cpp_core::FlowControl::kNone}; + intptr_t result = serialOpen(nullptr, &config, error_callback); EXPECT_EQ(result, kNotFoundError); EXPECT_NE(error_capture.last_message.find("nullptr"), std::string::npos); @@ -44,24 +46,30 @@ TEST_F(SerialOpenTest, NullPortParameter) TEST_F(SerialOpenTest, BaudrateTooLow) { const char *port = "/dev/null"; - intptr_t result = serialOpen(const_cast(static_cast(port)), 100, 8, 0, 1, error_callback); + const cpp_core::SerialConfig config{100, cpp_core::DataBits::kEight, cpp_core::Parity::kNone, + cpp_core::StopBits::kOne, cpp_core::FlowControl::kNone}; + intptr_t result = serialOpen(port, &config, error_callback); - EXPECT_EQ(result, kSetStateError); + EXPECT_EQ(result, static_cast(cpp_core::StatusCode::Configuration::kSetBaudrateError)); EXPECT_NE(error_capture.last_message.find("baudrate"), std::string::npos); } TEST_F(SerialOpenTest, BaudrateTooLowBoundary) { const char *port = "/dev/null"; - intptr_t result = serialOpen(const_cast(static_cast(port)), 299, 8, 0, 1, error_callback); + const cpp_core::SerialConfig config{299, cpp_core::DataBits::kEight, cpp_core::Parity::kNone, + cpp_core::StopBits::kOne, cpp_core::FlowControl::kNone}; + intptr_t result = serialOpen(port, &config, error_callback); - EXPECT_EQ(result, kSetStateError); + EXPECT_EQ(result, static_cast(cpp_core::StatusCode::Configuration::kSetBaudrateError)); } TEST_F(SerialOpenTest, BaudrateBoundaryValid) { const char *port = "/dev/null"; - intptr_t result = serialOpen(const_cast(static_cast(port)), 300, 8, 0, 1, error_callback); + const cpp_core::SerialConfig config{300, cpp_core::DataBits::kEight, cpp_core::Parity::kNone, + cpp_core::StopBits::kOne, cpp_core::FlowControl::kNone}; + intptr_t result = serialOpen(port, &config, error_callback); EXPECT_NE(result, kSetStateError); } @@ -69,24 +77,30 @@ TEST_F(SerialOpenTest, BaudrateBoundaryValid) TEST_F(SerialOpenTest, DataBitsTooLow) { const char *port = "/dev/null"; - intptr_t result = serialOpen(const_cast(static_cast(port)), 9600, 4, 0, 1, error_callback); + const cpp_core::SerialConfig config{9600, static_cast(4), cpp_core::Parity::kNone, + cpp_core::StopBits::kOne, cpp_core::FlowControl::kNone}; + intptr_t result = serialOpen(port, &config, error_callback); - EXPECT_EQ(result, kSetStateError); + EXPECT_EQ(result, static_cast(cpp_core::StatusCode::Configuration::kSetDataBitsError)); EXPECT_NE(error_capture.last_message.find("data bits"), std::string::npos); } TEST_F(SerialOpenTest, DataBitsTooHigh) { const char *port = "/dev/null"; - intptr_t result = serialOpen(const_cast(static_cast(port)), 9600, 9, 0, 1, error_callback); + const cpp_core::SerialConfig config{9600, static_cast(9), cpp_core::Parity::kNone, + cpp_core::StopBits::kOne, cpp_core::FlowControl::kNone}; + intptr_t result = serialOpen(port, &config, error_callback); - EXPECT_EQ(result, kSetStateError); + EXPECT_EQ(result, static_cast(cpp_core::StatusCode::Configuration::kSetDataBitsError)); } TEST_F(SerialOpenTest, ValidDataBits5) { const char *port = "/dev/null"; - intptr_t result = serialOpen(const_cast(static_cast(port)), 9600, 5, 0, 1, error_callback); + const cpp_core::SerialConfig config{9600, cpp_core::DataBits::kFive, cpp_core::Parity::kNone, + cpp_core::StopBits::kOne, cpp_core::FlowControl::kNone}; + intptr_t result = serialOpen(port, &config, error_callback); EXPECT_NE(result, kSetStateError); } @@ -94,7 +108,9 @@ TEST_F(SerialOpenTest, ValidDataBits5) TEST_F(SerialOpenTest, ValidDataBits6) { const char *port = "/dev/null"; - intptr_t result = serialOpen(const_cast(static_cast(port)), 9600, 6, 0, 1, error_callback); + const cpp_core::SerialConfig config{9600, cpp_core::DataBits::kSix, cpp_core::Parity::kNone, + cpp_core::StopBits::kOne, cpp_core::FlowControl::kNone}; + intptr_t result = serialOpen(port, &config, error_callback); EXPECT_NE(result, kSetStateError); } @@ -102,7 +118,9 @@ TEST_F(SerialOpenTest, ValidDataBits6) TEST_F(SerialOpenTest, ValidDataBits7) { const char *port = "/dev/null"; - intptr_t result = serialOpen(const_cast(static_cast(port)), 9600, 7, 0, 1, error_callback); + const cpp_core::SerialConfig config{9600, cpp_core::DataBits::kSeven, cpp_core::Parity::kNone, + cpp_core::StopBits::kOne, cpp_core::FlowControl::kNone}; + intptr_t result = serialOpen(port, &config, error_callback); EXPECT_NE(result, kSetStateError); } @@ -110,7 +128,9 @@ TEST_F(SerialOpenTest, ValidDataBits7) TEST_F(SerialOpenTest, ValidDataBits8) { const char *port = "/dev/null"; - intptr_t result = serialOpen(const_cast(static_cast(port)), 9600, 8, 0, 1, error_callback); + const cpp_core::SerialConfig config{9600, cpp_core::DataBits::kEight, cpp_core::Parity::kNone, + cpp_core::StopBits::kOne, cpp_core::FlowControl::kNone}; + intptr_t result = serialOpen(port, &config, error_callback); EXPECT_NE(result, kSetStateError); } @@ -118,7 +138,9 @@ TEST_F(SerialOpenTest, ValidDataBits8) TEST_F(SerialOpenTest, InvalidParity) { const char *port = "/dev/null"; - intptr_t result = serialOpen(const_cast(static_cast(port)), 9600, 8, 5, 1, error_callback); + const cpp_core::SerialConfig config{9600, cpp_core::DataBits::kEight, static_cast(5), + cpp_core::StopBits::kOne, cpp_core::FlowControl::kNone}; + intptr_t result = serialOpen(port, &config, error_callback); EXPECT_LT(result, 0); } @@ -126,7 +148,9 @@ TEST_F(SerialOpenTest, InvalidParity) TEST_F(SerialOpenTest, ValidParityNone) { const char *port = "/dev/null"; - intptr_t result = serialOpen(const_cast(static_cast(port)), 9600, 8, 0, 1, error_callback); + const cpp_core::SerialConfig config{9600, cpp_core::DataBits::kEight, cpp_core::Parity::kNone, + cpp_core::StopBits::kOne, cpp_core::FlowControl::kNone}; + intptr_t result = serialOpen(port, &config, error_callback); EXPECT_NE(result, kSetStateError); } @@ -134,7 +158,9 @@ TEST_F(SerialOpenTest, ValidParityNone) TEST_F(SerialOpenTest, ValidParityEven) { const char *port = "/dev/null"; - intptr_t result = serialOpen(const_cast(static_cast(port)), 9600, 8, 1, 1, error_callback); + const cpp_core::SerialConfig config{9600, cpp_core::DataBits::kEight, cpp_core::Parity::kEven, + cpp_core::StopBits::kOne, cpp_core::FlowControl::kNone}; + intptr_t result = serialOpen(port, &config, error_callback); EXPECT_NE(result, kSetStateError); } @@ -142,7 +168,9 @@ TEST_F(SerialOpenTest, ValidParityEven) TEST_F(SerialOpenTest, ValidParityOdd) { const char *port = "/dev/null"; - intptr_t result = serialOpen(const_cast(static_cast(port)), 9600, 8, 2, 1, error_callback); + const cpp_core::SerialConfig config{9600, cpp_core::DataBits::kEight, cpp_core::Parity::kOdd, + cpp_core::StopBits::kOne, cpp_core::FlowControl::kNone}; + intptr_t result = serialOpen(port, &config, error_callback); EXPECT_NE(result, kSetStateError); } @@ -150,7 +178,9 @@ TEST_F(SerialOpenTest, ValidParityOdd) TEST_F(SerialOpenTest, InvalidStopBits) { const char *port = "/dev/null"; - intptr_t result = serialOpen(const_cast(static_cast(port)), 9600, 8, 0, 3, error_callback); + const cpp_core::SerialConfig config{9600, cpp_core::DataBits::kEight, cpp_core::Parity::kNone, + static_cast(3), cpp_core::FlowControl::kNone}; + intptr_t result = serialOpen(port, &config, error_callback); EXPECT_LT(result, 0); } @@ -158,23 +188,29 @@ TEST_F(SerialOpenTest, InvalidStopBits) TEST_F(SerialOpenTest, ValidStopBits0) { const char *port = "/dev/null"; - intptr_t result = serialOpen(const_cast(static_cast(port)), 9600, 8, 0, 0, error_callback); + const cpp_core::SerialConfig config{9600, cpp_core::DataBits::kEight, cpp_core::Parity::kNone, + cpp_core::StopBits::kOne, cpp_core::FlowControl::kNone}; + intptr_t result = serialOpen(port, &config, error_callback); EXPECT_NE(result, kSetStateError); } -TEST_F(SerialOpenTest, ValidStopBits1) +TEST_F(SerialOpenTest, RejectsLegacyStopBits1) { const char *port = "/dev/null"; - intptr_t result = serialOpen(const_cast(static_cast(port)), 9600, 8, 0, 1, error_callback); + const cpp_core::SerialConfig config{9600, cpp_core::DataBits::kEight, cpp_core::Parity::kNone, + static_cast(1), cpp_core::FlowControl::kNone}; + intptr_t result = serialOpen(port, &config, error_callback); - EXPECT_NE(result, kSetStateError); + EXPECT_EQ(result, static_cast(cpp_core::StatusCode::Configuration::kSetStopBitsError)); } TEST_F(SerialOpenTest, ValidStopBits2) { const char *port = "/dev/null"; - intptr_t result = serialOpen(const_cast(static_cast(port)), 9600, 8, 0, 2, error_callback); + const cpp_core::SerialConfig config{9600, cpp_core::DataBits::kEight, cpp_core::Parity::kNone, + cpp_core::StopBits::kTwo, cpp_core::FlowControl::kNone}; + intptr_t result = serialOpen(port, &config, error_callback); EXPECT_NE(result, kSetStateError); } @@ -182,7 +218,9 @@ TEST_F(SerialOpenTest, ValidStopBits2) TEST_F(SerialOpenTest, NonExistentPort) { const char *port = "/dev/ttyNONEXISTENT99999"; - intptr_t result = serialOpen(const_cast(static_cast(port)), 9600, 8, 0, 1, error_callback); + const cpp_core::SerialConfig config{9600, cpp_core::DataBits::kEight, cpp_core::Parity::kNone, + cpp_core::StopBits::kOne, cpp_core::FlowControl::kNone}; + intptr_t result = serialOpen(port, &config, error_callback); EXPECT_EQ(result, kNotFoundError); } @@ -194,15 +232,18 @@ TEST_F(SerialOpenTest, VariousBaudrates) for (int baudrate : baudrates) { - intptr_t result = - serialOpen(const_cast(static_cast(port)), baudrate, 8, 0, 1, error_callback); + const cpp_core::SerialConfig config{baudrate, cpp_core::DataBits::kEight, cpp_core::Parity::kNone, + cpp_core::StopBits::kOne, cpp_core::FlowControl::kNone}; + intptr_t result = serialOpen(port, &config, error_callback); EXPECT_NE(result, kSetStateError) << "Baudrate " << baudrate << " should be valid"; } } TEST_F(SerialOpenTest, NoErrorCallbackNullPort) { - intptr_t result = serialOpen(nullptr, 9600, 8, 0, 1, nullptr); + const cpp_core::SerialConfig config{9600, cpp_core::DataBits::kEight, cpp_core::Parity::kNone, + cpp_core::StopBits::kOne, cpp_core::FlowControl::kNone}; + intptr_t result = serialOpen(nullptr, &config, nullptr); EXPECT_EQ(result, kNotFoundError); } diff --git a/src/serial_read.cpp b/src/serial_read.cpp index 1abea59..8be5621 100644 --- a/src/serial_read.cpp +++ b/src/serial_read.cpp @@ -5,10 +5,11 @@ extern "C" { - MODULE_API auto serialRead(int64_t handle, void *buffer, int buffer_size, int timeout_ms, int multiplier, - ErrorCallbackT error_callback) -> int + MODULE_API auto serialRead(int64_t handle, std::uint8_t *buffer, int buffer_size, + const cpp_core::SerialTimeoutConfig *timeout_config, ErrorCallbackT error_callback) + -> int { - return cpp_bindings_linux::detail::readImpl(handle, buffer, buffer_size, timeout_ms, multiplier, nullptr, 0, + return cpp_bindings_linux::detail::readImpl(handle, buffer, buffer_size, timeout_config, nullptr, 0, error_callback); } diff --git a/src/serial_read.test.cpp b/src/serial_read.test.cpp index bb2437c..657551a 100644 --- a/src/serial_read.test.cpp +++ b/src/serial_read.test.cpp @@ -36,7 +36,8 @@ class SerialReadTest : public ::testing::Test TEST_F(SerialReadTest, ReadNullBuffer) { - int result = serialRead(1, nullptr, 10, 100, 0, error_callback); + const cpp_core::SerialTimeoutConfig timeout_config{100, 0}; + int result = serialRead(1, nullptr, 10, &timeout_config, error_callback); EXPECT_EQ(result, kBufferError); EXPECT_NE(error_capture.last_message.find("buffer"), std::string::npos); @@ -45,7 +46,8 @@ TEST_F(SerialReadTest, ReadNullBuffer) TEST_F(SerialReadTest, ReadZeroBufferSize) { std::array buffer{}; - int result = serialRead(1, buffer.data(), 0, 100, 0, error_callback); + const cpp_core::SerialTimeoutConfig timeout_config{100, 0}; + int result = serialRead(1, reinterpret_cast(buffer.data()), 0, &timeout_config, error_callback); EXPECT_EQ(result, kBufferError); } @@ -53,7 +55,8 @@ TEST_F(SerialReadTest, ReadZeroBufferSize) TEST_F(SerialReadTest, ReadNegativeBufferSize) { std::array buffer{}; - int result = serialRead(1, buffer.data(), -1, 100, 0, error_callback); + const cpp_core::SerialTimeoutConfig timeout_config{100, 0}; + int result = serialRead(1, reinterpret_cast(buffer.data()), -1, &timeout_config, error_callback); EXPECT_EQ(result, kBufferError); } @@ -61,7 +64,9 @@ TEST_F(SerialReadTest, ReadNegativeBufferSize) TEST_F(SerialReadTest, ReadInvalidHandleZero) { std::array buffer{}; - int result = serialRead(0, buffer.data(), static_cast(buffer.size()), 100, 0, error_callback); + const cpp_core::SerialTimeoutConfig timeout_config{100, 0}; + int result = serialRead(0, reinterpret_cast(buffer.data()), static_cast(buffer.size()), + &timeout_config, error_callback); EXPECT_EQ(result, kInvalidHandleError); } @@ -69,7 +74,9 @@ TEST_F(SerialReadTest, ReadInvalidHandleZero) TEST_F(SerialReadTest, ReadInvalidHandleNegative) { std::array buffer{}; - int result = serialRead(-1, buffer.data(), static_cast(buffer.size()), 100, 0, error_callback); + const cpp_core::SerialTimeoutConfig timeout_config{100, 0}; + int result = serialRead(-1, reinterpret_cast(buffer.data()), static_cast(buffer.size()), + &timeout_config, error_callback); EXPECT_EQ(result, kInvalidHandleError); } @@ -78,7 +85,9 @@ TEST_F(SerialReadTest, ReadInvalidHandleTooLarge) { std::array buffer{}; auto too_large = static_cast(std::numeric_limits::max()) + 1; - int result = serialRead(too_large, buffer.data(), static_cast(buffer.size()), 100, 0, error_callback); + const cpp_core::SerialTimeoutConfig timeout_config{100, 0}; + int result = serialRead(too_large, reinterpret_cast(buffer.data()), static_cast(buffer.size()), + &timeout_config, error_callback); EXPECT_EQ(result, kInvalidHandleError); } @@ -89,7 +98,9 @@ TEST_F(SerialReadTest, ReadFromDevNull) ASSERT_GE(fd, 0); std::array buffer{}; - int result = serialRead(fd, buffer.data(), static_cast(buffer.size()), 0, 0, error_callback); + const cpp_core::SerialTimeoutConfig timeout_config{0, 0}; + int result = serialRead(fd, reinterpret_cast(buffer.data()), static_cast(buffer.size()), + &timeout_config, error_callback); EXPECT_EQ(result, 0); close(fd); @@ -101,7 +112,9 @@ TEST_F(SerialReadTest, ReadWithLargeBufferSize) ASSERT_GE(fd, 0); std::array buffer{}; - int result = serialRead(fd, buffer.data(), static_cast(buffer.size()), 0, 0, error_callback); + const cpp_core::SerialTimeoutConfig timeout_config{0, 0}; + int result = serialRead(fd, reinterpret_cast(buffer.data()), static_cast(buffer.size()), + &timeout_config, error_callback); EXPECT_EQ(result, 0); close(fd); @@ -110,7 +123,9 @@ TEST_F(SerialReadTest, ReadWithLargeBufferSize) TEST_F(SerialReadTest, ReadNoErrorCallback) { std::array buffer{}; - int result = serialRead(0, buffer.data(), static_cast(buffer.size()), 100, 0, nullptr); + const cpp_core::SerialTimeoutConfig timeout_config{100, 0}; + int result = serialRead(0, reinterpret_cast(buffer.data()), static_cast(buffer.size()), + &timeout_config, nullptr); EXPECT_EQ(result, kInvalidHandleError); } @@ -124,7 +139,9 @@ TEST_F(SerialReadTest, ReadWithVariousTimeouts) for (int timeout : {0, 1, 10, 100, 1000}) { - int result = serialRead(fd, buffer.data(), static_cast(buffer.size()), timeout, 0, error_callback); + const cpp_core::SerialTimeoutConfig timeout_config{timeout, 0}; + int result = serialRead(fd, reinterpret_cast(buffer.data()), static_cast(buffer.size()), + &timeout_config, error_callback); EXPECT_EQ(result, 0) << "Timeout " << timeout << " should return 0 for /dev/null"; } diff --git a/src/serial_read_line.cpp b/src/serial_read_line.cpp deleted file mode 100644 index 1ab1473..0000000 --- a/src/serial_read_line.cpp +++ /dev/null @@ -1,16 +0,0 @@ -#include - -#include "detail/read_impl.hpp" - -extern "C" -{ - - MODULE_API auto serialReadLine(int64_t handle, void *buffer, int buffer_size, int timeout_ms, int multiplier, - ErrorCallbackT error_callback) -> int - { - static constexpr unsigned char kNewline = '\n'; - return cpp_bindings_linux::detail::readImpl(handle, buffer, buffer_size, timeout_ms, multiplier, &kNewline, 1, - error_callback); - } - -} // extern "C" diff --git a/src/serial_read_until.cpp b/src/serial_read_until.cpp deleted file mode 100644 index d805677..0000000 --- a/src/serial_read_until.cpp +++ /dev/null @@ -1,24 +0,0 @@ -#include - -#include "detail/fail_msg.hpp" -#include "detail/read_impl.hpp" -#include "detail/status_value.hpp" - -extern "C" -{ - - MODULE_API auto serialReadUntil(int64_t handle, void *buffer, int buffer_size, int timeout_ms, int multiplier, - void *until_char, ErrorCallbackT error_callback) -> int - { - if (until_char == nullptr) - { - return cpp_bindings_linux::detail::failMsg( - error_callback, cpp_bindings_linux::detail::statusValue(cpp_core::StatusCode::Io::kBufferError), - "Terminator pointer must not be null"); - } - - return cpp_bindings_linux::detail::readImpl(handle, buffer, buffer_size, timeout_ms, multiplier, - static_cast(until_char), 1, error_callback); - } - -} // extern "C" diff --git a/src/serial_read_until_sequence.cpp b/src/serial_read_until_sequence.cpp index 40ea730..bd5cbd0 100644 --- a/src/serial_read_until_sequence.cpp +++ b/src/serial_read_until_sequence.cpp @@ -4,31 +4,22 @@ #include "detail/read_impl.hpp" #include "detail/status_value.hpp" -#include - extern "C" { - MODULE_API auto serialReadUntilSequence(int64_t handle, void *buffer, int buffer_size, int timeout_ms, - int multiplier, void *sequence, ErrorCallbackT error_callback) -> int + MODULE_API auto serialReadUntilSequence(int64_t handle, std::uint8_t *buffer, int buffer_size, + const cpp_core::SerialTimeoutConfig *timeout_config, + const std::uint8_t *sequence, int sequence_size, + ErrorCallbackT error_callback) -> int { - if (sequence == nullptr) - { - return cpp_bindings_linux::detail::failMsg( - error_callback, cpp_bindings_linux::detail::statusValue(cpp_core::StatusCode::Io::kBufferError), - "Sequence pointer must not be null"); - } - - const auto *sequence_bytes = static_cast(sequence); - const int sequence_size = static_cast(std::strlen(reinterpret_cast(sequence_bytes))); - if (sequence_size <= 0) + if (sequence == nullptr || sequence_size <= 0) { return cpp_bindings_linux::detail::failMsg( error_callback, cpp_bindings_linux::detail::statusValue(cpp_core::StatusCode::Io::kBufferError), - "Sequence must not be empty"); + "Sequence must not be null and sequence_size must be positive"); } - return cpp_bindings_linux::detail::readImpl(handle, buffer, buffer_size, timeout_ms, multiplier, sequence_bytes, + return cpp_bindings_linux::detail::readImpl(handle, buffer, buffer_size, timeout_config, sequence, sequence_size, error_callback); } diff --git a/src/serial_set_data_bits.cpp b/src/serial_set_data_bits.cpp index 40b600f..652fd6e 100644 --- a/src/serial_set_data_bits.cpp +++ b/src/serial_set_data_bits.cpp @@ -5,9 +5,9 @@ #include "detail/fail_validation.hpp" #include "detail/read_termios2.hpp" #include "detail/status_value.hpp" +#include "detail/termios2.hpp" #include "detail/validate_data_bits_value.hpp" #include "detail/write_termios2.hpp" -#include "detail/termios2.hpp" #include #include @@ -15,7 +15,8 @@ extern "C" { - MODULE_API auto serialSetDataBits(int64_t handle, int data_bits, ErrorCallbackT error_callback) -> int + MODULE_API auto serialSetDataBits(int64_t handle, cpp_core::DataBits data_bits, ErrorCallbackT error_callback) + -> int { cpp_bindings_linux::detail::HandleContext handle_context; const auto status = @@ -25,20 +26,21 @@ extern "C" return status; } - if (!cpp_bindings_linux::detail::validateDataBitsValue(data_bits)) + if (!cpp_bindings_linux::detail::validateDataBitsValue(cpp_core::toInt(data_bits))) { return cpp_bindings_linux::detail::failValidation( - error_callback, cpp_bindings_linux::detail::statusValue(cpp_core::StatusCode::Configuration::kSetDataBitsError)); + error_callback, + cpp_bindings_linux::detail::statusValue(cpp_core::StatusCode::Configuration::kSetDataBitsError)); } termios2 serial_settings{}; - if (cpp_bindings_linux::detail::readTermios2( - handle_context.file_descriptor, &serial_settings, error_callback) < 0) + if (cpp_bindings_linux::detail::readTermios2(handle_context.file_descriptor, &serial_settings, + error_callback) < 0) { return static_cast(cpp_core::StatusCode::Control::kGetStateError); } - cpp_bindings_linux::detail::applyDataBits(&serial_settings, data_bits); + cpp_bindings_linux::detail::applyDataBits(&serial_settings, cpp_core::toInt(data_bits)); if (cpp_bindings_linux::detail::writeTermios2( handle_context.file_descriptor, &serial_settings, error_callback, diff --git a/src/serial_set_event_callback.cpp b/src/serial_set_event_callback.cpp new file mode 100644 index 0000000..cae45d2 --- /dev/null +++ b/src/serial_set_event_callback.cpp @@ -0,0 +1,159 @@ +#include + +#include "detail/fail_errno.hpp" +#include "detail/handle_types.hpp" +#include "detail/is_serial_device_name.hpp" +#include "detail/status_value.hpp" + +#include +#include +#include +#include +#include +#include +#include + +namespace +{ +using EventCallback = void (*)(cpp_core::PortEvent, const char *); +using cpp_bindings_linux::detail::UniqueFd; + +struct MonitorState +{ + UniqueFd inotify_fd; + UniqueFd stop_fd; + std::atomic stopped{false}; + + void stop() + { + stopped.store(true, std::memory_order_release); + const std::uint64_t value = 1; + while (write(stop_fd.get(), &value, sizeof(value)) < 0 && errno == EINTR) + { + } + } +}; + +void monitorLoop(const std::shared_ptr &state, EventCallback callback) +{ + alignas(inotify_event) char buffer[4096]; + while (!state->stopped.load(std::memory_order_acquire)) + { + pollfd fds[2]{{state->inotify_fd.get(), POLLIN, 0}, {state->stop_fd.get(), POLLIN, 0}}; + const int ready = poll(fds, 2, -1); + if (ready < 0 && errno == EINTR) + { + continue; + } + if (ready < 0 || fds[1].revents != 0 || (fds[0].revents & (POLLERR | POLLHUP | POLLNVAL)) != 0) + { + return; + } + if ((fds[0].revents & POLLIN) == 0) + { + continue; + } + const ssize_t bytes_read = read(state->inotify_fd.get(), buffer, sizeof(buffer)); + if (bytes_read <= 0) + { + continue; + } + const char *cursor = buffer; + while (cursor < buffer + bytes_read && !state->stopped.load(std::memory_order_acquire)) + { + const auto *event = reinterpret_cast(cursor); + if (event->len > 0 && cpp_bindings_linux::detail::isSerialDeviceName(event->name)) + { + const std::string path = std::string("/dev/") + event->name; + callback((event->mask & (IN_CREATE | IN_MOVED_TO)) != 0 ? cpp_core::PortEvent::kAttached + : cpp_core::PortEvent::kDetached, + path.c_str()); + } + cursor += sizeof(inotify_event) + event->len; + } + } +} + +struct Monitor +{ + std::mutex mutex; + std::shared_ptr state; + std::thread thread; + + ~Monitor() + { + if (state) + { + state->stop(); + } + if (thread.joinable()) + { + thread.join(); + } + } +}; + +Monitor g_monitor; +} // namespace + +MODULE_API auto serialSetEventCallback(EventCallback callback_fn, ErrorCallbackT error_callback) -> int +{ + // Build the replacement before changing the active monitor. A failed registration + // leaves the current callback intact, and all descriptors are owned by its state. + std::shared_ptr next; + if (callback_fn != nullptr) + { + next = std::make_shared(); + next->inotify_fd = UniqueFd(inotify_init1(IN_CLOEXEC | IN_NONBLOCK)); + if (!next->inotify_fd.valid() || + inotify_add_watch(next->inotify_fd.get(), "/dev/", IN_CREATE | IN_DELETE | IN_MOVED_TO | IN_MOVED_FROM) < 0) + { + return cpp_bindings_linux::detail::failErrno( + error_callback, cpp_bindings_linux::detail::statusValue(cpp_core::StatusCode::Monitor::kMonitorError)); + } + next->stop_fd = UniqueFd(eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK)); + if (!next->stop_fd.valid()) + { + return cpp_bindings_linux::detail::failErrno( + error_callback, cpp_bindings_linux::detail::statusValue(cpp_core::StatusCode::Monitor::kMonitorError)); + } + } + + std::thread previous; + try + { + std::lock_guard lock(g_monitor.mutex); + std::thread replacement; + if (next) + { + replacement = std::thread(monitorLoop, next, callback_fn); + } + if (g_monitor.state) + { + g_monitor.state->stop(); + } + previous = std::move(g_monitor.thread); + g_monitor.state = std::move(next); + g_monitor.thread = std::move(replacement); + } + catch (const std::system_error &error) + { + return cpp_bindings_linux::detail::failMsg( + error_callback, cpp_bindings_linux::detail::statusValue(cpp_core::StatusCode::Monitor::kMonitorError), + error.what()); + } + + // Join outside the lock so callbacks can register or clear themselves. + if (previous.joinable()) + { + if (previous.get_id() == std::this_thread::get_id()) + { + previous.detach(); + } + else + { + previous.join(); + } + } + return static_cast(cpp_core::StatusCode::kSuccess); +} diff --git a/src/serial_set_flow_control.cpp b/src/serial_set_flow_control.cpp index 3c8c420..04ed51c 100644 --- a/src/serial_set_flow_control.cpp +++ b/src/serial_set_flow_control.cpp @@ -5,8 +5,8 @@ #include "detail/parse_flow_control.hpp" #include "detail/read_termios2.hpp" #include "detail/status_value.hpp" -#include "detail/write_termios2.hpp" #include "detail/termios2.hpp" +#include "detail/write_termios2.hpp" #include #include @@ -14,7 +14,8 @@ extern "C" { - MODULE_API auto serialSetFlowControl(int64_t handle, int mode, ErrorCallbackT error_callback) -> int + MODULE_API auto serialSetFlowControl(int64_t handle, cpp_core::FlowControl mode, ErrorCallbackT error_callback) + -> int { cpp_bindings_linux::detail::HandleContext handle_context; const auto status = @@ -25,7 +26,7 @@ extern "C" } const auto flow_control = cpp_bindings_linux::detail::parseFlowControl( - mode, error_callback, + cpp_core::toInt(mode), error_callback, cpp_bindings_linux::detail::statusValue(cpp_core::StatusCode::Configuration::kSetFlowControlError)); if (!flow_control.has_value()) { @@ -33,8 +34,8 @@ extern "C" } termios2 serial_settings{}; - if (cpp_bindings_linux::detail::readTermios2( - handle_context.file_descriptor, &serial_settings, error_callback) < 0) + if (cpp_bindings_linux::detail::readTermios2(handle_context.file_descriptor, &serial_settings, + error_callback) < 0) { return static_cast(cpp_core::StatusCode::Control::kGetStateError); } diff --git a/src/serial_set_parity.cpp b/src/serial_set_parity.cpp index 0591761..524d5bd 100644 --- a/src/serial_set_parity.cpp +++ b/src/serial_set_parity.cpp @@ -5,8 +5,8 @@ #include "detail/parse_parity.hpp" #include "detail/read_termios2.hpp" #include "detail/status_value.hpp" -#include "detail/write_termios2.hpp" #include "detail/termios2.hpp" +#include "detail/write_termios2.hpp" #include #include @@ -14,7 +14,7 @@ extern "C" { - MODULE_API auto serialSetParity(int64_t handle, int parity, ErrorCallbackT error_callback) -> int + MODULE_API auto serialSetParity(int64_t handle, cpp_core::Parity parity, ErrorCallbackT error_callback) -> int { cpp_bindings_linux::detail::HandleContext handle_context; const auto status = @@ -25,7 +25,7 @@ extern "C" } const auto parity_value = cpp_bindings_linux::detail::parseParity( - parity, error_callback, + cpp_core::toInt(parity), error_callback, cpp_bindings_linux::detail::statusValue(cpp_core::StatusCode::Configuration::kSetParityError)); if (!parity_value.has_value()) { @@ -33,8 +33,8 @@ extern "C" } termios2 serial_settings{}; - if (cpp_bindings_linux::detail::readTermios2( - handle_context.file_descriptor, &serial_settings, error_callback) < 0) + if (cpp_bindings_linux::detail::readTermios2(handle_context.file_descriptor, &serial_settings, + error_callback) < 0) { return static_cast(cpp_core::StatusCode::Control::kGetStateError); } diff --git a/src/serial_set_stop_bits.cpp b/src/serial_set_stop_bits.cpp index 3eae4f2..21e9c59 100644 --- a/src/serial_set_stop_bits.cpp +++ b/src/serial_set_stop_bits.cpp @@ -5,8 +5,8 @@ #include "detail/parse_stop_bits.hpp" #include "detail/read_termios2.hpp" #include "detail/status_value.hpp" -#include "detail/write_termios2.hpp" #include "detail/termios2.hpp" +#include "detail/write_termios2.hpp" #include #include @@ -14,7 +14,8 @@ extern "C" { - MODULE_API auto serialSetStopBits(int64_t handle, int stop_bits, ErrorCallbackT error_callback) -> int + MODULE_API auto serialSetStopBits(int64_t handle, cpp_core::StopBits stop_bits, ErrorCallbackT error_callback) + -> int { cpp_bindings_linux::detail::HandleContext handle_context; const auto status = @@ -25,7 +26,7 @@ extern "C" } const auto stop_bits_value = cpp_bindings_linux::detail::parseStopBits( - stop_bits, error_callback, + cpp_core::toInt(stop_bits), error_callback, cpp_bindings_linux::detail::statusValue(cpp_core::StatusCode::Configuration::kSetStopBitsError)); if (!stop_bits_value.has_value()) { @@ -33,8 +34,8 @@ extern "C" } termios2 serial_settings{}; - if (cpp_bindings_linux::detail::readTermios2( - handle_context.file_descriptor, &serial_settings, error_callback) < 0) + if (cpp_bindings_linux::detail::readTermios2(handle_context.file_descriptor, &serial_settings, + error_callback) < 0) { return static_cast(cpp_core::StatusCode::Control::kGetStateError); } diff --git a/src/serial_v3_api.test.cpp b/src/serial_v3_api.test.cpp new file mode 100644 index 0000000..5c8e42d --- /dev/null +++ b/src/serial_v3_api.test.cpp @@ -0,0 +1,198 @@ +#include +#include + +#include "detail/handle_types.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace +{ +using cpp_bindings_linux::detail::UniqueFd; +constexpr auto kConfig = cpp_core::SerialConfig::make<9600, cpp_core::DataBits::kEight>(); +constexpr auto kTimeout = cpp_core::SerialTimeoutConfig::make<10, 1>(); +constexpr int kTimeoutError = static_cast(cpp_core::StatusCode::Configuration::kSetTimeoutError); +constexpr int kBufferError = static_cast(cpp_core::StatusCode::Io::kBufferError); +int g_error = 0; +void captureError(int code, const char *) +{ + g_error = code; +} +void portEvent(cpp_core::PortEvent, const char *) +{ +} +} // namespace + +TEST(SerialV3ApiTest, RejectsNullAndInvalidOpenConfigurations) +{ + EXPECT_EQ(serialOpen("/dev/null", nullptr), static_cast(cpp_core::StatusCode::Control::kSetStateError)); + const auto check = [](cpp_core::SerialConfig config, int expected) { + g_error = 0; + EXPECT_EQ(serialOpen("/dev/null", &config, captureError), expected); + EXPECT_EQ(g_error, expected); + }; + auto config = kConfig; + config.parity = static_cast(99); + check(config, static_cast(cpp_core::StatusCode::Configuration::kSetParityError)); + config = kConfig; + config.stop_bits = static_cast(1); + check(config, static_cast(cpp_core::StatusCode::Configuration::kSetStopBitsError)); + config = kConfig; + config.flow_mode = static_cast(99); + check(config, static_cast(cpp_core::StatusCode::Configuration::kSetFlowControlError)); +} + +TEST(SerialV3ApiTest, RejectsNullNegativeAndOverflowingTimeouts) +{ + UniqueFd fd(open("/dev/null", O_RDWR | O_NONBLOCK)); + ASSERT_TRUE(fd.valid()); + std::array buffer{}; + const auto check = [&](const cpp_core::SerialTimeoutConfig *timeout) { + g_error = 0; + EXPECT_EQ(serialRead(fd.get(), buffer.data(), 4, timeout, captureError), kTimeoutError); + EXPECT_EQ(g_error, kTimeoutError); + g_error = 0; + EXPECT_EQ(serialWrite(fd.get(), buffer.data(), 4, timeout, captureError), kTimeoutError); + EXPECT_EQ(g_error, kTimeoutError); + EXPECT_EQ(serialReadUntilSequence(fd.get(), buffer.data(), 4, timeout, buffer.data(), 1), kTimeoutError); + }; + check(nullptr); + for (const auto timeout : {cpp_core::SerialTimeoutConfig{-1, 1}, {1, -1}, {std::numeric_limits::max(), 2}}) + { + check(&timeout); + } + const cpp_core::SerialTimeoutConfig zero{0, std::numeric_limits::max()}; + EXPECT_EQ(serialRead(fd.get(), buffer.data(), 4, &zero), 0); + EXPECT_EQ(serialWrite(fd.get(), buffer.data(), 4, &zero), 4); +} + +TEST(SerialV3ApiTest, ReadsBinarySequenceWithEmbeddedZeroAndLeavesFollowingBytes) +{ + std::array fds{}; + ASSERT_EQ(pipe2(fds.data(), O_NONBLOCK | O_CLOEXEC), 0); + UniqueFd reader(fds[0]); + UniqueFd writer(fds[1]); + constexpr std::array payload{'a', 'a', 0, 'a', 0, 'b', 'x', 'y'}; + constexpr std::array sequence{'a', 0, 'b', 'z'}; + ASSERT_EQ(write(writer.get(), payload.data(), payload.size()), static_cast(payload.size())); + std::array buffer{}; + // The explicit size excludes 'z'; the sequence has no C-string terminator. + EXPECT_EQ(serialReadUntilSequence(reader.get(), buffer.data(), 16, &kTimeout, sequence.data(), 3), 6); + EXPECT_EQ(std::memcmp(buffer.data(), payload.data(), 6), 0); + EXPECT_EQ(serialRead(reader.get(), buffer.data(), 2, &kTimeout), 2); + EXPECT_EQ(buffer[0], 'x'); + EXPECT_EQ(buffer[1], 'y'); + EXPECT_EQ(serialClose(reader.release()), 0); +} + +TEST(SerialV3ApiTest, ValidatesSequenceSizeAndHonorsBufferCapacity) +{ + UniqueFd fd(open("/dev/null", O_RDONLY)); + ASSERT_TRUE(fd.valid()); + std::array buffer{}; + EXPECT_EQ(serialReadUntilSequence(fd.get(), buffer.data(), 4, &kTimeout, nullptr, 1), kBufferError); + EXPECT_EQ(serialReadUntilSequence(fd.get(), buffer.data(), 4, &kTimeout, buffer.data(), 0), kBufferError); + EXPECT_EQ(serialReadUntilSequence(fd.get(), buffer.data(), 4, &kTimeout, buffer.data(), -1), kBufferError); + + std::array fds{}; + ASSERT_EQ(pipe2(fds.data(), O_NONBLOCK | O_CLOEXEC), 0); + UniqueFd reader(fds[0]); + UniqueFd writer(fds[1]); + constexpr std::array payload{'a', 'b', 'c', 'd', 'e'}; + ASSERT_EQ(write(writer.get(), payload.data(), payload.size()), static_cast(payload.size())); + EXPECT_EQ(serialReadUntilSequence(reader.get(), buffer.data(), 4, &kTimeout, payload.data(), 5), 4); + EXPECT_EQ(std::memcmp(buffer.data(), payload.data(), 4), 0); + EXPECT_EQ(serialClose(reader.release()), 0); +} + +TEST(SerialV3ApiTest, OpensPseudoTerminalWithFlowControlAndTypedSettings) +{ + UniqueFd master(posix_openpt(O_RDWR | O_NOCTTY | O_CLOEXEC)); + ASSERT_TRUE(master.valid()); + ASSERT_EQ(grantpt(master.get()), 0); + ASSERT_EQ(unlockpt(master.get()), 0); + const char *path = ptsname(master.get()); + ASSERT_NE(path, nullptr); + for (auto flow : {cpp_core::FlowControl::kNone, cpp_core::FlowControl::kRtsCts, cpp_core::FlowControl::kXonXoff}) + { + auto config = kConfig; + config.flow_mode = flow; + const auto handle = serialOpen(path, &config); + ASSERT_GT(handle, 0); + UniqueFd slave(static_cast(handle)); + EXPECT_EQ(serialGetBaudrate(handle), config.baudrate); + EXPECT_EQ(serialGetDataBits(handle), config.data_bits); + EXPECT_EQ(serialGetParity(handle), config.parity); + EXPECT_EQ(serialGetStopBits(handle), config.stop_bits); + EXPECT_EQ(serialGetFlowControl(handle), flow); + EXPECT_EQ(serialSetStopBits(handle, cpp_core::StopBits::kTwo), 0); + EXPECT_EQ(serialGetStopBits(handle), cpp_core::StopBits::kTwo); + EXPECT_EQ(serialSetStopBits(handle, static_cast(1)), + static_cast(cpp_core::StatusCode::Configuration::kSetStopBitsError)); + EXPECT_EQ(serialSetFlowControl(handle, cpp_core::FlowControl::kNone), 0); + EXPECT_EQ(serialGetFlowControl(handle), cpp_core::FlowControl::kNone); + EXPECT_EQ(serialWaitForDrain(handle), 0); + EXPECT_EQ(serialClose(slave.release()), 0); + } +} + +TEST(SerialV3ApiTest, TypedGettersPreserveNegativeErrors) +{ + constexpr int expected = static_cast(cpp_core::StatusCode::Connection::kInvalidHandleError); + EXPECT_EQ(cpp_core::toInt(serialGetDataBits(-1)), expected); + EXPECT_EQ(cpp_core::toInt(serialGetParity(-1)), expected); + EXPECT_EQ(cpp_core::toInt(serialGetStopBits(-1)), expected); + EXPECT_EQ(cpp_core::toInt(serialGetFlowControl(-1)), expected); + EXPECT_EQ(serialWaitForDrain(-1), expected); +} + +TEST(SerialV3ApiTest, MetadataDescribesLoadedBinding) +{ + meta(nullptr); + cpp_core::Meta info{}; + meta(&info); + EXPECT_STREQ(info.version_string, CPP_BINDINGS_LINUX_TEST_VERSION); + ASSERT_NE(info.git_commit_hash_full, nullptr); + EXPECT_EQ(std::strlen(info.git_commit_hash_full), 40U); + ASSERT_NE(info.git_commit_hash_short, nullptr); + EXPECT_TRUE(std::string_view(info.git_commit_hash_full).starts_with(info.git_commit_hash_short)); + EXPECT_NE(info.prerelease, nullptr); + EXPECT_NE(info.prerelease_type, nullptr); + EXPECT_NE(info.prerelease_number, nullptr); + EXPECT_NE(info.git_tag, nullptr); + EXPECT_NE(info.git_commit_date, nullptr); + EXPECT_NE(info.git_branch, nullptr); + EXPECT_NE(info.git_dirty_suffix, nullptr); +} + +TEST(SerialV3ApiTest, EventCallbackCanBeStartedReplacedAndStoppedRepeatedly) +{ + EXPECT_EQ(serialSetEventCallback(nullptr), 0); + for (int iteration = 0; iteration < 5; ++iteration) + { + EXPECT_EQ(serialSetEventCallback(portEvent), 0); + EXPECT_EQ(serialSetEventCallback(portEvent), 0); + EXPECT_EQ(serialSetEventCallback(nullptr), 0); + } + EXPECT_EQ(serialSetEventCallback(nullptr), 0); +} + +TEST(SerialV3ApiDeathTest, ActiveMonitorIsStoppedOnExit) +{ + EXPECT_EXIT( + { + if (serialSetEventCallback(portEvent) != 0) + { + std::exit(1); + } + std::exit(0); + }, + ::testing::ExitedWithCode(0), ""); +} diff --git a/src/serial_drain.cpp b/src/serial_wait_for_drain.cpp similarity index 84% rename from src/serial_drain.cpp rename to src/serial_wait_for_drain.cpp index fd49ebc..b53680b 100644 --- a/src/serial_drain.cpp +++ b/src/serial_wait_for_drain.cpp @@ -1,4 +1,4 @@ -#include +#include #include "detail/acquire_handle_context.hpp" #include "detail/fail_errno.hpp" @@ -9,7 +9,7 @@ extern "C" { - MODULE_API auto serialDrain(int64_t handle, ErrorCallbackT error_callback) -> int + MODULE_API auto serialWaitForDrain(int64_t handle, ErrorCallbackT error_callback) -> int { cpp_bindings_linux::detail::HandleContext handle_context; const auto status = diff --git a/src/serial_write.cpp b/src/serial_write.cpp index a506d1c..3ae7339 100644 --- a/src/serial_write.cpp +++ b/src/serial_write.cpp @@ -5,11 +5,11 @@ extern "C" { - MODULE_API auto serialWrite(int64_t handle, const void *buffer, int buffer_size, int timeout_ms, int multiplier, - ErrorCallbackT error_callback) -> int + MODULE_API auto serialWrite(int64_t handle, const std::uint8_t *buffer, int buffer_size, + const cpp_core::SerialTimeoutConfig *timeout_config, ErrorCallbackT error_callback) + -> int { - return cpp_bindings_linux::detail::writeImpl(handle, buffer, buffer_size, timeout_ms, multiplier, - error_callback); + return cpp_bindings_linux::detail::writeImpl(handle, buffer, buffer_size, timeout_config, error_callback); } } // extern "C" diff --git a/src/serial_write.test.cpp b/src/serial_write.test.cpp index c08a291..003f7b9 100644 --- a/src/serial_write.test.cpp +++ b/src/serial_write.test.cpp @@ -38,7 +38,8 @@ class SerialWriteTest : public ::testing::Test TEST_F(SerialWriteTest, WriteNullBuffer) { - int result = serialWrite(1, nullptr, 10, 100, 0, error_callback); + const cpp_core::SerialTimeoutConfig timeout_config{100, 0}; + int result = serialWrite(1, nullptr, 10, &timeout_config, error_callback); EXPECT_EQ(result, kBufferError); EXPECT_NE(error_capture.last_message.find("buffer"), std::string::npos); @@ -47,7 +48,9 @@ TEST_F(SerialWriteTest, WriteNullBuffer) TEST_F(SerialWriteTest, WriteZeroBufferSize) { std::array buffer{}; - int result = serialWrite(1, buffer.data(), 0, 100, 0, error_callback); + const cpp_core::SerialTimeoutConfig timeout_config{100, 0}; + int result = + serialWrite(1, reinterpret_cast(buffer.data()), 0, &timeout_config, error_callback); EXPECT_EQ(result, kBufferError); } @@ -55,7 +58,9 @@ TEST_F(SerialWriteTest, WriteZeroBufferSize) TEST_F(SerialWriteTest, WriteNegativeBufferSize) { std::array buffer{}; - int result = serialWrite(1, buffer.data(), -1, 100, 0, error_callback); + const cpp_core::SerialTimeoutConfig timeout_config{100, 0}; + int result = + serialWrite(1, reinterpret_cast(buffer.data()), -1, &timeout_config, error_callback); EXPECT_EQ(result, kBufferError); } @@ -63,7 +68,9 @@ TEST_F(SerialWriteTest, WriteNegativeBufferSize) TEST_F(SerialWriteTest, WriteInvalidHandleZero) { const char *buffer = "test"; - int result = serialWrite(0, buffer, static_cast(strlen(buffer)), 100, 0, error_callback); + const cpp_core::SerialTimeoutConfig timeout_config{100, 0}; + int result = serialWrite(0, reinterpret_cast(buffer), static_cast(strlen(buffer)), + &timeout_config, error_callback); EXPECT_EQ(result, kInvalidHandleError); } @@ -71,7 +78,9 @@ TEST_F(SerialWriteTest, WriteInvalidHandleZero) TEST_F(SerialWriteTest, WriteInvalidHandleNegative) { const char *buffer = "test"; - int result = serialWrite(-1, buffer, static_cast(strlen(buffer)), 100, 0, error_callback); + const cpp_core::SerialTimeoutConfig timeout_config{100, 0}; + int result = serialWrite(-1, reinterpret_cast(buffer), static_cast(strlen(buffer)), + &timeout_config, error_callback); EXPECT_EQ(result, kInvalidHandleError); } @@ -80,7 +89,9 @@ TEST_F(SerialWriteTest, WriteInvalidHandleTooLarge) { const char *buffer = "test"; auto too_large = static_cast(std::numeric_limits::max()) + 1; - int result = serialWrite(too_large, buffer, static_cast(strlen(buffer)), 100, 0, error_callback); + const cpp_core::SerialTimeoutConfig timeout_config{100, 0}; + int result = serialWrite(too_large, reinterpret_cast(buffer), + static_cast(strlen(buffer)), &timeout_config, error_callback); EXPECT_EQ(result, kInvalidHandleError); } @@ -92,7 +103,9 @@ TEST_F(SerialWriteTest, WriteToDevNull) const char *test_data = "Hello World"; const int len = static_cast(strlen(test_data)); - int result = serialWrite(fd, test_data, len, 0, 0, error_callback); + const cpp_core::SerialTimeoutConfig timeout_config{0, 0}; + int result = + serialWrite(fd, reinterpret_cast(test_data), len, &timeout_config, error_callback); EXPECT_EQ(result, len); close(fd); @@ -105,7 +118,9 @@ TEST_F(SerialWriteTest, WriteLargeBuffer) std::string large_data(4096, 'A'); const int len = static_cast(large_data.size()); - int result = serialWrite(fd, large_data.c_str(), len, 0, 0, error_callback); + const cpp_core::SerialTimeoutConfig timeout_config{0, 0}; + int result = serialWrite(fd, reinterpret_cast(large_data.c_str()), len, &timeout_config, + error_callback); EXPECT_EQ(result, len); close(fd); @@ -120,7 +135,9 @@ TEST_F(SerialWriteTest, WriteMultipleSmallBuffers) const int len = static_cast(strlen(data)); for (int i = 0; i < 10; ++i) { - int result = serialWrite(fd, data, len, 0, 0, error_callback); + const cpp_core::SerialTimeoutConfig timeout_config{0, 0}; + int result = + serialWrite(fd, reinterpret_cast(data), len, &timeout_config, error_callback); EXPECT_EQ(result, len); } @@ -134,7 +151,8 @@ TEST_F(SerialWriteTest, WriteNoErrorCallback) const char *test_data = "test"; const int len = static_cast(strlen(test_data)); - int result = serialWrite(fd, test_data, len, 0, 0, nullptr); + const cpp_core::SerialTimeoutConfig timeout_config{0, 0}; + int result = serialWrite(fd, reinterpret_cast(test_data), len, &timeout_config, nullptr); EXPECT_EQ(result, len); close(fd); @@ -150,7 +168,9 @@ TEST_F(SerialWriteTest, WriteWithVariousTimeouts) for (int timeout : {0, 1, 10, 100, 1000}) { - int result = serialWrite(fd, test_data, len, timeout, 0, error_callback); + const cpp_core::SerialTimeoutConfig timeout_config{timeout, 0}; + int result = + serialWrite(fd, reinterpret_cast(test_data), len, &timeout_config, error_callback); EXPECT_EQ(result, len) << "Timeout " << timeout << " should succeed for /dev/null"; } @@ -163,7 +183,8 @@ TEST_F(SerialWriteTest, WriteEmptyStringToDevNull) ASSERT_GE(fd, 0); const char *empty = ""; - int result = serialWrite(fd, empty, 0, 0, 0, error_callback); + const cpp_core::SerialTimeoutConfig timeout_config{0, 0}; + int result = serialWrite(fd, reinterpret_cast(empty), 0, &timeout_config, error_callback); EXPECT_EQ(result, kBufferError); close(fd); diff --git a/tests/integration.test.cpp b/tests/integration.test.cpp index 1ed6a35..691745b 100644 --- a/tests/integration.test.cpp +++ b/tests/integration.test.cpp @@ -47,12 +47,15 @@ TEST_F(SerialIntegrationTest, ReadWritePipeRoundTrip) const char *test_message = "Hello"; const int msg_len = static_cast(strlen(test_message)); - int write_result = serialWrite(pipefd[1], test_message, msg_len, 100, 0, error_callback); + const cpp_core::SerialTimeoutConfig timeout_config0{100, 0}; + int write_result = serialWrite(pipefd[1], reinterpret_cast(test_message), msg_len, + &timeout_config0, error_callback); EXPECT_EQ(write_result, msg_len); std::array read_buffer{}; - int read_result = - serialRead(pipefd[0], read_buffer.data(), static_cast(read_buffer.size()), 100, 0, error_callback); + const cpp_core::SerialTimeoutConfig timeout_config1{100, 0}; + int read_result = serialRead(pipefd[0], reinterpret_cast(read_buffer.data()), + static_cast(read_buffer.size()), &timeout_config1, error_callback); EXPECT_EQ(read_result, msg_len); EXPECT_EQ(std::string(read_buffer.data()), std::string(test_message)); @@ -71,12 +74,17 @@ TEST_F(SerialIntegrationTest, MultipleWrites) const char *msg1 = "Hello"; const char *msg2 = "World"; - serialWrite(pipefd[1], msg1, static_cast(strlen(msg1)), 100, 0, error_callback); - serialWrite(pipefd[1], msg2, static_cast(strlen(msg2)), 100, 0, error_callback); + const cpp_core::SerialTimeoutConfig timeout_config2{100, 0}; + serialWrite(pipefd[1], reinterpret_cast(msg1), static_cast(strlen(msg1)), + &timeout_config2, error_callback); + const cpp_core::SerialTimeoutConfig timeout_config3{100, 0}; + serialWrite(pipefd[1], reinterpret_cast(msg2), static_cast(strlen(msg2)), + &timeout_config3, error_callback); std::array read_buffer{}; - int read_result = - serialRead(pipefd[0], read_buffer.data(), static_cast(read_buffer.size()), 100, 0, error_callback); + const cpp_core::SerialTimeoutConfig timeout_config4{100, 0}; + int read_result = serialRead(pipefd[0], reinterpret_cast(read_buffer.data()), + static_cast(read_buffer.size()), &timeout_config4, error_callback); EXPECT_GE(read_result, 0); close(pipefd[0]); @@ -89,10 +97,14 @@ TEST_F(SerialIntegrationTest, CloseAfterOperations) ASSERT_EQ(pipe(pipefd.data()), 0); const char *test_data = "test"; - serialWrite(pipefd[1], test_data, static_cast(strlen(test_data)), 100, 0, error_callback); + const cpp_core::SerialTimeoutConfig timeout_config5{100, 0}; + serialWrite(pipefd[1], reinterpret_cast(test_data), static_cast(strlen(test_data)), + &timeout_config5, error_callback); std::array buffer{}; - serialRead(pipefd[0], buffer.data(), static_cast(buffer.size()), 100, 0, error_callback); + const cpp_core::SerialTimeoutConfig timeout_config6{100, 0}; + serialRead(pipefd[0], reinterpret_cast(buffer.data()), static_cast(buffer.size()), + &timeout_config6, error_callback); int close_result1 = serialClose(pipefd[0], error_callback); int close_result2 = serialClose(pipefd[1], error_callback); diff --git a/tests/serial_arduino.test.cpp b/tests/serial_arduino.test.cpp index a722d87..3c59bf6 100644 --- a/tests/serial_arduino.test.cpp +++ b/tests/serial_arduino.test.cpp @@ -3,7 +3,6 @@ #include #include #include -#include #include #include #include @@ -15,8 +14,6 @@ #include #include #include -#include -#include #include #include #include @@ -25,6 +22,7 @@ #include #include #include +#include #include #include @@ -91,8 +89,9 @@ class SerialArduinoTest : public ::testing::Test { const char *env_port = std::getenv("SERIAL_TEST_PORT"); // NOLINT(concurrency-mt-unsafe) const char *selected_port = (env_port != nullptr && env_port[0] != '\0') ? env_port : "/dev/ttyUSB0"; - handle_ = - serialOpen(const_cast(static_cast(selected_port)), kDefaultBaudrate, 8, 0, 0, nullptr); + const cpp_core::SerialConfig config0{kDefaultBaudrate, cpp_core::DataBits::kEight, cpp_core::Parity::kNone, + cpp_core::StopBits::kOne, cpp_core::FlowControl::kNone}; + handle_ = serialOpen(selected_port, &config0, nullptr); if (handle_ <= 0) { @@ -148,8 +147,9 @@ class SerialArduinoTest : public ::testing::Test while (total_read < expected_bytes && std::chrono::steady_clock::now() < deadline) { - const int chunk = - serialRead(handle_, destination + total_read, expected_bytes - total_read, kShortReadTimeoutMs, 1, nullptr); + const cpp_core::SerialTimeoutConfig timeout_config1{kShortReadTimeoutMs, 1}; + const int chunk = serialRead(handle_, reinterpret_cast(destination + total_read), + expected_bytes - total_read, &timeout_config1, nullptr); if (chunk < 0) { return chunk; @@ -173,9 +173,11 @@ class SerialArduinoTest : public ::testing::Test const int message_size = static_cast(message.size()); ASSERT_GT(message_size, 0); - const int written = serialWrite(handle_, message.data(), message_size, 1000, 1, nullptr); + const cpp_core::SerialTimeoutConfig timeout_config2{1000, 1}; + const int written = serialWrite(handle_, reinterpret_cast(message.data()), message_size, + &timeout_config2, nullptr); ASSERT_EQ(written, message_size) << "Failed to write full message"; - ASSERT_EQ(serialDrain(handle_, nullptr), kSuccess); + ASSERT_EQ(serialWaitForDrain(handle_, nullptr), kSuccess); const int waiting = waitForAvailableBytes(message_size, kEchoTimeoutMs); ASSERT_GE(waiting, message_size) << "Timed out waiting for echoed bytes"; @@ -216,7 +218,9 @@ TEST_F(SerialArduinoTest, ReadTimeoutReturnsZeroWhenNoDataIsPending) ASSERT_EQ(serialClearBufferIn(handle_, nullptr), kSuccess); std::array buffer{}; - const int read_bytes = serialRead(handle_, buffer.data(), static_cast(buffer.size()), 100, 1, nullptr); + const cpp_core::SerialTimeoutConfig timeout_config3{100, 1}; + const int read_bytes = serialRead(handle_, reinterpret_cast(buffer.data()), + static_cast(buffer.size()), &timeout_config3, nullptr); EXPECT_EQ(read_bytes, 0); } @@ -225,12 +229,16 @@ TEST_F(SerialArduinoTest, ReadLineStopsAtNewline) ASSERT_EQ(serialClearBufferIn(handle_, nullptr), kSuccess); constexpr std::string_view message = "Line helper test\n"; - ASSERT_EQ(serialWrite(handle_, message.data(), static_cast(message.size()), 1000, 1, nullptr), + const cpp_core::SerialTimeoutConfig timeout_config4{1000, 1}; + ASSERT_EQ(serialWrite(handle_, reinterpret_cast(message.data()), + static_cast(message.size()), &timeout_config4, nullptr), static_cast(message.size())); std::array buffer{}; - const int read_bytes = - serialReadLine(handle_, buffer.data(), static_cast(buffer.size()), kEchoTimeoutMs, 1, nullptr); + const cpp_core::SerialTimeoutConfig timeout_config5{kEchoTimeoutMs, 1}; + const int read_bytes = serialReadUntilSequence(handle_, reinterpret_cast(buffer.data()), + static_cast(buffer.size()), &timeout_config5, + reinterpret_cast("\n"), 1, nullptr); ASSERT_EQ(read_bytes, static_cast(message.size())); EXPECT_EQ(std::string_view(buffer.data(), static_cast(read_bytes)), message); @@ -243,12 +251,16 @@ TEST_F(SerialArduinoTest, ReadUntilStopsAtRequestedByte) constexpr std::string_view message = "Echo until!"; constexpr char terminator = '!'; - ASSERT_EQ(serialWrite(handle_, message.data(), static_cast(message.size()), 1000, 1, nullptr), + const cpp_core::SerialTimeoutConfig timeout_config6{1000, 1}; + ASSERT_EQ(serialWrite(handle_, reinterpret_cast(message.data()), + static_cast(message.size()), &timeout_config6, nullptr), static_cast(message.size())); std::array buffer{}; - const int read_bytes = serialReadUntil(handle_, buffer.data(), static_cast(buffer.size()), kEchoTimeoutMs, 1, - const_cast(&terminator), nullptr); + const cpp_core::SerialTimeoutConfig timeout_config7{kEchoTimeoutMs, 1}; + const int read_bytes = serialReadUntilSequence( + handle_, reinterpret_cast(buffer.data()), static_cast(buffer.size()), &timeout_config7, + reinterpret_cast(const_cast(&terminator)), 1, nullptr); ASSERT_EQ(read_bytes, static_cast(message.size())); EXPECT_EQ(std::string_view(buffer.data(), static_cast(read_bytes)), message); @@ -261,12 +273,16 @@ TEST_F(SerialArduinoTest, ReadUntilSequenceStopsAtRequestedSuffix) constexpr std::string_view message = "prefix-END"; char sequence[] = "END"; - ASSERT_EQ(serialWrite(handle_, message.data(), static_cast(message.size()), 1000, 1, nullptr), + const cpp_core::SerialTimeoutConfig timeout_config8{1000, 1}; + ASSERT_EQ(serialWrite(handle_, reinterpret_cast(message.data()), + static_cast(message.size()), &timeout_config8, nullptr), static_cast(message.size())); std::array buffer{}; - const int read_bytes = serialReadUntilSequence(handle_, buffer.data(), static_cast(buffer.size()), - kEchoTimeoutMs, 1, sequence, nullptr); + const cpp_core::SerialTimeoutConfig timeout_config9{kEchoTimeoutMs, 1}; + const int read_bytes = serialReadUntilSequence(handle_, reinterpret_cast(buffer.data()), + static_cast(buffer.size()), &timeout_config9, + reinterpret_cast(sequence), 3, nullptr); ASSERT_EQ(read_bytes, static_cast(message.size())); EXPECT_EQ(std::string_view(buffer.data(), static_cast(read_bytes)), message); @@ -293,7 +309,9 @@ TEST_F(SerialArduinoTest, CanObserveAndClearPendingInput) ASSERT_EQ(serialClearBufferIn(handle_, nullptr), kSuccess); constexpr std::string_view message = "Buffered input\n"; - ASSERT_EQ(serialWrite(handle_, message.data(), static_cast(message.size()), 1000, 1, nullptr), + const cpp_core::SerialTimeoutConfig timeout_config10{1000, 1}; + ASSERT_EQ(serialWrite(handle_, reinterpret_cast(message.data()), + static_cast(message.size()), &timeout_config10, nullptr), static_cast(message.size())); const int waiting = waitForAvailableBytes(static_cast(message.size()), kEchoTimeoutMs); @@ -304,7 +322,10 @@ TEST_F(SerialArduinoTest, CanObserveAndClearPendingInput) EXPECT_EQ(serialInBytesWaiting(handle_, nullptr), 0); std::array buffer{}; - EXPECT_EQ(serialRead(handle_, buffer.data(), static_cast(buffer.size()), 100, 1, nullptr), 0); + const cpp_core::SerialTimeoutConfig timeout_config11{100, 1}; + EXPECT_EQ(serialRead(handle_, reinterpret_cast(buffer.data()), static_cast(buffer.size()), + &timeout_config11, nullptr), + 0); } TEST_F(SerialArduinoTest, CanRoundTripLineSettingsAndRecoverCommunication) @@ -315,35 +336,35 @@ TEST_F(SerialArduinoTest, CanRoundTripLineSettingsAndRecoverCommunication) } EXPECT_EQ(serialGetBaudrate(handle_, nullptr), kDefaultBaudrate); - EXPECT_EQ(serialGetDataBits(handle_, nullptr), 8); - EXPECT_EQ(serialGetParity(handle_, nullptr), 0); - EXPECT_EQ(serialGetStopBits(handle_, nullptr), 0); - EXPECT_EQ(serialGetFlowControl(handle_, nullptr), 0); + EXPECT_EQ(serialGetDataBits(handle_, nullptr), cpp_core::DataBits::kEight); + EXPECT_EQ(serialGetParity(handle_, nullptr), cpp_core::Parity::kNone); + EXPECT_EQ(serialGetStopBits(handle_, nullptr), cpp_core::StopBits::kOne); + EXPECT_EQ(serialGetFlowControl(handle_, nullptr), cpp_core::FlowControl::kNone); ASSERT_EQ(serialSetBaudrate(handle_, 57600, nullptr), kSuccess); EXPECT_EQ(serialGetBaudrate(handle_, nullptr), 57600); ASSERT_EQ(serialSetBaudrate(handle_, kDefaultBaudrate, nullptr), kSuccess); EXPECT_EQ(serialGetBaudrate(handle_, nullptr), kDefaultBaudrate); - ASSERT_EQ(serialSetDataBits(handle_, 7, nullptr), kSuccess); - EXPECT_EQ(serialGetDataBits(handle_, nullptr), 7); - ASSERT_EQ(serialSetDataBits(handle_, 8, nullptr), kSuccess); - EXPECT_EQ(serialGetDataBits(handle_, nullptr), 8); + ASSERT_EQ(serialSetDataBits(handle_, cpp_core::DataBits::kSeven, nullptr), kSuccess); + EXPECT_EQ(serialGetDataBits(handle_, nullptr), cpp_core::DataBits::kSeven); + ASSERT_EQ(serialSetDataBits(handle_, cpp_core::DataBits::kEight, nullptr), kSuccess); + EXPECT_EQ(serialGetDataBits(handle_, nullptr), cpp_core::DataBits::kEight); - ASSERT_EQ(serialSetParity(handle_, 2, nullptr), kSuccess); - EXPECT_EQ(serialGetParity(handle_, nullptr), 2); - ASSERT_EQ(serialSetParity(handle_, 0, nullptr), kSuccess); - EXPECT_EQ(serialGetParity(handle_, nullptr), 0); + ASSERT_EQ(serialSetParity(handle_, cpp_core::Parity::kOdd, nullptr), kSuccess); + EXPECT_EQ(serialGetParity(handle_, nullptr), cpp_core::Parity::kOdd); + ASSERT_EQ(serialSetParity(handle_, cpp_core::Parity::kNone, nullptr), kSuccess); + EXPECT_EQ(serialGetParity(handle_, nullptr), cpp_core::Parity::kNone); - ASSERT_EQ(serialSetStopBits(handle_, 2, nullptr), kSuccess); - EXPECT_EQ(serialGetStopBits(handle_, nullptr), 2); - ASSERT_EQ(serialSetStopBits(handle_, 0, nullptr), kSuccess); - EXPECT_EQ(serialGetStopBits(handle_, nullptr), 0); + ASSERT_EQ(serialSetStopBits(handle_, cpp_core::StopBits::kTwo, nullptr), kSuccess); + EXPECT_EQ(serialGetStopBits(handle_, nullptr), cpp_core::StopBits::kTwo); + ASSERT_EQ(serialSetStopBits(handle_, cpp_core::StopBits::kOne, nullptr), kSuccess); + EXPECT_EQ(serialGetStopBits(handle_, nullptr), cpp_core::StopBits::kOne); - ASSERT_EQ(serialSetFlowControl(handle_, 2, nullptr), kSuccess); - EXPECT_EQ(serialGetFlowControl(handle_, nullptr), 2); - ASSERT_EQ(serialSetFlowControl(handle_, 0, nullptr), kSuccess); - EXPECT_EQ(serialGetFlowControl(handle_, nullptr), 0); + ASSERT_EQ(serialSetFlowControl(handle_, cpp_core::FlowControl::kXonXoff, nullptr), kSuccess); + EXPECT_EQ(serialGetFlowControl(handle_, nullptr), cpp_core::FlowControl::kXonXoff); + ASSERT_EQ(serialSetFlowControl(handle_, cpp_core::FlowControl::kNone, nullptr), kSuccess); + EXPECT_EQ(serialGetFlowControl(handle_, nullptr), cpp_core::FlowControl::kNone); // USB CDC devices can need a short resync window after multiple line-coding changes. sleepForMilliseconds(150); @@ -354,21 +375,24 @@ TEST_F(SerialArduinoTest, CanRoundTripLineSettingsAndRecoverCommunication) TEST_F(SerialArduinoTest, IdleOutputControlFunctionsSucceed) { EXPECT_EQ(serialOutBytesWaiting(handle_, nullptr), 0); - EXPECT_EQ(serialDrain(handle_, nullptr), kSuccess); + EXPECT_EQ(serialWaitForDrain(handle_, nullptr), kSuccess); EXPECT_EQ(serialClearBufferOut(handle_, nullptr), kSuccess); } TEST(SerialInvalidHandleTest, InvalidHandleRead) { std::array buffer{}; - const int result = serialRead(-1, buffer.data(), static_cast(buffer.size()), 1000, 1, nullptr); + const cpp_core::SerialTimeoutConfig timeout_config12{1000, 1}; + const int result = serialRead(-1, reinterpret_cast(buffer.data()), static_cast(buffer.size()), + &timeout_config12, nullptr); EXPECT_EQ(result, kInvalidHandleError) << "Should return error for invalid handle"; } TEST(SerialInvalidHandleTest, InvalidHandleWrite) { const char *data = "test"; - const int result = serialWrite(-1, data, 4, 1000, 1, nullptr); + const cpp_core::SerialTimeoutConfig timeout_config13{1000, 1}; + const int result = serialWrite(-1, reinterpret_cast(data), 4, &timeout_config13, nullptr); EXPECT_EQ(result, kInvalidHandleError) << "Should return error for invalid handle"; }