/*
 * Copyright (c) 2000-2016 Apple Inc. All rights reserved.
 *
 * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. The rights granted to you under the License
 * may not be used to create, or enable the creation or redistribution of,
 * unlawful or unlicensed copies of an Apple operating system, or to
 * circumvent, violate, or enable the circumvention or violation of, any
 * terms of an Apple operating system software license agreement.
 * 
 * Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
 */
#ifdef KERNEL
#include <sys/systm.h>
#include <libkern/OSKextLib.h>
#include <libkern/OSKextLibPrivate.h>
#else
#include <libc.h>
#include <libkern/OSKextLib.h>
#include <System/libkern/OSKextLibPrivate.h>
#endif /* KERNEL */

#include <libkern/OSKextLibPrivate.h>

#define VERS_MAJOR_DIGITS        (4)
#define VERS_MINOR_DIGITS        (4)
#define VERS_REVISION_DIGITS     (4)
#define VERS_STAGE_DIGITS        (1)
#define VERS_STAGE_LEVEL_DIGITS  (3)

#define VERS_MAJOR_MAX           (9999)
#define VERS_STAGE_LEVEL_MAX      (255)

#define VERS_MAJOR_MULT  (1000000000000)
#define VERS_MINOR_MULT      (100000000)
#define VERS_REVISION_MULT       (10000)
#define VERS_STAGE_MULT           (1000)


typedef enum {
    kOSKextVersionStageInvalid     = 0,
    kOSKextVersionStageDevelopment = 1,
    kOSKextVersionStageAlpha       = 3,
    kOSKextVersionStageBeta        = 5,
    kOSKextVersionStageCandidate   = 7,
    kOSKextVersionStageRelease     = 9,
} OSKextVersionStage;


/*********************************************************************
*********************************************************************/
static int __vers_isdigit(char c) {
    return (c == '0' ||
        c == '1' || c == '2' || c == '3' ||
        c == '4' || c == '5' || c == '6' ||
        c == '7' || c == '8' || c == '9');
}

/*********************************************************************
*********************************************************************/
static int __vers_isspace(char c) {
    return (c == ' ' ||
        c == '\t' ||
        c == '\r' ||
        c == '\n');
}

/*********************************************************************
*********************************************************************/
static int
__vers_digit_for_char(char c) {
	switch (c) {
		case '0': return 0;
		case '1': return 1;
		case '2': return 2;
		case '3': return 3;
		case '4': return 4;
		case '5': return 5;
		case '6': return 6;
		case '7': return 7;
		case '8': return 8;
		case '9': return 9;
		default:  return -1;
	}
}

/*********************************************************************
*********************************************************************/
static int __VERS_isreleasestate(char c) {
    return (c == 'd' || c == 'a' || c == 'b' || c == 'f');
}


/*********************************************************************
*********************************************************************/
static OSKextVersionStage __OSKextVersionStageForString(const char ** string_p) {
    const char * string;

    if (!string_p || !*string_p) {
        return kOSKextVersionStageInvalid;
    }

    string = *string_p;

    if (__vers_isspace(string[0]) || string[0] == '\0') {
        return kOSKextVersionStageRelease;
    } else {
        switch (string[0]) {
          case 'd':
              if (__vers_isdigit(string[1])) {
                  *string_p = &string[1];
                  return kOSKextVersionStageDevelopment;
              }
              break;
          case 'a':
              if (__vers_isdigit(string[1])) {
                  *string_p = &string[1];
                  return kOSKextVersionStageAlpha;
              }
              break;
          case 'b':
              if (__vers_isdigit(string[1])) {
                  *string_p = &string[1];
                  return kOSKextVersionStageBeta;
              }
              break;
          case 'f':
              if (__vers_isdigit(string[1])) {
                  *string_p = &string[1];
                  return kOSKextVersionStageCandidate;
              } else if (string[1] == 'c' && __vers_isdigit(string[2])) {
                  *string_p = &string[2];
                  return kOSKextVersionStageCandidate;
              } else {
                  return kOSKextVersionStageInvalid;
              }
          default:
              return kOSKextVersionStageInvalid;
        }
    }

    return kOSKextVersionStageInvalid;
}

/*********************************************************************
*********************************************************************/
static const char *
__OSKextVersionStringForStage(OSKextVersionStage stage)
{
	switch (stage) {
		case kOSKextVersionStageInvalid:     return NULL;
		case kOSKextVersionStageDevelopment: return "d";
		case kOSKextVersionStageAlpha:       return "a";
		case kOSKextVersionStageBeta:        return "b";
		case kOSKextVersionStageCandidate:   return "f";
		case kOSKextVersionStageRelease:     return "";
	}
}

