/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include <errno.h>

#include "sdp_os_defs.h"
#include "sipcc_sdp.h"
#include "sdp_private.h"

#include "sdp_log.h"
#include "prprf.h"

static const char *logTag = "sdp_token";

#define MCAST_STRING_LEN 4


sdp_result_e sdp_parse_version (sdp_t *sdp_p, uint16_t level, const char *ptr)
{
    sdp_result_e result = SDP_FAILURE;

    sdp_p->version = (uint16_t)sdp_getnextnumtok(ptr, &ptr, " \t", &result);
    if ((result != SDP_SUCCESS) || (sdp_p->version != SDP_CURRENT_VERSION)) {
        sdp_parse_error(sdp_p,
            "%s Invalid version (%u) found, parse failed.",
            sdp_p->debug_str, (unsigned)sdp_p->version);
        sdp_p->conf_p->num_invalid_param++;
        return (SDP_INVALID_PARAMETER);
    }

    if (sdp_p->debug_flag[SDP_DEBUG_TRACE]) {
        SDP_PRINT("%s Parse version line successful, version %u",
                  sdp_p->debug_str, (unsigned)sdp_p->version);
    }
    return (SDP_SUCCESS);
}

sdp_result_e sdp_build_version (sdp_t *sdp_p, uint16_t level, flex_string *fs)
{
    if (sdp_p->version == SDP_INVALID_VALUE) {
        if (sdp_p->conf_p->version_reqd == TRUE) {
            SDPLogError(logTag, "%s Invalid version for v= line, "
                        "build failed.", sdp_p->debug_str);
            sdp_p->conf_p->num_invalid_param++;
            return (SDP_INVALID_PARAMETER);
        } else {
            /* v= line is not required. */
            return (SDP_SUCCESS);
        }
    }

    flex_string_sprintf(fs, "v=%u\r\n", (unsigned)sdp_p->version);

    if (sdp_p->debug_flag[SDP_DEBUG_TRACE]) {
        SDP_PRINT("%s Built v= version line", sdp_p->debug_str);
    }
    return (SDP_SUCCESS);
}

static sdp_result_e sdp_verify_unsigned(const char *ptr, uint64_t max_value)
{
    uint64_t numeric_value;
    /* Checking for only numbers since PR_sscanf will ignore trailing
       characters */
    size_t end = strspn(ptr, "0123456789");

    if (ptr[end] != '\0')
        return SDP_INVALID_PARAMETER;

    if (PR_sscanf(ptr, "%llu", &numeric_value) != 1)
        return SDP_INVALID_PARAMETER;

    if (numeric_value > max_value)
        return SDP_INVALID_PARAMETER;

    return SDP_SUCCESS;
}

sdp_result_e sdp_parse_owner (sdp_t *sdp_p, uint16_t level, const char *ptr)
{
    int          i;
    sdp_result_e result;
    char         tmp[SDP_MAX_STRING_LEN];
    /* The spec says this:

        The numeric value of the session id
        and version in the o line MUST be representable with a 64 bit signed
        integer.  The initial value of the version MUST be less than
        (2**62)-1, to avoid rollovers.
    */
    const uint64_t max_value_sessid = ((((uint64_t) 1) << 63) - 1);
    /* Do not check that this is 2^62 - 1; that's just the limit on
     * the initial version, not every version number. */
    const uint64_t max_value_version = ((((uint64_t) 1) << 63) - 1);

    if (sdp_p->owner_name[0] != '\0') {
        sdp_p->conf_p->num_invalid_token_order++;
        sdp_parse_error(sdp_p,
            "%s Warning: More than one o= line specified.",
            sdp_p->debug_str);
    }

    /* Find the owner name. */
    ptr = sdp_getnextstrtok(ptr, sdp_p->owner_name, sizeof(sdp_p->owner_name), " \t", &result);
    if (result != SDP_SUCCESS) {
        sdp_parse_error(sdp_p,
            "%s No owner name specified for o=.",
            sdp_p->debug_str);
        sdp_p->conf_p->num_invalid_param++;
        return (SDP_INVALID_PARAMETER);
    }

    /* Find the owner session id.  This is a numeric field but is
     * stored as a string since it may be 64 bit.
     */
    ptr = sdp_getnextstrtok(ptr, sdp_p->owner_sessid, sizeof(sdp_p->owner_sessid), " \t", &result);
    if (result == SDP_SUCCESS) {
        /* Make sure the sessid is numeric, even though we store it as
         * a string.
         */
        result = sdp_verify_unsigned(sdp_p->owner_sessid, max_value_sessid);
    }
    if (result != SDP_SUCCESS) {
        sdp_parse_error(sdp_p,
            "%s Invalid owner session id specified for o=.",
            sdp_p->debug_str);
        sdp_p->conf_p->num_invalid_param++;
        return (SDP_INVALID_PARAMETER);
    }

    /* Find the owner version. */
    ptr = sdp_getnextstrtok(ptr, sdp_p->owner_version, sizeof(sdp_p->owner_version), " \t", &result);
    if (result == SDP_SUCCESS) {
        /* Make sure the version is numeric, even though we store it as
         * a string.
         */
        result = sdp_verify_unsigned(sdp_p->owner_version, max_value_version);
    }
    if (result != SDP_SUCCESS) {
        sdp_parse_error(sdp_p,
            "%s Invalid owner version specified for o=.",
            sdp_p->debug_str);
        sdp_p->conf_p->num_invalid_param++;
        return (SDP_INVALID_PARAMETER);
    }

    /* Find the owner network type. */
    ptr = sdp_getnextstrtok(ptr, tmp, sizeof(tmp), " \t", &result);
    if (result != SDP_SUCCESS) {
        sdp_parse_error(sdp_p,
            "%s No owner network type specified for o=.",
            sdp_p->debug_str);
        sdp_p->conf_p->num_invalid_param++;
        return (SDP_INVALID_PARAMETER);
    }
    sdp_p->owner_network_type = SDP_NT_UNSUPPORTED;
    for (i=0; i < SDP_MAX_NETWORK_TYPES; i++) {
        if (cpr_strncasecmp(tmp, sdp_nettype[i].name,
                        sdp_nettype[i].strlen) == 0) {
            if (sdp_p->conf_p->nettype_supported[i] == TRUE) {
                sdp_p->owner_network_type = (sdp_nettype_e)i;
            }
        }
    }
    if (sdp_p->owner_network_type == SDP_NT_UNSUPPORTED) {
        sdp_parse_error(sdp_p,
            "%s Owner network type unsupported (%s)",
            sdp_p->debug_str, tmp);
        sdp_p->conf_p->num_invalid_param++;
        return (SDP_INVALID_PARAMETER);
    }

    /* Find the owner address type. */
    ptr = sdp_getnextstrtok(ptr, tmp, sizeof(tmp), " \t", &result);
    if (result != SDP_SUCCESS) {
        sdp_parse_error(sdp_p,
            "%s No owner address type specified for o=.",
            sdp_p->debug_str);
        sdp_p->conf_p->num_invalid_param++;
        return (SDP_INVALID_PARAMETER);
    }
    sdp_p->owner_addr_type = SDP_AT_UNSUPPORTED;
    for (i=0; i < SDP_MAX_ADDR_TYPES; i++) {
        if (cpr_strncasecmp(tmp, sdp_addrtype[i].name,
                        sdp_addrtype[i].strlen) == 0) {
            if (sdp_p->conf_p->addrtype_supported[i] == TRUE) {
                sdp_p->owner_addr_type = (sdp_addrtype_e)i;
            }
        }
    }
    if ((sdp_p->owner_addr_type == SDP_AT_UNSUPPORTED) &&
        (sdp_p->owner_network_type != SDP_NT_ATM)) {
        sdp_parse_error(sdp_p,
            "%s Owner address type unsupported (%s)",
            sdp_p->debug_str, tmp);
        sdp_p->conf_p->num_invalid_param++;
        return (SDP_INVALID_PARAMETER);
    }

    /* Find the owner address. */
    ptr = sdp_getnextstrtok(ptr, sdp_p->owner_addr, sizeof(sdp_p->owner_addr), " \t", &result);
    if (result != SDP_SUCCESS) {
        sdp_parse_error(sdp_p,
            "%s No owner address specified.", sdp_p->debug_str);
        sdp_p->conf_p->num_invalid_param++;
        return (SDP_INVALID_PARAMETER);
    }

    if (sdp_p->debug_flag[SDP_DEBUG_TRACE]) {
        SDP_PRINT("%s Parse owner: name %s, session id %s, version %s",
                  sdp_p->debug_str, sdp_p->owner_name, sdp_p->owner_sessid,
                  sdp_p->owner_version);
        SDP_PRINT("%s              network %s, address type %s, "
                  "address %s", sdp_p->debug_str,
                  sdp_get_network_name(sdp_p->owner_network_type),
                  sdp_get_address_name(sdp_p->owner_addr_type),
                  sdp_p->owner_addr);
    }
    return (SDP_SUCCESS);
}

