diff --git a/IcomRepeaterProtocolHandler.cpp b/IcomRepeaterProtocolHandler.cpp new file mode 100644 index 0000000..747e955 --- /dev/null +++ b/IcomRepeaterProtocolHandler.cpp @@ -0,0 +1,647 @@ +/* + * Copyright (C) 2010-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. + */ + +#include "IcomRepeaterProtocolHandler.h" +#include "CCITTChecksum.h" +#include "DStarDefines.h" +#include "Utils.h" +#include "Log.h" + +// Allow space for a big DD packet +const unsigned int BUFFER_LENGTH = 2500U; + +const unsigned int QUEUE_LENGTH = 50U; + +const unsigned int LOOP_DELAY = 5UL; +const unsigned int LOOP_TICKS = 200U; + +CIcomRepeaterProtocolHandler::CIcomRepeaterProtocolHandler(const std::string& address, unsigned int port, const std::string& icomAddress, unsigned int icomPort) : +m_socket(address, port), +m_icomAddress(), +m_icomPort(icomPort), +m_over1(false), +m_seqNo(0U), +m_tries(0U), +m_ackQueue(NULL), +m_killed(false), +m_type(RT_NONE), +m_buffer(NULL), +m_rptrQueue(QUEUE_LENGTH), +m_gwyQueue(QUEUE_LENGTH), +m_retryTimer(LOOP_TICKS, 0U, 200U) // 200ms +{ + assert(!icomAddress.empty()); + assert(!address.empty()); + assert(icomPort > 0U); + assert(port > 0U); + + m_icomAddress.s_addr = ::inet_addr(icomAddress.c_str()); + + m_buffer = new unsigned char[BUFFER_LENGTH]; + + auto now = ::time(NULL); + ::srand(now); +} + +CIcomRepeaterProtocolHandler::~CIcomRepeaterProtocolHandler() +{ + while (!m_gwyQueue.empty()) { + CDataQueue* dq = m_gwyQueue.getData(); + free(dq); + } + + while (!m_rptrQueue.empty()) { + CDataQueue* dq = m_rptrQueue.getData(); + free(dq); + } + + delete[] m_buffer; +} + +void CIcomRepeaterProtocolHandler::setCount(unsigned int count) +{ + m_over1 = count > 1U; +} + +bool CIcomRepeaterProtocolHandler::open() +{ + bool ret = m_socket.open(); + if (!ret) + return false; + + unsigned char buffer[10U]; + + buffer[0U] = 'I'; + buffer[1U] = 'N'; + buffer[2U] = 'I'; + buffer[3U] = 'T'; + buffer[4U] = 0x00; + buffer[5U] = 0x00; + buffer[6U] = 0x73; + buffer[7U] = 0x00; + buffer[8U] = 0x00; + buffer[9U] = 0x00; + + ret = m_socket.write(buffer, 10U, m_icomAddress, m_icomPort); + if (!ret) { + m_socket.close(); + return false; + } + + // Wait for a reply from the RP2C + for (unsigned int i = 0U; i < 10U; i++) { + in_addr address; + unsigned int port; + int length = m_socket.read(m_buffer, BUFFER_LENGTH, address, port); + + if (length == 10 && m_buffer[0U] == 'I' && m_buffer[1U] == 'N' && m_buffer[2U] == 'I' && m_buffer[3U] == 'T' && m_buffer[6U] == 0x72 && m_buffer[7U] == 0x00) { + m_seqNo = m_buffer[4U] * 256U + m_buffer[5U] + 1U; + wxLogMessage("Initial sequence number from the RP2C is %u", m_seqNo); + + // Start the thread + Create(); + Run(); + + return true; + } + + Sleep(1); + } + + m_socket.close(); + + wxLogError("No reply from the RP2C for 10 seconds, aborting"); + + return false; +} + +void* CIcomRepeaterProtocolHandler::Entry() +{ + wxLogMessage("Starting the Icom Controller thread"); + + try { + while (!m_killed) { + sendGwyPackets(); + + Sleep(LOOP_DELAY); + + readIcomPackets(); + + m_retryTimer.clock(); + } + } + catch (std::exception& e) { + std::string message(e.what()); + wxLogError("Exception raised in the Icom Controller thread - \"%s\"", message.c_str()); + } + catch (...) { + wxLogError("Unknown exception raised in the Icom Controller thread"); + } + + wxLogMessage("Stopping the Icom Controller thread"); + + m_socket.close(); + + return NULL; +} + +bool CIcomRepeaterProtocolHandler::writeHeader(CHeaderData& header) +{ + CDataQueue* dq = new CDataQueue(new CHeaderData(header)); + + m_gwyQueue.addData(dq); + + return true; +} + +bool CIcomRepeaterProtocolHandler::writeAMBE(CAMBEData& data) +{ + CDataQueue* dq = new CDataQueue(new CAMBEData(data)); + + m_gwyQueue.addData(dq); + + return true; +} + +bool CIcomRepeaterProtocolHandler::writeDD(CDDData& data) +{ + CDataQueue* dq = new CDataQueue(new CDDData(data)); + + m_gwyQueue.addData(dq); + + return true; +} + +bool CIcomRepeaterProtocolHandler::writeText(CTextData&) +{ + return true; +} + +bool CIcomRepeaterProtocolHandler::writeStatus(CStatusData&) +{ + return true; +} + +void CIcomRepeaterProtocolHandler::readIcomPackets() +{ + for (;;) { + // No more data? + in_addr address; + unsigned int port; + int length = m_socket.read(m_buffer, BUFFER_LENGTH, address, port); + if (length <= 0) + return; + + if (address.s_addr != m_icomAddress.s_addr || port != m_icomPort) { + wxLogError("Incoming Icom data from an unknown source"); + continue; + } + + // Invalid packet type? + if (m_buffer[0] != 'D' || m_buffer[1] != 'S' || m_buffer[2] != 'T' || m_buffer[3] != 'R') { + CUtils::dump("Missing DSTR from the RP2C", m_buffer, length); + continue; + } + + // An ack + if (length == 10 && m_buffer[6] == 0x72) { + uint16_t seqNo = m_buffer[4] * 256U + m_buffer[5]; + + if (seqNo == m_seqNo) + m_seqNo++; + else + m_seqNo = seqNo; + + // Free the ack queue + free(m_ackQueue); + m_ackQueue = NULL; + + m_retryTimer.stop(); + m_tries = 0U; + + continue; + } + + // Send an ack for all packet types except an ack + uint16_t seqNo = m_buffer[4] * 256U + m_buffer[5]; + sendAck(seqNo); + + // Heard data + if (m_buffer[6] == 0x73 && m_buffer[7] == 0x21) { + CHeardData* heard = new CHeardData; + bool ret = heard->setIcomRepeaterData(m_buffer, length, m_icomAddress, m_icomPort); + if (!ret) { + wxLogError("Invalid heard data from the RP2C"); + delete heard; + continue; + } + + m_rptrQueue.addData(new CDataQueue(heard)); + continue; + } + + // Poll data + if (m_buffer[6] == 0x73 && m_buffer[7] == 0x00) { + m_rptrQueue.addData(new CDataQueue); + continue; + } + + // DD Data + if (m_buffer[6] == 0x73 && m_buffer[7] == 0x11) { + CDDData* data = new CDDData; + bool ret = data->setIcomRepeaterData(m_buffer, length, m_icomAddress, m_icomPort); + if (!ret) { + wxLogError("Invalid DD data from the RP2C"); + delete data; + continue; + } + + m_rptrQueue.addData(new CDataQueue(data)); + continue; + } + + // DV data + if (m_buffer[6] == 0x73 && m_buffer[7] == 0x12 && m_buffer[10] == 0x20) { + if ((m_buffer[16] & 0x80) == 0x80) { + CHeaderData* header = new CHeaderData; + bool ret = header->setIcomRepeaterData(m_buffer, length, true, m_icomAddress, m_icomPort); + if (!ret) { + wxLogError("Invalid header data or checksum from the RP2C"); + delete header; + continue; + } + + if (m_over1) + sendMultiReply(*header); + else + sendSingleReply(*header); + + m_rptrQueue.addData(new CDataQueue(header)); + continue; + } else { + CAMBEData* data = new CAMBEData; + bool ret = data->setIcomRepeaterData(m_buffer, length, m_icomAddress, m_icomPort); + if (!ret) { + wxLogError("Invalid AMBE data from the RP2C"); + delete data; + continue; + } + + m_rptrQueue.addData(new CDataQueue(data)); + continue; + } + } + + // An unknown type + CUtils::dump("Unknown packet type from the RP2C", m_buffer, length); + } +} + +void CIcomRepeaterProtocolHandler::sendGwyPackets() +{ + // Anything to send + if (m_gwyQueue.empty() && m_ackQueue == NULL) + return; + + if (m_tries > 0U && !m_retryTimer.hasExpired()) + return; + + if (m_ackQueue == NULL) { + m_ackQueue = m_gwyQueue.getData(); + if (m_ackQueue == NULL) { + wxLogError("getData of a non-empty gateway queue is NULL"); + return; + } + } + + unsigned int length = 0U; + + switch (m_ackQueue->getType()) { + case RT_HEADER: { + CHeaderData* header = m_ackQueue->getHeader(); + header->setRptSeq(m_seqNo); + length = header->getIcomRepeaterData(m_buffer, 60U, true); + } + break; + + case RT_AMBE: { + CAMBEData* data = m_ackQueue->getAMBE(); + data->setRptSeq(m_seqNo); + length = data->getIcomRepeaterData(m_buffer, 60U); + } + break; + + case RT_DD: { + CDDData* data = m_ackQueue->getDD(); + data->setRptSeq(m_seqNo); + length = data->getIcomRepeaterData(m_buffer, BUFFER_LENGTH); + } + break; + + default: + wxLogError("Invalid type in the gateway queue"); + break; + } + + if (length > 0U) { + m_socket.write(m_buffer, length, m_icomAddress, m_icomPort); + + m_tries++; + m_retryTimer.start(); + + if (m_tries > 0U && (m_tries % 100U) == 0U) + wxLogMessage("No reply from the RP2C after %u retries", m_tries); + } +} + +REPEATER_TYPE CIcomRepeaterProtocolHandler::read() +{ + if (m_rptrQueue.empty()) { + m_type = RT_NONE; + } else { + CDataQueue* dq = m_rptrQueue.peek(); + if (dq == NULL) { + wxLogError("Peek of a non-empty repeater queue is NULL"); + m_type = RT_NONE; + } else { + m_type = dq->getType(); + } + } + + return m_type; +} + +CPollData* CIcomRepeaterProtocolHandler::readPoll() +{ + if (m_type != RT_POLL) + return NULL; + + CDataQueue* dq = m_rptrQueue.getData(); + if (dq == NULL) { + wxLogError("Missing DataQueue in readPoll"); + return NULL; + } + + if (dq->getType() != RT_POLL) { + wxLogError("Wrong DataQueue type in readPoll"); + delete dq; + return NULL; + } + + delete dq; + + CPollData* data = new CPollData; + data->setData1("icom_rp2c"); + + return data; +} + +CHeaderData* CIcomRepeaterProtocolHandler::readHeader() +{ + if (m_type != RT_HEADER) + return NULL; + + CDataQueue* dq = m_rptrQueue.getData(); + if (dq == NULL) { + wxLogError("Missing DataQueue in readHeader"); + return NULL; + } + + if (dq->getType() != RT_HEADER) { + wxLogError("Wrong DataQueue type in readHeader"); + delete dq; + return NULL; + } + + CHeaderData* header = dq->getHeader(); + + delete dq; + + return header; +} + +CAMBEData* CIcomRepeaterProtocolHandler::readAMBE() +{ + if (m_type != RT_AMBE) + return NULL; + + CDataQueue* dq = m_rptrQueue.getData(); + if (dq == NULL) { + wxLogError("Missing DataQueue in readData"); + return NULL; + } + + if (dq->getType() != RT_AMBE) { + wxLogError("Wrong DataQueue type in readData"); + delete dq; + return NULL; + } + + CAMBEData* data = dq->getAMBE(); + + delete dq; + + return data; +} + +CHeardData* CIcomRepeaterProtocolHandler::readHeard() +{ + if (m_type != RT_HEARD) + return NULL; + + CDataQueue* dq = m_rptrQueue.getData(); + if (dq == NULL) { + wxLogError("Missing DataQueue in readHeard"); + return NULL; + } + + if (dq->getType() != RT_HEARD) { + wxLogError("Wrong DataQueue type in readHeard"); + delete dq; + return NULL; + } + + CHeardData* heard = dq->getHeard(); + + delete dq; + + return heard; +} + +CDDData* CIcomRepeaterProtocolHandler::readDD() +{ + if (m_type != RT_DD) + return NULL; + + CDataQueue* dq = m_rptrQueue.getData(); + if (dq == NULL) { + wxLogError("Missing DataQueue in readData"); + return NULL; + } + + if (dq->getType() != RT_DD) { + wxLogError("Wrong DataQueue type in readData"); + delete dq; + return NULL; + } + + CDDData* data = dq->getDD(); + + delete dq; + + return data; +} + +CHeaderData* CIcomRepeaterProtocolHandler::readBusyHeader() +{ + return NULL; +} + +CAMBEData* CIcomRepeaterProtocolHandler::readBusyAMBE() +{ + return NULL; +} + +void CIcomRepeaterProtocolHandler::close() +{ + m_killed = true; + + Wait(); +} + +bool CIcomRepeaterProtocolHandler::sendAck(uint16_t seqNo) +{ + unsigned char buffer[10U]; + + buffer[0U] = 'D'; + buffer[1U] = 'S'; + buffer[2U] = 'T'; + buffer[3U] = 'R'; + + buffer[4U] = seqNo / 256U; + buffer[5U] = seqNo % 256U; + + buffer[6U] = 0x72U; + buffer[7U] = 0x00U; + + buffer[8U] = 0x00U; + buffer[9U] = 0x00U; + + return m_socket.write(buffer, 10U, m_icomAddress, m_icomPort); +} + +void CIcomRepeaterProtocolHandler::sendSingleReply(const CHeaderData& header) +{ + unsigned int id = ::rand() % 0xFFFF; + + CHeaderData replyHdr; + replyHdr.setId(id); + replyHdr.setBand1(0xFFU); + replyHdr.setBand2(0xFFU); + replyHdr.setBand3(0xFFU); + replyHdr.setFlag1(0x01U); + replyHdr.setMyCall1(header.getRptCall2()); + replyHdr.setMyCall2(" "); + replyHdr.setYourCall(header.getMyCall1()); + replyHdr.setRptCall1(header.getRptCall2()); + replyHdr.setRptCall2(header.getRptCall1()); + + writeHeader(replyHdr); + + unsigned char buffer[DV_FRAME_MAX_LENGTH_BYTES]; + ::memcpy(buffer + 0U, NULL_AMBE_DATA_BYTES, VOICE_FRAME_LENGTH_BYTES); + ::memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, END_PATTERN_BYTES, END_PATTERN_LENGTH_BYTES); + + CAMBEData replyData; + replyData.setId(id); + replyData.setBand1(0xFFU); + replyData.setBand2(0xFFU); + replyData.setBand3(0xFFU); + replyData.setSeq(0x40U); // Seq = 0 and end-of-data + replyData.setData(buffer, DV_FRAME_MAX_LENGTH_BYTES); + + writeAMBE(replyData); +} + +void CIcomRepeaterProtocolHandler::sendMultiReply(const CHeaderData& header) +{ + unsigned int id = ::rand() % 0xFFFF; + + CHeaderData replyHdr; + replyHdr.setId(id); + replyHdr.setBand1(0xFFU); + replyHdr.setBand2(0xFFU); + replyHdr.setBand3(0xFFU); + replyHdr.setFlag1(0x41U); + replyHdr.setMyCall1(header.getRptCall2()); + replyHdr.setMyCall2(" "); + replyHdr.setYourCall(header.getMyCall1()); + replyHdr.setRptCall1(header.getRptCall2()); + replyHdr.setRptCall2(header.getRptCall1()); + + writeHeader(replyHdr); + + CAMBEData replyData; + unsigned char buffer[DV_FRAME_LENGTH_BYTES]; + + replyData.setId(id); + replyData.setBand1(0xFFU); + replyData.setBand2(0xFFU); + replyData.setBand3(0xFFU); + + ::memcpy(buffer + 0U, NULL_AMBE_DATA_BYTES, VOICE_FRAME_LENGTH_BYTES); + ::memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, END_PATTERN_BYTES + 0U, 3U); + + replyData.setSeq(0x00U); // Seq = 0 + replyData.setData(buffer, DV_FRAME_LENGTH_BYTES); + + writeAMBE(replyData); + + ::memset(buffer + 0U, 0x00, DV_FRAME_LENGTH_BYTES); + ::memcpy(buffer + 0U, END_PATTERN_BYTES + 3U, 3U); + + replyData.setSeq(0x41U); // Seq = 1 and end-of-data + replyData.setData(buffer, DV_FRAME_LENGTH_BYTES); + + writeAMBE(replyData); +} + +void CIcomRepeaterProtocolHandler::free(CDataQueue* dataQueue) +{ + if (dataQueue == NULL) + return; + + switch (dataQueue->getType()) { + case RT_HEADER: + delete dataQueue->getHeader(); + break; + case RT_HEARD: + delete dataQueue->getHeard(); + break; + case RT_AMBE: + delete dataQueue->getAMBE(); + break; + case RT_DD: + delete dataQueue->getDD(); + break; + default: + break; + } + + delete dataQueue; +} diff --git a/IcomRepeaterProtocolHandler.h b/IcomRepeaterProtocolHandler.h new file mode 100644 index 0000000..66875c8 --- /dev/null +++ b/IcomRepeaterProtocolHandler.h @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2010-2013 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 IcomRepeaterProtocolHandler_H +#define IcomRepeaterProtocolHandler_H + +#include +#include + +#include "RepeaterProtocolHandler.h" +#include "UDPReaderWriter.h" +#include "DStarDefines.h" +#include "RingBuffer.h" +#include "HeaderData.h" +#include "StatusData.h" +#include "HeardData.h" +#include "AMBEData.h" +#include "TextData.h" +#include "PollData.h" +#include "DDData.h" +#include "Timer.h" +#include "Thread.h" + +class CDataQueue { +public: + CDataQueue(CHeaderData* header) : + m_type(RT_HEADER), + m_header(header), + m_heard(NULL), + m_ambe(NULL), + m_dd(NULL) + { + } + + CDataQueue(CHeardData* heard) : + m_type(RT_HEARD), + m_header(NULL), + m_heard(heard), + m_ambe(NULL), + m_dd(NULL) + { + } + + CDataQueue(CAMBEData* data) : + m_type(RT_AMBE), + m_header(NULL), + m_heard(NULL), + m_ambe(data), + m_dd(NULL) + { + } + + CDataQueue(CDDData* data) : + m_type(RT_DD), + m_header(NULL), + m_heard(NULL), + m_ambe(NULL), + m_dd(data) + { + } + + CDataQueue() : + m_type(RT_POLL), + m_header(NULL), + m_heard(NULL), + m_ambe(NULL), + m_dd(NULL) + { + } + + REPEATER_TYPE getType() const + { + return m_type; + } + + CHeaderData* getHeader() const + { + return m_header; + } + + CHeardData* getHeard() const + { + return m_heard; + } + + CAMBEData* getAMBE() const + { + return m_ambe; + } + + CDDData* getDD() const + { + return m_dd; + } + +private: + REPEATER_TYPE m_type; + CHeaderData* m_header; + CHeardData* m_heard; + CAMBEData* m_ambe; + CDDData* m_dd; +}; + +class CIcomRepeaterProtocolHandler : public IRepeaterProtocolHandler, public CThread { +public: + CIcomRepeaterProtocolHandler(const std::string& address, unsigned int port, const std::string& icomAddress, unsigned int icomPort); + virtual ~CIcomRepeaterProtocolHandler(); + + virtual void setCount(unsigned int count); + + virtual bool open(); + + virtual void* Entry(); + + virtual bool writeHeader(CHeaderData& header); + virtual bool writeAMBE(CAMBEData& data); + virtual bool writeDD(CDDData& data); + virtual bool writeText(CTextData& text); + virtual bool writeStatus(CStatusData& status); + + virtual REPEATER_TYPE read(); + virtual CPollData* readPoll(); + virtual CHeardData* readHeard(); + virtual CHeaderData* readHeader(); + virtual CAMBEData* readAMBE(); + virtual CDDData* readDD(); + virtual CHeaderData* readBusyHeader(); + virtual CAMBEData* readBusyAMBE(); + + virtual void close(); + +private: + CUDPReaderWriter m_socket; + in_addr m_icomAddress; + unsigned int m_icomPort; + bool m_over1; + wxUint16 m_seqNo; + unsigned int m_tries; + CDataQueue* m_ackQueue; + bool m_killed; + REPEATER_TYPE m_type; + unsigned char* m_buffer; + CRingBuffer m_rptrQueue; + CRingBuffer m_gwyQueue; + CTimer m_retryTimer; + + void readIcomPackets(); + void sendGwyPackets(); + bool sendAck(uint16_t seqNo); + + void sendSingleReply(const CHeaderData& header); + void sendMultiReply(const CHeaderData& header); + + void free(CDataQueue* dataQueue); +}; + +#endif