#include "LibItsSecurity_Functions.hh"

#include <openssl/sha.h>
#include <openssl/ec.h>
#include <openssl/ecdsa.h>
#include <openssl/objects.h>

#define FIELD_SIZE_256 (256/8)
#define SIGNATURE_SIZE_256 (2+FIELD_SIZE_256*2)
#define FIELD_SIZE_384 (384/8)
#define SIGNATURE_SIZE_384 (2+FIELD_SIZE_284*2)

namespace LibItsSecurity__Functions 
{
//        group signing
/*          * @desc    Produces a 256-bit (32-byte) hash value
            * @param   p_toBeHashedData Data to be used to calculate the hash value
            * @return  The hash value
            fx_hashWithSha256(in octetstring p_toBeHashedData) return Oct32;
*/
OCTETSTRING fx__hashWithSha256(
  const OCTETSTRING& p__toBeHashedData
) {

  unsigned char sha256[SHA_DIGEST_LENGTH];
  SHA256((const unsigned char*)p__toBeHashedData,p__toBeHashedData.lengthof(),sha256);
  return OCTETSTRING(SHA_DIGEST_LENGTH,sha256);
}

/*          * @desc    Produces a Elliptic Curve Digital Signature Algorithm (ECDSA) signaturee
            * @param   p_toBeSignedSecuredMessage    The data to be signed
            * @param   p_privateKey        The private key
            * @return  The signature value
            fx_signWithEcdsaNistp256WithSha256(in octetstring p_toBeSignedSecuredMessage, in octetstring<UInt64> p_privateKey) return octetstring;
*/
OCTETSTRING fx__signWithEcdsaNistp256WithSha256(
  const OCTETSTRING& p__toBeSignedSecuredMessage,
  const OCTETSTRING& p__privateKey
) {

  unsigned char sha256[SHA_DIGEST_LENGTH];
  unsigned char signature[SIGNATURE_SIZE_256];
  unsigned char *r = &signature[2], *s = &signature[2+FIELD_SIZE_256];
  memset(signature, 0, sizeof(signature));
  // signature[0] = 0; // ecdsa_nistp256_with_sha256
  // signature[1] = 0; // uncompressed

  SHA256((const unsigned char*)p__toBeSignedSecuredMessage,p__toBeSignedSecuredMessage.lengthof(),sha256);

  EC_KEY * k = EC_KEY_new();

  EC_GROUP * group = EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1);
  EC_KEY_set_group(k, group);
  EC_GROUP_free(group);

  BIGNUM * p = BN_new();
  BN_bin2bn((const unsigned char*)p__privateKey, p__privateKey.lengthof(), p);
  EC_KEY_set_private_key(k, p);
  BN_clear_free(p);
  
  if (0 == EC_KEY_check_key(k)){
    
    ECDSA_SIG *sig;
    sig = ECDSA_do_sign(sha256, SHA_DIGEST_LENGTH, k);
    BN_bn2bin(sig->r, r);
    BN_bn2bin(sig->s, s);
    ECDSA_SIG_free(sig);
  }
  EC_KEY_free(k);

  return OCTETSTRING(sizeof(signature), signature);
}