sdp_result_e sdp_build_owner (sdp_t *sdp_p, uint16_t level, flex_string *fs)
{
    if ((sdp_p->owner_name[0] == '\0') ||
        (sdp_p->owner_network_type >= SDP_MAX_NETWORK_TYPES) ||
        (sdp_p->owner_addr_type >= SDP_MAX_ADDR_TYPES) ||
        (sdp_p->owner_addr[0] == '\0')) {

        if((sdp_p->owner_network_type == SDP_NT_ATM) &&
           (sdp_p->owner_addr_type == SDP_AT_INVALID)) {
          flex_string_sprintf(fs, "o=%s %s %s %s - -\r\n",
                    sdp_p->owner_name, sdp_p->owner_sessid,
                    sdp_p->owner_version,
                    sdp_get_network_name(sdp_p->owner_network_type));
        }

        if (sdp_p->conf_p->owner_reqd == TRUE) {
            SDPLogError(logTag, "%s Invalid params for o= owner line, "
                        "build failed.", sdp_p->debug_str);
            sdp_p->conf_p->num_invalid_param++;
            return (SDP_INVALID_PARAMETER);
        } else {
            /* o= line is not required. */
            return (SDP_SUCCESS);
        }
    }

    flex_string_sprintf(fs, "o=%s %s %s %s %s %s\r\n",
                    sdp_p->owner_name, sdp_p->owner_sessid,
                    sdp_p->owner_version,
                    sdp_get_network_name(sdp_p->owner_network_type),
                    sdp_get_address_name(sdp_p->owner_addr_type),
                    sdp_p->owner_addr);

    if (sdp_p->debug_flag[SDP_DEBUG_TRACE]) {
        SDP_PRINT("%s Built o= owner line", sdp_p->debug_str);
    }
    return (SDP_SUCCESS);
}

sdp_result_e sdp_parse_sessname (sdp_t *sdp_p, uint16_t level, const char *ptr)
{
    int   str_len;
    char *endptr;

    if (sdp_p->sessname[0] != '\0') {
        sdp_p->conf_p->num_invalid_token_order++;
        sdp_parse_error(sdp_p,
            "%s Warning: More than one s= line specified.",
            sdp_p->debug_str);
    }

    endptr = sdp_findchar(ptr, "\r\n");
    if (ptr == endptr) {
        sdp_parse_error(sdp_p,
            "%s Warning: No session name specified.",
            sdp_p->debug_str);
    }
    str_len = MIN(endptr - ptr, SDP_MAX_STRING_LEN);
    sstrncpy(sdp_p->sessname, ptr, str_len+1);

    if (sdp_p->debug_flag[SDP_DEBUG_TRACE]) {
        SDP_PRINT("%s Parse session name, %s",
                  sdp_p->debug_str, sdp_p->sessname);
    }
    return (SDP_SUCCESS);
}

sdp_result_e sdp_build_sessname (sdp_t *sdp_p, uint16_t level, flex_string *fs)
{
    if (sdp_p->sessname[0] == '\0') {
        if (sdp_p->conf_p->session_name_reqd == TRUE) {
            SDPLogError(logTag, "%s No param defined for s= session name line, "
                        "build failed.", sdp_p->debug_str);
            sdp_p->conf_p->num_invalid_param++;
            return (SDP_INVALID_PARAMETER);
        } else {
            /* s= line is not required. */
            return (SDP_SUCCESS);
        }
    }

    flex_string_sprintf(fs, "s=%s\r\n", sdp_p->sessname);

    if (sdp_p->debug_flag[SDP_DEBUG_TRACE]) {
        SDP_PRINT("%s Built s= session name line", sdp_p->debug_str);
    }
    return (SDP_SUCCESS);
}

/* We don't want to store the session info, but we do want to validate
 * that at most one i= line exists at each level and if the line exists
 * there should be a parameter.
 */
sdp_result_e sdp_parse_sessinfo (sdp_t *sdp_p, uint16_t level, const char *ptr)
{
    char *endptr;
    sdp_mca_t *mca_p;

    if (level == SDP_SESSION_LEVEL) {
        if (sdp_p->sessinfo_found == TRUE) {
            sdp_p->conf_p->num_invalid_token_order++;
            sdp_parse_error(sdp_p,
                "%s Warning: More than one i= line specified.",
                sdp_p->debug_str);
        }
        sdp_p->sessinfo_found = TRUE;
    } else {
        mca_p = sdp_find_media_level(sdp_p, level);
        if (mca_p == NULL) {
            return (SDP_FAILURE);
        }
        if (mca_p->sessinfo_found == TRUE) {
            sdp_p->conf_p->num_invalid_token_order++;
            sdp_parse_error(sdp_p,
                "%s Warning: More than one i= line specified"
                " for media line %u.", sdp_p->debug_str, (unsigned)level);
        }
        mca_p->sessinfo_found = TRUE;
    }

    endptr = sdp_findchar(ptr, "\n");
    if (ptr == endptr) {
        sdp_parse_error(sdp_p,
            "%s Warning: No session info specified.",
            sdp_p->debug_str);
    }

    if (sdp_p->debug_flag[SDP_DEBUG_TRACE]) {
        SDP_PRINT("%s Parsed session info line.", sdp_p->debug_str);
    }
    return (SDP_SUCCESS);
}

sdp_result_e sdp_build_sessinfo (sdp_t *sdp_p, uint16_t level, flex_string *fs)
{
    /* Build session info line not supported. */
    return (SDP_SUCCESS);
}

sdp_result_e sdp_parse_uri (sdp_t *sdp_p, uint16_t level, const char *ptr)
{
    char *endptr;

    if (sdp_p->uri_found == TRUE) {
        sdp_p->conf_p->num_invalid_token_order++;
        sdp_parse_error(sdp_p,
            "%s Warning: More than one u= line specified.",
            sdp_p->debug_str);
    }
    sdp_p->uri_found = TRUE;

    endptr = sdp_findchar(ptr, "\n");
    if (ptr == endptr) {
        sdp_parse_error(sdp_p,
            "%s Warning: No URI info specified.", sdp_p->debug_str);
    }

    if (sdp_p->debug_flag[SDP_DEBUG_TRACE]) {
        SDP_PRINT("%s Parsed URI line.", sdp_p->debug_str);
    }
    return (SDP_SUCCESS);
}

sdp_result_e sdp_build_uri (sdp_t *sdp_p, uint16_t level, flex_string *fs)
{
    /* Build URI line not supported. */
    return (SDP_SUCCESS);
}

sdp_result_e sdp_parse_email (sdp_t *sdp_p, uint16_t level, const char *ptr)
{
    char *endptr;

    endptr = sdp_findchar(ptr, "\n");
    if (ptr == endptr) {
        sdp_parse_error(sdp_p,
            "%s Warning: No email info specified.", sdp_p->debug_str);
    }

    if (sdp_p->debug_flag[SDP_DEBUG_TRACE]) {
        SDP_PRINT("%s Parse email line", sdp_p->debug_str);
    }
    return (SDP_SUCCESS);
}

sdp_result_e sdp_build_email (sdp_t *sdp_p, uint16_t level, flex_string *fs)
{
    /* Build email line not supported. */
    return (SDP_SUCCESS);
}

sdp_result_e sdp_parse_phonenum (sdp_t *sdp_p, uint16_t level, const char *ptr)
{
    char *endptr;

    endptr = sdp_findchar(ptr, "\n");
    if (ptr == endptr) {
        sdp_parse_error(sdp_p,
            "%s Warning: No phone number info specified.",
            sdp_p->debug_str);
    }

    if (sdp_p->debug_flag[SDP_DEBUG_TRACE]) {
        SDP_PRINT("%s Parse phone number line", sdp_p->debug_str);
    }
    return (SDP_SUCCESS);
}

sdp_result_e sdp_build_phonenum (sdp_t *sdp_p, uint16_t level, flex_string *fs)
{
    /* Build phone number line not supported. */
    return (SDP_SUCCESS);
}

