/* 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 <limits.h>
#include <ctype.h>
#include "sdp_os_defs.h"
#include "sipcc_sdp.h"
#include "sdp_private.h"
#include "sdp_log.h"

#define MKI_BUF_LEN 4

static const char* logTag = "sdp_utils";

// Actually checks for ASCII only unlike isdigit() on Windows.
// Also avoids UB issues with isdigit() sign extension when
// char is signed.
int sdp_is_ascii_digit(const char c) {
  return '0' <= c && c <= '9';
}

sdp_mca_t *sdp_alloc_mca (uint32_t line) {
    sdp_mca_t           *mca_p;

    /* Allocate resource for new media stream. */
    mca_p = (sdp_mca_t *)SDP_MALLOC(sizeof(sdp_mca_t));
    if (mca_p == NULL) {
        return (NULL);
    }
    /* Initialize mca structure */
    mca_p->media              = SDP_MEDIA_INVALID;
    mca_p->conn.nettype       = SDP_NT_INVALID;
    mca_p->conn.addrtype      = SDP_AT_INVALID;
    mca_p->conn.conn_addr[0]  = '\0';
    mca_p->conn.is_multicast  = FALSE;
    mca_p->conn.ttl           = 0;
    mca_p->conn.num_of_addresses = 0;
    mca_p->transport          = SDP_TRANSPORT_INVALID;
    mca_p->port               = SDP_INVALID_VALUE;
    mca_p->num_ports          = SDP_INVALID_VALUE;
    mca_p->vpi                = SDP_INVALID_VALUE;
    mca_p->vci                = 0;
    mca_p->vcci               = SDP_INVALID_VALUE;
    mca_p->cid                = SDP_INVALID_VALUE;
    mca_p->num_payloads       = 0;
    mca_p->sessinfo_found     = FALSE;
    mca_p->encrypt.encrypt_type  = SDP_ENCRYPT_INVALID;
    mca_p->media_attrs_p      = NULL;
    mca_p->next_p             = NULL;
    mca_p->mid                = 0;
    mca_p->bw.bw_data_count   = 0;
    mca_p->bw.bw_data_list    = NULL;
    mca_p->line_number        = line;
    mca_p->sctp_fmt           = SDP_SCTP_MEDIA_FMT_UNKNOWN;

    return (mca_p);
}

/*
 * next_token
 *
 * copy token param with chars from str until null, cr, lf, or one of the delimiters is found.
 * delimiters at the beginning will be skipped.
 * The pointer *string_of_tokens is moved forward to the next token on sucess.
 *
 */
static sdp_result_e next_token(const char **string_of_tokens, char *token, unsigned token_max_len, const char *delim)
{
  int flag2moveon = 0;
  const char *str;
  const char *token_end;
  const char *next_delim;

  if (!string_of_tokens || !*string_of_tokens || !token || !delim) {
    return SDP_FAILURE;
  }

  str = *string_of_tokens;
  token_end = token + token_max_len - 1;

  /* Locate front of token, skipping any delimiters */
  for ( ; ((*str != '\0') && (*str != '\n') && (*str != '\r')); str++) {
    flag2moveon = 1;  /* Default to move on unless we find a delimiter */
    for (next_delim=delim; *next_delim; next_delim++) {
      if (*str == *next_delim) {
        flag2moveon = 0;
        break;
      }
    }
    if( flag2moveon ) {
      break;  /* We're at the beginning of the token */
    }
  }

  /* Make sure there's really a token present. */
  if ((*str == '\0') || (*str == '\n') || (*str == '\r')) {
    return SDP_EMPTY_TOKEN;
  }

  /* Now locate end of token */
  flag2moveon = 0;

  while ((token < token_end) &&
         (*str != '\0') && (*str != '\n') && (*str != '\r')) {
      for (next_delim=delim; *next_delim; next_delim++) {
          if (*str == *next_delim) {
              flag2moveon = 1;
              break;
          }
      }
      if( flag2moveon ) {
          break;
      } else {
          *token++ = *str++;
      }
  }

  /* mark end of token */
  *token = '\0';

  /* set the string of tokens to the next token */
  *string_of_tokens = str;

  return SDP_SUCCESS;
}