/*          * @desc    Verify the signature of the specified data
            * @param   p_toBeVerifiedData          The data to be verified
            * @param   p_signature                 The signature
            * @param   p_ecdsaNistp256PublicKeyX   The public key (x coordinate)
            * @param   p_ecdsaNistp256PublicKeyY   The public key (y coordinate)
            * @return  true on success, false otherwise
            fx_verifyWithEcdsaNistp256WithSha256(in octetstring p_toBeVerifiedData, in octetstring p_signature, in octetstring p_ecdsaNistp256PublicKeyX, in octetstring p_ecdsaNistp256PublicKeyY) return boolean;
*/
BOOLEAN fx__verifyWithEcdsaNistp256WithSha256(
  const OCTETSTRING& p__toBeVerifiedData,
  const OCTETSTRING& p__signature,
  const OCTETSTRING& p__ecdsaNistp256PublicKeyX,
  const OCTETSTRING& p__ecdsaNistp256PublicKeyY
) {
  unsigned char sha256[SHA_DIGEST_LENGTH];
  
  SHA256((const unsigned char*)p__toBeVerifiedData,p__toBeVerifiedData.lengthof(),sha256);
  const unsigned char * r = (const unsigned char*)p__signature;
  const unsigned char * x = (const unsigned char*)p__ecdsaNistp256PublicKeyX;
  const unsigned char * y = (const unsigned char*)p__ecdsaNistp256PublicKeyY;
  int alg = 0, type = 0;
  if(p__signature.lengthof() >= SIGNATURE_SIZE_256)
    alg = *(r++);
  if(p__signature.lengthof() >= (SIGNATURE_SIZE_256-1))
    type = *(r++);

  ECDSA_SIG * sig = ECDSA_SIG_new();
  BN_bin2bn(r,    32, sig->s);
  BN_bin2bn(r+32, 32, sig->r);

  EC_KEY * k;
  EC_GROUP * group;
  EC_POINT * pnt;
  BIGNUM *bnx, *bny;

  k = EC_KEY_new();

  group = EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1);
  EC_KEY_set_group(k, group);
  EC_GROUP_free(group); 

  pnt = EC_POINT_new(group);
  bnx = BN_new(); BN_bin2bn(x, p__ecdsaNistp256PublicKeyX.lengthof(), bnx);
  bny = BN_new(); BN_bin2bn(y, p__ecdsaNistp256PublicKeyY.lengthof(), bny);
  EC_POINT_set_affine_coordinates_GFp(group, pnt, bnx, bny, NULL);
  BN_clear_free(bnx); BN_clear_free(bny);
  EC_KEY_set_public_key(k, pnt);
  EC_POINT_free(pnt);

  int rc = ECDSA_do_verify(sha256, SHA_DIGEST_LENGTH, sig, k);
  EC_KEY_free(k);
  ECDSA_SIG_free(sig);

  return rc==0 ? TRUE : FALSE;
}

/*          * @desc    Produce a new public/private key pair based on Elliptic Curve Digital Signature Algorithm (ECDSA) algorithm.
            *          This function should not be used by the ATS
            * @param   p_privateKey    The new private key value
            * @param   p_publicKeyX    The new public key value (x coordinate)
            * @param   p_publicKeyX    The new public key value (y coordinate)
            * @return  true on success, false otherwise
            fx_generateKeyPair(out octetstring<UInt64> p_privateKey, out octetstring p_publicKeyX, out octetstring p_publicKeyY) return boolean;
*/
BOOLEAN fx__generateKeyPair(
  OCTETSTRING& p__privateKey,
  OCTETSTRING& p__publicKeyX,
  OCTETSTRING& p__publicKeyY
) {
   return TRUE;
}

//        group encryption

//        group certificatesLoader

/*          * @desc    Load in memory cache the certificates available in the specified directory
            * @param   p_rootDirectory Root directory to access to the certificates identified by the certificate ID
            * @param   p_configId      A configuration identifier
            * @remark  This method SHALL be call before any usage of certificates
            * @return  true on success, false otherwise
            fx_loadCertificates(in charstring p_rootDirectory, in charstring p_configId) return boolean;
*/
BOOLEAN fx__loadCertificates(
  const CHARSTRING& p__rootDirectory,
  const CHARSTRING& p__configId
) {
   return TRUE;
}

/*          * @desc    Unload from memory cache the certificates
            * @return  true on success, false otherwise
            fx_unloadCertificates() return boolean;
*/
BOOLEAN fx__unloadCertificates(
) {
   return TRUE;
}

/*          * @desc    Read the specified certificate
            * @param   p_certificateId the certificate identifier
            * @param   p_certificate   the expected certificate
            * @return  true on success, false otherwise
            fx_readCertificate(in charstring p_certificateId, out octetstring p_certificate) return boolean;
*/
BOOLEAN fx__readCertificate(
  const CHARSTRING& p__certificateId,
  OCTETSTRING& p__certificate
) {
   return TRUE;
}