sdp_result_e sdp_parse_connection (sdp_t *sdp_p, uint16_t level, const char *ptr)
{
    int           i;
    const char   *slash_ptr;
    sdp_result_e  result;
    sdp_conn_t   *conn_p;
    sdp_mca_t    *mca_p;
    char          tmp[SDP_MAX_STRING_LEN];
    char mcast_str[MCAST_STRING_LEN];
    int  mcast_bits;
    unsigned long strtoul_result;
    char *strtoul_end;

    if (level == SDP_SESSION_LEVEL) {
        conn_p = &(sdp_p->default_conn);
    } else {
        mca_p = sdp_find_media_level(sdp_p, level);
        if (mca_p == NULL) {
            return (SDP_FAILURE);
        }
        conn_p = &(mca_p->conn);
    }

    /* See if the c= line is already defined at this level. We don't
     * currently support multihoming and so we only support one c= at
     * each level.
     */
    if (conn_p->nettype != SDP_NT_INVALID) {
        sdp_p->conf_p->num_invalid_token_order++;
        sdp_parse_error(sdp_p,
            "%s c= line specified twice at same level, "
            "parse failed.", sdp_p->debug_str);
        return (SDP_INVALID_TOKEN_ORDERING);
    }

    /* Find the connection network type. */
    ptr = sdp_getnextstrtok(ptr, tmp, sizeof(tmp), " \t", &result);
    if (result != SDP_SUCCESS) {
        sdp_parse_error(sdp_p,
            "%s No connection network type specified for c=.",
            sdp_p->debug_str);
        sdp_p->conf_p->num_invalid_param++;
        return (SDP_INVALID_PARAMETER);
    }
    conn_p->nettype = SDP_NT_UNSUPPORTED;
    for (i=0; i < SDP_MAX_NETWORK_TYPES; i++) {
        if (cpr_strncasecmp(tmp, sdp_nettype[i].name,
                        sdp_nettype[i].strlen) == 0) {
            if (sdp_p->conf_p->nettype_supported[i] == TRUE) {
                conn_p->nettype = (sdp_nettype_e)i;
            }
        }
    }
    if (conn_p->nettype == SDP_NT_UNSUPPORTED) {
        sdp_parse_error(sdp_p,
            "%s Warning: Connection network type unsupported "
            "(%s) for c=.", sdp_p->debug_str, tmp);
    }

    /* Find the connection address type. */
    ptr = sdp_getnextstrtok(ptr, tmp, sizeof(tmp), " \t", &result);
    if (result != SDP_SUCCESS) {
        if (conn_p->nettype == SDP_NT_ATM) {
            /* If the nettype is ATM, addr type and addr are not reqd */
            if (sdp_p->debug_flag[SDP_DEBUG_TRACE]) {
                SDP_PRINT("%s Parse connection: network %s", sdp_p->debug_str,
                          sdp_get_network_name(conn_p->nettype));
            }
            return (SDP_SUCCESS);
        } else {
            sdp_parse_error(sdp_p,
                "%s No connection address type specified for "
                "c=.", sdp_p->debug_str);
            sdp_p->conf_p->num_invalid_param++;
            return (SDP_INVALID_PARAMETER);
        }
    }
    conn_p->addrtype = SDP_AT_UNSUPPORTED;
    for (i=0; i < SDP_MAX_ADDR_TYPES; i++) {
        if (cpr_strncasecmp(tmp, sdp_addrtype[i].name,
                        sdp_addrtype[i].strlen) == 0) {
            if (sdp_p->conf_p->addrtype_supported[i] == TRUE) {
                conn_p->addrtype = (sdp_addrtype_e)i;
            }
        }
    }
    if (conn_p->addrtype == SDP_AT_UNSUPPORTED) {
        sdp_parse_error(sdp_p,
            "%s Warning: Connection address type unsupported "
            "(%s) for c=.", sdp_p->debug_str, tmp);
    }

    /* Find the connection address. */
    ptr = sdp_getnextstrtok(ptr, conn_p->conn_addr, sizeof(conn_p->conn_addr), " \t", &result);
    if (result != SDP_SUCCESS) {
        sdp_parse_error(sdp_p,
            "%s No connection address specified for c=.",
            sdp_p->debug_str);
        sdp_p->conf_p->num_invalid_param++;
        return (SDP_INVALID_PARAMETER);
    }
    /* We currently only support addrs containing '/'s for EPN addrs.
     * For other addrs this would indicate multicast addrs. */
    /* Multicast host group addresses are defined to be the IP addresses
     * whose high-order four bits are 1110, giving an address range from
     * 224.0.0.0 through 239.255.255.255
     */
    /* multicast addr check */
    sstrncpy (mcast_str, conn_p->conn_addr, MCAST_STRING_LEN);

    if (conn_p->addrtype == SDP_AT_IP4) {
        errno = 0;
        strtoul_result = strtoul(mcast_str, &strtoul_end, 10);

        if (errno || mcast_str == strtoul_end || strtoul_result > 255) {
            sdp_parse_error(sdp_p,
                            "%s Error parsing address %s for mcast.",
                            sdp_p->debug_str, mcast_str);
            sdp_p->conf_p->num_invalid_param++;
            return SDP_INVALID_PARAMETER;
        }


        mcast_bits = (int) strtoul_result;
        if ((mcast_bits >= SDP_MIN_MCAST_ADDR_HI_BIT_VAL ) &&
            (mcast_bits <= SDP_MAX_MCAST_ADDR_HI_BIT_VAL)) {
            SDP_PRINT("%s Parsed to be a multicast address with mcast bits %d",
                      sdp_p->debug_str, mcast_bits);
            conn_p->is_multicast = TRUE;
        }
    }

    if (conn_p->addrtype != SDP_AT_EPN) {
        slash_ptr = sdp_findchar(conn_p->conn_addr, "/");
        if (slash_ptr[0] != '\0') {
            /* this used to rely on the above busted multicast check */
            SDP_PRINT("%s An address with slash %s",
                      sdp_p->debug_str, conn_p->conn_addr);
            conn_p->conn_addr[slash_ptr - conn_p->conn_addr] = '\0';
            slash_ptr++;
            slash_ptr = sdp_getnextstrtok(slash_ptr, tmp, sizeof(tmp),
                                          "/", &result);
            if (result != SDP_SUCCESS) {
                sdp_parse_error(sdp_p,
                                "%s No ttl value specified for this multicast addr with a slash",
                                sdp_p->debug_str);
                sdp_p->conf_p->num_invalid_param++;
                return (SDP_INVALID_PARAMETER);
            }

            errno = 0;
            strtoul_result = strtoul(tmp, &strtoul_end, 10);

            if (errno || tmp == strtoul_end || conn_p->ttl > SDP_MAX_TTL_VALUE) {
                sdp_parse_error(sdp_p,
                                "%s Invalid TTL: Value must be in the range 0-255 ",
                                sdp_p->debug_str);
                sdp_p->conf_p->num_invalid_param++;
                return (SDP_INVALID_PARAMETER);
            }

            conn_p->ttl = (int) strtoul_result;

            /* search for num of addresses */
            /*sa_ignore NO_NULL_CHK
              {ptr is valid since the pointer was checked earlier and the
              function would have exited if NULL.}*/
            slash_ptr = sdp_findchar(slash_ptr, "/");
            if (slash_ptr != NULL &&
                slash_ptr[0] != '\0') {
                SDP_PRINT("%s Found a num addr field for multicast addr %s ",
                          sdp_p->debug_str,slash_ptr);
                slash_ptr++;

                errno = 0;
                strtoul_result = strtoul(slash_ptr, &strtoul_end, 10);

                if (errno || slash_ptr == strtoul_end || strtoul_result == 0) {
                    sdp_parse_error(sdp_p,
                                    "%s Invalid Num of addresses: Value must be > 0 ",
                                    sdp_p->debug_str);
                    sdp_p->conf_p->num_invalid_param++;
                    return SDP_INVALID_PARAMETER;
                }

                conn_p->num_of_addresses = (int) strtoul_result;
            }
        }
    }

    /* See if the address is the choose param and if it's allowed. */
    if ((sdp_p->conf_p->allow_choose[SDP_CHOOSE_CONN_ADDR] == FALSE) &&
        (strcmp(conn_p->conn_addr, "$") == 0)) {
        sdp_parse_error(sdp_p,
            "%s Warning: Choose parameter for connection "
            "address specified but not allowed.", sdp_p->debug_str);
    }

    if (sdp_p->debug_flag[SDP_DEBUG_TRACE]) {
        SDP_PRINT("%s Parse connection: network %s, address type %s, "
                  "address %s ttl= %u num of addresses = %u",
                  sdp_p->debug_str,
                  sdp_get_network_name(conn_p->nettype),
                  sdp_get_address_name(conn_p->addrtype),
                  conn_p->conn_addr, (unsigned)conn_p->ttl, (unsigned)conn_p->num_of_addresses);
    }
    return (SDP_SUCCESS);
}

