# HG changeset patch # User Mychaela Falconia # Date 1717894718 0 # Node ID e7b192a5dee515c6fae765caee1cf613523bd1a3 # Parent ce450869db093692fad82e0f06cb4ef5e5d8e8be mtctest: initial import from old ThemWi diff -r ce450869db09 -r e7b192a5dee5 mtctest/disconnect.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mtctest/disconnect.c Sun Jun 09 00:58:38 2024 +0000 @@ -0,0 +1,33 @@ +/* + * In this module we implement the sending of MNCC_DISC_REQ, + * signaling a caller-requested disconnect aka hang-up. + * It is the graceful way of signaling disconnect, + * as opposed to simply killing the test call process with ^C + * and letting themwi-mncc handle the broken socket. + */ + +#include +#include +#include +#include +#include +#include +#include +#include "../include/mncc.h" +#include "../include/gsm48_const.h" + +int disconnect_mode; + +send_disconnect_req() +{ + struct gsm_mncc msg; + + printf("Sending disconnect request\n"); + bzero(&msg, sizeof(struct gsm_mncc)); + msg.msg_type = MNCC_DISC_REQ; + msg.callref = 1; + mncc_set_cause(&msg, GSM48_CAUSE_LOC_USER, + GSM48_CC_CAUSE_NORM_CALL_CLEAR); + send_mncc_to_gsm(&msg, sizeof(struct gsm_mncc)); + disconnect_mode = 1; +} diff -r ce450869db09 -r e7b192a5dee5 mtctest/main.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mtctest/main.c Sun Jun 09 00:58:38 2024 +0000 @@ -0,0 +1,78 @@ +/* + * Main module for ThemWi test MT call generator. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +extern int mtc_socket; +extern int disconnect_mode; + +struct timeval cur_event_time; + +static void +drain_stdin() +{ + char buf[256]; + + read(0, buf, sizeof buf); +} + +main(argc, argv) + char **argv; +{ + extern int optind; + extern char *optarg; + char *from; + fd_set fds; + int c; + + from = 0; + while ((c = getopt(argc, argv, "f:")) != EOF) { + switch (c) { + case 'f': + from = optarg; + continue; + default: + usage: + fprintf(stderr, + "usage: %s [-f from-number] to-number\n", + argv[0]); + exit(1); + } + } + if (argc != optind + 1) + goto usage; + openlog("themwi-test-mtc", 0, LOG_LOCAL5); + init_setup_msg(from, argv[optind]); + obtain_dummy_rtp(); + connect_mtc_socket(); + send_setup_msg(); + /* main select loop */ + for (;;) { + FD_ZERO(&fds); + FD_SET(mtc_socket, &fds); + if (!disconnect_mode) + FD_SET(0, &fds); + c = select(mtc_socket+1, &fds, 0, 0, 0); + if (c < 0) { + if (errno == EINTR) + continue; + perror("select"); + exit(1); + } + gettimeofday(&cur_event_time, 0); + if (FD_ISSET(mtc_socket, &fds)) + mtc_socket_select(); + if (!disconnect_mode && FD_ISSET(0, &fds)) { + drain_stdin(); + send_disconnect_req(); + } + } +} diff -r ce450869db09 -r e7b192a5dee5 mtctest/setup.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mtctest/setup.c Sun Jun 09 00:58:38 2024 +0000 @@ -0,0 +1,243 @@ +/* + * In this module we compose the MNCC_SETUP_REQ message + * initiating our test MT call. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "../include/mncc.h" +#include "../include/gsm48_const.h" +#include "../include/number_db_v2.h" +#include "../libnumdb2/lookup_func.h" + +struct gsm_mncc setup_msg; + +static void +set_called_number(arg) + char *arg; +{ + int rc, ndig; + char short_num[5], long_num[12]; + struct short_number_rec *snum; + + if (!strncmp(arg, "imsi:", 5)) { + rc = grok_imsi_user_arg(arg, setup_msg.imsi); + if (rc < 0) { + fprintf(stderr, + "error: call-by-IMSI destination \"%s\" is invalid\n", + arg); + exit(1); + } + return; + } + if (arg[0] == '+') { + if (arg[1] != '1') { + fprintf(stderr, + "error: plus-format call destination number must begin with 1\n"); + exit(1); + } + if (grok_number_string(arg+1, 1) != 11) { +bad_plus1: fprintf(stderr, + "error: malformed +1 call destination number\n"); + exit(1); + } + dehyphen_number_string(arg+1, setup_msg.called.number); + if (!is_nanp_valid_prefix(setup_msg.called.number+1)) + goto bad_plus1; + setup_msg.called.type = GSM48_TON_INTERNATIONAL; + setup_msg.called.plan = GSM48_NPI_ISDN_E164; + setup_msg.fields |= MNCC_F_CALLED; + return; + } + ndig = grok_number_string(arg, 1); + switch (ndig) { + case 4: + dehyphen_number_string(arg, short_num); + if (read_number_db() < 0) { + fprintf(stderr, "error reading number database\n"); + exit(1); + } + snum = numdb_lookup_short(short_num); + if (!snum) { + fprintf(stderr, + "error: short dial number %s is not valid\n", + short_num); + exit(1); + } + switch (snum->short_num_type) { + case SHORT_NUM_TYPE_ABBREV: + setup_msg.called.type = GSM48_TON_INTERNATIONAL; + setup_msg.called.plan = GSM48_NPI_ISDN_E164; + sprintf(setup_msg.called.number, "1%03u%03u%04u", + snum->fullnum_prefix[0], + snum->fullnum_prefix[1], snum->short_num); + break; + case SHORT_NUM_TYPE_ITN: + setup_msg.called.type = GSM48_TON_NET_SPEC; + setup_msg.called.plan = GSM48_NPI_PRIVATE; + strcpy(setup_msg.called.number, short_num); + break; + default: + fprintf(stderr, + "error: short dial number %s is not abbrev or ITN\n", + short_num); + exit(1); + } + setup_msg.fields |= MNCC_F_CALLED; + return; + case 10: + dehyphen_number_string(arg, long_num); + if (!is_nanp_valid_prefix(long_num)) + break; + setup_msg.called.type = GSM48_TON_INTERNATIONAL; + setup_msg.called.plan = GSM48_NPI_ISDN_E164; + setup_msg.called.number[0] = '1'; + strcpy(setup_msg.called.number+1, long_num); + setup_msg.fields |= MNCC_F_CALLED; + return; + case 11: + dehyphen_number_string(arg, long_num); + if (long_num[0] != '1') + break; + if (!is_nanp_valid_prefix(long_num+1)) + break; + setup_msg.called.type = GSM48_TON_INTERNATIONAL; + setup_msg.called.plan = GSM48_NPI_ISDN_E164; + strcpy(setup_msg.called.number, long_num); + setup_msg.fields |= MNCC_F_CALLED; + return; + } + fprintf(stderr, "error: call destination number \"%s\" is invalid\n", + arg); + exit(1); +} + +static void +set_calling_number(arg) + char *arg; +{ + unsigned ndig; + int c; + + if (!strcmp(arg, "unavail")) { + setup_msg.calling.present = GSM48_PRES_UNAVAIL; + return; + } + if (!strcmp(arg, "blocked")) { + setup_msg.calling.present = GSM48_PRES_RESTR; + return; + } + if (*arg == '+') { + setup_msg.calling.type = GSM48_TON_INTERNATIONAL; + arg++; + } + for (ndig = 0; *arg; ) { + c = *arg++; + if (c == ',') + break; + if (c == '-') + continue; + if (!is_valid_ext_digit(c)) { + fprintf(stderr, + "error: calling number argument contains invalid digit \'%c\'\n", + c); + exit(1); + } + if (ndig >= 32) { + fprintf(stderr, + "error: calling number argument is too long\n"); + exit(1); + } + setup_msg.calling.number[ndig] = c; + ndig++; + } + if (!ndig) { + fprintf(stderr, + "error: calling number argument has no digits\n"); + exit(1); + } + setup_msg.calling.plan = GSM48_NPI_ISDN_E164; + for (;;) { + while (*arg == ',') + arg++; + if (!*arg) + return; + if (!strncmp(arg, "ton=", 4)) { + arg += 4; + if (arg[0] >= '0' && arg[0] <= '7' && !isdigit(arg[1])){ + setup_msg.calling.type = *arg - '0'; + arg++; + } else { + fprintf(stderr, + "error: calling number argument contains invalid ton= part\n"); + exit(1); + } + } else if (!strncmp(arg, "npi=", 4)) { + arg += 4; + if (arg[0] >= '0' && arg[0] <= '9' && !isdigit(arg[1])){ + setup_msg.calling.plan = *arg - '0'; + arg++; + } else if (arg[0] == '1' && arg[1] >= '0' && + arg[1] <= '5' && !isdigit(arg[2])) { + setup_msg.calling.plan = atoi(arg); + arg += 2; + } else { + fprintf(stderr, + "error: calling number argument contains invalid npi= part\n"); + exit(1); + } + } else if (!strncmp(arg, "restr", 5)) { + arg += 5; + setup_msg.calling.present = GSM48_PRES_RESTR; + } else if (!strncmp(arg, "vp", 2)) { + arg += 2; + setup_msg.calling.screen = GSM48_SCRN_USER_PASS; + } else if (!strncmp(arg, "vf", 2)) { + arg += 2; + setup_msg.calling.screen = GSM48_SCRN_USER_FAIL; + } else if (!strncmp(arg, "net", 3)) { + arg += 3; + setup_msg.calling.screen = GSM48_SCRN_NETWORK; + } else { +inv_qual: fprintf(stderr, + "error: calling number argument contains invalid qualifier\n"); + exit(1); + } + if (!*arg) + return; + if (*arg != ',') + goto inv_qual; + } +} + +void +init_setup_msg(from, to) + char *from, *to; +{ + setup_msg.msg_type = MNCC_SETUP_REQ; + setup_msg.callref = 1; + set_called_number(to); + if (from) { + set_calling_number(from); + setup_msg.fields |= MNCC_F_CALLING; + } +} + +void +send_setup_msg() +{ + if (setup_msg.imsi[0]) + printf("Calling IMSI %s\n", setup_msg.imsi); + else + printf("Calling %s%s\n", + setup_msg.called.type == GSM48_TON_INTERNATIONAL ? "+" + : "", + setup_msg.called.number); + send_mncc_to_gsm(&setup_msg, sizeof(struct gsm_mncc)); +} diff -r ce450869db09 -r e7b192a5dee5 mtctest/sig_handler.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mtctest/sig_handler.c Sun Jun 09 00:58:38 2024 +0000 @@ -0,0 +1,403 @@ +/* + * In this module we handle all incoming messages from MNCC, + * printing all of them and generating protocol-required responses + * for some. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "../include/mncc.h" +#include "../include/gsm48_const.h" + +extern int disconnect_mode; +extern struct sockaddr_storage dummy_rtp_endp; +extern struct timeval cur_event_time; + +static void +print_bearer_cap(bcap) + struct gsm_mncc_bearer_cap *bcap; +{ + int i, sv; + + printf("Bearer cap: itcap=%d tmod=%d coding=%d rrq=%d\n", + bcap->transfer, bcap->mode, bcap->coding, bcap->radio); + printf(" speech: CTM=%d sv", bcap->speech_ctm); + for (i = 0; i < 8; i++) { + sv = bcap->speech_ver[i]; + if (sv < 0) + break; + printf(" %d", sv); + } + putchar('\n'); + printf(" data: ra=%d sig=%d async=%d nstop=%d ndata=%d\n", + bcap->data.rate_adaption, bcap->data.sig_access, + bcap->data.async, bcap->data.nr_stop_bits, + bcap->data.nr_data_bits); + printf(" urate=%d par=%d irate=%d transp=%d mtype=%d\n", + bcap->data.user_rate, bcap->data.parity, + bcap->data.interm_rate, bcap->data.transp, + bcap->data.modem_type); +} + +static void +print_cc_cap(cc) + struct gsm_mncc_cccap *cc; +{ + printf("CC capabilities: DTMF=%d PCP=%d\n", cc->dtmf, cc->pcp); +} + +static void +print_cause(cause) + struct gsm_mncc_cause *cause; +{ + int i; + + printf("Cause: loc=%d coding=%d value=%d", cause->location, + cause->coding, cause->value); + if (cause->rec) + printf(" rec=0x%02X", cause->rec_val); + for (i = 0; i < cause->diag_len; i++) { + if (!(i & 15)) { + putchar('\n'); + putchar(' '); + } + printf(" %02X", cause->diag[i] & 0xFF); + } + putchar('\n'); +} + +static void +print_progress(prog) + struct gsm_mncc_progress *prog; +{ + printf("Progress: loc=%d coding=%d descr=0x%02X", prog->location, + prog->coding, prog->descr); +} + +static void +print_useruser(uu) + struct gsm_mncc_useruser *uu; +{ + printf("User-User IE: proto=0x%02X\n", uu->proto); + /* dump to be implemented if and when we actually get a UU somewhere */ +} + +static void +print_keypad(kp) + int kp; +{ + if (kp >= '!' && kp <= '~') + printf("Keypad code: %c\n", kp); + else + printf("Keypad code: 0x%02X\n", kp); +} + +static void +print_facility(fac) + struct gsm_mncc_facility *fac; +{ + int i; + + printf("Facility IE: %d byte(s)", fac->len); + for (i = 0; i < fac->len; i++) { + if (!(i & 15)) { + putchar('\n'); + putchar(' '); + } + printf(" %02X", fac->info[i] & 0xFF); + } + putchar('\n'); +} + +static void +print_ssver(ssv) + struct gsm_mncc_ssversion *ssv; +{ + int i; + + printf("SS version IE: %d byte(s)", ssv->len); + for (i = 0; i < ssv->len; i++) { + if (!(i & 15)) { + putchar('\n'); + putchar(' '); + } + printf(" %02X", ssv->info[i] & 0xFF); + } + putchar('\n'); +} + +static void +print_fields(msg) + struct gsm_mncc *msg; +{ + if (msg->fields & MNCC_F_BEARER_CAP) + print_bearer_cap(&msg->bearer_cap); + if (msg->fields & MNCC_F_CCCAP) + print_cc_cap(&msg->cccap); + if (msg->fields & MNCC_F_CAUSE) + print_cause(&msg->cause); + if (msg->fields & MNCC_F_PROGRESS) + print_progress(&msg->progress); + if (msg->fields & MNCC_F_USERUSER) + print_useruser(&msg->useruser); + if (msg->more) + printf("More data flag set\n"); + if (msg->fields & MNCC_F_KEYPAD) + print_keypad(msg->keypad); + if (msg->fields & MNCC_F_FACILITY) + print_facility(&msg->facility); + if (msg->fields & MNCC_F_SSVERSION) + print_ssver(&msg->ssversion); +} + +static void +print_sdp(sdp) + char *sdp; +{ + char *cp, *ep, *np; + + for (cp = sdp; *cp; cp = np) { + ep = index(cp, '\n'); + if (ep) { + *ep = '\0'; + np = ep + 1; + } else + np = 0; + ep = index(cp, '\r'); + if (ep) + *ep = '\0'; + printf("%s %s\n", cp == sdp ? "SDP:" : " ", cp); + } +} + +static void +send_connect_ack() +{ + struct gsm_mncc ack; + + printf("Sending connect ack\n"); + bzero(&ack, sizeof(struct gsm_mncc)); + ack.msg_type = MNCC_SETUP_COMPL_REQ; + ack.callref = 1; + send_mncc_to_gsm(&ack, sizeof(struct gsm_mncc)); +} + +static void +send_rtp_connect() +{ + struct gsm_mncc_rtp rtp; + + printf("Sending MNCC_RTP_CONNECT\n"); + bzero(&rtp, sizeof(struct gsm_mncc_rtp)); + rtp.msg_type = MNCC_RTP_CONNECT; + rtp.callref = 1; + bcopy(&dummy_rtp_endp, &rtp.addr, sizeof(struct sockaddr_storage)); + send_mncc_to_gsm(&rtp, sizeof(struct gsm_mncc_rtp)); +} + +static void +handle_dtmf_time() +{ + static struct timeval last_dtmf_time; + struct timeval delta; + unsigned ms; + + if (timerisset(&last_dtmf_time) && + timercmp(&cur_event_time, &last_dtmf_time, >)) { + timersub(&cur_event_time, &last_dtmf_time, &delta); + if (delta.tv_sec >= 2) + printf("Time since last DTMF event: %u s\n", + (unsigned) delta.tv_sec); + else { + ms = delta.tv_sec * 1000 + delta.tv_usec / 1000; + printf("Time since last DTMF event: %u ms\n", ms); + } + } + bcopy(&cur_event_time, &last_dtmf_time, sizeof(struct timeval)); +} + +static void +handle_signaling_msg(msg, msglen) + struct gsm_mncc *msg; + unsigned msglen; +{ + if (msglen != sizeof(struct gsm_mncc)) { + fprintf(stderr, + "error: Rx MNCC message type 0x%x has wrong length\n", + msg->msg_type); + exit(1); + } + if (msg->callref != 1) { + fprintf(stderr, + "error: Rx MNCC message type 0x%x has unexpected callref 0x%x\n", + msg->msg_type, msg->callref); + exit(1); + } + switch (msg->msg_type) { + case MNCC_SETUP_CNF: + printf("MNCC_SETUP_CNF: call is answered\n"); + print_fields(msg); + print_sdp(msg->sdp); + send_rtp_connect(); + send_connect_ack(); + return; + case MNCC_CALL_CONF_IND: + printf("MNCC_CALL_CONF_IND: call is confirmed\n"); + print_fields(msg); + print_sdp(msg->sdp); + return; + case MNCC_ALERT_IND: + printf("MNCC_ALERT_IND: call is alerting\n"); + print_fields(msg); + print_sdp(msg->sdp); + return; + case MNCC_NOTIFY_IND: + printf("NNCC_NOTIFY_IND: NOTIFY byte from MS: 0x%02X\n", + msg->notify); + return; + case MNCC_DISC_IND: + printf("MNCC_DISC_IND: MS initiates disconnect\n"); + print_fields(msg); + disconnect_mode = 1; + printf("Responding with release request\n"); + msg->msg_type = MNCC_REL_REQ; + send_mncc_to_gsm(msg, sizeof(struct gsm_mncc)); + return; + case MNCC_FACILITY_IND: + printf("MNCC_FACILITY_IND: call-related SS from MS\n"); + print_fields(msg); + return; + case MNCC_START_DTMF_IND: + printf("MNCC_START_DTMF_IND: MS sending DTMF start\n"); + print_fields(msg); + handle_dtmf_time(); + if (msg->fields & MNCC_F_KEYPAD && + is_valid_dtmf_digit(msg->keypad)) { + printf("Responding with ACK\n"); + msg->msg_type = MNCC_START_DTMF_RSP; + } else { + printf("Responding with Reject\n"); + msg->msg_type = MNCC_START_DTMF_REJ; + mncc_set_cause(msg, GSM48_CAUSE_LOC_PRN_S_LU, + GSM48_CC_CAUSE_INVAL_MAND_INF); + } + send_mncc_to_gsm(msg, sizeof(struct gsm_mncc)); + return; + case MNCC_STOP_DTMF_IND: + printf("MNCC_STOP_DTMF_IND: MS sending DTMF stop\n"); + handle_dtmf_time(); + msg->msg_type = MNCC_STOP_DTMF_RSP; + send_mncc_to_gsm(msg, sizeof(struct gsm_mncc)); + return; + case MNCC_MODIFY_IND: + printf("MNCC_MODIFY_IND: MS requests call modification\n"); + print_fields(msg); + msg->msg_type = MNCC_MODIFY_REJ; + mncc_set_cause(msg, GSM48_CAUSE_LOC_PRN_S_LU, + GSM48_CC_CAUSE_SERV_OPT_UNIMPL); + send_mncc_to_gsm(msg, sizeof(struct gsm_mncc)); + return; + case MNCC_HOLD_IND: + printf("MNCC_HOLD_IND: MS requests call hold\n"); + msg->msg_type = MNCC_HOLD_CNF; + send_mncc_to_gsm(msg, sizeof(struct gsm_mncc)); + return; + case MNCC_RETRIEVE_IND: + printf("MNCC_RETRIEVE_IND: MS requests call retrieve\n"); + send_rtp_connect(); + msg->msg_type = MNCC_RETRIEVE_CNF; + send_mncc_to_gsm(msg, sizeof(struct gsm_mncc)); + return; + case MNCC_USERINFO_IND: + printf("MNCC_USERINFO_IND: user-user info\n"); + print_fields(msg); + return; + case MNCC_REL_IND: + printf("MNCC_REL_IND: final release\n"); + print_fields(msg); + exit(0); + case MNCC_REL_CNF: + printf("MNCC_REL_CNF: final release in response to request\n"); + print_fields(msg); + exit(0); + case MNCC_REJ_IND: + printf("MNCC_REJ_IND: MT call rejected\n"); + print_fields(msg); + exit(0); + } +} + +static void +handle_rtp_msg(msg, msglen) + struct gsm_mncc_rtp *msg; + unsigned msglen; +{ + if (msglen != sizeof(struct gsm_mncc_rtp)) { + fprintf(stderr, + "error: Rx MNCC message type 0x%x has wrong length\n", + msg->msg_type); + exit(1); + } + if (msg->callref != 1) { + fprintf(stderr, + "error: Rx MNCC message type 0x%x has unexpected callref 0x%x\n", + msg->msg_type, msg->callref); + exit(1); + } + switch (msg->msg_type) { + case MNCC_RTP_CREATE: + printf("MNCC_RTP_CREATE: RTP info from MSC\n"); + printf("payload_type=0x%x payload_msg_type=0x%x\n", + msg->payload_type, msg->payload_msg_type); + print_sdp(msg->sdp); + return; + case MNCC_RTP_CONNECT: + printf("MNCC_RTP_CONNECT: error response\n"); + return; + case MNCC_RTP_FREE: + printf("MNCC_RTP_FREE: bogon\n"); + return; + } +} + +void +msg_from_mncc(msg, msglen) + union mncc_msg *msg; + unsigned msglen; +{ + switch (msg->msg_type) { + case MNCC_SETUP_CNF: + case MNCC_CALL_CONF_IND: + case MNCC_ALERT_IND: + case MNCC_NOTIFY_IND: + case MNCC_DISC_IND: + case MNCC_FACILITY_IND: + case MNCC_START_DTMF_IND: + case MNCC_STOP_DTMF_IND: + case MNCC_MODIFY_IND: + case MNCC_HOLD_IND: + case MNCC_RETRIEVE_IND: + case MNCC_USERINFO_IND: + case MNCC_REL_IND: + case MNCC_REL_CNF: + case MNCC_REJ_IND: + handle_signaling_msg(msg, msglen); + return; + case MNCC_RTP_CREATE: + case MNCC_RTP_CONNECT: + case MNCC_RTP_FREE: + handle_rtp_msg(msg, msglen); + return; + default: + fprintf(stderr, + "error: received unexpected MNCC message type 0x%x\n", + msg->msg_type); + exit(1); + } +} diff -r ce450869db09 -r e7b192a5dee5 mtctest/sock_conn.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mtctest/sock_conn.c Sun Jun 09 00:58:38 2024 +0000 @@ -0,0 +1,61 @@ +/* + * In this module we implement our connection to the MNCC daemon's + * mtcall socket. + */ + +#include +#include +#include +#include +#include +#include +#include "../include/mncc.h" + +static char mtcall_socket_pathname[] = "/var/gsm/mtcall_socket"; + +int mtc_socket; + +connect_mtc_socket() +{ + struct sockaddr_un sa; + unsigned sa_len; + int rc; + + mtc_socket = socket(AF_UNIX, SOCK_SEQPACKET, 0); + if (mtc_socket < 0) { + perror("socket(AF_UNIX, SOCK_SEQPACKET, 0)"); + exit(1); + } + fill_sockaddr_un(mtcall_socket_pathname, &sa, &sa_len); + rc = connect(mtc_socket, (struct sockaddr *) &sa, sa_len); + if (rc < 0) { + perror(mtcall_socket_pathname); + exit(1); + } + return(0); +} + +void +mtc_socket_select() +{ + union mncc_msg msg; + int rc; + + rc = recv(mtc_socket, &msg, sizeof msg, 0); + if (rc < 0) { + perror("read from socket"); + exit(1); + } + if (rc < 4) { + fprintf(stderr, "short read from socket: %d bytes\n", rc); + exit(1); + } + msg_from_mncc(&msg, rc); +} + +send_mncc_to_gsm(msg, msglen) + union mncc_msg *msg; + unsigned msglen; +{ + return send(mtc_socket, msg, msglen, 0); +}