smartsdr-dsp/DSP_API/SmartSDR_Interface/discovery_client.c

321 lines
8.9 KiB
C
Raw Normal View History

/* *****************************************************************************
* discovery_client.c
*
* \brief Discovery Client - Receives and parses discovery packets
*
*
* \author Eric Wachsmann, KE5DTO
* \date 2014-09-01
*
*******************************************************************************
*
* Copyright (C) 2014 FlexRadio Systems.
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*
* Contact Information:
* email: gpl<at>flexradiosystems.com
* Mail: FlexRadio Systems, Suite 1-150, 4616 W. Howard LN, Austin, TX 78728
*
* ***************************************************************************/
#include <stdio.h>
#include <stdlib.h> // for malloc
#include <string.h> // for memset
#include <sys/socket.h>
#include <netinet/in.h> // for htonl, htons, IPPROTO_UDP
#include <pthread.h>
#include <errno.h> // for errno
#include <unistd.h>
#include "common.h"
#include "discovery_client.h"
#include "cmd.h" // for tokenize
#include "io_utils.h"
static int _dc_sock;
static pthread_t _dc_thread;
static BOOL _dc_abort = FALSE;
void printRadio(Radio radio)
{
output("discovery_protocol_version=%s model=%s serial=%s version=%s nickname=%s callsign=%s ip=%s port=%s status=%s\n",
radio->discovery_protocol_version,
radio->model,
radio->serial,
radio->version,
radio->nickname,
radio->callsign,
radio->ip,
radio->port,
radio->status);
}
char* protocol_version;
char* model;
char* serial;
char* version;
char* nickname;
char* callsign;
char* ip;
char* port;
char* status;
static void _dc_RadioFound(Radio radio)
{
if(getIP(radio->ip) == ntohl(net_get_ip()))
{
output("Radio found");
// yes -- connect and stop looking for more radios
// TODO: connect
// start a keepalive to keep the channel open and know when it dies
tc_Init(radio->ip, radio->port);
tc_startKeepalive();
usleep(250000);
hal_Listener_Init();
dc_Exit();
}
// print the content of the object
//printRadio(radio);
// because the radio object is malloc'ed, we need to recover the memory
safe_free(radio);
radio = 0;
}
static void _dc_ListenerParsePacket(uint8* packet, int32 length, struct sockaddr_in* sender)
{
//output("_dc_ListenerParsePacket\n");
// is this packet long enough to inspect for VITA header info?
if(length < 16)
{
// no -- discard the packet
//output("_dc_ListenerParsePacket: packet too short\n");
return;
}
// cast the incoming packet as a VITA packet
VitaIFData p = (VitaIFData)packet;
// does this packet have our OUI?
if(ntohl(p->class_id_h) != 0x00001C2D)
{
// no -- discard this packet
output("_dc_ListenerParsePacket: wrong OUI (0x%08X)\n", htonl(p->class_id_h));
return;
}
// is this packet an extended data packet?
if((ntohl(p->header) & VITA_HEADER_PACKET_TYPE_MASK) != VITA_PACKET_TYPE_EXT_DATA_WITH_STREAM_ID)
{
// no -- discard this packet
output("_dc_ListenerParsePacket: wrong packet type (0x%08X)\n", p->header & VITA_HEADER_PACKET_TYPE_MASK);
return;
}
// is this packet marked as a SL_VITA_DISCOVERY_CLASS?
if((ntohl(p->class_id_l) & VITA_CLASS_ID_PACKET_CLASS_MASK) != 0xFFFF)
{
// no -- discard this packet
output("_dc_ListenerParsePacket: wrong packet class (0x%04X)\n", p->class_id_l & VITA_CLASS_ID_PACKET_CLASS_MASK);
return;
}
// if we made it this far, then we can safely assume this is a
// discovery packet and we will attempt to split the payload up
// similar to a command on spaces and parse the data
int argc, i;
char *argv[MAX_ARGC + 1]; //Add one extra so we can null terminate the array
// split the payload string up
tokenize((char*)p->payload, &argc, argv, MAX_ARGC);
//output("_dc_ListenerParsePacket: payload: %s\n", p->payload);
//output("_dc_ListenerParsePacket: tokenize argc=%u\n", argc);
Radio radio = (Radio)safe_malloc(sizeof(radioType));
if(!radio)
{
output("_dc_ListenerParsePacket: Out of memory!\n");
return;
}
// clear the newly allocated memory
memset(radio, 0, sizeof(radioType));
// for each token, process the string
for(i=0; i<argc; i++)
{
if(strncmp(argv[i], "discovery_protocol_version", strlen("discovery_protocol_version")) == 0)
radio->discovery_protocol_version = argv[i]+strlen("discovery_protocol_version=");
else if(strncmp(argv[i], "model", strlen("model")) == 0)
radio->model = argv[i]+strlen("model=");
else if(strncmp(argv[i], "serial", strlen("serial")) == 0)
radio->serial = argv[i]+strlen("serial=");
else if(strncmp(argv[i], "version", strlen("version")) == 0)
radio->version = argv[i]+strlen("version=");
else if(strncmp(argv[i], "nickname", strlen("nickname")) == 0)
radio->nickname = argv[i]+strlen("nickname=");
else if(strncmp(argv[i], "callsign", strlen("callsign")) == 0)
radio->callsign = argv[i]+strlen("callsign=");
else if(strncmp(argv[i], "ip", strlen("ip")) == 0)
radio->ip = argv[i]+strlen("ip=");
else if(strncmp(argv[i], "port", strlen("port")) == 0)
radio->port = argv[i]+strlen("port=");
else if(strncmp(argv[i], "status", strlen("status")) == 0)
radio->status = argv[i]+strlen("status=");
}
// did we get at least an IP, port, and version?
if(radio->ip != 0 && radio->port != 0 && radio->version != 0)
{
// yes -- report the radio as found
_dc_RadioFound(radio);
}
}
static struct timeval timeout;
//! Allocates a buffer and receives one packet.
//! /param buffer Buffer to be allocated and populated with packet data
//! /returns Number of bytes read if successful, otherwise an error (recvfrom)
static BOOL _dc_ListenerRecv(uint8* buffer, int32* len, struct sockaddr_in* sender_addr)
{
//output("_dc_ListenerRecv\n");
uint32 addr_len = sizeof(struct sockaddr_in);
// we will wait up to 1 second for data in case someone is trying to abort us
timeout.tv_sec = 1;
timeout.tv_usec = 0;
fd_set socks;
FD_ZERO(&socks);
FD_SET(_dc_sock, &socks);
// see if there is data in the socket (but timeout if none)
select(_dc_sock + 1, &socks, NULL, NULL, &timeout);
if (FD_ISSET(_dc_sock, &socks))
{
// yes there is data -- get it
*len = recvfrom(_dc_sock, buffer, ETH_FRAME_LEN, 0, (struct sockaddr*)sender_addr, &addr_len);
//precisionTimerLap("HAL Listener Recv");
if(*len < 0)
output("_dc_ListenerRecv: recvfrom returned -1 errno=%08X\n", errno);
//else
//output("_hal_ListenerRecv: Error len=%d sender=%s:%u\n", len, inet_ntoa(sender_addr.sin_addr), htons(sender_addr.sin_port));
// TODO: May need to filter here to handle security (packet injection)
return TRUE;
}
*len = 0;
return FALSE;
}
static void* _dc_ListenerLoop(void* param)
{
//printf("_dc_ListenerLoop\n");
struct sockaddr_in sender;
uint8 buf[ETH_FRAME_LEN];
while(!_dc_abort)
{
// get some data
int32 length = 0;
BOOL success = FALSE;
while (!success && !_dc_abort)
{
memset(&sender,0,sizeof(struct sockaddr_in));
memset(&buf,0,ETH_FRAME_LEN);
success = _dc_ListenerRecv(buf, &length, &sender);
}
if (!_dc_abort)
{
if(length == 0) // socket has been closed
{
output("_dc_ListenerLoop error: socket closed\n");
break;
}
if(length < 0)
{
output("_dc_ListenerLoop error: loop stopped\n");
break;
}
// length was reasonable -- lets try to parse the packet
//precisionTimerLap("HAL Listener Parse Packet");
_dc_ListenerParsePacket(buf, length, &sender);
}
}
return NULL;
}
void dc_Init(void)
{
output("Discovery Client Init: Opening socket");
int true = TRUE;
if((_dc_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1)
{
output("...failed! (socket call returned -1)\n");
return;
}
// set up destination address
struct sockaddr_in addr;
memset(&addr,0,sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY); // this is where we could limit to one IP
addr.sin_port=htons(DISCOVERY_PORT);
/* If you're running on the same box as the smartsdr firmware this is necessary so
* that both processes can bind to the same VITA port
*/
errno = 0;
setsockopt(_dc_sock, SOL_SOCKET, SO_REUSEADDR, &true, sizeof(true));
if (errno)
{
output("error with reuse option: errno=%d",errno);
}
// bind the socket to the port and/or IP
output("...binding");
errno = 0;
if(bind(_dc_sock, (struct sockaddr *)&addr, sizeof(addr)) == -1)
{
output("...failed! (bind call returned -1: errno:%d)\n", errno);
return;
}
output("\n");
// start the listener thread
pthread_create(&_dc_thread, NULL, &_dc_ListenerLoop, NULL);
}
void dc_Exit(void)
{
_dc_abort = TRUE;
}