sdp_result_e sdp_build_connection (sdp_t *sdp_p, uint16_t level, flex_string *fs)
{
    sdp_mca_t  *mca_p;
    sdp_conn_t *conn_p;

    if (level == SDP_SESSION_LEVEL) {
        conn_p = &(sdp_p->default_conn);
    } else {
        mca_p = sdp_find_media_level(sdp_p, level);
        if (mca_p == NULL) {
            return (SDP_FAILURE);
        }
        conn_p = &(mca_p->conn);
    }

    if((conn_p->nettype == SDP_NT_ATM ) &&
       (conn_p->addrtype == SDP_AT_INVALID)) {
        /*allow c= line to be built without address type and address fields
         * This is a special case for ATM PVC*/
        flex_string_sprintf(fs, "c=%s\r\n",
                    sdp_get_network_name(conn_p->nettype));
        return SDP_SUCCESS;
    }
    if ((conn_p->nettype >= SDP_MAX_NETWORK_TYPES) ||
        (conn_p->addrtype >= SDP_MAX_ADDR_TYPES) ||
        (conn_p->conn_addr[0] == '\0')) {
        /* Connection info isn't set - don't need to build the token. */
        return (SDP_SUCCESS);
    }

    if (conn_p->is_multicast) {
        if (conn_p->num_of_addresses > 1) {
            flex_string_sprintf(fs, "c=%s %s %s/%u/%u\r\n",
                             sdp_get_network_name(conn_p->nettype),
                             sdp_get_address_name(conn_p->addrtype),
                             conn_p->conn_addr,
                             (unsigned)conn_p->ttl,
                             (unsigned)conn_p->num_of_addresses);
        } else {
            flex_string_sprintf(fs, "c=%s %s %s/%u\r\n",
                             sdp_get_network_name(conn_p->nettype),
                             sdp_get_address_name(conn_p->addrtype),
                             conn_p->conn_addr,
                             (unsigned)conn_p->ttl);
        }
    } else {

        flex_string_sprintf(fs, "c=%s %s %s\r\n",
                         sdp_get_network_name(conn_p->nettype),
                         sdp_get_address_name(conn_p->addrtype),
                         conn_p->conn_addr);
    }

    if (sdp_p->debug_flag[SDP_DEBUG_TRACE]) {
        SDP_PRINT("%s Built c= connection line", sdp_p->debug_str);
    }
    return (SDP_SUCCESS);
}

/*
 * sdp_parse_bandwidth
 *
 * This function parses a bandwidth field. The parsing is done in accordance
 * to the following ABNF:
 *
 * bandwidth-fields =    *("b=" bwtype ":" bandwidth CRLF)
 * bwtype =              1*(alpha-numeric)
 * bandwidth =           1*(DIGIT)
 *
 * It currently supports three types of valid bwtypes - AS, CT and TIAS
 */
sdp_result_e sdp_parse_bandwidth (sdp_t *sdp_p, uint16_t level, const char *ptr)
{
    int                  i;
    sdp_mca_t            *mca_p;
    sdp_bw_t             *bw_p;
    sdp_bw_data_t        *bw_data_p;
    sdp_bw_data_t        *new_bw_data_p;
    sdp_result_e         result;
    char                 tmp[SDP_MAX_STRING_LEN];
    sdp_bw_modifier_e    bw_modifier = SDP_BW_MODIFIER_UNSUPPORTED;
    int                  bw_val = 0;

    if (level == SDP_SESSION_LEVEL) {
        bw_p = &(sdp_p->bw);
    } else {
        mca_p = sdp_find_media_level(sdp_p, level);
        if (mca_p == NULL) {
            return (SDP_FAILURE);
        }
        bw_p = &(mca_p->bw);
    }

    if (sdp_p->debug_flag[SDP_DEBUG_TRACE]) {
        SDP_PRINT("%s Parse bandwidth line", sdp_p->debug_str);
    }

    /* Find the bw type (AS, CT or TIAS) */
    ptr = sdp_getnextstrtok(ptr, tmp, sizeof(tmp), ":", &result);
    if (result != SDP_SUCCESS) {
        sdp_parse_error(sdp_p,
            "%s No bandwidth type specified for b= ",
            sdp_p->debug_str);
        sdp_p->conf_p->num_invalid_param++;
        return (SDP_INVALID_PARAMETER);
    }
    for (i=0; i < SDP_MAX_BW_MODIFIER_VAL; i++) {
        if (cpr_strncasecmp(tmp, sdp_bw_modifier_val[i].name,
                        sdp_bw_modifier_val[i].strlen) == 0) {
            bw_modifier  = (sdp_bw_modifier_e)i;
            break;
        }
    }

    if (bw_modifier == SDP_BW_MODIFIER_UNSUPPORTED) {
        /* We don't understand this parameter, so according to RFC4566 sec 5.8
         * ignore it. */
        return (SDP_SUCCESS);
    }

    /* Find the BW type value */
    /*sa_ignore NO_NULL_CHK
      {ptr is valid since the pointer was checked earlier and the
       function would have exited if NULL.}*/
    if (*ptr == ':') {
        ptr++;
        bw_val = sdp_getnextnumtok(ptr, &ptr, " \t", &result);
        if ((result != SDP_SUCCESS)) {
            sdp_parse_error(sdp_p,
                "%s Error: No BW Value specified ",
                sdp_p->debug_str);
            sdp_p->conf_p->num_invalid_param++;
            return (SDP_INVALID_PARAMETER);
        }
    }

    /*
     * Allocate a new sdp_bw_data_t instance and set it's values from the
     * input parameters.
     */
    new_bw_data_p = (sdp_bw_data_t*)SDP_MALLOC(sizeof(sdp_bw_data_t));
    if (new_bw_data_p == NULL) {
        sdp_p->conf_p->num_invalid_param++;
        return (SDP_NO_RESOURCE);
    }
    new_bw_data_p->next_p = NULL;
    new_bw_data_p->bw_modifier = bw_modifier;
    new_bw_data_p->bw_val = bw_val;

    /*
     * Enqueue the sdp_bw_data_t instance at the end of the list of
     * sdp_bw_data_t instances.
     */
    if (bw_p->bw_data_list == NULL) {
        bw_p->bw_data_list = new_bw_data_p;
    } else {
        for (bw_data_p = bw_p->bw_data_list;
             bw_data_p->next_p != NULL;
             bw_data_p = bw_data_p->next_p) {
            ; // Empty For
        }
        bw_data_p->next_p = new_bw_data_p;
    }
    bw_p->bw_data_count++;

    if (sdp_p->debug_flag[SDP_DEBUG_TRACE]) {
        SDP_PRINT("%s Parsed bw type %s, value %d", sdp_p->debug_str,
                     sdp_get_bw_modifier_name(new_bw_data_p->bw_modifier),
                     new_bw_data_p->bw_val);
    }

    return (SDP_SUCCESS);
}

/*
 * sdp_build_bandwidth
 *
 * Builds *all* the bandwith lines for the specified level.
 */
sdp_result_e sdp_build_bandwidth (sdp_t *sdp_p, uint16_t level, flex_string *fs)
{
    sdp_bw_t            *bw_p;
    sdp_bw_data_t       *bw_data_p;
    sdp_mca_t           *mca_p;

    if (level == SDP_SESSION_LEVEL) {
        bw_p = &(sdp_p->bw);
    } else {
        mca_p = sdp_find_media_level(sdp_p, level);
        if (mca_p == NULL) {
            return (SDP_FAILURE);
        }
        bw_p = &(mca_p->bw);
    }

    bw_data_p = bw_p->bw_data_list;
    while (bw_data_p) {
        flex_string_sprintf(fs, "b=%s:%d\r\n",
                         sdp_get_bw_modifier_name(bw_data_p->bw_modifier),
                         bw_data_p->bw_val);

        if (sdp_p->debug_flag[SDP_DEBUG_TRACE]) {
           SDP_PRINT("%s Built b=%s:%d bandwidth line", sdp_p->debug_str,
                     sdp_get_bw_modifier_name(bw_data_p->bw_modifier),
                     bw_data_p->bw_val);
        }

        bw_data_p = bw_data_p->next_p;
    }

    return (SDP_SUCCESS);
}

