commit c04c3e1aef5b0d458ea8f4c7b9ea1af764afb9e6 Author: Jonathan Naylor Date: Thu Apr 20 20:51:30 2017 +0100 Initial commit. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..690a1f9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,18 @@ +Debug +Release +x64 +MMDVMHost +*.o +*.opendb +*.bak +*.obj +*~ +*.sdf +*.log +*.zip +*.exe +*.user +*.VC.db +.vs +*.ambe +GitVersion.h diff --git a/Conf.cpp b/Conf.cpp new file mode 100644 index 0000000..2aaa34b --- /dev/null +++ b/Conf.cpp @@ -0,0 +1,290 @@ +/* + * Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "Conf.h" +#include "Log.h" + +#include +#include +#include +#include + +const int BUFFER_SIZE = 500; + +enum SECTION { + SECTION_NONE, + SECTION_GENERAL, + SECTION_LOG, + SECTION_MMDVM, + SECTION_DMR_NETWORK, + SECTION_XLX_NETWORK +}; + +CConf::CConf(const std::string& file) : +m_file(file), +m_daemon(false), +m_xlxSlot(2U), +m_timeout(10U), +m_logDisplayLevel(0U), +m_logFileLevel(0U), +m_logFilePath(), +m_logFileRoot(), +m_mmdvmAddress(), +m_mmdvmPort(0U), +m_mmdvmLocal(0U), +m_mmdvmDebug(false), +m_xlxNetworkAddress(), +m_xlxNetworkPort(0U), +m_xlxNetworkLocal(0U), +m_xlxNetworkPassword(), +m_xlxNetworkOptions(), +m_xlxNetworkSlot(1U), +m_xlxNetworkTG(9U), +m_xlxNetworkDebug(false), +m_dmrNetworkAddress(), +m_dmrNetworkPort(0U), +m_dmrNetworkLocal(0U), +m_dmrNetworkPassword(), +m_dmrNetworkDebug(false) +{ +} + +CConf::~CConf() +{ +} + +bool CConf::read() +{ + FILE* fp = ::fopen(m_file.c_str(), "rt"); + if (fp == NULL) { + ::fprintf(stderr, "Couldn't open the .ini file - %s\n", m_file.c_str()); + return false; + } + + SECTION section = SECTION_NONE; + + char buffer[BUFFER_SIZE]; + while (::fgets(buffer, BUFFER_SIZE, fp) != NULL) { + if (buffer[0U] == '#') + continue; + + if (buffer[0U] == '[') { + if (::strncmp(buffer, "[General]", 9U) == 0) + section = SECTION_GENERAL; + else if (::strncmp(buffer, "[Log]", 5U) == 0) + section = SECTION_LOG; + else if (::strncmp(buffer, "[MMDVM]", 13U) == 0) + section = SECTION_MMDVM; + else if (::strncmp(buffer, "[XLX Network]", 13U) == 0) + section = SECTION_XLX_NETWORK; + else if (::strncmp(buffer, "[DMR Network]", 13U) == 0) + section = SECTION_DMR_NETWORK; + else + section = SECTION_NONE; + + continue; + } + + char* key = ::strtok(buffer, " \t=\r\n"); + if (key == NULL) + continue; + + char* value = ::strtok(NULL, "\r\n"); + if (value == NULL) + continue; + + if (section == SECTION_GENERAL) { + if (::strcmp(key, "Daemon") == 0) + m_daemon = ::atoi(value) == 1; + else if (::strcmp(key, "XLSSlot") == 0) + m_xlxSlot = (unsigned int)::atoi(value); + else if (::strcmp(key, "Timeout") == 0) + m_timeout = (unsigned int)::atoi(value); + } else if (section == SECTION_LOG) { + if (::strcmp(key, "FilePath") == 0) + m_logFilePath = value; + else if (::strcmp(key, "FileRoot") == 0) + m_logFileRoot = value; + else if (::strcmp(key, "FileLevel") == 0) + m_logFileLevel = (unsigned int)::atoi(value); + else if (::strcmp(key, "DisplayLevel") == 0) + m_logDisplayLevel = (unsigned int)::atoi(value); + } else if (section == SECTION_MMDVM) { + if (::strcmp(key, "Address") == 0) + m_mmdvmAddress = value; + else if (::strcmp(key, "Port") == 0) + m_mmdvmPort = (unsigned int)::atoi(value); + else if (::strcmp(key, "Local") == 0) + m_mmdvmLocal = (unsigned int)::atoi(value); + else if (::strcmp(key, "Debug") == 0) + m_mmdvmDebug = ::atoi(value) == 1; + } else if (section == SECTION_XLX_NETWORK) { + if (::strcmp(key, "Address") == 0) + m_xlxNetworkAddress = value; + else if (::strcmp(key, "Port") == 0) + m_xlxNetworkPort = (unsigned int)::atoi(value); + else if (::strcmp(key, "Local") == 0) + m_xlxNetworkLocal = (unsigned int)::atoi(value); + else if (::strcmp(key, "Password") == 0) + m_xlxNetworkPassword = value; + else if (::strcmp(key, "Options") == 0) + m_xlxNetworkOptions = value; + else if (::strcmp(key, "Slot") == 0) + m_xlxNetworkSlot = (unsigned int)::atoi(value); + else if (::strcmp(key, "TG") == 0) + m_xlxNetworkTG = (unsigned int)::atoi(value); + else if (::strcmp(key, "Debug") == 0) + m_xlxNetworkDebug = ::atoi(value) == 1; + } else if (section == SECTION_DMR_NETWORK) { + if (::strcmp(key, "Address") == 0) + m_dmrNetworkAddress = value; + else if (::strcmp(key, "Port") == 0) + m_dmrNetworkPort = (unsigned int)::atoi(value); + else if (::strcmp(key, "Local") == 0) + m_dmrNetworkLocal = (unsigned int)::atoi(value); + else if (::strcmp(key, "Password") == 0) + m_dmrNetworkPassword = value; + else if (::strcmp(key, "Debug") == 0) + m_dmrNetworkDebug = ::atoi(value) == 1; + } + } + + ::fclose(fp); + + return true; +} + +bool CConf::getDaemon() const +{ + return m_daemon; +} + +unsigned int CConf::getXLXSlot() const +{ + return m_xlxSlot; +} + +unsigned int CConf::getTimeout() const +{ + return m_timeout; +} + +unsigned int CConf::getLogDisplayLevel() const +{ + return m_logDisplayLevel; +} + +unsigned int CConf::getLogFileLevel() const +{ + return m_logFileLevel; +} + +std::string CConf::getLogFilePath() const +{ + return m_logFilePath; +} + +std::string CConf::getLogFileRoot() const +{ + return m_logFileRoot; +} + +std::string CConf::getMMDVMAddress() const +{ + return m_mmdvmAddress; +} + +unsigned int CConf::getMMDVMPort() const +{ + return m_mmdvmPort; +} + +unsigned int CConf::getMMDVMLocal() const +{ + return m_mmdvmLocal; +} + +bool CConf::getMMDVMDebug() const +{ + return m_mmdvmDebug; +} + +std::string CConf::getXLXNetworkAddress() const +{ + return m_xlxNetworkAddress; +} + +unsigned int CConf::getXLXNetworkPort() const +{ + return m_xlxNetworkPort; +} + +unsigned int CConf::getXLXNetworkLocal() const +{ + return m_xlxNetworkLocal; +} + +std::string CConf::getXLXNetworkPassword() const +{ + return m_xlxNetworkPassword; +} + +std::string CConf::getXLXNetworkOptions() const +{ + return m_xlxNetworkOptions; +} + +unsigned int CConf::getXLXNetworkSlot() const +{ + return m_xlxNetworkSlot; +} + +unsigned int CConf::getXLXNetworkTG() const +{ + return m_xlxNetworkTG; +} + +bool CConf::getXLXNetworkDebug() const +{ + return m_xlxNetworkDebug; +} + +std::string CConf::getDMRNetworkAddress() const +{ + return m_dmrNetworkAddress; +} + +unsigned int CConf::getDMRNetworkPort() const +{ + return m_dmrNetworkPort; +} + +unsigned int CConf::getDMRNetworkLocal() const +{ + return m_dmrNetworkLocal; +} + +std::string CConf::getDMRNetworkPassword() const +{ + return m_dmrNetworkPassword; +} + +bool CConf::getDMRNetworkDebug() const +{ + return m_dmrNetworkDebug; +} diff --git a/Conf.h b/Conf.h new file mode 100644 index 0000000..ea0ac70 --- /dev/null +++ b/Conf.h @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#if !defined(CONF_H) +#define CONF_H + +#include +#include + +class CConf +{ +public: + CConf(const std::string& file); + ~CConf(); + + bool read(); + + // The General section + bool getDaemon() const; + unsigned int getXLXSlot() const; + unsigned int getTimeout() const; + + // The Log section + unsigned int getLogDisplayLevel() const; + unsigned int getLogFileLevel() const; + std::string getLogFilePath() const; + std::string getLogFileRoot() const; + + // The MMDVM Network section + std::string getMMDVMAddress() const; + unsigned int getMMDVMPort() const; + unsigned int getMMDVMLocal() const; + bool getMMDVMDebug() const; + + // The DMR Network section + std::string getDMRNetworkAddress() const; + unsigned int getDMRNetworkPort() const; + unsigned int getDMRNetworkLocal() const; + std::string getDMRNetworkPassword() const; + bool getDMRNetworkDebug() const; + + // The XLX Network section + std::string getXLXNetworkAddress() const; + unsigned int getXLXNetworkPort() const; + unsigned int getXLXNetworkLocal() const; + std::string getXLXNetworkPassword() const; + std::string getXLXNetworkOptions() const; + unsigned int getXLXNetworkSlot() const; + unsigned int getXLXNetworkTG() const; + bool getXLXNetworkDebug() const; + +private: + std::string m_file; + bool m_daemon; + unsigned int m_xlxSlot; + unsigned int m_timeout; + + unsigned int m_logDisplayLevel; + unsigned int m_logFileLevel; + std::string m_logFilePath; + std::string m_logFileRoot; + + std::string m_mmdvmAddress; + unsigned int m_mmdvmPort; + unsigned int m_mmdvmLocal; + bool m_mmdvmDebug; + + std::string m_dmrNetworkAddress; + unsigned int m_dmrNetworkPort; + unsigned int m_dmrNetworkLocal; + std::string m_dmrNetworkPassword; + bool m_dmrNetworkDebug; + + std::string m_xlxNetworkAddress; + unsigned int m_xlxNetworkPort; + unsigned int m_xlxNetworkLocal; + std::string m_xlxNetworkPassword; + std::string m_xlxNetworkOptions; + unsigned int m_xlxNetworkSlot; + unsigned int m_xlxNetworkTG; + bool m_xlxNetworkDebug; +}; + +#endif diff --git a/DMRData.cpp b/DMRData.cpp new file mode 100644 index 0000000..2dd806a --- /dev/null +++ b/DMRData.cpp @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2015,2016,2017 Jonathan Naylor, G4KLX + * + * 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; version 2 of the License. + * + * 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. + */ + +#include "DMRData.h" +#include "DMRDefines.h" +#include "Utils.h" +#include "Log.h" + +#include +#include +#include + + +CDMRData::CDMRData(const CDMRData& data) : +m_slotNo(data.m_slotNo), +m_data(NULL), +m_srcId(data.m_srcId), +m_dstId(data.m_dstId), +m_flco(data.m_flco), +m_dataType(data.m_dataType), +m_seqNo(data.m_seqNo), +m_n(data.m_n), +m_ber(data.m_ber), +m_rssi(data.m_rssi), +m_streamId(data.m_streamId) +{ + m_data = new unsigned char[2U * DMR_FRAME_LENGTH_BYTES]; + ::memcpy(m_data, data.m_data, 2U * DMR_FRAME_LENGTH_BYTES); +} + +CDMRData::CDMRData() : +m_slotNo(1U), +m_data(NULL), +m_srcId(0U), +m_dstId(0U), +m_flco(FLCO_GROUP), +m_dataType(0U), +m_seqNo(0U), +m_n(0U), +m_ber(0U), +m_rssi(0U), +m_streamId(0U) +{ + m_data = new unsigned char[2U * DMR_FRAME_LENGTH_BYTES]; +} + +CDMRData::~CDMRData() +{ + delete[] m_data; +} + +CDMRData& CDMRData::operator=(const CDMRData& data) +{ + if (this != &data) { + ::memcpy(m_data, data.m_data, DMR_FRAME_LENGTH_BYTES); + + m_slotNo = data.m_slotNo; + m_srcId = data.m_srcId; + m_dstId = data.m_dstId; + m_flco = data.m_flco; + m_dataType = data.m_dataType; + m_seqNo = data.m_seqNo; + m_n = data.m_n; + m_ber = data.m_ber; + m_rssi = data.m_rssi; + m_streamId = data.m_streamId; + } + + return *this; +} + +unsigned int CDMRData::getSlotNo() const +{ + return m_slotNo; +} + +void CDMRData::setSlotNo(unsigned int slotNo) +{ + assert(slotNo == 1U || slotNo == 2U); + + m_slotNo = slotNo; +} + +unsigned char CDMRData::getDataType() const +{ + return m_dataType; +} + +void CDMRData::setDataType(unsigned char dataType) +{ + m_dataType = dataType; +} + +unsigned int CDMRData::getSrcId() const +{ + return m_srcId; +} + +void CDMRData::setSrcId(unsigned int id) +{ + m_srcId = id; +} + +unsigned int CDMRData::getDstId() const +{ + return m_dstId; +} + +void CDMRData::setDstId(unsigned int id) +{ + m_dstId = id; +} + +FLCO CDMRData::getFLCO() const +{ + return m_flco; +} + +void CDMRData::setFLCO(FLCO flco) +{ + m_flco = flco; +} + +unsigned char CDMRData::getSeqNo() const +{ + return m_seqNo; +} + +void CDMRData::setSeqNo(unsigned char seqNo) +{ + m_seqNo = seqNo; +} + +unsigned char CDMRData::getN() const +{ + return m_n; +} + +void CDMRData::setN(unsigned char n) +{ + m_n = n; +} + +unsigned char CDMRData::getBER() const +{ + return m_ber; +} + +void CDMRData::setBER(unsigned char ber) +{ + m_ber = ber; +} + +unsigned char CDMRData::getRSSI() const +{ + return m_rssi; +} + +void CDMRData::setRSSI(unsigned char rssi) +{ + m_rssi = rssi; +} + +unsigned int CDMRData::getData(unsigned char* buffer) const +{ + assert(buffer != NULL); + + ::memcpy(buffer, m_data, DMR_FRAME_LENGTH_BYTES); + + return DMR_FRAME_LENGTH_BYTES; +} + +void CDMRData::setData(const unsigned char* buffer) +{ + assert(buffer != NULL); + + ::memcpy(m_data, buffer, DMR_FRAME_LENGTH_BYTES); +} + +unsigned int CDMRData::getStreamId() const +{ + return m_streamId; +} + +void CDMRData::setStreamId(unsigned int id) +{ + m_streamId = id; +} diff --git a/DMRData.h b/DMRData.h new file mode 100644 index 0000000..b4ad00c --- /dev/null +++ b/DMRData.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2015,2016,2017 by Jonathan Naylor, G4KLX + * + * 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; version 2 of the License. + * + * 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. + */ + +#ifndef DMRData_H +#define DMRData_H + +#include "DMRDefines.h" + +class CDMRData { +public: + CDMRData(const CDMRData& data); + CDMRData(); + ~CDMRData(); + + CDMRData& operator=(const CDMRData& data); + + unsigned int getSlotNo() const; + void setSlotNo(unsigned int slotNo); + + unsigned int getSrcId() const; + void setSrcId(unsigned int id); + + unsigned int getDstId() const; + void setDstId(unsigned int id); + + FLCO getFLCO() const; + void setFLCO(FLCO flco); + + unsigned char getN() const; + void setN(unsigned char n); + + unsigned char getSeqNo() const; + void setSeqNo(unsigned char seqNo); + + unsigned char getDataType() const; + void setDataType(unsigned char dataType); + + unsigned char getBER() const; + void setBER(unsigned char ber); + + unsigned char getRSSI() const; + void setRSSI(unsigned char ber); + + void setData(const unsigned char* buffer); + unsigned int getData(unsigned char* buffer) const; + + void setStreamId(unsigned int id); + unsigned int getStreamId() const; + +private: + unsigned int m_slotNo; + unsigned char* m_data; + unsigned int m_srcId; + unsigned int m_dstId; + FLCO m_flco; + unsigned char m_dataType; + unsigned char m_seqNo; + unsigned char m_n; + unsigned char m_ber; + unsigned char m_rssi; + unsigned int m_streamId; +}; + +#endif diff --git a/DMRDefines.h b/DMRDefines.h new file mode 100644 index 0000000..b4fa2be --- /dev/null +++ b/DMRDefines.h @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#if !defined(DMRDefines_H) +#define DMRDefines_H + +const unsigned int DMR_FRAME_LENGTH_BITS = 264U; +const unsigned int DMR_FRAME_LENGTH_BYTES = 33U; + +const unsigned int DMR_SYNC_LENGTH_BITS = 48U; +const unsigned int DMR_SYNC_LENGTH_BYTES = 6U; + +const unsigned int DMR_EMB_LENGTH_BITS = 8U; +const unsigned int DMR_EMB_LENGTH_BYTES = 1U; + +const unsigned int DMR_SLOT_TYPE_LENGTH_BITS = 8U; +const unsigned int DMR_SLOT_TYPE_LENGTH_BYTES = 1U; + +const unsigned int DMR_EMBEDDED_SIGNALLING_LENGTH_BITS = 32U; +const unsigned int DMR_EMBEDDED_SIGNALLING_LENGTH_BYTES = 4U; + +const unsigned int DMR_AMBE_LENGTH_BITS = 108U * 2U; +const unsigned int DMR_AMBE_LENGTH_BYTES = 27U; + +const unsigned char BS_SOURCED_AUDIO_SYNC[] = {0x07U, 0x55U, 0xFDU, 0x7DU, 0xF7U, 0x5FU, 0x70U}; +const unsigned char BS_SOURCED_DATA_SYNC[] = {0x0DU, 0xFFU, 0x57U, 0xD7U, 0x5DU, 0xF5U, 0xD0U}; + +const unsigned char MS_SOURCED_AUDIO_SYNC[] = {0x07U, 0xF7U, 0xD5U, 0xDDU, 0x57U, 0xDFU, 0xD0U}; +const unsigned char MS_SOURCED_DATA_SYNC[] = {0x0DU, 0x5DU, 0x7FU, 0x77U, 0xFDU, 0x75U, 0x70U}; + +const unsigned char DIRECT_SLOT1_AUDIO_SYNC[] = {0x05U, 0xD5U, 0x77U, 0xF7U, 0x75U, 0x7FU, 0xF0U}; +const unsigned char DIRECT_SLOT1_DATA_SYNC[] = {0x0FU, 0x7FU, 0xDDU, 0x5DU, 0xDFU, 0xD5U, 0x50U}; + +const unsigned char DIRECT_SLOT2_AUDIO_SYNC[] = {0x07U, 0xDFU, 0xFDU, 0x5FU, 0x55U, 0xD5U, 0xF0U}; +const unsigned char DIRECT_SLOT2_DATA_SYNC[] = {0x0DU, 0x75U, 0x57U, 0xF5U, 0xFFU, 0x7FU, 0x50U}; + +const unsigned char SYNC_MASK[] = {0x0FU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xF0U}; + +const unsigned char DT_MASK = 0x0FU; +const unsigned char DT_VOICE_PI_HEADER = 0x00U; +const unsigned char DT_VOICE_LC_HEADER = 0x01U; +const unsigned char DT_TERMINATOR_WITH_LC = 0x02U; +const unsigned char DT_CSBK = 0x03U; +const unsigned char DT_DATA_HEADER = 0x06U; +const unsigned char DT_RATE_12_DATA = 0x07U; +const unsigned char DT_RATE_34_DATA = 0x08U; +const unsigned char DT_IDLE = 0x09U; +const unsigned char DT_RATE_1_DATA = 0x0AU; + +// Dummy values +const unsigned char DT_VOICE_SYNC = 0xF0U; +const unsigned char DT_VOICE = 0xF1U; + +const unsigned char DMR_IDLE_RX = 0x80U; +const unsigned char DMR_SYNC_DATA = 0x40U; +const unsigned char DMR_SYNC_AUDIO = 0x20U; + +const unsigned char DMR_SLOT1 = 0x00U; +const unsigned char DMR_SLOT2 = 0x80U; + +const unsigned char DPF_UDT = 0x00U; +const unsigned char DPF_RESPONSE = 0x01U; +const unsigned char DPF_UNCONFIRMED_DATA = 0x02U; +const unsigned char DPF_CONFIRMED_DATA = 0x03U; +const unsigned char DPF_DEFINED_SHORT = 0x0DU; +const unsigned char DPF_DEFINED_RAW = 0x0EU; +const unsigned char DPF_PROPRIETARY = 0x0FU; + +const unsigned char FID_ETSI = 0U; +const unsigned char FID_DMRA = 16U; + +enum FLCO { + FLCO_GROUP = 0, + FLCO_USER_USER = 3, + FLCO_TALKER_ALIAS_HEADER = 4, + FLCO_TALKER_ALIAS_BLOCK1 = 5, + FLCO_TALKER_ALIAS_BLOCK2 = 6, + FLCO_TALKER_ALIAS_BLOCK3 = 7, + FLCO_GPS_INFO = 8 +}; + +#endif diff --git a/DMRGateway.cpp b/DMRGateway.cpp new file mode 100644 index 0000000..5e470d4 --- /dev/null +++ b/DMRGateway.cpp @@ -0,0 +1,457 @@ +/* + * Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "DMRGateway.h" +#include "Version.h" +#include "StopWatch.h" +#include "Thread.h" +#include "Log.h" + +#include +#include + +#if !defined(_WIN32) && !defined(_WIN64) +#include +#include +#include +#include +#include +#endif + +#if defined(_WIN32) || defined(_WIN64) +const char* DEFAULT_INI_FILE = "DMRGateway.ini"; +#else +const char* DEFAULT_INI_FILE = "/etc/DMRGateway.ini"; +#endif + +static bool m_killed = false; +static int m_signal = 0; + +#if !defined(_WIN32) && !defined(_WIN64) +static void sigHandler(int signum) +{ + m_killed = true; + m_signal = signum; +} +#endif + +enum DMRGW_STATUS { + DMRGWS_NONE, + DMRGWS_NETWORK, + DMRGWS_REFLECTOR +}; + +const char* HEADER1 = "This software is for use on amateur radio networks only,"; +const char* HEADER2 = "it is to be used for educational purposes only. Its use on"; +const char* HEADER3 = "commercial networks is strictly prohibited."; +const char* HEADER4 = "Copyright(C) 2015-2017 by Jonathan Naylor, G4KLX and others"; + +int main(int argc, char** argv) +{ + const char* iniFile = DEFAULT_INI_FILE; + if (argc > 1) { + for (int currentArg = 1; currentArg < argc; ++currentArg) { + std::string arg = argv[currentArg]; + if ((arg == "-v") || (arg == "--version")) { + ::fprintf(stdout, "DMRGateway version %s\n", VERSION); + return 0; + } else if (arg.substr(0,1) == "-") { + ::fprintf(stderr, "Usage: DMRGateway [-v|--version] [filename]\n"); + return 1; + } else { + iniFile = argv[currentArg]; + } + } + } + +#if !defined(_WIN32) && !defined(_WIN64) + ::signal(SIGTERM, sigHandler); + ::signal(SIGHUP, sigHandler); +#endif + + int ret = 0; + + do { + m_signal = 0; + + CDMRGateway* host = new CDMRGateway(std::string(iniFile)); + ret = host->run(); + + delete host; + + if (m_signal == 15) + ::LogInfo("Caught SIGTERM, exiting"); + + if (m_signal == 1) + ::LogInfo("Caught SIGHUP, restarting"); + } while (m_signal == 1); + + ::LogFinalise(); + + return ret; +} + +CDMRGateway::CDMRGateway(const std::string& confFile) : +m_conf(confFile), +m_mmdvm(NULL), +m_dmrNetwork(NULL), +m_xlxNetwork(NULL), +m_reflector(0U) +{ +} + +CDMRGateway::~CDMRGateway() +{ +} + +int CDMRGateway::run() +{ + bool ret = m_conf.read(); + if (!ret) { + ::fprintf(stderr, "DMRGateway: cannot read the .ini file\n"); + return 1; + } + + ret = ::LogInitialise(m_conf.getLogFilePath(), m_conf.getLogFileRoot(), m_conf.getLogFileLevel(), m_conf.getLogDisplayLevel()); + if (!ret) { + ::fprintf(stderr, "DMRGateway: unable to open the log file\n"); + return 1; + } + +#if !defined(_WIN32) && !defined(_WIN64) + bool m_daemon = m_conf.getDaemon(); + if (m_daemon) { + // Create new process + pid_t pid = ::fork(); + if (pid == -1) { + ::LogWarning("Couldn't fork() , exiting"); + return -1; + } + else if (pid != 0) + exit(EXIT_SUCCESS); + + // Create new session and process group + if (::setsid() == -1){ + ::LogWarning("Couldn't setsid(), exiting"); + return -1; + } + + // Set the working directory to the root directory + if (::chdir("/") == -1){ + ::LogWarning("Couldn't cd /, exiting"); + return -1; + } + + ::close(STDIN_FILENO); + ::close(STDOUT_FILENO); + ::close(STDERR_FILENO); +#if !defined(HD44780) && !defined(OLED) + //If we are currently root... + if (getuid() == 0) { + struct passwd* user = ::getpwnam("mmdvm"); + if (user == NULL) { + ::LogError("Could not get the mmdvm user, exiting"); + return -1; + } + + uid_t mmdvm_uid = user->pw_uid; + gid_t mmdvm_gid = user->pw_gid; + + //Set user and group ID's to mmdvm:mmdvm + if (setgid(mmdvm_gid) != 0) { + ::LogWarning("Could not set mmdvm GID, exiting"); + return -1; + } + + if (setuid(mmdvm_uid) != 0) { + ::LogWarning("Could not set mmdvm UID, exiting"); + return -1; + } + + //Double check it worked (AKA Paranoia) + if (setuid(0) != -1){ + ::LogWarning("It's possible to regain root - something is wrong!, exiting"); + return -1; + } + + } + } +#else + ::LogWarning("Dropping root permissions in daemon mode is disabled with HD44780 display"); + } +#endif +#endif + + LogInfo(HEADER1); + LogInfo(HEADER2); + LogInfo(HEADER3); + LogInfo(HEADER4); + + LogMessage("DMRGateway-%s is starting", VERSION); + + ret = createMMDVM(); + if (!ret) + return 1; + + LogMessage("Waiting for MMDVM to connect....."); + + for (;;) { + CThread::sleep(100U); + + unsigned char config[400U]; + unsigned int len = m_mmdvm->getConfig(config); + if (len > 0U) + break; + } + + LogMessage("MMDVM has connected"); + + unsigned int xlxSlot = m_conf.getXLXSlot(); + unsigned int xlxNetworkSlot = m_conf.getXLXNetworkSlot(); + unsigned int xlxNetworkTG = m_conf.getXLXNetworkTG(); + unsigned int timeout = m_conf.getTimeout(); + + LogInfo("Id: %u", m_mmdvm->getId()); + LogInfo("XLX Local Slot: %u", xlxSlot); + LogInfo("XLX Reflector Slot: %u", xlxNetworkSlot); + LogInfo("XLX TG: %u", xlxNetworkTG); + LogInfo("Timeout: %us", timeout); + + + ret = createDMRNetwork(); + if (!ret) + return 1; + + ret = createXLXNetwork(); + if (!ret) + return 1; + + DMRGW_STATUS status = DMRGWS_NONE; + + CTimer timer(1000U, timeout); + + CStopWatch stopWatch; + stopWatch.start(); + + LogMessage("DMRGateway-%s is running", VERSION); + + while (!m_killed) { + CDMRData data; + + bool ret = m_mmdvm->read(data); + if (ret) { + unsigned int slotNo = data.getSlotNo(); + if (slotNo == xlxSlot) { + FLCO flco = data.getFLCO(); + unsigned int id = data.getDstId(); + + if (flco == FLCO_GROUP && id == xlxNetworkTG) { + data.setSlotNo(xlxNetworkSlot); + m_xlxNetwork->write(data); + status = DMRGWS_REFLECTOR; + timer.start(); + } else if (flco == FLCO_USER_USER) { + unsigned int reflector = data.getDstId(); + if (reflector != m_reflector) { + LogMessage("Switching to reflector %u", reflector); + m_reflector = reflector; + } + + data.setSlotNo(xlxNetworkSlot); + m_xlxNetwork->write(data); + status = DMRGWS_REFLECTOR; + timer.start(); + } else { + m_dmrNetwork->write(data); + status = DMRGWS_NETWORK; + timer.start(); + } + } else { + m_dmrNetwork->write(data); + } + } + + ret = m_xlxNetwork->read(data); + if (ret) { + if (status == DMRGWS_NONE || status == DMRGWS_REFLECTOR) { + unsigned int slotNo = data.getSlotNo(); + if (slotNo == xlxNetworkSlot) { + data.setSlotNo(xlxSlot); + m_mmdvm->write(data); + status = DMRGWS_REFLECTOR; + timer.start(); + } + } + } + + ret = m_dmrNetwork->read(data); + if (ret) { + unsigned int slotNo = data.getSlotNo(); + if (slotNo == xlxSlot) { + // Stop BM from using the same TG as XLX + unsigned int dstId = data.getDstId(); + FLCO flco = data.getFLCO(); + if (flco != FLCO_GROUP || dstId != xlxNetworkTG) { + if (status == DMRGWS_NONE || status == DMRGWS_NETWORK) { + m_mmdvm->write(data); + status = DMRGWS_NETWORK; + timer.start(); + } + } + } else { + m_mmdvm->write(data); + } + } + + unsigned int ms = stopWatch.elapsed(); + stopWatch.start(); + + m_mmdvm->clock(ms); + m_dmrNetwork->clock(ms); + m_xlxNetwork->clock(ms); + + timer.clock(ms); + if (timer.isRunning() && timer.hasExpired()) { + status = DMRGWS_NONE; + timer.stop(); + } + + if (ms < 10U) + CThread::sleep(10U); + } + + LogMessage("DMRGateway-%s is exiting on receipt of SIGHUP1", VERSION); + + m_mmdvm->close(); + delete m_mmdvm; + + m_dmrNetwork->close(); + delete m_dmrNetwork; + + m_xlxNetwork->close(); + delete m_xlxNetwork; + + return 0; +} + +bool CDMRGateway::createMMDVM() +{ + std::string address = m_conf.getMMDVMAddress(); + unsigned int port = m_conf.getMMDVMPort(); + unsigned int local = m_conf.getMMDVMLocal(); + bool debug = m_conf.getMMDVMDebug(); + + LogInfo("MMDVM Network Parameters"); + LogInfo(" Address: %s", address.c_str()); + LogInfo(" Port: %u", port); + if (local > 0U) + LogInfo(" Local: %u", local); + else + LogInfo(" Local: random"); + + m_mmdvm = new CMMDVMNetwork(address, port, local, debug); + + bool ret = m_mmdvm->open(); + if (!ret) { + delete m_mmdvm; + m_mmdvm = NULL; + return false; + } + + return true; +} + +bool CDMRGateway::createDMRNetwork() +{ + std::string address = m_conf.getDMRNetworkAddress(); + unsigned int port = m_conf.getDMRNetworkPort(); + unsigned int local = m_conf.getDMRNetworkLocal(); + unsigned int id = m_mmdvm->getId(); + std::string password = m_conf.getDMRNetworkPassword(); + bool debug = m_conf.getDMRNetworkDebug(); + + LogInfo("DMR Network Parameters"); + LogInfo(" Address: %s", address.c_str()); + LogInfo(" Port: %u", port); + if (local > 0U) + LogInfo(" Local: %u", local); + else + LogInfo(" Local: random"); + + m_dmrNetwork = new CDMRNetwork(address, port, local, id, password, debug); + + std::string options = m_mmdvm->getOptions(); + if (!options.empty()) { + LogInfo(" Options: %s", options.c_str()); + m_dmrNetwork->setOptions(options); + } + + unsigned char config[400U]; + unsigned int len = m_mmdvm->getConfig(config); + + m_dmrNetwork->setConfig(config, len); + + bool ret = m_dmrNetwork->open(); + if (!ret) { + delete m_dmrNetwork; + m_dmrNetwork = NULL; + return false; + } + + return true; +} + +bool CDMRGateway::createXLXNetwork() +{ + std::string address = m_conf.getXLXNetworkAddress(); + unsigned int port = m_conf.getXLXNetworkPort(); + unsigned int local = m_conf.getXLXNetworkLocal(); + unsigned int id = m_mmdvm->getId(); + std::string password = m_conf.getXLXNetworkPassword(); + std::string options = m_conf.getXLXNetworkOptions(); + bool debug = m_conf.getXLXNetworkDebug(); + + LogInfo("XLX Network Parameters"); + LogInfo(" Address: %s", address.c_str()); + LogInfo(" Port: %u", port); + if (local > 0U) + LogInfo(" Local: %u", local); + else + LogInfo(" Local: random"); + + m_xlxNetwork = new CDMRNetwork(address, port, local, id, password, debug); + + if (!options.empty()) { + LogInfo(" Options: %s", options.c_str()); + m_xlxNetwork->setOptions(options); + } + + unsigned char config[400U]; + unsigned int len = m_mmdvm->getConfig(config); + + m_xlxNetwork->setConfig(config, len); + + bool ret = m_xlxNetwork->open(); + if (!ret) { + delete m_xlxNetwork; + m_xlxNetwork = NULL; + return false; + } + + return true; +} diff --git a/DMRGateway.h b/DMRGateway.h new file mode 100644 index 0000000..6a5be31 --- /dev/null +++ b/DMRGateway.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#if !defined(DMRGateway_H) +#define DMRGateway_H + +#include "MMDVMNetwork.h" +#include "DMRNetwork.h" +#include "Conf.h" + +#include + +class CDMRGateway +{ +public: + CDMRGateway(const std::string& confFile); + ~CDMRGateway(); + + int run(); + +private: + CConf m_conf; + CMMDVMNetwork* m_mmdvm; + CDMRNetwork* m_dmrNetwork; + CDMRNetwork* m_xlxNetwork; + unsigned int m_reflector; + + bool createMMDVM(); + bool createDMRNetwork(); + bool createXLXNetwork(); +}; + +#endif diff --git a/DMRGateway.ini b/DMRGateway.ini new file mode 100644 index 0000000..01ff5d8 --- /dev/null +++ b/DMRGateway.ini @@ -0,0 +1,34 @@ +[General] +XLXSlot=2 +Timeout=10 +Daemon=0 + +[Log] +# Logging levels, 0=No logging +DisplayLevel=1 +FileLevel=1 +FilePath=. +FileRoot=DMRGateway + +[MMDVM] +Address=44.131.4.1 +Port=62031 +# Local=3350 +Debug=0 + +[XLX Network] +Address=xlx950.epf.lu +Port=55555 +# Local=3351 +# Options= +Password=passw0rd +Slot=2 +TG=9 +Debug=0 + +[DMR Network] +Address=44.131.4.1 +Port=62031 +# Local=3352 +Password=PASSWORD +Debug=0 diff --git a/DMRGateway.sln b/DMRGateway.sln new file mode 100644 index 0000000..83dca23 --- /dev/null +++ b/DMRGateway.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DMRGateway", "DMRGateway.vcxproj", "{862A3182-C71A-4B01-B58D-5F0725927A56}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {862A3182-C71A-4B01-B58D-5F0725927A56}.Debug|x64.ActiveCfg = Debug|x64 + {862A3182-C71A-4B01-B58D-5F0725927A56}.Debug|x64.Build.0 = Debug|x64 + {862A3182-C71A-4B01-B58D-5F0725927A56}.Debug|x86.ActiveCfg = Debug|Win32 + {862A3182-C71A-4B01-B58D-5F0725927A56}.Debug|x86.Build.0 = Debug|Win32 + {862A3182-C71A-4B01-B58D-5F0725927A56}.Release|x64.ActiveCfg = Release|x64 + {862A3182-C71A-4B01-B58D-5F0725927A56}.Release|x64.Build.0 = Release|x64 + {862A3182-C71A-4B01-B58D-5F0725927A56}.Release|x86.ActiveCfg = Release|Win32 + {862A3182-C71A-4B01-B58D-5F0725927A56}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/DMRGateway.vcxproj b/DMRGateway.vcxproj new file mode 100644 index 0000000..b3a07e6 --- /dev/null +++ b/DMRGateway.vcxproj @@ -0,0 +1,182 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + {862A3182-C71A-4B01-B58D-5F0725927A56} + Win32Proj + DMRGateway + 8.1 + + + + Application + true + v140 + Unicode + + + Application + false + v140 + true + Unicode + + + Application + true + v140 + Unicode + + + Application + false + v140 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + + + true + + + false + + + false + + + + + + Level3 + Disabled + _CRT_SECURE_NO_WARNINGS + + + Console + true + wsock32.lib;%(AdditionalDependencies) + + + + + + + Level3 + Disabled + _CRT_SECURE_NO_WARNINGS + + + Console + true + wsock32.lib;%(AdditionalDependencies) + + + + + Level3 + + + MaxSpeed + true + true + _CRT_SECURE_NO_WARNINGS + + + Console + true + true + true + wsock32.lib;%(AdditionalDependencies) + + + + + Level3 + + + MaxSpeed + true + true + _CRT_SECURE_NO_WARNINGS + + + Console + true + true + true + wsock32.lib;%(AdditionalDependencies) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/DMRGateway.vcxproj.filters b/DMRGateway.vcxproj.filters new file mode 100644 index 0000000..c7a6705 --- /dev/null +++ b/DMRGateway.vcxproj.filters @@ -0,0 +1,98 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/DMRNetwork.cpp b/DMRNetwork.cpp new file mode 100644 index 0000000..00af2a7 --- /dev/null +++ b/DMRNetwork.cpp @@ -0,0 +1,469 @@ +/* + * Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "DMRNetwork.h" + +#include "StopWatch.h" +#include "SHA256.h" +#include "Utils.h" +#include "Log.h" + +#include +#include + +const unsigned int BUFFER_LENGTH = 500U; + +const unsigned int HOMEBREW_DATA_PACKET_LENGTH = 55U; + + +CDMRNetwork::CDMRNetwork(const std::string& address, unsigned int port, unsigned int local, unsigned int id, const std::string& password, bool debug) : +m_address(), +m_port(port), +m_id(NULL), +m_password(password), +m_debug(debug), +m_socket(local), +m_status(WAITING_CONNECT), +m_retryTimer(1000U, 10U), +m_timeoutTimer(1000U, 60U), +m_buffer(NULL), +m_salt(NULL), +m_rxData(1000U, "DMR Network"), +m_options(), +m_configData(NULL), +m_configLen(0U), +m_beacon(false) +{ + assert(!address.empty()); + assert(port > 0U); + assert(id > 1000U); + assert(!password.empty()); + + m_address = CUDPSocket::lookup(address); + + m_buffer = new unsigned char[BUFFER_LENGTH]; + m_salt = new unsigned char[sizeof(uint32_t)]; + m_id = new uint8_t[4U]; + + m_id[0U] = id >> 24; + m_id[1U] = id >> 16; + m_id[2U] = id >> 8; + m_id[3U] = id >> 0; + + CStopWatch stopWatch; + ::srand(stopWatch.start()); +} + +CDMRNetwork::~CDMRNetwork() +{ + delete[] m_buffer; + delete[] m_salt; + delete[] m_id; +} + +void CDMRNetwork::setOptions(const std::string& options) +{ + m_options = options; +} + +void CDMRNetwork::setConfig(const unsigned char* data, unsigned int len) +{ + m_configData = new unsigned char[len]; + ::memcpy(m_configData, data, len); + + m_configLen = len; +} + +bool CDMRNetwork::open() +{ + LogMessage("DMR, Opening DMR Network"); + + m_status = WAITING_CONNECT; + m_timeoutTimer.stop(); + m_retryTimer.start(); + + return true; +} + +bool CDMRNetwork::read(CDMRData& data) +{ + if (m_status != RUNNING) + return false; + + if (m_rxData.isEmpty()) + return false; + + unsigned char length = 0U; + + m_rxData.getData(&length, 1U); + m_rxData.getData(m_buffer, length); + + // Is this a data packet? + if (::memcmp(m_buffer, "DMRD", 4U) != 0) + return false; + + unsigned char seqNo = m_buffer[4U]; + + unsigned int srcId = (m_buffer[5U] << 16) | (m_buffer[6U] << 8) | (m_buffer[7U] << 0); + + unsigned int dstId = (m_buffer[8U] << 16) | (m_buffer[9U] << 8) | (m_buffer[10U] << 0); + + unsigned int slotNo = (m_buffer[15U] & 0x80U) == 0x80U ? 2U : 1U; + + FLCO flco = (m_buffer[15U] & 0x40U) == 0x40U ? FLCO_USER_USER : FLCO_GROUP; + + unsigned int streamId; + ::memcpy(&streamId, m_buffer + 16U, 4U); + + data.setSeqNo(seqNo); + data.setSlotNo(slotNo); + data.setSrcId(srcId); + data.setDstId(dstId); + data.setFLCO(flco); + data.setStreamId(streamId); + + bool dataSync = (m_buffer[15U] & 0x20U) == 0x20U; + bool voiceSync = (m_buffer[15U] & 0x10U) == 0x10U; + + if (dataSync) { + unsigned char dataType = m_buffer[15U] & 0x0FU; + data.setData(m_buffer + 20U); + data.setDataType(dataType); + data.setN(0U); + } else if (voiceSync) { + data.setData(m_buffer + 20U); + data.setDataType(DT_VOICE_SYNC); + data.setN(0U); + } else { + unsigned char n = m_buffer[15U] & 0x0FU; + data.setData(m_buffer + 20U); + data.setDataType(DT_VOICE); + data.setN(n); + } + + return true; +} + +bool CDMRNetwork::write(const CDMRData& data) +{ + if (m_status != RUNNING) + return false; + + unsigned char buffer[HOMEBREW_DATA_PACKET_LENGTH]; + ::memset(buffer, 0x00U, HOMEBREW_DATA_PACKET_LENGTH); + + buffer[0U] = 'D'; + buffer[1U] = 'M'; + buffer[2U] = 'R'; + buffer[3U] = 'D'; + + unsigned int srcId = data.getSrcId(); + buffer[5U] = srcId >> 16; + buffer[6U] = srcId >> 8; + buffer[7U] = srcId >> 0; + + unsigned int dstId = data.getDstId(); + buffer[8U] = dstId >> 16; + buffer[9U] = dstId >> 8; + buffer[10U] = dstId >> 0; + + ::memcpy(buffer + 11U, m_id, 4U); + + unsigned int slotNo = data.getSlotNo(); + + buffer[15U] = slotNo == 1U ? 0x00U : 0x80U; + + FLCO flco = data.getFLCO(); + buffer[15U] |= flco == FLCO_GROUP ? 0x00U : 0x40U; + + unsigned char dataType = data.getDataType(); + if (dataType == DT_VOICE_SYNC) { + buffer[15U] |= 0x10U; + } else if (dataType == DT_VOICE) { + buffer[15U] |= data.getN(); + } else { + buffer[15U] |= (0x20U | dataType); + } + + buffer[4U] = data.getSeqNo(); + + unsigned int streamId = data.getStreamId(); + ::memcpy(buffer + 16U, &streamId, 4U); + + data.getData(buffer + 20U); + + buffer[53U] = data.getBER(); + + buffer[54U] = data.getRSSI(); + + if (m_debug) + CUtils::dump(1U, "Network Transmitted", buffer, HOMEBREW_DATA_PACKET_LENGTH); + + write(buffer, HOMEBREW_DATA_PACKET_LENGTH); + + return true; +} + +void CDMRNetwork::close() +{ + LogMessage("DMR, Closing DMR Network"); + + if (m_status == RUNNING) { + unsigned char buffer[9U]; + ::memcpy(buffer + 0U, "RPTCL", 5U); + ::memcpy(buffer + 5U, m_id, 4U); + write(buffer, 9U); + } + + m_socket.close(); + + m_retryTimer.stop(); + m_timeoutTimer.stop(); +} + +void CDMRNetwork::clock(unsigned int ms) +{ + if (m_status == WAITING_CONNECT) { + m_retryTimer.clock(ms); + if (m_retryTimer.isRunning() && m_retryTimer.hasExpired()) { + bool ret = m_socket.open(); + if (ret) { + ret = writeLogin(); + if (!ret) + return; + + m_status = WAITING_LOGIN; + m_timeoutTimer.start(); + } + + m_retryTimer.start(); + } + + return; + } + + in_addr address; + unsigned int port; + int length = m_socket.read(m_buffer, BUFFER_LENGTH, address, port); + if (length < 0) { + LogError("DMR, Socket has failed, retrying connection to the master"); + close(); + open(); + return; + } + + // if (m_debug && length > 0) + // CUtils::dump(1U, "Network Received", m_buffer, length); + + if (length > 0 && m_address.s_addr == address.s_addr && m_port == port) { + if (::memcmp(m_buffer, "DMRD", 4U) == 0) { + if (m_debug) + CUtils::dump(1U, "Network Received", m_buffer, length); + + unsigned char len = length; + m_rxData.addData(&len, 1U); + m_rxData.addData(m_buffer, len); + } else if (::memcmp(m_buffer, "MSTNAK", 6U) == 0) { + if (m_status == RUNNING) { + LogWarning("DMR, The master is restarting, logging back in"); + m_status = WAITING_LOGIN; + m_timeoutTimer.start(); + m_retryTimer.start(); + } else { + /* Once the modem death spiral has been prevented in Modem.cpp + the Network sometimes times out and reaches here. + We want it to reconnect so... */ + LogError("DMR, Login to the master has failed, retrying ..."); + close(); + open(); + return; + } + } else if (::memcmp(m_buffer, "RPTACK", 6U) == 0) { + switch (m_status) { + case WAITING_LOGIN: + LogDebug("DMR, Sending authorisation"); + ::memcpy(m_salt, m_buffer + 6U, sizeof(uint32_t)); + writeAuthorisation(); + m_status = WAITING_AUTHORISATION; + m_timeoutTimer.start(); + m_retryTimer.start(); + break; + case WAITING_AUTHORISATION: + LogDebug("DMR, Sending configuration"); + writeConfig(); + m_status = WAITING_CONFIG; + m_timeoutTimer.start(); + m_retryTimer.start(); + break; + case WAITING_CONFIG: + if (m_options.empty()) { + LogMessage("DMR, Logged into the master successfully"); + m_status = RUNNING; + } else { + LogDebug("DMR, Sending options"); + writeOptions(); + m_status = WAITING_OPTIONS; + } + m_timeoutTimer.start(); + m_retryTimer.start(); + break; + case WAITING_OPTIONS: + LogMessage("DMR, Logged into the master successfully"); + m_status = RUNNING; + m_timeoutTimer.start(); + m_retryTimer.start(); + break; + default: + break; + } + } else if (::memcmp(m_buffer, "MSTCL", 5U) == 0) { + LogError("DMR, Master is closing down"); + close(); + open(); + } else if (::memcmp(m_buffer, "MSTPONG", 7U) == 0) { + m_timeoutTimer.start(); + } else if (::memcmp(m_buffer, "RPTSBKN", 7U) == 0) { + m_beacon = true; + } else { + CUtils::dump("Unknown packet from the master", m_buffer, length); + } + } + + m_retryTimer.clock(ms); + if (m_retryTimer.isRunning() && m_retryTimer.hasExpired()) { + switch (m_status) { + case WAITING_LOGIN: + writeLogin(); + break; + case WAITING_AUTHORISATION: + writeAuthorisation(); + break; + case WAITING_OPTIONS: + writeOptions(); + break; + case WAITING_CONFIG: + writeConfig(); + break; + case RUNNING: + writePing(); + break; + default: + break; + } + + m_retryTimer.start(); + } + + m_timeoutTimer.clock(ms); + if (m_timeoutTimer.isRunning() && m_timeoutTimer.hasExpired()) { + LogError("DMR, Connection to the master has timed out, retrying connection"); + close(); + open(); + } +} + +bool CDMRNetwork::writeLogin() +{ + unsigned char buffer[8U]; + + ::memcpy(buffer + 0U, "RPTL", 4U); + ::memcpy(buffer + 4U, m_id, 4U); + + return write(buffer, 8U); +} + +bool CDMRNetwork::writeAuthorisation() +{ + size_t size = m_password.size(); + + unsigned char* in = new unsigned char[size + sizeof(uint32_t)]; + ::memcpy(in, m_salt, sizeof(uint32_t)); + for (size_t i = 0U; i < size; i++) + in[i + sizeof(uint32_t)] = m_password.at(i); + + unsigned char out[40U]; + ::memcpy(out + 0U, "RPTK", 4U); + ::memcpy(out + 4U, m_id, 4U); + + CSHA256 sha256; + sha256.buffer(in, (unsigned int)(size + sizeof(uint32_t)), out + 8U); + + delete[] in; + + return write(out, 40U); +} + +bool CDMRNetwork::writeOptions() +{ + char buffer[300U]; + + ::memcpy(buffer + 0U, "RPTO", 4U); + ::memcpy(buffer + 4U, m_id, 4U); + ::strcpy(buffer + 8U, m_options.c_str()); + + return write((unsigned char*)buffer, (unsigned int)m_options.length() + 8U); +} + +bool CDMRNetwork::writeConfig() +{ + char buffer[400U]; + + ::memcpy(buffer + 0U, "RPTC", 4U); + ::memcpy(buffer + 4U, m_id, 4U); + ::memcpy(buffer + 8U, m_configData, m_configLen); + + return write((unsigned char*)buffer, m_configLen + 8U); +} + +bool CDMRNetwork::writePing() +{ + unsigned char buffer[11U]; + + ::memcpy(buffer + 0U, "RPTPING", 7U); + ::memcpy(buffer + 7U, m_id, 4U); + + return write(buffer, 11U); +} + +bool CDMRNetwork::wantsBeacon() +{ + bool beacon = m_beacon; + + m_beacon = false; + + return beacon; +} + +bool CDMRNetwork::write(const unsigned char* data, unsigned int length) +{ + assert(data != NULL); + assert(length > 0U); + + // if (m_debug) + // CUtils::dump(1U, "Network Transmitted", data, length); + + bool ret = m_socket.write(data, length, m_address, m_port); + if (!ret) { + LogError("DMR, Socket has failed when writing data to the master, retrying connection"); + m_socket.close(); + open(); + return false; + } + + return true; +} diff --git a/DMRNetwork.h b/DMRNetwork.h new file mode 100644 index 0000000..8434c7e --- /dev/null +++ b/DMRNetwork.h @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#if !defined(DMRNetwork_H) +#define DMRNetwork_H + +#include "UDPSocket.h" +#include "Timer.h" +#include "RingBuffer.h" +#include "DMRData.h" + +#include +#include + +class CDMRNetwork +{ +public: + CDMRNetwork(const std::string& address, unsigned int port, unsigned int local, unsigned int id, const std::string& password, bool debug); + ~CDMRNetwork(); + + void setOptions(const std::string& options); + + void setConfig(const unsigned char* config, unsigned int len); + + bool open(); + + bool read(CDMRData& data); + + bool write(const CDMRData& data); + + bool wantsBeacon(); + + void clock(unsigned int ms); + + void close(); + +private: + in_addr m_address; + unsigned int m_port; + uint8_t* m_id; + std::string m_password; + bool m_debug; + CUDPSocket m_socket; + + enum STATUS { + WAITING_CONNECT, + WAITING_LOGIN, + WAITING_AUTHORISATION, + WAITING_CONFIG, + WAITING_OPTIONS, + RUNNING + }; + + STATUS m_status; + CTimer m_retryTimer; + CTimer m_timeoutTimer; + unsigned char* m_buffer; + unsigned char* m_salt; + + CRingBuffer m_rxData; + + std::string m_options; + + unsigned char* m_configData; + unsigned int m_configLen; + + bool m_beacon; + + bool writeLogin(); + bool writeAuthorisation(); + bool writeOptions(); + bool writeConfig(); + bool writePing(); + + bool write(const unsigned char* data, unsigned int length); +}; + +#endif diff --git a/LICENCE b/LICENCE new file mode 100644 index 0000000..3912109 --- /dev/null +++ b/LICENCE @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin St, 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. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, 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 or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +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 give any other recipients of the Program a copy of this License +along with the Program. + +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 Program or any portion +of it, thus forming a work based on the Program, 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) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +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 Program, 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 Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) 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; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, 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 executable. However, as a +special exception, the source code 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. + +If distribution of executable or 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 counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program 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. + + 5. 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 Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program 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 to +this License. + + 7. 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 Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program 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 Program. + +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. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program 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. + + 9. The Free Software Foundation may publish revised and/or new versions +of the 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 Program +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 Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, 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 + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "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 PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. 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 PROGRAM 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 PROGRAM (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 PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), 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 Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. 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. + + + Copyright (C) + + 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 St, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/Log.cpp b/Log.cpp new file mode 100644 index 0000000..fc37ebf --- /dev/null +++ b/Log.cpp @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2015,2016 by Jonathan Naylor G4KLX + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "Log.h" + +#if defined(_WIN32) || defined(_WIN64) +#include +#else +#include +#endif + +#include +#include +#include +#include +#include +#include + +static unsigned int m_fileLevel = 2U; +static std::string m_filePath; +static std::string m_fileRoot; + +static FILE* m_fpLog = NULL; + +static unsigned int m_displayLevel = 2U; + +static struct tm m_tm; + +static char LEVELS[] = " DMIWEF"; + +static bool LogOpen() +{ + if (m_fileLevel == 0U) + return true; + + time_t now; + ::time(&now); + + struct tm* tm = ::gmtime(&now); + + if (tm->tm_mday == m_tm.tm_mday && tm->tm_mon == m_tm.tm_mon && tm->tm_year == m_tm.tm_year) { + if (m_fpLog != NULL) + return true; + } else { + if (m_fpLog != NULL) + ::fclose(m_fpLog); + } + + char filename[100U]; +#if defined(_WIN32) || defined(_WIN64) + ::sprintf(filename, "%s\\%s-%04d-%02d-%02d.log", m_filePath.c_str(), m_fileRoot.c_str(), tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday); +#else + ::sprintf(filename, "%s/%s-%04d-%02d-%02d.log", m_filePath.c_str(), m_fileRoot.c_str(), tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday); +#endif + + m_fpLog = ::fopen(filename, "a+t"); + m_tm = *tm; + + return m_fpLog != NULL; +} + +bool LogInitialise(const std::string& filePath, const std::string& fileRoot, unsigned int fileLevel, unsigned int displayLevel) +{ + m_filePath = filePath; + m_fileRoot = fileRoot; + m_fileLevel = fileLevel; + m_displayLevel = displayLevel; + return ::LogOpen(); +} + +void LogFinalise() +{ + if (m_fpLog != NULL) + ::fclose(m_fpLog); +} + +void Log(unsigned int level, const char* fmt, ...) +{ + assert(fmt != NULL); + + char buffer[300U]; +#if defined(_WIN32) || defined(_WIN64) + SYSTEMTIME st; + ::GetSystemTime(&st); + + ::sprintf(buffer, "%c: %04u-%02u-%02u %02u:%02u:%02u.%03u ", LEVELS[level], st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds); +#else + struct timeval now; + ::gettimeofday(&now, NULL); + + struct tm* tm = ::gmtime(&now.tv_sec); + + ::sprintf(buffer, "%c: %04d-%02d-%02d %02d:%02d:%02d.%03lu ", LEVELS[level], tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, now.tv_usec / 1000U); +#endif + + va_list vl; + va_start(vl, fmt); + + ::vsprintf(buffer + ::strlen(buffer), fmt, vl); + + va_end(vl); + + if (level >= m_fileLevel && m_fileLevel != 0U) { + bool ret = ::LogOpen(); + if (!ret) + return; + + ::fprintf(m_fpLog, "%s\n", buffer); + ::fflush(m_fpLog); + } + + if (level >= m_displayLevel && m_displayLevel != 0U) { + ::fprintf(stdout, "%s\n", buffer); + ::fflush(stdout); + } + + if (level == 6U) { // Fatal + ::fclose(m_fpLog); + exit(1); + } +} diff --git a/Log.h b/Log.h new file mode 100644 index 0000000..d671ef9 --- /dev/null +++ b/Log.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2015,2016 by Jonathan Naylor G4KLX + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#if !defined(LOG_H) +#define LOG_H + +#include + +#define LogDebug(fmt, ...) Log(1U, fmt, ##__VA_ARGS__) +#define LogMessage(fmt, ...) Log(2U, fmt, ##__VA_ARGS__) +#define LogInfo(fmt, ...) Log(3U, fmt, ##__VA_ARGS__) +#define LogWarning(fmt, ...) Log(4U, fmt, ##__VA_ARGS__) +#define LogError(fmt, ...) Log(5U, fmt, ##__VA_ARGS__) +#define LogFatal(fmt, ...) Log(6U, fmt, ##__VA_ARGS__) + +extern void Log(unsigned int level, const char* fmt, ...); + +extern bool LogInitialise(const std::string& filePath, const std::string& fileRoot, unsigned int fileLevel, unsigned int displayLevel); +extern void LogFinalise(); + +#endif diff --git a/MMDVMNetwork.cpp b/MMDVMNetwork.cpp new file mode 100644 index 0000000..d6e49df --- /dev/null +++ b/MMDVMNetwork.cpp @@ -0,0 +1,279 @@ +/* + * Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "MMDVMNetwork.h" + +#include "StopWatch.h" +#include "SHA256.h" +#include "Utils.h" +#include "Log.h" + +#include +#include + +const unsigned int BUFFER_LENGTH = 500U; + +const unsigned int HOMEBREW_DATA_PACKET_LENGTH = 55U; + + +CMMDVMNetwork::CMMDVMNetwork(const std::string& address, unsigned int port, unsigned int local, bool debug) : +m_address(), +m_port(port), +m_id(0U), +m_netId(NULL), +m_debug(debug), +m_socket(local), +m_buffer(NULL), +m_rxData(1000U, "MMDVM Network"), +m_options(), +m_configData(NULL), +m_configLen(0U) +{ + assert(!address.empty()); + assert(port > 0U); + + m_address = CUDPSocket::lookup(address); + + m_buffer = new unsigned char[BUFFER_LENGTH]; + m_netId = new unsigned char[4U]; + + CStopWatch stopWatch; + ::srand(stopWatch.start()); +} + +CMMDVMNetwork::~CMMDVMNetwork() +{ + delete[] m_netId; + delete[] m_buffer; + delete[] m_configData; +} + +std::string CMMDVMNetwork::getOptions() const +{ + return m_options; +} + +unsigned int CMMDVMNetwork::getConfig(unsigned char* config) const +{ + if (m_configData == 0U) + return 0U; + + ::memcpy(config, m_configData, m_configLen); + + return m_configLen; +} + +unsigned int CMMDVMNetwork::getId() const +{ + return m_id; +} + +bool CMMDVMNetwork::open() +{ + LogMessage("DMR, Opening MMDVM Network"); + + return m_socket.open(); +} + +bool CMMDVMNetwork::read(CDMRData& data) +{ + if (m_rxData.isEmpty()) + return false; + + unsigned char length = 0U; + + m_rxData.getData(&length, 1U); + m_rxData.getData(m_buffer, length); + + // Is this a data packet? + if (::memcmp(m_buffer, "DMRD", 4U) != 0) + return false; + + unsigned char seqNo = m_buffer[4U]; + + unsigned int srcId = (m_buffer[5U] << 16) | (m_buffer[6U] << 8) | (m_buffer[7U] << 0); + + unsigned int dstId = (m_buffer[8U] << 16) | (m_buffer[9U] << 8) | (m_buffer[10U] << 0); + + unsigned int slotNo = (m_buffer[15U] & 0x80U) == 0x80U ? 2U : 1U; + + FLCO flco = (m_buffer[15U] & 0x40U) == 0x40U ? FLCO_USER_USER : FLCO_GROUP; + + unsigned int streamId; + ::memcpy(&streamId, m_buffer + 16U, 4U); + + data.setSeqNo(seqNo); + data.setSlotNo(slotNo); + data.setSrcId(srcId); + data.setDstId(dstId); + data.setFLCO(flco); + data.setStreamId(streamId); + + bool dataSync = (m_buffer[15U] & 0x20U) == 0x20U; + bool voiceSync = (m_buffer[15U] & 0x10U) == 0x10U; + + if (dataSync) { + unsigned char dataType = m_buffer[15U] & 0x0FU; + data.setData(m_buffer + 20U); + data.setDataType(dataType); + data.setN(0U); + } else if (voiceSync) { + data.setData(m_buffer + 20U); + data.setDataType(DT_VOICE_SYNC); + data.setN(0U); + } else { + unsigned char n = m_buffer[15U] & 0x0FU; + data.setData(m_buffer + 20U); + data.setDataType(DT_VOICE); + data.setN(n); + } + + return true; +} + +bool CMMDVMNetwork::write(const CDMRData& data) +{ + unsigned char buffer[HOMEBREW_DATA_PACKET_LENGTH]; + ::memset(buffer, 0x00U, HOMEBREW_DATA_PACKET_LENGTH); + + buffer[0U] = 'D'; + buffer[1U] = 'M'; + buffer[2U] = 'R'; + buffer[3U] = 'D'; + + unsigned int srcId = data.getSrcId(); + buffer[5U] = srcId >> 16; + buffer[6U] = srcId >> 8; + buffer[7U] = srcId >> 0; + + unsigned int dstId = data.getDstId(); + buffer[8U] = dstId >> 16; + buffer[9U] = dstId >> 8; + buffer[10U] = dstId >> 0; + + ::memcpy(buffer + 11U, m_netId, 4U); + + unsigned int slotNo = data.getSlotNo(); + + buffer[15U] = slotNo == 1U ? 0x00U : 0x80U; + + FLCO flco = data.getFLCO(); + buffer[15U] |= flco == FLCO_GROUP ? 0x00U : 0x40U; + + unsigned char dataType = data.getDataType(); + if (dataType == DT_VOICE_SYNC) { + buffer[15U] |= 0x10U; + } else if (dataType == DT_VOICE) { + buffer[15U] |= data.getN(); + } else { + buffer[15U] |= (0x20U | dataType); + } + + buffer[4U] = data.getSeqNo(); + + unsigned int streamId = data.getStreamId(); + ::memcpy(buffer + 16U, &streamId, 4U); + + data.getData(buffer + 20U); + + buffer[53U] = data.getBER(); + + buffer[54U] = data.getRSSI(); + + if (m_debug) + CUtils::dump(1U, "Network Transmitted", buffer, HOMEBREW_DATA_PACKET_LENGTH); + + m_socket.write(buffer, HOMEBREW_DATA_PACKET_LENGTH, m_address, m_port); + + return true; +} + +void CMMDVMNetwork::close() +{ + LogMessage("DMR, Closing MMDVM Network"); + + m_socket.close(); +} + +void CMMDVMNetwork::clock(unsigned int ms) +{ + in_addr address; + unsigned int port; + int length = m_socket.read(m_buffer, BUFFER_LENGTH, address, port); + if (length < 0) { + LogError("DMR, Socket has failed, reopening"); + close(); + open(); + return; + } + + // if (m_debug && length > 0) + // CUtils::dump(1U, "Network Received", m_buffer, length); + + if (length > 0 && m_address.s_addr == address.s_addr && m_port == port) { + if (::memcmp(m_buffer, "DMRD", 4U) == 0) { + if (m_debug) + CUtils::dump(1U, "Network Received", m_buffer, length); + + unsigned char len = length; + m_rxData.addData(&len, 1U); + m_rxData.addData(m_buffer, len); + } + else if (::memcmp(m_buffer, "RPTL", 4U) == 0) { + m_id = (m_buffer[4U] << 24) | (m_buffer[5U] << 16) | (m_buffer[6U] << 8) | (m_buffer[7U] << 0); + ::memcpy(m_netId, m_buffer + 4U, 4U); + + unsigned char ack[10U]; + ::memcpy(ack + 0U, "RPTACK", 6U); + + uint32_t salt = 1U; + ::memcpy(ack + 6U, &salt, sizeof(uint32_t)); + + m_socket.write(ack, 10U, m_address, m_port); + } + else if (::memcmp(m_buffer, "RPTK", 4U) == 0) { + unsigned char ack[6U]; + ::memcpy(ack, "RPTACK", 6U); + m_socket.write(ack, 6U, m_address, m_port); + } + else if (::memcmp(m_buffer, "RPTC", 4U) == 0) { + m_configLen = length - 8U; + m_configData = new unsigned char[m_configLen]; + ::memcpy(m_configData, m_buffer + 8U, m_configLen); + + unsigned char ack[6U]; + ::memcpy(ack, "RPTACK", 6U); + m_socket.write(ack, 6U, m_address, m_port); + } + else if (::memcmp(m_buffer, "RPTO", 4U) == 0) { + m_options = std::string((char*)(m_buffer + 8U), length - 8U); + + unsigned char ack[6U]; + ::memcpy(ack, "RPTACK", 6U); + m_socket.write(ack, 6U, m_address, m_port); + } + else if (::memcmp(m_buffer, "RPTPING", 7U) == 0) { + unsigned char pong[7U]; + ::memcpy(pong, "MSTPONG", 6U); + m_socket.write(pong, 7U, m_address, m_port); + } + else { + CUtils::dump("Unknown packet from the master", m_buffer, length); + } + } +} diff --git a/MMDVMNetwork.h b/MMDVMNetwork.h new file mode 100644 index 0000000..0027975 --- /dev/null +++ b/MMDVMNetwork.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#if !defined(MMDVMNetwork_H) +#define MMDVMNetwork_H + +#include "UDPSocket.h" +#include "Timer.h" +#include "RingBuffer.h" +#include "DMRData.h" + +#include +#include + +class CMMDVMNetwork +{ +public: + CMMDVMNetwork(const std::string& address, unsigned int port, unsigned int local, bool debug); + ~CMMDVMNetwork(); + + std::string getOptions() const; + + unsigned int getConfig(unsigned char* config) const; + + unsigned int getId() const; + + bool open(); + + bool read(CDMRData& data); + + bool write(const CDMRData& data); + + void clock(unsigned int ms); + + void close(); + +private: + in_addr m_address; + unsigned int m_port; + unsigned int m_id; + unsigned char* m_netId; + bool m_debug; + CUDPSocket m_socket; + unsigned char* m_buffer; + std::string m_options; + unsigned char* m_configData; + unsigned int m_configLen; + + CRingBuffer m_rxData; +}; + +#endif diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..4f5c011 --- /dev/null +++ b/Makefile @@ -0,0 +1,20 @@ +# This makefile is for all platforms, but doesn't include support for the HD44780 display on the Raspberry Pi. + +CC = gcc +CXX = g++ +CFLAGS = -g -O3 -Wall -std=c++0x -pthread +LIBS = -lpthread +LDFLAGS = -g + +OBJECTS = Conf.o DMRData.o DMRGateway.o DMRNetwork.o Log.o MMDVMNetwork.o SHA256.o StopWatch.o Thread.o Timer.o UDPSocket.o Utils.o + +all: DMRGateway + +DMRGateway: $(OBJECTS) + $(CXX) $(OBJECTS) $(CFLAGS) $(LIBS) -o DMRGateway + +%.o: %.cpp + $(CXX) $(CFLAGS) -c -o $@ $< + +clean: + $(RM) DMRGateway *.o *.d *.bak *~ diff --git a/RingBuffer.h b/RingBuffer.h new file mode 100644 index 0000000..707de1c --- /dev/null +++ b/RingBuffer.h @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2006-2009,2012,2013,2015,2016 by Jonathan Naylor G4KLX + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef RingBuffer_H +#define RingBuffer_H + +#include "Log.h" + +#include +#include +#include + +template class CRingBuffer { +public: + CRingBuffer(unsigned int length, const char* name) : + m_length(length), + m_name(name), + m_buffer(NULL), + m_iPtr(0U), + m_oPtr(0U) + { + assert(length > 0U); + assert(name != NULL); + + m_buffer = new T[length]; + + ::memset(m_buffer, 0x00, m_length * sizeof(T)); + } + + ~CRingBuffer() + { + delete[] m_buffer; + } + + bool addData(const T* buffer, unsigned int nSamples) + { + if (nSamples >= freeSpace()) { + LogError("%s buffer overflow, clearing the buffer. (%u >= %u)", m_name, nSamples, freeSpace()); + clear(); + return false; + } + + for (unsigned int i = 0U; i < nSamples; i++) { + m_buffer[m_iPtr++] = buffer[i]; + + if (m_iPtr == m_length) + m_iPtr = 0U; + } + + return true; + } + + bool getData(T* buffer, unsigned int nSamples) + { + if (dataSize() < nSamples) { + LogError("**** Underflow in %s ring buffer, %u < %u", m_name, dataSize(), nSamples); + return false; + } + + for (unsigned int i = 0U; i < nSamples; i++) { + buffer[i] = m_buffer[m_oPtr++]; + + if (m_oPtr == m_length) + m_oPtr = 0U; + } + + return true; + } + + bool peek(T* buffer, unsigned int nSamples) + { + if (dataSize() < nSamples) { + LogError("**** Underflow peek in %s ring buffer, %u < %u", m_name, dataSize(), nSamples); + return false; + } + + unsigned int ptr = m_oPtr; + for (unsigned int i = 0U; i < nSamples; i++) { + buffer[i] = m_buffer[ptr++]; + + if (ptr == m_length) + ptr = 0U; + } + + return true; + } + + void clear() + { + m_iPtr = 0U; + m_oPtr = 0U; + + ::memset(m_buffer, 0x00, m_length * sizeof(T)); + } + + unsigned int freeSpace() const + { + unsigned int len = m_length; + + if (m_oPtr > m_iPtr) + len = m_oPtr - m_iPtr; + else if (m_iPtr > m_oPtr) + len = m_length - (m_iPtr - m_oPtr); + + if (len > m_length) + len = 0U; + + return len; + } + + unsigned int dataSize() const + { + return m_length - freeSpace(); + } + + bool hasSpace(unsigned int length) const + { + return freeSpace() > length; + } + + bool hasData() const + { + return m_oPtr != m_iPtr; + } + + bool isEmpty() const + { + return m_oPtr == m_iPtr; + } + +private: + unsigned int m_length; + const char* m_name; + T* m_buffer; + unsigned int m_iPtr; + unsigned int m_oPtr; +}; + +#endif diff --git a/SHA256.cpp b/SHA256.cpp new file mode 100644 index 0000000..b3366e0 --- /dev/null +++ b/SHA256.cpp @@ -0,0 +1,373 @@ +/* + * Copyright (C) 2005, 2006, 2008 Free Software Foundation, Inc. + * Copyright (C) 2011,2015 by Jonathan Naylor G4KLX + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "SHA256.h" + +#include +#include +#include + +#ifdef WORDS_BIGENDIAN +# define SWAP(n) (n) +#else +# define SWAP(n) \ + (((n) << 24) | (((n) & 0xff00) << 8) | (((n) >> 8) & 0xff00) | ((n) >> 24)) +#endif + +#define BLOCKSIZE 4096 +#if BLOCKSIZE % 64 != 0 +# error "invalid BLOCKSIZE" +#endif + +/* This array contains the bytes used to pad the buffer to the next + 64-byte boundary. */ +static const unsigned char fillbuf[64] = { 0x80, 0 /* , 0, 0, ... */ }; + + +/* + Takes a pointer to a 256 bit block of data (eight 32 bit ints) and + intializes it to the start constants of the SHA256 algorithm. This + must be called before using hash in the call to sha256_hash +*/ +CSHA256::CSHA256() : +m_state(NULL), +m_total(NULL), +m_buflen(0U), +m_buffer(NULL) +{ + m_state = new uint32_t[8U]; + m_total = new uint32_t[2U]; + m_buffer = new uint32_t[32U]; + + init(); +} + +CSHA256::~CSHA256() +{ + delete[] m_state; + delete[] m_total; + delete[] m_buffer; +} + +void CSHA256::init() +{ + m_state[0] = 0x6a09e667UL; + m_state[1] = 0xbb67ae85UL; + m_state[2] = 0x3c6ef372UL; + m_state[3] = 0xa54ff53aUL; + m_state[4] = 0x510e527fUL; + m_state[5] = 0x9b05688cUL; + m_state[6] = 0x1f83d9abUL; + m_state[7] = 0x5be0cd19UL; + + m_total[0] = m_total[1] = 0; + m_buflen = 0; +} + +/* Copy the value from v into the memory location pointed to by *cp, + If your architecture allows unaligned access this is equivalent to + * (uint32_t *) cp = v */ +static inline void set_uint32(unsigned char* cp, uint32_t v) +{ + assert(cp != NULL); + + ::memcpy(cp, &v, sizeof v); +} + +/* Put result from CTX in first 32 bytes following RESBUF. The result + must be in little endian byte order. */ +unsigned char* CSHA256::read(unsigned char* resbuf) +{ + assert(resbuf != NULL); + + for (unsigned int i = 0U; i < 8U; i++) + set_uint32(resbuf + i * sizeof(m_state[0]), SWAP(m_state[i])); + + return resbuf; +} + +/* Process the remaining bytes in the internal buffer and the usual + prolog according to the standard and write the result to RESBUF. */ +void CSHA256::conclude() +{ + /* Take yet unprocessed bytes into account. */ + unsigned int bytes = m_buflen; + unsigned int size = (bytes < 56) ? 64 / 4 : 64 * 2 / 4; + + /* Now count remaining bytes. */ + m_total[0] += bytes; + if (m_total[0] < bytes) + ++m_total[1]; + + /* Put the 64-bit file length in *bits* at the end of the buffer. + Use set_uint32 rather than a simple assignment, to avoid risk of + unaligned access. */ + set_uint32((unsigned char*)&m_buffer[size - 2], SWAP((m_total[1] << 3) | (m_total[0] >> 29))); + set_uint32((unsigned char*)&m_buffer[size - 1], SWAP(m_total[0] << 3)); + + ::memcpy(&((char*)m_buffer)[bytes], fillbuf, (size - 2) * 4 - bytes); + + /* Process last bytes. */ + processBlock((unsigned char*)m_buffer, size * 4); +} + +unsigned char* CSHA256::finish(unsigned char* resbuf) +{ + assert(resbuf != NULL); + + conclude(); + + return read(resbuf); +} + +/* Compute SHA256 message digest for LEN bytes beginning at BUFFER. The + result is always in little endian byte order, so that a byte-wise + output yields to the wanted ASCII representation of the message + digest. */ +unsigned char* CSHA256::buffer(const unsigned char* buffer, unsigned int len, unsigned char* resblock) +{ + assert(buffer != NULL); + assert(resblock != NULL); + + /* Initialize the computation context. */ + init(); + + /* Process whole buffer but last len % 64 bytes. */ + processBytes(buffer, len); + + /* Put result in desired memory area. */ + return finish(resblock); +} + +void CSHA256::processBytes(const unsigned char* buffer, unsigned int len) +{ + assert(buffer != NULL); + + /* When we already have some bits in our internal buffer concatenate + both inputs first. */ + if (m_buflen != 0U) { + unsigned int left_over = m_buflen; + unsigned int add = 128U - left_over > len ? len : 128U - left_over; + + ::memcpy(&((char*)m_buffer)[left_over], buffer, add); + m_buflen += add; + + if (m_buflen > 64U) { + processBlock((unsigned char*)m_buffer, m_buflen & ~63U); + + m_buflen &= 63U; + + /* The regions in the following copy operation cannot overlap. */ + ::memcpy(m_buffer, &((char*)m_buffer)[(left_over + add) & ~63U], m_buflen); + } + + buffer += add; + len -= add; + } + + /* Process available complete blocks. */ + if (len >= 64U) { +//#if !_STRING_ARCH_unaligned +//# define alignof(type) offsetof (struct { char c; type x; }, x) +//# define UNALIGNED_P(p) (((unsigned int) p) % alignof (uint32_t) != 0) +// if (UNALIGNED_P (buffer)) { +// while (len > 64U) { +// ::memcpy(m_buffer, buffer, 64U); +// processBlock((unsigned char*)m_buffer, 64U); +// buffer += 64U; +// len -= 64U; +// } +// } else +//#endif + { + processBlock(buffer, len & ~63U); + buffer += (len & ~63U); + len &= 63U; + } + } + + /* Move remaining bytes in internal buffer. */ + if (len > 0U) { + unsigned int left_over = m_buflen; + + ::memcpy(&((char*)m_buffer)[left_over], buffer, len); + left_over += len; + + if (left_over >= 64U) { + processBlock((unsigned char*)m_buffer, 64U); + left_over -= 64U; + ::memcpy(m_buffer, &m_buffer[16], left_over); + } + + m_buflen = left_over; + } +} + +/* --- Code below is the primary difference between sha1.c and sha256.c --- */ + +/* SHA256 round constants */ +#define K(I) roundConstants[I] +static const uint32_t roundConstants[64] = { + 0x428a2f98UL, 0x71374491UL, 0xb5c0fbcfUL, 0xe9b5dba5UL, + 0x3956c25bUL, 0x59f111f1UL, 0x923f82a4UL, 0xab1c5ed5UL, + 0xd807aa98UL, 0x12835b01UL, 0x243185beUL, 0x550c7dc3UL, + 0x72be5d74UL, 0x80deb1feUL, 0x9bdc06a7UL, 0xc19bf174UL, + 0xe49b69c1UL, 0xefbe4786UL, 0x0fc19dc6UL, 0x240ca1ccUL, + 0x2de92c6fUL, 0x4a7484aaUL, 0x5cb0a9dcUL, 0x76f988daUL, + 0x983e5152UL, 0xa831c66dUL, 0xb00327c8UL, 0xbf597fc7UL, + 0xc6e00bf3UL, 0xd5a79147UL, 0x06ca6351UL, 0x14292967UL, + 0x27b70a85UL, 0x2e1b2138UL, 0x4d2c6dfcUL, 0x53380d13UL, + 0x650a7354UL, 0x766a0abbUL, 0x81c2c92eUL, 0x92722c85UL, + 0xa2bfe8a1UL, 0xa81a664bUL, 0xc24b8b70UL, 0xc76c51a3UL, + 0xd192e819UL, 0xd6990624UL, 0xf40e3585UL, 0x106aa070UL, + 0x19a4c116UL, 0x1e376c08UL, 0x2748774cUL, 0x34b0bcb5UL, + 0x391c0cb3UL, 0x4ed8aa4aUL, 0x5b9cca4fUL, 0x682e6ff3UL, + 0x748f82eeUL, 0x78a5636fUL, 0x84c87814UL, 0x8cc70208UL, + 0x90befffaUL, 0xa4506cebUL, 0xbef9a3f7UL, 0xc67178f2UL, +}; + +/* Round functions. */ +#define F2(A,B,C) ( ( A & B ) | ( C & ( A | B ) ) ) +#define F1(E,F,G) ( G ^ ( E & ( F ^ G ) ) ) + +/* Process LEN bytes of BUFFER, accumulating context into CTX. + It is assumed that LEN % 64 == 0. + Most of this code comes from GnuPG's cipher/sha1.c. */ + +void CSHA256::processBlock(const unsigned char* buffer, unsigned int len) +{ + assert(buffer != NULL); + + const uint32_t* words = (uint32_t*)buffer; + unsigned int nwords = len / sizeof(uint32_t); + const uint32_t* endp = words + nwords; + uint32_t x[16]; + uint32_t a = m_state[0]; + uint32_t b = m_state[1]; + uint32_t c = m_state[2]; + uint32_t d = m_state[3]; + uint32_t e = m_state[4]; + uint32_t f = m_state[5]; + uint32_t g = m_state[6]; + uint32_t h = m_state[7]; + + /* First increment the byte count. FIPS PUB 180-2 specifies the possible + length of the file up to 2^64 bits. Here we only compute the + number of bytes. Do a double word increment. */ + m_total[0] += len; + if (m_total[0] < len) + ++m_total[1]; + + #define rol(x, n) (((x) << (n)) | ((x) >> (32 - (n)))) + #define S0(x) (rol(x,25)^rol(x,14)^(x>>3)) + #define S1(x) (rol(x,15)^rol(x,13)^(x>>10)) + #define SS0(x) (rol(x,30)^rol(x,19)^rol(x,10)) + #define SS1(x) (rol(x,26)^rol(x,21)^rol(x,7)) + + #define M(I) (tm = S1(x[(I-2)&0x0f]) + x[(I-7)&0x0f] + S0(x[(I-15)&0x0f]) + x[I&0x0f], x[I&0x0f] = tm) + + #define R(A,B,C,D,E,F,G,H,K,M) do { t0 = SS0(A) + F2(A,B,C); \ + t1 = H + SS1(E) + F1(E,F,G) + K + M; \ + D += t1; H = t0 + t1; \ + } while(0) + + while (words < endp) { + uint32_t tm; + uint32_t t0, t1; + /* FIXME: see sha1.c for a better implementation. */ + for (unsigned int t = 0U; t < 16U; t++) { + x[t] = SWAP(*words); + words++; + } + + R( a, b, c, d, e, f, g, h, K( 0), x[ 0] ); + R( h, a, b, c, d, e, f, g, K( 1), x[ 1] ); + R( g, h, a, b, c, d, e, f, K( 2), x[ 2] ); + R( f, g, h, a, b, c, d, e, K( 3), x[ 3] ); + R( e, f, g, h, a, b, c, d, K( 4), x[ 4] ); + R( d, e, f, g, h, a, b, c, K( 5), x[ 5] ); + R( c, d, e, f, g, h, a, b, K( 6), x[ 6] ); + R( b, c, d, e, f, g, h, a, K( 7), x[ 7] ); + R( a, b, c, d, e, f, g, h, K( 8), x[ 8] ); + R( h, a, b, c, d, e, f, g, K( 9), x[ 9] ); + R( g, h, a, b, c, d, e, f, K(10), x[10] ); + R( f, g, h, a, b, c, d, e, K(11), x[11] ); + R( e, f, g, h, a, b, c, d, K(12), x[12] ); + R( d, e, f, g, h, a, b, c, K(13), x[13] ); + R( c, d, e, f, g, h, a, b, K(14), x[14] ); + R( b, c, d, e, f, g, h, a, K(15), x[15] ); + R( a, b, c, d, e, f, g, h, K(16), M(16) ); + R( h, a, b, c, d, e, f, g, K(17), M(17) ); + R( g, h, a, b, c, d, e, f, K(18), M(18) ); + R( f, g, h, a, b, c, d, e, K(19), M(19) ); + R( e, f, g, h, a, b, c, d, K(20), M(20) ); + R( d, e, f, g, h, a, b, c, K(21), M(21) ); + R( c, d, e, f, g, h, a, b, K(22), M(22) ); + R( b, c, d, e, f, g, h, a, K(23), M(23) ); + R( a, b, c, d, e, f, g, h, K(24), M(24) ); + R( h, a, b, c, d, e, f, g, K(25), M(25) ); + R( g, h, a, b, c, d, e, f, K(26), M(26) ); + R( f, g, h, a, b, c, d, e, K(27), M(27) ); + R( e, f, g, h, a, b, c, d, K(28), M(28) ); + R( d, e, f, g, h, a, b, c, K(29), M(29) ); + R( c, d, e, f, g, h, a, b, K(30), M(30) ); + R( b, c, d, e, f, g, h, a, K(31), M(31) ); + R( a, b, c, d, e, f, g, h, K(32), M(32) ); + R( h, a, b, c, d, e, f, g, K(33), M(33) ); + R( g, h, a, b, c, d, e, f, K(34), M(34) ); + R( f, g, h, a, b, c, d, e, K(35), M(35) ); + R( e, f, g, h, a, b, c, d, K(36), M(36) ); + R( d, e, f, g, h, a, b, c, K(37), M(37) ); + R( c, d, e, f, g, h, a, b, K(38), M(38) ); + R( b, c, d, e, f, g, h, a, K(39), M(39) ); + R( a, b, c, d, e, f, g, h, K(40), M(40) ); + R( h, a, b, c, d, e, f, g, K(41), M(41) ); + R( g, h, a, b, c, d, e, f, K(42), M(42) ); + R( f, g, h, a, b, c, d, e, K(43), M(43) ); + R( e, f, g, h, a, b, c, d, K(44), M(44) ); + R( d, e, f, g, h, a, b, c, K(45), M(45) ); + R( c, d, e, f, g, h, a, b, K(46), M(46) ); + R( b, c, d, e, f, g, h, a, K(47), M(47) ); + R( a, b, c, d, e, f, g, h, K(48), M(48) ); + R( h, a, b, c, d, e, f, g, K(49), M(49) ); + R( g, h, a, b, c, d, e, f, K(50), M(50) ); + R( f, g, h, a, b, c, d, e, K(51), M(51) ); + R( e, f, g, h, a, b, c, d, K(52), M(52) ); + R( d, e, f, g, h, a, b, c, K(53), M(53) ); + R( c, d, e, f, g, h, a, b, K(54), M(54) ); + R( b, c, d, e, f, g, h, a, K(55), M(55) ); + R( a, b, c, d, e, f, g, h, K(56), M(56) ); + R( h, a, b, c, d, e, f, g, K(57), M(57) ); + R( g, h, a, b, c, d, e, f, K(58), M(58) ); + R( f, g, h, a, b, c, d, e, K(59), M(59) ); + R( e, f, g, h, a, b, c, d, K(60), M(60) ); + R( d, e, f, g, h, a, b, c, K(61), M(61) ); + R( c, d, e, f, g, h, a, b, K(62), M(62) ); + R( b, c, d, e, f, g, h, a, K(63), M(63) ); + + a = m_state[0] += a; + b = m_state[1] += b; + c = m_state[2] += c; + d = m_state[3] += d; + e = m_state[4] += e; + f = m_state[5] += f; + g = m_state[6] += g; + h = m_state[7] += h; + } +} diff --git a/SHA256.h b/SHA256.h new file mode 100644 index 0000000..7c48f19 --- /dev/null +++ b/SHA256.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2005, 2006, 2008, 2009 Free Software Foundation, Inc. + * Copyright (C) 2011,2015,2016 by Jonathan Naylor G4KLX + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef SHA256_H +#define SHA256_H + +#include + +enum { + SHA256_DIGEST_SIZE = 256 / 8 +}; + +class CSHA256 { +public: + CSHA256(); + ~CSHA256(); + + /* Starting with the result of former calls of this function (or the + initialization function update the context for the next LEN bytes + starting at BUFFER. + It is necessary that LEN is a multiple of 64!!! */ + void processBlock(const unsigned char* buffer, unsigned int len); + + /* Starting with the result of former calls of this function (or the + initialization function update the context for the next LEN bytes + starting at BUFFER. + It is NOT required that LEN is a multiple of 64. */ + void processBytes(const unsigned char* buffer, unsigned int len); + + /* Process the remaining bytes in the buffer and put result from CTX + in first 32 bytes following RESBUF. The result is always in little + endian byte order, so that a byte-wise output yields to the wanted + ASCII representation of the message digest. */ + unsigned char* finish(unsigned char* resbuf); + + /* Put result from CTX in first 32 bytes following RESBUF. The result is + always in little endian byte order, so that a byte-wise output yields + to the wanted ASCII representation of the message digest. */ + unsigned char* read(unsigned char* resbuf); + + /* Compute SHA256 message digest for LEN bytes beginning at BUFFER. The + result is always in little endian byte order, so that a byte-wise + output yields to the wanted ASCII representation of the message + digest. */ + unsigned char* buffer(const unsigned char* buffer, unsigned int len, unsigned char* resblock); + +private: + uint32_t* m_state; + uint32_t* m_total; + unsigned int m_buflen; + uint32_t* m_buffer; + + void init(); + void conclude(); +}; + +#endif diff --git a/StopWatch.cpp b/StopWatch.cpp new file mode 100644 index 0000000..77d539d --- /dev/null +++ b/StopWatch.cpp @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2015,2016 by Jonathan Naylor G4KLX + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "StopWatch.h" + +#if defined(_WIN32) || defined(_WIN64) + +CStopWatch::CStopWatch() : +m_frequency(), +m_start() +{ + ::QueryPerformanceFrequency(&m_frequency); +} + +CStopWatch::~CStopWatch() +{ +} + +unsigned long CStopWatch::start() +{ + ::QueryPerformanceCounter(&m_start); + + return (unsigned long)(m_start.QuadPart / m_frequency.QuadPart); +} + +unsigned int CStopWatch::elapsed() +{ + LARGE_INTEGER now; + ::QueryPerformanceCounter(&now); + + LARGE_INTEGER temp; + temp.QuadPart = (now.QuadPart - m_start.QuadPart) * 1000; + + return (unsigned int)(temp.QuadPart / m_frequency.QuadPart); +} + +#else + +#include + +CStopWatch::CStopWatch() : +m_start() +{ +} + +CStopWatch::~CStopWatch() +{ +} + +unsigned long CStopWatch::start() +{ + ::gettimeofday(&m_start, NULL); + + return m_start.tv_usec; +} + +unsigned int CStopWatch::elapsed() +{ + struct timeval now; + ::gettimeofday(&now, NULL); + + unsigned int elapsed = (now.tv_sec - m_start.tv_sec) * 1000U; + elapsed += now.tv_usec / 1000U; + elapsed -= m_start.tv_usec / 1000U; + + return elapsed; +} + +#endif diff --git a/StopWatch.h b/StopWatch.h new file mode 100644 index 0000000..811047e --- /dev/null +++ b/StopWatch.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2015,2016 by Jonathan Naylor G4KLX + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#if !defined(STOPWATCH_H) +#define STOPWATCH_H + +#if defined(_WIN32) || defined(_WIN64) +#include +#else +#include +#endif + +class CStopWatch +{ +public: + CStopWatch(); + ~CStopWatch(); + + unsigned long start(); + unsigned int elapsed(); + +private: +#if defined(_WIN32) || defined(_WIN64) + LARGE_INTEGER m_frequency; + LARGE_INTEGER m_start; +#else + struct timeval m_start; +#endif +}; + +#endif diff --git a/Thread.cpp b/Thread.cpp new file mode 100644 index 0000000..b334436 --- /dev/null +++ b/Thread.cpp @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2015,2016 by Jonathan Naylor G4KLX + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "Thread.h" + +#if defined(_WIN32) || defined(_WIN64) + +CThread::CThread() : +m_handle() +{ +} + +CThread::~CThread() +{ +} + +bool CThread::run() +{ + m_handle = ::CreateThread(NULL, 0, &helper, this, 0, NULL); + + return m_handle != NULL; +} + + +void CThread::wait() +{ + ::WaitForSingleObject(m_handle, INFINITE); + + ::CloseHandle(m_handle); +} + + +DWORD CThread::helper(LPVOID arg) +{ + CThread* p = (CThread*)arg; + + p->entry(); + + return 0UL; +} + +void CThread::sleep(unsigned int ms) +{ + ::Sleep(ms); +} + +#else + +#include + +CThread::CThread() : +m_thread() +{ +} + +CThread::~CThread() +{ +} + +bool CThread::run() +{ + return ::pthread_create(&m_thread, NULL, helper, this) == 0; +} + + +void CThread::wait() +{ + ::pthread_join(m_thread, NULL); +} + + +void* CThread::helper(void* arg) +{ + CThread* p = (CThread*)arg; + + p->entry(); + + return NULL; +} + +void CThread::sleep(unsigned int ms) +{ + ::usleep(ms * 1000); +} + +#endif diff --git a/Thread.h b/Thread.h new file mode 100644 index 0000000..352d938 --- /dev/null +++ b/Thread.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2015,2016 by Jonathan Naylor G4KLX + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#if !defined(THREAD_H) +#define THREAD_H + +#if defined(_WIN32) || defined(_WIN64) +#include +#else +#include +#endif + +class CThread +{ +public: + CThread(); + virtual ~CThread(); + + virtual bool run(); + + virtual void entry() = 0; + + virtual void wait(); + + static void sleep(unsigned int ms); + +private: +#if defined(_WIN32) || defined(_WIN64) + HANDLE m_handle; +#else + pthread_t m_thread; +#endif + +#if defined(_WIN32) || defined(_WIN64) + static DWORD __stdcall helper(LPVOID arg); +#else + static void* helper(void* arg); +#endif +}; + +#endif diff --git a/Timer.cpp b/Timer.cpp new file mode 100644 index 0000000..53956e4 --- /dev/null +++ b/Timer.cpp @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2009,2010,2015 by Jonathan Naylor G4KLX + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "Timer.h" + +#include +#include + +CTimer::CTimer(unsigned int ticksPerSec, unsigned int secs, unsigned int msecs) : +m_ticksPerSec(ticksPerSec), +m_timeout(0U), +m_timer(0U) +{ + assert(ticksPerSec > 0U); + + if (secs > 0U || msecs > 0U) { + // m_timeout = ((secs * 1000U + msecs) * m_ticksPerSec) / 1000U + 1U; + unsigned long long temp = (secs * 1000ULL + msecs) * m_ticksPerSec; + m_timeout = (unsigned int)(temp / 1000ULL + 1ULL); + } +} + +CTimer::~CTimer() +{ +} + +void CTimer::setTimeout(unsigned int secs, unsigned int msecs) +{ + if (secs > 0U || msecs > 0U) { + // m_timeout = ((secs * 1000U + msecs) * m_ticksPerSec) / 1000U + 1U; + unsigned long long temp = (secs * 1000ULL + msecs) * m_ticksPerSec; + m_timeout = (unsigned int)(temp / 1000ULL + 1ULL); + } else { + m_timeout = 0U; + m_timer = 0U; + } +} + +unsigned int CTimer::getTimeout() const +{ + if (m_timeout == 0U) + return 0U; + + return (m_timeout - 1U) / m_ticksPerSec; +} + +unsigned int CTimer::getTimer() const +{ + if (m_timer == 0U) + return 0U; + + return (m_timer - 1U) / m_ticksPerSec; +} diff --git a/Timer.h b/Timer.h new file mode 100644 index 0000000..87d68f5 --- /dev/null +++ b/Timer.h @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2009,2010,2011,2014 by Jonathan Naylor G4KLX + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef Timer_H +#define Timer_H + +class CTimer { +public: + CTimer(unsigned int ticksPerSec, unsigned int secs = 0U, unsigned int msecs = 0U); + ~CTimer(); + + void setTimeout(unsigned int secs, unsigned int msecs = 0U); + + unsigned int getTimeout() const; + unsigned int getTimer() const; + + unsigned int getRemaining() + { + if (m_timeout == 0U || m_timer == 0U) + return 0U; + + if (m_timer >= m_timeout) + return 0U; + + return (m_timeout - m_timer) / m_ticksPerSec; + } + + bool isRunning() + { + return m_timer > 0U; + } + + void start(unsigned int secs, unsigned int msecs = 0U) + { + setTimeout(secs, msecs); + + start(); + } + + void start() + { + if (m_timeout > 0U) + m_timer = 1U; + } + + void stop() + { + m_timer = 0U; + } + + bool hasExpired() + { + if (m_timeout == 0U || m_timer == 0U) + return false; + + if (m_timer >= m_timeout) + return true; + + return false; + } + + void clock(unsigned int ticks = 1U) + { + if (m_timer > 0U && m_timeout > 0U) + m_timer += ticks; + } + +private: + unsigned int m_ticksPerSec; + unsigned int m_timeout; + unsigned int m_timer; +}; + +#endif diff --git a/UDPSocket.cpp b/UDPSocket.cpp new file mode 100644 index 0000000..396f1f7 --- /dev/null +++ b/UDPSocket.cpp @@ -0,0 +1,262 @@ +/* + * Copyright (C) 2006-2016 by Jonathan Naylor G4KLX + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "UDPSocket.h" +#include "Log.h" + +#include + +#if !defined(_WIN32) && !defined(_WIN64) +#include +#include +#endif + + +CUDPSocket::CUDPSocket(const std::string& address, unsigned int port) : +m_address(address), +m_port(port), +m_fd(-1) +{ + assert(!address.empty()); + +#if defined(_WIN32) || defined(_WIN64) + WSAData data; + int wsaRet = ::WSAStartup(MAKEWORD(2, 2), &data); + if (wsaRet != 0) + LogError("Error from WSAStartup"); +#endif +} + +CUDPSocket::CUDPSocket(unsigned int port) : +m_address(), +m_port(port), +m_fd(-1) +{ +#if defined(_WIN32) || defined(_WIN64) + WSAData data; + int wsaRet = ::WSAStartup(MAKEWORD(2, 2), &data); + if (wsaRet != 0) + LogError("Error from WSAStartup"); +#endif +} + +CUDPSocket::~CUDPSocket() +{ +#if defined(_WIN32) || defined(_WIN64) + ::WSACleanup(); +#endif +} + +in_addr CUDPSocket::lookup(const std::string& hostname) +{ + in_addr addr; +#if defined(_WIN32) || defined(_WIN64) + unsigned long address = ::inet_addr(hostname.c_str()); + if (address != INADDR_NONE && address != INADDR_ANY) { + addr.s_addr = address; + return addr; + } + + struct hostent* hp = ::gethostbyname(hostname.c_str()); + if (hp != NULL) { + ::memcpy(&addr, hp->h_addr_list[0], sizeof(struct in_addr)); + return addr; + } + + LogError("Cannot find address for host %s", hostname.c_str()); + + addr.s_addr = INADDR_NONE; + return addr; +#else + in_addr_t address = ::inet_addr(hostname.c_str()); + if (address != in_addr_t(-1)) { + addr.s_addr = address; + return addr; + } + + struct hostent* hp = ::gethostbyname(hostname.c_str()); + if (hp != NULL) { + ::memcpy(&addr, hp->h_addr_list[0], sizeof(struct in_addr)); + return addr; + } + + LogError("Cannot find address for host %s", hostname.c_str()); + + addr.s_addr = INADDR_NONE; + return addr; +#endif +} + +bool CUDPSocket::open() +{ + m_fd = ::socket(PF_INET, SOCK_DGRAM, 0); + if (m_fd < 0) { +#if defined(_WIN32) || defined(_WIN64) + LogError("Cannot create the UDP socket, err: %lu", ::GetLastError()); +#else + LogError("Cannot create the UDP socket, err: %d", errno); +#endif + return false; + } + + if (m_port > 0U) { + sockaddr_in addr; + ::memset(&addr, 0x00, sizeof(sockaddr_in)); + addr.sin_family = AF_INET; + addr.sin_port = htons(m_port); + addr.sin_addr.s_addr = htonl(INADDR_ANY); + + if (!m_address.empty()) { +#if defined(_WIN32) || defined(_WIN64) + addr.sin_addr.s_addr = ::inet_addr(m_address.c_str()); +#else + addr.sin_addr.s_addr = ::inet_addr(m_address.c_str()); +#endif + if (addr.sin_addr.s_addr == INADDR_NONE) { + LogError("The local address is invalid - %s", m_address.c_str()); + return false; + } + } + + int reuse = 1; + if (::setsockopt(m_fd, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse, sizeof(reuse)) == -1) { +#if defined(_WIN32) || defined(_WIN64) + LogError("Cannot set the UDP socket option, err: %lu", ::GetLastError()); +#else + LogError("Cannot set the UDP socket option, err: %d", errno); +#endif + return false; + } + + if (::bind(m_fd, (sockaddr*)&addr, sizeof(sockaddr_in)) == -1) { +#if defined(_WIN32) || defined(_WIN64) + LogError("Cannot bind the UDP address, err: %lu", ::GetLastError()); +#else + LogError("Cannot bind the UDP address, err: %d", errno); +#endif + return false; + } + } + + return true; +} + +int CUDPSocket::read(unsigned char* buffer, unsigned int length, in_addr& address, unsigned int& port) +{ + assert(buffer != NULL); + assert(length > 0U); + + // Check that the readfrom() won't block + fd_set readFds; + FD_ZERO(&readFds); +#if defined(_WIN32) || defined(_WIN64) + FD_SET((unsigned int)m_fd, &readFds); +#else + FD_SET(m_fd, &readFds); +#endif + + // Return immediately + timeval tv; + tv.tv_sec = 0L; + tv.tv_usec = 0L; + + int ret = ::select(m_fd + 1, &readFds, NULL, NULL, &tv); + if (ret < 0) { +#if defined(_WIN32) || defined(_WIN64) + LogError("Error returned from UDP select, err: %lu", ::GetLastError()); +#else + LogError("Error returned from UDP select, err: %d", errno); +#endif + return -1; + } + + if (ret == 0) + return 0; + + sockaddr_in addr; +#if defined(_WIN32) || defined(_WIN64) + int size = sizeof(sockaddr_in); +#else + socklen_t size = sizeof(sockaddr_in); +#endif + +#if defined(_WIN32) || defined(_WIN64) + int len = ::recvfrom(m_fd, (char*)buffer, length, 0, (sockaddr *)&addr, &size); +#else + ssize_t len = ::recvfrom(m_fd, (char*)buffer, length, 0, (sockaddr *)&addr, &size); +#endif + if (len <= 0) { +#if defined(_WIN32) || defined(_WIN64) + LogError("Error returned from recvfrom, err: %lu", ::GetLastError()); +#else + LogError("Error returned from recvfrom, err: %d", errno); +#endif + return -1; + } + + address = addr.sin_addr; + port = ntohs(addr.sin_port); + + return len; +} + +bool CUDPSocket::write(const unsigned char* buffer, unsigned int length, const in_addr& address, unsigned int port) +{ + assert(buffer != NULL); + assert(length > 0U); + + sockaddr_in addr; + ::memset(&addr, 0x00, sizeof(sockaddr_in)); + + addr.sin_family = AF_INET; + addr.sin_addr = address; + addr.sin_port = htons(port); + +#if defined(_WIN32) || defined(_WIN64) + int ret = ::sendto(m_fd, (char *)buffer, length, 0, (sockaddr *)&addr, sizeof(sockaddr_in)); +#else + ssize_t ret = ::sendto(m_fd, (char *)buffer, length, 0, (sockaddr *)&addr, sizeof(sockaddr_in)); +#endif + if (ret < 0) { +#if defined(_WIN32) || defined(_WIN64) + LogError("Error returned from sendto, err: %lu", ::GetLastError()); +#else + LogError("Error returned from sendto, err: %d", errno); +#endif + return false; + } + +#if defined(_WIN32) || defined(_WIN64) + if (ret != int(length)) + return false; +#else + if (ret != ssize_t(length)) + return false; +#endif + + return true; +} + +void CUDPSocket::close() +{ +#if defined(_WIN32) || defined(_WIN64) + ::closesocket(m_fd); +#else + ::close(m_fd); +#endif +} diff --git a/UDPSocket.h b/UDPSocket.h new file mode 100644 index 0000000..e0af272 --- /dev/null +++ b/UDPSocket.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2009-2011,2013,2015,2016 by Jonathan Naylor G4KLX + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef UDPSocket_H +#define UDPSocket_H + +#include + +#if !defined(_WIN32) && !defined(_WIN64) +#include +#include +#include +#include +#include +#include +#include +#include +#else +#include +#endif + +class CUDPSocket { +public: + CUDPSocket(const std::string& address, unsigned int port = 0U); + CUDPSocket(unsigned int port = 0U); + ~CUDPSocket(); + + bool open(); + + int read(unsigned char* buffer, unsigned int length, in_addr& address, unsigned int& port); + bool write(const unsigned char* buffer, unsigned int length, const in_addr& address, unsigned int port); + + void close(); + + static in_addr lookup(const std::string& hostName); + +private: + std::string m_address; + unsigned short m_port; + int m_fd; +}; + +#endif diff --git a/Utils.cpp b/Utils.cpp new file mode 100644 index 0000000..49ded13 --- /dev/null +++ b/Utils.cpp @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2009,2014,2015,2016 Jonathan Naylor, G4KLX + * + * 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; version 2 of the License. + * + * 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. + */ + +#include "Utils.h" +#include "Log.h" + +#include +#include + +void CUtils::dump(const std::string& title, const unsigned char* data, unsigned int length) +{ + assert(data != NULL); + + dump(2U, title, data, length); +} + +void CUtils::dump(int level, const std::string& title, const unsigned char* data, unsigned int length) +{ + assert(data != NULL); + + ::Log(level, "%s", title.c_str()); + + unsigned int offset = 0U; + + while (length > 0U) { + std::string output; + + unsigned int bytes = (length > 16U) ? 16U : length; + + for (unsigned i = 0U; i < bytes; i++) { + char temp[10U]; + ::sprintf(temp, "%02X ", data[offset + i]); + output += temp; + } + + for (unsigned int i = bytes; i < 16U; i++) + output += " "; + + output += " *"; + + for (unsigned i = 0U; i < bytes; i++) { + unsigned char c = data[offset + i]; + + if (::isprint(c)) + output += c; + else + output += '.'; + } + + output += '*'; + + ::Log(level, "%04X: %s", offset, output.c_str()); + + offset += 16U; + + if (length >= 16U) + length -= 16U; + else + length = 0U; + } +} + +void CUtils::dump(const std::string& title, const bool* bits, unsigned int length) +{ + assert(bits != NULL); + + dump(2U, title, bits, length); +} + +void CUtils::dump(int level, const std::string& title, const bool* bits, unsigned int length) +{ + assert(bits != NULL); + + unsigned char bytes[100U]; + unsigned int nBytes = 0U; + for (unsigned int n = 0U; n < length; n += 8U, nBytes++) + bitsToByteBE(bits + n, bytes[nBytes]); + + dump(level, title, bytes, nBytes); +} + +void CUtils::byteToBitsBE(unsigned char byte, bool* bits) +{ + assert(bits != NULL); + + bits[0U] = (byte & 0x80U) == 0x80U; + bits[1U] = (byte & 0x40U) == 0x40U; + bits[2U] = (byte & 0x20U) == 0x20U; + bits[3U] = (byte & 0x10U) == 0x10U; + bits[4U] = (byte & 0x08U) == 0x08U; + bits[5U] = (byte & 0x04U) == 0x04U; + bits[6U] = (byte & 0x02U) == 0x02U; + bits[7U] = (byte & 0x01U) == 0x01U; +} + +void CUtils::byteToBitsLE(unsigned char byte, bool* bits) +{ + assert(bits != NULL); + + bits[0U] = (byte & 0x01U) == 0x01U; + bits[1U] = (byte & 0x02U) == 0x02U; + bits[2U] = (byte & 0x04U) == 0x04U; + bits[3U] = (byte & 0x08U) == 0x08U; + bits[4U] = (byte & 0x10U) == 0x10U; + bits[5U] = (byte & 0x20U) == 0x20U; + bits[6U] = (byte & 0x40U) == 0x40U; + bits[7U] = (byte & 0x80U) == 0x80U; +} + +void CUtils::bitsToByteBE(const bool* bits, unsigned char& byte) +{ + assert(bits != NULL); + + byte = bits[0U] ? 0x80U : 0x00U; + byte |= bits[1U] ? 0x40U : 0x00U; + byte |= bits[2U] ? 0x20U : 0x00U; + byte |= bits[3U] ? 0x10U : 0x00U; + byte |= bits[4U] ? 0x08U : 0x00U; + byte |= bits[5U] ? 0x04U : 0x00U; + byte |= bits[6U] ? 0x02U : 0x00U; + byte |= bits[7U] ? 0x01U : 0x00U; +} + +void CUtils::bitsToByteLE(const bool* bits, unsigned char& byte) +{ + assert(bits != NULL); + + byte = bits[0U] ? 0x01U : 0x00U; + byte |= bits[1U] ? 0x02U : 0x00U; + byte |= bits[2U] ? 0x04U : 0x00U; + byte |= bits[3U] ? 0x08U : 0x00U; + byte |= bits[4U] ? 0x10U : 0x00U; + byte |= bits[5U] ? 0x20U : 0x00U; + byte |= bits[6U] ? 0x40U : 0x00U; + byte |= bits[7U] ? 0x80U : 0x00U; +} diff --git a/Utils.h b/Utils.h new file mode 100644 index 0000000..ade28c0 --- /dev/null +++ b/Utils.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2009,2014,2015 by Jonathan Naylor, G4KLX + * + * 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; version 2 of the License. + * + * 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. + */ + +#ifndef Utils_H +#define Utils_H + +#include + +class CUtils { +public: + static void dump(const std::string& title, const unsigned char* data, unsigned int length); + static void dump(int level, const std::string& title, const unsigned char* data, unsigned int length); + + static void dump(const std::string& title, const bool* bits, unsigned int length); + static void dump(int level, const std::string& title, const bool* bits, unsigned int length); + + static void byteToBitsBE(unsigned char byte, bool* bits); + static void byteToBitsLE(unsigned char byte, bool* bits); + + static void bitsToByteBE(const bool* bits, unsigned char& byte); + static void bitsToByteLE(const bool* bits, unsigned char& byte); + +private: +}; + +#endif diff --git a/Version.h b/Version.h new file mode 100644 index 0000000..af7b3da --- /dev/null +++ b/Version.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2015,2016,2017 by Jonathan Naylor G4KLX + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#if !defined(VERSION_H) +#define VERSION_H + +const char* VERSION = "20170420"; + +#endif