/*------------------------------------------------------------------
 * strtok_s.c
 *
 * October 2008, Bo Berry
 *
 * Copyright (c) 2008-2011 by Cisco Systems, Inc
 * All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *------------------------------------------------------------------
 */

#include "safeclib_private.h"
#include "safe_str_constraint.h"
#include "safe_str_lib.h"


/**
 * NAME
 *    strtok_s
 *
 * SYNOPSIS
 *    #include "safe_str_lib.h"
 *    char *
 *    strtok_s(char *dest, rsize_t *dmax, char *src, char **ptr)
 *
 * DESCRIPTION
 *    A sequence of calls to the strtok_s function breaks the string
 *    pointed to by dest into a sequence of tokens, each of which is
 *    delimited by a character from the string pointed to by src. The
 *    fourth argument points to a caller-provided char pointer into
 *    which the strtok_s function stores information necessary for
 *    it to continue scanning the same string.
 *
 *    The first call in a sequence has a non-null first argument and
 *    dmax points to an object whose value is the number of elements
 *    in the character array pointed to by the first argument. The
 *    first call stores an initial value in the object pointed to by
 *    ptr and updates the value pointed to by dmax to reﬂect the
 *    number of elements that remain in relation to ptr. Subsequent
 *    calls in the sequence have a null first argument and the objects
 *    pointed to by dmax and ptr are required to have the values
 *    stored by the previous call in the sequence, which are then
 *    updated. The separator string pointed to by src may be different
 *    from call to call.
 *
 *    The first call in the sequence searches the string pointed to
 *    by dest for the first character that is not contained in the
 *    current separator string pointed to by src. If no such character
 *    is found, then there are no tokens in the string pointed to
 *    by dest and the strtok_s function returns a null pointer. If
 *    such a character is found, it is the start of the first token.
 *
 *    The strtok_s function then searches from there for the first
 *    character in dest that is contained in the current separator
 *    string. If no such character is found, the current token
 *    extends to the end of the string pointed to by dest, and
 *    subsequent searches in the same string for a token return
 *    a null pointer. If such a character is found, it is
 *    overwritten by a null character, which terminates the
 *    current token.
 *
 *    In all cases, the strtok_s function stores sufﬁcient information
 *    in the pointer pointed to by ptr so that subsequent calls,
 *    with a null pointer for dest and the unmodiﬁed pointer value
 *    for ptr, shall start searching just past the element overwritten
 *    by a null character (if any).
 *
 * SPECIFIED IN
 *    ISO/IEC TR 24731-1, Programming languages, environments
 *    and system software interfaces, Extensions to the C Library,
 *    Part I: Bounds-checking interfaces
 *
 * INPUT PARAMETERS
 *    dest      pointer to string to tokenize
 *
 *    dmax      restricted maximum length of dest string
 *
 *    src       pointer to delimiter string (len < 255)
 *
 *    ptr       returned pointer to token
 *
 * OUTPUT PARAMETERS
 *    dmax      update length
 *
 *    ptr       update pointer to token
 *
 * RUNTIME CONSTRAINTS
 *    src shall not be a null pointer.
 *    ptr shall not be a null pointer.
 *    dmax shall not be a null pointer.
 *    *dmax shall not be 0.
 *
 *    If dest is a null pointer, then *ptr shall not be a null pointer.
 *
 *    dest must not be unterminated.
 *
 *    The value of *dmax shall not be greater than RSIZE_MAX_STR. The
 *    end of the token found shall occur within the first *dmax
 *    characters of dest for the first call, and shall occur within
 *    the first *dmax characters of where searching resumes on
 *    subsequent calls.
 *
 * RETURN VALUE
 *     The strtok_s function returns a pointer to the first character
 *     of a token; or a null pointer if there is no token or there
 *     is a runtime-constraint violation.
 *
 *     EOK
 *     ESNULLP     NULL pointer
 *     ESZEROL     zero length
 *     ESLEMAX     length exceeds max limit
 *     ESUNTERM    unterminated string
 *
 * EXAMPLES
 * [1] Sequencial strtok_s() calls to tokenize a string
 *
 *    String to tokenize str1 = ",.:*one,two;three,;four*.*.five-six***"
 *           len=38
 *    String of delimiters str2 = ",.;*"
 *
 *    p2tok = strtok_s(str1, &len, str2, &p2str);
 *    token -one-  remaining -two;three,;four*.*.five-six***- len=30
 *
 *    p2tok = strtok_s(NULL, &len, str2, &p2str);
 *    token -two-  remaining -three,;four*.*.five-six***- len=26
 *
 *    p2tok = strtok_s(NULL, &len, str2, &p2str);
 *    token -three-  remaining -;four*.*.five-six***- len=20
 *
 *    p2tok = strtok_s(NULL, &len, str2, &p2str);
 *    token -four-  remaining -.*.five-six***- len=14
 *
 *    p2tok = strtok_s(NULL, &len, str2, &p2str);
 *    token -five-six-  remaining -**- len=2
 *
 *    p2tok = strtok_s(NULL, &len, str2, &p2str);
 *    token -(null)-  remaining -**- len=0
 *
 *
 * [2] While loop with same entry data as [1]
 *
 *     p2tok = str1;
 *     while (p2tok && len) {
 *         p2tok = strtok_s(NULL, &len, str2, &p2str);
 *         printf("  token --   remaining --  len=0 \n",
 *                 p2tok, p2str, (int)len );
 *     }
 *
 *-
 */
