direwolf/server.c

1250 lines
34 KiB
C

//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2011,2012,2013 John Langner, WB2OSZ
//
// 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, see <http://www.gnu.org/licenses/>.
//
/*------------------------------------------------------------------
*
* Module: server.c
*
* Purpose: Provide service to other applications via "AGW TCPIP Socket Interface".
*
* Input:
*
* Outputs:
*
* Description: This provides a TCP socket for communication with a client application.
* It implements a subset of the AGW socket interface.
*
* Commands from application recognized:
*
* 'R' Request for version number.
* (See below for response.)
*
* 'G' Ask about radio ports.
* (See below for response.)
*
* 'g' Capabilities of a port. (new in 0.8)
* (See below for response.)
*
* 'k' Ask to start receiving RAW AX25 frames.
*
* 'm' Ask to start receiving Monitor AX25 frames.
*
* 'V' Transmit UI data frame.
* Generate audio for transmission.
*
* 'H' Report recently heard stations. Not implemented yet.
*
* 'K' Transmit raw AX.25 frame.
*
* 'X' Register CallSign
*
* 'x' Unregister CallSign
*
* A message is printed if any others are received.
*
* TODO: Should others be implemented?
*
*
* Messages sent to client application:
*
* 'R' Reply to Request for version number.
* Currently responds with major 1, minor 0.
*
* 'G' Reply to Ask about radio ports.
*
* 'g' Reply to capabilities of a port. (new in 0.8)
*
* 'K' Received AX.25 frame in raw format.
* (Enabled with 'k' command.)
*
* 'U' Received AX.25 frame in monitor format.
* (Enabled with 'm' command.)
*
*
*
* References: AGWPE TCP/IP API Tutorial
* http://uz7ho.org.ua/includes/agwpeapi.htm
*
* Getting Started with Winsock
* http://msdn.microsoft.com/en-us/library/windows/desktop/bb530742(v=vs.85).aspx
*
*---------------------------------------------------------------*/
/*
* Native Windows: Use the Winsock interface.
* Linux: Use the BSD socket interface.
* Cygwin: Can use either one.
*/
#if __WIN32__
#include <winsock2.h>
#define _WIN32_WINNT 0x0501
#include <ws2tcpip.h>
#else
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#endif
#include <unistd.h>
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <time.h>
#include <ctype.h>
#include "direwolf.h"
#include "tq.h"
#include "ax25_pad.h"
#include "textcolor.h"
#include "audio.h"
#include "server.h"
static int client_sock; /* File descriptor for socket for */
/* communication with client application. */
/* Set to -1 if not connected. */
/* (Don't use SOCKET type because it is unsigned.) */
static int enable_send_raw_to_client; /* Should we send received packets to client app? */
static int enable_send_monitor_to_client;
static int num_channels; /* Number of radio ports. */
static void * connect_listen_thread (void *arg);
static void * cmd_listen_thread (void *arg);
/*
* Message header for AGW protocol.
* Assuming little endian such as x86 or ARM.
* Byte swapping would be required for big endian cpu.
*/
#if __GNUC__
#if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__
#error This needs to be more portable to work on big endian.
#endif
#endif
struct agwpe_s {
short portx; /* 0 for first, 1 for second, etc. */
short port_hi_reserved;
short kind_lo; /* message type */
short kind_hi;
char call_from[10];
char call_to[10];
int data_len; /* Number of data bytes following. */
int user_reserved;
};
/*-------------------------------------------------------------------
*
* Name: debug_print
*
* Purpose: Print message to/from client for debugging.
*
* Inputs: fromto - Direction of message.
* pmsg - Address of the message block.
* msg_len - Length of the message.
*
*--------------------------------------------------------------------*/
static int debug_client = 0; /* Print information flowing from and to client. */
void server_set_debug (int n)
{
debug_client = n;
}
void hex_dump (unsigned char *p, int len)
{
int n, i, offset;
offset = 0;
while (len > 0) {
n = len < 16 ? len : 16;
dw_printf (" %03x: ", offset);
for (i=0; i<n; i++) {
dw_printf (" %02x", p[i]);
}
for (i=n; i<16; i++) {
dw_printf (" ");
}
dw_printf (" ");
for (i=0; i<n; i++) {
dw_printf ("%c", isprint(p[i]) ? p[i] : '.');
}
dw_printf ("\n");
p += 16;
offset += 16;
len -= 16;
}
}
typedef enum fromto_e { FROM_CLIENT=0, TO_CLIENT=1 } fromto_t;
static void debug_print (fromto_t fromto, struct agwpe_s *pmsg, int msg_len)
{
char direction [10];
char datakind[80];
const char *prefix [2] = { "<<<", ">>>" };
switch (fromto) {
case FROM_CLIENT:
strcpy (direction, "from"); /* from the client application */
switch (pmsg->kind_lo) {
case 'P': strcpy (datakind, "Application Login"); break;
case 'X': strcpy (datakind, "Register CallSign"); break;
case 'x': strcpy (datakind, "Unregister CallSign"); break;
case 'G': strcpy (datakind, "Ask Port Information"); break;
case 'm': strcpy (datakind, "Enable Reception of Monitoring Frames"); break;
case 'R': strcpy (datakind, "AGWPE Version Info"); break;
case 'g': strcpy (datakind, "Ask Port Capabilities"); break;
case 'H': strcpy (datakind, "Callsign Heard on a Port"); break;
case 'y': strcpy (datakind, "Ask Outstanding frames waiting on a Port"); break;
case 'Y': strcpy (datakind, "Ask Outstanding frames waiting for a connection"); break;
case 'M': strcpy (datakind, "Send UNPROTO Information"); break;
case 'C': strcpy (datakind, "Connect, Start an AX.25 Connection"); break;
case 'D': strcpy (datakind, "Send Connected Data"); break;
case 'd': strcpy (datakind, "Disconnect, Terminate an AX.25 Connection"); break;
case 'v': strcpy (datakind, "Connect VIA, Start an AX.25 circuit thru digipeaters"); break;
case 'V': strcpy (datakind, "Send UNPROTO VIA"); break;
case 'c': strcpy (datakind, "Non-Standard Connections, Connection with PID"); break;
case 'K': strcpy (datakind, "Send data in raw AX.25 format"); break;
case 'k': strcpy (datakind, "Activate reception of Frames in raw format"); break;
default: strcpy (datakind, "**INVALID**"); break;
}
break;
case TO_CLIENT:
default:
strcpy (direction, "to"); /* sent to the client application. */
switch (pmsg->kind_lo) {
case 'R': strcpy (datakind, "Version Number"); break;
case 'X': strcpy (datakind, "Callsign Registration"); break;
case 'G': strcpy (datakind, "Port Information"); break;
case 'g': strcpy (datakind, "Capabilities of a Port"); break;
case 'y': strcpy (datakind, "Frames Outstanding on a Port"); break;
case 'Y': strcpy (datakind, "Frames Outstanding on a Connection"); break;
case 'H': strcpy (datakind, "Heard Stations on a Port"); break;
case 'C': strcpy (datakind, "AX.25 Connection Received"); break;
case 'D': strcpy (datakind, "Connected AX.25 Data"); break;
case 'M': strcpy (datakind, "Monitored Connected Information"); break;
case 'S': strcpy (datakind, "Monitored Supervisory Information"); break;
case 'U': strcpy (datakind, "Monitored Unproto Information"); break;
case 'T': strcpy (datakind, "Monitoring Own Information"); break;
case 'K': strcpy (datakind, "Monitored Information in Raw Format"); break;
default: strcpy (datakind, "**INVALID**"); break;
}
}
text_color_set(DW_COLOR_DEBUG);
dw_printf ("\n");
dw_printf ("%s %s %s AGWPE client application, total length = %d\n",
prefix[(int)fromto], datakind, direction, msg_len);
dw_printf ("\tportx = %d, port_hi_reserved = %d\n", pmsg->portx, pmsg->port_hi_reserved);
dw_printf ("\tkind_lo = %d = '%c', kind_hi = %d\n", pmsg->kind_lo, pmsg->kind_lo, pmsg->kind_hi);
dw_printf ("\tcall_from = \"%s\", call_to = \"%s\"\n", pmsg->call_from, pmsg->call_to);
dw_printf ("\tdata_len = %d, user_reserved = %d, data =\n", pmsg->data_len, pmsg->user_reserved);
hex_dump ((char*)pmsg + sizeof(struct agwpe_s), pmsg->data_len);
if (msg_len < 36) {
text_color_set (DW_COLOR_ERROR);
dw_printf ("AGWPE message length, %d, is shorter than minumum 36.\n", msg_len);
}
if (msg_len != pmsg->data_len + 36) {
text_color_set (DW_COLOR_ERROR);
dw_printf ("AGWPE message length, %d, inconsistent with data length %d.\n", msg_len, pmsg->data_len);
}
}
/*-------------------------------------------------------------------
*
* Name: server_init
*
* Purpose: Set up a server to listen for connection requests from
* an application such as Xastir.
*
* Inputs: mc->agwpe_port - TCP port for server.
* Main program has default of 8000 but allows
* an alternative to be specified on the command line
*
* Outputs:
*
* Description: This starts two threads:
* * to listen for a connection from client app.
* * to listen for commands from client app.
* so the main application doesn't block while we wait for these.
*
*--------------------------------------------------------------------*/
void server_init (struct misc_config_s *mc)
{
#if __WIN32__
HANDLE connect_listen_th;
HANDLE cmd_listen_th;
#else
pthread_t connect_listen_tid;
pthread_t cmd_listen_tid;
#endif
int e;
int server_port = mc->agwpe_port;
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("server_init ( %d )\n", server_port);
debug_a = 1;
#endif
client_sock = -1;
enable_send_raw_to_client = 0;
enable_send_monitor_to_client = 0;
num_channels = mc->num_channels;
/*
* This waits for a client to connect and sets client_sock.
*/
#if __WIN32__
connect_listen_th = _beginthreadex (NULL, 0, connect_listen_thread, (void *)server_port, 0, NULL);
if (connect_listen_th == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Could not create AGW connect listening thread\n");
return;
}
#else
e = pthread_create (&connect_listen_tid, NULL, connect_listen_thread, (void *)(long)server_port);
if (e != 0) {
text_color_set(DW_COLOR_ERROR);
perror("Could not create AGW connect listening thread");
return;
}
#endif
/*
* This reads messages from client when client_sock is valid.
*/
#if __WIN32__
cmd_listen_th = _beginthreadex (NULL, 0, cmd_listen_thread, NULL, 0, NULL);
if (cmd_listen_th == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Could not create AGW command listening thread\n");
return;
}
#else
e = pthread_create (&cmd_listen_tid, NULL, cmd_listen_thread, NULL);
if (e != 0) {
text_color_set(DW_COLOR_ERROR);
perror("Could not create AGW command listening thread");
return;
}
#endif
}
/*-------------------------------------------------------------------
*
* Name: connect_listen_thread
*
* Purpose: Wait for a connection request from an application.
*
* Inputs: arg - TCP port for server.
* Main program has default of 8000 but allows
* an alternative to be specified on the command line
*
* Outputs: client_sock - File descriptor for communicating with client app.
*
* Description: Wait for connection request from client and establish
* communication.
* Note that the client can go away and come back again and
* re-establish communication without restarting this application.
*
*--------------------------------------------------------------------*/
static void * connect_listen_thread (void *arg)
{
#if __WIN32__
struct addrinfo hints;
struct addrinfo *ai = NULL;
int err;
char server_port_str[12];
SOCKET listen_sock;
WSADATA wsadata;
sprintf (server_port_str, "%d", (int)(long)arg);
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("DEBUG: serverport = %d = '%s'\n", (int)(long)arg, server_port_str);
#endif
err = WSAStartup (MAKEWORD(2,2), &wsadata);
if (err != 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf("WSAStartup failed: %d\n", err);
return (NULL);
}
if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 2) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Could not find a usable version of Winsock.dll\n");
WSACleanup();
//sleep (1);
return (NULL);
}
memset (&hints, 0, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = AI_PASSIVE;
err = getaddrinfo(NULL, server_port_str, &hints, &ai);
if (err != 0) {
text_color_set(DW_COLOR_ERROR);
dw_printf("getaddrinfo failed: %d\n", err);
//sleep (1);
WSACleanup();
return (NULL);
}
listen_sock= socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
if (listen_sock == INVALID_SOCKET) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("connect_listen_thread: Socket creation failed, err=%d", WSAGetLastError());
return (NULL);
}
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf("Binding to port %s ... \n", server_port_str);
#endif
err = bind( listen_sock, ai->ai_addr, (int)ai->ai_addrlen);
if (err == SOCKET_ERROR) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Bind failed with error: %d\n", WSAGetLastError());
dw_printf("Some other application is probably already using port %s.\n", server_port_str);
freeaddrinfo(ai);
closesocket(listen_sock);
WSACleanup();
return (NULL);
}
freeaddrinfo(ai);
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf("opened socket as fd (%d) on port (%s) for stream i/o\n", listen_sock, server_port_str );
#endif
while (1) {
while (client_sock > 0) {
SLEEP_SEC(1); /* Already connected. Try again later. */
}
#define QUEUE_SIZE 5
if(listen(listen_sock,QUEUE_SIZE) == SOCKET_ERROR)
{
text_color_set(DW_COLOR_ERROR);
dw_printf("Listen failed with error: %d\n", WSAGetLastError());
return (NULL);
}
text_color_set(DW_COLOR_INFO);
dw_printf("Ready to accept AGW client application on port %s ...\n", server_port_str);
client_sock = accept(listen_sock, NULL, NULL);
if (client_sock == -1) {
text_color_set(DW_COLOR_ERROR);
dw_printf("Accept failed with error: %d\n", WSAGetLastError());
closesocket(listen_sock);
WSACleanup();
return (NULL);
}
text_color_set(DW_COLOR_INFO);
dw_printf("\nConnected to AGW client application ...\n\n");
/*
* The command to change this is actually a toggle, not explicit on or off.
* Make sure it has proper state when we get a new connection.
*/
enable_send_raw_to_client = 0;
enable_send_monitor_to_client = 0;
}
#else
struct sockaddr_in sockaddr; /* Internet socket address stuct */
socklen_t sockaddr_size = sizeof(struct sockaddr_in);
int server_port = (int)(long)arg;
int listen_sock;
listen_sock= socket(AF_INET,SOCK_STREAM,0);
if (listen_sock == -1) {
text_color_set(DW_COLOR_ERROR);
perror ("connect_listen_thread: Socket creation failed");
return (NULL);
}
sockaddr.sin_addr.s_addr = INADDR_ANY;
sockaddr.sin_port = htons(server_port);
sockaddr.sin_family = AF_INET;
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf("Binding to port %d ... \n", server_port);
#endif
if (bind(listen_sock,(struct sockaddr*)&sockaddr,sizeof(sockaddr)) == -1) {
text_color_set(DW_COLOR_ERROR);
perror ("connect_listen_thread: Bind failed");
return (NULL);
}
getsockname( listen_sock, (struct sockaddr *)(&sockaddr), &sockaddr_size);
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf("opened socket as fd (%d) on port (%d) for stream i/o\n", listen_sock, ntohs(sockaddr.sin_port) );
#endif
while (1) {
while (client_sock > 0) {
SLEEP_SEC(1); /* Already connected. Try again later. */
}
#define QUEUE_SIZE 5
if(listen(listen_sock,QUEUE_SIZE) == -1)
{
text_color_set(DW_COLOR_ERROR);
perror ("connect_listen_thread: Listen failed");
return (NULL);
}
text_color_set(DW_COLOR_INFO);
dw_printf("Ready to accept AGW client application on port %d ...\n", server_port);
client_sock = accept(listen_sock, (struct sockaddr*)(&sockaddr),&sockaddr_size);
text_color_set(DW_COLOR_INFO);
dw_printf("\nConnected to AGW client application ...\n\n");
/*
* The command to change this is actually a toggle, not explicit on or off.
* Make sure it has proper state when we get a new connection.
*/
enable_send_raw_to_client = 0;
enable_send_monitor_to_client = 0;
}
#endif
}
/*-------------------------------------------------------------------
*
* Name: server_send_rec_packet
*
* Purpose: Send a received packet to the client app.
*
* Inputs: chan - Channel number where packet was received.
* 0 = first, 1 = second if any.
*
* pp - Identifier for packet object.
*
* fbuf - Address of raw received frame buffer.
* flen - Length of raw received frame.
*
*
* Description: Send message to client if connected.
* Disconnect from client, and notify user, if any error.
*
* There are two different formats:
* RAW - the original received frame.
* MONITOR - just the information part.
*
*--------------------------------------------------------------------*/
void server_send_rec_packet (int chan, packet_t pp, unsigned char *fbuf, int flen)
{
struct {
struct agwpe_s hdr;
char data[1+AX25_MAX_PACKET_LEN];
} agwpe_msg;
int err;
int info_len;
unsigned char *pinfo;
/*
* RAW format
*/
if (enable_send_raw_to_client
&& client_sock > 0){
memset (&agwpe_msg.hdr, 0, sizeof(agwpe_msg.hdr));
agwpe_msg.hdr.portx = chan;
agwpe_msg.hdr.kind_lo = 'K';
ax25_get_addr_with_ssid (pp, AX25_SOURCE, agwpe_msg.hdr.call_from);
ax25_get_addr_with_ssid (pp, AX25_DESTINATION, agwpe_msg.hdr.call_to);
agwpe_msg.hdr.data_len = flen + 1;
/* Stick in extra byte for the "TNC" to use. */
agwpe_msg.data[0] = 0;
memcpy (agwpe_msg.data + 1, fbuf, (size_t)flen);
if (debug_client) {
debug_print (TO_CLIENT, &agwpe_msg.hdr, sizeof(agwpe_msg.hdr) + agwpe_msg.hdr.data_len);
}
#if __WIN32__
err = send (client_sock, (char*)(&agwpe_msg), sizeof(agwpe_msg.hdr) + agwpe_msg.hdr.data_len, 0);
if (err == SOCKET_ERROR)
{
text_color_set(DW_COLOR_ERROR);
dw_printf ("\nError %d sending message to AGW client application. Closing connection.\n\n", WSAGetLastError());
closesocket (client_sock);
client_sock = -1;
WSACleanup();
}
#else
err = write (client_sock, &agwpe_msg, sizeof(agwpe_msg.hdr) + agwpe_msg.hdr.data_len);
if (err <= 0)
{
text_color_set(DW_COLOR_ERROR);
dw_printf ("\nError sending message to AGW client application. Closing connection.\n\n");
close (client_sock);
client_sock = -1;
}
#endif
}
/* MONITOR format - only for UI frames. */
if (enable_send_monitor_to_client
&& client_sock > 0
&& ax25_get_control(pp) == AX25_UI_FRAME){
time_t clock;
struct tm *tm;
clock = time(NULL);
tm = localtime(&clock);
memset (&agwpe_msg.hdr, 0, sizeof(agwpe_msg.hdr));
agwpe_msg.hdr.portx = chan;
agwpe_msg.hdr.kind_lo = 'U';
ax25_get_addr_with_ssid (pp, AX25_SOURCE, agwpe_msg.hdr.call_from);
ax25_get_addr_with_ssid (pp, AX25_DESTINATION, agwpe_msg.hdr.call_to);
info_len = ax25_get_info (pp, &pinfo);
/* http://uz7ho.org.ua/includes/agwpeapi.htm#_Toc500723812 */
/* Description mentions one CR character after timestamp but example has two. */
/* Actual observed cases have only one. */
/* Also need to add extra CR, CR, null at end. */
/* The documentation example includes these 3 extra in the Len= value */
/* but actual observed data uses only the packet info length. */
sprintf (agwpe_msg.data, " %d:Fm %s To %s <UI pid=%02X Len=%d >[%02d:%02d:%02d]\r%s\r\r",
chan+1, agwpe_msg.hdr.call_from, agwpe_msg.hdr.call_to,
ax25_get_pid(pp), info_len,
tm->tm_hour, tm->tm_min, tm->tm_sec,
pinfo);
agwpe_msg.hdr.data_len = strlen(agwpe_msg.data) + 1 /* include null */ ;
if (debug_client) {
debug_print (TO_CLIENT, &agwpe_msg.hdr, sizeof(agwpe_msg.hdr) + agwpe_msg.hdr.data_len);
}
#if __WIN32__
err = send (client_sock, (char*)(&agwpe_msg), sizeof(agwpe_msg.hdr) + agwpe_msg.hdr.data_len, 0);
if (err == SOCKET_ERROR)
{
text_color_set(DW_COLOR_ERROR);
dw_printf ("\nError %d sending message to AGW client application. Closing connection.\n\n", WSAGetLastError());
closesocket (client_sock);
client_sock = -1;
WSACleanup();
}
#else
err = write (client_sock, &agwpe_msg, sizeof(agwpe_msg.hdr) + agwpe_msg.hdr.data_len);
if (err <= 0)
{
text_color_set(DW_COLOR_ERROR);
dw_printf ("\nError sending message to AGW client application. Closing connection.\n\n");
close (client_sock);
client_sock = -1;
}
#endif
}
} /* server_send_rec_packet */
/*-------------------------------------------------------------------
*
* Name: read_from_socket
*
* Purpose: Read from socket until we have desired number of bytes.
*
* Inputs: fd - file descriptor.
* ptr - address where data should be placed.
* len - desired number of bytes.
*
* Description: Just a wrapper for the "read" system call but it should
* never return fewer than the desired number of bytes.
*
*--------------------------------------------------------------------*/
static int read_from_socket (int fd, char *ptr, int len)
{
int got_bytes = 0;
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("read_from_socket (%d, %p, %d)\n", fd, ptr, len);
#endif
while (got_bytes < len) {
int n;
#if __WIN32__
//TODO: any flags for send/recv?
n = recv (fd, ptr + got_bytes, len - got_bytes, 0);
#else
n = read (fd, ptr + got_bytes, len - got_bytes);
#endif
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("read_from_socket: n = %d\n", n);
#endif
if (n <= 0) {
return (n);
}
got_bytes += n;
}
assert (got_bytes >= 0 && got_bytes <= len);
#if DEBUG
text_color_set(DW_COLOR_DEBUG);
dw_printf ("read_from_socket: return %d\n", got_bytes);
#endif
return (got_bytes);
}
/*-------------------------------------------------------------------
*
* Name: cmd_listen_thread
*
* Purpose: Wait for command messages from an application.
*
* Inputs: arg - Not used.
*
* Outputs: client_sock - File descriptor for communicating with client app.
*
* Description: Process messages from the client application.
* Note that the client can go away and come back again and
* re-establish communication without restarting this application.
*
*--------------------------------------------------------------------*/
static void * cmd_listen_thread (void *arg)
{
int n;
struct {
struct agwpe_s hdr; /* Command header. */
char data[512]; /* Additional data used by some commands. */
/* Maximum for 'V': 1 + 8*10 + 256 */
} cmd;
while (1) {
while (client_sock <= 0) {
SLEEP_SEC(1); /* Not connected. Try again later. */
}
n = read_from_socket (client_sock, (char *)(&cmd.hdr), sizeof(cmd.hdr));
if (n != sizeof(cmd.hdr)) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("\nError getting message header from client application.\n");
dw_printf ("Tried to read %d bytes but got only %d.\n", (int)sizeof(cmd.hdr), n);
dw_printf ("Closing connection.\n\n");
#if __WIN32__
closesocket (client_sock);
#else
close (client_sock);
#endif
client_sock = -1;
continue;
}
assert (cmd.hdr.data_len >= 0 && cmd.hdr.data_len < sizeof(cmd.data));
cmd.data[0] = '\0';
if (cmd.hdr.data_len > 0) {
n = read_from_socket (client_sock, cmd.data, cmd.hdr.data_len);
if (n != cmd.hdr.data_len) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("\nError getting message data from client application.\n");
dw_printf ("Tried to read %d bytes but got only %d.\n", cmd.hdr.data_len, n);
dw_printf ("Closing connection.\n\n");
#if __WIN32__
closesocket (client_sock);
#else
close (client_sock);
#endif
client_sock = -1;
return NULL;
}
if (n > 0) {
cmd.data[cmd.hdr.data_len] = '\0';
}
}
/*
* print & process message from client.
*/
if (debug_client) {
debug_print (FROM_CLIENT, &cmd.hdr, sizeof(cmd.hdr) + cmd.hdr.data_len);
}
switch (cmd.hdr.kind_lo) {
case 'R': /* Request for version number */
{
struct {
struct agwpe_s hdr;
int major_version;
int minor_version;
} reply;
memset (&reply, 0, sizeof(reply));
reply.hdr.kind_lo = 'R';
reply.hdr.data_len = sizeof(reply.major_version) + sizeof(reply.minor_version);
assert (reply.hdr.data_len ==8);
// Xastir only prints this and doesn't care otherwise.
// APRSIS32 doesn't seem to care.
// UI-View32 wants on 2000.15 or later.
reply.major_version = 2005;
reply.minor_version = 127;
assert (sizeof(reply) == 44);
if (debug_client) {
debug_print (TO_CLIENT, &reply.hdr, sizeof(reply));
}
// TODO: Should have unified function instead of multiple versions everywhere.
#if __WIN32__
send (client_sock, (char*)(&reply), sizeof(reply), 0);
#else
write (client_sock, &reply, sizeof(reply));
#endif
}
break;
case 'G': /* Ask about radio ports */
{
struct {
struct agwpe_s hdr;
char info[100];
} reply;
memset (&reply, 0, sizeof(reply));
reply.hdr.kind_lo = 'G';
reply.hdr.data_len = sizeof (reply.info);
// Xastir only prints this and doesn't care otherwise.
// YAAC uses this to identify available channels.
if (num_channels == 1) {
sprintf (reply.info, "1;Port1 Single channel;");
}
else {
sprintf (reply.info, "2;Port1 Left channel;Port2 Right Channel;");
}
assert (reply.hdr.data_len == 100);
if (debug_client) {
debug_print (TO_CLIENT, &reply.hdr, sizeof(reply));
}
#if __WIN32__
send (client_sock, (char*)(&reply), sizeof(reply), 0);
#else
write (client_sock, &reply, sizeof(reply));
#endif
}
break;
case 'g': /* Ask about capabilities of a port. */
{
struct {
struct agwpe_s hdr;
unsigned char on_air_baud_rate; /* 0=1200, 3=9600 */
unsigned char traffic_level; /* 0xff if not in autoupdate mode */
unsigned char tx_delay;
unsigned char tx_tail;
unsigned char persist;
unsigned char slottime;
unsigned char maxframe;
unsigned char active_connections;
int how_many_bytes;
} reply;
memset (&reply, 0, sizeof(reply));
reply.hdr.portx = cmd.hdr.portx; /* Reply with same port number ! */
reply.hdr.kind_lo = 'g';
reply.hdr.data_len = 12;
// YAAC asks for this.
// Fake it to keep application happy.
reply.on_air_baud_rate = 0;
reply.traffic_level = 1;
reply.tx_delay = 0x19;
reply.tx_tail = 4;
reply.persist = 0xc8;
reply.slottime = 4;
reply.maxframe = 7;
reply.active_connections = 0;
reply.how_many_bytes = 1;
assert (sizeof(reply) == 48);
if (debug_client) {
debug_print (TO_CLIENT, &reply.hdr, sizeof(reply));
}
#if __WIN32__
send (client_sock, (char*)(&reply), sizeof(reply), 0);
#else
write (client_sock, &reply, sizeof(reply));
#endif
}
break;
case 'H': /* Ask about recently heard stations. */
{
#if 0
struct {
struct agwpe_s hdr;
char info[100];
} reply;
memset (&reply.hdr, 0, sizeof(reply.hdr));
reply.hdr.kind_lo = 'H';
// TODO: Implement properly.
reply.hdr.portx = cmd.hdr.portx
strcpy (reply.hdr.call_from, "WB2OSZ-15");
strcpy (agwpe_msg.data, ...);
reply.hdr.data_len = strlen(reply.info);
if (debug_client) {
debug_print (TO_CLIENT, &reply.hdr, sizeof(reply.hdr) + reply.hdr.data_len);
}
#if __WIN32__
send (client_sock, &reply, sizeof(reply.hdr) + reply.hdr.data_len, 0);
#else
write (client_sock, &reply, sizeof(reply.hdr) + reply.hdr.data_len);
#endif
#endif
}
break;
case 'k': /* Ask to start receiving RAW AX25 frames */
// Actually it is a toggle so we must be sure to clear it for a new connection.
enable_send_raw_to_client = ! enable_send_raw_to_client;
break;
case 'm': /* Ask to start receiving Monitor frames */
// Actually it is a toggle so we must be sure to clear it for a new connection.
enable_send_monitor_to_client = ! enable_send_monitor_to_client;
break;
case 'V': /* Transmit UI data frame */
{
// Data format is:
// 1 byte for number of digipeaters.
// 10 bytes for each digipeater.
// data part of message.
char stemp[512];
char *p;
int ndigi;
int k;
packet_t pp;
//unsigned char fbuf[AX25_MAX_PACKET_LEN+2];
//int flen;
strcpy (stemp, cmd.hdr.call_from);
strcat (stemp, ">");
strcat (stemp, cmd.hdr.call_to);
cmd.data[cmd.hdr.data_len] = '\0';
ndigi = cmd.data[0];
p = cmd.data + 1;
for (k=0; k<ndigi; k++) {
strcat (stemp, ",");
strcat (stemp, p);
p += 10;
}
strcat (stemp, ":");
strcat (stemp, p);
//text_color_set(DW_COLOR_DEBUG);
//dw_printf ("Transmit '%s'\n", stemp);
pp = ax25_from_text (stemp, 1);
if (pp == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Failed to create frame from AGW 'V' message.\n");
}
else {
/* This goes into the low priority queue because it is an original. */
/* Note that the protocol has no way to set the "has been used" */
/* bits in the digipeater fields. */
/* This explains why the digipeating option is grayed out in */
/* xastir when using the AGW interface. */
/* The current version uses only the 'V' message, not 'K' for transmitting. */
tq_append (cmd.hdr.portx, TQ_PRIO_1_LO, pp);
}
}
break;
case 'K': /* Transmit raw AX.25 frame */
{
// Message contains:
// port number for transmission.
// data length
// data which is raw ax.25 frame.
//
packet_t pp;
pp = ax25_from_frame ((unsigned char *)cmd.data+1, cmd.hdr.data_len, -1);
if (pp == NULL) {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Failed to create frame from AGW 'K' message.\n");
}
else {
/* How can we determine if it is an original or repeated message? */
/* If there is at least one digipeater in the frame, AND */
/* that digipeater has been used, it should go out quickly thru */
/* the high priority queue. */
/* Otherwise, it is an original for the low priority queue. */
if (ax25_get_num_repeaters(pp) >= 1 &&
ax25_get_h(pp,AX25_REPEATER_1)) {
tq_append (cmd.hdr.portx, TQ_PRIO_0_HI, pp);
}
else {
tq_append (cmd.hdr.portx, TQ_PRIO_1_LO, pp);
}
}
}
break;
case 'X': /* Register CallSign */
/* Send success status. */
{
struct {
struct agwpe_s hdr;
char data;
} reply;
memset (&reply, 0, sizeof(reply));
reply.hdr.kind_lo = 'X';
memcpy (reply.hdr.call_from, cmd.hdr.call_from, sizeof(reply.hdr.call_from));
reply.hdr.data_len = 1;
reply.data = 1; /* success */
// Version 1.0.
// Previously used sizeof(reply) but compiler rounded it up to next byte boundary.
// That's why more cumbersome size expression is used.
if (debug_client) {
debug_print (TO_CLIENT, &reply.hdr, sizeof(reply.hdr) + sizeof(reply.data));
}
#if __WIN32__
send (client_sock, (char*)(&reply), sizeof(reply.hdr) + sizeof(reply.data), 0);
#else
write (client_sock, &reply, sizeof(reply.hdr) + sizeof(reply.data));
#endif
}
break;
case 'x': /* Unregister CallSign */
/* No reponse is expected. */
break;
case 'C': /* Connect, Start an AX.25 Connection */
case 'v': /* Connect VIA, Start an AX.25 circuit thru digipeaters */
case 'D': /* Send Connected Data */
case 'd': /* Disconnect, Terminate an AX.25 Connection */
// Version 1.0. Better message instead of generic unexpected command.
text_color_set(DW_COLOR_ERROR);
dw_printf ("\n");
dw_printf ("Can't process command from AGW client app.\n");
dw_printf ("Connected packet mode is not implemented.\n");
break;
#if 0
case 'M': /* Send UNPROTO Information */
Not sure what we might want to do here.
AGWterminal sends this for beacon or ask QRA.
<<< Send UNPROTO Information from AGWPE client application, total length = 253
portx = 0, port_hi_reserved = 0
kind_lo = 77 = 'M', kind_hi = 0
call_from = "SV2AGW-1", call_to = "BEACON"
data_len = 217, user_reserved = 588, data =
000: 54 68 69 73 20 76 65 72 73 69 6f 6e 20 75 73 65 This version use
010: 73 20 74 68 65 20 6e 65 77 20 41 47 57 20 50 61 s the new AGW Pa
020: 63 6b 65 74 20 45 6e 67 69 6e 65 20 77 69 6e 73 cket Engine wins
<<< Send UNPROTO Information from AGWPE client application, total length = 37
portx = 0, port_hi_reserved = 0
kind_lo = 77 = 'M', kind_hi = 0
call_from = "SV2AGW-1", call_to = "QRA"
data_len = 1, user_reserved = 32218432, data =
000: 0d .
break;
#endif
default:
text_color_set(DW_COLOR_ERROR);
dw_printf ("--- Unexpected Command from application using AGW protocol:\n");
debug_print (FROM_CLIENT, &cmd.hdr, sizeof(cmd.hdr) + cmd.hdr.data_len);
break;
}
}
}
/* end server.c */