/*****************************************************************************
 * Project:     ApproovSDK
 * File:        Approov.h
 * Copyright(c) 2018 by CriticalBlue Ltd.
 *
 * The Approov SDK performs the actual client app attestations via the Approov
 * cloud service in order to access a remote "Attester" and retrieve a
 * time-limited Approov token.
 *****************************************************************************/

#import <Foundation/Foundation.h>

//! Project version number for Approov.
FOUNDATION_EXPORT double ApproovVersionNumber;

//! Project version string for Approov.
FOUNDATION_EXPORT const char ApproovVersionString[];

// Enumeration of results that may be generated as a result of an Approov fetch operation.
typedef NS_ENUM(NSUInteger, ApproovTokenFetchStatus)
{
    // Indicates that a fetch successfully completed.
    ApproovTokenFetchStatusSuccess,

    // Indicates that the fetch failed because there is currently no network connectivity.
    ApproovTokenFetchStatusNoNetwork,

    // Indicates that the fetch failed because the certificate presented on the Approov endpoint was not one
    // that was expected. This might indicate the device is running on a network with a firewall that terminates TLS.
    ApproovTokenFetchStatusMITMDetected,

    // Indicates that the fetch failed due to poor network connectivity.
    ApproovTokenFetchStatusPoorNetwork,

    // Indicates that the fetch failed, perhaps because Approov services (primary and failover) are down.
    ApproovTokenFetchStatusNoApproovService,

    // Indicates that the provided domain is not in the correct format or is not of the https scheme.
    ApproovTokenFetchStatusBadURL,

    // Indicates that the URL provided is a for a domain that has not be specified in the Approov administration portal.
    // This may be an incorrect URL or may indicate that no Approov token is required for protecting this endpoint.
    ApproovTokenFetchStatusUnknownURL,

    // Indicates that no Approov token is needed for the domain. This is returned as a result of a configuration
    // that is set server side, indicating that the domain is pinned but no Approov token is required.
    ApproovTokenFetchStatusUnprotectedURL,

    // Indicates that the Approov SDK has not been initialized.
    ApproovTokenFetchStatusNotInitialized,
    
    // Indicates a custom JWT or secure string fetch has been rejected because Approov attestation fails.
    ApproovTokenFetchStatusRejected,
    
    // Indicates that a custom JWT or secure string fetch fails because the feature is not enabled.
    ApproovTokenFetchStatusDisabled,
    
    // Indicates an attempt to fetch a secure string that has not been defined.
    ApproovTokenFetchStatusUnknownKey,
    
    // Indicates an attempt to fetch a secure string with a bad key.
    ApproovTokenFetchStatusBadKey,
    
    // Indicates an attempt to fetch a custom JWT with a bad payload.
    ApproovTokenFetchStatusBadPayload,
    
    // Indicates an internal SDK error.
    ApproovTokenFetchStatusInternalError
};

/**
 * This interface is used for the result value of the fetch methods.
 */
__attribute__((visibility("default"))) @interface ApproovTokenFetchResult: NSObject

// The result code generated by a fetch operation
@property (readonly) ApproovTokenFetchStatus status;

// The last fetched token as a string which should not be cached by the app client code. This is the empty string if
// no token could be obtained.
@property (readonly, nonnull) NSString *token;

// The secure string of the last secure string fetch. This value should not be cached by the app client code. This may
// be nil if the string is not available.
@property (readonly, nullable) NSString *secureString;

// Any Attestation Response Code (ARC) providing details of the device properties. This is the empty string if
// no ARC was obtained.
@property (readonly, nonnull) NSString *ARC;

// Any rejection reasons describing why Approov attestation has failed. This is a comma separated list of
// device properties, or an empty string for a pass or if the feature is not enabled.
@property (readonly, nonnull) NSString *rejectionReasons;

// Provides a flag indicating if a new updated configuration JWT has been obtained from the server. Once set this
// remains true for subsequent token fetches until a fetchConfig() call is made.
@property (readonly) BOOL isConfigChanged;

