From 58f1c9a91226f4954a4799fab082f186923aa806 Mon Sep 17 00:00:00 2001 From: Andreas Eversberg Date: Sat, 3 Oct 2020 16:25:48 +0200 Subject: Add libraries from Osmocom-Analog --- src/libosmocc/session.c | 639 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 639 insertions(+) create mode 100644 src/libosmocc/session.c (limited to 'src/libosmocc/session.c') diff --git a/src/libosmocc/session.c b/src/libosmocc/session.c new file mode 100644 index 0000000..72e805a --- /dev/null +++ b/src/libosmocc/session.c @@ -0,0 +1,639 @@ +/* Osmo-CC: Media Session handling + * + * (C) 2016 by Andreas Eversberg + * 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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include "../libtimer/timer.h" +#include "../libdebug/debug.h" +#include "endpoint.h" + +#define NTP_OFFSET 2208988800 + +enum osmo_cc_session_nettype default_nettype = osmo_cc_session_nettype_inet; +enum osmo_cc_session_addrtype default_addrtype = osmo_cc_session_addrtype_ipv4; +const char *default_unicast_address = "127.0.0.1"; + +void osmo_cc_set_local_peer(enum osmo_cc_session_nettype nettype, enum osmo_cc_session_addrtype addrtype, const char *address) +{ + default_nettype = nettype; + default_addrtype = addrtype; + default_unicast_address = strdup(address); +} + +osmo_cc_session_t *osmo_cc_new_session(void *priv, const char *username, const char *sess_id, const char *sess_version, enum osmo_cc_session_nettype nettype, enum osmo_cc_session_addrtype addrtype, const char *unicast_address, const char *session_name, int debug) +{ + osmo_cc_session_t *session; + + if (debug) PDEBUG(DCC, DEBUG_DEBUG, "Creating session structure.\n"); + + session = calloc(1, sizeof(*session)); + if (!session) { + PDEBUG(DCC, DEBUG_ERROR, "No mem!\n"); + abort(); + } + session->priv = priv; + if (username) { + int i; + for (i = 0; username[i]; i++) { + if ((uint8_t)username[i] < 33) { + PDEBUG(DCC, DEBUG_ERROR, "Fatal error: SDP's originator (username) uses invalid characters, please fix!\n"); + abort(); + } + } + session->origin_local.username = strdup(username); + } + if (!username) + session->origin_local.username = strdup("-"); + if (debug) PDEBUG(DCC, DEBUG_DEBUG, " -> user name = %s\n", session->origin_local.username); + if (sess_id) + session->origin_local.sess_id = strdup(sess_id); + if (sess_version) + session->origin_local.sess_version = strdup(sess_version); + if (!sess_id || !sess_version) { + struct timeval tv; + char ntp_timestamp[32]; + /* get time NTP format time stamp (time since 1900) */ + gettimeofday(&tv, NULL); + sprintf(ntp_timestamp, "%" PRIu64, (uint64_t)tv.tv_sec + NTP_OFFSET); + if (!sess_id) + session->origin_local.sess_id = strdup(ntp_timestamp); + if (!sess_version) + session->origin_local.sess_version = strdup(ntp_timestamp); + } + if (debug) PDEBUG(DCC, DEBUG_DEBUG, " -> session ID = %s\n", session->origin_local.sess_id); + if (debug) PDEBUG(DCC, DEBUG_DEBUG, " -> session version = %s\n", session->origin_local.sess_version); + if (nettype) + session->origin_local.nettype = strdup(osmo_cc_session_nettype2string(nettype)); + else + session->origin_local.nettype = strdup(osmo_cc_session_nettype2string(default_nettype)); + if (debug) PDEBUG(DCC, DEBUG_DEBUG, " -> network type = %s\n", session->origin_local.nettype); + if (addrtype) + session->origin_local.addrtype = strdup(osmo_cc_session_addrtype2string(addrtype)); + else + session->origin_local.addrtype = strdup(osmo_cc_session_addrtype2string(default_addrtype)); + if (debug) PDEBUG(DCC, DEBUG_DEBUG, " -> address type = %s\n", session->origin_local.addrtype); + if (unicast_address) + session->origin_local.unicast_address = strdup(unicast_address); + else + session->origin_local.unicast_address = strdup(default_unicast_address); + if (debug) PDEBUG(DCC, DEBUG_DEBUG, " -> unicast address = %s\n", session->origin_local.unicast_address); + if (session_name) + session->name = strdup(session_name); + if (!session_name) + session->name = strdup("-"); + if (debug) PDEBUG(DCC, DEBUG_DEBUG, " -> session name = %s\n", session->name); + + return session; +} + +void osmo_cc_free_session(osmo_cc_session_t *session) +{ + PDEBUG(DCC, DEBUG_DEBUG, "Free session structure.\n"); + + free((char *)session->origin_local.username); + free((char *)session->origin_local.sess_id); + free((char *)session->origin_local.sess_version); + free((char *)session->origin_local.nettype); + free((char *)session->origin_local.addrtype); + free((char *)session->origin_local.unicast_address); + free((char *)session->origin_remote.username); + free((char *)session->origin_remote.sess_id); + free((char *)session->origin_remote.sess_version); + free((char *)session->origin_remote.nettype); + free((char *)session->origin_remote.addrtype); + free((char *)session->origin_remote.unicast_address); + free((char *)session->name); + while (session->media_list) + osmo_cc_free_media(session->media_list); + free(session); +} + +osmo_cc_session_media_t *osmo_cc_add_media(osmo_cc_session_t *session, enum osmo_cc_session_nettype nettype, enum osmo_cc_session_addrtype addrtype, const char *address, enum osmo_cc_session_media_type type, uint16_t port, enum osmo_cc_session_media_proto proto, int send, int receive, void (*receiver)(struct osmo_cc_session_codec *codec, uint16_t sequence_number, uint32_t timestamp, uint8_t *data, int len), int debug) +{ + osmo_cc_session_media_t *media, **mediap; + + media = calloc(1, sizeof(*media)); + if (!media) { + PDEBUG(DCC, DEBUG_ERROR, "No mem!\n"); + abort(); + } + media->session = session; + if (nettype) + media->connection_data_local.nettype = nettype; + else + media->connection_data_local.nettype = default_nettype; + if (addrtype) + media->connection_data_local.addrtype = addrtype; + else + media->connection_data_local.addrtype = default_addrtype; + if (address) + media->connection_data_local.address = strdup(address); + else + media->connection_data_local.address = strdup(default_unicast_address); + media->description.type = type; + media->description.port_local = port; + media->description.proto = proto; + media->send = send; + media->receive = receive; + media->receiver = receiver; + media->tx_sequence = random(); + media->tx_timestamp = random(); + mediap = &media->session->media_list; + while (*mediap) + mediap = &((*mediap)->next); + *mediap = media; + + if (debug) PDEBUG(DCC, DEBUG_DEBUG, "Adding session media.\n"); + if (debug) PDEBUG(DCC, DEBUG_DEBUG, " -> network type = %s\n", osmo_cc_session_nettype2string(media->connection_data_local.nettype)); + if (debug) PDEBUG(DCC, DEBUG_DEBUG, " -> address type = %s\n", osmo_cc_session_addrtype2string(media->connection_data_local.addrtype)); + if (debug) PDEBUG(DCC, DEBUG_DEBUG, " -> address = %s\n", media->connection_data_local.address); + if (debug) PDEBUG(DCC, DEBUG_DEBUG, " -> media type = %s\n", osmo_cc_session_media_type2string(media->description.type)); + if (debug) PDEBUG(DCC, DEBUG_DEBUG, " -> media port = %d\n", media->description.port_local); + if (debug) PDEBUG(DCC, DEBUG_DEBUG, " -> media proto = %s\n", osmo_cc_session_media_proto2string(media->description.proto)); + if (debug) PDEBUG(DCC, DEBUG_DEBUG, "Opening and binding media port %d\n", media->description.port_local); + + return media; +} + +void osmo_cc_free_media(osmo_cc_session_media_t *media) +{ + osmo_cc_session_media_t **mediap; + + PDEBUG(DCC, DEBUG_DEBUG, "Free session media.\n"); + + osmo_cc_rtp_close(media); + free((char *)media->connection_data_local.nettype_name); + free((char *)media->connection_data_local.addrtype_name); + free((char *)media->connection_data_local.address); + free((char *)media->connection_data_remote.nettype_name); + free((char *)media->connection_data_remote.addrtype_name); + free((char *)media->connection_data_remote.address); + while (media->codec_list) + osmo_cc_free_codec(media->codec_list); + mediap = &media->session->media_list; + while (*mediap != media) + mediap = &((*mediap)->next); + *mediap = media->next; + free(media); +} + +osmo_cc_session_codec_t *osmo_cc_add_codec(osmo_cc_session_media_t *media, const char *payload_name, uint32_t payload_rate, int payload_channels, void (*encoder)(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len), void (*decoder)(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len), int debug) +{ + osmo_cc_session_codec_t *codec, **codecp; + int rc; + + codec = calloc(1, sizeof(*codec)); + if (!codec) { + PDEBUG(DCC, DEBUG_ERROR, "No mem!\n"); + abort(); + } + codec->media = media; + if (payload_name) { + codec->payload_name = strdup(payload_name); + codec->payload_rate = payload_rate; + codec->payload_channels = payload_channels; + rc = osmo_cc_payload_type_by_attrs(&codec->payload_type_local, payload_name, &payload_rate, &payload_channels); + if (rc < 0) { + /* hunt for next free dynamic payload type */ + uint8_t fmt = 96; + osmo_cc_session_codec_t *c; + osmo_cc_session_for_each_codec(media->codec_list, c) { + if (c->payload_type_local >= fmt) + fmt = c->payload_type_local + 1; + } + codec->payload_type_local = fmt; + } + } + codec->encoder = encoder; + codec->decoder = decoder; + codecp = &codec->media->codec_list; + while (*codecp) + codecp = &((*codecp)->next); + *codecp = codec; + + if (debug) PDEBUG(DCC, DEBUG_DEBUG, "Adding session codec.\n"); + if (debug) PDEBUG(DCC, DEBUG_DEBUG, " -> payload type = %d\n", codec->payload_type_local); + if (debug) PDEBUG(DCC, DEBUG_DEBUG, " -> payload name = %s\n", codec->payload_name); + if (debug) PDEBUG(DCC, DEBUG_DEBUG, " -> payload rate = %d\n", codec->payload_rate); + if (debug) PDEBUG(DCC, DEBUG_DEBUG, " -> payload channels = %d\n", codec->payload_channels); + + return codec; +} + +void osmo_cc_free_codec(osmo_cc_session_codec_t *codec) +{ + osmo_cc_session_codec_t **codecp; + + PDEBUG(DCC, DEBUG_DEBUG, "Free session codec.\n"); + + free((char *)codec->payload_name); + codecp = &codec->media->codec_list; + while (*codecp != codec) + codecp = &((*codecp)->next); + *codecp = codec->next; + free(codec); +} + +int osmo_cc_session_check(osmo_cc_session_t *session, int remote) +{ + struct osmo_cc_session_origin *orig; + struct osmo_cc_session_media *media; + struct osmo_cc_session_connection_data *cd; + struct osmo_cc_session_media_description *md; + struct osmo_cc_session_codec *codec; + int i, j; + + if (remote) + orig = &session->origin_remote; + else + orig = &session->origin_local; + if (!orig->username + || !orig->sess_id + || !orig->sess_version + || !orig->nettype + || !orig->addrtype + || !orig->unicast_address) { + PDEBUG(DCC, DEBUG_NOTICE, "Missing data in session origin\n"); + return -EINVAL; + } + if (!session->name) { + PDEBUG(DCC, DEBUG_NOTICE, "Missing data in session origin\n"); + return -EINVAL; + } + if (!session->media_list) { + PDEBUG(DCC, DEBUG_NOTICE, "Missing media session\n"); + return -EINVAL; + } + i = 0; + osmo_cc_session_for_each_media(session->media_list, media) { + i++; + if (remote) + cd = &media->connection_data_remote; + else + cd = &media->connection_data_local; + if (!cd->nettype && !cd->nettype_name) { + PDEBUG(DCC, DEBUG_NOTICE, "Session with media #%d is missing connection network type\n", i); + return -EINVAL; + } + if (!cd->addrtype && !cd->addrtype_name) { + PDEBUG(DCC, DEBUG_NOTICE, "Session with media #%d is missing connection address type\n", i); + return -EINVAL; + } + if (!cd->address) { + PDEBUG(DCC, DEBUG_NOTICE, "Session with media #%d is missing connection address\n", i); + return -EINVAL; + } + md = &media->description; + if (!md->type && !md->type_name) { + PDEBUG(DCC, DEBUG_NOTICE, "Session with media #%d is missing media type\n", i); + return -EINVAL; + } + if (!md->proto && !md->proto_name) { + PDEBUG(DCC, DEBUG_NOTICE, "Session with media #%d is missing protocol\n", i); + return -EINVAL; + } + j = 0; + osmo_cc_session_for_each_codec(media->codec_list, codec) { + j++; + if (!codec->payload_name) { + PDEBUG(DCC, DEBUG_NOTICE, "Session with media #%d, codec #%d is missing name\n", i, j); + return -EINVAL; + } + if (!codec->payload_rate) { + PDEBUG(DCC, DEBUG_NOTICE, "Session with media #%d, codec #%d is missing rate\n", i, j); + return -EINVAL; + } + if (!codec->payload_channels) { + PDEBUG(DCC, DEBUG_NOTICE, "Session with media #%d, codec #%d is missing channel count\n", i, j); + return -EINVAL; + } + } + } + + return 0; +} + +/* check session description and generate SDP */ +const char *osmo_cc_session_send_offer(osmo_cc_session_t *session) +{ + const char *sdp; + int rc; + + PDEBUG(DCC, DEBUG_DEBUG, "Generating session offer and opening RTP stream.\n"); + + rc = osmo_cc_session_check(session, 0); + if (rc < 0) { + PDEBUG(DCC, DEBUG_ERROR, "Please fix!\n"); + abort(); + } + + sdp = osmo_cc_session_gensdp(session); + osmo_cc_debug_sdp(sdp); + + return sdp; +} + +osmo_cc_session_t *osmo_cc_session_receive_offer(void *priv, const char *sdp) +{ + osmo_cc_session_t *session; + int rc; + + PDEBUG(DCC, DEBUG_DEBUG, "Parsing session offer.\n"); + + osmo_cc_debug_sdp(sdp); + session = osmo_cc_session_parsesdp(priv, sdp); + if (!session) + return NULL; + + rc = osmo_cc_session_check(session, 0); + if (rc < 0) { + osmo_cc_free_session(session); + return NULL; + } + + return session; +} + +void osmo_cc_session_accept_media(osmo_cc_session_media_t *media, enum osmo_cc_session_nettype nettype, enum osmo_cc_session_addrtype addrtype, const char *address, int send, int receive, void (*receiver)(struct osmo_cc_session_codec *codec, uint16_t sequence_number, uint32_t timestamp, uint8_t *data, int len)) +{ + media->accepted = 1; + if (nettype) + media->connection_data_local.nettype = nettype; + else + media->connection_data_local.nettype = default_nettype; + if (addrtype) + media->connection_data_local.addrtype = addrtype; + else + media->connection_data_local.addrtype = default_addrtype; + free((char *)media->connection_data_local.address); + if (address) + media->connection_data_local.address = strdup(address); + else + media->connection_data_local.address = strdup(default_unicast_address); + media->send = send; + media->receive = receive; + media->receiver = receiver; + + PDEBUG(DCC, DEBUG_DEBUG, "Accepting session media.\n"); + PDEBUG(DCC, DEBUG_DEBUG, " -> network type = %s\n", osmo_cc_session_nettype2string(media->connection_data_local.nettype)); + PDEBUG(DCC, DEBUG_DEBUG, " -> address type = %s\n", osmo_cc_session_addrtype2string(media->connection_data_local.addrtype)); + PDEBUG(DCC, DEBUG_DEBUG, " -> address = %s\n", media->connection_data_local.address); +} + + +void osmo_cc_session_accept_codec(osmo_cc_session_codec_t *codec, void (*encoder)(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len), void (*decoder)(uint8_t *src_data, int src_len, uint8_t **dst_data, int *dst_len)) +{ + codec->accepted = 1; + codec->encoder = encoder; + codec->decoder = decoder; + /* when we accept a codec, we just use the same payload type as the remote */ + codec->payload_type_local = codec->payload_type_remote; + + PDEBUG(DCC, DEBUG_DEBUG, "Accepting session codec.\n"); + PDEBUG(DCC, DEBUG_DEBUG, " -> payload type = %d\n", codec->payload_type_local); + PDEBUG(DCC, DEBUG_DEBUG, " -> payload name = %s\n", codec->payload_name); + PDEBUG(DCC, DEBUG_DEBUG, " -> payload rate = %d\n", codec->payload_rate); + PDEBUG(DCC, DEBUG_DEBUG, " -> payload channels = %d\n", codec->payload_channels); +} + +/* remove codecs/media that have not been accepted and generate SDP */ +const char *osmo_cc_session_send_answer(osmo_cc_session_t *session) +{ + osmo_cc_session_media_t *media; + osmo_cc_session_codec_t *codec, **codec_p; + const char *sdp; + int rc; + + PDEBUG(DCC, DEBUG_DEBUG, "Generating session answer.\n"); + + /* loop all media */ + osmo_cc_session_for_each_media(session->media_list, media) { + /* remove unaccepted codecs */ + codec_p = &media->codec_list; + codec = *codec_p; + while (codec) { + if (!codec->accepted) { + osmo_cc_free_codec(codec); + codec = *codec_p; + continue; + } + codec_p = &codec->next; + codec = *codec_p; + } + /* mark media as unused, if no codec or not accepted */ + if (!media->accepted || !media->codec_list) + media->description.port_local = 0; + } + + rc = osmo_cc_session_check(session, 0); + if (rc < 0) { + PDEBUG(DCC, DEBUG_ERROR, "Please fix!\n"); + abort(); + } + + sdp = osmo_cc_session_gensdp(session); + osmo_cc_debug_sdp(sdp); + + return sdp; +} + +/* Apply remote session description to local session description. + * If remote media's port is 0, remove from local session description. + * If codecs in the remote session description are missing, remove from local session description. + */ +static int osmo_cc_session_negotiate(osmo_cc_session_t *session_local, struct osmo_cc_session *session_remote) +{ + osmo_cc_session_media_t *media_local, *media_remote, **media_local_p; + osmo_cc_session_codec_t *codec_local, *codec_remote, **codec_local_p; + int rc; + + PDEBUG(DCC, DEBUG_DEBUG, "Negotiating session.\n"); + + /* copy remote session infromation */ + session_local->origin_remote.username = strdup(session_remote->origin_remote.username); + session_local->origin_remote.sess_id = strdup(session_remote->origin_remote.sess_id); + session_local->origin_remote.sess_version = strdup(session_remote->origin_remote.sess_version); + session_local->origin_remote.nettype = strdup(session_remote->origin_remote.nettype); + session_local->origin_remote.addrtype = strdup(session_remote->origin_remote.addrtype); + session_local->origin_remote.unicast_address = strdup(session_remote->origin_remote.unicast_address); + + /* loop all media */ + for (media_local = session_local->media_list, media_remote = session_remote->media_list; media_local && media_remote; media_local = media_local->next, media_remote = media_remote->next) { + /* copy remote media information */ + media_local->connection_data_remote.nettype = media_remote->connection_data_remote.nettype; + if (media_remote->connection_data_remote.nettype_name) + media_local->connection_data_remote.nettype_name = strdup(media_remote->connection_data_remote.nettype_name); + media_local->connection_data_remote.addrtype = media_remote->connection_data_remote.addrtype; + if (media_remote->connection_data_remote.addrtype_name) + media_local->connection_data_remote.addrtype_name = strdup(media_remote->connection_data_remote.addrtype_name); + if (media_remote->connection_data_remote.address) + media_local->connection_data_remote.address = strdup(media_remote->connection_data_remote.address); + media_local->description.port_remote = media_remote->description.port_remote; + media_local->send = media_remote->send; + media_local->receive = media_remote->receive; + /* loop all codecs and remove if they are not found in local session description */ + codec_local_p = &media_local->codec_list; + codec_local = *codec_local_p; + while (codec_local) { + /* search for equal codec, payload type may differe for each direction */ + osmo_cc_session_for_each_codec(media_remote->codec_list, codec_remote) { + if (!strcmp(codec_local->payload_name, codec_remote->payload_name) + && codec_local->payload_rate == codec_remote->payload_rate + && codec_local->payload_channels == codec_remote->payload_channels) + break; + } + if (!codec_remote) { + osmo_cc_free_codec(codec_local); + codec_local = *codec_local_p; + continue; + } + /* copy remote codec information */ + codec_local->payload_type_remote = codec_remote->payload_type_remote; + codec_local_p = &codec_local->next; + codec_local = *codec_local_p; + } + } + if (media_local) { + PDEBUG(DCC, DEBUG_NOTICE, "Negotiation failed, because remote endpoint returns less media streams than we offered.\n"); + return -EINVAL; + } + if (media_remote) { + PDEBUG(DCC, DEBUG_NOTICE, "Negotiation failed, because remote endpoint returns more media streams than we offered.\n"); + return -EINVAL; + } + + /* remove media with port == 0 or no codec at all */ + media_local_p = &session_local->media_list; + media_local = *media_local_p; + while (media_local) { + if (media_local->description.port_remote == 0 || !media_local->codec_list) { + osmo_cc_free_media(media_local); + media_local = *media_local_p; + continue; + } + media_local_p = &media_local->next; + media_local = *media_local_p; + } + + rc = osmo_cc_session_check(session_local, 1); + if (rc < 0) + return rc; + + return 0; +} + +int osmo_cc_session_receive_answer(osmo_cc_session_t *session, const char *sdp) +{ + osmo_cc_session_t *session_remote; + int rc; + + PDEBUG(DCC, DEBUG_DEBUG, "Parsing session answer.\n"); + + osmo_cc_debug_sdp(sdp); + session_remote = osmo_cc_session_parsesdp(NULL, sdp); + if (!session_remote) + return -EINVAL; + + rc = osmo_cc_session_check(session_remote, 1); + if (rc < 0) { + osmo_cc_free_session(session_remote); + return rc; + } + rc = osmo_cc_session_negotiate(session, session_remote); + if (rc < 0) { + osmo_cc_free_session(session_remote); + return rc; + } + osmo_cc_free_session(session_remote); + + return 0; +} + +const char *osmo_cc_session_nettype2string(enum osmo_cc_session_nettype nettype) +{ + switch (nettype) { + case osmo_cc_session_nettype_inet: + return "IN"; + default: + return NULL; + } +} + +const char *osmo_cc_session_addrtype2string(enum osmo_cc_session_addrtype addrtype) +{ + switch (addrtype) { + case osmo_cc_session_addrtype_ipv4: + return "IP4"; + case osmo_cc_session_addrtype_ipv6: + return "IP6"; + default: + return NULL; + } +} + +const char *osmo_cc_session_media_type2string(enum osmo_cc_session_media_type media_type) +{ + switch (media_type) { + case osmo_cc_session_media_type_audio: + return "audio"; + case osmo_cc_session_media_type_video: + return "video"; + default: + return NULL; + } +} + +const char *osmo_cc_session_media_proto2string(enum osmo_cc_session_media_proto media_proto) +{ + switch (media_proto) { + case osmo_cc_session_media_proto_rtp: + return "RTP/AVP"; + default: + return NULL; + } +} + +int osmo_cc_session_if_codec(osmo_cc_session_codec_t *codec, const char *name, uint32_t rate, int channels) +{ + return (!strcmp(codec->payload_name, name) + && codec->payload_rate == rate + && codec->payload_channels == channels); +} + +int osmo_cc_session_handle(osmo_cc_session_t *session) +{ + osmo_cc_session_media_t *media; + int w = 0, rc; + + osmo_cc_session_for_each_media(session->media_list, media) { + do { + rc = osmo_cc_rtp_receive(media); + if (rc >= 0) + w = 1; + } while (rc >= 0); + } + + return w; +} + -- cgit v1.2.3