sdp_result_e sdp_parse_timespec (sdp_t *sdp_p, uint16_t level, const char *ptr)
{
    char            *tmpptr;
    sdp_result_e     result;
    sdp_timespec_t  *timespec_p;
    sdp_timespec_t  *next_timespec_p;

    timespec_p = (sdp_timespec_t *)SDP_MALLOC(sizeof(sdp_timespec_t));
    if (timespec_p == NULL) {
        sdp_p->conf_p->num_no_resource++;
        return (SDP_NO_RESOURCE);
    }

    /* Validate start and stop times. */
    ptr = sdp_getnextstrtok(ptr, timespec_p->start_time, sizeof(timespec_p->start_time), " \t", &result);
    if (result == SDP_SUCCESS) {
        /* Make sure the start_time is numeric, even though we store it as
         * a string.
         */
        (void)sdp_getnextnumtok(timespec_p->start_time,
                                (const char **)&tmpptr, " \t", &result);
    }
    if (result != SDP_SUCCESS) {
        sdp_parse_error(sdp_p,
            "%s Invalid timespec start time specified.",
            sdp_p->debug_str);
        sdp_p->conf_p->num_invalid_param++;
        SDP_FREE(timespec_p);
        return (SDP_INVALID_PARAMETER);
    }

    ptr = sdp_getnextstrtok(ptr, timespec_p->stop_time, sizeof(timespec_p->stop_time), " \t", &result);
    if (result == SDP_SUCCESS) {
        /* Make sure the start_time is numeric, even though we store it as
         * a string.
         */
        (void)sdp_getnextnumtok(timespec_p->stop_time,
                                (const char **)&tmpptr, " \t", &result);
    }
    if (result != SDP_SUCCESS) {
        sdp_parse_error(sdp_p,
            "%s Invalid timespec stop time specified.",
            sdp_p->debug_str);
        sdp_p->conf_p->num_invalid_param++;
        SDP_FREE(timespec_p);
        return (SDP_INVALID_PARAMETER);
    }

    /* Link the new timespec in to the end of the list. */
    if (sdp_p->timespec_p == NULL) {
        sdp_p->timespec_p = timespec_p;
    } else {
        next_timespec_p = sdp_p->timespec_p;
        while (next_timespec_p->next_p != NULL) {
            next_timespec_p = next_timespec_p->next_p;
        }
        next_timespec_p->next_p = timespec_p;
    }

    if (sdp_p->debug_flag[SDP_DEBUG_TRACE]) {
        SDP_PRINT("%s Parsed timespec line", sdp_p->debug_str);
    }
    return (SDP_SUCCESS);
}

sdp_result_e sdp_build_timespec (sdp_t *sdp_p, uint16_t level, flex_string *fs)
{
    if ((sdp_p->timespec_p == NULL) ||
        (sdp_p->timespec_p->start_time[0] == '\0') ||
        (sdp_p->timespec_p->stop_time[0] == '\0')) {
        if (sdp_p->conf_p->timespec_reqd == TRUE) {
            SDPLogError(logTag, "%s Invalid params for t= time spec line, "
                        "build failed.", sdp_p->debug_str);
            sdp_p->conf_p->num_invalid_param++;
            return (SDP_INVALID_PARAMETER);
        } else {
            /* t= line not required. */
            return (SDP_SUCCESS);
        }
    }

    /* Note: We only support one t= line currently. */
    flex_string_sprintf(fs, "t=%s %s\r\n", sdp_p->timespec_p->start_time,
                    sdp_p->timespec_p->stop_time);

    if (sdp_p->debug_flag[SDP_DEBUG_TRACE]) {
        SDP_PRINT("%s Built t= timespec line", sdp_p->debug_str);
    }
    return (SDP_SUCCESS);
}

sdp_result_e sdp_parse_repeat_time (sdp_t *sdp_p, uint16_t level, const char *ptr)
{
    char *endptr;

    endptr = sdp_findchar(ptr, "\n");
    if (ptr == endptr) {
        sdp_parse_error(sdp_p,
            "%s Warning: No repeat time parameters "
            "specified.", sdp_p->debug_str);
    }

    if (sdp_p->debug_flag[SDP_DEBUG_TRACE]) {
        SDP_PRINT("%s Parsed repeat time line", sdp_p->debug_str);
    }
    return (SDP_SUCCESS);
}

sdp_result_e sdp_build_repeat_time (sdp_t *sdp_p, uint16_t level, flex_string *fs)
{
    /* Build repeat time line not supported. */
    return (SDP_SUCCESS);
}

sdp_result_e sdp_parse_timezone_adj (sdp_t *sdp_p, uint16_t level, const char *ptr)
{
    char *endptr;

    endptr = sdp_findchar(ptr, "\n");
    if (ptr == endptr) {
        sdp_parse_error(sdp_p,
            "%s Warning: No timezone parameters specified.",
            sdp_p->debug_str);
    }

    if (sdp_p->debug_flag[SDP_DEBUG_TRACE]) {
        SDP_PRINT("%s Parse timezone adustment line", sdp_p->debug_str);
    }
    return (SDP_SUCCESS);
}

sdp_result_e sdp_build_timezone_adj (sdp_t *sdp_p, uint16_t level, flex_string *fs)
{
    /* Build timezone adjustment line not supported. */
    return (SDP_SUCCESS);
}

sdp_result_e sdp_parse_encryption (sdp_t *sdp_p, uint16_t level, const char *ptr)
{
    int                  i;
    sdp_result_e         result;
    sdp_encryptspec_t   *encrypt_p;
    sdp_mca_t           *mca_p;
    char                 tmp[SDP_MAX_STRING_LEN];

    if (level == SDP_SESSION_LEVEL) {
        encrypt_p = &(sdp_p->encrypt);
    } else {
        mca_p = sdp_find_media_level(sdp_p, level);
        if (mca_p == NULL) {
            return (SDP_FAILURE);
        }
        encrypt_p = &(mca_p->encrypt);
    }
    encrypt_p->encrypt_key[0] = '\0';

    /* Find the encryption type. */
    ptr = sdp_getnextstrtok(ptr, tmp, sizeof(tmp), ":", &result);
    if (result != SDP_SUCCESS) {
        sdp_parse_error(sdp_p,
            "%s No encryption type specified for k=.",
            sdp_p->debug_str);
        sdp_p->conf_p->num_invalid_param++;
        return (SDP_INVALID_PARAMETER);
    }
    encrypt_p->encrypt_type = SDP_ENCRYPT_UNSUPPORTED;
    for (i=0; i < SDP_MAX_ENCRYPT_TYPES; i++) {
        if (cpr_strncasecmp(tmp, sdp_encrypt[i].name,
                        sdp_encrypt[i].strlen) == 0) {
            encrypt_p->encrypt_type = (sdp_encrypt_type_e)i;
            break;
        }
    }
    if (encrypt_p->encrypt_type == SDP_ENCRYPT_UNSUPPORTED) {
        sdp_parse_error(sdp_p,
            "%s Warning: Encryption type unsupported (%s).",
            sdp_p->debug_str, tmp);
    }

    /* Find the encryption key. */
    encrypt_p->encrypt_key[0] = '\0';
    /*sa_ignore NO_NULL_CHK
      {ptr is valid since the pointer was checked earlier and the
       function would have exited if NULL.}*/
    if (*ptr == ':')
        ptr++;
    if (encrypt_p->encrypt_type != SDP_ENCRYPT_PROMPT) {
        ptr = sdp_getnextstrtok(ptr, encrypt_p->encrypt_key, sizeof(encrypt_p->encrypt_key), " \t", &result);
        if ((result != SDP_SUCCESS) &&
            ((encrypt_p->encrypt_type == SDP_ENCRYPT_CLEAR) ||
             (encrypt_p->encrypt_type == SDP_ENCRYPT_BASE64) ||
             (encrypt_p->encrypt_type == SDP_ENCRYPT_URI))) {
            sdp_parse_error(sdp_p,
                "%s Warning: No encryption key specified "
                "as required.", sdp_p->debug_str);
            sdp_p->conf_p->num_invalid_param++;
            return (SDP_INVALID_PARAMETER);
        }
    }

    if (sdp_p->debug_flag[SDP_DEBUG_TRACE]) {
        SDP_PRINT("%s Parse encryption type %s, key %s", sdp_p->debug_str,
                   sdp_get_encrypt_name(encrypt_p->encrypt_type),
                   encrypt_p->encrypt_key);
    }
    return (SDP_SUCCESS);
}

/* If the encryption info is valid, we build it.  Else skip it. */
sdp_result_e sdp_build_encryption (sdp_t *sdp_p, uint16_t level, flex_string *fs)
{
    sdp_encryptspec_t   *encrypt_p;
    sdp_mca_t           *mca_p;

    if (level == SDP_SESSION_LEVEL) {
        encrypt_p = &(sdp_p->encrypt);
    } else {
        mca_p = sdp_find_media_level(sdp_p, level);
        if (mca_p == NULL) {
            return (SDP_FAILURE);
        }
        encrypt_p = &(mca_p->encrypt);
    }

    if ((encrypt_p->encrypt_type >= SDP_MAX_ENCRYPT_TYPES) ||
        ((encrypt_p->encrypt_type != SDP_ENCRYPT_PROMPT) &&
         (encrypt_p->encrypt_key[0] == '\0'))) {
        /* Encryption info isn't set - don't need to build the token. */
        return (SDP_SUCCESS);
    }

    flex_string_sprintf(fs, "k=%s",
                     sdp_get_encrypt_name(encrypt_p->encrypt_type));

    if (encrypt_p->encrypt_type == SDP_ENCRYPT_PROMPT) {
        /* There is no key to print. */
        flex_string_sprintf(fs, "\r\n");
    } else {
        flex_string_sprintf(fs, ":%s\r\n", encrypt_p->encrypt_key);
    }

    if (sdp_p->debug_flag[SDP_DEBUG_TRACE]) {
        SDP_PRINT("%s Built k= encryption line", sdp_p->debug_str);
    }
    return (SDP_SUCCESS);
}

