#import "ConfirmPacket.h"
#import "Util.h"
#import "Conversions.h"

@implementation ConfirmPacket

@synthesize confirmMac, hashChainH0, unusedAndSignatureLengthAndFlags, cacheExperationInterval, isPart1, iv;

#define TRUNCATED_HMAC_LENGTH 8
#define CONFIRM_IV_LENGTH 16
#define WORD_LENGTH 4

+(ConfirmPacket*) confirm1PacketWithHashChain:(HashChain*)hashChain
                                    andMacKey:(NSData*)macKey
                                 andCipherKey:(NSData*)cipherKey
                                        andIv:(NSData*)iv {
    return [ConfirmPacket confirmPacketWithHashChainH0:[hashChain h0]
                   andUnusedAndSignatureLengthAndFlags:0
                            andCacheExpirationInterval:0xFFFFFFFF
                                             andMacKey:macKey
                                          andCipherKey:cipherKey
                                                 andIv:iv
                                            andIsPart1:true];
}

+(ConfirmPacket*) confirm2PacketWithHashChain:(HashChain*)hashChain
                                    andMacKey:(NSData*)macKey
                                 andCipherKey:(NSData*)cipherKey
                                        andIv:(NSData*)iv {
    return [ConfirmPacket confirmPacketWithHashChainH0:[hashChain h0]
                   andUnusedAndSignatureLengthAndFlags:0
                            andCacheExpirationInterval:0xFFFFFFFF
                                             andMacKey:macKey
                                          andCipherKey:cipherKey
                                                 andIv:iv
                                            andIsPart1:false];
}

+(ConfirmPacket*) confirmPacketParsedFromHandshakePacket:(HandshakePacket*)handshakePacket
                                              withMacKey:(NSData*)macKey
                                            andCipherKey:(NSData*)cipherKey
                                              andIsPart1:(bool)isPart1 {
    require(handshakePacket != nil);
    require(macKey != nil);
    require(cipherKey != nil);
    NSData* expectedConfirmTypeId = isPart1 ? HANDSHAKE_TYPE_CONFIRM_1 : HANDSHAKE_TYPE_CONFIRM_2;
    
    checkOperation([[handshakePacket typeId] isEqualToData:expectedConfirmTypeId]);
    checkOperation([[handshakePacket payload] length] == TRUNCATED_HMAC_LENGTH + CONFIRM_IV_LENGTH + HASH_CHAIN_ITEM_LENGTH + WORD_LENGTH + WORD_LENGTH);
    NSData* decryptedData = [self getDecryptedDataFromPayload:[handshakePacket payload] withMacKey:macKey andCipherKey:cipherKey];
    
    return [ConfirmPacket confirmPacketParsedFrom:handshakePacket
                                  withHashChainH0:[self getHashChainH0FromDecryptedData:decryptedData]
              andUnusedAndSignatureLengthAndFlags:[self getUnusedAndSignatureLengthAndFlagsFromDecryptedData:decryptedData]
                       andCacheExpirationInterval:[self getCacheExpirationIntervalFromDecryptedData:decryptedData]
                                  andIncludedHmac:[self getIncludedHmacFromPayload:[handshakePacket payload]]
                                            andIv:[self getIvFromPayload:[handshakePacket payload]]
                                       andIsPart1:isPart1];
}
+(ConfirmPacket*) confirmPacketWithHashChainH0:(NSData*)hashChainH0
           andUnusedAndSignatureLengthAndFlags:(uint32_t)unused
                    andCacheExpirationInterval:(uint32_t)cacheExpirationInterval
                                     andMacKey:(NSData*)macKey
                                  andCipherKey:(NSData*)cipherKey
                                         andIv:(NSData*)iv
                                    andIsPart1:(bool)isPart1 {
    require(macKey != nil);
    require(cipherKey != nil);
    require(hashChainH0 != nil);
    require(iv != nil);
    require([iv length] == CONFIRM_IV_LENGTH);
    require([hashChainH0 length] == HASH_CHAIN_ITEM_LENGTH);
    
    ConfirmPacket* p = [ConfirmPacket new];
    p->hashChainH0 = hashChainH0;
    p->unusedAndSignatureLengthAndFlags = unused;
    p->cacheExperationInterval = cacheExpirationInterval;
    p->iv = iv;
    p->isPart1 = isPart1;
    p->embedding = [p generateEmbedding:cipherKey andMacKey:macKey];
    return p;
}