char *
strtok_s(char *dest, rsize_t *dmax, const char *src, char **ptr)
{

/*
 * CONFIGURE: The spec does not call out a maximum for the src
 * string, so one is defined here.
 */
#define  STRTOK_DELIM_MAX_LEN   ( 16 )


    const char *pt;
    char *ptoken;
    rsize_t dlen;
    rsize_t slen;

    if (dmax == NULL) {
        invoke_safe_str_constraint_handler("strtok_s: dmax is NULL",
                   NULL, ESNULLP);
        return (NULL);
    }

    if (*dmax == 0) {
        invoke_safe_str_constraint_handler("strtok_s: dmax is 0",
                   NULL, ESZEROL);
        return (NULL);
    }

    if (*dmax > RSIZE_MAX_STR) {
        invoke_safe_str_constraint_handler("strtok_s: dmax exceeds max",
                   NULL, ESLEMAX);
        return (NULL);
    }

    if (src == NULL) {
        invoke_safe_str_constraint_handler("strtok_s: src is null",
                   NULL, ESNULLP);
        return (NULL);
    }

    if (ptr == NULL) {
        invoke_safe_str_constraint_handler("strtok_s: ptr is null",
                   NULL, ESNULLP);
        return (NULL);
    }

    /* if the source was NULL, use the tokenizer context */
    if (dest == NULL) {
        dest = *ptr;
    }

    /*
     * scan dest for a delimiter
     */
    dlen = *dmax;
    ptoken = NULL;
    while (*dest != '\0' && !ptoken) {

        if (dlen == 0) {
            *ptr = NULL;
            invoke_safe_str_constraint_handler(
                      "strtok_s: dest is unterminated",
                       NULL, ESUNTERM);
            return (NULL);
        }

        /*
         * must scan the entire delimiter list
         * ISO should have included a delimiter string limit!!
         */
        slen = STRTOK_DELIM_MAX_LEN;
        pt = src;
        while (*pt != '\0') {

            if (slen == 0) {
                *ptr = NULL;
                invoke_safe_str_constraint_handler(
                          "strtok_s: src is unterminated",
                           NULL, ESUNTERM);
                return (NULL);
            }
            slen--;

            if (*dest == *pt) {
                ptoken = NULL;
                break;
            } else {
                pt++;
                ptoken = dest;
            }
        }
        dest++;
        dlen--;
    }

    /*
     * if the beginning of a token was not found, then no
     * need to continue the scan.
     */
    if (ptoken == NULL) {
        *dmax = dlen;
        return (ptoken);
    }

    /*
     * Now we need to locate the end of the token
     */
    while (*dest != '\0') {

        if (dlen == 0) {
            *ptr = NULL;
            invoke_safe_str_constraint_handler(
                      "strtok_s: dest is unterminated",
                       NULL, ESUNTERM);
            return (NULL);
        }

        slen = STRTOK_DELIM_MAX_LEN;
        pt = src;
        while (*pt != '\0') {

            if (slen == 0) {
                *ptr = NULL;
                invoke_safe_str_constraint_handler(
                          "strtok_s: src is unterminated",
                           NULL, ESUNTERM);
                return (NULL);
            }
            slen--;

            if (*dest == *pt) {
                /*
                 * found a delimiter, set to null
                 * and return context ptr to next char
                 */
                *dest = '\0';
                *ptr = (dest + 1);  /* return pointer for next scan */
                *dmax = dlen - 1;   /* account for the nulled delimiter */
                return (ptoken);
            } else {
                /*
                 * simply scanning through the delimiter string
                 */
                pt++;
            }
        }
        dest++;
        dlen--;
    }

    *dmax = dlen;
    return (ptoken);
}