// Provides a flag indicating if current user APIs must be updated to reflect a new version
// available from getPins() or getPinsJSON(). Calling getPins() or getPinsJSON() will clear this flag for the
// next Approov token fetch.
@property (readonly) BOOL isForceApplyPins;

// The measurement configuration is only provided if the last fetch operation for the domain initiated a
// measurement and the token fetch was successful. This provides a binary buffer that must be provided to the
// getIntegrityMeasurementProof() or getDeviceMeasurementProof() to obtain a new proof.
@property (readonly, nullable) NSData *measurementConfig;

// ApproovTokenFetchCallback type definition
typedef void (^ApproovTokenFetchCallback)(ApproovTokenFetchResult *_Nonnull result);

/**
 * Gets a loggable version of the result Approov token. This provides the decoded JSON payload along with the first six
 * characters of the base64 encoded signature as an additional "sip" claim. This can be safely logged as it cannot be
 * transformed into a valid token since the full signature is not provided, but it can be subsequently checked for
 * validity if the shared secret is known with a very high probability. The loggable token is always valid JSON. If
 * there is no valid token then the status is provided in the "status" claim. Note that this is not applicable to JWE tokens.
 *
 * @return Loggable Approov token string
 */
- (nonnull NSString *)loggableToken;

@end

/**
 * Approov public class interface.
 */
__attribute__((visibility("default"))) @interface Approov: NSObject

/**
 * Gets a human readable string from an Approov token fetch status.
 *
 * @param approovTokenFetchStatus is the ApproovTokenFetchStatus
 * @return the string for the ApproovTokenFetchStatusq
 */
+ (nonnull NSString *)stringFromApproovTokenFetchStatus:(ApproovTokenFetchStatus)approovTokenFetchStatus;

/**
 * Initialize the Approov SDK. This must be called prior to any other methods on the Approov
 * SDK. The SDK is initialized with a initial configuration and an optional update configuration
 * and comment. The iniitial configuration is either a short init string or a signed JWT token
 * that is obtained from the Approov CLI tool and contains all necessary parameters to initialize the SDK.
 * An updated configuration may be transmitted while the SDK is in use and this must be stored in the
 * local storage of the app. If "auto" is provided as the update configuration then the SDK will manage
 * its own configuration update storage. If "auto" is used then on the very first initialization
 * a short network fetch will be attempted to get the latest update configuration if possible. If
 * "reinit" is provided for the comment then it allows the SDK to be reinitialized to change Approov
 * accounts.
 *
 * @param initialConfig is the initial configuration which is either a short init string or full JWT and must be present
 * @param updateConfig is any update configuration JWT, "auto" or nil if there is none
 * @param comment is an optional comment that may be "reinit" to force SDK reinitialization, or nil otherwise
 * @param error the reference to an error object which will be set if an error occurred
 * @return YES if the Approov framework was successfully initialized or NO otherwise
 */
+ (BOOL)initialize:(nonnull NSString *)initialConfig updateConfig:(nullable NSString *)updateConfig
    comment:(nullable NSString * )comment error:(NSError *_Nullable *_Nullable)error;

/**
 * Fetches the current configuration for the SDK. This may be the initial configuration or may
 * be a new updated configuration returned from the Approov cloud service. Such updates of the
 * configuration allow new sets of certificate pins and other configuration to be passed to
 * an app instance that is running in the field.
 *
 * Normally this method returns the latest configuration that is available and is cached in the
 * SDK. Thus the method will return quickly. However, if this method is called when there has
 * been no prior call to fetch an Approov token then a network request to the Approov cloud
 * service will be made to obtain any latest configuration update. The maximum timeout period
 * is set to be quite short but the caller must be aware that this delay may occur.
 *
 * Note that the returned configuration should generally be kept in local storage for the app
 * so that it can be made available on initialisation of the Approov SDK next time the app
 * is started.
 *
 * It is possible to see if a new configuration becomes available from the isConfigChanged
 * property of the TokenFetchResult. This changed flag is only cleared for future token fetches
 * if a call to this method is made.
 *
 * @return String representation of the configuration or empty string or nil if SDK not initialized.
 */
