diff --git a/src/net/connection_line_buffer.cpp b/src/net/connection_line_buffer.cpp index fd8a0c23e543379dee088d9d875f27a38eacd5e3..00ae1b8e4f0af093685ea08751c4156c43fb08d2 100644 --- a/src/net/connection_line_buffer.cpp +++ b/src/net/connection_line_buffer.cpp @@ -9,7 +9,7 @@ namespace rmrf::net { -static std::string::size_type default_eol_search( +std::string::size_type default_eol_search( const std::string &data, std::string::size_type start_position ) { @@ -50,7 +50,10 @@ connection_line_buffer::connection_line_buffer( max(max_line_size), data("") { - this->client->set_incomming_data_callback(std::bind(&connection_line_buffer::conn_data_in_cb, this, std::placeholders::_1)); + this->client->set_incomming_data_callback( + [this](const std::string& data_in) { + conn_data_in_cb(data_in); + }); } connection_line_buffer::connection_line_buffer( @@ -63,26 +66,27 @@ connection_line_buffer::connection_line_buffer( void connection_line_buffer::conn_data_in_cb(const std::string &data_in) { - /** - * Known limitation: If the last received data ends with '\r' and the next incoming data would start with '\n' there is no way - * to detect this as the received message might indeed be a complete one ending with '\r' and one cannot wait for a potential - * continuation of said message to arrive. - */ + // Iterate throug the incomming data as long as we find line breaks + for (std::string::size_type strpos = 0, nextpos = -1; strpos != std::string::npos; strpos = nextpos++) { + // Search for the next line break + nextpos = this->search(data_in, strpos); - std::string::size_type strpos = 0; - - while (strpos != std::string::npos) { - std::string::size_type nextpos = this->search(data_in, strpos); + // Advance, if the line would be empty + if (nextpos == strpos) { + nextpos = this->search(data_in, ++strpos); + } if (nextpos == std::string::npos) { + // If we didn't find one we store the remaining data in the buffer this->data += data_in.substr(strpos, data_in.length() - strpos); break; } else { - this->found_next_line_cb(this->data + data_in.substr(strpos, nextpos - strpos), true); + // If we find one we send the buffer plus the incomming data up to the line break to the callback + const std::string data_to_send = this->data + data_in.substr(strpos, nextpos - strpos); + this->found_next_line_cb(data_to_send, true); + // and clear the buffer this->data = std::string(""); } - - strpos = nextpos; } if (this->data.length() > this->max) { diff --git a/src/net/connection_line_buffer.hpp b/src/net/connection_line_buffer.hpp index 389a94f891ac25304ca845db59b58b6d3638d33a..137814147e11f0ec949c3c9587cbcdb0769bdad1 100644 --- a/src/net/connection_line_buffer.hpp +++ b/src/net/connection_line_buffer.hpp @@ -17,7 +17,7 @@ namespace rmrf::net { typedef std::function<std::string::size_type(const std::string&, std::string::size_type)> eol_search_t; -static std::string::size_type default_eol_search(const std::string& data, std::string::size_type start_position); +std::string::size_type default_eol_search(const std::string& data, std::string::size_type start_position); class connection_line_buffer { public: @@ -31,7 +31,7 @@ private: std::string data; public: - connection_line_buffer(std::shared_ptr<connection_client> c, found_next_line_cb_t found_next_line_cb_, std::string::size_type max_line_size, eol_search_t search_lb = default_eol_search); + connection_line_buffer(std::shared_ptr<connection_client> c, found_next_line_cb_t found_next_line_cb_, std::string::size_type max_line_size, eol_search_t search_lb); connection_line_buffer(std::shared_ptr<connection_client> c, found_next_line_cb_t found_next_line_cb_, std::string::size_type max_line_size); private: diff --git a/test/connection_line_buffer_test.cpp b/test/connection_line_buffer_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..cb21581720079773f61112489cee1f6c1d89557f --- /dev/null +++ b/test/connection_line_buffer_test.cpp @@ -0,0 +1,89 @@ +#define BOOST_AUTO_TEST_MAIN +#define BOOST_TEST_MODULE RMRF_TESTS +#include <boost/test/included/unit_test.hpp> + +#include <algorithm> +#include <iostream> +#include <memory> +#include <string> + +#include "loopback_connection_client.hpp" + +#include "net/connection_line_buffer.hpp" + + +using namespace rmrf::net; +using namespace rmrf::test; + +//BOOST_AUTO_TEST_SUITE( LINE_BUFFER ) + +int mut_send_stage = 0; +bool extra_failed = false; +const bool display_dbg_msg = false; + +void mut_send_data_cb(const std::string& data) { + std::cout << "This method should never have been called yet we've got:" << std::endl; + std::cout << data << std::endl; + BOOST_CHECK(false); +} + +void next_line_cb(const std::string& data, bool complete) { + if constexpr (display_dbg_msg) std::cout << mut_send_stage << ": " << data << std::endl; + + switch (mut_send_stage++) { + case 0: + BOOST_CHECK_EQUAL(data, "The first line"); + BOOST_CHECK(complete); + break; + case 1: + BOOST_CHECK_EQUAL(data, "The second line"); + BOOST_CHECK(complete); + break; + case 2: + BOOST_CHECK_EQUAL(data, "The third line"); + BOOST_CHECK(complete); + break; + case 3: + BOOST_CHECK_EQUAL(data.length(), 151); + BOOST_CHECK(!complete); + BOOST_CHECK(std::find_if(data.cbegin(), data.cend(), [](char c) { + return c != 'a'; + }) == data.cend()); + break; + default: + extra_failed = true; + } +} + +BOOST_AUTO_TEST_CASE(Default_EoL_Search_Test) { + BOOST_CHECK_EQUAL(default_eol_search("This line contains no line break", 0), std::string::npos); + std::string data = "This\r line contains line\r\n breaks"; + BOOST_CHECK_EQUAL(default_eol_search(data, 0), 4); + BOOST_CHECK_EQUAL(data.substr(0, 4), "This"); + BOOST_CHECK_EQUAL(default_eol_search(data, 5), 25); + BOOST_CHECK_EQUAL(data.substr(5, 25 - 5), " line contains line\r"); +} + +BOOST_AUTO_TEST_CASE(Connection_Line_Buffer_Test) { + mut_send_stage = 0; + auto ll_client = std::make_shared<loopback_connection_client>(mut_send_data_cb, false); + connection_line_buffer clb(ll_client, next_line_cb, 150); + + if constexpr (display_dbg_msg) std::cout << "Testing legit lines" << std::endl; + + ll_client->send_data_to_incomming_data_cb("The first"); + ll_client->send_data_to_incomming_data_cb(" line\r"); + ll_client->send_data_to_incomming_data_cb("The second line\r"); + ll_client->send_data_to_incomming_data_cb("\nThe third line\n"); + + if constexpr (display_dbg_msg) std::cout << "Testing line overflow" << std::endl; + + for (int i = 0; i < 151; i++) { + ll_client->send_data_to_incomming_data_cb("a"); + } + + BOOST_CHECK_EQUAL(mut_send_stage, 4); + BOOST_CHECK(!extra_failed); +} + +//BOOST_AUTO_TEST_SUITE_END() diff --git a/test/loopback_connection_client.hpp b/test/loopback_connection_client.hpp index 8c1ee84d57ec07c7b9821ba5223fde5f921b74f2..f9b1828ee8876fc071b3c09cf68b1a05c2189fd2 100644 --- a/test/loopback_connection_client.hpp +++ b/test/loopback_connection_client.hpp @@ -14,6 +14,7 @@ class loopback_connection_client : public rmrf::net::connection_client { private: const rmrf::net::connection_client::incomming_data_cb send_data_cb; std::vector<std::string> data_archive; + const bool echo_data_transfer; public: /** @@ -21,11 +22,13 @@ public: * send data. */ loopback_connection_client( - rmrf::net::connection_client::incomming_data_cb send_data_cb_ + rmrf::net::connection_client::incomming_data_cb send_data_cb_, + bool echo_data ) : rmrf::net::connection_client{}, send_data_cb(send_data_cb_), - data_archive{} + data_archive{}, + echo_data_transfer(echo_data) { // Does nothing special } @@ -43,7 +46,12 @@ public: */ virtual void write_data(const std::string& data) { // TODO fixme + if (this->echo_data_transfer) { + std::cout << "<-- " << data << std::endl; + } + this->data_archive.push_back(data); + if (this->send_data_cb) { this->send_data_cb(data); } @@ -56,6 +64,10 @@ public: * @param data The data to send. */ void send_data_to_incomming_data_cb(const std::string& data) { + if (this->echo_data_transfer) { + std::cout << "--> " << data << std::endl; + } + if (this->in_data_cb) { this->in_data_cb(data); } @@ -67,6 +79,10 @@ public: std::vector<std::string> get_send_data() { return this->data_archive; } + + void clear_sent_data() { + this->data_archive.clear(); + } }; }