/*          * @desc    Read the specified certificate digest
            * @param   p_certificateId the certificate identifier
            * @param   p_digest   the expected certificate
            * @return  true on success, false otherwise
            fx_readCertificateDigest(in charstring p_certificateId, out HashedId8 p_digest) return boolean;
*/
BOOLEAN fx__readCertificateDigest(
  const CHARSTRING& p__certificateId,
  OCTETSTRING& p__digest
) {
   return TRUE;
}

/*          * @desc    Read the private keys for the specified certificate
            * @param   p_keysId            the keys identifier
            * @param   p_signingPrivateKey the signing private key
            * @return  true on success, false otherwise
            fx_readSigningKey(in charstring p_keysId, out Oct32 p_signingPrivateKey) return boolean;
*/
BOOLEAN fx__readSigningKey(
  const CHARSTRING& p__keysId,
  OCTETSTRING& p__signingPrivateKey
) {
   return TRUE;
}

/*          * @desc    Read the private keys for the specified certificate
            * @param   p_keysId            the keys identifier
            * @param   p_encryptPrivateKey the encrypt private key
            * @return  true on success, false otherwise
            fx_readEncryptingKey(in charstring p_keysId, out Oct32 p_encryptingPrivateKey) return boolean;
*/
BOOLEAN fx__readEncryptingKey(
  const CHARSTRING& p__keysId,
  OCTETSTRING& p__encryptingPrivateKey
) {
   return TRUE;
}

//        group geodesic 

/*          * @desc    Check that given polygon doesn't have neither self-intersections nor holes.
            * @param   p_region   Polygonal Region
            * @return  true on success, false otherwise
            * @verdict Unchanged
            fx_isValidPolygonalRegion(in PolygonalRegion p_region) return boolean;
*/
BOOLEAN fx__isValidPolygonalRegion(
  const LibItsSecurity__TypesAndValues::PolygonalRegion& p__region
) {
  return TRUE;
}

/*          * @desc Check if a polygonal region is inside another one
            * @param p_parent  The main polygonal region
            * @param p_region  The polygonal region to be included
            * @return true on success, false otherwise
            * @verdict Unchanged
            fx_isPolygonalRegionInside(in PolygonalRegion p_parent, in PolygonalRegion p_region) return boolean;
*/
BOOLEAN fx__isPolygonalRegionInside(
  const LibItsSecurity__TypesAndValues::PolygonalRegion& p__parent,
  const LibItsSecurity__TypesAndValues::PolygonalRegion& p__region
) {
   return TRUE;
}

/*          * @desc Check that the location is inside a circular region
            * @param p_region      The circular region to consider
            * @param p_location    The device location
            * @return true on success, false otherwise
            * @verdict Unchanged
            fx_isLocationInsideCircularRegion(in CircularRegion p_region, in ThreeDLocation p_location) return boolean;
*/
BOOLEAN fx__isLocationInsideCircularRegion(
  const LibItsSecurity__TypesAndValues::CircularRegion& p__region,
  const LibItsSecurity__TypesAndValues::ThreeDLocation& p__location
) {
   return TRUE;
}

/*          * @desc Check that the location is inside a rectangular region
            * @param p_region      The rectangular region to consider
            * @param p_location    The device location
            * @return true on success, false otherwise
            * @verdict Unchanged
            fx_isLocationInsideRectangularRegion(in RectangularRegions p_region, in ThreeDLocation p_location) return boolean;
*/
BOOLEAN fx__isLocationInsideRectangularRegion(
  const LibItsSecurity__TypesAndValues::RectangularRegions& p__region,
  const LibItsSecurity__TypesAndValues::ThreeDLocation& p__location
) {
   return TRUE;
}

/*          * @desc Check that the location is inside a polygonal region
            * @param p_region      The polygonal region to consider
            * @param p_location    The device location
            * @return true on success, false otherwise
            * @verdict Unchanged
            fx_isLocationInsidePolygonalRegion(in PolygonalRegion p_region, in ThreeDLocation p_location) return boolean;
*/
BOOLEAN fx__isLocationInsidePolygonalRegion(
  const LibItsSecurity__TypesAndValues::PolygonalRegion& p__region,
  const LibItsSecurity__TypesAndValues::ThreeDLocation& p__location
) {
   return TRUE;
}


