Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 36 additions & 10 deletions include/libserial/serial.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,15 @@ size_t getMaxSafeReadSize() const;
*/
int getBaudRate() const;

/**
* @brief Gets the current data length setting
*
* Retrieves the number of data bits configured for serial communication.
*
* @return The current data length (number of data bits)
*/
DataLength getDataLength() const;

#ifdef BUILD_TESTING_ON
// WARNING: Test helper only! This function bypasses normal initialization
// and may leave the Serial object in an inconsistent state. It is intended
Expand All @@ -341,19 +350,43 @@ void setFdForTest(int fd) {
// WARNING: Test helper only! This function allows injection of custom
// system call functions for testing error handling. It should NEVER be
// used in production code.
void setSystemCallFunctions(
std::function<int(struct pollfd*, nfds_t, int)> poll_func,
std::function<ssize_t(int, void*, size_t)> read_func) {
void setPollSystemFunction(
std::function<int(struct pollfd*, nfds_t, int)> poll_func) {
poll_ = [poll_func](struct pollfd* f, nfds_t n, int t) {
return poll_func(f, n, t);
};
}

void setReadSystemFunction(
std::function<ssize_t(int, void*, size_t)> read_func) {
read_ = [read_func](int fd, void* buf, size_t sz) {
return read_func(fd, buf, sz);
};
}

/* *INDENT-OFF* */
void setIoctlSystemFunction(
std::function<int(int, unsigned long, void*)> ioctl_func) { // NOLINT
ioctl_ = [ioctl_func](int fd, unsigned long request, void* arg) { // NOLINT
return ioctl_func(fd, request, arg);
};
}
/* *INDENT-ON* */
#endif

private:
/**
* @brief Ioctl system call function wrapper
*
* Allows injection of custom ioctl function for testing.
*/
/* *INDENT-OFF* */
std::function<int(int, unsigned long, void*)> ioctl_ = // NOLINT
[](int fd, unsigned long request, void* arg) { // NOLINT
return ::ioctl(fd, request, arg);
};
/* *INDENT-ON* */

/**
* @brief Poll system call function wrapper
*
Expand Down Expand Up @@ -456,13 +489,6 @@ uint16_t min_number_char_read_{0};
*/
CanonicalMode canonical_mode_{CanonicalMode::ENABLE};

/**
* @brief Data length setting
*
* Specifies the number of data bits per character (default EIGHT).
*/
DataLength data_length_{DataLength::EIGHT};

/**
* @brief Line terminator character
*
Expand Down
34 changes: 21 additions & 13 deletions src/serial.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -189,11 +189,18 @@ size_t Serial::readUntil(std::shared_ptr<std::string> buffer, char terminator) {
}

void Serial::flushInputBuffer() {
if (ioctl(fd_serial_port_, TCFLSH, TCIFLUSH) != 0) {
if (ioctl_(fd_serial_port_, TCFLSH, TCIFLUSH) != 0) {
throw SerialException("Error flushing input buffer: " + std::string(strerror(errno)));
}
}

void Serial::setTermios2() {
ssize_t error = ioctl_(fd_serial_port_, TCSETS2, &options_);
if (error < 0) {
throw SerialException("Error set Termios2: " + std::string(strerror(errno)));
}
}

void Serial::setBaudRate(unsigned int baud_rate) {
this->getTermios2();
options_.c_cflag &= ~CBAUD;
Expand All @@ -207,13 +214,6 @@ void Serial::setBaudRate(BaudRate baud_rate) {
this->setBaudRate(static_cast<unsigned int>(baud_rate));
}

void Serial::setTermios2() {
ssize_t error = ioctl(fd_serial_port_, TCSETS2, &options_);
if (error < 0) {
throw SerialException("Error set Termios2: " + std::string(strerror(errno)));
}
}

void Serial::setReadTimeout(std::chrono::milliseconds timeout) {
read_timeout_ms_ = timeout;
}
Expand All @@ -223,10 +223,7 @@ void Serial::setWriteTimeout(std::chrono::milliseconds timeout) {
}

void Serial::setDataLength(DataLength nbits) {
data_length_ = nbits;

this->getTermios2();
// Clear bits
options_.c_cflag &= ~CSIZE;
switch (nbits) {
case DataLength::FIVE:
Expand Down Expand Up @@ -355,7 +352,7 @@ size_t Serial::getMaxSafeReadSize() const {

int Serial::getAvailableData() const {
int bytes_available;
if (ioctl(fd_serial_port_, FIONREAD, &bytes_available) < 0) {
if (ioctl_(fd_serial_port_, FIONREAD, &bytes_available) < 0) {
throw SerialException("Error getting available data: " + std::string(strerror(errno)));
}
return bytes_available;
Expand All @@ -366,8 +363,19 @@ int Serial::getBaudRate() const {
return (static_cast<int>(options_.c_ispeed));
}

DataLength Serial::getDataLength() const {
this->getTermios2();
switch (options_.c_cflag & CSIZE) {
case CS5: return DataLength::FIVE;
case CS6: return DataLength::SIX;
case CS7: return DataLength::SEVEN;
case CS8: return DataLength::EIGHT;
default: return DataLength::EIGHT;
}
}

void Serial::getTermios2() const {
ssize_t error = ioctl(fd_serial_port_, TCGETS2, &options_);
ssize_t error = ioctl_(fd_serial_port_, TCGETS2, &options_);
if (error < 0) {
throw SerialException("Error get Termios2: " + std::string(strerror(errno)));
}
Expand Down
89 changes: 73 additions & 16 deletions test/test_serial_pty.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,31 @@ TEST_F(PseudoTerminalTest, ParameterizedConstructor) {
libserial::Serial serial_port(slave_port_);
}

TEST_F(PseudoTerminalTest, SetTermios2WithFail) {
libserial::Serial serial_port;

serial_port.open(slave_port_);

// Inject failure into ioctl for setTermios2
serial_port.setIoctlSystemFunction(
[](int, unsigned long, void*) -> int { // NOLINT
errno = EIO;
return -1;
});

EXPECT_THROW({
serial_port.setBaudRate(9600);
}, libserial::SerialException);

// Restore ioctl function for cleanup
serial_port.setIoctlSystemFunction(
[](int fd, unsigned long request, void* arg) -> int { // NOLINT
return ::ioctl(fd, request, arg);
});

serial_port.close();
}

TEST_F(PseudoTerminalTest, SetAndGetBaudRate) {
libserial::Serial serial_port;

Expand All @@ -125,6 +150,45 @@ TEST_F(PseudoTerminalTest, SetAndGetBaudRate) {
serial_port.close();
}

// TEST_F(PseudoTerminalTest, SetAndGetDataLength) {
// libserial::Serial serial_port;

// serial_port.open(slave_port_);

// // Test multiple data lengths to be more thorough
// std::vector<libserial::DataLength> test_lengths = {
// libserial::DataLength::FIVE,
// libserial::DataLength::SIX,
// libserial::DataLength::SEVEN,
// libserial::DataLength::EIGHT
// };

// for (const auto& expected_length : test_lengths) {
// // Set data length
// EXPECT_NO_THROW({
// serial_port.setDataLength(expected_length);
// });

// // Add a small delay and flush
// std::this_thread::sleep_for(std::chrono::milliseconds(50));

// // Force a re-read of the current settings
// serial_port.close();
// serial_port.open(slave_port_);

// // Get data length and verify
// libserial::DataLength actual_length;
// EXPECT_NO_THROW({
// actual_length = serial_port.getDataLength();
// });

// EXPECT_EQ(actual_length, expected_length)
// << "Failed for data length: " << static_cast<int>(expected_length);
// }

// serial_port.close();
// }

TEST_F(PseudoTerminalTest, SetParity) {
libserial::Serial serial_port;

Expand Down Expand Up @@ -309,10 +373,11 @@ TEST_F(PseudoTerminalTest, ReadWithReadFail) {
auto read_buffer = std::make_shared<std::string>();

for (const auto& [error_num, error_msg] : errors_read_) {
serial_port.setSystemCallFunctions(
serial_port.setPollSystemFunction(
[](struct pollfd*, nfds_t, int) -> int {
return 1;
},
});
serial_port.setReadSystemFunction(
[error_num](int, void*, size_t) -> ssize_t {
errno = error_num;
return -1;
Expand All @@ -337,13 +402,10 @@ TEST_F(PseudoTerminalTest, ReadWithPollFail) {
auto read_buffer = std::make_shared<std::string>();

for (const auto& [error_num, error_msg] : errors_poll_) {
serial_port.setSystemCallFunctions(
serial_port.setPollSystemFunction(
[error_num](struct pollfd*, nfds_t, int) -> int {
errno = error_num;
return -1;
},
[](int, void*, size_t) -> ssize_t {
return 1;
});

auto expected_what = "Error in poll(): " + error_msg;
Expand Down Expand Up @@ -436,10 +498,7 @@ TEST_F(PseudoTerminalTest, ReadBytesWithReadFail) {
auto read_buffer = std::make_shared<std::string>();

for (const auto& [error_num, error_msg] : errors_read_) {
serial_port.setSystemCallFunctions(
[](struct pollfd*, nfds_t, int) -> int {
return 1;
},
serial_port.setReadSystemFunction(
[error_num](int, void*, size_t) -> ssize_t {
errno = error_num;
return -1;
Expand Down Expand Up @@ -554,10 +613,11 @@ TEST_F(PseudoTerminalTest, ReadUntilWithReadFail) {
// Skip these as they are handled differently in readUntil
continue;
}
serial_port.setSystemCallFunctions(
serial_port.setPollSystemFunction(
[](struct pollfd*, nfds_t, int) -> int {
return 1;
},
});
serial_port.setReadSystemFunction(
[error_num](int, void*, size_t) -> ssize_t {
errno = error_num;
return -1;
Expand All @@ -582,13 +642,10 @@ TEST_F(PseudoTerminalTest, ReadUntilWithPollFail) {
auto read_buffer = std::make_shared<std::string>();

for (const auto& [error_num, error_msg] : errors_poll_) {
serial_port.setSystemCallFunctions(
serial_port.setPollSystemFunction(
[error_num](struct pollfd*, nfds_t, int) -> int {
errno = error_num;
return -1;
},
[](int, void*, size_t) -> ssize_t {
return 1;
});

auto expected_what = "Error in poll(): " + error_msg;
Expand Down