-(HandshakePacket*) generateEmbedding:(NSData*)cipherKey andMacKey:(NSData*)macKey {
    require(cipherKey != nil);
    require(macKey != nil);
    
    NSData* sensitiveData = [@[
                             hashChainH0,
                             [NSData dataWithBigEndianBytesOfUInt32:unusedAndSignatureLengthAndFlags],
                             [NSData dataWithBigEndianBytesOfUInt32:cacheExperationInterval]
                             ] concatDatas];
    
    NSData* encrytedSensitiveData = [sensitiveData encryptWithAesInCipherFeedbackModeWithKey:cipherKey andIv:iv];
    NSData* hmacForSensitiveData = [[encrytedSensitiveData hmacWithSha256WithKey:macKey] take:TRUNCATED_HMAC_LENGTH];
    
    NSData* typeId = isPart1 ? HANDSHAKE_TYPE_CONFIRM_1 : HANDSHAKE_TYPE_CONFIRM_2;
    
    NSData* payload = [@[
                       hmacForSensitiveData,
                       iv,
                       encrytedSensitiveData
                       ] concatDatas];
    
    return [HandshakePacket handshakePacketWithTypeId:typeId andPayload:payload];
}

+(ConfirmPacket*) confirmPacketParsedFrom:(HandshakePacket*)source
                          withHashChainH0:(NSData*)hashChainH0
      andUnusedAndSignatureLengthAndFlags:(uint32_t)unused
               andCacheExpirationInterval:(uint32_t)cacheExpirationInterval
                          andIncludedHmac:(NSData*)includedHmac
                                    andIv:(NSData*)iv
                               andIsPart1:(bool)isPart1 {
    
    ConfirmPacket* p = [ConfirmPacket new];
    
    p->hashChainH0 = hashChainH0;
    p->unusedAndSignatureLengthAndFlags = unused;
    p->cacheExperationInterval = cacheExpirationInterval;
    p->iv = iv;
    p->isPart1 = isPart1;
    p->confirmMac = includedHmac;
    p->embedding = source;
    return p;
    
}

+(NSData*) getIncludedHmacFromPayload:(NSData*)payload {
    return [payload take:TRUNCATED_HMAC_LENGTH];
}
+(NSData*) getIvFromPayload:(NSData*)payload {
    return [payload subdataVolatileWithRange:NSMakeRange(TRUNCATED_HMAC_LENGTH, CONFIRM_IV_LENGTH)];
}
+(NSData*) getVolatileEncryptedDataFromPayload:(NSData*)payload {
    return [payload skipVolatile:TRUNCATED_HMAC_LENGTH + CONFIRM_IV_LENGTH];
}
+(NSData*) getDecryptedDataFromPayload:(NSData*)payload
                            withMacKey:(NSData*)macKey
                          andCipherKey:(NSData*)cipherKey {
    
    NSData* iv = [self getIvFromPayload:payload];
    NSData* volatileEncryptedData = [self getVolatileEncryptedDataFromPayload:payload];
    NSData* includedHmac = [self getIncludedHmacFromPayload:payload];
    
    NSData* expectedHmac = [[volatileEncryptedData hmacWithSha256WithKey:macKey] take:TRUNCATED_HMAC_LENGTH];
    checkOperation([includedHmac isEqualToData_TimingSafe:expectedHmac]);
    
    return [volatileEncryptedData decryptWithAesInCipherFeedbackModeWithKey:cipherKey andIv:iv];
}
+(NSData*) getHashChainH0FromDecryptedData:(NSData*)decryptedData {
    return [decryptedData take:HASH_CHAIN_ITEM_LENGTH];
}
+(uint32_t) getUnusedAndSignatureLengthAndFlagsFromDecryptedData:(NSData*)decryptedData {
    return [decryptedData bigEndianUInt32At:HASH_CHAIN_ITEM_LENGTH];
}
+(uint32_t) getCacheExpirationIntervalFromDecryptedData:(NSData*)decryptedData {
    return [decryptedData bigEndianUInt32At:HASH_CHAIN_ITEM_LENGTH+WORD_LENGTH];
}

-(HandshakePacket*) embeddedIntoHandshakePacket {
    return embedding;
}

@end