+ (nullable NSString *)fetchConfig;

/**
 * Provide access to the API pin information held in the current app configuration.Pins of a particular type are retrieved
 * If the SDK has not be initialized then nil is returned. Note that if the isForceApplyPins flag was set on the last Approov
 * token fetch then this clears the flag for future fetches as it indicates that the latest pin information has been read.
 * Pins may be returned with "*" as the domain as these represent the trusted root pins of all the acceptable
 * certificate authorities.
 *
 * @param pinType is the format of the pins that should be retrieved
 * @result NSDictionary mapping from domain names to a list of pins in base64 format for that domain
*/
+ (nullable NSDictionary<NSString *, NSArray<NSString *> *> *)getPins:(nonnull NSString *)pinType;

/**
 * Provide access to the API pin information held in the current app configuration. This method provides the information in
 * marsahlled JSON form.Pins of a particular type are retrieved. If the SDK has not be initialized then nil is returned. Note that
 * if the isForceApplyPins flag was set on the last Approov token fetch then this clears the flag for future fetches as it indicates
 * that the latest pin information has been read. Pins may be returned with "*" as the domain as these represent the trusted
 * root pins of all the acceptable certificate authorities.
 *
 * @param pinType is the format of the pins that should be retrieved
 * @result String representation of the JSON representing the pins, or nil if the SDK has not been initialized
*/
+ (nullable NSString *)getPinsJSON:(nonnull NSString *)pinType;

/**
 * Initiates an asynchronous request to obtain an Approov token and other results. The call returns
 * immediately and when the token is fetched (or if the request times out due to an error) then
 * a callback function is called with the result. This callback is made on a different thread. If
 * an Approov token fetch has been completed previously and the token are unexpired then this callback
 * may be very rapid without a need to perform a network transaction. Note though that the caller
 * should never cache the Approov token as it may become invalidated at any point.
 *
 * If a new Approov token is required then a more extensive app measurement is performed that involves
 * communicating with the Approov cloud service. There is a chance that due to poor network
 * connectivity or other factors an Approov token cannot be obtained, and this is reflected in the
 * returned status.
 *
 * All calls must provide a url which provides the high level domain of the API to which the Approov
 * token is going to be sent. Different API domains will have different Approov tokens associated with
 * them so it is important that the Approov token is only sent to requests for that domain. If the
 * domain has not been configured in the admin portal then an error is obtained. Note that the provided
 * URL may be suffixed by "?measurement" to initiate a measurement process for use with integrity and
 * device proofs.
 *
 * @param callbackHandler is a function that takes an ApproovTokenFetchResult when the token is obtained
 * @param url provides the top level domain url for which a token is being fetched
 */
+ (void)fetchApproovToken:(nonnull ApproovTokenFetchCallback)callbackHandler :(nonnull NSString *)url;

/**
 * Initiates a synchronous request to obtain an Approov token and other results. If an Approov token fetch
 * has been completed previously and the tokens are unexpired then this may return the same one
 * without a need to perform a network transaction. Note though that the caller should never cache the
 * Approov token as it may become invalidated at any point.
 *
 * If a new Approov token is required then a more extensive app measurement is performed that involves
 * communicating with the Approov cloud service. Thus this method may take up to several seconds to
 * return and should not be called from a UI thread. There is also a chance that due to poor network
 * connectivity or other factors an Approov token cannot be obtained, and this is reflected in the
 * returned status.
 *
 * All calls must provide a URL which provides the high level domain of the API to which the Approov
 * token is going to be sent. Different API domains will have different Approov tokens associated with
 * them so it is important that the Approov token is only sent to requests for that domain. If the
 * domain has not been configured in the admin portal then an error is obtained. Note that the provided
 * URL may be suffixed by "?measurement" to initiate a measurement process for use with integrity and
 * device proofs.
 *
 * @param url provides the top level domain URL for which a token is being fetched
 * @return results of fetching a token
 */
+ (nonnull ApproovTokenFetchResult *)fetchApproovTokenAndWait:(nonnull NSString *)url;

