/**************************************************************************/
/*!
    @file     didacticEnc.cpp
    @author   anian buehler @ letsgoING.org
*/
/**************************************************************************/

#include "Arduino.h"
#include "DidacticEnc.h"


//**************************************************************************
//SYMETRIC
//**************************************************************************
DidacticEncSymetric::DidacticEncSymetric(){}
DidacticEncSymetric::~DidacticEncSymetric(){}

int  DidacticEncSymetric::setKey(char* key){
  if(strlen(key) == DE_LENGHT_KEY){
    memcpy(_key, key, DE_LENGHT_KEY+1);
    return DE_ERROR_NOERROR;
  } else if(strlen(key) > DE_LENGHT_KEY){
    return DE_ERROR_KEYLENGHT_LONG;
  } else{
    return DE_ERROR_KEYLENGHT_SHORT;
  }
}

//Nachricht wird entschlüsselt und überschrieben
void DidacticEncSymetric::crypt(char* messageIn, int lengthMessage, char* messageOut, char* key, int lengthKey){
  //ACHTUNG: bei gleichem Zeichen in Schlüssel und Nachricht wird die Nachricht unterbrochen
  int keyCounter = 0;
  int msgCounter = 0;
  
  for (msgCounter = 0; msgCounter < lengthMessage; msgCounter++) {
    keyCounter = msgCounter % lengthKey;                          //Zähler für key der nie größer wird als lengthKey (Restwert der Division durch Schlüssellänge)
    messageOut[msgCounter] =  messageIn[msgCounter] ^ key[keyCounter]; //XOR-Verknüpfung
  }
  messageOut[msgCounter + 1] = '\0';                               //"EndOfString" an letzter Stelle
}

void DidacticEncSymetric::crypt(char* messageIn, char* messageOut, char* key){
  crypt(messageIn, strlen(messageIn), messageOut, key, strlen(key) );
}

void DidacticEncSymetric::crypt(char* messageIn, char* messageOut){
  crypt(messageIn, strlen(messageIn), messageOut, _key, strlen(_key));
}




//**************************************************************************
//ASYMETRIC
//**************************************************************************
DidacticEncAsymetric::DidacticEncAsymetric(){}
DidacticEncAsymetric::~DidacticEncAsymetric(){}

void DidacticEncAsymetric::generateRsaKeys(long p, long q, long& n, long& e, long& d) {
  long x = 0;
  long y = 0;
  long m = 0;

  n = p * q;
  m = (p - 1) * (q - 1);

  e = 3; //start value
  while (gcd(e, m) > 1) {
    e += 2;
  }
  
  if (egcd(e, m, x, y) == 1) {
    if (x < 0) {
      x += m;
    }
    
    d = x % m;
  }
}

long DidacticEncAsymetric::cryptRsa(long key, long n, long message) {
  return modPow(message, key, n);
}

long DidacticEncAsymetric::getPrime(){
  return prime[random(0, MAX_NR_OF_PRIMES- MAX_PRIME_OFFSET)];
}

long DidacticEncAsymetric::getPrimeWithOffset(long basicPrime){
  int index = 0;
  while(prime[index] != basicPrime && index <= MAX_NR_OF_PRIMES- MAX_PRIME_OFFSET){
    ++index;
  }
  return prime[index + random(1,MAX_PRIME_OFFSET)];
}

void DidacticEncAsymetric::setRandomSeedPin(int analogPin){
  if(analogPin <= A5 && analogPin >= A0){
     _randomPin = analogPin;
  }
}

void DidacticEncAsymetric::generateKeys(){
  randomSeed(analogRead(_randomPin));
  long firstPrime =  getPrime();
  long secondPrime = getPrimeWithOffset(firstPrime); 
  generateRsaKeys(firstPrime, secondPrime,  _rsaModule, _publicKey, _privateKey);
}

long DidacticEncAsymetric::getPrivateKey(){
  return _privateKey;
}
long DidacticEncAsymetric::getPublicKey(){
    return _publicKey;
}
long DidacticEncAsymetric::getRsaModule(){
  return _rsaModule;
}

int DidacticEncAsymetric::encrypt(char* plainText, int lengthplainText, char* cypherText, long key, long rsaModule){
    for(int i = 0; i< lengthplainText; ++i){
      long currentMessage = (long) plainText[i];
      snprintf(&(cypherText[strlen(cypherText)]), MAX_LEN_CYPHERTEXT, "%ld", cryptRsa( key, rsaModule, currentMessage));
      snprintf(&(cypherText[strlen(cypherText)]), MAX_LEN_CYPHERTEXT, DE_CYPHER_SEPARATOR);
      }
    return strlen(cypherText);
}

int DidacticEncAsymetric::encrypt(char* plainText, char* cypherText, long key, long rsaModule){
  return encrypt(plainText, strlen(plainText), cypherText, key, rsaModule);
}

int DidacticEncAsymetric::decrypt(char* cypherText, char* plainText, long key, long rsaModule) {
  char *currentNumber;
  char *savePointer = cypherText;
  int counter = 0;
  while ((currentNumber = strtok_r(savePointer, DE_CYPHER_SEPARATOR, &savePointer)) != NULL)  // Don't use \n here it fails
  {
    long number = atol(currentNumber);
    plainText[counter] = cryptRsa( key, rsaModule, number);
    ++counter;
  }
  plainText[counter] = '\0';
  return counter;
}
//***************


/**********************************************
 * MATH FUNCTIONS FOR RSA
 **********************************************/

// Basic Euclidean Algorithm for greatest common divider
long DidacticEncAsymetric::gcd (long a, long b) {
  long r;
  while (b != 0) {
    r = a % b;
    a = b;
    b = r;
  }
  return a;
}

/*recursive version of gcd
  long gcd(long a, long b)
  {
    if (a == 0)
        return b;
    return gcd(b%a, a);
  } */

// extended euclidean algorithm: also finds coefficients x and y
long DidacticEncAsymetric::egcd (long a, long b, long& x, long& y){
  if (a == 0)
  {
    x = 0;
    y = 1;
    return b;
  }

  long _x, _y;
  long gcd = egcd(b % a, a, _x, _y);

  x = _y - (b / a) * _x;
  y = _x;

  return gcd;
}

//modular exponentation (to keep values as small as possible)
long DidacticEncAsymetric::modPow(long base, long exponent, long modulus) {
  if (modulus == 1) {
    return 0;
  }
  long c = 1;
  for (long i = 0; i < exponent; i++) {
    c = c * base % modulus;
  }
  return c;
}