Add libph_socket (not used by any app now)
parent
9f662d309f
commit
746c1fb4ff
|
@ -33,6 +33,7 @@ src/libhagelbarger/libhagelbarger.a
|
|||
src/libdtmf/libdtmf.a
|
||||
src/libtimer/libtimer.a
|
||||
src/libselect/libselect.a
|
||||
src/libph_socket/libph_socket.a
|
||||
src/libsamplerate/libsamplerate.a
|
||||
src/libscrambler/libscrambler.a
|
||||
src/libemphasis/libemphasis.a
|
||||
|
|
|
@ -75,6 +75,7 @@ AC_OUTPUT(
|
|||
src/libdtmf/Makefile
|
||||
src/libtimer/Makefile
|
||||
src/libselect/Makefile
|
||||
src/libph_socket/Makefile
|
||||
src/libsamplerate/Makefile
|
||||
src/libscrambler/Makefile
|
||||
src/libemphasis/Makefile
|
||||
|
|
|
@ -16,6 +16,7 @@ SUBDIRS = \
|
|||
libdtmf \
|
||||
libtimer \
|
||||
libselect \
|
||||
libph_socket \
|
||||
libsamplerate \
|
||||
libscrambler \
|
||||
libemphasis \
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
AM_CPPFLAGS = -Wall -Wextra -g $(all_includes)
|
||||
|
||||
noinst_LIBRARIES = libph_socket.a
|
||||
|
||||
libph_socket_a_SOURCES = \
|
||||
ph_socket.c
|
||||
|
|
@ -0,0 +1,339 @@
|
|||
/* 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 "../libtimer/timer.h"
|
||||
#include "../libselect/select.h"
|
||||
#include "../libdebug/debug.h"
|
||||
#include "ph_socket.h"
|
||||
|
||||
static int ph_socket_listen_cb(struct osmo_fd *ofd, unsigned int __attribute__((unused)) what);
|
||||
static int ph_socket_connect_cb(struct osmo_fd *ofd, unsigned int __attribute__((unused)) what);
|
||||
static void ph_socket_timeout_cb(void *data);
|
||||
|
||||
static void open_connection(ph_socket_t *s)
|
||||
{
|
||||
uint8_t enable = PH_CTRL_UNBLOCK;
|
||||
int rc, flags;
|
||||
|
||||
if (s->connect_ofd.fd > 0)
|
||||
return;
|
||||
|
||||
LOGP(DPH, LOGL_DEBUG, "Trying to connect to PH-socket server.\n");
|
||||
rc = socket(PF_UNIX, SOCK_STREAM, 0);
|
||||
if (rc < 0) {
|
||||
LOGP(DPH, LOGL_ERROR, "Failed to create UNIX socket.\n");
|
||||
osmo_timer_schedule(&s->retry_timer, SOCKET_RETRY_TIMER, 0);
|
||||
return;
|
||||
}
|
||||
s->connect_ofd.fd = rc;
|
||||
s->connect_ofd.data = s;
|
||||
s->connect_ofd.when = BSC_FD_READ;
|
||||
s->connect_ofd.cb = ph_socket_connect_cb;
|
||||
osmo_fd_register(&s->connect_ofd);
|
||||
/* set nonblocking io, because we do multiple reads when handling read event */
|
||||
flags = fcntl(s->connect_ofd.fd, F_GETFL);
|
||||
flags |= O_NONBLOCK;
|
||||
fcntl(s->connect_ofd.fd, F_SETFL, flags);
|
||||
/* connect */
|
||||
rc = connect(s->connect_ofd.fd, (struct sockaddr *)&s->sock_address, sizeof(s->sock_address));
|
||||
if (rc < 0 && errno != EAGAIN) {
|
||||
if (!s->connect_failed)
|
||||
LOGP(DPH, LOGL_NOTICE, "Failed to connect UNIX socket, retrying...\n");
|
||||
close(s->connect_ofd.fd);
|
||||
s->connect_failed = 1;
|
||||
osmo_fd_unregister(&s->connect_ofd);
|
||||
s->connect_ofd.fd = 0;
|
||||
osmo_timer_schedule(&s->retry_timer, SOCKET_RETRY_TIMER, 0);
|
||||
return;
|
||||
}
|
||||
s->connect_failed = 0;
|
||||
LOGP(DPH, LOGL_INFO, "Connection to PH-socket server.\n");
|
||||
/* 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);
|
||||
}
|
||||
|
||||
static void close_connection(ph_socket_t *s)
|
||||
{
|
||||
struct socket_msg_list *ml;
|
||||
uint8_t disable = PH_CTRL_BLOCK;
|
||||
|
||||
if (s->connect_ofd.fd <= 0)
|
||||
return;
|
||||
|
||||
LOGP(DPH, LOGL_INFO, "Connection from PH-socket closed.\n");
|
||||
|
||||
/* indicate loss of socket connection */
|
||||
s->ph_socket_rx_msg(s, 0, (s->listen_ofd.fd > 0) ? PH_PRIM_CTRL_REQ : PH_PRIM_CTRL_IND, &disable, 1);
|
||||
|
||||
osmo_fd_unregister(&s->connect_ofd);
|
||||
close(s->connect_ofd.fd);
|
||||
s->connect_ofd.fd = 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;
|
||||
}
|
||||
|
||||
if (s->listen_ofd.fd <= 0) {
|
||||
/* set timer, so that retry is delayed */
|
||||
osmo_timer_schedule(&s->retry_timer, SOCKET_RETRY_TIMER, 0);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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);
|
||||
|
||||
s->retry_timer.data = s;
|
||||
s->retry_timer.cb = ph_socket_timeout_cb;
|
||||
|
||||
if (server) {
|
||||
rc = socket(PF_UNIX, SOCK_STREAM, 0);
|
||||
if (rc < 0) {
|
||||
LOGP(DPH, LOGL_ERROR, "Failed to create UNIX socket.\n");
|
||||
return rc;
|
||||
}
|
||||
s->listen_ofd.fd = rc;
|
||||
s->listen_ofd.data = s;
|
||||
s->listen_ofd.when = BSC_FD_READ;
|
||||
s->listen_ofd.cb = ph_socket_listen_cb;
|
||||
osmo_fd_register(&s->listen_ofd);
|
||||
|
||||
rc = bind(s->listen_ofd.fd, (struct sockaddr *)(&s->sock_address), sizeof(s->sock_address));
|
||||
if (rc < 0) {
|
||||
LOGP(DPH, LOGL_ERROR, "Failed to bind UNIX socket with path '%s' (errno = %d (%s)).\n",
|
||||
s->name, errno, strerror(errno));
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = listen(s->listen_ofd.fd, 1);
|
||||
if (rc < 0) {
|
||||
LOGP(DPH, LOGL_ERROR, "Failed to listen to UNIX socket with path '%s' (errno = %d (%s)).\n",
|
||||
s->name, errno, strerror(errno));
|
||||
return rc;
|
||||
}
|
||||
} else
|
||||
open_connection(s);
|
||||
|
||||
LOGP(DPH, LOGL_INFO, "Created PH-socket at '%s'.\n", s->name);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ph_socket_exit(ph_socket_t *s)
|
||||
{
|
||||
LOGP(DPH, LOGL_INFO, "Destroyed PH-socket.\n");
|
||||
|
||||
close_connection(s);
|
||||
if (s->listen_ofd.fd > 0) {
|
||||
osmo_fd_unregister(&s->listen_ofd);
|
||||
close(s->listen_ofd.fd);
|
||||
s->listen_ofd.fd = 0;
|
||||
}
|
||||
|
||||
if (osmo_timer_pending(&s->retry_timer))
|
||||
osmo_timer_del(&s->retry_timer);
|
||||
}
|
||||
|
||||
static int ph_socket_listen_cb(struct osmo_fd *ofd, unsigned int __attribute__((unused)) what)
|
||||
{
|
||||
ph_socket_t *s = ofd->data;
|
||||
struct sockaddr_un sock_address;
|
||||
uint8_t enable = PH_CTRL_UNBLOCK;
|
||||
int rc, flags;
|
||||
|
||||
socklen_t sock_len = sizeof(sock_address);
|
||||
/* see if there is an incoming connection */
|
||||
rc = accept(s->listen_ofd.fd, (struct sockaddr *)&sock_address, &sock_len);
|
||||
if (rc > 0) {
|
||||
if (s->connect_ofd.fd > 0) {
|
||||
LOGP(DPH, LOGL_ERROR, "Rejecting incoming connection, because we already have a client "
|
||||
"connected!\n");
|
||||
close(rc);
|
||||
} else {
|
||||
LOGP(DPH, LOGL_INFO, "Connection from PH-socket client.\n");
|
||||
s->connect_ofd.fd = rc;
|
||||
s->connect_ofd.data = s;
|
||||
s->connect_ofd.when = BSC_FD_READ;
|
||||
s->connect_ofd.cb = ph_socket_connect_cb;
|
||||
osmo_fd_register(&s->connect_ofd);
|
||||
/* set nonblocking io, because we do multiple reads when handling read event */
|
||||
flags = fcntl(s->connect_ofd.fd, F_GETFL);
|
||||
flags |= O_NONBLOCK;
|
||||
fcntl(s->connect_ofd.fd, 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);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ph_socket_connect_cb(struct osmo_fd *ofd, unsigned __attribute__((unused)) int what)
|
||||
{
|
||||
ph_socket_t *s = ofd->data;
|
||||
int rc;
|
||||
|
||||
if (what & BSC_FD_READ) {
|
||||
rx_again:
|
||||
if (!s->rx_msg)
|
||||
s->rx_msg = calloc(1, sizeof(*s->rx_msg));
|
||||
if (s->rx_header_index < (int)sizeof(s->rx_msg->msg.header)) {
|
||||
/* read header until complete */
|
||||
rc = recv(s->connect_ofd.fd, ((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;
|
||||
goto rx_again;
|
||||
} else if (rc == 0 || errno != EAGAIN) {
|
||||
close_connection(s);
|
||||
return 0;
|
||||
}
|
||||
} else if (s->rx_data_index < s->rx_msg->msg.header.length) {
|
||||
/* read data until complete */
|
||||
rc = recv(s->connect_ofd.fd, 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;
|
||||
goto rx_again;
|
||||
} else if (rc == 0 || errno != EAGAIN) {
|
||||
close_connection(s);
|
||||
return 0;
|
||||
}
|
||||
} 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
|
||||
&& s->rx_msg->msg.header.prim != PH_PRIM_DATA_CNF) {
|
||||
LOGP(DPH, LOGL_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)
|
||||
LOGP(DPH, LOGL_DEBUG, " -> data:%s\n", osmo_hexdump(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;
|
||||
}
|
||||
}
|
||||
|
||||
if (what & BSC_FD_WRITE) {
|
||||
if (s->tx_list) {
|
||||
/* some frame in tx list, so try sending it */
|
||||
rc = send(s->connect_ofd.fd, ((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) {
|
||||
LOGP(DPH, LOGL_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);
|
||||
} else if (rc == 0 || errno != EAGAIN) {
|
||||
close_connection(s);
|
||||
return 0;
|
||||
}
|
||||
} else
|
||||
s->connect_ofd.when &= ~BSC_FD_WRITE;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ph_socket_timeout_cb(void *data)
|
||||
{
|
||||
ph_socket_t *s = data;
|
||||
|
||||
open_connection(s);
|
||||
}
|
||||
|
||||
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
|
||||
&& prim != PH_PRIM_DATA_CNF) {
|
||||
LOGP(DPH, LOGL_DEBUG, "message 0x%02x channel %d to socket\n", prim, channel);
|
||||
if (length)
|
||||
LOGP(DPH, LOGL_DEBUG, " -> data:%s\n", osmo_hexdump(data, length));
|
||||
}
|
||||
|
||||
if (length > (int)sizeof(tx_msg->msg.data)) {
|
||||
LOGP(DPH, LOGL_NOTICE, "Frame from HDLC process too large for socket, dropping!\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (s->connect_ofd.fd <= 0) {
|
||||
LOGP(DPH, LOGL_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;
|
||||
s->connect_ofd.when |= BSC_FD_WRITE;
|
||||
}
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
|
||||
#include <sys/un.h>
|
||||
|
||||
/*
|
||||
* Procedure:
|
||||
*
|
||||
* If socket connection is establised, a PH_PRIM_CTRL_REQ message with
|
||||
* PH_CTRL_ENABLE information is received by the socket server user.
|
||||
* If the socket connection is lost, a PH_PRIM_CTRL_REQ message with
|
||||
* PH_CTRL_DISABLE information is received by the user.
|
||||
*
|
||||
* If socket connection is establised, a PH_PRIM_CTRL_IND message with
|
||||
* PH_CTRL_ENABLE information is received by the socket client user.
|
||||
* If the socket connection is lost, a PH_PRIM_CTRL_IND message with
|
||||
* PH_CTRL_DISABLE information is received by the user.
|
||||
*
|
||||
* The socket server should enable or disable interface depending on
|
||||
* the PH_CTRL_ENABLE / PH_CTRL_DISABLE information.
|
||||
*
|
||||
* The socket client user shall keep track of last PH_PRIM_ACT_IND /
|
||||
* PH_PRIM_DEACT_IND message and treat a PH_PRIM_CTRL_IND message with
|
||||
* PH_CTRL_DISABLE information as a deactivation of all channels that
|
||||
* were activated. Also it shall reject every PH_RIM_ACT_REQ with a
|
||||
* PH_PRIM_DACT_IND, if the socket is currently unavailable.
|
||||
*
|
||||
* PH_PRIM_CTRL_REQ and PH_PRIM_CTRL_IND messages with PH_CTRL_ENABLE
|
||||
* and PH_CTRL_DISABLE informations are not assoicated with a channel
|
||||
* number. The socket sender shall set it to 0, the receiver shall
|
||||
* ignore it.
|
||||
*
|
||||
* A missing MODE in PH_PRIM_ACT_REQ is interepreted as default:
|
||||
* HDLC on D-channel, TRANS on B-channel.
|
||||
*
|
||||
* Each packet on the socket shall have the follwoing header:
|
||||
* uint8_t channel;
|
||||
* uint8_t prim;
|
||||
* uint16_t length;
|
||||
*
|
||||
* The length shall be in host's endian on UN sockets and in network
|
||||
* endian on TCP sockets and not being transmitted on UDP sockets.
|
||||
*
|
||||
* 0 to 65535 bytes shall follow the header, depending on the length
|
||||
* information field.
|
||||
*/
|
||||
|
||||
/* all primitives */
|
||||
#define PH_PRIM_DATA_REQ 0x00 /* any data sent to channel from upper layer */
|
||||
#define PH_PRIM_DATA_IND 0x01 /* any data received from channel to upper layer */
|
||||
#define PH_PRIM_DATA_CNF 0x02 /* confirm data sent to channel */
|
||||
|
||||
#define PH_PRIM_CTRL_REQ 0x04 /* implementation specific requests towards interface */
|
||||
#define PH_PRIM_CTRL_IND 0x05 /* implementation specific indications from interface */
|
||||
|
||||
#define PH_PRIM_ACT_REQ 0x08 /* activation request of channel, mode is given as payload */
|
||||
#define PH_PRIM_ACT_IND 0x09 /* activation indication that the channel is now active */
|
||||
|
||||
#define PH_PRIM_DACT_REQ 0x0c /* deactivation request of channel */
|
||||
#define PH_PRIM_DACT_IND 0x0d /* deactivation indication that the channel is now inactive */
|
||||
|
||||
/* one byte sent activation request */
|
||||
#define PH_MODE_TRANS 0x00 /* raw data is sent via B-channel */
|
||||
#define PH_MODE_HDLC 0x01 /* HDLC transcoding is performed via B-channel */
|
||||
|
||||
/* one byte sent with control messages */
|
||||
#define PH_CTRL_BLOCK 0x00 /* disable (block) interface, when socket is disconnected */
|
||||
#define PH_CTRL_UNBLOCK 0x01 /* enable (unblock) interface, when socket is connected */
|
||||
#define PH_CTRL_LOOP_DISABLE 0x04 /* disable loopback */
|
||||
#define PH_CTRL_LOOP1_ENABLE 0x05 /* enable LT transceier loopback */
|
||||
#define PH_CTRL_LOOP2_ENABLE 0x06 /* enable NT transceier loopback */
|
||||
#define PH_CTRL_LOOP_ERROR 0x10 /* frame error report (loopback test) */
|
||||
#define PH_CTRL_VIOLATION_LT 0x11 /* code violation received by LT */
|
||||
#define PH_CTRL_VIOLATION_NT 0x12 /* code violation received by NT */
|
||||
|
||||
struct socket_msg {
|
||||
struct {
|
||||
uint8_t channel;
|
||||
uint8_t prim;
|
||||
uint16_t length;
|
||||
} header;
|
||||
uint8_t data[65536];
|
||||
} __attribute__((packed));
|
||||
|
||||
struct socket_msg_list {
|
||||
struct socket_msg_list *next;
|
||||
struct socket_msg msg;
|
||||
};
|
||||
|
||||
#define SOCKET_RETRY_TIMER 2
|
||||
|
||||
typedef struct ph_socket {
|
||||
const char *name;
|
||||
void (*ph_socket_rx_msg)(struct ph_socket *s, int channel, uint8_t prim, uint8_t *data, int length);
|
||||
void *priv;
|
||||
struct sockaddr_un sock_address;
|
||||
struct osmo_fd listen_ofd; /* socket to listen to incoming connections */
|
||||
struct osmo_fd connect_ofd; /* socket of incoming connection */
|
||||
int connect_failed; /* used to print a failure only once */
|
||||
struct osmo_timer_list retry_timer; /* timer to connect again */
|
||||
struct socket_msg_list *tx_list, **tx_list_tail;
|
||||
struct socket_msg_list *rx_msg;
|
||||
int rx_header_index;
|
||||
int rx_data_index;
|
||||
} ph_socket_t;
|
||||
|
||||
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);
|
||||
void ph_socket_exit(ph_socket_t *s);
|
||||
void ph_socket_tx_msg(ph_socket_t *s, int channel, uint8_t prim, uint8_t *data, int length);
|
Loading…
Reference in New Issue