/*********************************************************************************
 *  MIT License
 *  
 *  Copyright (c) 2020-2025 Gregg E. Berman
 *  
 *  https://github.com/HomeSpan/HomeSpan
 *  
 *  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 THE
 *  AUTHORS OR COPYRIGHT HOLDERS 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.
 *  
 ********************************************************************************/
 
#pragma once

#include <sstream>
#include <WiFi.h>

#include "HomeSpan.h"
#include "HAPConstants.h"
#include "HKDF.h"
#include "SRP.h"

const TLV8_names HAP_Names[] = {
  {kTLVType_Separator,"SEPARATOR"},
  {kTLVType_State,"STATE"},
  {kTLVType_PublicKey,"PUBKEY"},
  {kTLVType_Method,"METHOD"},
  {kTLVType_Salt,"SALT"},
  {kTLVType_Error,"ERROR"},
  {kTLVType_Proof,"PROOF"},
  {kTLVType_EncryptedData,"ENC.DATA"},
  {kTLVType_Signature,"SIGNATURE"},
  {kTLVType_Identifier,"IDENTIFIER"},
  {kTLVType_Permissions,"PERMISSION"}
};

#define hap_controller_IDBYTES  36
#define hap_accessory_IDBYTES   17
 
/////////////////////////////////////////////////
// NONCE Structure (HAP used last 64 of 96 bits)

struct Nonce {
  uint8_t x[12];
  Nonce();
  void zero();
  uint8_t *get();
  void inc();
};

/////////////////////////////////////////////////
// Accessory Structure for Permanently-Stored Data

struct Accessory {
  uint8_t ID[hap_accessory_IDBYTES];               // Pairing ID in form "XX:XX:XX:XX:XX:XX" (no null terminator)
  uint8_t LTSK[crypto_sign_SECRETKEYBYTES];        // Long Term Ed2519 Secret Key
  uint8_t LTPK[crypto_sign_PUBLICKEYBYTES];        // Long Term Ed2519 Public Key
};

/////////////////////////////////////////////////
// HAPClient Structure
// Reads and Writes from each HAP Client connection

struct HAPClient {

  // common structures and data shared across all HAP Clients

  static const int MAX_HTTP=8096;                     // max number of bytes allowed for HTTP message
  static const int MAX_CONTROLLERS=16;                // maximum number of paired controllers (HAP requires at least 16)
  static const int MAX_ACCESSORIES=150;               // maximum number of allowed Accessories (HAP limit=150)
  
  static pairState pairStatus;                                      // tracks pair-setup status
  static Accessory accessory;                                       // Accessory ID and Ed25519 public and secret keys - permanently stored
  static list<Controller, Mallocator<Controller>> controllerList;   // linked-list of Paired Controller IDs and ED25519 long-term public keys - permanently stored

  // individual structures and data defined for each Hap Client connection
  
  NetworkClient client;           // handle to client
  int clientNumber;               // client number
  Controller *cPair=NULL;         // pointer to info on current, session-verified Paired Controller (NULL=un-verified, and therefore un-encrypted, connection)
   
  // These temporary Curve25519 keys are generated in the first call to pair-verify and used in the second call to pair-verify so must persist for a short period

  struct tempKeys_t {
    uint8_t publicCurveKey[crypto_box_PUBLICKEYBYTES];     // Accessory's Curve25519 Public Key
    uint8_t sharedCurveKey[crypto_box_PUBLICKEYBYTES];     // Shared-Secret Curve25519 Key derived from Accessory's Secret Key and Controller's Public Key
    uint8_t sessionKey[crypto_box_PUBLICKEYBYTES];         // Session Key Curve25519 (derived with various HKDF calls)
    uint8_t iosCurveKey[crypto_box_PUBLICKEYBYTES];        // Controller's Curve25519 Public Key    
  } temp;
  
  // CurveKey and CurveKey Nonces are created once each new session is verified in /pair-verify.  Keys persist for as long as connection is open
  
  uint8_t a2cKey[32];             // AccessoryToControllerKey derived from HKDF-SHA-512 of sharedCurveKey (HAP Section 6.5.2)
  uint8_t c2aKey[32];             // ControllerToAccessoryKey derived from HKDF-SHA-512 of sharedCurveKey (HAP Section 6.5.2)
  Nonce a2cNonce;                 // encryption nonce (starts at zero at end of each Pair-Verify and increment every encryption - NOT DOCUMENTED)
  Nonce c2aNonce;                 // decryption nonce (starts at zero at end of each Pair-Verify and increment every encryption - NOT DOCUMENTED)

  // define member methods