sdp_result_e sdp_parse_media (sdp_t *sdp_p, uint16_t level, const char *ptr)
{
    uint16_t                   i;
    uint16_t                   num_port_params=0;
    int32_t                 num[SDP_MAX_PORT_PARAMS];
    tinybool              valid_param = FALSE;
    sdp_result_e          result;
    sdp_mca_t            *mca_p;
    sdp_mca_t            *next_mca_p;
    char                  tmp[SDP_MAX_STRING_LEN];
    char                  port[SDP_MAX_STRING_LEN];
    const char           *port_ptr;
    int32_t                 sctp_port;

    /* Allocate resource for new media stream. */
    mca_p = sdp_alloc_mca(sdp_p->parse_line);
    if (mca_p == NULL) {
        sdp_p->conf_p->num_no_resource++;
        return (SDP_NO_RESOURCE);
    }

    /* Find the media type. */
    ptr = sdp_getnextstrtok(ptr, tmp, sizeof(tmp), " \t", &result);
    if (result != SDP_SUCCESS) {
        sdp_parse_error(sdp_p,
            "%s No media type specified, parse failed.",
            sdp_p->debug_str);
        SDP_FREE(mca_p);
        sdp_p->conf_p->num_invalid_param++;
        return (SDP_INVALID_PARAMETER);
    }
    mca_p->media = SDP_MEDIA_UNSUPPORTED;
    for (i=0; i < SDP_MAX_MEDIA_TYPES; i++) {
        if (cpr_strncasecmp(tmp, sdp_media[i].name,
                        sdp_media[i].strlen) == 0) {
            mca_p->media = (sdp_media_e)i;
        }
    }
    if (mca_p->media == SDP_MEDIA_UNSUPPORTED) {
        sdp_parse_error(sdp_p,
            "%s Warning: Media type unsupported (%s).",
            sdp_p->debug_str, tmp);
    }

    /* Find the port token parameters, but don't process it until
     * we determine the transport protocol as that determines what
     * port number formats are valid.
     */
    ptr = sdp_getnextstrtok(ptr, port, sizeof(port), " \t", &result);
    if (result != SDP_SUCCESS) {
        sdp_parse_error(sdp_p,
            "%s No port specified in m= media line, "
            "parse failed.", sdp_p->debug_str);
        SDP_FREE(mca_p);
        sdp_p->conf_p->num_invalid_param++;
        return (SDP_INVALID_PARAMETER);
    }
    port_ptr = port;
    for (i=0; i < SDP_MAX_PORT_PARAMS; i++) {
          num[i] = sdp_getnextnumtok(port_ptr, (const char **)&port_ptr,
                                     "/ \t", &result);
          if (result != SDP_SUCCESS) {
              break;
          }
        num_port_params++;
    }

    /* Find the transport protocol type. */
    ptr = sdp_getnextstrtok(ptr, tmp, sizeof(tmp), " \t", &result);
    if (result != SDP_SUCCESS) {
        sdp_parse_error(sdp_p,
            "%s No transport protocol type specified, "
            "parse failed.", sdp_p->debug_str);
        SDP_FREE(mca_p);
        sdp_p->conf_p->num_invalid_param++;
        return (SDP_INVALID_PARAMETER);
    }
    mca_p->transport = SDP_TRANSPORT_UNSUPPORTED;
    for (i=0; i < SDP_MAX_TRANSPORT_TYPES; i++) {
        if (cpr_strncasecmp(tmp, sdp_transport[i].name,
                        sdp_transport[i].strlen) == 0) {
            mca_p->transport = (sdp_transport_e)i;
            break;
        }
    }

    if (mca_p->transport == SDP_TRANSPORT_UNSUPPORTED) {
        /* If we don't recognize or don't support the transport type,
         * just store the first num as the port.
         */
        mca_p->port = num[0];
        sdp_parse_error(sdp_p,
            "%s Warning: Transport protocol type unsupported "
            "(%s).", sdp_p->debug_str, tmp);
    }

    /* Check for each of the possible port formats according to the
     * type of transport protocol specified.
     */
    valid_param = FALSE;
    switch (num_port_params) {
    case 1:
        if ((mca_p->transport == SDP_TRANSPORT_RTPAVP) ||
            (mca_p->transport == SDP_TRANSPORT_RTPSAVP) ||
            (mca_p->transport == SDP_TRANSPORT_RTPSAVPF) ||
            (mca_p->transport == SDP_TRANSPORT_UDPTLSRTPSAVP) ||
            (mca_p->transport == SDP_TRANSPORT_UDPTLSRTPSAVPF) ||
            (mca_p->transport == SDP_TRANSPORT_TCPDTLSRTPSAVP) ||
            (mca_p->transport == SDP_TRANSPORT_TCPDTLSRTPSAVPF) ||
            (mca_p->transport == SDP_TRANSPORT_UDP) ||
            (mca_p->transport == SDP_TRANSPORT_TCP) ||
            (mca_p->transport == SDP_TRANSPORT_UDPTL) ||
            (mca_p->transport == SDP_TRANSPORT_UDPSPRT) ||
            (mca_p->transport == SDP_TRANSPORT_LOCAL) ||
            (mca_p->transport == SDP_TRANSPORT_DTLSSCTP) ||
            (mca_p->transport == SDP_TRANSPORT_UDPDTLSSCTP) ||
            (mca_p->transport == SDP_TRANSPORT_TCPDTLSSCTP)) {
            /* Port format is simply <port>.  Make sure that either
             * the choose param is allowed or that the choose value
             * wasn't specified.
             */
            if ((sdp_p->conf_p->allow_choose[SDP_CHOOSE_PORTNUM]) ||
                (num[0] != SDP_CHOOSE_PARAM)) {
                mca_p->port        = num[0];
                mca_p->port_format = SDP_PORT_NUM_ONLY;
                valid_param        = TRUE;
            }
        } else if (mca_p->transport == SDP_TRANSPORT_AAL1AVP) {
            /* Port format is simply <vcci>, choose param is not allowed.
             */
            if (num[0] != SDP_CHOOSE_PARAM) {
                mca_p->vcci        = num[0];
                mca_p->port_format = SDP_PORT_VCCI;
                valid_param        = TRUE;
            }
        } else if ((mca_p->transport == SDP_TRANSPORT_AAL2_ITU) ||
            (mca_p->transport == SDP_TRANSPORT_AAL2_ATMF) ||
            (mca_p->transport == SDP_TRANSPORT_AAL2_CUSTOM)) {
            /* Port format is simply <port>, and choose param is allowed,
             * according to AAL2 definitions.
             */
            mca_p->port        = num[0];
            mca_p->port_format = SDP_PORT_NUM_ONLY;
            valid_param        = TRUE;
        }
        break;
    case 2:
        if ((mca_p->transport == SDP_TRANSPORT_RTPAVP) ||
            (mca_p->transport == SDP_TRANSPORT_RTPSAVP) ||
            (mca_p->transport == SDP_TRANSPORT_RTPSAVPF) ||
            (mca_p->transport == SDP_TRANSPORT_UDPTLSRTPSAVP) ||
            (mca_p->transport == SDP_TRANSPORT_UDPTLSRTPSAVPF) ||
            (mca_p->transport == SDP_TRANSPORT_TCPDTLSRTPSAVP) ||
            (mca_p->transport == SDP_TRANSPORT_TCPDTLSRTPSAVPF) ||
            (mca_p->transport == SDP_TRANSPORT_UDP) ||
            (mca_p->transport == SDP_TRANSPORT_LOCAL)) {
            /* Port format is <port>/<num of ports>. Make sure choose
             * params were not specified.
             */
            if ((num[0] != SDP_CHOOSE_PARAM) &&
                (num[1] != SDP_CHOOSE_PARAM)) {
                mca_p->port        = num[0];
                mca_p->num_ports   = num[1];
                mca_p->port_format = SDP_PORT_NUM_COUNT;
                valid_param        = TRUE;
            }
        } else if (mca_p->transport == SDP_TRANSPORT_UDPTL) {
            /* Port format is <port>/<num of ports>. Make sure choose
             * params were not specified.  For UDPTL, only "1" may
             * be specified for number of ports.
             */
            if ((num[0] != SDP_CHOOSE_PARAM) &&
                (num[1] == 1)) {
                mca_p->port        = num[0];
                mca_p->num_ports   = 1;
                mca_p->port_format = SDP_PORT_NUM_COUNT;
                valid_param        = TRUE;
            }
        } else if (mca_p->transport == SDP_TRANSPORT_CES10) {
            /* Port format is <vpi>/<vci>. Make sure choose
             * params were not specified.
             */
            if ((num[0] != SDP_CHOOSE_PARAM) &&
                (num[1] != SDP_CHOOSE_PARAM)) {
                mca_p->vpi         = num[0];
                mca_p->vci         = num[1];
                mca_p->port_format = SDP_PORT_VPI_VCI;
                valid_param        = TRUE;
            }
        } else if ((mca_p->transport == SDP_TRANSPORT_AAL2_ITU) ||
                   (mca_p->transport == SDP_TRANSPORT_AAL2_ATMF) ||
                   (mca_p->transport == SDP_TRANSPORT_AAL2_CUSTOM)) {
            /* Port format is either <vcci>/<cid> or $/$.  If one
             * param is '$' the other must be also.  The choose params
             * are allowed by default and don't need to be allowed
             * through the appl config.
             */
            if (((num[0] != SDP_CHOOSE_PARAM) &&
                 (num[1] != SDP_CHOOSE_PARAM)) ||
                ((num[0] == SDP_CHOOSE_PARAM) &&
                 (num[1] == SDP_CHOOSE_PARAM))) {
                mca_p->vcci        = num[0];
                mca_p->cid         = num[1];
                mca_p->port_format = SDP_PORT_VCCI_CID;
                valid_param        = TRUE;
            }
        }
        break;
    case 3:
        if (mca_p->transport == SDP_TRANSPORT_AAL1AVP) {
            /* Port format is <port>/<vpi>/<vci>. Make sure choose
             * params were not specified.
             */
            if ((num[0] != SDP_CHOOSE_PARAM) &&
                (num[1] != SDP_CHOOSE_PARAM) &&
                (num[2] != SDP_CHOOSE_PARAM)) {
                mca_p->port        = num[0];
                mca_p->vpi         = num[1];
                mca_p->vci         = num[2];
                mca_p->port_format = SDP_PORT_NUM_VPI_VCI;
                valid_param        = TRUE;
            }
        }
        break;
    case 4:
        if ((mca_p->transport == SDP_TRANSPORT_AAL2_ITU) ||
            (mca_p->transport == SDP_TRANSPORT_AAL2_ATMF) ||
            (mca_p->transport == SDP_TRANSPORT_AAL2_CUSTOM)) {
            /* Port format is <port>/<vpi>/<vci>/<cid>. Make sure choose
             * params were not specified.
             */
            if ((num[0] != SDP_CHOOSE_PARAM) &&
                (num[1] != SDP_CHOOSE_PARAM) &&
                (num[2] != SDP_CHOOSE_PARAM) &&
                (num[3] != SDP_CHOOSE_PARAM)) {
                mca_p->port        = num[0];
                mca_p->vpi         = num[1];
                mca_p->vci         = num[2];
                mca_p->cid         = num[3];
                mca_p->port_format = SDP_PORT_NUM_VPI_VCI_CID;
                valid_param        = TRUE;
            }
        }
        break;
    }
    if (valid_param == FALSE) {
        sdp_parse_error(sdp_p,
            "%s Invalid port format (%s) specified for transport "
            "protocol (%s), parse failed.", sdp_p->debug_str,
            port, sdp_get_transport_name(mca_p->transport));
        sdp_p->conf_p->num_invalid_param++;
        SDP_FREE(mca_p);
        return (SDP_INVALID_PARAMETER);
    }

        if ((mca_p->transport == SDP_TRANSPORT_DTLSSCTP) ||
            (mca_p->transport == SDP_TRANSPORT_UDPDTLSSCTP) ||
            (mca_p->transport == SDP_TRANSPORT_TCPDTLSSCTP)) {
        ptr = sdp_getnextstrtok(ptr, port, sizeof(port), " \t", &result);
        if (result != SDP_SUCCESS) {
            sdp_parse_error(sdp_p,
                "%s No sctp port specified in m= media line, "
                "parse failed.", sdp_p->debug_str);
            SDP_FREE(mca_p);
            sdp_p->conf_p->num_invalid_param++;
            return (SDP_INVALID_PARAMETER);
        }
        port_ptr = port;

        if ((mca_p->transport == SDP_TRANSPORT_UDPDTLSSCTP) ||
            (mca_p->transport == SDP_TRANSPORT_TCPDTLSSCTP)) {
            if (cpr_strncasecmp(port_ptr, "webrtc-datachannel",
                                sizeof("webrtc-datachannel")) != 0) {
                sdp_parse_error(sdp_p,
                    "%s No webrtc-datachannel token in m= media line, "
                    "parse failed.", sdp_p->debug_str);
                SDP_FREE(mca_p);
                sdp_p->conf_p->num_invalid_param++;
                return (SDP_INVALID_PARAMETER);
            }
            mca_p->sctp_fmt = SDP_SCTP_MEDIA_FMT_WEBRTC_DATACHANNEL;
        } else {
            sctp_port = sdp_getnextnumtok(port_ptr, (const char **)&port_ptr,
                                           "/ \t", &result);
            if (result != SDP_SUCCESS) {
                sdp_parse_error(sdp_p,
                    "%s No sctp port specified in m= media line, "
                    "parse failed.", sdp_p->debug_str);
                SDP_FREE(mca_p);
                sdp_p->conf_p->num_invalid_param++;
                return (SDP_INVALID_PARAMETER);
            }
            mca_p->sctpport = sctp_port;
        }
    } else {
        /* Transport is a non-AAL2 type and not SCTP.  Parse payloads
           normally. */
        sdp_parse_payload_types(sdp_p, mca_p, ptr);
    }


    /* Media line params are valid.  Add it into the SDP. */
    sdp_p->mca_count++;
    if (sdp_p->mca_p == NULL) {
        sdp_p->mca_p = mca_p;
    } else {
        for (next_mca_p = sdp_p->mca_p; next_mca_p->next_p != NULL;
             next_mca_p = next_mca_p->next_p) {
            ; // Empty For
        }
        next_mca_p->next_p = mca_p;
    }

    if (sdp_p->debug_flag[SDP_DEBUG_TRACE]) {

        SDP_PRINT("%s Parsed media type %s, ", sdp_p->debug_str,
                  sdp_get_media_name(mca_p->media));
        switch (mca_p->port_format) {
        case SDP_PORT_NUM_ONLY:
            SDP_PRINT("Port num %d, ", mca_p->port);
            break;

        case SDP_PORT_NUM_COUNT:
            SDP_PRINT("Port num %d, count %d, ",
                      mca_p->port, mca_p->num_ports);
            break;
        case SDP_PORT_VPI_VCI:
            SDP_PRINT("VPI/VCI %d/%u, ", mca_p->vpi, mca_p->vci);
            break;
        case SDP_PORT_VCCI:
            SDP_PRINT("VCCI %d, ", mca_p->vcci);
            break;
        case SDP_PORT_NUM_VPI_VCI:
            SDP_PRINT("Port %d, VPI/VCI %d/%u, ", mca_p->port,
                      mca_p->vpi, mca_p->vci);
            break;
        case SDP_PORT_VCCI_CID:
            SDP_PRINT("VCCI %d, CID %d, ", mca_p->vcci, mca_p->cid);
            break;
        case SDP_PORT_NUM_VPI_VCI_CID:
            SDP_PRINT("Port %d, VPI/VCI %d/%u, CID %d, ", mca_p->port,
                      mca_p->vpi, mca_p->vci, mca_p->cid);
            break;
        default:
            SDP_PRINT("Port format not valid, ");
            break;
        }

        if ((mca_p->transport >= SDP_TRANSPORT_AAL2_ITU) &&
            (mca_p->transport <= SDP_TRANSPORT_AAL2_CUSTOM)) {
            for (i=0; i < mca_p->media_profiles_p->num_profiles; i++) {
                SDP_PRINT("Profile %s, Num payloads %u ",
                   sdp_get_transport_name(mca_p->media_profiles_p->profile[i]),
                   (unsigned)mca_p->media_profiles_p->num_payloads[i]);
            }
        } else {
            SDP_PRINT("Transport %s, Num payloads %u",
                      sdp_get_transport_name(mca_p->transport),
                      (unsigned)mca_p->num_payloads);
        }
    }
    return (SDP_SUCCESS);
}

