mirror of https://github.com/wb2osz/direwolf.git
1250 lines
34 KiB
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 */
|