SLP Server

This contains the entry point for the SLP
and starts the SLP Server.

Change-Id: I5976c8168a1af2703143c9bead61583197949115
Signed-off-by: Ratan Gupta <ratagupt@in.ibm.com>
diff --git a/Makefile.am b/Makefile.am
new file mode 100755
index 0000000..1b1e804
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,12 @@
+sbin_PROGRAMS = \
+	slpd
+
+slpd_SOURCES = \
+	main.cpp \
+	sock_channel.cpp \
+	slp_server.cpp \
+	slp_parser.cpp \
+	slp.cpp \
+	slp_message_handler.cpp
+
+slpd_LDFLAGS = $(SYSTEMD_LIBS)
diff --git a/README.md b/README.md
new file mode 100755
index 0000000..083274a
--- /dev/null
+++ b/README.md
@@ -0,0 +1,18 @@
+## To Build
+```
+To build this package, do the following steps:
+
+    1. ./bootstrap.sh
+    2. ./configure ${CONFIGURE_FLAGS}
+    3. make
+
+To full clean the repository again run `./bootstrap.sh clean`.
+
+SLPD:-This is a unicast SLP UDP server which serves the following
+two messages
+1) finsrvs
+2) findsrvtypes
+
+NOTE:- Multicast support is not there and this server neither
+listen to any advertisement messages nor it advertises it's
+services with DA.
diff --git a/bootstrap.sh b/bootstrap.sh
new file mode 100755
index 0000000..50b75b7
--- /dev/null
+++ b/bootstrap.sh
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+AUTOCONF_FILES="Makefile.in aclocal.m4 ar-lib autom4te.cache compile \
+        config.guess config.h.in config.sub configure depcomp install-sh \
+        ltmain.sh missing *libtool test-driver"
+
+case $1 in
+    clean)
+        test -f Makefile && make maintainer-clean
+        for file in ${AUTOCONF_FILES}; do
+            find -name "$file" | xargs -r rm -rf
+        done
+        exit 0
+        ;;
+esac
+
+autoreconf -i
+echo 'Run "./configure ${CONFIGURE_FLAGS} && make"'
diff --git a/configure.ac b/configure.ac
new file mode 100755
index 0000000..d20b4be
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,22 @@
+# Initialization
+AC_PREREQ([2.69])
+AC_INIT([slpd-lite], [1.0], [https://github.com/openbmc/slpd-lite/issues])
+AC_CONFIG_HEADERS([config.h])
+AM_INIT_AUTOMAKE([subdir-objects -Wall -Werror foreign dist-xz])
+
+# Checks for programs.
+AC_PROG_CXX
+AX_CXX_COMPILE_STDCXX_14([noext])
+AM_PROG_AR
+AC_PROG_INSTALL
+AC_PROG_MAKE_SET
+
+# Checks for libraries.
+PKG_CHECK_MODULES([SYSTEMD], [libsystemd >= 221])
+
+# Checks for header files.
+AC_CHECK_HEADER(systemd/sd-bus.h, ,[AC_MSG_ERROR([Could not find systemd/sd-bus.h...systemd developement package required])])
+# Checks for typedefs, structures, and compiler characteristics.
+
+AC_CONFIG_FILES([Makefile])
+AC_OUTPUT
diff --git a/main.cpp b/main.cpp
new file mode 100644
index 0000000..13da385
--- /dev/null
+++ b/main.cpp
@@ -0,0 +1,82 @@
+#include <algorithm>
+#include <iomanip>
+
+#include "slp.hpp"
+#include "slp_meta.hpp"
+#include "slp_server.hpp"
+#include "sock_channel.hpp"
+
+
+/* Call Back for the sd event loop */
+static int requestHandler(sd_event_source* es, int fd, uint32_t revents,
+                          void* userdata)
+{
+    int rc = slp::SUCCESS;
+    timeval tv{slp::TIMEOUT, 0};
+    udpsocket::Channel channel(fd, tv);
+    std::vector<uint8_t> recvBuff;
+    slp::Message req;
+    std::vector<uint8_t> resp;
+    // Read the packet
+    std::tie(rc, recvBuff) = channel.read();
+
+    if (rc < 0)
+    {
+        std::cerr << "E> SLP Error in Read : " << std::hex << rc << "\n";
+        return rc;
+    }
+
+    switch (recvBuff[0])
+    {
+        case slp::VERSION_2:
+        {
+            std::cout << "SLP Request" << "\n";
+            //print the buffer
+            std::for_each(recvBuff.begin(), recvBuff.end(),
+                          [](uint8_t & ch)
+            {
+                std::cout << std::hex << std::setfill('0')
+                          << std::setw(2) << (int)ch << ' ' ;
+            });
+            std::cout << "\n";
+            //Parse the buffer and construct the req object
+            std::tie(rc, req) = slp::parser::parseBuffer(recvBuff);
+            if (!rc)
+            {
+                //Passing the req object to handler to serve it
+                std::tie(rc, resp) = slp::handler::processRequest(req);
+            }
+            break;
+        }
+        default:
+            rc = static_cast<uint8_t>(slp::Error::VER_NOT_SUPPORTED);
+            break;
+    }
+
+    //if there was error during Parsing of request
+    //or processing of request then handle the error.
+    if (rc)
+    {
+        std::cerr << "E> SLP Error rc=" << rc << "\n";
+        resp = slp::handler::processError(req, rc);
+    }
+
+    //print and send the response
+    std::cout << "SLP Response" << "\n";
+    std::for_each(resp.begin(), resp.end(),
+                  [](uint8_t & ch)
+    {
+        std::cout << std::hex << std::setfill('0')
+                  << std::setw(2) << (int)ch << ' ' ;
+    });
+
+    channel.write(resp);
+    return slp::SUCCESS;
+}
+
+
+int main(int argc, char* argv[])
+{
+    slp::udp::Server svr(slp::PORT, requestHandler);
+    return svr.run();
+}
diff --git a/slp.cpp b/slp.cpp
new file mode 100644
index 0000000..6338b05
--- /dev/null
+++ b/slp.cpp
@@ -0,0 +1,31 @@
+#include "slp.hpp"
+
+slp::Message::Message(const slp::Message& rhs)
+{
+    *this = rhs;
+}
+
+slp::Message& slp::Message::operator=(const slp::Message& rhs)
+{
+
+    if (this != &rhs)
+    {
+        header = rhs.header;
+        body = rhs.body;
+    }
+}
+
+
+slp::Payload::Payload(const slp::Payload& rhs)
+{
+    *this = rhs;
+}
+
+slp::Payload& slp::Payload::operator=(const slp::Payload& rhs)
+{
+    if (this != &rhs)
+    {
+        this->srvtyperqst = rhs.srvtyperqst ;
+        this->srvrqst = rhs.srvrqst;
+    }
+}
diff --git a/slp.hpp b/slp.hpp
new file mode 100644
index 0000000..5f9703c
--- /dev/null
+++ b/slp.hpp
@@ -0,0 +1,282 @@
+#pragma once
+
+#include <stdio.h>
+
+#include <array>
+#include <list>
+#include <map>
+#include <memory>
+#include <string>
+#include <tuple>
+#include <vector>
+
+#include "slp_service_info.hpp"
+
+namespace slp
+{
+
+using buffer = std::vector<uint8_t>;
+
+template<typename T>
+using deleted_unique_ptr = std::unique_ptr<T, std::function<void(T*)>>;
+
+namespace request
+{
+
+/*
+ * @struct ServiceType
+ *
+ * SLP Message structure for ServiceType Request.
+ */
+struct ServiceType
+{
+    std::string prList;
+    std::string namingAuth;
+    std::string scopeList;
+};
+
+/*
+ * @struct Service
+ *
+ * SLP Message structure for Service Request.
+ */
+struct Service
+{
+    std::string prList;
+    std::string srvType;
+    std::string scopeList;
+    std::string predicate;
+    std::string spistr;
+};
+}//namespace request
+
+/*
+ * @enum FunctionType
+ *
+ * SLP Protocol supported Message types.
+ */
+enum class FunctionType : uint8_t
+{
+    SRVRQST     = 0x01,
+    SRVRPLY     = 0x02,
+    ATTRRQST    = 0x06,
+    ATTRRPLY    = 0x07,
+    SRVTYPERQST = 0x09,
+    SRVTYPERPLY = 0x0A,
+    SAADV       = 0x0B,
+};
+
+/*
+ * @enum Error
+ *
+ * SLP Protocol defined Error Codes.
+ */
+enum class Error : uint8_t
+{
+    LANGUAGE_NOT_SUPPORTED = 0x01,
+    PARSE_ERROR            = 0x02,
+    INVALID_REGISTRATION   = 0x03,
+    SCOPE_NOT_SUPPORTED    = 0x04,
+    AUTHENTICATION_UNKNOWN = 0x05,
+    AUTHENTICATION_ABSENT  = 0x06,
+    AUTHENTICATION_FAILED  = 0x07,
+    VER_NOT_SUPPORTED      = 0x09,
+    INTERNAL_ERROR         = 0x0A,
+    DA_BUSY_NOW            = 0x0B,
+    OPTION_NOT_UNDERSTOOD  = 0x0C,
+    INVALID_UPDATE         = 0x0D,
+    MSG_NOT_SUPPORTED      = 0x0E,
+};
+
+/*
+ * @struct Header
+ *
+ * SLP Protocol Header
+ */
+struct Header
+{
+    uint8_t version = 0;
+    uint8_t functionID = 0;
+    std::array<uint8_t, 3> length;
+    uint16_t flags = 0;
+    std::array<uint8_t, 3> extOffset;
+    uint16_t xid = 0;
+    uint16_t langtagLen = 0;
+    std::string langtag;
+
+};
+
+/*
+ * @struct Payload
+ * This is a payload of the SLP Message currently
+ * we are supporting two request.
+ *
+ */
+struct Payload
+{
+    request::ServiceType srvtyperqst;
+    request::Service srvrqst;
+};
+
+
+/*
+ * @struct Messsage
+ *
+ * This will denote the slp Message.
+ */
+struct Message
+{
+    Header header;
+    Payload body;
+};
+
+
+namespace parser
+{
+
+/** Parse a buffer and fill the header and the body of the message.
+ *
+ * @param[in] buffer - The buffer from which data should be parsed.
+ *
+ * @return Zero on success and parsed msg object,
+ *         non-zero on failure and empty msg object.
+ *
+ */
+
+std::tuple<int, Message> parseBuffer(const buffer& buf);
+
+namespace internal
+{
+
+/** Parse header data from the buffer.
+ *
+ * @param[in] buffer - The buffer from which data should be parsed.
+ *
+ * @return Zero on success and fills header object inside message,
+ *         non-zero on failure and empty msg object.
+ *
+ * @internal
+ */
+
+std::tuple<int, Message> parseHeader(const buffer& buf);
+
+/** Parse a srvType request
+ *
+ * @param[in] buffer - The buffer from which data should be parsed.
+ *
+ * @return Zero on success,and fills the body object inside message.
+ *         non-zero on failure and empty msg object.
+ *
+ * @internal
+ */
+
+int parseSrvTypeRqst(const buffer& buf, Message& req);
+
+/** Parse a service request.
+  *
+  * @param[in] buffer - The buffer from which data should be parsed.
+  *
+  * @return Zero on success,and fills the body object inside message.
+  *         non-zero on failure and empty msg object.
+  *
+  * @internal
+  */
+
+int parseSrvRqst(const buffer& buf, Message& req);
+
+}//namespace internal
+}//namespce parser
+
+
+namespace handler
+{
+
+/** Handle the  request  message.
+ *
+ * @param[in] msg - The message to process.
+ *
+ * @return In case of success, the vector is populated with the data
+ *         available on the socket and return code is 0.
+ *         In case of error, nonzero code and vector is set to size 0.
+ *
+ */
+
+std::tuple<int, buffer> processRequest(const Message& msg);
+
+/** Handle the error
+ *
+ * @param[in] msg - Req message.
+ * @param[in] err - Error code.
+ *
+ * @return the vector populated with the error data
+ */
+
+buffer processError(const Message& req,
+                    const uint8_t err);
+namespace internal
+{
+
+using ServiceList = std::map<std::string, slp::ConfigData>;
+/** Handle the  SrvRequest message.
+ *
+ * @param[in] msg - The message to process
+ *
+ * @return In case of success, the vector is populated with the data
+ *         available on the socket and return code is 0.
+ *         In case of error, nonzero code and vector is set to size 0.
+ *
+ * @internal
+ */
+
+std::tuple<int, buffer> processSrvRequest(const Message& msg);
+
+
+/** Handle the  SrvTypeRequest message.
+ *
+ * @param[in] msg - The message to process
+ *
+ * @return In case of success, the vector is populated with the data
+ *         available on the socket and return code is 0.
+ *         In case of error, nonzero code and vector is set to size 0.
+ *
+ * @internal
+ *
+ */
+
+std::tuple<int, buffer> processSrvTypeRequest(const Message& msg);
+
+/**  Read the SLPinfo from the configuration.
+ *
+ * @param[in] filename - Name of the conf file
+ *
+ * @return the list of the services
+ *
+ * @internal
+ *
+ */
+ServiceList readSLPServiceInfo(const std::string& filename);
+
+/**  Get all the interface address
+ *
+ * @return the list of the interface address.
+ *
+ * @internal
+ *
+ */
+
+std::list<std::string> getIntfAddrs();
+
+/** Fill the buffer with the header data from the request object
+ *
+ * @param[in] req - Header data will be copied from
+ *
+ * @return the vector is populated with the data
+ *
+ * @internal
+ */
+buffer prepareHeader(const Message& req);
+
+
+}//namespace internal
+}//namespce handler
+}//namespce slp
diff --git a/slp_meta.hpp b/slp_meta.hpp
new file mode 100644
index 0000000..791d3d6
--- /dev/null
+++ b/slp_meta.hpp
@@ -0,0 +1,86 @@
+#pragma once
+
+namespace slp
+{
+/** @brief SLP configuartion file which is having the service information */
+constexpr auto CONF_FILE = "/etc/slp_lite.conf";
+/** @brief SLP Version */
+constexpr size_t VERSION_2 = 2;
+constexpr auto SUCCESS = 0;
+/** @brief SLP Port */
+constexpr auto PORT = 427;
+
+constexpr auto TIMEOUT = 30;
+/** @brief SLP service lifetime */
+constexpr auto LIFETIME = 5;
+
+/** @brief Defines the constants for slp header.
+ *  Size and the offsets.
+ */
+namespace header
+{
+
+constexpr size_t SIZE_VERSION = 1;
+constexpr size_t SIZE_LENGTH = 1;
+constexpr size_t SIZE_FLAGS = 2;
+constexpr size_t SIZE_EXT = 3;
+constexpr size_t SIZE_XID = 2;
+constexpr size_t SIZE_LANG = 2;
+
+constexpr size_t OFFSET_VERSION = 0;
+constexpr size_t OFFSET_FUNCTION = 1;
+constexpr size_t OFFSET_LENGTH = 4;
+constexpr size_t OFFSET_FLAGS = 5;
+constexpr size_t OFFSET_EXT = 7;
+constexpr size_t OFFSET_XID = 10;
+constexpr size_t OFFSET_LANG_LEN = 12;
+constexpr size_t OFFSET_LANG = 14;
+
+constexpr size_t MIN_LEN = 14;
+}//namespace header
+
+/** @brief Defines the constants for slp response.
+ *  Size and the offsets.
+ */
+
+namespace response
+{
+
+constexpr size_t SIZE_ERROR = 2;
+constexpr size_t SIZE_SERVICE = 2;
+constexpr size_t SIZE_URL_COUNT = 2;
+constexpr size_t SIZE_URL_ENTRY = 6;
+constexpr size_t SIZE_RESERVED = 1;
+constexpr size_t SIZE_LIFETIME = 2;
+constexpr size_t SIZE_URLLENGTH = 2;
+constexpr size_t SIZE_AUTH = 1;
+
+constexpr size_t OFFSET_ERROR = 16;
+constexpr size_t OFFSET_SERVICE_LEN = 18;
+constexpr size_t OFFSET_SERVICE = 20;
+constexpr size_t OFFSET_URL_ENTRY = 18;
+
+}//namespace response
+
+/** @brief Defines the constants for slp request.
+ *  Size and the offsets.
+ */
+namespace request
+{
+
+constexpr size_t MIN_SRVTYPE_LEN = 22;
+constexpr size_t MIN_SRV_LEN = 26;
+
+constexpr size_t SIZE_PRLIST = 2;
+constexpr size_t SIZE_NAMING = 2;
+constexpr size_t SIZE_SCOPE = 2;
+constexpr size_t SIZE_SERVICE_TYPE = 2;
+constexpr size_t SIZE_PREDICATE = 2;
+constexpr size_t SIZE_SLPI = 2;
+
+constexpr size_t OFFSET_PR_LEN = 16;
+constexpr size_t OFFSET_PR = 18;
+constexpr size_t OFFSET_SERVICE = 20;
+
+}//namespace request
+}//namespace slp
diff --git a/slp_server.cpp b/slp_server.cpp
new file mode 100644
index 0000000..e307dab
--- /dev/null
+++ b/slp_server.cpp
@@ -0,0 +1,110 @@
+#include "slp_server.hpp"
+
+#include <memory>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "sock_channel.hpp"
+
+/** General udp server which waits for the POLLIN event
+    on the port and calls the call back once it gets the event.
+    usage would be create the server with the port and the call back
+    and call the run method.
+ */
+int slp::udp::Server::run()
+{
+    struct sockaddr_in in {};
+
+    sd_event* event = nullptr;
+
+    slp::deleted_unique_ptr<sd_event> eventPtr(event, [](sd_event * event)
+    {
+        if (!event)
+        {
+            event = sd_event_unref(event);
+        }
+    });
+
+    int fd = -1, r;
+    sigset_t ss;
+
+    r = sd_event_default(&event);
+    if (r < 0)
+    {
+        goto finish;
+    }
+
+    eventPtr.reset(event);
+    event = nullptr;
+
+    if (sigemptyset(&ss) < 0 || sigaddset(&ss, SIGTERM) < 0 ||
+        sigaddset(&ss, SIGINT) < 0)
+    {
+        r = -errno;
+        goto finish;
+    }
+    /* Block SIGTERM first, so that the event loop can handle it */
+    if (sigprocmask(SIG_BLOCK, &ss, NULL) < 0)
+    {
+        r = -errno;
+        goto finish;
+    }
+
+    /* Let's make use of the default handler and "floating"
+       reference features of sd_event_add_signal() */
+
+    r = sd_event_add_signal(eventPtr.get(), NULL, SIGTERM, NULL, NULL);
+    if (r < 0)
+    {
+        goto finish;
+    }
+
+    r = sd_event_add_signal(eventPtr.get(), NULL, SIGINT, NULL, NULL);
+    if (r < 0)
+    {
+        goto finish;
+    }
+
+    fd = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);
+    if (fd < 0)
+    {
+        r = -errno;
+        goto finish;
+    }
+
+    in.sin_family = AF_INET;
+    in.sin_port = htons(this->port);
+    in.sin_addr.s_addr = INADDR_ANY;
+
+    if (bind(fd, (struct sockaddr*)&in, sizeof(in)) < 0)
+    {
+        r = -errno;
+        goto finish;
+    }
+
+    r = sd_event_add_io(eventPtr.get(), nullptr, fd, EPOLLIN, this->callme,
+                        nullptr);
+    if (r < 0)
+    {
+        goto finish;
+    }
+
+    r = sd_event_loop(eventPtr.get());
+
+finish:
+
+    if (fd >= 0)
+    {
+        (void) close(fd);
+    }
+
+    if (r < 0)
+    {
+        fprintf(stderr, "Failure: %s\n", strerror(-r));
+    }
+
+    return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
diff --git a/slp_server.hpp b/slp_server.hpp
new file mode 100644
index 0000000..0ab12c9
--- /dev/null
+++ b/slp_server.hpp
@@ -0,0 +1,46 @@
+#pragma once
+
+#include <iostream>
+#include <string>
+#include <sys/types.h>
+#include <systemd/sd-bus.h>
+#include <systemd/sd-daemon.h>
+#include <systemd/sd-event.h>
+
+#include "slp_meta.hpp"
+#include "slp.hpp"
+
+namespace slp
+{
+
+namespace udp
+{
+/** General udp server which waits for the POLLIN event
+    on the port and calls the call back once it gets the event.
+    usage would be create the server with the port and the call back
+    and call the run method.
+ */
+class Server
+{
+
+    public:
+
+        Server(): Server(slp::PORT, nullptr) {};
+
+        Server(uint16_t port, sd_event_io_handler_t cb):
+            port(port),
+            callme(cb) {};
+
+        Server(const Server&) = delete;
+        Server& operator=(const Server&) = delete;
+        Server(Server&&) = default;
+        Server& operator=(Server &&) = default;
+
+        uint16_t port;
+        sd_event_io_handler_t callme;
+
+        int run();
+
+};
+}//namespce udp
+}//namespace slp
diff --git a/slp_service_info.hpp b/slp_service_info.hpp
new file mode 100644
index 0000000..29fe1c4
--- /dev/null
+++ b/slp_service_info.hpp
@@ -0,0 +1,52 @@
+#pragma once
+#include <iostream>
+#include <fstream>
+#include <sstream>
+namespace slp
+{
+struct ConfigData
+{
+    std::string name;
+    std::string type;
+    std::string port;
+
+    friend std::istream& operator>>(std::istream& str, ConfigData& data)
+    {
+        std::string line;
+        constexpr auto DELIMITER = " ";
+        size_t  delimtrPos = 0;
+        size_t  delimtrPrevPos = 0;
+        std::array<std::string, 3>tokens;
+        std::getline(str, line);
+        size_t count = 0;
+
+        delimtrPos = line.find(DELIMITER, delimtrPrevPos);
+        while (delimtrPos != std::string::npos)
+        {
+            tokens[count] = line.substr(delimtrPrevPos, (delimtrPos - delimtrPrevPos));
+            delimtrPrevPos = delimtrPos + 1;
+
+            delimtrPos = line.find(DELIMITER, delimtrPrevPos);
+            if (delimtrPos == std::string::npos && delimtrPrevPos < line.length())
+            {
+                delimtrPos = line.length();
+            }
+
+            count++;
+        }
+
+        if (count > 2)
+        {
+            data.name = tokens[0];
+            data.type = tokens[1];
+            data.port = tokens[2];
+        }
+        else
+        {
+            str.setstate(std::ios::failbit);
+        }
+        return str;
+    }
+
+};
+}