/*
 * verify_sdescriptions_mki
 *
 * Verifies the syntax of the MKI parameter.
 *
 * mki            = mki-value ":" mki-length
 * mki-value      = 1*DIGIT
 * mki-length     = 1*3DIGIT   ; range 1..128
 *
 * Inputs:
 *   buf      - ptr to start of MKI string assumes NULL
 *              terminated string
 *   mkiValue - buffer to store the MKI value, assumes calling
 *              function has provided memory for this.
 *   mkiLen   - integer to store the MKI length
 *
 * Outputs:
 *   Returns TRUE if syntax is correct and stores the
 *   MKI value in mkiVal and stores the length in mkiLen.
 *   Returns FALSE otherwise.
 */

tinybool
verify_sdescriptions_mki (char *buf, char *mkiVal, uint16_t *mkiLen)
{

    char       *ptr,
               mkiValBuf[SDP_SRTP_MAX_MKI_SIZE_BYTES],
               mkiLenBuf[MKI_BUF_LEN];
    int        idx = 0;
    unsigned long strtoul_result;
    char *strtoul_end;

    ptr = buf;
    /* MKI must begin with a digit */
    if (!ptr || (!sdp_is_ascii_digit(*ptr))) {
        return FALSE;
    }

    /* scan until we reach a non-digit or colon */
    while (*ptr) {
        if (*ptr == ':') {
            /* terminate the MKI value */
            mkiValBuf[idx] = 0;
            ptr++;
            break;
        } else if ((sdp_is_ascii_digit(*ptr) && (idx < SDP_SRTP_MAX_MKI_SIZE_BYTES-1))) {
             mkiValBuf[idx++] = *ptr;
        } else {
             return FALSE;
        }

        ptr++;
    }

    /* there has to be a mki length */
    if (*ptr == 0) {
        return FALSE;
    }

    idx = 0;

    /* verify the mki length (max 3 digits) */
    while (*ptr) {
        if (sdp_is_ascii_digit(*ptr) && (idx < 3)) {
            mkiLenBuf[idx++] = *ptr;
        } else {
            return FALSE;
        }

        ptr++;
    }

    mkiLenBuf[idx] = 0;

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

    /* mki len must be between 1..128 */
    if (errno || mkiLenBuf == strtoul_end || strtoul_result < 1 || strtoul_result > 128) {
      *mkiLen = 0;
      return FALSE;
    }

    *mkiLen = (uint16_t) strtoul_result;
    sstrncpy(mkiVal, mkiValBuf, MKI_BUF_LEN);

    return TRUE;
}

/*
 * verify_srtp_lifetime
 *
 * Verifies the Lifetime parameter syntax.
 *
 *  lifetime = ["2^"] 1*(DIGIT)
 *
 * Inputs:
 *   buf - pointer to start of lifetime string. Assumes string is
 *         NULL terminated.
 * Outputs:
 *   Returns TRUE if syntax is correct. Returns FALSE otherwise.
 */

tinybool
verify_sdescriptions_lifetime (char *buf)
{

    char     *ptr;
    tinybool tokenFound = FALSE;

    ptr = buf;
    if (!ptr || *ptr == 0) {
        return FALSE;
    }

    while (*ptr) {
        if (*ptr == '^') {
            if (tokenFound) {
                /* make sure we don't have multiple ^ */
                return FALSE;
            } else {
                tokenFound = TRUE;
                /* Lifetime is in power of 2 format, make sure first and second
                 * chars are 2^
                 */

                if (buf[0] != '2' || buf[1] != '^') {
                    return FALSE;
                }
            }
        } else if (!sdp_is_ascii_digit(*ptr)) {
                   return FALSE;
        }

        ptr++;

    }

    /* Make sure if the format is 2^ that there is a number after the ^. */
    if (tokenFound) {
        if (strlen(buf) <= 2) {
            return FALSE;
        }
    }

    return TRUE;
}


/*
 * sdp_validate_maxprate
 *
 * This function validates that the string passed in is of the form:
 * packet-rate = 1*DIGIT ["." 1*DIGIT]
 */
tinybool
sdp_validate_maxprate(const char *string_parm)
{
    tinybool retval = FALSE;

    if (string_parm && (*string_parm)) {
        while (sdp_is_ascii_digit(*string_parm)) {
            string_parm++;
        }

        if (*string_parm == '.') {
            string_parm++;
            while (sdp_is_ascii_digit(*string_parm)) {
                string_parm++;
            }
        }

        if (*string_parm == '\0') {
            retval = TRUE;
        } else {
            retval = FALSE;
        }
    }

    return retval;
}