/*********************************************************************
*********************************************************************/
OSKextVersion OSKextParseVersionString(const char * versionString)
{
    OSKextVersion   result             = -1;
    int             vers_digit         = -1;
    int             num_digits_scanned = 0;
    OSKextVersion   vers_major         = 0;
    OSKextVersion   vers_minor         = 0;
    OSKextVersion   vers_revision      = 0;
    OSKextVersion   vers_stage         = 0;
    OSKextVersion   vers_stage_level   = 0;
    const char    * current_char_p;

    if (!versionString || *versionString == '\0') {
        return -1;
    }

    current_char_p = (const char *)&versionString[0];

   /*****
    * Check for an initial digit of the major release number.
    */
    vers_major = __vers_digit_for_char(*current_char_p);
    if (vers_major < 0) {
        return -1;
    }

    current_char_p++;
    num_digits_scanned = 1;

   /* Complete scan for major version number. Legal characters are
    * any digit, period, any buildstage letter.
    */
    while (num_digits_scanned < VERS_MAJOR_DIGITS) {
        if (__vers_isspace(*current_char_p) || *current_char_p == '\0') {
            vers_stage = kOSKextVersionStageRelease;
            goto finish;
        } else if (__vers_isdigit(*current_char_p)) {
            vers_digit = __vers_digit_for_char(*current_char_p);
            if (vers_digit < 0) {
                return -1;
            }
            vers_major = (vers_major) * 10 + vers_digit;
            current_char_p++;
            num_digits_scanned++;
        } else if (__VERS_isreleasestate(*current_char_p)) {
            goto release_state;
        } else if (*current_char_p == '.') {
            current_char_p++;
            goto minor_version;
        } else {
            return -1;
        }
    }

   /* Check for too many digits.
    */
    if (num_digits_scanned == VERS_MAJOR_DIGITS) {
         if (*current_char_p == '.') {
            current_char_p++;
        } else if (__vers_isdigit(*current_char_p)) {
            return -1;
        }
    }

minor_version:

    num_digits_scanned = 0;

   /* Scan for minor version number. Legal characters are
    * any digit, period, any buildstage letter.
    */
    while (num_digits_scanned < VERS_MINOR_DIGITS) {
        if (__vers_isspace(*current_char_p) || *current_char_p == '\0') {
            vers_stage = kOSKextVersionStageRelease;
            goto finish;
        } else if (__vers_isdigit(*current_char_p)) {
            vers_digit = __vers_digit_for_char(*current_char_p);
            if (vers_digit < 0) {
                return -1;
            }
            vers_minor = (vers_minor) * 10 + vers_digit;
            current_char_p++;
            num_digits_scanned++;
        } else if (__VERS_isreleasestate(*current_char_p)) {
            goto release_state;
        } else if (*current_char_p == '.') {
            current_char_p++;
            goto revision;
        } else {
            return -1;
        }
    }

   /* Check for too many digits.
    */
    if (num_digits_scanned == VERS_MINOR_DIGITS) {
         if (*current_char_p == '.') {
            current_char_p++;
        } else if (__vers_isdigit(*current_char_p)) {
            return -1;
        }
    }

revision:

    num_digits_scanned = 0;

   /* Scan for revision version number. Legal characters are
    * any digit, any buildstage letter (NOT PERIOD).
    */
    while (num_digits_scanned < VERS_REVISION_DIGITS) {
        if (__vers_isspace(*current_char_p) || *current_char_p == '\0') {
            vers_stage = kOSKextVersionStageRelease;
            goto finish;
        } else if (__vers_isdigit(*current_char_p)) {
            vers_digit = __vers_digit_for_char(*current_char_p);
            if (vers_digit < 0) {
                return -1;
            }
            vers_revision = (vers_revision) * 10 + vers_digit;
            current_char_p++;
            num_digits_scanned++;
        } else if (__VERS_isreleasestate(*current_char_p)) {
            goto release_state;
        } else {
            return -1;
        }
    }

   /* Check for too many digits.
    */
    if (num_digits_scanned == VERS_REVISION_DIGITS) {
         if (*current_char_p == '.') {
            current_char_p++;
        } else if (__vers_isdigit(*current_char_p)) {
            return -1;
        }
    }

release_state:

   /*****
    * Check for the release state.
    */
    if (__vers_isspace(*current_char_p) || *current_char_p == '\0') {
        vers_stage = kOSKextVersionStageRelease;
        goto finish;
    } else {
        vers_stage = __OSKextVersionStageForString(&current_char_p);
        if (vers_stage == kOSKextVersionStageInvalid) {
            return -1;
        }
    }


// stage level

    num_digits_scanned = 0;

   /* Scan for stage level number. Legal characters are
    * any digit only.
    */
    while (num_digits_scanned < VERS_STAGE_LEVEL_DIGITS) {
        if (__vers_isspace(*current_char_p) || *current_char_p == '\0') {
            if (num_digits_scanned) {
                goto finish;
            } else {
                return -1;
            }
        } else if (__vers_isdigit(*current_char_p)) {
            vers_digit = __vers_digit_for_char(*current_char_p);
            if (vers_digit < 0) {
                return -1;
            }
            vers_stage_level = (vers_stage_level) * 10 + vers_digit;
            current_char_p++;
            num_digits_scanned++;
        } else {
            return -1;
        }
    }

   /* Check for too many digits.
    */
    if ((num_digits_scanned == VERS_STAGE_LEVEL_DIGITS) &&
        ! (__vers_isspace(*current_char_p) || (*current_char_p == '\0'))) {

        return -1;
    }

    if (vers_stage_level > VERS_STAGE_LEVEL_MAX) {
        return -1;
    }

finish:

    if (vers_stage == kOSKextVersionStageCandidate && vers_stage_level == 0) {
        return -1;
    }

    result = (vers_major * VERS_MAJOR_MULT) +
             (vers_minor * VERS_MINOR_MULT) +
             (vers_revision * VERS_REVISION_MULT) +
             (vers_stage * VERS_STAGE_MULT) +
             vers_stage_level; 

    return result;
}