sdp_result_e sdp_build_media (sdp_t *sdp_p, uint16_t level, flex_string *fs)
{
    int                   i, j;
    sdp_mca_t            *mca_p;
    tinybool              invalid_params=FALSE;
    sdp_media_profiles_t *profile_p;

    /* Find the right media line */
    mca_p = sdp_find_media_level(sdp_p, level);
    if (mca_p == NULL) {
        return (SDP_FAILURE);
    }

    /* Validate params for this media line */
    if ((mca_p->media >= SDP_MAX_MEDIA_TYPES) ||
        (mca_p->port_format >= SDP_MAX_PORT_FORMAT_TYPES) ||
        (mca_p->transport >= SDP_MAX_TRANSPORT_TYPES)) {
        invalid_params = TRUE;
    }

    if (invalid_params == TRUE) {
        SDPLogError(logTag, "%s Invalid params for m= media line, "
                    "build failed.", sdp_p->debug_str);
        sdp_p->conf_p->num_invalid_param++;
        return (SDP_INVALID_PARAMETER);
    }

    /* Build the media type */
    flex_string_sprintf(fs, "m=%s ", sdp_get_media_name(mca_p->media));

    /* Build the port based on the specified port format */
    if (mca_p->port_format == SDP_PORT_NUM_ONLY) {
        if (mca_p->port == SDP_CHOOSE_PARAM) {
            flex_string_sprintf(fs, "$ ");
        } else {
            flex_string_sprintf(fs, "%u ", (unsigned)mca_p->port);
        }
    } else if (mca_p->port_format == SDP_PORT_NUM_COUNT) {
        flex_string_sprintf(fs, "%u/%u ", (unsigned)mca_p->port,
                        (unsigned)mca_p->num_ports);
    } else if (mca_p->port_format == SDP_PORT_VPI_VCI) {
        flex_string_sprintf(fs, "%u/%u ",
                         (unsigned)mca_p->vpi, (unsigned)mca_p->vci);
    } else if (mca_p->port_format == SDP_PORT_VCCI) {
        flex_string_sprintf(fs, "%u ", (unsigned)mca_p->vcci);
    } else if (mca_p->port_format == SDP_PORT_NUM_VPI_VCI) {
        flex_string_sprintf(fs, "%u/%u/%u ", (unsigned)mca_p->port,
                         (unsigned)mca_p->vpi, (unsigned)mca_p->vci);
    } else if (mca_p->port_format == SDP_PORT_VCCI_CID) {
        if ((mca_p->vcci == SDP_CHOOSE_PARAM) &&
            (mca_p->cid == SDP_CHOOSE_PARAM)) {
            flex_string_sprintf(fs, "$/$ ");
        } else if ((mca_p->vcci == SDP_CHOOSE_PARAM) ||
                   (mca_p->cid == SDP_CHOOSE_PARAM)) {
            /* If one is set but not the other, this is an error. */
            SDPLogError(logTag, "%s Invalid params for m= port parameter, "
                        "build failed.", sdp_p->debug_str);
            sdp_p->conf_p->num_invalid_param++;
            return (SDP_INVALID_PARAMETER);
        } else {
            flex_string_sprintf(fs, "%u/%u ",
                             (unsigned)mca_p->vcci, (unsigned)mca_p->cid);
        }
    } else if (mca_p->port_format == SDP_PORT_NUM_VPI_VCI_CID) {
        flex_string_sprintf(fs, "%u/%u/%u/%u ", (unsigned)mca_p->port,
                        (unsigned)mca_p->vpi, (unsigned)mca_p->vci, (unsigned)mca_p->cid);
    }

    /* If the media line has AAL2 profiles, build them differently. */
    if ((mca_p->transport == SDP_TRANSPORT_AAL2_ITU) ||
        (mca_p->transport == SDP_TRANSPORT_AAL2_ATMF) ||
        (mca_p->transport == SDP_TRANSPORT_AAL2_CUSTOM)) {
        profile_p = mca_p->media_profiles_p;
        for (i=0; i < profile_p->num_profiles; i++) {
            flex_string_sprintf(fs, "%s",
                             sdp_get_transport_name(profile_p->profile[i]));

            for (j=0; j < profile_p->num_payloads[i]; j++) {
                flex_string_sprintf(fs, " %u",
                                 (unsigned)profile_p->payload_type[i][j]);
            }
            flex_string_sprintf(fs, " ");
        }
        flex_string_sprintf(fs, "\n");
        if (sdp_p->debug_flag[SDP_DEBUG_TRACE]) {
            SDP_PRINT("%s Built m= media line", sdp_p->debug_str);
        }
        return (SDP_SUCCESS);
    }

    /* Build the transport name */
    flex_string_sprintf(fs, "%s",
                     sdp_get_transport_name(mca_p->transport));

    if(mca_p->transport != SDP_TRANSPORT_DTLSSCTP) {

        /* Build the format lists */
        for (i=0; i < mca_p->num_payloads; i++) {
            if (mca_p->payload_indicator[i] == SDP_PAYLOAD_ENUM) {
                flex_string_sprintf(fs, " %s",
                                 sdp_get_payload_name((sdp_payload_e)mca_p->payload_type[i]));
            } else {
                flex_string_sprintf(fs, " %u", (unsigned)mca_p->payload_type[i]);
            }
        }
    } else {
        /* Add port to SDP if transport is DTLS/SCTP */
        flex_string_sprintf(fs, " %u", (unsigned)mca_p->sctpport);
    }

    flex_string_sprintf(fs, "\r\n");

    if (sdp_p->debug_flag[SDP_DEBUG_TRACE]) {
        SDP_PRINT("%s Built m= media line", sdp_p->debug_str);
    }
    return (SDP_SUCCESS);
}


