//---------------------------------------------------------------------------
// Copyright (C) 2001 Dallas Semiconductor Corporation, 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 DALLAS SEMICONDUCTOR 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.
//
// Except as contained in this notice, the name of Dallas Semiconductor
// shall not be used except as stated in the Dallas Semiconductor
// Branding Policy.
//---------------------------------------------------------------------------
//
// tm_check.c - A sample program showing how to use the DS1994/DS1427/DS2404
//              to secure a software package and tie it to a timed license
//              held in the 1-Wire device, basically providing a software timed
//              trial.
//
//              This program assumes that the 1-Wire device has been initialized
//              appropriately by the tm_init program, and continuously polls
//              the 1-Wire device to check for a valid license
//              and/or part expiration.  See pseudocode specification at the
//              bottom of this file.
//

#include <stdio.h>
#include <stdlib.h>
#include "ownet.h"
#include "findtype.h"
#include "owfile.h"
#include "time04.h"
#include "memory.h"
#include "string.h"

// defines
#define MAXDEVICES 4
#define MAXTIMEOFFSET 378691201 // about 12 years (should exceed battery life).

// error defines
#define NO_ERROR 1
#define NO_DEVICE 2
#define NO_FILES 3

// local functions
void ComputeSHAVM(uchar *MT, int *hash); // function to compute sha-1
int NLF2(int B, int C, int D, int n);    // used by above function to compute sha-1
int getNumber(int min, int max);         // help with user input

// constants used in SHA computation to create the new MAC
static const int KTN[4] = {0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xca62c1d6};