/**
 * Initiates an asynchronous request to obtain a custom JWT with the given payload. The call returns
 * immediately and when the token is fetched (or if the request times out due to an error, or if the
 * request is rejected) then a callback method is called on the supplied interface instance object.
 * This callback is made on a different thread. The payload must be valid JSON or else a BadPayload
 * error status is returned. If the attestation process fails then the error status Rejected is
 * returned. Finally, if the feature is not enabled then the Disabled status is returned.
 *
 * @param callbackHandler is a function that takes an ApproovTokenFetchResult when the token is obtained
 * @param payload provide marshaled JSON to be included in the custom JWT to be fetched
 */
+ (void)fetchCustomJWT:(nonnull ApproovTokenFetchCallback)callbackHandler :(nonnull NSString *)payload;

/**
 * Initiates an synchronous request to obtain a custom JWT with the given payload. The call only
 * returns when the custom JWT has been obtained (or if the request times out due to an error, or
 * if the request is rejected) then a callback method is called on the supplied interface instance
 * object. This callback is made on a different thread. The payload must be valid JSON or else
 * a BadPayload error status is returned. If the attestation process fails then the error status
 * Rejected is returned. Finally, if the feature is not enabled then the Disabled status is returned.
 *
 * @param payload provide marshaled JSON to be included in the custom JWT to be fetched
 * @return results of fetching a token
 */
+ (nonnull ApproovTokenFetchResult *)fetchCustomJWTAndWait:(nonnull NSString *)payload;

/**
 * Initiates an asynchronous request to obtain, or update, a string held securely by the SDK. The call
 * returns immediately and when the secure string is fetched (or if the request times out due to an
 * error, or if the request is rejected) then a callback method is called on the supplied interface
 * instance object. This callback is made on a different thread. This provides the secure string value.
 * It is also possible to update the secure string value by providing a new definition. In this case the
 * new value is returned as the secure string. Use of an empty string for newDef removes the string entry.
 * Note that after an initial fetch secure strings will be available for some period until they expire, with
 * no latency for a network operation. If the string is not defined then the UnknownKey error status
 * is returned. If the attestation process fails then the error status Rejected is returned. If the provided
 * key is invalid then a BadKey status is returned. Finally, if the feature is not enabled then the Disabled
 * status is returned.
 *
 * @param callbackHandler is a function that takes an ApproovTokenFetchResult when the token is obtained
 * @param key is the name of the key (max 64 characters) that the secure string is held against
 * @param newDef is any new definition for the secure string, or nil otherwise
 */
+ (void)fetchSecureString:(nonnull ApproovTokenFetchCallback)callbackHandler :(nonnull NSString *)key :(nullable NSString *)newDef;

/**
 * Initiates an synchronous request to obtain, or update, a string held securely by the SDK. The
 * call only returns when secure string has been obtained (or if the request times out due to an error, or
 * if the request is rejected). This provides the secure string value. It is also possible to update
 * the secure string value by providing a new definition. In this case the new value is returned as the
 * secure string. Use of an empty string for newDef removes the string entry. Note that after an initial fetch secure
 * strings will be available for some period until they expire, with no latency for a network operation. If
 * the string is not defined then the UnknownKey error status is returned. If the attestation process fails
 * then the error status Rejected is returned. If the provided key is invalid then a BadKey status is returned.
 * Finally, if the feature is not enabled then the Disabled status is returned.
 *
 * @param key is the name of the key (max 64 characters) that the secure string is held against
 * @param newDef is any new definition for the secure string, empty for deletion or nil otherwise
 * @return results of fetching the secure string
 */
+ (nonnull ApproovTokenFetchResult *)fetchSecureStringAndWait:(nonnull NSString *)key :(nullable NSString *)newDef;

/**
 * Sets a user defined property on the SDK. This may provide information about the
 * app state or aspects of the environment it is running in. This has no direct
 * impact on Approov except it is visible as a property on attesting devices and
 * can be analyzed using device filters. Note that properties longer than 128
 * characters are ignored and all non ASCII characters are removed. The special value
 * "$error" may be used to mark an error condition for offline measurement mismatches.
 *
 * @param property to be set, which may be nil
 */