/*          * @desc Check if the location is inside an identified region
            * @param p_region      The identified region to consider
            * @param p_location    The device location
            * @return true on success, false otherwise
            * @verdict Unchanged
            fx_isLocationInsideIdentifiedRegion(in IdentifiedRegion p_region, in ThreeDLocation p_location) return boolean;
*/
BOOLEAN fx__isLocationInsideIdentifiedRegion(
  const LibItsSecurity__TypesAndValues::IdentifiedRegion& p__region,
  const LibItsSecurity__TypesAndValues::ThreeDLocation& p__location
) {
   return TRUE;
}

/*          * @desc Check if the location is inside an undefined region
            * @param p_region      The identified region to consider
            * @param p_location    The device location
            * @return true on success, false otherwise
            * @verdict Unchanged
            fx_isLocationInsideOtherRegion(in octetstring p_region, in ThreeDLocation p_location) return boolean; 
*/
BOOLEAN fx__isLocationInsideOtherRegion(
  const OCTETSTRING& p_region,
  const LibItsSecurity__TypesAndValues::ThreeDLocation& p_location
) {
   return TRUE;
} 

/*           * @desc    Check that p_circular_region_1 circular region is included into p_circular_region_2 circular region
             * @param   p_circular_region_1    Circular region 1
             * @param   p_circular_region_2    Circular region 2
             * @return  true on success, false otherwise
            fx_areCirclesInside(in CircularRegion p_circular_region_1, in CircularRegion p_circular_region_2) return boolean;
*/
BOOLEAN fx__areCirclesInside(
  const LibItsSecurity__TypesAndValues::CircularRegion& p_circular_region_1,
  const LibItsSecurity__TypesAndValues::CircularRegion& p_circular_region_2
) {
   return TRUE;
}

/*           * @desc    Check that p_rectanglar_region_1 rectangular region is included into p_rectanglar_region_2 rectangular region
             * @param   p_rectanglar_region_1    Rectangular region 1
             * @param   p_rectanglar_region_2    Rectangular region 2
             * @return  true on success, false otherwise
            fx_areRectanglesInside(in RectangularRegions p_rectanglar_region_1, in RectangularRegions p_rectanglar_region_2) return boolean;
*/
BOOLEAN fx__areRectanglesInside(
  const LibItsSecurity__TypesAndValues::RectangularRegions& p_rectanglar_region_1,
  const LibItsSecurity__TypesAndValues::RectangularRegions& p_rectanglar_region_2
) {
   return TRUE;
}

/*           * @desc    Check that p_polygonal_region_1 polygonal region is included into p_polygonal_region_2 polygonal region
             * @param   p_polygonal_region_1    Polygonal region 1
             * @param   p_polygonal_region_2    Polygonal region 2
             * @return  true on success, false otherwise
            fx_arePolygonsInside(in PolygonalRegion p_polygonal_region_1, in PolygonalRegion p_polygonal_region_2) return boolean;
*/
BOOLEAN fx__arePolygonsInside(
  const LibItsSecurity__TypesAndValues::PolygonalRegion& p_polygonal_region_1,
  const LibItsSecurity__TypesAndValues::PolygonalRegion& p_polygonal_region_2
) {
   return TRUE;
}

/*          * @desc Convert a spacial coordinate from DMS to Dms
            * @param p_degrees The degrees (D)
            * @param p_minutes The minutes (M)
            * @param p_seconds The seconds (S)
            * @param p_latlon  The latitude/longitude: (N|S|E|W)
            * @return The decimal coordinate on success, 0.0, otherwise
            * @verdict Unchanged
            fx_dms2dd(in Int p_degrees, in Int p_minutes, in float p_seconds, in Oct1 p_latlon) return float;
*/
FLOAT fx__dms2dd(
  const INTEGER& p__degrees,
  const INTEGER& p__minutes,
  const FLOAT& p__seconds,
  const OCTETSTRING& p__latlon
) {
  return 0.0;
}

} // end of namespace