int main(int argc, char **argv) {
  int i = 0;
  int j = 0;
  int portnum = 0;                  // port number where 1-Wire net is located
  int num = 0;                      // number of 1-Wire Clock devices found on 1-Wire Net
  timedate td;                      // timedate struct
  uint RTCTime = 0;                 // variable to hold the RTC time in seconds.
  uint RTCATime = 0;                // variable to hold the RTCA time in seconds.
  uint SoftTime = 0;                // get "Soft" time with this value.
  uchar SoftTimeArray[5];           // holds the soft time in an array of uchar
  uchar secret[51];                 // holds the secret input by the user.
  int secretlength;                 // secret length in number of bytes.
  uchar usertext[51];               // holds the usertext retrieved from 1-Wire file.
  int usertextlength;               // user text length in number of bytes
  uchar inputforsha[64];            // ROM + UserText + SOFTTIME + Secret
  int intMAC[5];                    // MAC resulting from SHA1 hash as 5 uint integers
  uchar MAC[20];                    // MAC resulting from SHA1 hash of inputforsha array as a byte array
  uchar newMAC[20];                 // MAC constructed from ROM + UserText + SOFTTIME + Secret
  uchar filedata[76];               // file data
  int filedatalength = 0;           // length of file data
  short filehandle = 0;             // handle to 1-Wire file
  SMALLINT comparisonerror = FALSE; // boolean for comparing old and new MACs
  FileEntry fe;                     // 1-Wire file info used to create a 1-Wire file
  SMALLINT errordetect = NO_ERROR;  // flag when an error occurrs in communicating to 1-Wire device
  uchar TimeSN[MAXDEVICES][8];      // the serial numbers (1-Wire Net Addresses) for the devices
  uint delayTime = 0;               // delay between main loop iterations.
  uint noDeviceDelayTime = 1000;    // delay between no-device loop iterations.

  // Make sure we can talk to the part when it is alarming.
  // The following variable should always be set when
  // communicating to an alarming 1-Wire Clock device.
  FAMILY_CODE_04_ALARM_TOUCHRESET_COMPLIANCE = TRUE;

  // initialize timedate struct
  td.day = 0;
  td.hour = 0;
  td.minute = 0;
  td.month = 0;
  td.second = 0;
  td.year = 0;

  // initialize secret and usertext to be full of zeroes
  memset(&secret[0], 0x00, 51);
  memset(&usertext[0], 0x00, 51);

  memset(&SoftTimeArray[0], 0x00, 5);
  memset(&inputforsha[0], 0x00, 64);
  memset(&intMAC[0], 0x00, 20);
  memset(&MAC[0], 0x00, 20);
  memset(&newMAC[0], 0x00, 20);
  memset(&filedata[0], 0x00, 76);

  // initialize file entry.
  // Name the file and extension to read in FileEntry struct.
  fe.Name[0] = 0x45; //'E'
  fe.Name[1] = 0x58; //'X'
  fe.Name[2] = 0x50; //'P'
  fe.Name[3] = 0x20; // blank
  fe.Ext = 0x00;

  puts("\nStarting 'Timed Trial' checking program for 1-Wire Clocks\n");

  // check for required port name
  if (argc != 2) {
    printf("1-Wire Net name required on command line!\n"
           " (example: \"COM1\" (Win32 DS2480),\"/dev/cua0\" "
           "(Linux DS2480),\"1\" (Win32 TMEX)\n");
    exit(1);
  }
  // acquire the port
  if ((portnum = owAcquireEx(argv[1])) < 0) {
    printf("Failed to acquire port.\n");
    exit(1);
  }
  // success
  printf("Port opened: %s\n", argv[1]);

  // prompt the user to enter the secret
  printf("\nPlease enter the secret, either as text or in hexadecimal.\n\n");
  printf("Data Entry Mode\n");
  printf("(0) Text (single line)\n");
  printf("(1) Hex (XX XX XX XX ...)\n");
  secretlength = getData(&secret[0], 51, getNumber(0, 1));

  // get list of 1-Wire Clock devices
  num = FindDevices(portnum, &TimeSN[0], TIME_FAM, MAXDEVICES);

  // check if more than 1 present
  if (num > 1) {
    printf("\nMore than one 1-Wire clock discovered, picking ");
    printf("\nthe first device found.\n");
  }

  // loop to read 1-Wire part and display status.
  while (TRUE) {
    // put in an extra return
    printf("\n");

    // get list of 1-Wire Clock devices
    num = FindDevices(portnum, &TimeSN[0], TIME_FAM, MAXDEVICES);

    // check if Clock not present
    if (num == 0) {
      printf("NO DEVICE PRESENT");
      msDelay((int)noDeviceDelayTime);
      continue;
    }

    // read RTC and RTCA
    if (!getRTC(portnum, &TimeSN[0][0], &td)) { // read RTC
      errordetect = NO_DEVICE;
    }
    RTCTime = DateToSeconds(&td);                // convert timedate struct to number of seconds
    if (!getRTCA(portnum, &TimeSN[0][0], &td)) { // read RTCA
      errordetect = NO_DEVICE;
    }
    RTCATime = DateToSeconds(&td);

    // retrieve file from part.
    if (!owOpenFile(portnum, &TimeSN[0][0], &fe, &filehandle)) {
      errordetect = NO_FILE;
    } else if (!owReadFile(portnum, &TimeSN[0][0], filehandle, &filedata[0], 76, &filedatalength)) {
      errordetect = NO_FILE;
    } else if (!owCloseFile(portnum, &TimeSN[0][0], filehandle)) {
      errordetect = NO_FILE;
    }

    // deal with NO_DEVICE error
    if (errordetect == NO_DEVICE || RTCTime < 1) { // some bad RTC reads give RTCTime as 0.
      printf("NO DEVICE PRESENT");
      errordetect = NO_ERROR;
      msDelay((int)noDeviceDelayTime);
      continue;
    }

    // print out the 1-Wire net address of clock
    i = 0;
    for (j = 7; j >= 0; j--) {
      printf("%02X", TimeSN[i][j]);
    }
    printf(",");

    // deal with NO_FILE error
    if (errordetect == NO_FILE) {
      printf(" Token NOT VALID, no license file");
      errordetect = NO_ERROR;
      msDelay((int)delayTime);
      continue;
    }

    usertextlength = filedatalength - 25; // 25 is MAC length (20) plus SoftTime length (5)

    // parse file to retrieve the user text, the soft expiration time, and the MAC.
    memcpy(&usertext[0], &filedata[0], usertextlength);      // get usertext
    memcpy(&SoftTimeArray[0], &filedata[usertextlength], 5); // get SoftTime as bytes
    memcpy(&MAC[0], &filedata[(usertextlength + 5)], 20);    // get MAC from file as 20 bytes

    // Construct a new MAC with  ROM + UserText + SOFTTIME + Secret =>(SHA)=>  newMAC
    // First, build 64-byte inputforsha array: ROM + UserText + SOFTTIME + Secret
    memcpy(&inputforsha[0], &TimeSN[0][0], 8);
    memcpy(&inputforsha[8], &usertext[0], usertextlength);
    memcpy(&inputforsha[(usertextlength + 8)], &SoftTimeArray[0], 5);
    SoftTime = uchar_to_bin(&SoftTimeArray[1], 4); // convert SoftTime to number of seconds
    memcpy(&inputforsha[(usertextlength + 13)], &secret, secretlength);

    // Create the SHA1 MAC from the inputforsha array
    ComputeSHAVM(&inputforsha[0], &intMAC[0]);

    // Make new MAC as a byte array from the intMAC int integer array
    memcpy(&newMAC[0], &intMAC[0], 20);

    // Compare old MAC from clock to new MAC against the entered secret
    comparisonerror = FALSE;
    for (i = 0; i < 20; i++) {
      if (MAC[i] != newMAC[i])
        comparisonerror = TRUE;
    }
    if (comparisonerror == TRUE) {
      printf(" Token NOT VALID, secret must be different");
      comparisonerror = FALSE; // reset error
    } else {
      printf(" Token VALID,");

      if (RTCTime < SoftTime) {
        printf(" License VALID, ");
        PrintChars(&usertext[0], usertextlength);
      } else {
        printf(" License EXPIRED");
        if (RTCTime >= RTCATime) {
          printf(", Device EXPIRED");
        }
      }
    }

    // reset errordetect
    errordetect = NO_ERROR;
    msDelay((int)delayTime);
  }

  // release the port
  owRelease(portnum);
  return 1;
}

