From 78758f35bf63bef5f4279a465ea7dd726b2a6ba3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Del=20Pino?= <stephane.delpino44@gmail.com>
Date: Mon, 31 Jan 2022 12:06:27 +0100
Subject: [PATCH] Begin socket handling: core functionalities are available

On provides a way to create
- server socket (listening to a given port)
- accept connections
- client socket that can connect to an host:port
these three functionalities are the only ones accessible from pugs'
language

On the other hand, reading and writing are provided by low level
calls. On can exchange values or arrays of values (pointers+size). It
remains unclear if we should allow transfer of higher level objects,
since socket exchanges should be generally used between different
codes?
---
 src/language/modules/CMakeLists.txt       |   1 +
 src/language/modules/ModuleRepository.cpp |   2 +
 src/language/modules/SocketModule.cpp     |  43 ++++++
 src/language/modules/SocketModule.hpp     |  28 ++++
 src/utils/CMakeLists.txt                  |   3 +-
 src/utils/Socket.cpp                      | 177 ++++++++++++++++++++++
 src/utils/Socket.hpp                      |  79 ++++++++++
 7 files changed, 332 insertions(+), 1 deletion(-)
 create mode 100644 src/language/modules/SocketModule.cpp
 create mode 100644 src/language/modules/SocketModule.hpp
 create mode 100644 src/utils/Socket.cpp
 create mode 100644 src/utils/Socket.hpp

diff --git a/src/language/modules/CMakeLists.txt b/src/language/modules/CMakeLists.txt
index f7f1baeaf..c4edd2505 100644
--- a/src/language/modules/CMakeLists.txt
+++ b/src/language/modules/CMakeLists.txt
@@ -10,6 +10,7 @@ add_library(PugsLanguageModules
   MeshModule.cpp
   ModuleRepository.cpp
   SchemeModule.cpp
+  SocketModule.cpp
   UnaryOperatorRegisterForVh.cpp
   UtilsModule.cpp
   WriterModule.cpp
diff --git a/src/language/modules/ModuleRepository.cpp b/src/language/modules/ModuleRepository.cpp
index 71a1368b9..85810cbfc 100644
--- a/src/language/modules/ModuleRepository.cpp
+++ b/src/language/modules/ModuleRepository.cpp
@@ -6,6 +6,7 @@
 #include <language/modules/MathModule.hpp>
 #include <language/modules/MeshModule.hpp>
 #include <language/modules/SchemeModule.hpp>
+#include <language/modules/SocketModule.hpp>
 #include <language/modules/UtilsModule.hpp>
 #include <language/modules/WriterModule.hpp>
 #include <language/utils/BasicAffectationRegistrerFor.hpp>
@@ -56,6 +57,7 @@ ModuleRepository::ModuleRepository()
   this->_subscribe(std::make_unique<MathModule>());
   this->_subscribe(std::make_unique<MeshModule>());
   this->_subscribe(std::make_unique<SchemeModule>());
+  this->_subscribe(std::make_unique<SocketModule>());
   this->_subscribe(std::make_unique<UtilsModule>());
   this->_subscribe(std::make_unique<WriterModule>());
 }
