From b927b7a05dfa87886badcb413743bc06111edac1 Mon Sep 17 00:00:00 2001
From: Doralitze <doralitze@chaotikum.org>
Date: Tue, 5 Jan 2021 21:58:48 +0100
Subject: [PATCH] add: connection_line_buffer class

TODO: Write unit test for connection_line_buffer using loopback client.
Warning: Do not rely on connection_line_buffer::conn_data_in_cb until the unit test case proves its correctness (Nice looking solution but kinda smells like an off-by-one)
---
 src/net/connection_line_buffer.cpp | 74 ++++++++++++++++++++++++++++++
 src/net/connection_line_buffer.hpp | 38 +++++++++++++++
 2 files changed, 112 insertions(+)
 create mode 100644 src/net/connection_line_buffer.cpp
 create mode 100644 src/net/connection_line_buffer.hpp

diff --git a/src/net/connection_line_buffer.cpp b/src/net/connection_line_buffer.cpp
new file mode 100644
index 0000000..7d1c562
--- /dev/null
+++ b/src/net/connection_line_buffer.cpp
@@ -0,0 +1,74 @@
+/*
+ * connection_line_buffer.cpp
+ *
+ *  Created on: 05.01.2021
+ *      Author: doralitze
+ */
+
+#include "net/connection_line_buffer.hpp"
+
+namespace rmrf::net {
+
+static std::string::size_type default_eol_search(const std::string& data, std::string::size_type start_position) {
+	const std::string::size_type s = data.size();
+	for (std::string::size_type i = start_position; i < s; i++) {
+		switch (data[i]) {
+		case '\r':
+			if(i < s - 1) {
+				if (data[i + 1] == '\n') {
+					i++;
+				}
+			}
+			[[fallthrough]];
+		case '\n':
+			return i;
+			break;
+		default:
+			break;
+		}
+	}
+	return std::string::npos;
+}
+
+connection_line_buffer::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) :
+		search(search_lb),
+		client(c),
+		found_next_line_cb(found_next_line_cb_),
+		max(max_line_size),
+		data("") {
+	this->client->set_incomming_data_callback(std::bind(&connection_line_buffer::conn_data_in_cb, this, std::placeholders::_1));
+}
+
+connection_line_buffer::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) : connection_line_buffer{c, found_next_line_cb_, max_line_size, &default_eol_search} { }
+
+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.
+	 */
+
+	std::string::size_type strpos = 0;
+
+	while(strpos != std::string::npos) {
+		std::string::size_type nextpos = this->search(data_in, strpos);
+		if (nextpos == std::string::npos) {
+			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);
+			this->data = std::string("");
+		}
+		strpos = nextpos;
+	}
+
+	if (this->data.length() > this->max) {
+		this->found_next_line_cb(this->data, false);
+		this->data = std::string("");
+	}
+}
+
+}
diff --git a/src/net/connection_line_buffer.hpp b/src/net/connection_line_buffer.hpp
new file mode 100644
index 0000000..6eaf17c
--- /dev/null
+++ b/src/net/connection_line_buffer.hpp
@@ -0,0 +1,38 @@
+/*
+ * connection_line_buffer.hpp
+ *
+ *  Created on: 05.01.2021
+ *      Author: doralitze
+ */
+
+#pragma once
+
+#include <functional>
+#include <memory>
+#include <string>
+
+#include "net/connection_client.hpp"
+
+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);
+
+class connection_line_buffer {
+public:
+	typedef std::function<void(const std::string&, bool)> found_next_line_cb_t;
+private:
+	const eol_search_t search;
+	std::shared_ptr<connection_client> client;
+	found_next_line_cb_t found_next_line_cb;
+	std::string::size_type max;
+	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);
+private:
+	void conn_data_in_cb(const std::string& data_in);
+};
+
+}
-- 
GitLab