/*********************************************************************
* This function must be safe to call in panic context.
*********************************************************************/
Boolean OSKextVersionGetString(
    OSKextVersion   aVersion,
    char          * buffer,
    uint32_t        bufferLength)
{
    int             cpos = 0;
    OSKextVersion   vers_major = 0;
    OSKextVersion   vers_minor = 0;
    OSKextVersion   vers_revision = 0;
    OSKextVersion   vers_stage = 0;
    OSKextVersion   vers_stage_level = 0;
    const char    * stage_string = NULL;  // don't free

   /* No buffer or length less than longest possible vers string,
    * return 0.
    */
    if (!buffer || bufferLength < kOSKextVersionMaxLength) {
        return FALSE;
    }

    bzero(buffer, bufferLength * sizeof(char));

    if (aVersion < 0) {
        strlcpy(buffer, "(invalid)", bufferLength);
        return TRUE;
    }
    if (aVersion == 0) {
        strlcpy(buffer, "(missing)", bufferLength);
        return TRUE;
    }

    vers_major = aVersion / VERS_MAJOR_MULT;
    if (vers_major > VERS_MAJOR_MAX) {
        strlcpy(buffer, "(invalid)", bufferLength);
        return TRUE;
    }

    vers_minor = aVersion - (vers_major * VERS_MAJOR_MULT);
    vers_minor /= VERS_MINOR_MULT;

    vers_revision = aVersion -
        ( (vers_major * VERS_MAJOR_MULT) + (vers_minor * VERS_MINOR_MULT) );
    vers_revision /= VERS_REVISION_MULT;

    vers_stage = aVersion -
        ( (vers_major * VERS_MAJOR_MULT) + (vers_minor * VERS_MINOR_MULT) +
          (vers_revision * VERS_REVISION_MULT));
    vers_stage /= VERS_STAGE_MULT;

    vers_stage_level = aVersion -
        ( (vers_major * VERS_MAJOR_MULT) + (vers_minor * VERS_MINOR_MULT) +
          (vers_revision * VERS_REVISION_MULT) + (vers_stage * VERS_STAGE_MULT));
    if (vers_stage_level > VERS_STAGE_LEVEL_MAX) {
        strlcpy(buffer, "(invalid)", bufferLength);
        return TRUE;
    }

    cpos = snprintf(buffer, bufferLength, "%u", (uint32_t)vers_major);

   /* Always include the minor version; it just looks weird without.
    */
    buffer[cpos] = '.';
    cpos++;
    cpos += snprintf(buffer+cpos, bufferLength - cpos, "%u", (uint32_t)vers_minor);

   /* The revision is displayed only if nonzero.
    */
    if (vers_revision) {
        buffer[cpos] = '.';
        cpos++;
        cpos += snprintf(buffer+cpos, bufferLength - cpos, "%u",
			(uint32_t)vers_revision);
    }

    stage_string = __OSKextVersionStringForStage(vers_stage);
    if (!stage_string) {
        strlcpy(buffer, "(invalid)", bufferLength);
        return TRUE;
    }
    if (stage_string[0]) {
        strlcat(buffer, stage_string, bufferLength);
        cpos += strlen(stage_string);
    }

    if (vers_stage < kOSKextVersionStageRelease) {
        snprintf(buffer+cpos, bufferLength - cpos, "%u", (uint32_t)vers_stage_level);
    }

    return TRUE;
}

/*********************************************************************
*********************************************************************/
#ifndef KERNEL
OSKextVersion OSKextParseVersionCFString(CFStringRef versionString)
{
    OSKextVersion result = -1;
    char         versBuffer[kOSKextVersionMaxLength];
    
    if (CFStringGetCString(versionString, versBuffer,
        sizeof(versBuffer), kCFStringEncodingASCII)) {

        result = OSKextParseVersionString(versBuffer);
    }
    return result;
}
#endif