char *sdp_findchar (const char *ptr, char *char_list)
{
    int i;

    for (;*ptr != '\0'; ptr++) {
        for (i=0; char_list[i] != '\0'; i++) {
            if (*ptr == char_list[i]) {
                return ((char *)ptr);
            }
        }
    }
    return ((char *)ptr);
}

/* Locate the next token in a line.  The delim characters are passed in
 * as a param.  The token also will not go past a new line char or the
 * end of the string.  Skip any delimiters before the token.
 */
const char *sdp_getnextstrtok (const char *str, char *tokenstr, unsigned tokenstr_len,
                         const char *delim, sdp_result_e *result)
{
  const char *token_list = str;

  if (!str || !tokenstr || !delim || !result) {
    if (result) {
      *result = SDP_FAILURE;
    }
    return str;
  }

  *result = next_token(&token_list, tokenstr, tokenstr_len, delim);

  return token_list;
}



/* Locate the next null ("-") or numeric token in a string.  The delim
 * characters are passed in as a param.  The token also will not go past
 * a new line char or the end of the string.  Skip any delimiters before
 * the token.
 */
uint32_t sdp_getnextnumtok_or_null (const char *str, const char **str_end,
                               const char *delim, tinybool *null_ind,
                               sdp_result_e *result)
{
  const char *token_list = str;
  char temp_token[SDP_MAX_STRING_LEN];
  char *strtoul_end;
  unsigned long numval;

  if (null_ind) {
    *null_ind = FALSE;
  }

  if (!str || !str_end || !delim || !null_ind || !result) {
    if (result) {
      *result = SDP_FAILURE;
    }
    return 0;
  }

  *result = next_token(&token_list, temp_token, sizeof(temp_token), delim);

  if (*result != SDP_SUCCESS) {
    return 0;
  }

  /* First see if its the null char ("-") */
  if (temp_token[0] == '-') {
      *null_ind = TRUE;
      *result = SDP_SUCCESS;
      *str_end = str;
      return 0;
  }

  errno = 0;
  numval = strtoul(temp_token, &strtoul_end, 10);

  if (errno || strtoul_end == temp_token || numval > UINT_MAX) {
    *result = SDP_FAILURE;
    return 0;
  }

  *result = SDP_SUCCESS;
  *str_end = token_list;
  return (uint32_t) numval;
}


/* Locate the next numeric token in a string.  The delim characters are
 * passed in as a param.  The token also will not go past a new line char
 * or the end of the string.  Skip any delimiters before the token.
 */
uint32_t sdp_getnextnumtok (const char *str, const char **str_end,
                       const char *delim, sdp_result_e *result)
{
  const char *token_list = str;
  char temp_token[SDP_MAX_STRING_LEN];
  char *strtoul_end;
  unsigned long numval;

  if (!str || !str_end || !delim || !result) {
    if (result) {
      *result = SDP_FAILURE;
    }
    return 0;
  }

  *result = next_token(&token_list, temp_token, sizeof(temp_token), delim);

  if (*result != SDP_SUCCESS) {
    return 0;
  }

  errno = 0;
  numval = strtoul(temp_token, &strtoul_end, 10);

  if (errno || strtoul_end == temp_token || numval > UINT_MAX) {
    *result = SDP_FAILURE;
    return 0;
  }

  *result = SDP_SUCCESS;
  *str_end = token_list;
  return (uint32_t) numval;
}


/*
 * SDP Crypto Utility Functions.
 *
 * First a few common definitions.
 */

/*
 * Constants
 *
 * crypto_string = The string used to identify the start of sensative
 *	crypto data.
 *
 * inline_string = The string used to identify the start of key/salt
 *	crypto data.
 *
 * star_string = The string used to overwrite sensative data.
 *
 * '*_strlen' = The length of '*_string' in bytes (not including '\0')
 */
static const char crypto_string[] = "X-crypto:";
static const int crypto_strlen = sizeof(crypto_string) - 1;
static const char inline_string[] = "inline:";
static const int inline_strlen = sizeof(inline_string) - 1;
/* 40 characters is the current maximum for a Base64 encoded key/salt */
static const char star_string[] = "****************************************";
static const int star_strlen = sizeof(star_string) - 1;