//----------------------------------------------------------------------
// computes a SHA given the 64 byte MT digest buffer.  The resulting 5
// int values are stored in the given int array, hash.
//
// Note: This algorithm before's the SHA-1 algorithm as specified in the
// datasheet for the DS1961S, where the last step (which only involves
// math with constant values) is omitted.
//
// Parameters:
//  MT         - pointer to a buffer containing the message digest
//  hash       - resulting buffer
//
// Returns:
//             - see hash variable above
//
void ComputeSHAVM(uchar *MT, int *hash) {
  unsigned int MTword[80];
  int i;
  int ShftTmp;
  int Temp;

  for (i = 0; i < 16; i++) {
    MTword[i] = ((MT[i * 4] & 0x00FF) << 24) | ((MT[i * 4 + 1] & 0x00FF) << 16) | ((MT[i * 4 + 2] & 0x00FF) << 8) |
                (MT[i * 4 + 3] & 0x00FF);
  }

  for (; i < 80; i++) {
    ShftTmp = MTword[i - 3] ^ MTword[i - 8] ^ MTword[i - 14] ^ MTword[i - 16];
    MTword[i] = ((ShftTmp << 1) & 0xFFFFFFFE) | ((ShftTmp >> 31) & 0x00000001);
  }

  hash[0] = 0x67452301;
  hash[1] = 0xEFCDAB89;
  hash[2] = 0x98BADCFE;
  hash[3] = 0x10325476;
  hash[4] = 0xC3D2E1F0;

  for (i = 0; i < 80; i++) {
    ShftTmp = ((hash[0] << 5) & 0xFFFFFFE0) | ((hash[0] >> 27) & 0x0000001F);
    Temp = NLF2(hash[1], hash[2], hash[3], i) + hash[4] + KTN[i / 20] + MTword[i] + ShftTmp;
    hash[4] = hash[3];
    hash[3] = hash[2];
    hash[2] = ((hash[1] << 30) & 0xC0000000) | ((hash[1] >> 2) & 0x3FFFFFFF);
    hash[1] = hash[0];
    hash[0] = Temp;
  }
}

//----------------------------------------------------------------------
// calculation used for the SHA MAC
//
int NLF2(int B, int C, int D, int n) {
  if (n < 20)
    return ((B & C) | ((~B) & D));
  else if (n < 40)
    return (B ^ C ^ D);
  else if (n < 60)
    return ((B & C) | (B & D) | (C & D));
  else
    return (B ^ C ^ D);
}

//----------------------------------------------------------------------
// Retrieve user input from the console.
//
// Parameters:
//  min  minimum number to accept
//  max  maximum number to accept
//
// Returns:
//  numeric value entered from the console.
//
int getNumber(int min, int max) {
  int value = min, cnt;
  int done = FALSE;
  do {
    cnt = scanf("%d", &value);
    if (cnt > 0 && (value > max || value < min)) {
      printf("Value (%d) is outside of the limits (%d,%d)\n", value, min, max);
      printf("Try again:\n");
    } else
      done = TRUE;

  } while (!done);

  return value;
}

//-------------------------------------------------------------------
//
//       PSEUDOCODE SPECIFICATION:
//
//         TM_Check - Prompt for Secret. Check for valid token present
//         by doing the following:
//
//         loop
//         {
//            if (DS1994 PRESENT)
//            {
//               print ROM
//
//               Read Real-Time Clock (RTC) and Alarm (RTCA)
//               Read EXP.000 and extract: UserText, SOFTTIME and MAC
//
//               if (failed to read EXP.000)
//               {
//                  print 'Token NOT VALID, no license file'
//                  continue
//               }
//
//               Construct a new MAC with
//                    ROM + UserText + SOFTTIME + Secret =>(SHA)=>  MAC'
//
//               if (MAC != MAC')
//                  print 'Token NOT VALID, secret must be different'
//               else
//               {
//                  print 'Token VALID'
//
//                  if (RTC < SOFTTIME)
//                      print 'License VALID' + UserText
//                  else
//                      print 'License EXPIRED'
//
//                  if (RTC >= RTCA)
//                      print 'Device EXPIRED'
//               }
//            }
//            else
//               print 'NO DEVICE PRESENT'
//         }
//
//         So the possible output may be:
//
//         NO DEVICE PRESENT
//         NO DEVICE PRESENT
//         5400000011227704, Token NOT VALID, no license file
//         NO DEVICE PRESENT
//         NO DEVICE PRESENT
//         1100000055266704, Token NOT VALID, secret must be different
//         NO DEVICE PRESENT
//         D7000000068C7A04, Token VALID, License VALID, MyTextGoesHere
//         D7000000068C7A04, Token VALID, License VALID, MyTextGoesHere
//         D7000000068C7A04, Token VALID, License VALID, MyTextGoesHere
//         D7000000068C7A04, Token VALID, License EXPIRED
//         D7000000068C7A04, Token VALID, License EXPIRED
//         D7000000068C7A04, Token VALID, License EXPIRED
//         D7000000068C7A04, Token VALID, License EXPIRED, Device EXPIRED
//
