// ---------------------------------------------------------------------------
// - TlsExtension.hxx                                                        -
// - afnix:tls service - tls content types definition                        -
// ---------------------------------------------------------------------------
// - This program is free software;  you can redistribute it  and/or  modify -
// - it provided that this copyright notice is kept intact.                  -
// -                                                                         -
// - This program  is  distributed in  the hope  that it will be useful, but -
// - without  any  warranty;  without  even   the   implied    warranty   of -
// - merchantability or fitness for a particular purpose.  In no event shall -
// - the copyright holder be liable for any  direct, indirect, incidental or -
// - special damages arising in any way out of the use of this software.     -
// ---------------------------------------------------------------------------
// - copyright (c) 1999-2023 amaury darsch                                   -
// ---------------------------------------------------------------------------

#ifndef  AFNIX_TLSEXTENSION_HXX
#define  AFNIX_TLSEXTENSION_HXX

#include "Strvec.hpp"
#include "System.hpp"
#include "Crypto.hpp"
#include "TlsTypes.hxx"
#include "TlsUtils.hpp"
#include "array.tcc"

namespace afnix {

  // the supported extension code
  static const t_word PV_EXT_0000 =  0; // server name indication
  static const t_word PV_EXT_0013 = 13; // signature and hash

  // convert a byte to a hash code
  static Crypto::t_hash tls_extn_hash (const t_byte code) {
    if (code == 0x00U) return Crypto::HASH_NIL;
    else if (code == 0x01U) return Crypto::HASH_MD5;
    else if (code == 0x02U) return Crypto::HASH_SHA1;
    else if (code == 0x03U) return Crypto::HASH_SHA224;
    else if (code == 0x04U) return Crypto::HASH_SHA256;
    else if (code == 0x05U) return Crypto::HASH_SHA384;
    else if (code == 0x06U) return Crypto::HASH_SHA512;
    throw Exception ("tls-error", "invalid hash code to decode");
  }

  // convert a byte to a signature code
  static Crypto::t_sign tls_extn_sign (const t_byte code) {
    if (code == 0x00U) return Crypto::SIGN_NIL;
    else if (code == 0x01U) return Crypto::SIGN_RSA;
    else if (code == 0x02U) return Crypto::SIGN_DSA;
    throw Exception ("tls-error", "invalid sign code to decode");
  }

  // the base extension structure
  struct s_extn {
    // the extension code
    t_word d_code;
    // create a nil extension
    s_extn (void) {
      d_code = PV_EXT_0000;
    }
  };

  // 0000: server name
  struct s_0000 : s_extn {
    // the server list
    Strvec d_slst;
    // create a default extension
    s_0000 (void) {
      d_code = PV_EXT_0000;
      d_slst.add (System::hostname ());
    }
    // create an extension by data
    s_0000 (const long size, const t_byte* data) {
      d_code = PV_EXT_0000;
      decode (size, data);
    }
    // encode the extension by data
    Buffer encode (void) const {
      // create a result buffer
      Buffer result;
      // loop in the string vector
      long vlen = d_slst.length ();
      for (long k = 0L; k < vlen; k++) {
	// get the encoded name
	Buffer xbuf = d_slst.get (k);
	// add first the length
	long xlen = xbuf.length ();
	if (xlen > 65535L) {
	  throw Exception ("tls-error", "out of range sni value");
	}
	// add sni code and buffer chunk
	result.add (nilc);
	result.add (TlsUtils::tochunk (xbuf));
      }
      // here it is
      return TlsUtils::tochunk (result);
    }
    // decode the extension by data
    void decode (const long size, const t_byte* data) {
      if (size < 2) {
	throw Exception ("tls-error", "invalid extension size to decode");
      }
      // get the extension block size
      long boff = 0L;
      long bsiz = ((long) data[boff++] << 8); bsiz += ((long) data[boff++]);
      if (bsiz + 2L != size) {
	throw Exception ("tls-error", "inconsistent extension data size");
      }
      if (bsiz == 0L) return;
      // build the extension array
      while (boff < size) {
	// get the server name type
	t_byte code = data[boff++];
	// check for host name
	if (code == 0x00U) {
	  // the the server name size
	  long nsiz = ((long) data[boff++] << 8); nsiz += ((long) data[boff++]);
	  // collect name data
	  char name[nsiz+1];
	  for (long k = 0L; k < nsiz; k++) name[k] = data[boff++];
	  name[nsiz] = nilc;
	  // add the server name
	  d_slst.add (name);
	} else {
	  throw Exception ("tls-error", "invalid extension server name code");
	}
      }
      if (boff != size) {
	throw Exception ("tls-error", "inconsistent server name size");
      }
    }
  };
  
  // 0013: signature and hash algorithm
  struct s_0013 : s_extn {
    // signature and hash algorithm
    struct s_saha {
      // the hasher code
      Crypto::t_hash d_hash;
      // the signer code
      Crypto::t_sign d_sign;
    };
    t_array<s_saha> d_saha;
    // create a default extension
    s_0013 (void) {
      d_code = PV_EXT_0013;
    }
    // create an extension by version
    s_0013 (const t_byte vmaj, const t_byte vmin) {
      d_code = PV_EXT_0013;
      // check for tls 1.3
      if ((vmaj == TLS_VMAJ_3XX) && (vmin == TLS_VMIN_303)) {
	s_saha saha;
	saha.d_hash = Crypto::HASH_MD5;
	saha.d_sign = Crypto::SIGN_RSA;
	d_saha.push (saha);
      }
    }
    // create an extension by data
    s_0013 (const long size, const t_byte* data) {
      d_code = PV_EXT_0013;
      decode (size, data);
    }
    // decode an extension by data
    void decode (const long size, const t_byte* data) {
      if ((size < 2L) || ((size % 2) != 0)) {
	throw Exception ("tls-error", "invalid extension size to decode");
      }
      for (long k = 0L; k < size; k++) {
	s_saha saha;
	saha.d_hash = tls_extn_hash (data[k++]);
	saha.d_sign = tls_extn_sign (data[k]);
	d_saha.push(saha);
      }
    }
    // get the first valid hash algorithm
    Crypto::t_hash gethash (void) const {
      long xlen = d_saha.length ();
      for (long k = 0; k < xlen; k++) {
	if (Crypto::valid (d_saha[k].d_hash, d_saha[k].d_sign) == true) {
	  return d_saha[k].d_hash;
	}
      }
      return Crypto::HASH_NIL;
    }
    // get the first valid signature algorithm
    Crypto::t_sign getsign (void) const {
      long xlen = d_saha.length ();
      for (long k = 0; k < xlen; k++) {
	if (Crypto::valid (d_saha[k].d_hash, d_saha[k].d_sign) == true) {
	  return d_saha[k].d_sign;
	}
      }
      return Crypto::SIGN_NIL;
    }
  };

  // decode an extension by code and data
  static inline s_extn* tls_extn_xx (const t_word code,
				     const long size, const t_byte* data) {
    // check for nil
    if ((size == 0L) || (data == nullptr)) return nullptr;
    // check for extension codes
    if (code == PV_EXT_0000) return new s_0000 (size, data);
    if (code == PV_EXT_0013) return new s_0013 (size, data);
    // invalid code
    throw Exception ("tls-error", "invalid extension to decode");
  }
}

#endif