/*
 * MIN_CRYPTO_STRING_SIZE_BYTES = This value defines the minimum
 *	size of a string that could contain a key/salt. This value
 *	is used to skip out of parsing when there is no reasonable
 *	assumption that sensative data will be found. The general
 *	format of a SRTP Key Salt in SDP looks like:
 *
 * X-crypto:<crypto_suite_name> inline:<master_key_salt>||
 *
 *	if <crypto_suite_name> and <master_key_salt> is at least
 *	one character and one space is used before the "inline:",
 *	then this translates to a size of (aligned by collumn from
 *	the format shown above):
 *
 * 9+       1+                 1+7+    1+                2 = 21
 *
 */
#define MIN_CRYPTO_STRING_SIZE_BYTES 21

/*
 * Utility macros
 *
 * CHAR_IS_WHITESPACE = macro to determine if the passed _test_char
 *	is whitespace.
 *
 * SKIP_WHITESPACE = Macro to advance _cptr to the next non-whitespace
 *	character. _cptr will not be advanced past _max_cptr.
 *
 * FIND_WHITESPACE = Macro to advance _cptr until whitespace is found.
 *	_cptr will not be advanced past _max_cptr.
 */
#define CHAR_IS_WHITESPACE(_test_char) \
    ((((_test_char)==' ')||((_test_char)=='\t'))?1:0)

#define SKIP_WHITESPACE(_cptr, _max_cptr)           \
    while ((_cptr)<=(_max_cptr)) {                  \
        if (!CHAR_IS_WHITESPACE(*(_cptr))) break;   \
        (_cptr)++;                                  \
    }

#define FIND_WHITESPACE(_cptr, _max_cptr)           \
    while ((_cptr)<=(_max_cptr)) {                  \
        if (CHAR_IS_WHITESPACE(*(_cptr))) break;    \
        (_cptr)++;                                  \
    }

/* Function:    sdp_crypto_debug
 * Description: Check the passed buffer for sensitive data that should
 *		not be output (such as SRTP Master Key/Salt) and output
 *		the buffer as debug. Sensitive data will be replaced
 *		with the '*' character(s). This function may be used
 *		to display very large buffers so this function ensures
 *		that buginf is not overloaded.
 * Parameters:  buffer		pointer to the message buffer to filter.
 *              length_bytes	size of message buffer in bytes.
 * Returns:     Nothing.
 */
void sdp_crypto_debug (char *buffer, ulong length_bytes)
{
    char *current, *start;
    char *last = buffer + length_bytes;
    int result;

    /*
     * For SRTP Master Key/Salt has the form:
     * X-crypto:<crypto_suite_name> inline:<master_key_salt>||
     * Where <master_key_salt> is the data to elide (filter).
     */
    for (start=current=buffer;
         current<=last-MIN_CRYPTO_STRING_SIZE_BYTES;
         current++) {
        if ((*current == 'x') || (*current == 'X')) {
            result = cpr_strncasecmp(current, crypto_string, crypto_strlen);
            if (!result) {
                current += crypto_strlen;
                if (current > last) break;

                /* Skip over crypto suite name */
                FIND_WHITESPACE(current, last);

                /* Skip over whitespace */
                SKIP_WHITESPACE(current, last);

                /* identify inline keyword */
                result = cpr_strncasecmp(current, inline_string, inline_strlen);
                if (!result) {
                    int star_count = 0;

                    current += inline_strlen;
                    if (current > last) break;

                    sdp_dump_buffer(start, current - start);

                    /* Hide sensitive key/salt data */
                    while (current<=last) {
                        if (*current == '|' || *current == '\n') {
                            /* Done, print the stars */
                            while (star_count > star_strlen) {
                                /*
                                 * This code is only for the case where
                                 * too much base64 data was supplied
                                 */
                                sdp_dump_buffer((char*)star_string, star_strlen);
                                star_count -= star_strlen;
                            }
                            sdp_dump_buffer((char*)star_string, star_count);
                            break;
                        } else {
                            star_count++;
                            current++;
                        }
                    }
                    /* Update start pointer */
                    start=current;
                }
            }
        }
    }

    if (last > start) {
        /* Display remainder of buffer */
        sdp_dump_buffer(start, last - start);
    }
}

