2018-06-06 20:33:31 +01:00
/*
* Copyright ( C ) 2018 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 0213 9 , USA .
*/
# include "DAPNETGateway.h"
# include "StopWatch.h"
# include "Version.h"
# include "Thread.h"
# include "Timer.h"
# include "Log.h"
# if defined(_WIN32) || defined(_WIN64)
# include <Windows.h>
# else
# include <sys/time.h>
# include <unistd.h>
# include <signal.h>
# include <fcntl.h>
# include <pwd.h>
# endif
# if defined(_WIN32) || defined(_WIN64)
const char * DEFAULT_INI_FILE = " DAPNETGateway.ini " ;
# else
const char * DEFAULT_INI_FILE = " /etc/DAPNETGateway.ini " ;
# endif
2018-06-21 18:25:51 +01:00
# include <utility>
2018-06-06 20:33:31 +01:00
# include <cstdio>
# include <cstdlib>
# include <cstring>
# include <clocale>
# include <cassert>
# include <cmath>
2018-06-21 19:26:08 +01:00
const unsigned int FRAME_LENGTH_CODEWORDS = 2U ;
const unsigned int BATCH_LENGTH_CODEWORDS = 17U ;
const unsigned int PREAMBLE_LENGTH_CODEWORDS = BATCH_LENGTH_CODEWORDS + 1U ;
2018-06-21 18:25:51 +01:00
const unsigned int CODEWORD_TIME_US = 26667U ; // 26.667ms
2018-06-21 19:26:08 +01:00
const unsigned int FRAME_TIME_US = CODEWORD_TIME_US * FRAME_LENGTH_CODEWORDS ; // 53.333ms
const unsigned int BATCH_TIME_US = CODEWORD_TIME_US * BATCH_LENGTH_CODEWORDS ; // 453.333ms
const unsigned int PREAMBLE_TIME_US = CODEWORD_TIME_US * PREAMBLE_LENGTH_CODEWORDS ; // 480.006ms
2018-06-21 18:25:51 +01:00
const unsigned int SLOT_TIME_US = 6400000U ; // 6.4s
const unsigned int SLOT_TIME_MS = SLOT_TIME_US / 1000U ; // 6.4s
const unsigned int CODEWORDS_PER_SLOT = SLOT_TIME_US / CODEWORD_TIME_US ; // 240
const unsigned int BATCHES_PER_SLOT = SLOT_TIME_US / BATCH_TIME_US ; // 14
2018-06-13 18:53:40 +01:00
const unsigned char FUNCTIONAL_NUMERIC = 0U ;
const unsigned char FUNCTIONAL_ALPHANUMERIC = 3U ;
2018-06-25 19:11:33 +01:00
const unsigned int MAX_TIME_TO_HOLD_TIME_MESSAGES = 2000U ; // 2s
2018-06-06 20:33:31 +01:00
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 , " DAPNETGateway version %s \n " , VERSION ) ;
return 0 ;
} else if ( arg . substr ( 0 , 1 ) = = " - " ) {
: : fprintf ( stderr , " Usage: DAPNETGateway [-v|--version] [filename] \n " ) ;
return 1 ;
} else {
iniFile = argv [ currentArg ] ;
}
}
}
CDAPNETGateway * gateway = new CDAPNETGateway ( std : : string ( iniFile ) ) ;
int ret = gateway - > run ( ) ;
delete gateway ;
return ret ;
}
CDAPNETGateway : : CDAPNETGateway ( const std : : string & configFile ) :
m_conf ( configFile ) ,
m_dapnetNetwork ( NULL ) ,
m_pocsagNetwork ( NULL ) ,
2018-06-21 18:25:51 +01:00
m_queue ( ) ,
2018-06-21 18:56:54 +01:00
m_slotTimer ( ) ,
2018-06-21 18:25:51 +01:00
m_schedule ( NULL ) ,
2018-06-25 19:11:33 +01:00
m_allSlots ( false ) ,
2018-06-21 18:25:51 +01:00
m_currentSlot ( 0U ) ,
m_sentCodewords ( 0U ) ,
m_mmdvmFree ( false )
2018-06-06 20:33:31 +01:00
{
}
CDAPNETGateway : : ~ CDAPNETGateway ( )
{
2018-06-07 07:17:55 +01:00
for ( std : : deque < CPOCSAGMessage * > : : iterator it = m_queue . begin ( ) ; it ! = m_queue . end ( ) ; + + it )
delete * it ;
m_queue . clear ( ) ;
2018-06-06 20:33:31 +01:00
}
int CDAPNETGateway : : run ( )
{
bool ret = m_conf . read ( ) ;
if ( ! ret ) {
: : fprintf ( stderr , " DAPNETGateway: cannot read the .ini file \n " ) ;
return 1 ;
}
setlocale ( LC_ALL , " C " ) ;
# if !defined(_WIN32) && !defined(_WIN64)
bool m_daemon = m_conf . getDaemon ( ) ;
if ( m_daemon ) {
// Create new process
pid_t pid = : : fork ( ) ;
if ( pid = = - 1 ) {
2018-06-18 18:53:56 +01:00
: : fprintf ( stderr , " Couldn't fork() , exiting \n " ) ;
2018-06-06 20:33:31 +01:00
return - 1 ;
2018-06-18 18:53:56 +01:00
} else if ( pid ! = 0 ) {
2018-06-06 20:33:31 +01:00
exit ( EXIT_SUCCESS ) ;
2018-06-18 18:53:56 +01:00
}
2018-06-06 20:33:31 +01:00
// Create new session and process group
if ( : : setsid ( ) = = - 1 ) {
2018-06-18 18:53:56 +01:00
: : fprintf ( stderr , " Couldn't setsid(), exiting \n " ) ;
2018-06-06 20:33:31 +01:00
return - 1 ;
}
// Set the working directory to the root directory
if ( : : chdir ( " / " ) = = - 1 ) {
2018-06-18 18:53:56 +01:00
: : fprintf ( stderr , " Couldn't cd /, exiting \n " ) ;
2018-06-06 20:33:31 +01:00
return - 1 ;
}
: : close ( STDIN_FILENO ) ;
: : close ( STDOUT_FILENO ) ;
: : close ( STDERR_FILENO ) ;
2018-06-18 18:53:56 +01:00
// If we are currently root...
2018-06-06 20:33:31 +01:00
if ( getuid ( ) = = 0 ) {
struct passwd * user = : : getpwnam ( " mmdvm " ) ;
if ( user = = NULL ) {
2018-06-18 18:53:56 +01:00
: : fprintf ( stderr , " Could not get the mmdvm user, exiting \n " ) ;
2018-06-06 20:33:31 +01:00
return - 1 ;
}
uid_t mmdvm_uid = user - > pw_uid ;
gid_t mmdvm_gid = user - > pw_gid ;
2018-06-18 18:53:56 +01:00
// Set user and group ID's to mmdvm:mmdvm
2018-06-06 20:33:31 +01:00
if ( setgid ( mmdvm_gid ) ! = 0 ) {
2018-06-18 18:53:56 +01:00
: : fprintf ( stderr , " Could not set mmdvm GID, exiting \n " ) ;
2018-06-06 20:33:31 +01:00
return - 1 ;
}
if ( setuid ( mmdvm_uid ) ! = 0 ) {
2018-06-18 18:53:56 +01:00
: : fprintf ( stderr , " Could not set mmdvm UID, exiting \n " ) ;
2018-06-06 20:33:31 +01:00
return - 1 ;
}
2018-06-18 18:53:56 +01:00
// Double check it worked (AKA Paranoia)
2018-06-06 20:33:31 +01:00
if ( setuid ( 0 ) ! = - 1 ) {
2018-06-18 18:53:56 +01:00
: : fprintf ( stderr , " It's possible to regain root - something is wrong!, exiting \n " ) ;
2018-06-06 20:33:31 +01:00
return - 1 ;
}
}
}
# endif
2018-06-18 18:53:56 +01:00
ret = : : LogInitialise ( m_conf . getLogFilePath ( ) , m_conf . getLogFileRoot ( ) , m_conf . getLogFileLevel ( ) , m_conf . getLogDisplayLevel ( ) ) ;
if ( ! ret ) {
: : fprintf ( stderr , " DAPNETGateway: unable to open the log file \n " ) ;
return 1 ;
}
2018-06-06 20:33:31 +01:00
bool debug = m_conf . getDAPNETDebug ( ) ;
std : : string rptAddress = m_conf . getRptAddress ( ) ;
unsigned int rptPort = m_conf . getRptPort ( ) ;
std : : string myAddress = m_conf . getMyAddress ( ) ;
unsigned int myPort = m_conf . getMyPort ( ) ;
m_pocsagNetwork = new CPOCSAGNetwork ( myAddress , myPort , rptAddress , rptPort , debug ) ;
ret = m_pocsagNetwork - > open ( ) ;
if ( ! ret ) {
: : LogError ( " Cannot open the repeater network port " ) ;
: : LogFinalise ( ) ;
return 1 ;
}
std : : string callsign = m_conf . getCallsign ( ) ;
std : : string dapnetAddress = m_conf . getDAPNETAddress ( ) ;
unsigned int dapnetPort = m_conf . getDAPNETPort ( ) ;
std : : string dapnetAuthKey = m_conf . getDAPNETAuthKey ( ) ;
m_dapnetNetwork = new CDAPNETNetwork ( dapnetAddress , dapnetPort , callsign , dapnetAuthKey , VERSION , debug ) ;
ret = m_dapnetNetwork - > open ( ) ;
if ( ! ret ) {
m_pocsagNetwork - > close ( ) ;
delete m_pocsagNetwork ;
delete m_dapnetNetwork ;
: : LogError ( " Cannot open the DAPNET network port " ) ;
: : LogFinalise ( ) ;
return 1 ;
}
ret = m_dapnetNetwork - > login ( ) ;
if ( ! ret ) {
m_pocsagNetwork - > close ( ) ;
m_dapnetNetwork - > close ( ) ;
delete m_pocsagNetwork ;
delete m_dapnetNetwork ;
: : LogError ( " Cannot login to the DAPNET network " ) ;
: : LogFinalise ( ) ;
return 1 ;
}
: : LogMessage ( " Logged into the DAPNET network " ) ;
LogMessage ( " Starting DAPNETGateway-%s " , VERSION ) ;
for ( ; ; ) {
unsigned char buffer [ 200U ] ;
if ( m_pocsagNetwork - > read ( buffer ) > 0U ) {
switch ( buffer [ 0U ] ) {
case 0x00U :
// The MMDVM is idle
2018-06-21 18:25:51 +01:00
if ( ! m_mmdvmFree ) {
2018-06-25 19:11:33 +01:00
// LogDebug("*** MMDVM is free");
2018-06-21 18:25:51 +01:00
m_mmdvmFree = true ;
2018-06-21 18:56:54 +01:00
m_sentCodewords = ( m_slotTimer . elapsed ( ) * 1000U ) / CODEWORD_TIME_US ;
2018-06-21 18:25:51 +01:00
}
2018-06-06 20:33:31 +01:00
break ;
case 0xFFU :
// The MMDVM is busy
2018-06-25 19:11:33 +01:00
// LogDebug("*** MMDVM is busy");
2018-06-21 18:25:51 +01:00
m_mmdvmFree = false ;
2018-06-06 20:33:31 +01:00
break ;
default :
// The MMDVM is sending crap
LogWarning ( " Unknown data from the MMDVM - 0x%02X " , buffer [ 0U ] ) ;
break ;
}
}
bool ok = m_dapnetNetwork - > read ( ) ;
2018-06-06 21:38:43 +01:00
if ( ! ok )
recover ( ) ;
2018-06-06 20:33:31 +01:00
2018-06-06 22:00:43 +01:00
CPOCSAGMessage * message = m_dapnetNetwork - > readMessage ( ) ;
if ( message ! = NULL ) {
2018-06-21 19:26:08 +01:00
LogDebug ( " Queueing message to %07u, type %u, func %s: \" %.*s \" " , message - > m_ric , message - > m_type , message - > m_functional = = FUNCTIONAL_NUMERIC ? " Numeric " : " Alphanumeric " , message - > m_length , message - > m_message ) ;
m_queue . push_front ( message ) ;
2018-06-06 22:00:43 +01:00
}
2018-06-21 18:56:54 +01:00
unsigned int t = ( m_slotTimer . time ( ) / 100ULL ) % 1024ULL ;
2018-06-21 18:25:51 +01:00
unsigned int slot = t / 64U ;
if ( slot ! = m_currentSlot ) {
2018-06-25 19:11:33 +01:00
// LogDebug("Start of slot %u", slot);
2018-06-21 18:25:51 +01:00
m_currentSlot = slot ;
if ( m_schedule = = NULL | | m_currentSlot = = 0U )
loadSchedule ( ) ;
m_sentCodewords = 0U ;
2018-06-21 18:56:54 +01:00
m_slotTimer . start ( ) ;
2018-06-06 20:33:31 +01:00
}
2018-06-21 18:25:51 +01:00
sendMessages ( ) ;
2018-06-21 18:56:54 +01:00
CThread : : sleep ( 10U ) ;
2018-06-06 20:33:31 +01:00
}
m_pocsagNetwork - > close ( ) ;
delete m_pocsagNetwork ;
m_dapnetNetwork - > close ( ) ;
delete m_dapnetNetwork ;
: : LogFinalise ( ) ;
return 0 ;
}
2018-06-21 18:25:51 +01:00
void CDAPNETGateway : : sendMessages ( )
2018-06-06 20:33:31 +01:00
{
2018-06-21 18:25:51 +01:00
// If the MMDVM is busy, we can't send anything.
2018-06-25 19:11:33 +01:00
if ( ! m_mmdvmFree )
2018-06-21 18:25:51 +01:00
return ;
2018-06-06 20:33:31 +01:00
2018-06-21 18:25:51 +01:00
// Do we have a schedule?
2018-06-25 19:11:33 +01:00
if ( m_schedule = = NULL )
2018-06-21 18:25:51 +01:00
return ;
// Check to see if we're allowed to send within a slot.
2018-06-25 19:11:33 +01:00
if ( ! m_schedule [ m_currentSlot ] )
2018-06-21 18:25:51 +01:00
return ;
// If we have data to send, see if we have time to do so in the current schedule.
if ( m_queue . empty ( ) )
return ;
CPOCSAGMessage * message = m_queue . back ( ) ;
assert ( message ! = NULL ) ;
2018-06-25 19:11:33 +01:00
// Special case, only test if slots are being used.
if ( m_allSlots ) {
sendMessage ( message ) ;
m_queue . pop_back ( ) ;
delete message ;
return ;
}
2018-06-21 18:56:54 +01:00
unsigned int codewords = calculateCodewords ( message ) ;
// Do we have too much data already sent in this slot?
2018-06-21 19:26:08 +01:00
unsigned int totalCodewords = m_sentCodewords + PREAMBLE_LENGTH_CODEWORDS + codewords ;
2018-06-25 19:11:33 +01:00
if ( totalCodewords > = CODEWORDS_PER_SLOT ) {
// LogDebug("Too many codewords sent in slot %u already %u + %u + %u = %u >= %u", m_currentSlot, m_sentCodewords, PREAMBLE_LENGTH_CODEWORDS, codewords, totalCodewords, CODEWORDS_PER_SLOT);
2018-06-21 18:56:54 +01:00
return ;
2018-06-25 19:11:33 +01:00
}
2018-06-21 18:56:54 +01:00
// Is there enough time to send it in this slot before it ends?
2018-06-21 19:26:08 +01:00
unsigned int sendTime = ( PREAMBLE_TIME_US + codewords * CODEWORD_TIME_US ) / 1000U ;
2018-06-21 18:56:54 +01:00
unsigned int timeLeft = SLOT_TIME_MS - m_slotTimer . elapsed ( ) ;
2018-06-25 19:11:33 +01:00
if ( sendTime > = timeLeft ) {
// LogDebug("Too little time to send the message in slot %u, %u + %u + %u = %u >= %u = %u - %u", m_currentSlot, PREAMBLE_TIME_US, codewords, CODEWORD_TIME_US, sendTime, timeLeft, SLOT_TIME_MS, m_slotTimer.elapsed());
2018-06-21 18:25:51 +01:00
return ;
2018-06-25 19:11:33 +01:00
}
2018-06-21 18:25:51 +01:00
2018-06-25 19:11:33 +01:00
bool ret = sendMessage ( message ) ;
if ( ret )
m_sentCodewords = totalCodewords ;
2018-06-21 18:25:51 +01:00
m_queue . pop_back ( ) ;
delete message ;
2018-06-06 20:33:31 +01:00
}
bool CDAPNETGateway : : recover ( )
{
for ( ; ; ) {
m_dapnetNetwork - > close ( ) ;
bool ok = m_dapnetNetwork - > open ( ) ;
if ( ok ) {
ok = m_dapnetNetwork - > login ( ) ;
if ( ok )
return true ;
}
}
return false ;
}
2018-06-13 19:41:35 +01:00
bool CDAPNETGateway : : isTimeMessage ( const CPOCSAGMessage * message ) const
{
if ( message - > m_type = = 5U & & message - > m_functional = = FUNCTIONAL_NUMERIC )
return true ;
if ( message - > m_type = = 6U & & message - > m_functional = = FUNCTIONAL_ALPHANUMERIC & & : : memcmp ( message - > m_message , " XTIME= " , 6U ) = = 0 )
return true ;
return false ;
}
2018-06-21 18:25:51 +01:00
unsigned int CDAPNETGateway : : calculateCodewords ( const CPOCSAGMessage * message ) const
{
assert ( message ! = NULL ) ;
unsigned int len = 0U ;
if ( message - > m_functional = = FUNCTIONAL_NUMERIC )
len = message - > m_length / 5U ; // For the number packing, five to a word
else
2018-06-25 19:11:33 +01:00
len = ( message - > m_length * 7U ) / 20U ; // For the ASCII packing, 7/20 to a word
2018-06-21 18:25:51 +01:00
len + + ; // For the address word
if ( ( len % 2U ) = = 1U ) // Always an even number of words
len + + ;
2018-06-25 19:11:33 +01:00
len + = len % 16U ; // A very long message will include sync words
2018-06-21 18:25:51 +01:00
return len ;
}
void CDAPNETGateway : : loadSchedule ( )
{
bool * schedule = m_dapnetNetwork - > readSchedule ( ) ;
if ( schedule = = NULL )
return ;
delete [ ] m_schedule ;
m_schedule = schedule ;
2018-06-25 19:11:33 +01:00
m_allSlots = true ;
2018-06-21 18:25:51 +01:00
std : : string text ;
for ( unsigned int i = 0U ; i < 16U ; i + + ) {
2018-06-25 19:11:33 +01:00
if ( m_schedule [ i ] ) {
2018-06-21 18:25:51 +01:00
text + = " * " ;
2018-06-25 19:11:33 +01:00
} else {
2018-06-21 18:25:51 +01:00
text + = " - " ;
2018-06-25 19:11:33 +01:00
m_allSlots = false ;
}
2018-06-21 18:25:51 +01:00
}
2018-06-25 19:11:33 +01:00
if ( m_allSlots )
LogMessage ( " All slots are available for transmission " ) ;
else
LogMessage ( " Loaded new schedule: %s " , text . c_str ( ) ) ;
2018-06-21 18:25:51 +01:00
}
2018-06-21 19:26:08 +01:00
2018-06-25 19:11:33 +01:00
bool CDAPNETGateway : : sendMessage ( CPOCSAGMessage * message ) const
2018-06-21 19:26:08 +01:00
{
assert ( message ! = NULL ) ;
2018-06-25 19:11:33 +01:00
bool ret = isTimeMessage ( message ) ;
if ( ret & & message - > m_timeQueued . elapsed ( ) > = MAX_TIME_TO_HOLD_TIME_MESSAGES ) {
2018-06-21 19:26:08 +01:00
LogDebug ( " Rejecting message to %07u, type %u, func %s: \" %.*s \" " , message - > m_ric , message - > m_type , message - > m_functional = = FUNCTIONAL_NUMERIC ? " Numeric " : " Alphanumeric " , message - > m_length , message - > m_message ) ;
2018-06-25 19:11:33 +01:00
return false ;
} else {
LogMessage ( " Sending message in slot %u to %07u, type %u, func %s: \" %.*s \" " , m_currentSlot , message - > m_ric , message - > m_type , message - > m_functional = = FUNCTIONAL_NUMERIC ? " Numeric " : " Alphanumeric " , message - > m_length , message - > m_message ) ;
m_pocsagNetwork - > write ( message ) ;
return true ;
2018-06-21 19:26:08 +01:00
}
}