  void processRequest();                                      // process HAP request  
  int postPairSetupURL(uint8_t *content, size_t len);         // POST /pair-setup (HAP Section 5.6)
  int postPairVerifyURL(uint8_t *content, size_t len);        // POST /pair-verify (HAP Section 5.7)
  int postPairingsURL(uint8_t *content, size_t len);          // POST /pairings (HAP Sections 5.10-5.12)  
  int getAccessoriesURL();                                    // GET /accessories (HAP Section 6.6)
  int getCharacteristicsURL(char *urlBuf);                    // GET /characteristics (HAP Section 6.7.4)  
  int putCharacteristicsURL(char *json);                      // PUT /characteristics (HAP Section 6.7.2)
  int putPrepareURL(char *json);                              // PUT /prepare (HAP Section 6.7.2.4)

  void tlvRespond(TLV8 &tlv8);                                // respond to client with HTTP OK header and all defined TLV data records
  int receiveEncrypted(uint8_t *httpBuf, int messageSize);    // decrypt HTTP request (HAP Section 6.5)

  int notFoundError();           // return 404 error
  int badRequestError();         // return 400 error
  int unauthorizedError();       // return 470 error

  // define static methods
    
  static void init();            // initialize HAP after start-up
    
  static void hexPrintColumn(const uint8_t *buf, int n, int minLogLevel=0);            // prints 'n' bytes of *buf as HEX, one byte per row, subject to specified minimum log level
  static void hexPrintRow(const uint8_t *buf, int n, int minLogLevel=0);               // prints 'n' bytes of *buf as HEX, all on one row, subject to specified minimum log level
  static void charPrintRow(const uint8_t *buf, int n, int minLogLevel=0);              // prints 'n' bytes of *buf as CHAR, all on one row, subject to specified minimum log level
  
  static Controller *findController(uint8_t *id);                                      // returns pointer to controller with matching ID (or NULL if no match)
  static tagError addController(uint8_t *id, uint8_t *ltpk, boolean admin);            // stores data for new Controller with specified data.  Returns tagError (if any)
  static void removeController(uint8_t *id);                                           // removes specific Controller.  If no remaining admin Controllers, remove all others (if any) as per HAP requirements.
  static void printControllers(int minLogLevel=0);                                     // prints IDs of all allocated (paired) Controller, subject to specified minimum log level
  static void saveControllers();                                                       // saves Controller list in NVS
  static int nAdminControllers();                                                      // returns number of admin Controller
  static void tearDown(uint8_t *id);                                                   // tears down connections using Controller with ID=id; tears down all connections if id=NULL
  static void checkNotifications();                                                    // checks for Event Notifications and reports to controllers as needed (HAP Section 6.8)
  static void checkTimedWrites();                                                      // checks for expired Timed Write PIDs, and clears any found (HAP Section 6.7.2.4)
  static void eventNotify(SpanBufVec &pVec, HAPClient *ignore=NULL);                   // transmits EVENT Notifications for SpanBuf objects with optional flag to ignore a specific client

  static void getStatusURL(HAPClient *, void (*)(const char *, void *), void *, int refreshTime=0);       // GET / status (an optional, non-HAP feature)

  class HAPTLV : public TLV8 {   // dedicated class for HAP TLV8 records
    public:
      HAPTLV() : TLV8(HAP_Names,11){}
  };
  
};

/////////////////////////////////////////////////
// HapOut Structure

class HapOut : public std::ostream {

  private:

  struct HapStreamBuffer : public std::streambuf {

    const size_t bufSize=1024;            // max allowed for HAP encrypted records
    char *buffer;
    uint8_t *encBuf;
    HAPClient *hapClient=NULL;
    int logLevel=255;                     // default is NOT to print anything
    boolean enablePrettyPrint=false;
    size_t byteCount=0;
    size_t indent=0;
    uint8_t *hash;
    mbedtls_sha512_context *ctx;
    void (*callBack)(const char *, void *)=NULL;
    void *callBackUserData = NULL;
  
    void flushBuffer();
    int_type overflow(int_type c) override;
    int sync() override; 
    size_t getSize(){return(byteCount+pptr()-pbase());}
    void printFormatted(char *buf, size_t nChars, size_t nsp);
        
    HapStreamBuffer();
    ~HapStreamBuffer();
    
  };

  HapStreamBuffer hapBuffer;

  public:

  HapOut() : std::ostream(&hapBuffer){}
  
  HapOut& setHapClient(HAPClient *hapClient){hapBuffer.hapClient=hapClient;return(*this);}
  HapOut& setLogLevel(int logLevel){hapBuffer.logLevel=logLevel;return(*this);}
  HapOut& prettyPrint(){hapBuffer.enablePrettyPrint=true;hapBuffer.logLevel=0;return(*this);}
  HapOut& setCallback(void(*f)(const char *, void *)){hapBuffer.callBack=f;return(*this);}
  HapOut& setCallbackUserData(void *userData){hapBuffer.callBackUserData=userData;return(*this);}
  
  uint8_t *getHash(){return(hapBuffer.hash);}
  size_t getSize(){return(hapBuffer.getSize());}
};

/////////////////////////////////////////////////
// Extern Variables

extern HapOut hapOut;