diff --git a/src/language/modules/SocketModule.cpp b/src/language/modules/SocketModule.cpp
new file mode 100644
index 000000000..99eb7a702
--- /dev/null
+++ b/src/language/modules/SocketModule.cpp
@@ -0,0 +1,43 @@
+#include <language/modules/SocketModule.hpp>
+
+#include <language/utils/BuiltinFunctionEmbedder.hpp>
+#include <utils/Socket.hpp>
+
+SocketModule::SocketModule()
+{
+  this->_addTypeDescriptor(ast_node_data_type_from<std::shared_ptr<const Socket>>);
+
+  this->_addBuiltinFunction("createSocketServer",
+                            std::make_shared<BuiltinFunctionEmbedder<std::shared_ptr<const Socket>(const uint64_t&)>>(
+
+                              [](const uint64_t& port_number) -> std::shared_ptr<const Socket> {
+                                return std::make_shared<const Socket>(createSocketServer(port_number));
+                              }
+
+                              ));
+
+  this->_addBuiltinFunction("acceptSocketClient",
+                            std::make_shared<
+                              BuiltinFunctionEmbedder<std::shared_ptr<const Socket>(std::shared_ptr<const Socket>)>>(
+
+                              [](std::shared_ptr<const Socket> server_socket) -> std::shared_ptr<const Socket> {
+                                return std::make_shared<const Socket>(acceptSocketClient(*server_socket));
+                              }
+
+                              ));
+
+  this->_addBuiltinFunction("connectSocketServer",
+                            std::make_shared<BuiltinFunctionEmbedder<std::shared_ptr<const Socket>(const std::string&,
+                                                                                                   const uint64_t&)>>(
+
+                              [](const std::string& hostname,
+                                 const uint64_t& port_number) -> std::shared_ptr<const Socket> {
+                                return std::make_shared<const Socket>(connectSocketServer(hostname, port_number));
+                              }
+
+                              ));
+}
+
+void
+SocketModule::registerOperators() const
+{}
diff --git a/src/language/modules/SocketModule.hpp b/src/language/modules/SocketModule.hpp
new file mode 100644
index 000000000..0b32b64fa
--- /dev/null
+++ b/src/language/modules/SocketModule.hpp
@@ -0,0 +1,28 @@
+#ifndef SOCKET_MODULE_HPP
+#define SOCKET_MODULE_HPP
+
+#include <language/modules/BuiltinModule.hpp>
+#include <language/utils/ASTNodeDataTypeTraits.hpp>
+
+class Socket;
+
+template <>
+inline ASTNodeDataType ast_node_data_type_from<std::shared_ptr<const Socket>> =
+  ASTNodeDataType::build<ASTNodeDataType::type_id_t>("socket");
+
+class SocketModule : public BuiltinModule
+{
+ public:
+  std::string_view
+  name() const final
+  {
+    return "socket";
+  }
+
+  void registerOperators() const final;
+
+  SocketModule();
+  ~SocketModule() = default;
+};
+
+#endif   // SOCKET_MODULE_HPP
diff --git a/src/utils/CMakeLists.txt b/src/utils/CMakeLists.txt
index 19feb273c..741b0d510 100644
--- a/src/utils/CMakeLists.txt
+++ b/src/utils/CMakeLists.txt
@@ -15,7 +15,8 @@ add_library(
   RandomEngine.cpp
   RevisionInfo.cpp
   SignalManager.cpp
-  SLEPcWrapper.cpp)
+  SLEPcWrapper.cpp
+  Socket.cpp)
 
 target_link_libraries(
   PugsUtils
diff --git a/src/utils/Socket.cpp b/src/utils/Socket.cpp
new file mode 100644
index 000000000..d8ece713d
--- /dev/null
+++ b/src/utils/Socket.cpp
@@ -0,0 +1,177 @@
+#include <utils/Exceptions.hpp>
+#include <utils/Socket.hpp>
+
+#include <arpa/inet.h>
+#include <cstring>
+#include <iostream>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <stdexcept>
+#include <sys/socket.h>
+#include <unistd.h>
+
+class Socket::Internals
+{
+ private:
+  int m_socket_fd;
+  sockaddr_in m_address;
+
+ public:
+  friend Socket createSocketServer(int port_number);
+  friend Socket acceptSocketClient(const Socket& server);
+  friend Socket connectSocketServer(const std::string& server_name, int port_number);
+
+  friend std::ostream&
+  operator<<(std::ostream& os, const Socket::Internals& internals)
+  {
+    char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];
+    if (::getnameinfo(reinterpret_cast<const sockaddr*>(&internals.m_address), sizeof(internals.m_address), hbuf,
+                      sizeof(hbuf), sbuf, sizeof(sbuf), NI_NAMEREQD) == 0) {
+      os << hbuf << ':' << sbuf;
+    } else if (::getnameinfo(reinterpret_cast<const sockaddr*>(&internals.m_address), sizeof(internals.m_address), hbuf,
+                             sizeof(hbuf), sbuf, sizeof(sbuf), NI_NUMERICHOST) == 0) {
+      if (std ::string{hbuf} == "0.0.0.0") {
+        if (::gethostname(hbuf, NI_MAXHOST) == 0) {
+          os << hbuf << ':' << sbuf;
+        } else {
+          os << "localhost:" << sbuf;
+        }
+      } else {
+        os << hbuf << ':' << sbuf;
+      }
+    } else {
+      os << "<unknown host>";
+    }
+    return os;
+  }
+
+  int
+  fileDescriptor() const
+  {
+    return m_socket_fd;
+  }
+
+  Internals(const Internals&) = delete;
+  Internals(Internals&&)      = delete;
+
+  Internals() = default;
+
+  ~Internals()
+  {
+    close(m_socket_fd);
+  }
+};
+
+std::ostream&
+operator<<(std::ostream& os, const Socket& socket)
+{
+  return os << *socket.m_internals;
+}
+
+Socket
+createSocketServer(int port_number)
+{
+  auto p_socket_internals             = std::make_shared<Socket::Internals>();
+  Socket::Internals& socket_internals = *p_socket_internals;
+
+  socket_internals.m_socket_fd = ::socket(AF_INET, SOCK_STREAM, 0);
+  if (socket_internals.m_socket_fd < 0) {
+    throw NormalError(strerror(errno));
+  }
+
+  socket_internals.m_address.sin_family      = AF_INET;
+  socket_internals.m_address.sin_addr.s_addr = INADDR_ANY;
+  socket_internals.m_address.sin_port        = htons(port_number);
+
+  int on = 1;
+  ::setsockopt(socket_internals.m_socket_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+
+  if (::bind(socket_internals.m_socket_fd, reinterpret_cast<sockaddr*>(&socket_internals.m_address),
+             sizeof(socket_internals.m_address)) < 0) {
+    throw NormalError(strerror(errno));
+  }
+
+  ::listen(socket_internals.m_socket_fd, 1);
+
+  return Socket{p_socket_internals};
+}
+
+Socket
+acceptSocketClient(const Socket& server)
+{
+  auto p_socket_internals             = std::make_shared<Socket::Internals>();
+  Socket::Internals& socket_internals = *p_socket_internals;
+
+  std::cout << "Waiting for connection on " << server << '\n';
+
+  socklen_t address_lenght     = sizeof(socket_internals.m_address);
+  socket_internals.m_socket_fd = ::accept(server.m_internals->m_socket_fd,
+                                          reinterpret_cast<sockaddr*>(&socket_internals.m_address), &address_lenght);
+
+  if (socket_internals.m_socket_fd < 0) {
+    throw NormalError(strerror(errno));
+  }
+
+  Socket client(p_socket_internals);
+  std::cout << "Connected from " << client << '\n';
+
+  return client;
+}
+
+Socket
+connectSocketServer(const std::string& server_name, int port_number)
+{
+  std::cout << "Trying to establish connection to " << server_name << ':' << port_number << '\n';
+
+  auto p_socket_internals             = std::make_shared<Socket::Internals>();
+  Socket::Internals& socket_internals = *p_socket_internals;
+
+  socket_internals.m_socket_fd = ::socket(AF_INET, SOCK_STREAM, 0);
+  if (socket_internals.m_socket_fd < 0) {
+    throw NormalError(strerror(errno));
+  }
+
+  hostent* server = ::gethostbyname(server_name.c_str());
+  if (server == NULL) {
+    throw NormalError(strerror(errno));
+  }
+
+  sockaddr_in& serv_addr = socket_internals.m_address;
+  ::memset(reinterpret_cast<char*>(&serv_addr), 0, sizeof(serv_addr));
+  serv_addr.sin_family = AF_INET;
+
+  ::memcpy(reinterpret_cast<char*>(&serv_addr.sin_addr.s_addr), reinterpret_cast<char*>(server->h_addr),
+           server->h_length);
+
+  serv_addr.sin_port = ::htons(port_number);
+
+  if (::connect(socket_internals.m_socket_fd, reinterpret_cast<sockaddr*>(&serv_addr), sizeof(serv_addr))) {
+    throw NormalError(strerror(errno));
+  }
+
+  Socket server_socket{p_socket_internals};
+  std::cout << "Connected to " << server_socket << '\n';
+
+  return server_socket;
+}
+
+void
+Socket::_write(const char* data, const size_t lenght) const
+{
+  if (::write(this->m_internals->fileDescriptor(), data, lenght) < 0) {
+    throw NormalError(strerror(errno));
+  }
+}
+
+void
+Socket::_read(char* data, const size_t length) const
+{
+  size_t received = 0;
+  do {
+    int n = ::read(this->m_internals->fileDescriptor(), reinterpret_cast<char*>(data) + received, length - received);
+    if (n <= 0) {
+      throw NormalError(strerror(errno));
+    }
+    received += n;
+  } while (received < length);
+}
diff --git a/src/utils/Socket.hpp b/src/utils/Socket.hpp
new file mode 100644
index 000000000..89bc63b9f
--- /dev/null
+++ b/src/utils/Socket.hpp
@@ -0,0 +1,79 @@
+#ifndef SOCKET_HPP
+#define SOCKET_HPP
+
+#include <memory>
+#include <string>
+#include <type_traits>
+
+class Socket
+{
+ private:
+  class Internals;
+
+  std::shared_ptr<Internals> m_internals;
+
+  Socket(std::shared_ptr<Internals> internals) : m_internals{internals} {}
+
+  void _write(const char* data, const size_t lenght) const;
+  void _read(char* data, const size_t lenght) const;
+
+ public:
+  friend std::ostream& operator<<(std::ostream& os, const Socket& s);
+
+  friend Socket createSocketServer(int port_number);
+  friend Socket acceptSocketClient(const Socket& server);
+  friend Socket connectSocketServer(const std::string& server_name, int port_number);
+
+  template <typename T>
+  friend void write(const Socket& socket, const T& value);
+
+  template <typename T>
+  friend void read(const Socket& socket, T& value);
+
+  template <template <typename T, typename... R> typename ArrayT, typename T, typename... R>
+  friend void write(const Socket& socket, const ArrayT<T, R...>& array);
+
+  template <template <typename T, typename... R> typename ArrayT, typename T, typename... R>
+  friend void read(const Socket& socket, ArrayT<T, R...>& array);
+
+  Socket(Socket&&)      = default;
+  Socket(const Socket&) = default;
+
+  ~Socket() = default;
+};
+
+Socket createSocketServer(int port_number);
+Socket acceptSocketClient(const Socket& server);
+Socket connectSocketServer(const std::string& server_name, int port_number);
+
+template <typename T>
+inline void
+write(const Socket& socket, const T& value)
+{
+  socket._write(reinterpret_cast<const char*>(&value), sizeof(T) / sizeof(char));
+}
+
+template <template <typename T, typename... R> typename ArrayT, typename T, typename... R>
+void
+write(const Socket& socket, const ArrayT<T, R...>& array)
+{
+  socket._write(reinterpret_cast<const char*>(&array[0]), array.size() * sizeof(T) / sizeof(char));
+}
+
+template <typename T>
+inline void
+read(const Socket& socket, T& value)
+{
+  static_assert(not std::is_const_v<T>, "cannot read values into const data");
+  socket._read(reinterpret_cast<char*>(&value), sizeof(T) / sizeof(char));
+}
+
+template <template <typename T, typename... R> typename ArrayT, typename T, typename... R>
+void
+read(const Socket& socket, ArrayT<T, R...>& array)
+{
+  static_assert(not std::is_const_v<T>, "cannot read values into const data");
+  socket._read(reinterpret_cast<char*>(&array[0]), array.size() * sizeof(T) / sizeof(char));
+}
+
+#endif   // SOCKET_HPP
-- 
GitLab