/*
 * sdp_debug_msg_filter
 *
 * DESCRIPTION
 *     Check the passed message buffer for sensitive data that should
 *     not be output (such as SRTP Master Key/Salt). Sensitive data
 *     will be replaced with the '*' character(s).
 *
 * PARAMETERS
 *     buffer: pointer to the message buffer to filter.
 *
 *     length_bytes: size of message buffer in bytes.
 *
 * RETURN VALUE
 *     The buffer modified.
 */
char * sdp_debug_msg_filter (char *buffer, ulong length_bytes)
{
    char *current;
    char *last = buffer + length_bytes;
    int result;

    SDP_PRINT("\n%s:%d: Eliding sensitive data from debug output",
            __FILE__, __LINE__);
    /*
     * For SRTP Master Key/Salt has the form:
     * X-crypto:<crypto_suite_name> inline:<master_key_salt>||
     * Where <master_key_salt> is the data to elide (filter).
     */
    for (current=buffer;
         current<=last-MIN_CRYPTO_STRING_SIZE_BYTES;
         current++) {
        if ((*current == 'x') || (*current == 'X')) {
            result = cpr_strncasecmp(current, crypto_string, crypto_strlen);
            if (!result) {
                current += crypto_strlen;
                if (current > last) break;

                /* Skip over crypto suite name */
                FIND_WHITESPACE(current, last);

                /* Skip over whitespace */
                SKIP_WHITESPACE(current, last);

                /* identify inline keyword */
                result = cpr_strncasecmp(current, inline_string, inline_strlen);
                if (!result) {
                    current += inline_strlen;
                    if (current > last) break;

                    /* Hide sensitive key/salt data */
                    while (current<=last) {
                        if (*current == '|' || *current == '\n') {
                            /* Done */
                            break;
                        } else {
                            *current = '*';
                            current++;
                        }
                    }
                }
            }
        }
    }

    return buffer;
}


/* Function:    sdp_checkrange
 * Description: This checks the range of a ulong value to make sure its
 *              within the range of 0 and 4Gig. stroul cannot be used since
 *              for values greater greater than 4G, stroul will either wrap
 *              around or return ULONG_MAX.
 * Parameters:  sdp_p       Pointer to the sdp structure
 *              num         The number to check the range for
 *              u_val       This variable get populated with the ulong value
 *                          if the number is within the range.
 * Returns:     tinybool - returns TRUE if the number passed is within the
 *                         range, FALSE otherwise
 */
tinybool sdp_checkrange (sdp_t *sdp_p, char *num, ulong *u_val)
{
    ulong l_val;
    char *endP = NULL;
    *u_val = 0;

    if (!num || !*num) {
        return FALSE;
    }

    if (*num == '-') {
        if (sdp_p->debug_flag[SDP_DEBUG_ERRORS]) {
            SDPLogError(logTag, "%s ERROR: Parameter value is a negative number: %s",
                      sdp_p->debug_str, num);
        }
        return FALSE;
    }

    l_val = strtoul(num, &endP, 10);
    if (*endP == '\0') {

        if (l_val > 4294967295UL) {
            if (sdp_p->debug_flag[SDP_DEBUG_ERRORS]) {
                SDPLogError(logTag, "%s ERROR: Parameter value: %s is greater than 4294967295",
                          sdp_p->debug_str, num);
            }
            return FALSE;
        }

        if (l_val == 4294967295UL) {
            /*
             * On certain platforms where ULONG_MAX is equivalent to
             * 4294967295, strtoul will return ULONG_MAX even if the the
             * value of the string is greater than 4294967295. To detect
             * that scenario we make an explicit check here.
             */
            if (strcmp("4294967295", num)) {
                if (sdp_p->debug_flag[SDP_DEBUG_ERRORS]) {
                    SDPLogError(logTag, "%s ERROR: Parameter value: %s is greater than 4294967295",
                              sdp_p->debug_str, num);
                }
                return FALSE;
            }
        }
    }
    *u_val = l_val;
    return TRUE;
}

#undef CHAR_IS_WHITESPACE
#undef SKIP_WHITESPACE
#undef FIND_WHITESPACE
