aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatthias P. Braendli <matthias.braendli@mpb.li>2016-12-25 23:53:51 +0100
committerMatthias P. Braendli <matthias.braendli@mpb.li>2016-12-25 23:53:51 +0100
commitfd6d695275f88e83ebba6fa39afc044e329a690f (patch)
tree38ac7b49407970cb3c0b2b66972653a37d0d1fd5
parenteaf1c41bde2b58446697360af454266c4dc594a4 (diff)
downloaddabmod-fd6d695275f88e83ebba6fa39afc044e329a690f.tar.gz
dabmod-fd6d695275f88e83ebba6fa39afc044e329a690f.tar.bz2
dabmod-fd6d695275f88e83ebba6fa39afc044e329a690f.zip
Add first version of EDI input
-rw-r--r--Makefile.am35
-rw-r--r--doc/example.ini6
-rw-r--r--lib/InetAddress.cpp171
-rw-r--r--lib/InetAddress.h80
-rw-r--r--lib/UdpSocket.cpp256
-rw-r--r--lib/UdpSocket.h180
-rw-r--r--lib/crc.c267
-rw-r--r--lib/crc.h59
-rw-r--r--lib/edi/ETIDecoder.cpp388
-rw-r--r--lib/edi/ETIDecoder.hpp74
-rw-r--r--lib/edi/ETIWriter.cpp277
-rw-r--r--lib/edi/ETIWriter.hpp114
-rw-r--r--lib/edi/PFT.cpp553
-rw-r--r--lib/edi/PFT.hpp154
-rw-r--r--lib/edi/README.md1
-rw-r--r--lib/edi/buffer_unpack.hpp62
-rw-r--r--lib/edi/eti.cpp64
-rw-r--r--lib/edi/eti.hpp114
-rw-r--r--lib/fec/LICENSE502
-rw-r--r--lib/fec/README.md12
-rw-r--r--lib/fec/char.h24
-rw-r--r--lib/fec/decode_rs.h298
-rw-r--r--lib/fec/decode_rs_char.c22
-rw-r--r--lib/fec/encode_rs.h58
-rw-r--r--lib/fec/encode_rs_char.c15
-rw-r--r--lib/fec/fec.h30
-rw-r--r--lib/fec/init_rs.h104
-rw-r--r--lib/fec/init_rs_char.c35
-rw-r--r--lib/fec/rs-common.h26
-rw-r--r--src/DabMod.cpp15
-rw-r--r--src/InputEdiReader.cpp99
-rw-r--r--src/InputReader.h29
32 files changed, 4112 insertions, 12 deletions
diff --git a/Makefile.am b/Makefile.am
index a778975..793710d 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -23,7 +23,9 @@
ACLOCAL_AMFLAGS = -I m4
-EXTRA_DIST = COPYING NEWS README.md AUTHORS ChangeLog TODO doc
+EXTRA_DIST = COPYING NEWS README.md AUTHORS ChangeLog TODO doc \
+ lib/fec/README.md lib/fec/LICENSE \
+ lib/edi/README.md
if IS_GIT_REPO
GITVERSION_FLAGS = -DGITVERSION="\"`git describe --dirty`\""
@@ -35,9 +37,9 @@ bin_PROGRAMS = odr-dabmod
FFT_LDADD=
-odr_dabmod_CXXFLAGS = -Wall -Isrc -std=c++11 \
+odr_dabmod_CXXFLAGS = -Wall -Isrc -Ilib -std=c++11 \
$(GITVERSION_FLAGS)
-odr_dabmod_CFLAGS = -Wall -Isrc \
+odr_dabmod_CFLAGS = -Wall -Isrc -Ilib \
$(GITVERSION_FLAGS)
odr_dabmod_LDADD = $(FFT_LDADD)
odr_dabmod_SOURCES = src/DabMod.cpp \
@@ -78,6 +80,7 @@ odr_dabmod_SOURCES = src/DabMod.cpp \
src/OutputUHD.h \
src/InputMemory.cpp \
src/InputMemory.h \
+ src/InputEdiReader.cpp \
src/InputFileReader.cpp \
src/InputTcpReader.cpp \
src/InputZeroMQReader.cpp \
@@ -125,5 +128,29 @@ odr_dabmod_SOURCES = src/DabMod.cpp \
src/Utils.h \
src/TII.cpp \
src/TII.h \
- src/zmq.hpp
+ src/zmq.hpp \
+ lib/crc.h \
+ lib/crc.c \
+ lib/InetAddress.h \
+ lib/InetAddress.cpp \
+ lib/UdpSocket.h \
+ lib/UdpSocket.cpp \
+ lib/edi/buffer_unpack.hpp \
+ lib/edi/eti.cpp \
+ lib/edi/ETIDecoder.cpp \
+ lib/edi/ETIDecoder.hpp \
+ lib/edi/eti.hpp \
+ lib/edi/ETIWriter.cpp \
+ lib/edi/ETIWriter.hpp \
+ lib/edi/PFT.cpp \
+ lib/edi/PFT.hpp \
+ lib/fec/char.h \
+ lib/fec/decode_rs_char.c \
+ lib/fec/decode_rs.h \
+ lib/fec/encode_rs_char.c \
+ lib/fec/encode_rs.h \
+ lib/fec/fec.h \
+ lib/fec/init_rs_char.c \
+ lib/fec/init_rs.h \
+ lib/fec/rs-common.h
diff --git a/doc/example.ini b/doc/example.ini
index 8403e8f..361a4c4 100644
--- a/doc/example.ini
+++ b/doc/example.ini
@@ -52,6 +52,12 @@ source=/dev/stdin
; When the end of file is reached, it is possible to rewind it
loop=0
+; EDI input.
+; Listen for EDI data on a given UDP port
+;transport=edi
+;source=udp://:12002
+; No support yet for multicast, should work with and without PFT
+
; When recieving data using ZeroMQ, the source is the URI to be used
;transport=zeromq
;source=tcp://localhost:8080
diff --git a/lib/InetAddress.cpp b/lib/InetAddress.cpp
new file mode 100644
index 0000000..d92e1b9
--- /dev/null
+++ b/lib/InetAddress.cpp
@@ -0,0 +1,171 @@
+/*
+ Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009 Her Majesty the
+ Queen in Right of Canada (Communications Research Center Canada)
+
+ Copyright (C) 2016
+ Matthias P. Braendli, matthias.braendli@mpb.li
+
+ http://www.opendigitalradio.org
+ */
+/*
+ This file is part of ODR-DabMux.
+
+ ODR-DabMux is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the
+ License, or (at your option) any later version.
+
+ ODR-DabMux is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with ODR-DabMux. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "InetAddress.h"
+#include <iostream>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+
+#ifdef TRACE_ON
+# ifndef TRACE_CLASS
+# define TRACE_CLASS(clas, func) cout <<"-" <<(clas) <<"\t(" <<this <<")::" <<(func) <<endl
+# define TRACE_STATIC(clas, func) cout <<"-" <<(clas) <<"\t(static)::" <<(func) <<endl
+# endif
+#else
+# ifndef TRACE_CLASS
+# define TRACE_CLASS(clas, func)
+# define TRACE_STATIC(clas, func)
+# endif
+#endif
+
+
+int inetErrNo = 0;
+const char *inetErrMsg = nullptr;
+const char *inetErrDesc = nullptr;
+
+
+/**
+ * Constructs an IP address.
+ * @param port The port of this address
+ * @param name The name of this address
+ */
+InetAddress::InetAddress(int port, const char* name) {
+ TRACE_CLASS("InetAddress", "InetAddress(int, char)");
+ addr.sin_family = PF_INET;
+ addr.sin_addr.s_addr = htons(INADDR_ANY);
+ addr.sin_port = htons(port);
+ if (name)
+ setAddress(name);
+}
+
+
+/**
+ * Constructs a copy of inet
+ * @param inet The address to be copied
+ */
+InetAddress::InetAddress(const InetAddress &inet) {
+ TRACE_CLASS("InetAddress", "InetAddress(InetAddress)");
+ memcpy(&addr, &inet.addr, sizeof(addr));
+}
+
+
+/// Destructor
+InetAddress::~InetAddress() {
+ TRACE_CLASS("InetAddress" ,"~InetAddress()");
+}
+
+
+/// Returns the raw IP address of this InetAddress object.
+sockaddr *InetAddress::getAddress() {
+ TRACE_CLASS("InetAddress", "getAddress()");
+ return (sockaddr *)&addr;
+}
+
+
+/// Return the port of this address.
+int InetAddress::getPort()
+{
+ TRACE_CLASS("InetAddress", "getPort()");
+ return ntohs(addr.sin_port);
+}
+
+
+/**
+ * Returns the IP address string "%d.%d.%d.%d".
+ * @return IP address
+ */
+const char *InetAddress::getHostAddress() {
+ TRACE_CLASS("InetAddress", "getHostAddress()");
+ return inet_ntoa(addr.sin_addr);
+}
+
+
+/// Returns true if this address is multicast
+bool InetAddress::isMulticastAddress() {
+ TRACE_CLASS("InetAddress", "isMulticastAddress()");
+ return IN_MULTICAST(ntohl(addr.sin_addr.s_addr)); // a modifier
+}
+
+
+/**
+ * Set the port number
+ * @param port The new port number
+ */
+void InetAddress::setPort(int port)
+{
+ TRACE_CLASS("InetAddress", "setPort(int)");
+ addr.sin_port = htons(port);
+}
+
+
+/**
+ * Set the address
+ * @param name The new address name
+ * @return 0 if ok
+ * -1 if error
+ */
+int InetAddress::setAddress(const std::string& name)
+{
+ TRACE_CLASS("InetAddress", "setAddress(string)");
+ if (!name.empty()) {
+ if (atoi(name.c_str())) { // If it start with a number
+ if ((addr.sin_addr.s_addr = inet_addr(name.c_str())) == INADDR_NONE) {
+ addr.sin_addr.s_addr = htons(INADDR_ANY);
+ inetErrNo = 0;
+ inetErrMsg = "Invalid address";
+ inetErrDesc = name.c_str();
+ return -1;
+ }
+ }
+ else { // Assume it's a real name
+ hostent *host = gethostbyname(name.c_str());
+ if (host) {
+ addr.sin_addr = *(in_addr *)(host->h_addr);
+ } else {
+ addr.sin_addr.s_addr = htons(INADDR_ANY);
+ inetErrNo = 0;
+ inetErrMsg = "Could not find address";
+ inetErrDesc = name.c_str();
+ return -1;
+ }
+ }
+ }
+ else {
+ addr.sin_addr.s_addr = INADDR_ANY;
+ }
+ return 0;
+}
+
+
+void setInetError(const char* description)
+{
+ inetErrNo = 0;
+ inetErrNo = errno;
+ inetErrMsg = strerror(inetErrNo);
+ inetErrDesc = description;
+}
+
diff --git a/lib/InetAddress.h b/lib/InetAddress.h
new file mode 100644
index 0000000..0ccc70b
--- /dev/null
+++ b/lib/InetAddress.h
@@ -0,0 +1,80 @@
+/*
+ Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009 Her Majesty the
+ Queen in Right of Canada (Communications Research Center Canada)
+
+ Copyright (C) 2016
+ Matthias P. Braendli, matthias.braendli@mpb.li
+
+ http://www.opendigitalradio.org
+ */
+/*
+ This file is part of ODR-DabMux.
+
+ ODR-DabMux is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the
+ License, or (at your option) any later version.
+
+ ODR-DabMux is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with ODR-DabMux. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _InetAddress
+#define _InetAddress
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <arpa/inet.h>
+#include <pthread.h>
+#include <string>
+
+#define SOCKET int
+#define INVALID_SOCKET -1
+#define INVALID_PORT -1
+
+
+/// The last error number
+extern int inetErrNo;
+/// The last error message
+extern const char *inetErrMsg;
+/// The description of the last error
+extern const char *inetErrDesc;
+/// Set the number, message and description of the last error
+void setInetError(const char* description);
+
+
+/**
+ * This class represents an Internet Protocol (IP) address.
+ * @author Pascal Charest pascal.charest@crc.ca
+ */
+class InetAddress {
+ public:
+ InetAddress(int port = 0, const char* name = NULL);
+ InetAddress(const InetAddress &addr);
+ ~InetAddress();
+
+ sockaddr *getAddress();
+ const char *getHostAddress();
+ int getPort();
+ int setAddress(const std::string& name);
+ void setPort(int port);
+ bool isMulticastAddress();
+
+ private:
+ sockaddr_in addr;
+};
+
+
+#endif
diff --git a/lib/UdpSocket.cpp b/lib/UdpSocket.cpp
new file mode 100644
index 0000000..ccdd7ed
--- /dev/null
+++ b/lib/UdpSocket.cpp
@@ -0,0 +1,256 @@
+/*
+ Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009 Her Majesty the
+ Queen in Right of Canada (Communications Research Center Canada)
+
+ Copyright (C) 2016
+ Matthias P. Braendli, matthias.braendli@mpb.li
+
+ http://www.opendigitalradio.org
+ */
+/*
+ This file is part of ODR-DabMux.
+
+ ODR-DabMux is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the
+ License, or (at your option) any later version.
+
+ ODR-DabMux is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with ODR-DabMux. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "UdpSocket.h"
+
+#include <iostream>
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+
+using namespace std;
+
+UdpSocket::UdpSocket() :
+ listenSocket(INVALID_SOCKET)
+{
+ reinit(0, "");
+}
+
+UdpSocket::UdpSocket(int port) :
+ listenSocket(INVALID_SOCKET)
+{
+ reinit(port, "");
+}
+
+UdpSocket::UdpSocket(int port, const std::string& name) :
+ listenSocket(INVALID_SOCKET)
+{
+ reinit(port, name);
+}
+
+
+int UdpSocket::setBlocking(bool block)
+{
+ int res;
+ if (block)
+ res = fcntl(listenSocket, F_SETFL, 0);
+ else
+ res = fcntl(listenSocket, F_SETFL, O_NONBLOCK);
+ if (res == SOCKET_ERROR) {
+ setInetError("Can't change blocking state of socket");
+ return -1;
+ }
+ return 0;
+}
+
+int UdpSocket::reinit(int port, const std::string& name)
+{
+ if (listenSocket != INVALID_SOCKET) {
+ ::close(listenSocket);
+ }
+
+ if ((listenSocket = socket(PF_INET, SOCK_DGRAM, 0)) == INVALID_SOCKET) {
+ setInetError("Can't create socket");
+ return -1;
+ }
+ reuseopt_t reuse = 1;
+ if (setsockopt(listenSocket, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse))
+ == SOCKET_ERROR) {
+ setInetError("Can't reuse address");
+ return -1;
+ }
+
+ if (port) {
+ address.setAddress(name);
+ address.setPort(port);
+
+ if (bind(listenSocket, address.getAddress(), sizeof(sockaddr_in)) == SOCKET_ERROR) {
+ setInetError("Can't bind socket");
+ ::close(listenSocket);
+ listenSocket = INVALID_SOCKET;
+ return -1;
+ }
+ }
+ return 0;
+}
+
+int UdpSocket::close()
+{
+ if (listenSocket != INVALID_SOCKET) {
+ ::close(listenSocket);
+ }
+
+ listenSocket = INVALID_SOCKET;
+
+ return 0;
+}
+
+UdpSocket::~UdpSocket()
+{
+ if (listenSocket != INVALID_SOCKET) {
+ ::close(listenSocket);
+ }
+}
+
+
+int UdpSocket::receive(UdpPacket& packet)
+{
+ socklen_t addrSize;
+ addrSize = sizeof(*packet.getAddress().getAddress());
+ ssize_t ret = recvfrom(listenSocket,
+ packet.getData(),
+ packet.getSize(),
+ 0,
+ packet.getAddress().getAddress(),
+ &addrSize);
+
+ if (ret == SOCKET_ERROR) {
+ packet.setSize(0);
+ if (errno == EAGAIN) {
+ return 0;
+ }
+ setInetError("Can't receive UDP packet");
+ return -1;
+ }
+
+ packet.setSize(ret);
+ return 0;
+}
+
+int UdpSocket::send(UdpPacket& packet)
+{
+ int ret = sendto(listenSocket, packet.getData(), packet.getSize(), 0,
+ packet.getAddress().getAddress(), sizeof(*packet.getAddress().getAddress()));
+ if (ret == SOCKET_ERROR && errno != ECONNREFUSED) {
+ setInetError("Can't send UDP packet");
+ return -1;
+ }
+ return 0;
+}
+
+
+int UdpSocket::send(const std::vector<uint8_t>& data, InetAddress destination)
+{
+ int ret = sendto(listenSocket, &data[0], data.size(), 0,
+ destination.getAddress(), sizeof(*destination.getAddress()));
+ if (ret == SOCKET_ERROR && errno != ECONNREFUSED) {
+ setInetError("Can't send UDP packet");
+ return -1;
+ }
+ return 0;
+}
+
+
+/**
+ * Must be called to receive data on a multicast address.
+ * @param groupname The multica
+st address to join.
+ * @return 0 if ok, -1 if error
+ */
+int UdpSocket::joinGroup(char* groupname)
+{
+ ip_mreqn group;
+ if ((group.imr_multiaddr.s_addr = inet_addr(groupname)) == INADDR_NONE) {
+ setInetError(groupname);
+ return -1;
+ }
+ if (!IN_MULTICAST(ntohl(group.imr_multiaddr.s_addr))) {
+ setInetError("Not a multicast address");
+ return -1;
+ }
+ group.imr_address.s_addr = htons(INADDR_ANY);;
+ group.imr_ifindex = 0;
+ if (setsockopt(listenSocket, IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, sizeof(group))
+ == SOCKET_ERROR) {
+ setInetError("Can't join multicast group");
+ }
+ return 0;
+}
+
+int UdpSocket::setMulticastTTL(int ttl)
+{
+ if (setsockopt(listenSocket, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl))
+ == SOCKET_ERROR) {
+ setInetError("Can't set ttl");
+ return -1;
+ }
+
+ return 0;
+}
+
+int UdpSocket::setMulticastSource(const char* source_addr)
+{
+ struct in_addr addr;
+ if (inet_aton(source_addr, &addr) == 0) {
+ setInetError("Can't parse source address");
+ return -1;
+ }
+
+ if (setsockopt(listenSocket, IPPROTO_IP, IP_MULTICAST_IF, &addr, sizeof(addr))
+ == SOCKET_ERROR) {
+ setInetError("Can't set source address");
+ return -1;
+ }
+
+ return 0;
+}
+
+UdpPacket::UdpPacket() { }
+
+UdpPacket::UdpPacket(size_t initSize) :
+ m_buffer(initSize)
+{ }
+
+
+void UdpPacket::setSize(size_t newSize)
+{
+ m_buffer.resize(newSize);
+}
+
+
+uint8_t* UdpPacket::getData()
+{
+ return &m_buffer[0];
+}
+
+
+void UdpPacket::addData(const void *data, size_t size)
+{
+ uint8_t *d = (uint8_t*)data;
+ std::copy(d, d + size, std::back_inserter(m_buffer));
+}
+
+size_t UdpPacket::getSize()
+{
+ return m_buffer.size();
+}
+
+InetAddress UdpPacket::getAddress()
+{
+ return address;
+}
+
diff --git a/lib/UdpSocket.h b/lib/UdpSocket.h
new file mode 100644
index 0000000..f62db1d
--- /dev/null
+++ b/lib/UdpSocket.h
@@ -0,0 +1,180 @@
+/*
+ Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009 Her Majesty the
+ Queen in Right of Canada (Communications Research Center Canada)
+
+ Copyright (C) 2016
+ Matthias P. Braendli, matthias.braendli@mpb.li
+
+ http://www.opendigitalradio.org
+ */
+/*
+ This file is part of ODR-DabMux.
+
+ ODR-DabMux is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the
+ License, or (at your option) any later version.
+
+ ODR-DabMux is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with ODR-DabMux. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _UDPSOCKET
+#define _UDPSOCKET
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "InetAddress.h"
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <arpa/inet.h>
+#include <pthread.h>
+#define SOCKET int
+#define INVALID_SOCKET -1
+#define SOCKET_ERROR -1
+#define reuseopt_t int
+
+#include <stdlib.h>
+#include <iostream>
+#include <vector>
+
+class UdpPacket;
+
+
+/**
+ * This class represents a socket for sending and receiving UDP packets.
+ *
+ * A UDP socket is the sending or receiving point for a packet delivery service.
+ * Each packet sent or received on a datagram socket is individually
+ * addressed and routed. Multiple packets sent from one machine to another may
+ * be routed differently, and may arrive in any order.
+ */
+class UdpSocket
+{
+ public:
+ /** Create a new socket that will not be bound to any port. To be used
+ * for data output.
+ */
+ UdpSocket();
+ /** Create a new socket.
+ * @param port The port number on which the socket will be bound
+ */
+ UdpSocket(int port);
+ /** Create a new socket.
+ * @param port The port number on which the socket will be bound
+ * @param name The IP address on which the socket will be bound.
+ * It is used to bind the socket on a specific interface if
+ * the computer have many NICs.
+ */
+ UdpSocket(int port, const std::string& name);
+ ~UdpSocket();
+ UdpSocket(const UdpSocket& other) = delete;
+ const UdpSocket& operator=(const UdpSocket& other) = delete;
+
+ /** reinitialise socket. Close the already open socket, and
+ * create a new one
+ */
+ int reinit(int port, const std::string& name);
+
+ /** Close the socket
+ */
+ int close(void);
+
+ /** Send an UDP packet.
+ * @param packet The UDP packet to be sent. It includes the data and the
+ * destination address
+ * return 0 if ok, -1 if error
+ */
+ int send(UdpPacket& packet);
+
+ /** Send an UDP packet
+ *
+ * return 0 if ok, -1 if error
+ */
+ int send(const std::vector<uint8_t>& data, InetAddress destination);
+
+ /** Receive an UDP packet.
+ * @param packet The packet that will receive the data. The address will be set
+ * to the source address.
+ * @return 0 if ok, -1 if error
+ */
+ int receive(UdpPacket& packet);
+
+ int joinGroup(char* groupname);
+ int setMulticastSource(const char* source_addr);
+ int setMulticastTTL(int ttl);
+
+ /** Set blocking mode. By default, the socket is blocking.
+ * @return 0 if ok
+ * -1 if error
+ */
+ int setBlocking(bool block);
+
+ protected:
+
+ /// The address on which the socket is bound.
+ InetAddress address;
+ /// The low-level socket used by system functions.
+ SOCKET listenSocket;
+};
+
+/** This class represents a UDP packet.
+ *
+ * A UDP packet contains a payload (sequence of bytes) and an address. For
+ * outgoing packets, the address is the destination address. For incoming
+ * packets, the address tells the user from what source the packet arrived from.
+ */
+class UdpPacket
+{
+ public:
+ /** Construct an empty UDP packet.
+ */
+ UdpPacket();
+ UdpPacket(size_t initSize);
+ UdpPacket(const UdpPacket& packet) = delete;
+ const UdpPacket& operator=(const UdpPacket&) = delete;
+ UdpPacket(const UdpPacket&& packet) = delete;
+ const UdpPacket& operator=(const UdpPacket&&) = delete;
+
+ /** Give the pointer to data.
+ * @return The pointer
+ */
+ uint8_t* getData(void);
+
+ /** Append some data at the end of data buffer and adjust size.
+ * @param data Pointer to the data to add
+ * @param size Size in bytes of new data
+ */
+ void addData(const void *data, size_t size);
+
+ size_t getSize(void);
+
+ /** Changes size of the data buffer size. Keeps data intact unless
+ * truncated.
+ */
+ void setSize(size_t newSize);
+
+ /** Returns the UDP address of the packet.
+ */
+ InetAddress getAddress(void);
+
+ const std::vector<uint8_t>& getBuffer(void) const {
+ return m_buffer;
+ }
+
+
+ private:
+ std::vector<uint8_t> m_buffer;
+ InetAddress address;
+};
+
+#endif // _UDPSOCKET
diff --git a/lib/crc.c b/lib/crc.c
new file mode 100644
index 0000000..0c70263
--- /dev/null
+++ b/lib/crc.c
@@ -0,0 +1,267 @@
+/*
+ Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009 Her Majesty the
+ Queen in Right of Canada (Communications Research Center Canada)
+ */
+/*
+ This file is part of ODR-DabMux.
+
+ ODR-DabMux is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the
+ License, or (at your option) any later version.
+
+ ODR-DabMux is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with ODR-DabMux. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "crc.h"
+#ifndef _WIN32
+# include <unistd.h>
+# include <netinet/in.h>
+#endif
+#include <stdio.h>
+#include <fcntl.h>
+
+//#define CCITT 0x1021
+
+uint8_t crc8tab[256] = {
+ 0x00, 0x07, 0x0e, 0x09, 0x1c, 0x1b, 0x12, 0x15,
+ 0x38, 0x3f, 0x36, 0x31, 0x24, 0x23, 0x2a, 0x2d,
+ 0x70, 0x77, 0x7e, 0x79, 0x6c, 0x6b, 0x62, 0x65,
+ 0x48, 0x4f, 0x46, 0x41, 0x54, 0x53, 0x5a, 0x5d,
+ 0xe0, 0xe7, 0xee, 0xe9, 0xfc, 0xfb, 0xf2, 0xf5,
+ 0xd8, 0xdf, 0xd6, 0xd1, 0xc4, 0xc3, 0xca, 0xcd,
+ 0x90, 0x97, 0x9e, 0x99, 0x8c, 0x8b, 0x82, 0x85,
+ 0xa8, 0xaf, 0xa6, 0xa1, 0xb4, 0xb3, 0xba, 0xbd,
+ 0xc7, 0xc0, 0xc9, 0xce, 0xdb, 0xdc, 0xd5, 0xd2,
+ 0xff, 0xf8, 0xf1, 0xf6, 0xe3, 0xe4, 0xed, 0xea,
+ 0xb7, 0xb0, 0xb9, 0xbe, 0xab, 0xac, 0xa5, 0xa2,
+ 0x8f, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9d, 0x9a,
+ 0x27, 0x20, 0x29, 0x2e, 0x3b, 0x3c, 0x35, 0x32,
+ 0x1f, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0d, 0x0a,
+ 0x57, 0x50, 0x59, 0x5e, 0x4b, 0x4c, 0x45, 0x42,
+ 0x6f, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7d, 0x7a,
+ 0x89, 0x8e, 0x87, 0x80, 0x95, 0x92, 0x9b, 0x9c,
+ 0xb1, 0xb6, 0xbf, 0xb8, 0xad, 0xaa, 0xa3, 0xa4,
+ 0xf9, 0xfe, 0xf7, 0xf0, 0xe5, 0xe2, 0xeb, 0xec,
+ 0xc1, 0xc6, 0xcf, 0xc8, 0xdd, 0xda, 0xd3, 0xd4,
+ 0x69, 0x6e, 0x67, 0x60, 0x75, 0x72, 0x7b, 0x7c,
+ 0x51, 0x56, 0x5f, 0x58, 0x4d, 0x4a, 0x43, 0x44,
+ 0x19, 0x1e, 0x17, 0x10, 0x05, 0x02, 0x0b, 0x0c,
+ 0x21, 0x26, 0x2f, 0x28, 0x3d, 0x3a, 0x33, 0x34,
+ 0x4e, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5c, 0x5b,
+ 0x76, 0x71, 0x78, 0x7f, 0x6a, 0x6d, 0x64, 0x63,
+ 0x3e, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2c, 0x2b,
+ 0x06, 0x01, 0x08, 0x0f, 0x1a, 0x1d, 0x14, 0x13,
+ 0xae, 0xa9, 0xa0, 0xa7, 0xb2, 0xb5, 0xbc, 0xbb,
+ 0x96, 0x91, 0x98, 0x9f, 0x8a, 0x8d, 0x84, 0x83,
+ 0xde, 0xd9, 0xd0, 0xd7, 0xc2, 0xc5, 0xcc, 0xcb,
+ 0xe6, 0xe1, 0xe8, 0xef, 0xfa, 0xfd, 0xf4, 0xf3
+};
+
+
+uint16_t crc16tab[256] = {
+ 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,
+ 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef,
+ 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6,
+ 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de,
+ 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485,
+ 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d,
+ 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4,
+ 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc,
+ 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823,
+ 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b,
+ 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12,
+ 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a,
+ 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41,
+ 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49,
+ 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70,
+ 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78,
+ 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f,
+ 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067,
+ 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e,
+ 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256,
+ 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d,
+ 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
+ 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c,
+ 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634,
+ 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab,
+ 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3,
+ 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a,
+ 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92,
+ 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9,
+ 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1,
+ 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8,
+ 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0
+};
+
+
+uint32_t crc32tab[256] = {
+ 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9,
+ 0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005,
+ 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61,
+ 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd,
+ 0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9,
+ 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75,
+ 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011,
+ 0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd,
+ 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039,
+ 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5,
+ 0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81,
+ 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d,
+ 0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49,
+ 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95,
+ 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1,
+ 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d,
+ 0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae,
+ 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072,
+ 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16,
+ 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca,
+ 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde,
+ 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02,
+ 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066,
+ 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,
+ 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e,
+ 0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692,
+ 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6,
+ 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a,
+ 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e,
+ 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2,
+ 0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686,
+ 0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a,
+ 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637,
+ 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb,
+ 0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f,
+ 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53,
+ 0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47,
+ 0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b,
+ 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff,
+ 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623,
+ 0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7,
+ 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b,
+ 0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f,
+ 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3,
+ 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7,
+ 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b,
+ 0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f,
+ 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3,
+ 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640,
+ 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c,
+ 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8,
+ 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24,
+ 0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30,
+ 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec,
+ 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088,
+ 0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654,
+ 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0,
+ 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c,
+ 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18,
+ 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4,
+ 0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0,
+ 0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c,
+ 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668,
+ 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4
+};
+
+// This function can be used to create a new table with a different polynom
+void init_crc8tab(uint8_t l_code, uint8_t l_init)
+{
+ unsigned i, j, msb;
+ uint8_t nb;
+ uint8_t crc;
+
+ for (i = 0; i < 256; ++i) {
+ crc = l_init;
+ nb = i ^ 0xff;
+ for (j = 0; j < 8; ++j) {
+ msb = (nb & (0x80 >> j)) && 1;
+ msb ^= (crc >> 7);
+ crc <<= 1;
+ if (msb)
+ crc ^= l_code;
+ }
+ crc8tab[i] = crc;
+ }
+}
+
+
+void init_crc16tab(uint16_t l_code, uint16_t l_init)
+{
+ unsigned i, j, msb;
+ uint8_t nb;
+ uint16_t crc;
+
+ for (i = 0; i < 256; ++i) {
+ crc = l_init;
+ nb = i ^ 0xff;
+ for (j = 0; j < 8; ++j) {
+ msb = (nb & (0x80 >> j)) && 1;
+ msb ^= (crc >> 15);
+ crc <<= 1;
+ if (msb)
+ crc ^= l_code;
+ }
+ crc ^= 0xff00;
+ crc16tab[i] = crc;
+ }
+}
+
+
+void init_crc32tab(uint32_t l_code, uint32_t l_init)
+{
+ unsigned i, j, msb;
+ uint8_t nb;
+ uint32_t crc;
+
+ for (i = 0; i < 256; ++i) {
+ crc = l_init;
+ nb = i ^ 0xff;
+ for (j = 0; j < 8; ++j) {
+ msb = (nb & (0x80 >> j)) && 1;
+ msb ^= (crc >> 31);
+ crc <<= 1;
+ if (msb)
+ crc ^= l_code;
+ }
+ crc ^= 0xffffff00;
+ crc32tab[i] = crc;
+ }
+}
+
+
+uint8_t crc8(uint8_t l_crc, const void *lp_data, unsigned l_nb)
+{
+ const uint8_t* data = (const uint8_t*)lp_data;
+ while (l_nb--) {
+ l_crc = crc8tab[l_crc ^ *(data++)];
+ }
+ return (l_crc);
+}
+
+
+uint16_t crc16(uint16_t l_crc, const void *lp_data, unsigned l_nb)
+{
+ const uint8_t* data = (const uint8_t*)lp_data;
+ while (l_nb--) {
+ //fprintf(stdout, "crc 0x%02x 0x%04x\n", *data, l_crc);
+ l_crc =
+ (l_crc << 8) ^ crc16tab[(l_crc >> 8) ^ *(data++)];
+ }
+ return (l_crc);
+}
+
+
+uint32_t crc32(uint32_t l_crc, const void *lp_data, unsigned l_nb)
+{
+ const uint8_t* data = (const uint8_t*)lp_data;
+ while (l_nb--) {
+ l_crc =
+ (l_crc << 8) ^ crc32tab[((l_crc >> 24) ^ *(data++)) & 0xff];
+ }
+ return (l_crc);
+}
diff --git a/lib/crc.h b/lib/crc.h
new file mode 100644
index 0000000..b1785a1
--- /dev/null
+++ b/lib/crc.h
@@ -0,0 +1,59 @@
+/*
+ Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008, 2009 Her Majesty the
+ Queen in Right of Canada (Communications Research Center Canada)
+ */
+/*
+ This file is part of ODR-DabMux.
+
+ ODR-DabMux is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the
+ License, or (at your option) any later version.
+
+ ODR-DabMux is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with ODR-DabMux. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _CRC
+#define _CRC
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#ifndef _WIN32
+ #include <stdint.h>
+#else
+ #include <winsock2.h> // For types...
+ typedef BYTE uint8_t;
+ typedef WORD uint16_t;
+ typedef DWORD32 uint32_t;
+#endif
+
+
+#ifdef __cplusplus
+extern "C" { // }
+#endif
+
+void init_crc8tab(uint8_t l_code, uint8_t l_init);
+uint8_t crc8(uint8_t l_crc, const void *lp_data, unsigned l_nb);
+extern uint8_t crc8tab[];
+
+void init_crc16tab(uint16_t l_code, uint16_t l_init);
+uint16_t crc16(uint16_t l_crc, const void *lp_data, unsigned l_nb);
+extern uint16_t crc16tab[];
+
+void init_crc32tab(uint32_t l_code, uint32_t l_init);
+uint32_t crc32(uint32_t l_crc, const void *lp_data, unsigned l_nb);
+extern uint32_t crc32tab[];
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif //_CRC
diff --git a/lib/edi/ETIDecoder.cpp b/lib/edi/ETIDecoder.cpp
new file mode 100644
index 0000000..36e49fa
--- /dev/null
+++ b/lib/edi/ETIDecoder.cpp
@@ -0,0 +1,388 @@
+/*
+ Copyright (C) 2016
+ Matthias P. Braendli, matthias.braendli@mpb.li
+
+ http://opendigitalradio.org
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include "ETIDecoder.hpp"
+#include "buffer_unpack.hpp"
+#include "crc.h"
+#include "Log.h"
+#include <stdio.h>
+#include <cassert>
+#include <sstream>
+
+namespace EdiDecoder {
+
+using namespace std;
+
+ETIDecoder::ETIDecoder(ETIWriter& eti_writer) :
+ m_eti_writer(eti_writer)
+{
+}
+
+void ETIDecoder::push_bytes(const vector<uint8_t> &buf)
+{
+ copy(buf.begin(), buf.end(), back_inserter(m_input_data));
+
+ while (m_input_data.size() > 2) {
+ if (m_input_data[0] == 'A' and m_input_data[1] == 'F') {
+ decode_state_t st = decode_afpacket(m_input_data);
+
+ if (st.num_bytes_consumed == 0 and not st.complete) {
+ // We need to refill our buffer
+ break;
+ }
+
+ if (st.num_bytes_consumed) {
+ vector<uint8_t> remaining_data;
+ copy(m_input_data.begin() + st.num_bytes_consumed,
+ m_input_data.end(),
+ back_inserter(remaining_data));
+ m_input_data = remaining_data;
+ }
+
+ if (st.complete) {
+ m_eti_writer.assemble();
+ }
+
+ }
+ else if (m_input_data[0] == 'P' and m_input_data[1] == 'F') {
+ PFT::Fragment fragment;
+ const size_t fragment_bytes = fragment.loadData(m_input_data);
+
+ if (fragment_bytes == 0) {
+ // We need to refill our buffer
+ break;
+ }
+
+ vector<uint8_t> remaining_data;
+ copy(m_input_data.begin() + fragment_bytes,
+ m_input_data.end(),
+ back_inserter(remaining_data));
+ m_input_data = remaining_data;
+
+ if (fragment.isValid()) {
+ m_pft.pushPFTFrag(fragment);
+ }
+
+ auto af = m_pft.getNextAFPacket();
+ if (not af.empty()) {
+ decode_state_t st = decode_afpacket(af);
+
+ if (st.complete) {
+ m_eti_writer.assemble();
+ }
+ }
+
+ }
+ else {
+ etiLog.log(warn,"Unknown %c!", *m_input_data.data());
+ m_input_data.erase(m_input_data.begin());
+ }
+ }
+}
+
+void ETIDecoder::push_packet(const vector<uint8_t> &buf)
+{
+ if (buf.size() < 2) {
+ throw std::invalid_argument("Not enough bytes to read EDI packet header");
+ }
+
+ if (buf[0] == 'A' and buf[1] == 'F') {
+ const decode_state_t st = decode_afpacket(buf);
+
+ if (st.complete) {
+ m_eti_writer.assemble();
+ }
+
+ }
+ else if (buf[0] == 'P' and buf[1] == 'F') {
+ PFT::Fragment fragment;
+ fragment.loadData(buf);
+
+ if (fragment.isValid()) {
+ m_pft.pushPFTFrag(fragment);
+ }
+
+ auto af = m_pft.getNextAFPacket();
+ if (not af.empty()) {
+ decode_state_t st = decode_afpacket(af);
+
+ if (st.complete) {
+ m_eti_writer.assemble();
+ }
+ }
+ }
+ else {
+ const char packettype[3] = {(char)buf[0], (char)buf[1], '\0'};
+ std::stringstream ss;
+ ss << "Unknown EDI packet ";
+ ss << packettype;
+ throw std::invalid_argument(ss.str());
+ }
+}
+
+void ETIDecoder::setMaxDelay(int num_af_packets)
+{
+ m_pft.setMaxDelay(num_af_packets);
+}
+
+#define AFPACKET_HEADER_LEN 10 // includes SYNC
+
+decode_state_t ETIDecoder::decode_afpacket(
+ const std::vector<uint8_t> &input_data)
+{
+ if (input_data.size() < AFPACKET_HEADER_LEN) {
+ return {false, 0};
+ }
+
+ // read length from packet
+ uint32_t taglength = read_32b(input_data.begin() + 2);
+ uint16_t seq = read_16b(input_data.begin() + 6);
+
+ bool has_crc = (input_data[8] & 0x80) ? true : false;
+ uint8_t revision = input_data[8] & 0x7F;
+ uint8_t pt = input_data[9];
+ if (pt != 'T') {
+ // only support Tag
+ return {false, 0};
+ }
+
+ const size_t crclength = 2;
+ if (input_data.size() < AFPACKET_HEADER_LEN + taglength + crclength) {
+ return {false, 0};
+ }
+
+ if (not has_crc) {
+ throw invalid_argument("AF packet not supported, has no CRC");
+ }
+
+ uint16_t crc = 0xffff;
+ for (size_t i = 0; i < AFPACKET_HEADER_LEN + taglength; i++) {
+ crc = crc16(crc, &input_data[i], 1);
+ }
+ crc ^= 0xffff;
+
+ uint16_t packet_crc = read_16b(input_data.begin() + AFPACKET_HEADER_LEN + taglength);
+
+ if (packet_crc != crc) {
+ throw invalid_argument(
+ "AF Packet crc wrong");
+ }
+ else {
+ vector<uint8_t> payload(taglength);
+ copy(input_data.begin() + AFPACKET_HEADER_LEN,
+ input_data.begin() + AFPACKET_HEADER_LEN + taglength,
+ payload.begin());
+
+ return {decode_tagpacket(payload),
+ AFPACKET_HEADER_LEN + taglength + 2};
+ }
+}
+
+bool ETIDecoder::decode_tagpacket(const vector<uint8_t> &payload)
+{
+ size_t length = 0;
+
+ bool success = true;
+
+ for (size_t i = 0; i + 8 < payload.size(); i += 8 + length) {
+ char tag_sz[5];
+ tag_sz[4] = '\0';
+ copy(payload.begin() + i, payload.begin() + i + 4, tag_sz);
+
+ string tag(tag_sz);
+
+ uint32_t taglength = read_32b(payload.begin() + i + 4);
+
+ if (taglength % 8 != 0) {
+ etiLog.log(warn, "Invalid tag length!");
+ break;
+ }
+ taglength /= 8;
+
+ length = taglength;
+
+ vector<uint8_t> tag_value(taglength);
+ copy( payload.begin() + i+8,
+ payload.begin() + i+8+taglength,
+ tag_value.begin());
+
+ bool tagsuccess = false;
+ if (tag == "*ptr") {
+ tagsuccess = decode_starptr(tag_value);
+ }
+ else if (tag == "deti") {
+ tagsuccess = decode_deti(tag_value);
+ }
+ else if (tag.substr(0, 3) == "est") {
+ uint8_t n = tag_sz[3];
+ tagsuccess = decode_estn(tag_value, n);
+ }
+ else if (tag == "*dmy") {
+ tagsuccess = decode_stardmy(tag_value);
+ }
+ else {
+ etiLog.log(warn, "Unknown TAG %s", tag.c_str());
+ break;
+ }
+
+ if (not tagsuccess) {
+ etiLog.log(warn, "Error decoding TAG %s", tag.c_str());
+ success = tagsuccess;
+ break;
+ }
+ }
+
+ return success;
+}
+
+bool ETIDecoder::decode_starptr(const vector<uint8_t> &value)
+{
+ if (value.size() != 0x40 / 8) {
+ etiLog.log(warn, "Incorrect length %02lx for *PTR", value.size());
+ return false;
+ }
+
+ char protocol_sz[5];
+ protocol_sz[4] = '\0';
+ copy(value.begin(), value.begin() + 4, protocol_sz);
+ string protocol(protocol_sz);
+
+ uint16_t major = read_16b(value.begin() + 4);
+ uint16_t minor = read_16b(value.begin() + 6);
+
+ m_eti_writer.update_protocol(protocol, major, minor);
+
+ return true;
+}
+
+bool ETIDecoder::decode_deti(const vector<uint8_t> &value)
+{
+ /*
+ uint16_t detiHeader = fct | (fcth << 8) | (rfudf << 13) | (ficf << 14) | (atstf << 15);
+ packet.push_back(detiHeader >> 8);
+ packet.push_back(detiHeader & 0xFF);
+ */
+
+ uint16_t detiHeader = read_16b(value.begin());
+
+ eti_fc_data fc;
+
+ fc.atstf = (detiHeader >> 15) & 0x1;
+ fc.ficf = (detiHeader >> 14) & 0x1;
+ bool rfudf = (detiHeader >> 13) & 0x1;
+ uint8_t fcth = (detiHeader >> 8) & 0x1F;
+ uint8_t fct = detiHeader & 0xFF;
+
+ fc.dflc = fcth * 250 + fct; // modulo 5000 counter
+
+ uint32_t etiHeader = read_32b(value.begin() + 2);
+
+ uint8_t stat = (etiHeader >> 24) & 0xFF;
+
+ fc.mid = (etiHeader >> 22) & 0x03;
+ fc.fp = (etiHeader >> 19) & 0x07;
+ bool rfa = (etiHeader >> 17) & 0x1;
+ bool rfu = (etiHeader >> 16) & 0x1;
+ uint16_t mnsc = etiHeader & 0xFFFF;
+
+ const size_t fic_length_words = (fc.ficf ? (fc.mid == 3 ? 32 : 24) : 0);
+ const size_t fic_length = 4 * fic_length_words;
+
+ const size_t expected_length = 2 + 4 +
+ (fc.atstf ? 1 + 4 + 3 : 0) +
+ fic_length +
+ (rfudf ? 3 : 0);
+
+ if (value.size() != expected_length) {
+ etiLog.log(warn, " Assertion error: value.size() != expected_length: %zu %zu\n",
+ value.size(), expected_length);
+ assert(false);
+ }
+
+ m_eti_writer.update_err(stat);
+ m_eti_writer.update_mnsc(mnsc);
+
+ size_t i = 2 + 4;
+
+ if (fc.atstf) {
+ uint8_t utco = value[i];
+ i++;
+
+ uint32_t seconds = read_32b(value.begin() + i);
+ i += 4;
+
+ m_eti_writer.update_edi_time(utco, seconds);
+
+ fc.tsta = read_24b(value.begin() + i);
+ i += 3;
+ }
+ else {
+ // Null timestamp, ETSI ETS 300 799, C.2.2
+ fc.tsta = 0xFFFFFF;
+ }
+
+
+ if (fc.ficf) {
+ vector<uint8_t> fic(fic_length);
+ copy( value.begin() + i,
+ value.begin() + i + fic_length,
+ fic.begin());
+ i += fic_length;
+
+ m_eti_writer.update_fic(fic);
+ }
+
+ if (rfudf) {
+ uint32_t rfud = read_24b(value.begin() + i);
+
+ i += 3;
+ }
+
+ m_eti_writer.update_fc_data(fc);
+
+ return true;
+}
+
+bool ETIDecoder::decode_estn(const vector<uint8_t> &value, uint8_t n)
+{
+ uint32_t sstc = read_24b(value.begin());
+
+ eti_stc_data stc;
+
+ stc.scid = (sstc >> 18) & 0x3F;
+ stc.sad = (sstc >> 8) & 0x3FF;
+ stc.tpl = (sstc >> 2) & 0x3F;
+ uint8_t rfa = sstc & 0x3;
+
+ copy( value.begin() + 3,
+ value.end(),
+ back_inserter(stc.mst));
+
+ m_eti_writer.add_subchannel(stc);
+
+ return true;
+}
+
+bool ETIDecoder::decode_stardmy(const vector<uint8_t> &value)
+{
+ return true;
+}
+
+}
diff --git a/lib/edi/ETIDecoder.hpp b/lib/edi/ETIDecoder.hpp
new file mode 100644
index 0000000..e0c7218
--- /dev/null
+++ b/lib/edi/ETIDecoder.hpp
@@ -0,0 +1,74 @@
+/*
+ Copyright (C) 2016
+ Matthias P. Braendli, matthias.braendli@mpb.li
+
+ http://opendigitalradio.org
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#pragma once
+
+#include <stdint.h>
+#include <deque>
+#include <string>
+#include <vector>
+#include "ETIWriter.hpp"
+#include "PFT.hpp"
+
+namespace EdiDecoder {
+
+struct decode_state_t {
+ decode_state_t(bool _complete, size_t _num_bytes_consumed) :
+ complete(_complete), num_bytes_consumed(_num_bytes_consumed) {}
+ bool complete;
+ size_t num_bytes_consumed;
+};
+
+class ETIDecoder {
+ public:
+ ETIDecoder(ETIWriter& eti_writer);
+
+ /* Push bytes into the decoder. The buf can contain more
+ * than a single packet. This is useful when reading from streams
+ * (files, TCP)
+ */
+ void push_bytes(const std::vector<uint8_t> &buf);
+
+ /* Push a complete packet into the decoder. Useful for UDP and other
+ * datagram-oriented protocols.
+ */
+ void push_packet(const std::vector<uint8_t> &buf);
+
+ /* Set the maximum delay in number of AF Packets before we
+ * abandon decoding a given pseq.
+ */
+ void setMaxDelay(int num_af_packets);
+
+ private:
+ decode_state_t decode_afpacket(const std::vector<uint8_t> &input_data);
+ bool decode_tagpacket(const std::vector<uint8_t> &payload);
+ bool decode_starptr(const std::vector<uint8_t> &value);
+ bool decode_deti(const std::vector<uint8_t> &value);
+ bool decode_estn(const std::vector<uint8_t> &value, uint8_t n);
+ bool decode_stardmy(const std::vector<uint8_t> &value);
+
+ ETIWriter& m_eti_writer;
+
+ PFT::PFT m_pft;
+
+ std::vector<uint8_t> m_input_data;
+};
+
+}
diff --git a/lib/edi/ETIWriter.cpp b/lib/edi/ETIWriter.cpp
new file mode 100644
index 0000000..0eb3feb
--- /dev/null
+++ b/lib/edi/ETIWriter.cpp
@@ -0,0 +1,277 @@
+/*
+ Copyright (C) 2016
+ Matthias P. Braendli, matthias.braendli@mpb.li
+
+ http://opendigitalradio.org
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include "ETIWriter.hpp"
+#include "crc.h"
+#include "Log.h"
+#include <stdio.h>
+#include <cassert>
+#include <stdexcept>
+#include <sstream>
+
+namespace EdiDecoder {
+
+using namespace std;
+
+void ETIWriter::update_protocol(
+ const std::string& proto,
+ uint16_t major,
+ uint16_t minor)
+{
+ m_proto_valid = (proto == "DETI" and major == 0 and minor == 0);
+
+ if (not m_proto_valid) {
+ throw std::invalid_argument("Wrong EDI protocol");
+ }
+}
+
+void ETIWriter::reinit()
+{
+ m_proto_valid = false;
+ m_fc_valid = false;
+ m_fic.clear();
+ m_etiFrame.clear();
+ m_subchannels.clear();
+}
+
+void ETIWriter::update_err(uint8_t err)
+{
+ if (not m_proto_valid) {
+ throw std::logic_error("Cannot update ERR before protocol");
+ }
+ m_err = err;
+}
+
+void ETIWriter::update_fc_data(const eti_fc_data& fc_data)
+{
+ if (not m_proto_valid) {
+ throw std::logic_error("Cannot update FC before protocol");
+ }
+
+ m_fc_valid = false;
+ m_fc = fc_data;
+
+ if (not m_fc.ficf) {
+ throw std::invalid_argument("FIC must be present");
+ }
+
+ if (m_fc.mid > 4) {
+ throw std::invalid_argument("Invalid MID");
+ }
+
+ if (m_fc.fp > 7) {
+ throw std::invalid_argument("Invalid FP");
+ }
+
+ m_fc_valid = true;
+}
+
+void ETIWriter::update_fic(const std::vector<uint8_t>& fic)
+{
+ if (not m_proto_valid) {
+ throw std::logic_error("Cannot update FIC before protocol");
+ }
+
+ m_fic = fic;
+}
+
+void ETIWriter::update_edi_time(
+ uint32_t utco,
+ uint32_t seconds)
+{
+ if (not m_proto_valid) {
+ throw std::logic_error("Cannot update time before protocol");
+ }
+
+ m_utco = utco;
+ m_seconds = seconds;
+
+ // TODO check validity
+ m_time_valid = true;
+
+}
+
+void ETIWriter::update_mnsc(uint16_t mnsc)
+{
+ if (not m_proto_valid) {
+ throw std::logic_error("Cannot update MNSC before protocol");
+ }
+
+ m_mnsc = mnsc;
+}
+
+void ETIWriter::add_subchannel(const eti_stc_data& stc)
+{
+ if (not m_proto_valid) {
+ throw std::logic_error("Cannot add subchannel before protocol");
+ }
+
+ m_subchannels.push_back(stc);
+
+ if (m_subchannels.size() > 64) {
+ throw std::invalid_argument("Too many subchannels");
+ }
+
+}
+
+void ETIWriter::assemble()
+{
+ if (not m_proto_valid) {
+ throw std::logic_error("Cannot assemble ETI before protocol");
+ }
+
+ if (not m_fc_valid) {
+ throw std::logic_error("Cannot assemble ETI without FC");
+ }
+
+ if (m_fic.empty()) {
+ throw std::logic_error("Cannot assemble ETI without FIC data");
+ }
+
+ // Accept zero subchannels, because of an edge-case that can happen
+ // during reconfiguration. See ETS 300 799 Clause 5.3.3
+
+ // TODO check time validity
+
+ // ETS 300 799 Clause 5.3.2, but we don't support not having
+ // a FIC
+ if ( (m_fc.mid == 3 and m_fic.size() != 32 * 4) or
+ (m_fc.mid != 3 and m_fic.size() != 24 * 4) ) {
+ stringstream ss;
+ ss << "Invalid FIC length " << m_fic.size() <<
+ " for MID " << m_fc.mid;
+ throw std::invalid_argument(ss.str());
+ }
+
+
+ std::vector<uint8_t> eti;
+ eti.reserve(6144);
+
+ eti.push_back(m_err);
+
+ // FSYNC
+ if (m_fc.fct() % 2 == 1) {
+ eti.push_back(0xf8);
+ eti.push_back(0xc5);
+ eti.push_back(0x49);
+ }
+ else {
+ eti.push_back(0x07);
+ eti.push_back(0x3a);
+ eti.push_back(0xb6);
+ }
+
+ // LIDATA
+ // FC
+ eti.push_back(m_fc.fct());
+
+ const uint8_t NST = m_subchannels.size();
+
+ eti.push_back((m_fc.ficf << 7) | NST);
+
+ // We need to pack:
+ // FP 3 bits
+ // MID 2 bits
+ // FL 11 bits
+
+ // FL: EN 300 799 5.3.6
+ uint16_t FL = NST + 1 + m_fic.size();
+ for (const auto& subch : m_subchannels) {
+ FL += subch.mst.size();
+ }
+
+ const uint16_t fp_mid_fl = (m_fc.fp << 13) | (m_fc.mid << 11) | FL;
+
+ eti.push_back(fp_mid_fl >> 8);
+ eti.push_back(fp_mid_fl & 0xFF);
+
+ // STC
+ for (const auto& subch : m_subchannels) {
+ eti.push_back( (subch.scid << 2) | (subch.sad & 0x300) );
+ eti.push_back( subch.sad & 0xff );
+ eti.push_back( (subch.tpl << 2) | ((subch.stl() & 0x300) >> 8) );
+ eti.push_back( subch.stl() & 0xff );
+ }
+
+ // EOH
+ // MNSC
+ eti.push_back(m_mnsc >> 8);
+ eti.push_back(m_mnsc & 0xFF);
+
+ // CRC
+ // Calculate CRC from eti[4] to current position
+ uint16_t eti_crc = 0xFFFF;
+ eti_crc = crc16(eti_crc, &eti[4], eti.size() - 4);
+ eti_crc ^= 0xffff;
+ eti.push_back(eti_crc >> 8);
+ eti.push_back(eti_crc & 0xFF);
+
+ const size_t mst_start = eti.size();
+ // MST
+ // FIC data
+ copy(m_fic.begin(), m_fic.end(), back_inserter(eti));
+
+ // Data stream
+ for (const auto& subch : m_subchannels) {
+ copy(subch.mst.begin(), subch.mst.end(), back_inserter(eti));
+ }
+
+ // EOF
+ // CRC
+ uint16_t mst_crc = 0xFFFF;
+ mst_crc = crc16(mst_crc, &eti[mst_start], eti.size() - mst_start);
+ mst_crc ^= 0xffff;
+ eti.push_back(mst_crc >> 8);
+ eti.push_back(mst_crc & 0xFF);
+
+ // RFU
+ eti.push_back(0xff);
+ eti.push_back(0xff);
+
+ // TIST
+ eti.push_back(m_fc.tsta >> 24);
+ eti.push_back((m_fc.tsta >> 16) & 0xFF);
+ eti.push_back((m_fc.tsta >> 8) & 0xFF);
+ eti.push_back(m_fc.tsta & 0xFF);
+
+ if (eti.size() > 6144) {
+ throw std::logic_error("ETI frame cannot be longer than 6144");
+ }
+
+ eti.resize(6144, 0x55);
+
+ m_etiFrame = eti;
+
+}
+
+std::vector<uint8_t> ETIWriter::getEtiFrame()
+{
+ if (m_etiFrame.empty()) {
+ return {};
+ }
+
+ vector<uint8_t> eti(move(m_etiFrame));
+ reinit();
+
+ return eti;
+}
+
+}
+
diff --git a/lib/edi/ETIWriter.hpp b/lib/edi/ETIWriter.hpp
new file mode 100644
index 0000000..001f537
--- /dev/null
+++ b/lib/edi/ETIWriter.hpp
@@ -0,0 +1,114 @@
+/*
+ Copyright (C) 2016
+ Matthias P. Braendli, matthias.braendli@mpb.li
+
+ http://opendigitalradio.org
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#pragma once
+
+#include <stdint.h>
+#include <string>
+#include <vector>
+#include <list>
+#include "eti.hpp"
+
+namespace EdiDecoder {
+
+// Information for Frame Characterisation available in
+// EDI.
+//
+// Number of streams is given separately, and frame length
+// is calculeted in the ETIWriter
+struct eti_fc_data {
+ bool atstf;
+ uint32_t tsta;
+ bool ficf;
+ uint16_t dflc;
+ uint8_t mid;
+ uint8_t fp;
+
+ uint8_t fct(void) const { return dflc % 250; }
+};
+
+// Information for a subchannel available in EDI
+struct eti_stc_data {
+ uint8_t scid;
+ uint8_t sad;
+ uint8_t tpl;
+ std::vector<uint8_t> mst;
+
+ // Return the length of the MST in multiples of 64 bits
+ uint16_t stl(void) const { return mst.size() / 8; }
+};
+
+class ETIWriter {
+ public:
+ // Tell the ETIWriter what EDI protocol we receive in *ptr.
+ // This is not part of the ETI data, but is used as check
+ void update_protocol(
+ const std::string& proto,
+ uint16_t major,
+ uint16_t minor);
+
+ // Update the data for the frame characterisation
+ void update_fc_data(const eti_fc_data& fc_data);
+
+ void update_fic(const std::vector<uint8_t>& fic);
+
+ void update_err(uint8_t err);
+
+ // In addition to TSTA in ETI, EDI also transports more time
+ // stamp information.
+ void update_edi_time(
+ uint32_t utco,
+ uint32_t seconds);
+
+ void update_mnsc(uint16_t mnsc);
+
+ void add_subchannel(const eti_stc_data& stc);
+
+ // Tell the ETIWriter that the AFPacket is complete
+ void assemble(void);
+
+ // Return the assembled ETI frame or an empty frame if not ready
+ std::vector<uint8_t> getEtiFrame(void);
+
+ private:
+ void reinit(void);
+
+ bool m_proto_valid = false;
+
+ uint8_t m_err;
+
+ bool m_fc_valid = false;
+ eti_fc_data m_fc;
+
+ // m_fic is valid if non-empty
+ std::vector<uint8_t> m_fic;
+
+ std::vector<uint8_t> m_etiFrame;
+
+ std::list<eti_stc_data> m_subchannels;
+
+ bool m_time_valid = false;
+ uint32_t m_utco;
+ uint32_t m_seconds;
+
+ uint16_t m_mnsc = 0xffff;
+};
+
+}
diff --git a/lib/edi/PFT.cpp b/lib/edi/PFT.cpp
new file mode 100644
index 0000000..fa5840c
--- /dev/null
+++ b/lib/edi/PFT.cpp
@@ -0,0 +1,553 @@
+/* ------------------------------------------------------------------
+ * Copyright (C) 2017 AVT GmbH - Fabien Vercasson
+ * Copyright (C) 2016 Matthias P. Braendli
+ * matthias.braendli@mpb.li
+ *
+ * http://opendigitalradio.org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ * -------------------------------------------------------------------
+ */
+
+#include <stdio.h>
+#include <cassert>
+#include <cstring>
+#include <sstream>
+#include <stdexcept>
+#include <algorithm>
+#include "crc.h"
+#include "PFT.hpp"
+#include "Log.h"
+#include "buffer_unpack.hpp"
+extern "C" {
+#include "fec/fec.h"
+}
+
+namespace EdiDecoder {
+
+namespace PFT {
+
+using namespace std;
+
+const findex_t NUM_AFBUILDERS_TO_KEEP = 10;
+
+static bool checkCRC(const uint8_t *buf, size_t size)
+{
+ const uint16_t crc_from_packet = read_16b(buf + size - 2);
+ uint16_t crc_calc = 0xffff;
+ crc_calc = crc16(crc_calc, buf, size - 2);
+ crc_calc ^= 0xffff;
+
+ return crc_from_packet == crc_calc;
+}
+
+class FECDecoder {
+ public:
+ FECDecoder() {
+ m_rs_handler = init_rs_char(
+ symsize, gfPoly, firstRoot, primElem, nroots, pad);
+ }
+ FECDecoder(const FECDecoder& other) = delete;
+ FECDecoder& operator=(const FECDecoder& other) = delete;
+ ~FECDecoder() {
+ free_rs_char(m_rs_handler);
+ }
+
+ // return -1 in case of failure, non-negative value if errors
+ // were corrected.
+ // Known positions of erasures should be given in eras_pos to
+ // improve decoding probability. After calling this function
+ // eras_pos will contain the positions of the corrected errors.
+ int decode(vector<uint8_t> &data, vector<int> &eras_pos) {
+ assert(data.size() == N);
+ const size_t no_eras = eras_pos.size();
+
+ eras_pos.resize(nroots);
+ int num_err = decode_rs_char(m_rs_handler, data.data(),
+ eras_pos.data(), no_eras);
+ if (num_err > 0) {
+ eras_pos.resize(num_err);
+ }
+ return num_err;
+ }
+
+ // return -1 in case of failure, non-negative value if errors
+ // were corrected. No known erasures.
+ int decode(vector<uint8_t> &data) {
+ assert(data.size() == N);
+ int num_err = decode_rs_char(m_rs_handler, data.data(), nullptr, 0);
+ return num_err;
+ }
+
+ private:
+ void* m_rs_handler;
+
+ const int firstRoot = 1; // Discovered by analysing EDI dump
+ const int gfPoly = 0x11d;
+
+ // The encoding has to be 255, 207 always, because the chunk has to
+ // be padded at the end, and not at the beginning as libfec would
+ // do
+ const size_t N = 255;
+ const size_t K = 207;
+ const int primElem = 1;
+ const int symsize = 8;
+ const size_t nroots = N - K; // For EDI PFT, this must be 48
+ const size_t pad = ((1 << symsize) - 1) - N; // is 255-N
+
+};
+
+size_t Fragment::loadData(const std::vector<uint8_t> &buf)
+{
+ const size_t header_len = 14;
+ if (buf.size() < header_len) {
+ return 0;
+ }
+
+ size_t index = 0;
+
+ // Parse PFT Fragment Header (ETSI TS 102 821 V1.4.1 ch7.1)
+ if (not (buf[0] == 'P' and buf[1] == 'F') ) {
+ throw invalid_argument("Invalid PFT SYNC bytes");
+ }
+ index += 2; // Psync
+
+ _Pseq = read_16b(buf.begin()+index); index += 2;
+ _Findex = read_24b(buf.begin()+index); index += 3;
+ _Fcount = read_24b(buf.begin()+index); index += 3;
+ _FEC = unpack1bit(buf[index], 0);
+ _Addr = unpack1bit(buf[index], 1);
+ _Plen = read_16b(buf.begin()+index) & 0x3FFF; index += 2;
+
+ const size_t required_len = header_len +
+ (_FEC ? 1 : 0) +
+ (_Addr ? 2 : 0) +
+ 2; // CRC
+ if (buf.size() < required_len) {
+ return 0;
+ }
+
+ // Optional RS Header
+ _RSk = 0;
+ _RSz = 0;
+ if (_FEC) {
+ _RSk = buf[index]; index += 1;
+ _RSz = buf[index]; index += 1;
+ }
+
+ // Optional transport header
+ _Source = 0;
+ _Dest = 0;
+ if (_Addr) {
+ _Source = read_16b(buf.begin()+index); index += 2;
+ _Dest = read_16b(buf.begin()+index); index += 2;
+ }
+
+ index += 2;
+ const bool crc_valid = checkCRC(buf.data(), index);
+ const bool buf_has_enough_data = (buf.size() >= index + _Plen);
+
+ if (not buf_has_enough_data) {
+ return 0;
+ }
+
+ _valid = ((not _FEC) or crc_valid) and buf_has_enough_data;
+
+#if 0
+ if (!_valid) {
+ stringstream ss;
+ ss << "Invalid PF fragment: ";
+ if (_FEC) {
+ ss << " RSk=" << (uint32_t)_RSk << " RSz=" << (uint32_t)_RSz;
+ }
+
+ if (_Addr) {
+ ss << " Source=" << _Source << " Dest=" << _Dest;
+ }
+ etiLog.log(debug, "%s\n", ss.str().c_str());
+ }
+#endif
+
+ _payload.clear();
+ if (_valid) {
+ copy( buf.begin()+index,
+ buf.begin()+index+_Plen,
+ back_inserter(_payload));
+ index += _Plen;
+ }
+
+ return index;
+}
+
+
+AFBuilder::AFBuilder(pseq_t Pseq, findex_t Fcount, int lifetime)
+{
+ _Pseq = Pseq;
+ _Fcount = Fcount;
+ lifeTime = lifetime;
+}
+
+void AFBuilder::pushPFTFrag(const Fragment &frag)
+{
+ if (_Pseq != frag.Pseq() or _Fcount != frag.Fcount()) {
+ throw invalid_argument("Invalid PFT fragment Pseq or Fcount");
+ }
+ const auto Findex = frag.Findex();
+ const bool fragment_already_received = _fragments.count(Findex);
+
+ if (not fragment_already_received)
+ {
+ _fragments[Findex] = frag;
+ }
+}
+
+bool Fragment::checkConsistency(const Fragment& other) const
+{
+ /* Consistency check, TS 102 821 Clause 7.3.2.
+ *
+ * Every PFT Fragment produced from a single AF or RS Packet shall have
+ * the same values in all of the PFT Header fields except for the Findex,
+ * Plen and HCRC fields.
+ */
+
+ return other._Fcount == _Fcount and
+ other._FEC == _FEC and
+ other._RSk == _RSk and
+ other._RSz == _RSz and
+ other._Addr == _Addr and
+ other._Source == _Source and
+ other._Dest == _Dest and
+
+ /* The Plen field of all fragments shall be the s for the initial f-1
+ * fragments and s - (L%f) for the final fragment.
+ * Note that when Reed Solomon has been used, all fragments will be of
+ * length s.
+ */
+ (_FEC ? other._Plen == _Plen : true);
+}
+
+
+AFBuilder::decode_attempt_result_t AFBuilder::canAttemptToDecode() const
+{
+ if (_fragments.empty()) {
+ return AFBuilder::decode_attempt_result_t::no;
+ }
+
+ if (_fragments.size() == _Fcount) {
+ return AFBuilder::decode_attempt_result_t::yes;
+ }
+
+ /* Check that all fragments are consistent */
+ const Fragment& first = _fragments.begin()->second;
+ if (not std::all_of(_fragments.begin(), _fragments.end(),
+ [&](const pair<int, Fragment>& pair) {
+ const Fragment& frag = pair.second;
+ return first.checkConsistency(frag) and _Pseq == frag.Pseq();
+ }) ) {
+ throw invalid_argument("Inconsistent PFT fragments");
+ }
+
+ // Calculate the minimum number of fragments necessary to apply FEC.
+ // This can't be done with the last fragment that may have a
+ // smaller size
+ // ETSI TS 102 821 V1.4.1 ch 7.4.4
+ auto frag_it = _fragments.begin();
+ if (frag_it->second.Fcount() == _Fcount - 1) {
+ frag_it++;
+
+ if (frag_it == _fragments.end()) {
+ return AFBuilder::decode_attempt_result_t::no;
+ }
+ }
+
+ const Fragment& frag = frag_it->second;
+
+ if ( frag.FEC() )
+ {
+ const uint16_t _Plen = frag.Plen();
+
+ /* max number of RS chunks that may have been sent */
+ const uint32_t _cmax = (_Fcount*_Plen) / (frag.RSk()+48);
+ assert(_cmax > 0);
+
+ /* Receiving _rxmin fragments does not guarantee that decoding
+ * will succeed! */
+ const uint32_t _rxmin = _Fcount - (_cmax*48)/_Plen;
+
+ if (_fragments.size() >= _rxmin) {
+ return AFBuilder::decode_attempt_result_t::maybe;
+ }
+ }
+
+ return AFBuilder::decode_attempt_result_t::no;
+}
+
+std::vector<uint8_t> AFBuilder::extractAF() const
+{
+ if (not _af_packet.empty()) {
+ return _af_packet;
+ }
+
+ bool ok = false;
+
+ if (canAttemptToDecode() != AFBuilder::decode_attempt_result_t::no) {
+
+ auto frag_it = _fragments.begin();
+ if (frag_it->second.Fcount() == _Fcount - 1) {
+ frag_it++;
+
+ if (frag_it == _fragments.end()) {
+ throw std::runtime_error("Invalid attempt at extracting AF");
+ }
+ }
+
+ const Fragment& ref_frag = frag_it->second;
+ const auto RSk = ref_frag.RSk();
+ const auto RSz = ref_frag.RSz();
+ const auto Plen = ref_frag.Plen();
+
+ if ( ref_frag.FEC() )
+ {
+ const uint32_t cmax = (_Fcount*Plen) / (RSk+48);
+
+ // Keep track of erasures (missing fragments) for
+ // every chunk
+ map<int, vector<int> > erasures;
+
+
+ // Assemble fragments into a RS block, immediately
+ // deinterleaving it.
+ vector<uint8_t> rs_block(Plen * _Fcount);
+ for (size_t j = 0; j < _Fcount; j++) {
+ const bool fragment_present = _fragments.count(j);
+ if (fragment_present) {
+ const auto& fragment = _fragments.at(j).payload();
+
+ if (j != _Fcount - 1 and fragment.size() != Plen) {
+ throw runtime_error("Incorrect fragment length " +
+ to_string(fragment.size()) + " " +
+ to_string(Plen));
+ }
+
+ if (j == _Fcount - 1 and fragment.size() > Plen) {
+ throw runtime_error("Incorrect last fragment length " +
+ to_string(fragment.size()) + " " +
+ to_string(Plen));
+ }
+
+ size_t k = 0;
+ for (; k < fragment.size(); k++) {
+ rs_block[k * _Fcount + j] = fragment[k];
+ }
+
+ for (; k < Plen; k++) {
+ rs_block[k * _Fcount + j] = 0x00;
+ }
+ }
+ else {
+ // fill with zeros if fragment is missing
+ for (size_t k = 0; k < Plen; k++) {
+ rs_block[k * _Fcount + j] = 0x00;
+
+ const size_t chunk_ix = (k * _Fcount + j) / (RSk + 48);
+ const size_t chunk_offset = (k * _Fcount + j) % (RSk + 48);
+ erasures[chunk_ix].push_back(chunk_offset);
+ }
+ }
+ }
+
+ // The RS block is a concatenation of chunks of RSk bytes + 48 parity
+ // followed by RSz padding
+
+ FECDecoder fec;
+ for (size_t i = 0; i < cmax; i++) {
+ // We need to pad the chunk ourself
+ vector<uint8_t> chunk(255);
+ const auto& block_begin = rs_block.begin() + (RSk + 48) * i;
+ copy(block_begin, block_begin + RSk, chunk.begin());
+ // bytes between RSk and 207 are 0x00 already
+ copy(block_begin + RSk, block_begin + RSk + 48,
+ chunk.begin() + 207);
+
+ int errors_corrected = -1;
+ if (erasures.count(i)) {
+ errors_corrected = fec.decode(chunk, erasures[i]);
+ }
+ else {
+ errors_corrected = fec.decode(chunk);
+ }
+
+ if (errors_corrected == -1) {
+ _af_packet.clear();
+ return {};
+ }
+
+#if 0
+ if (errors_corrected > 0) {
+ etiLog.log(debug, "Corrected %d errors at ", errors_corrected);
+ for (const auto &index : erasures[i]) {
+ etiLog.log(debug, " %d", index);
+ }
+ etiLog.log(debug, "\n");
+ }
+#endif
+
+ _af_packet.insert(_af_packet.end(), chunk.begin(), chunk.begin() + RSk);
+ }
+
+ _af_packet.resize(_af_packet.size() - RSz);
+ }
+ else {
+ // No FEC: just assemble fragments
+
+ for (size_t j = 0; j < _Fcount; ++j) {
+ const bool fragment_present = _fragments.count(j);
+ if (fragment_present)
+ {
+ const auto& fragment = _fragments.at(j);
+
+ _af_packet.insert(_af_packet.end(),
+ fragment.payload().begin(),
+ fragment.payload().end());
+ }
+ else {
+ throw logic_error("Missing fragment");
+ }
+ }
+ }
+
+ // EDI specific, must have a CRC.
+ if( _af_packet.size() >= 12 ) {
+ ok = checkCRC(_af_packet.data(), _af_packet.size());
+
+ if (not ok) {
+ etiLog.log(debug, "Too many errors to reconstruct AF from %zu/%u"
+ " PFT fragments\n", _fragments.size(), _Fcount);
+ }
+ }
+ }
+
+ if (not ok) {
+ _af_packet.clear();
+ }
+
+ return _af_packet;
+}
+
+void PFT::pushPFTFrag(const Fragment &fragment)
+{
+ // Start decoding the first pseq we receive. In normal
+ // operation without interruptions, the map should
+ // never become empty
+ if (m_afbuilders.empty()) {
+ m_next_pseq = fragment.Pseq();
+ etiLog.log(debug,"Initialise next_pseq to %u\n", m_next_pseq);
+ }
+
+ if (m_afbuilders.count(fragment.Pseq()) == 0) {
+ // The AFBuilder wants to know the lifetime in number of fragments,
+ // we know the delay in number of AF packets. Every AF packet
+ // is cut into Fcount fragments.
+ const int lifetime = fragment.Fcount() * m_max_delay;
+
+ // Build the afbuilder in the map in-place
+ m_afbuilders.emplace(std::piecewise_construct,
+ /* key */
+ std::forward_as_tuple(fragment.Pseq()),
+ /* builder */
+ std::forward_as_tuple(fragment.Pseq(), fragment.Fcount(), lifetime));
+ }
+
+ auto& p = m_afbuilders.at(fragment.Pseq());
+ p.pushPFTFrag(fragment);
+
+#if 0
+ etiLog.log(debug, "After new frag with pseq %u, afbuilders: ", fragment.Pseq());
+ for (const auto &k : m_afbuilders) {
+ etiLog.log(debug, "%u ", k.first);
+ }
+ etiLog.log(debug, "\n");
+#endif
+}
+
+
+std::vector<uint8_t> PFT::getNextAFPacket()
+{
+ if (m_afbuilders.count(m_next_pseq) == 0) {
+ assert(m_afbuilders.empty());
+ return {};
+ }
+
+ auto &builder = m_afbuilders.at(m_next_pseq);
+ //const auto lt = builder.lifeTime;
+ //const auto nf = builder.numberOfFragments();
+
+ using dar_t = AFBuilder::decode_attempt_result_t;
+
+ if (builder.canAttemptToDecode() == dar_t::yes) {
+ //etiLog.log(debug, "pseq %d (%d %d/%d) yes\n", m_next_pseq, lt, nf.first, nf.second);
+ auto afpacket = builder.extractAF();
+ assert(not afpacket.empty());
+ incrementNextPseq();
+ return afpacket;
+ }
+ else if (builder.canAttemptToDecode() == dar_t::maybe) {
+ //etiLog.log(debug, "pseq %d (%d %d/%d) maybe\n", m_next_pseq, lt, nf.first, nf.second);
+ builder.lifeTime--;
+ if (builder.lifeTime == 0) {
+ // Attempt Reed-Solomon decoding
+ auto afpacket = builder.extractAF();
+
+ if (afpacket.empty()) {
+ etiLog.log(debug,"pseq %d timed out after RS\n", m_next_pseq);
+ }
+ incrementNextPseq();
+ return afpacket;
+ }
+ }
+ else {
+ //etiLog.log(debug, "pseq %d (%d %d/%d) no\n", m_next_pseq, lt, nf.first, nf.second);
+ builder.lifeTime--;
+ if (builder.lifeTime == 0) {
+ etiLog.log(debug, "pseq %d timed out\n", m_next_pseq);
+ incrementNextPseq();
+ }
+ }
+
+ return {};
+}
+
+void PFT::setMaxDelay(int num_af_packets)
+{
+ m_max_delay = num_af_packets;
+}
+
+bool PFT::isPacketBuildable(pseq_t pseq) const
+{
+ return m_afbuilders.count(pseq) > 0 and
+ not m_afbuilders.at(pseq).extractAF().empty();
+}
+
+void PFT::incrementNextPseq()
+{
+ if (m_afbuilders.count(m_next_pseq - NUM_AFBUILDERS_TO_KEEP) > 0) {
+ m_afbuilders.erase(m_next_pseq - NUM_AFBUILDERS_TO_KEEP);
+ }
+
+ m_next_pseq++;
+}
+
+}
+
+}
diff --git a/lib/edi/PFT.hpp b/lib/edi/PFT.hpp
new file mode 100644
index 0000000..9ab14ae
--- /dev/null
+++ b/lib/edi/PFT.hpp
@@ -0,0 +1,154 @@
+/* ------------------------------------------------------------------
+ * Copyright (C) 2017 AVT GmbH - Fabien Vercasson
+ * Copyright (C) 2016 Matthias P. Braendli
+ * matthias.braendli@mpb.li
+ *
+ * http://opendigitalradio.org
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ * -------------------------------------------------------------------
+ */
+
+#pragma once
+#include <stdio.h>
+#include <cassert>
+#include <vector>
+#include <map>
+#include <stdint.h>
+
+namespace EdiDecoder {
+namespace PFT {
+
+using pseq_t = uint16_t;
+using findex_t = uint32_t; // findex is a 24-bit value
+
+class Fragment
+{
+ public:
+ // Load the data for one fragment from buf into
+ // the Fragment.
+ // \returns the number of bytes of useful data found in buf
+ // A non-zero return value doesn't imply a valid fragment
+ // the isValid() method must be used to verify this.
+ size_t loadData(const std::vector<uint8_t> &buf);
+
+ bool isValid() const { return _valid; }
+ pseq_t Pseq() const { return _Pseq; }
+ findex_t Findex() const { return _Findex; }
+ findex_t Fcount() const { return _Fcount; }
+ bool FEC() const { return _FEC; }
+ uint16_t Plen() const { return _Plen; }
+ uint8_t RSk() const { return _RSk; }
+ uint8_t RSz() const { return _RSz; }
+ const std::vector<uint8_t>& payload() const
+ { return _payload; }
+
+ bool checkConsistency(const Fragment& other) const;
+
+ private:
+ std::vector<uint8_t> _payload;
+
+ pseq_t _Pseq;
+ findex_t _Findex;
+ findex_t _Fcount;
+ bool _FEC;
+ bool _Addr;
+ uint16_t _Plen;
+ uint8_t _RSk;
+ uint8_t _RSz;
+ uint16_t _Source;
+ uint16_t _Dest;
+ bool _valid;
+};
+
+/* The AFBuilder collects Fragments and builds an Application Frame
+ * out of them. It does error correction if necessary
+ */
+class AFBuilder
+{
+ public:
+ enum class decode_attempt_result_t {
+ yes, // The AF packet can be build because all fragments are present
+ maybe, // RS decoding may correctly decode the AF packet
+ no, // Not enough fragments present to permit RS
+ };
+
+ AFBuilder(pseq_t Pseq, findex_t Fcount, int lifetime);
+
+ void pushPFTFrag(const Fragment &frag);
+
+ /* Assess if it may be possible to decode this AF packet */
+ decode_attempt_result_t canAttemptToDecode() const;
+
+ /* Try to build the AF with received fragments.
+ * Apply error correction if necessary (missing packets/CRC errors)
+ * \return an empty vector if building the AF is not possible
+ */
+ std::vector<uint8_t> extractAF(void) const;
+
+ std::pair<findex_t, findex_t>
+ numberOfFragments(void) const {
+ return {_fragments.size(), _Fcount};
+ }
+
+ /* The user of this instance can keep track of the lifetime of this
+ * builder
+ */
+ int lifeTime;
+
+ private:
+
+ // A map from fragment index to fragment
+ std::map<findex_t, Fragment> _fragments;
+
+ // cached version of decoded AF packet
+ mutable std::vector<uint8_t> _af_packet;
+
+ pseq_t _Pseq;
+ findex_t _Fcount;
+};
+
+class PFT
+{
+ public:
+ void pushPFTFrag(const Fragment &fragment);
+
+ /* Try to build the AF packet for the next pseq. This might
+ * skip one pseq according to the maximum delay setting.
+ *
+ * \return an empty vector if building the AF is not possible
+ */
+ std::vector<uint8_t> getNextAFPacket(void);
+
+ /* Set the maximum delay in number of AF Packets before we
+ * abandon decoding a given pseq.
+ */
+ void setMaxDelay(int num_af_packets);
+
+ private:
+ bool isPacketBuildable(pseq_t pseq) const;
+
+ void incrementNextPseq(void);
+
+ pseq_t m_next_pseq;
+ int m_max_delay = 10;
+
+ // Keep one AFBuilder for each Pseq
+ std::map<pseq_t, AFBuilder> m_afbuilders;
+
+};
+
+}
+
+}
diff --git a/lib/edi/README.md b/lib/edi/README.md
new file mode 100644
index 0000000..b6ab67a
--- /dev/null
+++ b/lib/edi/README.md
@@ -0,0 +1 @@
+These files are copied from the odr-edilib project.
diff --git a/lib/edi/buffer_unpack.hpp b/lib/edi/buffer_unpack.hpp
new file mode 100644
index 0000000..05a1534
--- /dev/null
+++ b/lib/edi/buffer_unpack.hpp
@@ -0,0 +1,62 @@
+/*
+ Copyright (C) 2016
+ Matthias P. Braendli, matthias.braendli@mpb.li
+
+ http://opendigitalradio.org
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#pragma once
+#include <stdint.h>
+
+namespace EdiDecoder {
+
+template<class T>
+uint16_t read_16b(T buf)
+{
+ uint16_t value = 0;
+ value = (uint16_t)(buf[0]) << 8;
+ value |= (uint16_t)(buf[1]);
+ return value;
+}
+
+template<class T>
+uint32_t read_24b(T buf)
+{
+ uint32_t value = 0;
+ value = (uint32_t)(buf[0]) << 16;
+ value |= (uint32_t)(buf[1]) << 8;
+ value |= (uint32_t)(buf[2]);
+ return value;
+}
+
+template<class T>
+uint32_t read_32b(T buf)
+{
+ uint32_t value = 0;
+ value = (uint32_t)(buf[0]) << 24;
+ value |= (uint32_t)(buf[1]) << 16;
+ value |= (uint32_t)(buf[2]) << 8;
+ value |= (uint32_t)(buf[3]);
+ return value;
+}
+
+inline uint32_t unpack1bit(uint8_t byte, int bitpos)
+{
+ return (byte & 1 << (7-bitpos)) > (7-bitpos);
+}
+
+}
diff --git a/lib/edi/eti.cpp b/lib/edi/eti.cpp
new file mode 100644
index 0000000..3fc99ef
--- /dev/null
+++ b/lib/edi/eti.cpp
@@ -0,0 +1,64 @@
+/*
+ Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the
+ Queen in Right of Canada (Communications Research Center Canada)
+
+ Copyright (C) 2016
+ Matthias P. Braendli, matthias.braendli@mpb.li
+
+ http://opendigitalradio.org
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "eti.hpp"
+
+namespace EdiDecoder {
+
+//definitions des structures des champs du ETI(NI, G703)
+
+uint16_t eti_FC::getFrameLength()
+{
+ return (uint16_t)((FL_high << 8) | FL_low);
+}
+
+void eti_FC::setFrameLength(uint16_t length)
+{
+ FL_high = (length >> 8) & 0x07;
+ FL_low = length & 0xff;
+}
+
+void eti_STC::setSTL(uint16_t length)
+{
+ STL_high = length >> 8;
+ STL_low = length & 0xff;
+}
+
+uint16_t eti_STC::getSTL()
+{
+ return (uint16_t)((STL_high << 8) + STL_low);
+}
+
+void eti_STC::setStartAddress(uint16_t address)
+{
+ startAddress_high = address >> 8;
+ startAddress_low = address & 0xff;
+}
+
+uint16_t eti_STC::getStartAddress()
+{
+ return (uint16_t)((startAddress_high << 8) + startAddress_low);
+}
+
+}
diff --git a/lib/edi/eti.hpp b/lib/edi/eti.hpp
new file mode 100644
index 0000000..451ca48
--- /dev/null
+++ b/lib/edi/eti.hpp
@@ -0,0 +1,114 @@
+/*
+ Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Her Majesty the
+ Queen in Right of Canada (Communications Research Center Canada)
+
+ Copyright (C) 2016
+ Matthias P. Braendli, matthias.braendli@mpb.li
+
+ http://opendigitalradio.org
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#pragma once
+
+#include <stdint.h>
+#define PACKED __attribute__ ((packed))
+
+#include <time.h>
+
+namespace EdiDecoder {
+
+struct eti_SYNC {
+ uint32_t ERR:8;
+ uint32_t FSYNC:24;
+} PACKED;
+
+struct eti_FC {
+ uint32_t FCT:8;
+ uint32_t NST:7;
+ uint32_t FICF:1;
+ uint32_t FL_high:3;
+ uint32_t MID:2;
+ uint32_t FP:3;
+ uint32_t FL_low:8;
+ uint16_t getFrameLength();
+ void setFrameLength(uint16_t length);
+} PACKED;
+
+struct eti_STC {
+ uint32_t startAddress_high:2;
+ uint32_t SCID:6;
+ uint32_t startAddress_low:8;
+ uint32_t STL_high:2;
+ uint32_t TPL:6;
+ uint32_t STL_low:8;
+ void setSTL(uint16_t length);
+ uint16_t getSTL();
+ void setStartAddress(uint16_t address);
+ uint16_t getStartAddress();
+} PACKED;
+
+struct eti_EOH {
+ uint16_t MNSC;
+ uint16_t CRC;
+} PACKED;
+
+struct eti_EOF {
+ uint16_t CRC;
+ uint16_t RFU;
+} PACKED;
+
+struct eti_TIST {
+ uint32_t TIST;
+} PACKED;
+
+struct eti_MNSC_TIME_0 {
+ uint32_t type:4;
+ uint32_t identifier:4;
+ uint32_t rfa:8;
+} PACKED;
+
+struct eti_MNSC_TIME_1 {
+ uint32_t second_unit:4;
+ uint32_t second_tens:3;
+ uint32_t accuracy:1;
+
+ uint32_t minute_unit:4;
+ uint32_t minute_tens:3;
+ uint32_t sync_to_frame:1;
+} PACKED;
+
+struct eti_MNSC_TIME_2 {
+ uint32_t hour_unit:4;
+ uint32_t hour_tens:4;
+
+ uint32_t day_unit:4;
+ uint32_t day_tens:4;
+} PACKED;
+
+struct eti_MNSC_TIME_3 {
+ uint32_t month_unit:4;
+ uint32_t month_tens:4;
+
+ uint32_t year_unit:4;
+ uint32_t year_tens:4;
+} PACKED;
+
+struct eti_extension_TIME {
+ uint32_t TIME_SECONDS;
+} PACKED;
+
+}
diff --git a/lib/fec/LICENSE b/lib/fec/LICENSE
new file mode 100644
index 0000000..5a883d3
--- /dev/null
+++ b/lib/fec/LICENSE
@@ -0,0 +1,502 @@
+GNU LESSER GENERAL PUBLIC LICENSE
+ Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+(This is the first released version of the Lesser GPL. It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.)
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+ This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it. You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+ When we speak of free software, we are referring to freedom of use,
+not price. Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+ To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights. These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+ For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you. You must make sure that they, too, receive or can get the source
+code. If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it. And you must show them these terms so they know their rights.
+
+ We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+ To protect each distributor, we want to make it very clear that
+there is no warranty for the free library. Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+ Finally, software patents pose a constant threat to the existence of
+any free program. We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder. Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+ Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License. This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License. We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+ When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library. The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom. The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+ We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License. It also provides other free software developers Less
+of an advantage over competing non-free programs. These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries. However, the Lesser license provides advantages in certain
+special circumstances.
+
+ For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard. To achieve this, non-free programs must be
+allowed to use the library. A more frequent case is that a free
+library does the same job as widely used non-free libraries. In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+ In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software. For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+ Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+ The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library". The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+ A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+ The "Library", below, refers to any such software library or work
+which has been distributed under these terms. A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language. (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+ "Source code" for a work means the preferred form of the work for
+making modifications to it. For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+ Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it). Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+ 1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+ You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+ 2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) The modified work must itself be a software library.
+
+ b) You must cause the files modified to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ c) You must cause the whole of the work to be licensed at no
+ charge to all third parties under the terms of this License.
+
+ d) If a facility in the modified Library refers to a function or a
+ table of data to be supplied by an application program that uses
+ the facility, other than as an argument passed when the facility
+ is invoked, then you must make a good faith effort to ensure that,
+ in the event an application does not supply such function or
+ table, the facility still operates, and performs whatever part of
+ its purpose remains meaningful.
+
+ (For example, a function in a library to compute square roots has
+ a purpose that is entirely well-defined independent of the
+ application. Therefore, Subsection 2d requires that any
+ application-supplied function or table used by this function must
+ be optional: if the application does not supply it, the square
+ root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library. To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License. (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.) Do not make any other change in
+these notices.
+
+ Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+ This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+ 4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+ If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library". Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+ However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library". The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+ When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library. The
+threshold for this to be true is not precisely defined by law.
+
+ If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work. (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+ Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+ 6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+ You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License. You must supply a copy of this License. If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License. Also, you must do one
+of these things:
+
+ a) Accompany the work with the complete corresponding
+ machine-readable source code for the Library including whatever
+ changes were used in the work (which must be distributed under
+ Sections 1 and 2 above); and, if the work is an executable linked
+ with the Library, with the complete machine-readable "work that
+ uses the Library", as object code and/or source code, so that the
+ user can modify the Library and then relink to produce a modified
+ executable containing the modified Library. (It is understood
+ that the user who changes the contents of definitions files in the
+ Library will not necessarily be able to recompile the application
+ to use the modified definitions.)
+
+ b) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (1) uses at run time a
+ copy of the library already present on the user's computer system,
+ rather than copying library functions into the executable, and (2)
+ will operate properly with a modified version of the library, if
+ the user installs one, as long as the modified version is
+ interface-compatible with the version that the work was made with.
+
+ c) Accompany the work with a written offer, valid for at
+ least three years, to give the same user the materials
+ specified in Subsection 6a, above, for a charge no more
+ than the cost of performing this distribution.
+
+ d) If distribution of the work is made by offering access to copy
+ from a designated place, offer equivalent access to copy the above
+ specified materials from the same place.
+
+ e) Verify that the user has already received a copy of these
+ materials or that you have already sent this user a copy.
+
+ For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it. However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+ It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+ 7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+ a) Accompany the combined library with a copy of the same work
+ based on the Library, uncombined with any other library
+ facilities. This must be distributed under the terms of the
+ Sections above.
+
+ b) Give prominent notice with the combined library of the fact
+ that part of it is a work based on the Library, and explaining
+ where to find the accompanying uncombined form of the same work.
+
+ 8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License. Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License. However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+ 9. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Library or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+ 10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+ 11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all. For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded. In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+ 13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation. If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+ 14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission. For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this. Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+ NO WARRANTY
+
+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Libraries
+
+ If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change. You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+ To apply these terms, attach the following notices to the library. It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+ {description}
+ Copyright (C) {year} {fullname}
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the
+ library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+ {signature of Ty Coon}, 1 April 1990
+ Ty Coon, President of Vice
+
+That's all there is to it!
diff --git a/lib/fec/README.md b/lib/fec/README.md
new file mode 100644
index 0000000..a44d28d
--- /dev/null
+++ b/lib/fec/README.md
@@ -0,0 +1,12 @@
+FEC routines from KA9Q's libfec
+===============================
+
+This folder contains part of the libfec library by KA9Q. Only the
+char-sized Reed-Solomon encoder and decoder is here.
+
+The files have been copied from the libfec fork at
+https://github.com/Opendigitalradio/ka9q-fec
+
+Original code is at http://www.ka9q.net/code/fec/
+
+All files in this folder are licenced under the LGPL v2.1, please see LICENCE
diff --git a/lib/fec/char.h b/lib/fec/char.h
new file mode 100644
index 0000000..25efd65
--- /dev/null
+++ b/lib/fec/char.h
@@ -0,0 +1,24 @@
+/* Stuff specific to the 8-bit symbol version of the general purpose RS codecs
+ *
+ * Copyright 2003, Phil Karn, KA9Q
+ * May be used under the terms of the GNU Lesser General Public License (LGPL)
+ */
+typedef unsigned char data_t;
+
+#define MODNN(x) modnn(rs,x)
+
+#define MM (rs->mm)
+#define NN (rs->nn)
+#define ALPHA_TO (rs->alpha_to)
+#define INDEX_OF (rs->index_of)
+#define GENPOLY (rs->genpoly)
+#define NROOTS (rs->nroots)
+#define FCR (rs->fcr)
+#define PRIM (rs->prim)
+#define IPRIM (rs->iprim)
+#define PAD (rs->pad)
+#define A0 (NN)
+
+
+
+
diff --git a/lib/fec/decode_rs.h b/lib/fec/decode_rs.h
new file mode 100644
index 0000000..647b885
--- /dev/null
+++ b/lib/fec/decode_rs.h
@@ -0,0 +1,298 @@
+/* The guts of the Reed-Solomon decoder, meant to be #included
+ * into a function body with the following typedefs, macros and variables supplied
+ * according to the code parameters:
+
+ * data_t - a typedef for the data symbol
+ * data_t data[] - array of NN data and parity symbols to be corrected in place
+ * retval - an integer lvalue into which the decoder's return code is written
+ * NROOTS - the number of roots in the RS code generator polynomial,
+ * which is the same as the number of parity symbols in a block.
+ Integer variable or literal.
+ * NN - the total number of symbols in a RS block. Integer variable or literal.
+ * PAD - the number of pad symbols in a block. Integer variable or literal.
+ * ALPHA_TO - The address of an array of NN elements to convert Galois field
+ * elements in index (log) form to polynomial form. Read only.
+ * INDEX_OF - The address of an array of NN elements to convert Galois field
+ * elements in polynomial form to index (log) form. Read only.
+ * MODNN - a function to reduce its argument modulo NN. May be inline or a macro.
+ * FCR - An integer literal or variable specifying the first consecutive root of the
+ * Reed-Solomon generator polynomial. Integer variable or literal.
+ * PRIM - The primitive root of the generator poly. Integer variable or literal.
+ * DEBUG - If set to 1 or more, do various internal consistency checking. Leave this
+ * undefined for production code
+
+ * The memset(), memmove(), and memcpy() functions are used. The appropriate header
+ * file declaring these functions (usually <string.h>) must be included by the calling
+ * program.
+ */
+
+
+#if !defined(NROOTS)
+#error "NROOTS not defined"
+#endif
+
+#if !defined(NN)
+#error "NN not defined"
+#endif
+
+#if !defined(PAD)
+#error "PAD not defined"
+#endif
+
+#if !defined(ALPHA_TO)
+#error "ALPHA_TO not defined"
+#endif
+
+#if !defined(INDEX_OF)
+#error "INDEX_OF not defined"
+#endif
+
+#if !defined(MODNN)
+#error "MODNN not defined"
+#endif
+
+#if !defined(FCR)
+#error "FCR not defined"
+#endif
+
+#if !defined(PRIM)
+#error "PRIM not defined"
+#endif
+
+#if !defined(NULL)
+#define NULL ((void *)0)
+#endif
+
+#undef MIN
+#define MIN(a,b) ((a) < (b) ? (a) : (b))
+#undef A0
+#define A0 (NN)
+
+{
+ int deg_lambda, el, deg_omega;
+ int i, j, r,k;
+ data_t u,q,tmp,num1,num2,den,discr_r;
+ data_t lambda[NROOTS+1], s[NROOTS]; /* Err+Eras Locator poly
+ * and syndrome poly */
+ data_t b[NROOTS+1], t[NROOTS+1], omega[NROOTS+1];
+ data_t root[NROOTS], reg[NROOTS+1], loc[NROOTS];
+ int syn_error, count;
+
+ /* form the syndromes; i.e., evaluate data(x) at roots of g(x) */
+ for(i=0;i<NROOTS;i++)
+ s[i] = data[0];
+
+ for(j=1;j<NN-PAD;j++){
+ for(i=0;i<NROOTS;i++){
+ if(s[i] == 0){
+ s[i] = data[j];
+ } else {
+ s[i] = data[j] ^ ALPHA_TO[MODNN(INDEX_OF[s[i]] + (FCR+i)*PRIM)];
+ }
+ }
+ }
+
+ /* Convert syndromes to index form, checking for nonzero condition */
+ syn_error = 0;
+ for(i=0;i<NROOTS;i++){
+ syn_error |= s[i];
+ s[i] = INDEX_OF[s[i]];
+ }
+
+ if (!syn_error) {
+ /* if syndrome is zero, data[] is a codeword and there are no
+ * errors to correct. So return data[] unmodified
+ */
+ count = 0;
+ goto finish;
+ }
+ memset(&lambda[1],0,NROOTS*sizeof(lambda[0]));
+ lambda[0] = 1;
+
+ if (no_eras > 0) {
+ /* Init lambda to be the erasure locator polynomial */
+ lambda[1] = ALPHA_TO[MODNN(PRIM*(NN-1-eras_pos[0]))];
+ for (i = 1; i < no_eras; i++) {
+ u = MODNN(PRIM*(NN-1-eras_pos[i]));
+ for (j = i+1; j > 0; j--) {
+ tmp = INDEX_OF[lambda[j - 1]];
+ if(tmp != A0)
+ lambda[j] ^= ALPHA_TO[MODNN(u + tmp)];
+ }
+ }
+
+#if DEBUG >= 1
+ /* Test code that verifies the erasure locator polynomial just constructed
+ Needed only for decoder debugging. */
+
+ /* find roots of the erasure location polynomial */
+ for(i=1;i<=no_eras;i++)
+ reg[i] = INDEX_OF[lambda[i]];
+
+ count = 0;
+ for (i = 1,k=IPRIM-1; i <= NN; i++,k = MODNN(k+IPRIM)) {
+ q = 1;
+ for (j = 1; j <= no_eras; j++)
+ if (reg[j] != A0) {
+ reg[j] = MODNN(reg[j] + j);
+ q ^= ALPHA_TO[reg[j]];
+ }
+ if (q != 0)
+ continue;
+ /* store root and error location number indices */
+ root[count] = i;
+ loc[count] = k;
+ count++;
+ }
+ if (count != no_eras) {
+ fprintf(stderr, "count = %d no_eras = %d\n lambda(x) is WRONG\n",count,no_eras);
+ count = -1;
+ goto finish;
+ }
+#if DEBUG >= 2
+ fprintf(stderr, "\n Erasure positions as determined by roots of Eras Loc Poly:\n");
+ for (i = 0; i < count; i++)
+ fprintf(stderr, "%d ", loc[i]);
+ fprintf(stderr, "\n");
+#endif
+#endif
+ }
+ for(i=0;i<NROOTS+1;i++)
+ b[i] = INDEX_OF[lambda[i]];
+
+ /*
+ * Begin Berlekamp-Massey algorithm to determine error+erasure
+ * locator polynomial
+ */
+ r = no_eras;
+ el = no_eras;
+ while (++r <= NROOTS) { /* r is the step number */
+ /* Compute discrepancy at the r-th step in poly-form */
+ discr_r = 0;
+ for (i = 0; i < r; i++){
+ if ((lambda[i] != 0) && (s[r-i-1] != A0)) {
+ discr_r ^= ALPHA_TO[MODNN(INDEX_OF[lambda[i]] + s[r-i-1])];
+ }
+ }
+ discr_r = INDEX_OF[discr_r]; /* Index form */
+ if (discr_r == A0) {
+ /* 2 lines below: B(x) <-- x*B(x) */
+ memmove(&b[1],b,NROOTS*sizeof(b[0]));
+ b[0] = A0;
+ } else {
+ /* 7 lines below: T(x) <-- lambda(x) - discr_r*x*b(x) */
+ t[0] = lambda[0];
+ for (i = 0 ; i < NROOTS; i++) {
+ if(b[i] != A0)
+ t[i+1] = lambda[i+1] ^ ALPHA_TO[MODNN(discr_r + b[i])];
+ else
+ t[i+1] = lambda[i+1];
+ }
+ if (2 * el <= r + no_eras - 1) {
+ el = r + no_eras - el;
+ /*
+ * 2 lines below: B(x) <-- inv(discr_r) *
+ * lambda(x)
+ */
+ for (i = 0; i <= NROOTS; i++)
+ b[i] = (lambda[i] == 0) ? A0 : MODNN(INDEX_OF[lambda[i]] - discr_r + NN);
+ } else {
+ /* 2 lines below: B(x) <-- x*B(x) */
+ memmove(&b[1],b,NROOTS*sizeof(b[0]));
+ b[0] = A0;
+ }
+ memcpy(lambda,t,(NROOTS+1)*sizeof(t[0]));
+ }
+ }
+
+ /* Convert lambda to index form and compute deg(lambda(x)) */
+ deg_lambda = 0;
+ for(i=0;i<NROOTS+1;i++){
+ lambda[i] = INDEX_OF[lambda[i]];
+ if(lambda[i] != A0)
+ deg_lambda = i;
+ }
+ /* Find roots of the error+erasure locator polynomial by Chien search */
+ memcpy(&reg[1],&lambda[1],NROOTS*sizeof(reg[0]));
+ count = 0; /* Number of roots of lambda(x) */
+ for (i = 1,k=IPRIM-1; i <= NN; i++,k = MODNN(k+IPRIM)) {
+ q = 1; /* lambda[0] is always 0 */
+ for (j = deg_lambda; j > 0; j--){
+ if (reg[j] != A0) {
+ reg[j] = MODNN(reg[j] + j);
+ q ^= ALPHA_TO[reg[j]];
+ }
+ }
+ if (q != 0)
+ continue; /* Not a root */
+ /* store root (index-form) and error location number */
+#if DEBUG>=2
+ fprintf(stderr, "count %d root %d loc %d\n",count,i,k);
+#endif
+ root[count] = i;
+ loc[count] = k;
+ /* If we've already found max possible roots,
+ * abort the search to save time
+ */
+ if(++count == deg_lambda)
+ break;
+ }
+ if (deg_lambda != count) {
+ /*
+ * deg(lambda) unequal to number of roots => uncorrectable
+ * error detected
+ */
+ count = -1;
+ goto finish;
+ }
+ /*
+ * Compute err+eras evaluator poly omega(x) = s(x)*lambda(x) (modulo
+ * x**NROOTS). in index form. Also find deg(omega).
+ */
+ deg_omega = deg_lambda-1;
+ for (i = 0; i <= deg_omega;i++){
+ tmp = 0;
+ for(j=i;j >= 0; j--){
+ if ((s[i - j] != A0) && (lambda[j] != A0))
+ tmp ^= ALPHA_TO[MODNN(s[i - j] + lambda[j])];
+ }
+ omega[i] = INDEX_OF[tmp];
+ }
+
+ /*
+ * Compute error values in poly-form. num1 = omega(inv(X(l))), num2 =
+ * inv(X(l))**(FCR-1) and den = lambda_pr(inv(X(l))) all in poly-form
+ */
+ for (j = count-1; j >=0; j--) {
+ num1 = 0;
+ for (i = deg_omega; i >= 0; i--) {
+ if (omega[i] != A0)
+ num1 ^= ALPHA_TO[MODNN(omega[i] + i * root[j])];
+ }
+ num2 = ALPHA_TO[MODNN(root[j] * (FCR - 1) + NN)];
+ den = 0;
+
+ /* lambda[i+1] for i even is the formal derivative lambda_pr of lambda[i] */
+ for (i = MIN(deg_lambda,NROOTS-1) & ~1; i >= 0; i -=2) {
+ if(lambda[i+1] != A0)
+ den ^= ALPHA_TO[MODNN(lambda[i+1] + i * root[j])];
+ }
+#if DEBUG >= 1
+ if (den == 0) {
+ fprintf(stderr, "\n ERROR: denominator = 0\n");
+ count = -1;
+ goto finish;
+ }
+#endif
+ /* Apply error to data */
+ if (num1 != 0 && loc[j] >= PAD) {
+ data[loc[j]-PAD] ^= ALPHA_TO[MODNN(INDEX_OF[num1] + INDEX_OF[num2] + NN - INDEX_OF[den])];
+ }
+ }
+ finish:
+ if(eras_pos != NULL){
+ for(i=0;i<count;i++)
+ eras_pos[i] = loc[i];
+ }
+ retval = count;
+}
diff --git a/lib/fec/decode_rs_char.c b/lib/fec/decode_rs_char.c
new file mode 100644
index 0000000..7105233
--- /dev/null
+++ b/lib/fec/decode_rs_char.c
@@ -0,0 +1,22 @@
+/* General purpose Reed-Solomon decoder for 8-bit symbols or less
+ * Copyright 2003 Phil Karn, KA9Q
+ * May be used under the terms of the GNU Lesser General Public License (LGPL)
+ */
+
+#ifdef DEBUG
+#include <stdio.h>
+#endif
+
+#include <string.h>
+
+#include "char.h"
+#include "rs-common.h"
+
+int decode_rs_char(void *p, data_t *data, int *eras_pos, int no_eras){
+ int retval;
+ struct rs *rs = (struct rs *)p;
+
+#include "decode_rs.h"
+
+ return retval;
+}
diff --git a/lib/fec/encode_rs.h b/lib/fec/encode_rs.h
new file mode 100644
index 0000000..2c157f9
--- /dev/null
+++ b/lib/fec/encode_rs.h
@@ -0,0 +1,58 @@
+/* The guts of the Reed-Solomon encoder, meant to be #included
+ * into a function body with the following typedefs, macros and variables supplied
+ * according to the code parameters:
+
+ * data_t - a typedef for the data symbol
+ * data_t data[] - array of NN-NROOTS-PAD and type data_t to be encoded
+ * data_t parity[] - an array of NROOTS and type data_t to be written with parity symbols
+ * NROOTS - the number of roots in the RS code generator polynomial,
+ * which is the same as the number of parity symbols in a block.
+ Integer variable or literal.
+ *
+ * NN - the total number of symbols in a RS block. Integer variable or literal.
+ * PAD - the number of pad symbols in a block. Integer variable or literal.
+ * ALPHA_TO - The address of an array of NN elements to convert Galois field
+ * elements in index (log) form to polynomial form. Read only.
+ * INDEX_OF - The address of an array of NN elements to convert Galois field
+ * elements in polynomial form to index (log) form. Read only.
+ * MODNN - a function to reduce its argument modulo NN. May be inline or a macro.
+ * GENPOLY - an array of NROOTS+1 elements containing the generator polynomial in index form
+
+ * The memset() and memmove() functions are used. The appropriate header
+ * file declaring these functions (usually <string.h>) must be included by the calling
+ * program.
+
+ * Copyright 2004, Phil Karn, KA9Q
+ * May be used under the terms of the GNU Lesser General Public License (LGPL)
+ */
+
+
+#undef A0
+#define A0 (NN) /* Special reserved value encoding zero in index form */
+
+{
+ int i, j;
+ data_t feedback;
+
+ memset(parity,0,NROOTS*sizeof(data_t));
+
+ for(i=0;i<NN-NROOTS-PAD;i++){
+ feedback = INDEX_OF[data[i] ^ parity[0]];
+ if(feedback != A0){ /* feedback term is non-zero */
+#ifdef UNNORMALIZED
+ /* This line is unnecessary when GENPOLY[NROOTS] is unity, as it must
+ * always be for the polynomials constructed by init_rs()
+ */
+ feedback = MODNN(NN - GENPOLY[NROOTS] + feedback);
+#endif
+ for(j=1;j<NROOTS;j++)
+ parity[j] ^= ALPHA_TO[MODNN(feedback + GENPOLY[NROOTS-j])];
+ }
+ /* Shift */
+ memmove(&parity[0],&parity[1],sizeof(data_t)*(NROOTS-1));
+ if(feedback != A0)
+ parity[NROOTS-1] = ALPHA_TO[MODNN(feedback + GENPOLY[0])];
+ else
+ parity[NROOTS-1] = 0;
+ }
+}
diff --git a/lib/fec/encode_rs_char.c b/lib/fec/encode_rs_char.c
new file mode 100644
index 0000000..a9bf2b8
--- /dev/null
+++ b/lib/fec/encode_rs_char.c
@@ -0,0 +1,15 @@
+/* Reed-Solomon encoder
+ * Copyright 2002, Phil Karn, KA9Q
+ * May be used under the terms of the GNU Lesser General Public License (LGPL)
+ */
+#include <string.h>
+
+#include "char.h"
+#include "rs-common.h"
+
+void encode_rs_char(void *p,data_t *data, data_t *parity){
+ struct rs *rs = (struct rs *)p;
+
+#include "encode_rs.h"
+
+}
diff --git a/lib/fec/fec.h b/lib/fec/fec.h
new file mode 100644
index 0000000..0d1bae1
--- /dev/null
+++ b/lib/fec/fec.h
@@ -0,0 +1,30 @@
+/* Main header for reduced libfec.
+ *
+ * The FEC code in this folder is
+ * Copyright 2003 Phil Karn, KA9Q
+ * May be used under the terms of the GNU Lesser General Public License (LGPL)
+ */
+
+#pragma once
+
+#include <stdlib.h>
+
+#include "char.h"
+#include "rs-common.h"
+
+/* Initialize a Reed-Solomon codec
+ * symsize = symbol size, bits
+ * gfpoly = Field generator polynomial coefficients
+ * fcr = first root of RS code generator polynomial, index form
+ * prim = primitive element to generate polynomial roots
+ * nroots = RS code generator polynomial degree (number of roots)
+ * pad = padding bytes at front of shortened block
+ */
+void *init_rs_char(int symsize,int gfpoly,int fcr,int prim,int nroots,int pad);
+
+int decode_rs_char(void *p, data_t *data, int *eras_pos, int no_eras);
+
+void encode_rs_char(void *p,data_t *data, data_t *parity);
+
+void free_rs_char(void *p);
+
diff --git a/lib/fec/init_rs.h b/lib/fec/init_rs.h
new file mode 100644
index 0000000..a5e8b4a
--- /dev/null
+++ b/lib/fec/init_rs.h
@@ -0,0 +1,104 @@
+/* Common code for intializing a Reed-Solomon control block (char or int symbols)
+ * Copyright 2004 Phil Karn, KA9Q
+ * May be used under the terms of the GNU Lesser General Public License (LGPL)
+ */
+
+{
+ int i, j, sr,root,iprim;
+
+ rs = NULL;
+ /* Check parameter ranges */
+ if(symsize < 0 || symsize > 8*sizeof(data_t)){
+ goto done;
+ }
+
+ if(fcr < 0 || fcr >= (1<<symsize))
+ goto done;
+ if(prim <= 0 || prim >= (1<<symsize))
+ goto done;
+ if(nroots < 0 || nroots >= (1<<symsize))
+ goto done; /* Can't have more roots than symbol values! */
+ if(pad < 0 || pad >= ((1<<symsize) -1 - nroots))
+ goto done; /* Too much padding */
+
+ rs = (struct rs *)calloc(1,sizeof(struct rs));
+ if(rs == NULL)
+ goto done;
+
+ rs->mm = symsize;
+ rs->nn = (1<<symsize)-1;
+ rs->pad = pad;
+
+ rs->alpha_to = (data_t *)malloc(sizeof(data_t)*(rs->nn+1));
+ if(rs->alpha_to == NULL){
+ free(rs);
+ rs = NULL;
+ goto done;
+ }
+ rs->index_of = (data_t *)malloc(sizeof(data_t)*(rs->nn+1));
+ if(rs->index_of == NULL){
+ free(rs->alpha_to);
+ free(rs);
+ rs = NULL;
+ goto done;
+ }
+
+ /* Generate Galois field lookup tables */
+ rs->index_of[0] = A0; /* log(zero) = -inf */
+ rs->alpha_to[A0] = 0; /* alpha**-inf = 0 */
+ sr = 1;
+ for(i=0;i<rs->nn;i++){
+ rs->index_of[sr] = i;
+ rs->alpha_to[i] = sr;
+ sr <<= 1;
+ if(sr & (1<<symsize))
+ sr ^= gfpoly;
+ sr &= rs->nn;
+ }
+ if(sr != 1){
+ /* field generator polynomial is not primitive! */
+ free(rs->alpha_to);
+ free(rs->index_of);
+ free(rs);
+ rs = NULL;
+ goto done;
+ }
+
+ /* Form RS code generator polynomial from its roots */
+ rs->genpoly = (data_t *)malloc(sizeof(data_t)*(nroots+1));
+ if(rs->genpoly == NULL){
+ free(rs->alpha_to);
+ free(rs->index_of);
+ free(rs);
+ rs = NULL;
+ goto done;
+ }
+ rs->fcr = fcr;
+ rs->prim = prim;
+ rs->nroots = nroots;
+
+ /* Find prim-th root of 1, used in decoding */
+ for(iprim=1;(iprim % prim) != 0;iprim += rs->nn)
+ ;
+ rs->iprim = iprim / prim;
+
+ rs->genpoly[0] = 1;
+ for (i = 0,root=fcr*prim; i < nroots; i++,root += prim) {
+ rs->genpoly[i+1] = 1;
+
+ /* Multiply rs->genpoly[] by @**(root + x) */
+ for (j = i; j > 0; j--){
+ if (rs->genpoly[j] != 0)
+ rs->genpoly[j] = rs->genpoly[j-1] ^ rs->alpha_to[modnn(rs,rs->index_of[rs->genpoly[j]] + root)];
+ else
+ rs->genpoly[j] = rs->genpoly[j-1];
+ }
+ /* rs->genpoly[0] can never be zero */
+ rs->genpoly[0] = rs->alpha_to[modnn(rs,rs->index_of[rs->genpoly[0]] + root)];
+ }
+ /* convert rs->genpoly[] to index form for quicker encoding */
+ for (i = 0; i <= nroots; i++)
+ rs->genpoly[i] = rs->index_of[rs->genpoly[i]];
+ done:;
+
+}
diff --git a/lib/fec/init_rs_char.c b/lib/fec/init_rs_char.c
new file mode 100644
index 0000000..a51099a
--- /dev/null
+++ b/lib/fec/init_rs_char.c
@@ -0,0 +1,35 @@
+/* Initialize a RS codec
+ *
+ * Copyright 2002 Phil Karn, KA9Q
+ * May be used under the terms of the GNU Lesser General Public License (LGPL)
+ */
+#include <stdlib.h>
+
+#include "char.h"
+#include "rs-common.h"
+
+void free_rs_char(void *p){
+ struct rs *rs = (struct rs *)p;
+
+ free(rs->alpha_to);
+ free(rs->index_of);
+ free(rs->genpoly);
+ free(rs);
+}
+
+/* Initialize a Reed-Solomon codec
+ * symsize = symbol size, bits
+ * gfpoly = Field generator polynomial coefficients
+ * fcr = first root of RS code generator polynomial, index form
+ * prim = primitive element to generate polynomial roots
+ * nroots = RS code generator polynomial degree (number of roots)
+ * pad = padding bytes at front of shortened block
+ */
+void *init_rs_char(int symsize,int gfpoly,int fcr,int prim,
+ int nroots,int pad){
+ struct rs *rs;
+
+#include "init_rs.h"
+
+ return rs;
+}
diff --git a/lib/fec/rs-common.h b/lib/fec/rs-common.h
new file mode 100644
index 0000000..e64eb39
--- /dev/null
+++ b/lib/fec/rs-common.h
@@ -0,0 +1,26 @@
+/* Stuff common to all the general-purpose Reed-Solomon codecs
+ * Copyright 2004 Phil Karn, KA9Q
+ * May be used under the terms of the GNU Lesser General Public License (LGPL)
+ */
+
+/* Reed-Solomon codec control block */
+struct rs {
+ int mm; /* Bits per symbol */
+ int nn; /* Symbols per block (= (1<<mm)-1) */
+ data_t *alpha_to; /* log lookup table */
+ data_t *index_of; /* Antilog lookup table */
+ data_t *genpoly; /* Generator polynomial */
+ int nroots; /* Number of generator roots = number of parity symbols */
+ int fcr; /* First consecutive root, index form */
+ int prim; /* Primitive element, index form */
+ int iprim; /* prim-th root of 1, index form */
+ int pad; /* Padding bytes in shortened block */
+};
+
+static inline int modnn(struct rs *rs,int x){
+ while (x >= rs->nn) {
+ x -= rs->nn;
+ x = (x >> rs->mm) + (x & rs->nn);
+ }
+ return x;
+}
diff --git a/src/DabMod.cpp b/src/DabMod.cpp
index fc28218..e44ed20 100644
--- a/src/DabMod.cpp
+++ b/src/DabMod.cpp
@@ -187,6 +187,7 @@ int launch_modulator(int argc, char* argv[])
auto inputZeroMQReader = make_shared<InputZeroMQReader>();
#endif
+ auto inputEdiReader = make_shared<InputEdiReader>();
auto inputTcpReader = make_shared<InputTcpReader>();
struct sigaction sa;
@@ -712,6 +713,10 @@ int launch_modulator(int argc, char* argv[])
inputTcpReader->Open(inputName);
m.inputReader = inputTcpReader.get();
}
+ else if (inputTransport == "edi") {
+ inputEdiReader->Open(inputName);
+ m.inputReader = inputEdiReader.get();
+ }
else
{
fprintf(stderr, "Error, invalid input transport %s selected!\n", inputTransport.c_str());
@@ -770,11 +775,11 @@ int launch_modulator(int argc, char* argv[])
m.flowgraph = &flowgraph;
m.data.setLength(6144);
- shared_ptr<InputMemory> input(new InputMemory(&m.data));
- shared_ptr<DabModulator> modulator(
- new DabModulator(tist_offset_s, tist_delay_stages,
- tiiConfig, outputRate, clockRate, dabMode, gainMode,
- digitalgain, normalise, filterTapsFilename));
+ auto input = make_shared<InputMemory>(&m.data);
+ auto modulator = make_shared<DabModulator>(
+ tist_offset_s, tist_delay_stages,
+ tiiConfig, outputRate, clockRate, dabMode, gainMode,
+ digitalgain, normalise, filterTapsFilename);
flowgraph.connect(input, modulator);
if (format_converter) {
diff --git a/src/InputEdiReader.cpp b/src/InputEdiReader.cpp
new file mode 100644
index 0000000..26cd91e
--- /dev/null
+++ b/src/InputEdiReader.cpp
@@ -0,0 +1,99 @@
+/*
+ Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 Her Majesty
+ the Queen in Right of Canada (Communications Research Center Canada)
+
+ Copyright (C) 2016
+ Matthias P. Braendli, matthias.braendli@mpb.li
+
+ http://opendigitalradio.org
+ */
+/*
+ This file is part of ODR-DabMod.
+
+ ODR-DabMod is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as
+ published by the Free Software Foundation, either version 3 of the
+ License, or (at your option) any later version.
+
+ ODR-DabMod is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with ODR-DabMod. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "InputReader.h"
+#include "PcDebug.h"
+
+#include <stdexcept>
+#include <memory>
+#include <regex>
+#include <sys/types.h>
+#include <string.h>
+
+using namespace std;
+
+InputEdiReader::InputEdiReader() :
+ m_writer(),
+ m_decoder(m_writer),
+ m_sock()
+{
+}
+
+int InputEdiReader::Open(const std::string& uri)
+{
+ etiLog.level(info) << "Opening EDI :" << uri;
+
+ const std::regex re_udp("udp://:([0-9]+)");
+ std::smatch m;
+ if (std::regex_match(uri, m, re_udp)) {
+ m_port = std::stoi(m[1].str());
+
+ etiLog.level(info) << "EDI port :" << m_port;
+ return m_sock.reinit(m_port, "0.0.0.0");
+ }
+
+ return 1;
+}
+
+void InputEdiReader::rx_packet()
+{
+ const size_t packsize = 8192;
+ UdpPacket packet(packsize);
+
+ int ret = m_sock.receive(packet);
+ if (ret == 0) {
+ const auto &buf = packet.getBuffer();
+ if (packet.getSize() == packsize) {
+ fprintf(stderr, "Warning, possible UDP truncation\n");
+ }
+
+ m_decoder.push_packet(buf);
+ }
+ else {
+ fprintf(stderr, "Socket error: %s\n", inetErrMsg);
+ }
+}
+
+int InputEdiReader::GetNextFrame(void* buffer)
+{
+ vector<uint8_t> eti;
+ while (eti.empty()) {
+ rx_packet();
+ eti = m_writer.getEtiFrame();
+ }
+
+ assert(eti.size() == 6144);
+ copy(eti.begin(), eti.end(), reinterpret_cast<uint8_t*>(buffer));
+
+ return 6144;
+}
+
+void InputEdiReader::PrintInfo()
+{
+ fprintf(stderr, "EDI Input: \n");
+ fprintf(stderr, " Port : %d\n", m_port);
+}
+
diff --git a/src/InputReader.h b/src/InputReader.h
index 4d0792b..fddfccf 100644
--- a/src/InputReader.h
+++ b/src/InputReader.h
@@ -25,8 +25,7 @@
along with ODR-DabMod. If not, see <http://www.gnu.org/licenses/>.
*/
-#ifndef INPUTREADER_H
-#define INPUTREADER_H
+#pragma once
#ifdef HAVE_CONFIG_H
# include "config.h"
@@ -41,6 +40,8 @@
#endif
#include "porting.h"
#include "Log.h"
+#include "lib/edi/ETIDecoder.hpp"
+#include "lib/UdpSocket.h"
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
@@ -244,6 +245,28 @@ class InputZeroMQReader : public InputReader
struct InputZeroMQThreadData workerdata_;
};
-#endif
+class InputEdiReader : public InputReader
+{
+public:
+ InputEdiReader();
+
+ int Open(const std::string& uri);
+
+ int GetNextFrame(void* buffer);
+
+ void PrintInfo(void);
+
+private:
+ void rx_packet(void);
+
+ std::vector<uint8_t> getEtiFrame(void);
+
+ EdiDecoder::ETIWriter m_writer;
+ EdiDecoder::ETIDecoder m_decoder;
+
+ int m_port;
+ UdpSocket m_sock;
+};
+
#endif