/* Function:    sdp_parse_payload_types
 * Description: Parse a list of payload types.  The list may be part of
 *              a media line or part of a capability line.
 * Parameters:  sdp_ptr      The SDP handle returned by sdp_init_description.
 *              mca_p        The mca structure the payload types should be
 *                           added to.
 *              ptr          The pointer to the list of payloads.
 * Returns:     Nothing.
 */
void sdp_parse_payload_types (sdp_t *sdp_p, sdp_mca_t *mca_p, const char *ptr)
{
    uint16_t           i;
    uint16_t           num_payloads;
    sdp_result_e  result;
    tinybool      valid_payload;
    char          tmp[SDP_MAX_STRING_LEN];
    char         *tmp2;

    for (num_payloads = 0; (num_payloads < SDP_MAX_PAYLOAD_TYPES); ) {
        ptr = sdp_getnextstrtok(ptr, tmp, sizeof(tmp), " \t", &result);
        if (result != SDP_SUCCESS) {
            /* If there are no more payload types, we're finished */
            break;
        }
        mca_p->payload_type[num_payloads] = (uint16_t)sdp_getnextnumtok(tmp,
                                                        (const char **)&tmp2,
                                                        " \t", &result);
        if (result == SDP_SUCCESS) {
            if ((mca_p->media == SDP_MEDIA_IMAGE) &&
                (mca_p->transport == SDP_TRANSPORT_UDPTL)) {
                sdp_parse_error(sdp_p,
                    "%s Warning: Numeric payload type not "
                    "valid for media %s with transport %s.",
                    sdp_p->debug_str,
                    sdp_get_media_name(mca_p->media),
                    sdp_get_transport_name(mca_p->transport));
            } else {
                mca_p->payload_indicator[num_payloads] = SDP_PAYLOAD_NUMERIC;
                mca_p->num_payloads++;
                num_payloads++;
            }
            continue;
        }

        valid_payload = FALSE;
        for (i=0; i < SDP_MAX_STRING_PAYLOAD_TYPES; i++) {
            if (cpr_strncasecmp(tmp, sdp_payload[i].name,
                            sdp_payload[i].strlen) == 0) {
                valid_payload = TRUE;
                break;
            }
        }
        if (valid_payload == TRUE) {
            /* We recognized the payload type.  Make sure it
             * is valid for this media line. */
            valid_payload = FALSE;
            if ((mca_p->media == SDP_MEDIA_IMAGE) &&
                (mca_p->transport == SDP_TRANSPORT_UDPTL) &&
                (i == SDP_PAYLOAD_T38)) {
                valid_payload = TRUE;
            } else if ((mca_p->media == SDP_MEDIA_APPLICATION) &&
                       (mca_p->transport == SDP_TRANSPORT_UDP) &&
                       (i == SDP_PAYLOAD_XTMR)) {
                valid_payload = TRUE;
            } else if ((mca_p->media == SDP_MEDIA_APPLICATION) &&
                       (mca_p->transport == SDP_TRANSPORT_TCP) &&
                       (i == SDP_PAYLOAD_T120)) {
                valid_payload = TRUE;
            }

            if (valid_payload == TRUE) {
                mca_p->payload_indicator[num_payloads] = SDP_PAYLOAD_ENUM;
                mca_p->payload_type[num_payloads] = i;
                mca_p->num_payloads++;
                num_payloads++;
            } else {
                sdp_parse_error(sdp_p,
                    "%s Warning: Payload type %s not valid for "
                    "media %s with transport %s.",
                    sdp_p->debug_str,
                    sdp_get_payload_name((sdp_payload_e)i),
                    sdp_get_media_name(mca_p->media),
                    sdp_get_transport_name(mca_p->transport));
            }
        } else {
            /* Payload type wasn't recognized. */
            sdp_parse_error(sdp_p,
                "%s Warning: Payload type "
                "unsupported (%s).", sdp_p->debug_str, tmp);
        }
    }
    if (mca_p->num_payloads == 0) {
        sdp_parse_error(sdp_p,
            "%s Warning: No payload types specified.",
            sdp_p->debug_str);
    }
}

