298 lines
8.8 KiB
C
298 lines
8.8 KiB
C
/* PH-socket, a lightweight ISDN physical layer interface
|
|
*
|
|
* (C) 2022 by Andreas Eversberg <jolly@eversberg.eu>
|
|
* All Rights Reserved
|
|
*
|
|
* 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/>.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <errno.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
#include <time.h>
|
|
#include "../libdebug/debug.h"
|
|
#include "ph_socket.h"
|
|
|
|
#define CHAN s->name
|
|
|
|
int ph_socket_init(ph_socket_t *s, void (*ph_socket_rx_msg)(ph_socket_t *s, int channel, uint8_t prim, uint8_t *data, int length), void *priv, const char *socket_name, int server)
|
|
{
|
|
int rc, flags;
|
|
|
|
memset(s, 0, sizeof(*s));
|
|
s->name = socket_name;
|
|
s->ph_socket_rx_msg = ph_socket_rx_msg;
|
|
s->priv = priv;
|
|
s->tx_list_tail = &s->tx_list;
|
|
|
|
memset(&s->sock_address, 0, sizeof(s->sock_address));
|
|
s->sock_address.sun_family = AF_UNIX;
|
|
strcpy(s->sock_address.sun_path+1, socket_name);
|
|
|
|
if (server) {
|
|
rc = socket(PF_UNIX, SOCK_STREAM, 0);
|
|
if (rc < 0) {
|
|
PDEBUG_CHAN(DPH, DEBUG_ERROR, "Failed to create UNIX socket.\n");
|
|
return rc;
|
|
}
|
|
s->listen = rc;
|
|
|
|
rc = bind(s->listen, (struct sockaddr *)(&s->sock_address), sizeof(s->sock_address));
|
|
if (rc < 0) {
|
|
PDEBUG_CHAN(DPH, DEBUG_ERROR, "Failed to bind UNIX socket with path '%s' (errno = %d (%s)).\n", s->name, errno, strerror(errno));
|
|
return rc;
|
|
}
|
|
|
|
rc = listen(s->listen, 1);
|
|
if (rc < 0) {
|
|
PDEBUG_CHAN(DPH, DEBUG_ERROR, "Failed to listen to UNIX socket with path '%s' (errno = %d (%s)).\n", s->name, errno, strerror(errno));
|
|
return rc;
|
|
}
|
|
|
|
/* set nonblocking io */
|
|
flags = fcntl(s->listen, F_GETFL);
|
|
flags |= O_NONBLOCK;
|
|
fcntl(s->listen, F_SETFL, flags);
|
|
}
|
|
|
|
PDEBUG_CHAN(DPH, DEBUG_INFO, "Created PH-socket at '%s'.\n", s->name);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void close_connection(ph_socket_t *s)
|
|
{
|
|
struct socket_msg_list *ml;
|
|
uint8_t disable = PH_CTRL_DISABLE;
|
|
|
|
if (s->connect <= 0)
|
|
return;
|
|
|
|
PDEBUG_CHAN(DPH, DEBUG_INFO, "Connection from PH-socket closed.\n");
|
|
|
|
/* indicate loss of socket connection */
|
|
s->ph_socket_rx_msg(s, 0, (s->listen > 0) ? PH_PRIM_CTRL_REQ : PH_PRIM_CTRL_IND, &disable, 1);
|
|
|
|
close(s->connect);
|
|
s->connect = 0;
|
|
|
|
while ((ml = s->tx_list)) {
|
|
s->tx_list = ml->next;
|
|
free(ml);
|
|
}
|
|
s->tx_list_tail = &s->tx_list;
|
|
if (s->rx_msg) {
|
|
free(s->rx_msg);
|
|
s->rx_msg = NULL;
|
|
}
|
|
|
|
/* set timer, so that retry is delayed */
|
|
static struct timespec tv;
|
|
clock_gettime(CLOCK_REALTIME, &tv);
|
|
double now = (double)tv.tv_sec + (double)tv.tv_nsec / 1000000000.0;
|
|
s->connect_timer = now + SOCKET_RETRY_TIMER;
|
|
}
|
|
|
|
void ph_socket_exit(ph_socket_t *s)
|
|
{
|
|
PDEBUG_CHAN(DPH, DEBUG_INFO, "Destroyed PH-socket.\n");
|
|
|
|
close_connection(s);
|
|
if (s->listen > 0) {
|
|
close(s->listen);
|
|
s->listen = 0;
|
|
}
|
|
}
|
|
|
|
int ph_socket_work(ph_socket_t *s)
|
|
{
|
|
uint8_t enable = PH_CTRL_ENABLE;
|
|
int rc, flags;
|
|
int work = 0;
|
|
|
|
if (s->listen > 0) {
|
|
struct sockaddr_un sock_address;
|
|
socklen_t sock_len = sizeof(sock_address);
|
|
/* see if there is an incoming connection */
|
|
rc = accept(s->listen, (struct sockaddr *)&sock_address, &sock_len);
|
|
if (rc > 0) {
|
|
if (s->connect > 0) {
|
|
PDEBUG_CHAN(DPH, DEBUG_ERROR, "Rejecting incoming connection, because we already have a client connected!\n");
|
|
close(rc);
|
|
} else {
|
|
PDEBUG_CHAN(DPH, DEBUG_INFO, "Connection from PH-socket client.\n");
|
|
s->connect = rc;
|
|
/* set nonblocking io */
|
|
flags = fcntl(s->connect, F_GETFL);
|
|
flags |= O_NONBLOCK;
|
|
fcntl(s->connect, F_SETFL, flags);
|
|
/* reset rx buffer */
|
|
s->rx_header_index = 0;
|
|
s->rx_data_index = 0;
|
|
/* indicate established socket connection */
|
|
s->ph_socket_rx_msg(s, 0, PH_PRIM_CTRL_REQ, &enable, 1);
|
|
}
|
|
work = 1;
|
|
}
|
|
} else {
|
|
/* open connection, if closed */
|
|
if (s->connect <= 0) {
|
|
static struct timespec tv;
|
|
clock_gettime(CLOCK_REALTIME, &tv);
|
|
double now = (double)tv.tv_sec + (double)tv.tv_nsec / 1000000000.0;
|
|
if (s->connect_timer < now) {
|
|
PDEBUG_CHAN(DPH, DEBUG_DEBUG, "Trying to connect to PH-socket server.\n");
|
|
rc = socket(PF_UNIX, SOCK_STREAM, 0);
|
|
if (rc < 0) {
|
|
PDEBUG_CHAN(DPH, DEBUG_ERROR, "Failed to create UNIX socket.\n");
|
|
s->connect_timer = now + SOCKET_RETRY_TIMER;
|
|
return 0;
|
|
}
|
|
s->connect = rc;
|
|
/* connect */
|
|
rc = connect(s->connect, (struct sockaddr *)&s->sock_address, sizeof(s->sock_address));
|
|
if (rc < 0 && errno != EAGAIN) {
|
|
if (!s->connect_failed)
|
|
PDEBUG_CHAN(DPH, DEBUG_NOTICE, "Failed to connect UNIX socket, retrying...\n");
|
|
close(s->connect);
|
|
s->connect_failed = 1;
|
|
s->connect = 0;
|
|
s->connect_timer = now + SOCKET_RETRY_TIMER;
|
|
return 0;
|
|
}
|
|
s->connect_failed = 0;
|
|
PDEBUG_CHAN(DPH, DEBUG_INFO, "Connection to PH-socket server.\n");
|
|
/* set nonblocking io */
|
|
flags = fcntl(s->connect, F_GETFL);
|
|
flags |= O_NONBLOCK;
|
|
fcntl(s->connect, F_SETFL, flags);
|
|
/* reset rx buffer */
|
|
s->rx_header_index = 0;
|
|
s->rx_data_index = 0;
|
|
/* indicate established socket connection */
|
|
s->ph_socket_rx_msg(s, 0, PH_PRIM_CTRL_IND, &enable, 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* handle connection, if any */
|
|
if (s->connect > 0) {
|
|
if (!s->rx_msg)
|
|
s->rx_msg = calloc(1, sizeof(*s->rx_msg));
|
|
rx_again:
|
|
if (s->rx_header_index < (int)sizeof(s->rx_msg->msg.header)) {
|
|
/* read header until complete */
|
|
rc = recv(s->connect, ((uint8_t *)&s->rx_msg->msg.header) + s->rx_header_index, sizeof(s->rx_msg->msg.header) - s->rx_header_index, 0);
|
|
if (rc > 0) {
|
|
s->rx_header_index += rc;
|
|
work = 1;
|
|
goto rx_again;
|
|
} else if (rc == 0 || errno != EAGAIN) {
|
|
close_connection(s);
|
|
work = 1;
|
|
}
|
|
} else if (s->rx_data_index < s->rx_msg->msg.header.length) {
|
|
/* read data until complete */
|
|
rc = recv(s->connect, s->rx_msg->msg.data + s->rx_data_index, s->rx_msg->msg.header.length - s->rx_data_index, 0);
|
|
if (rc > 0) {
|
|
s->rx_data_index += rc;
|
|
work = 1;
|
|
goto rx_again;
|
|
} else if (rc == 0 || errno != EAGAIN) {
|
|
close_connection(s);
|
|
work = 1;
|
|
}
|
|
} else {
|
|
/* process and free message */
|
|
if (s->rx_msg->msg.header.prim != PH_PRIM_DATA_REQ
|
|
&& s->rx_msg->msg.header.prim != PH_PRIM_DATA_IND) {
|
|
PDEBUG_CHAN(DPH, DEBUG_DEBUG, "message 0x%02x channel %d from socket\n", s->rx_msg->msg.header.prim, s->rx_msg->msg.header.channel);
|
|
if (s->rx_msg->msg.header.length)
|
|
PDEBUG_CHAN(DPH, DEBUG_DEBUG, " -> data:%s\n", debug_hex(s->rx_msg->msg.data, s->rx_msg->msg.header.length));
|
|
}
|
|
s->ph_socket_rx_msg(s, s->rx_msg->msg.header.channel, s->rx_msg->msg.header.prim, s->rx_msg->msg.data, s->rx_msg->msg.header.length);
|
|
free(s->rx_msg);
|
|
s->rx_msg = NULL;
|
|
/* reset rx buffer */
|
|
s->rx_header_index = 0;
|
|
s->rx_data_index = 0;
|
|
}
|
|
|
|
tx_again:
|
|
if (s->tx_list) {
|
|
/* some frame in tx list, so try sending it */
|
|
rc = send(s->connect, ((uint8_t *)&s->tx_list->msg.header), sizeof(s->tx_list->msg.header) + s->tx_list->msg.header.length, 0);
|
|
if (rc > 0) {
|
|
struct socket_msg_list *ml;
|
|
if (rc != (int)sizeof(s->tx_list->msg.header) + s->tx_list->msg.header.length) {
|
|
PDEBUG_CHAN(DPH, DEBUG_ERROR, "Short write, please fix handling!\n");
|
|
}
|
|
/* remove list entry */
|
|
ml = s->tx_list;
|
|
s->tx_list = ml->next;
|
|
if (s->tx_list == NULL)
|
|
s->tx_list_tail = &s->tx_list;
|
|
free(ml);
|
|
work = 1;
|
|
goto tx_again;
|
|
} else if (rc == 0 || errno != EAGAIN) {
|
|
close_connection(s);
|
|
work = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return work;
|
|
}
|
|
|
|
void ph_socket_tx_msg(ph_socket_t *s, int channel, uint8_t prim, uint8_t *data, int length)
|
|
{
|
|
struct socket_msg_list *tx_msg;
|
|
|
|
if (prim != PH_PRIM_DATA_REQ
|
|
&& prim != PH_PRIM_DATA_IND) {
|
|
PDEBUG_CHAN(DPH, DEBUG_DEBUG, "message 0x%02x channel %d to socket\n", prim, channel);
|
|
if (length)
|
|
PDEBUG_CHAN(DPH, DEBUG_DEBUG, " -> data:%s\n", debug_hex(data, length));
|
|
}
|
|
|
|
if (length > (int)sizeof(tx_msg->msg.data)) {
|
|
PDEBUG_CHAN(DPH, DEBUG_NOTICE, "Frame from HDLC process too large for socket, dropping!\n");
|
|
return;
|
|
}
|
|
|
|
if (s->connect <= 0) {
|
|
PDEBUG_CHAN(DPH, DEBUG_NOTICE, "Dropping message for socket, socket is closed!\n");
|
|
return;
|
|
}
|
|
|
|
tx_msg = calloc(1, sizeof(*tx_msg));
|
|
tx_msg->msg.header.channel = channel;
|
|
tx_msg->msg.header.prim = prim;
|
|
if (length) {
|
|
tx_msg->msg.header.length = length;
|
|
memcpy(tx_msg->msg.data, data, length);
|
|
}
|
|
/* move message to list */
|
|
*s->tx_list_tail = tx_msg;
|
|
s->tx_list_tail = &tx_msg->next;
|
|
}
|
|
|