+ (void)setUserProperty:(nullable NSString *)property;

/**
 * Sets a development key on the SDK. This may be used to force a build of the app
 * to always pass attestation, even if the app itself is not registered or it is
 * being run on a simulator. This is to allow testing when the app might have to
 * be resigned to run on a particular test environment and the signing certificate
 * is either not known or not easily accessible. The development key is defined at
 * the account level and can be changed at any point.
 *
 * @param key is the development key to be set, which may be nil
 */
+ (void)setDevKey:(nullable NSString *)key;

/**
 * Sets a hash of the given data value into any future Approov tokens obtained in the 'pay'
 * claim. If the data values is transmitted to the API backend along with the
 * Approov token then this allows the backend to check that the data value was indeed
 * known to the app at the time of the token fetch and hasn't been spoofed. If the
 * data is the same as any previous one set then the token does not need to be updated.
 * Otherwise the next token fetch causes a new attestation to fetch a new token. Note that
 * this should not be done frequently due to the additional latency on token fetching that
 * will be caused. The hash appears in the 'pay' claim of the Approov token as a base64
 * encoded string of the SHA256 hash of the data. Note that the data is hashed locally and
 * never sent to the Approov cloud service.
 *
 * @param data is the data whose SHA256 hash is to be included in future Approov tokens
 */
+ (void)setDataHashInToken:(nonnull NSString *)data;

/**
 * Obtains an integrity measurement proof that is used to show that the app and its
 * environment have not changed since the time of the original integrity measurement.
 * The proof does an HMAC calculation over the secret integrity measurement value which
 * is salted by a provided nonce. This proves that the SDK is able to reproduce the
 * integrity measurement value. This may return nil if the SDK has not been initialized
 * or if the parameters are in invalid.
 *
 * @param nonce is a 16-byte (128-bit) nonce value used to salt the proof HMAC
 * @param measurementConfig is the measurement configuration obtained from a previous token fetch results
 * @return 32-byte (256-bit) measurement proof value or nil if there was an error
 */
+ (nullable NSData *)getIntegrityMeasurementProof:(nonnull NSData *)nonce :(nonnull NSData *)measurementConfig;

/**
 * Obtains a device measurement proof that is used to show that the device environment
 * has not changed since the time of the original integrity measurement. This allows the
 * app version, including the Approov SDK, to be updated while preserving the device
 * measurement. The proof does an HMAC calculation over the secret device measurement
 * value which is salted by a provided nonce. This proves that the SDK is able to reproduce
 * the device measurement value. This may return nil if the SDK has not been initialized
 * or if the parameters are in invalid.
 *
 * @param nonce is a 16-byte (128-bit) nonce value used to salt the proof HMAC
 * @param measurementConfig is the measurement configuration obtained from a previous token fetch results
 * @return 32-byte (256-bit) measurement proof value or nil if there was an error
 */
+ (nullable NSData *)getDeviceMeasurementProof:(nonnull NSData *)nonce :(nonnull NSData *)measurementConfig;

/**
 * Gets the device ID used by Approov to identify the particular device that the SDK is running on. Note
 * that different Approov apps on the same device will return a different ID. Moreover, the ID may be
 * changed by an uninstall and reinstall of the app.
 *
 * @return String of device ID or nil if SDK not initialized.
 */
+ (nullable NSString *)getDeviceID;

/**
 * Gets the signature for the given message. This uses an account specific message signing key that is
 * transmitted to the SDK after a successful token fetch if the facility is enabled for the account. Note
 * that if the attestation failed then the signing key provided is actually random so that the
 * signature will be incorrect. An Approov token should always be included in the message
 * being signed and sent alongside this signature to prevent replay attacks.
 *
 * @param message is the content of the message to be signed
 * @return base64 encoded signature of the message, or nil if no signing key is available
 */
+ (nullable NSString *)getMessageSignature:(nonnull NSString *)message;

@end
