// Copyright (C) 2003-2026, Foxit Software Inc..
// All Rights Reserved.
//
// http://www.foxitsoftware.com
//
// The following code is copyrighted and contains proprietary information and trade secrets of Foxit Software Inc..
// You cannot distribute any part of Foxit PDF SDK to any third party or general public,
// unless there is a separate license agreement with Foxit Software Inc. which explicitly grants you such rights.
//
// This file contains an example to demonstrate how to use Foxit PDF SDK to do LTV verification (using default callback) in PDF document.

#include "FSPDFObjC.h"


#include <string>

NSString *input_path = @"../input_files/";
NSString *output_path = @"../output_files/";
NSString *output_directory = @"../output_files/ltv/";

NSString *sn = @"bPxThFlBAYifO3yiW0C51bKbjPap1zHaf5aG5sX378TjFvB7Q0802A==";
NSString *key = @"8f3g1sGNtR8NAgfSNv+BvaJWy2fnrw36NZvzh8GUxYXvs+/YczAk4e34yqlOnmL4asgxGMO7+rlOcSHUSSKagx5g4G7VEXfkYLXnDDx77FOOedZPrFFcOCu1vT4+wOKzy1zkORq9cKtiFf44byH5sfRqf+OvELjep2teT9eF9yPCsSw2vRKljXlPy40DMP9XX1O5OTe+mSG3RvwKUMb0+RpG3YobM87j+/ynTDlJMMXw4vgwuTb1ZuAtlvkTdFz7HDLV81C6TYnn73ddHeGD3qYWBVsD4uJkDwyyH/SWr7QX0HSgVg8HSZqnab1VGdAUYxKY2vGWeOTJdu6qDGb5ZEb9O3kZZg+9LYAmUKzDfCYwfIsYZ8Y9LPC4wMx70kIq7JpNnzPvigrdshV8/ew1F0+qP/P5Z1EB53vDVrp2LVSuL873Wv4g3PzjZlNH+1qgT87hC+Buto4qsSUfP5Y2z0uApu+c/TfttCXbeMLSsw9TY1HQD3mkrZExMaQqRrAuXAcZWdvBnVuG3DCZmm8q0DLxJiCfYfViAU/AkbeKMIGaTJdrvIXBcssfde4nmU6qr0M4x4+Hs2ESddtY2QRtLAUBq/7h7GJlJHkEZr8ueaUy/6D/2vvAWz6wMLgmjlPbDTLqzfnCJylHCPtcJmu1SPPT9v1VreBX3+zNWcIT8HueyHEiUniqzwWkRKaotrnNb/zhnHLPpgaBSwKjyi8rOzzVwu/nY84G9ye4jsVjmkIRItKDrW7z22iS+QQuTwfqLVuMB0OD4jMkPCC2slzPfEeZnuh4+Rewln2qLRJVgI72zJ20OBWAelGSpO1Ii2Y3gOvojdmtUwx6R3Z1qFNWTTB25o9ytcux8BLOtTCkA8T8xaT4N339OTCsu/TmfahIDixKvr8cDa6F2WkBT8SOfv64W9PgiNv5B/BMOD2VWYaAl0N7yD0NkYYg/cDWRC4dfpdeip1jrr0k3mND0oSpIkxC+FicZbaYYwRdtJ9OSVWJ5OE0voLv8HgLze8mw6s4TrN3z+a2NQCveMa2VRN7YcEF7agPrvb/qU+otMwHae/wM7+f+HFAaOW+WTfzfxIVFB8/A1DN0NiEX+em4G7nO5F5ZcvfYPpaaioe8Nn33BbVLtMjXqsOBeM8AXiVfpD6YO75j7BqnBUPnivvD8BxJaaIQZvVSuQmvGm0ynt03IYwE8Rq+jYsunvsZ86jh5BiUR+Nm7sK0Qe9BdZn/7jdqohMv9czfMGUAscX+xBMunFbDpcRbWVpUADcm209L8Ndek2XY7YlCMxPgPVNvfWRF2jxehib9Tpd7zB5ZIDfAEH9zrpLfp29DLvCh2Yx8mH7xxdKG1O9Q0GdHphYHhej6smAcPm68R0GUQaTOLfXqNpueGJC1GAOI3Y4swjyt7jMTWcibnKLzwukWE5rnEDhn5n64JnyUM0ZFkRmtOiV";

////////////////////////

std::string TransformSignatureStateToString(uint32 sig_state) {
    std::string state_str;
    if ((sig_state & FSSignatureStateUnknown) == FSSignatureStateUnknown)
        state_str += "Unknown";
    if (sig_state & FSSignatureStateNoSignData){
        if (state_str.length() > 0)
            state_str += "|";
        state_str += "NoSignData";
    }
    if (sig_state & FSSignatureStateUnsigned) {
        if (state_str.length() > 0)
            state_str += "|";
        state_str += "Unsigned";
    }
    if (sig_state & FSSignatureStateSigned) {
        if (state_str.length() > 0)
            state_str += "|";
        state_str += "Signed";
    }
    if (sig_state & FSSignatureStateVerifyValid) {
        if (state_str.length() > 0)
            state_str += "|";
        state_str += "VerfiyValid";
    }
    if (sig_state & FSSignatureStateVerifyInvalid) {
        if (state_str.length() > 0)
            state_str += "|";
        state_str += "VerifyInvalid";
    }
    if (sig_state & FSSignatureStateVerifyErrorData) {
        if (state_str.length() > 0)
            state_str += "|";
        state_str += "VerifyErrorData";
    }
    if (sig_state & FSSignatureStateVerifyNoSupportWay) {
        if (state_str.length() > 0)
            state_str += "|";
        state_str += "VerifyNoSupportWay";
    }
    if (sig_state & FSSignatureStateVerifyErrorByteRange) {
        if (state_str.length() > 0)
            state_str += "|";
        state_str += "VerifyErrorByteRange";
    }
    if (sig_state & FSSignatureStateVerifyChange) {
        if (state_str.length() > 0)
            state_str += "|";
        state_str += "VerifyChange";
    }
    if (sig_state & FSSignatureStateVerifyIncredible) {
        if (state_str.length() > 0)
            state_str += "|";
        state_str += "VerifyIncredible";
    }
    if (sig_state & FSSignatureStateVerifyNoChange) {
        if (state_str.length() > 0)
            state_str += "|";
        state_str += "VerifyNoChange";
    }
    if (sig_state & FSSignatureStateVerifyIssueValid) {
        if (state_str.length() > 0)
            state_str += "|";
        state_str += "VerifyIssueValid";
    }
    if (sig_state & FSSignatureStateVerifyIssueUnknown) {
        if (state_str.length() > 0)
            state_str += "|";
        state_str += "VerifyIssueUnknown";
    }
    if (sig_state & FSSignatureStateVerifyIssueRevoke) {
        if (state_str.length() > 0)
            state_str += "|";
        state_str += "VerifyIssueRevoke";
    }
    if (sig_state & FSSignatureStateVerifyIssueExpire) {
        if (state_str.length() > 0)
            state_str += "|";
        state_str += "VerifyIssueExpire";
    }
    if (sig_state & FSSignatureStateVerifyIssueUncheck) {
        if (state_str.length() > 0)
            state_str += "|";
        state_str += "VerifyIssueUncheck";
    }
    if (sig_state & FSSignatureStateVerifyIssueCurrent) {
        if (state_str.length() > 0)
            state_str += "|";
        state_str += "VerifyIssueCurrent";
    }
    if (sig_state & FSSignatureStateVerifyTimestampNone) {
        if (state_str.length() > 0)
            state_str += "|";
        state_str += "VerifyTimestampNone";
    }
    if (sig_state & FSSignatureStateVerifyTimestampDoc) {
        if (state_str.length() > 0)
            state_str += "|";
        state_str += "VerifyTimestampDoc";
    }
    if (sig_state & FSSignatureStateVerifyTimestampValid) {
        if (state_str.length() > 0)
            state_str += "|";
        state_str += "VerifyTimestampValid";
    }
    if (sig_state & FSSignatureStateVerifyTimestampInvalid) {
        if (state_str.length() > 0)
            state_str += "|";
        state_str += "VerifyTimestampInvalid";
    }
    if (sig_state & FSSignatureStateVerifyTimestampExpire) {
        if (state_str.length() > 0)
            state_str += "|";
        state_str += "VerifyTimestampExpire";
    }
    if (sig_state & FSSignatureStateVerifyTimestampIssueUnknown) {
        if (state_str.length() > 0)
            state_str += "|";
        state_str += "VerifyTimestampIssueUnknown";
    }
    if (sig_state & FSSignatureStateVerifyTimestampIssueValid) {
        if (state_str.length() > 0)
            state_str += "|";
        state_str += "VerifyTimestampIssueValid";
    }
    if (sig_state & FSSignatureStateVerifyTimestampTimeBefore) {
        if (state_str.length() > 0)
            state_str += "|";
        state_str += "VerifyTimestampTimeBefore";
    }
    if (sig_state & FSSignatureStateCertCannotGetVRI) {
        if (state_str.length()>0) state_str += "|";
        state_str += "CertCannotGetVRI";
    }
    return state_str;
}

void PKCS7Signature(NSString* input_pdf_path, NSString* signed_pdf_path) {
    FSPDFDoc *pdf_doc = [[FSPDFDoc alloc] initWithPath:input_pdf_path];
    [pdf_doc startLoad:nil is_cache_stream:NO pause:nil];
    FSPDFPage *pdf_page = [pdf_doc getPage:0];

    float page_height = [pdf_page getHeight];
    float page_width = [pdf_page getWidth];
    FSRectF *new_sig_rect = [[FSRectF alloc] initWithLeft1:(float)(page_width / 2 - 50.0f) bottom1:(float)(page_height / 2 - 50.0f) right1:(float) (page_width / 2 + 50.0f) top1:(float)(page_height / 2 + 50.0f)];
    // Add a new signature to page.
    FSSignature *new_signature = [pdf_page addSignature:new_sig_rect];
    if ([new_signature isEmpty])
        return ;
    NSString *filter = @"Adobe.PPKLite";
    NSString *sub_filter = @"adbe.pkcs7.detached";
    // Set values for the new signature.
    [new_signature setKeyValue:FSSignatureKeyNameSigner value:@"Foxit PDF SDK"];
    [new_signature setKeyValue:FSSignatureKeyNameContactInfo value:@"support@foxitsoftware.com"];
    [new_signature setKeyValue:FSSignatureKeyNameDN value:@"CN=CN,MAIL=MAIL@MAIL.COM"];
    [new_signature setKeyValue:FSSignatureKeyNameLocation value:@"Fuzhou, China"];
    NSString *new_value;
    new_value = [NSString stringWithFormat:@"As a sample for subfilter \"%@\"", sub_filter];
    [new_signature setKeyValue:FSSignatureKeyNameReason value:new_value];
    [new_signature setKeyValue:FSSignatureKeyNameText value:new_value];
    FSDateTime *sign_time = [FSDateTime getLocalTime];
    [new_signature setSignTime:sign_time];
    
    // Set appearance flags to decide which content would be used in appearance.
    uint32 ap_flags = FSSignatureAPFlagLabel | FSSignatureAPFlagSigner |
    FSSignatureAPFlagReason | FSSignatureAPFlagDN |
    FSSignatureAPFlagLocation | FSSignatureAPFlagText |
    FSSignatureAPFlagSigningTime;
    [new_signature setAppearanceFlags:ap_flags];
    
    // Set filter and subfilter for the new signature.
    [new_signature setFilter:filter];
    [new_signature setSubFilter:sub_filter];

    // Sign the new signature.
    NSString *cert_file_path = [input_path stringByAppendingPathComponent:@"foxit_all.pfx"];
    NSString *cert_file_password = @"123456";
    // Cert file path will be passed back to application through callback function FSSignatureCallback::Sign().
    // In this demo, the cert file path will be used for signing in callback function FSSignatureCallback::Sign().
    [new_signature startSign:cert_file_path cert_password:cert_file_password digest_algorithm:FSSignatureDigestSHA1 save_path:signed_pdf_path client_data:nil pause:nil];
}


// Here, the implementation of FSTrustedCertStoreCallback is very simple : 
// trust all input certificate(s) when this callback function is triggered during LTV verification.
// User can improve the implementation of the callback class FSTrustedCertStoreCallback or choose not to use FSTrustedCertStoreCallback.
@interface MyTrustedCertStoreCallback : NSObject<FSTrustedCertStoreCallback>

@end

@implementation MyTrustedCertStoreCallback {
}

- (id)init {
  return self;
}

-(BOOL)isCertTrusted:(NSData*)cert {
	return YES;
}

-(BOOL)isCertTrustedRoot:(NSData*)cert {
	return YES;
}
@end


void UseLTVVerifier(FSPDFDoc* pdf_doc, bool is_to_add_dss) {
    // Here use default RevocationCallback, so no need to call FSLTVVerifier::setRevocationCallback
    FSLTVVerifier* ltv_verifier = [[FSLTVVerifier alloc] initWithDocument:pdf_doc is_verify_signature:YES use_expired_tst:YES ignore_doc_info:NO time_type:FSLTVVerifierSignatureCreationTime];
    
    
    // Use implemented FSTrustedCertStoreCallback to trust some cerificates during LTV verification.
    // Here, the implementation of FSTrustedCertStoreCallback is very simple : 
    // trust all input certificate(s) when this callback function is triggered during LTV verification.
    // User can improve the implementation of the callback class FSTrustedCertStoreCallback or choose not to use FSTrustedCertStoreCallback.
    MyTrustedCertStoreCallback* my_callback = [[MyTrustedCertStoreCallback alloc] init];
    [ltv_verifier setTrustedCertStoreCallback:my_callback];
    
    [ltv_verifier setVerifyMode:FSLTVVerifierVerifyModeAcrobat];
    // TODO: ADJUST CODE FROM HERE.
    FSSignatureVerifyResultArray* sig_verify_result_array = [ltv_verifier verify];
    unsigned long array_size = [sig_verify_result_array getSize];
    for (unsigned long i = 0; i < array_size; i++) {
        FSSignatureVerifyResult* sig_verify_result = [sig_verify_result_array getAt:i];
        NSString* signature_name = [sig_verify_result getSignatureName];
        unsigned int sig_state = [sig_verify_result getSignatureState];
        FSSignatureVerifyResultLTVState ltv_state = [sig_verify_result getLTVState];
        NSString* ltv_state_str;
        switch (ltv_state) {
            case FSSignatureVerifyResultLTVStateInactive:
                ltv_state_str = @"inactive";
                break;
            case FSSignatureVerifyResultLTVStateEnable:
                ltv_state_str = @"enabled";
                break;
            case FSSignatureVerifyResultLTVStateNotEnable:
                ltv_state_str = @"not enabled";
                break;
        }
        printf("Signature name:%s, signature state: %s, LTV state: %s\r\n",
               [signature_name UTF8String],
               TransformSignatureStateToString(sig_state).c_str(),
               [ltv_state_str UTF8String]);
    }
    
    if (is_to_add_dss) {
        for (size_t i = 0; i < array_size; i++) {
            FSSignatureVerifyResult* sig_verify_result = [sig_verify_result_array getAt:i];
            if (([sig_verify_result getSignatureState] & FSSignatureStateVerifyValid) == FSSignatureStateVerifyValid)
                [ltv_verifier addDSS:sig_verify_result];
        }
    }
}

void DoLTV(NSString* signed_pdf_path, NSString* saved_ltv_pdf_path) {
    // Use default SignatureCallback for signing a time stamp signature with filter "Adobe.PPKLite" and subfilter "ETSI.RFC3161",
    // so no need to register a custom signature callback.
    NSString* free_server_name = @"FreeTSAServer";
    NSString* free_server_url = @"http://ca.signfiles.com/TSAServer.aspx";
    [FSTimeStampServerMgr initialize];
    FSTimeStampServer* timestamp_server = [FSTimeStampServerMgr addServer:free_server_name server_url:free_server_url user_name:@"" password:@""];
    [FSTimeStampServerMgr setDefaultServer:timestamp_server];
    
    FSPDFDoc *pdf_doc = [[FSPDFDoc alloc] initWithPath:signed_pdf_path];
    [pdf_doc startLoad:nil is_cache_stream:NO pause:nil];
    // Add DSS
    printf("== Before Add DSS ==\r\n");
    UseLTVVerifier(pdf_doc, true);
    
    // Add DTS
    FSPDFPage *pdf_page = [pdf_doc getPage:0];
    // The new time stamp signature will have default filter name "Adobe.PPKLite" and default subfilter name "ETSI.RFC3161".
    FSRectF* empty_rect = [[FSRectF alloc] init];
    FSSignature* timestamp_signature = [pdf_page addSignatureWithSignatureType:empty_rect field_name:@"" signature_type:FSSignatureSignatureTypeTimeStamp to_check_permission:YES];
    [timestamp_signature startSign:@"" cert_password:@"" digest_algorithm:FSSignatureDigestSHA1 save_path:saved_ltv_pdf_path client_data:nil pause:nil];
    
    // Check saved file.
    FSPDFDoc *check_pdf_doc = [[FSPDFDoc alloc] initWithPath:saved_ltv_pdf_path];
    [check_pdf_doc startLoad:nil is_cache_stream:NO pause:nil];
    // Just LTV veify.
    printf("== After Add DSS ==\r\n");
    UseLTVVerifier(check_pdf_doc, false);
    
    [FSTimeStampServerMgr destroy];
}

int main(int argc, const char *argv[]) {
    @autoreleasepool {
        NSString *output_directory = [output_path stringByAppendingPathComponent:@"ltv/"];

        NSFileManager *fileManager = [NSFileManager defaultManager];
        BOOL isDir = NO;
        if ([fileManager fileExistsAtPath:output_directory isDirectory:&isDir]) {
            if (!isDir) {
                NSError *error = nil;
                if (![fileManager removeItemAtPath:output_directory error:&error] || error) {
                    return -1;
                }
                if (![fileManager createDirectoryAtPath:output_directory withIntermediateDirectories:YES attributes:nil error:&error] || error) {
                    return -1;
                }
            }
        } else {
            NSError *error = nil;
            if (![fileManager createDirectoryAtPath:output_directory withIntermediateDirectories:YES attributes:nil error:&error] || error) {
                return -1;
            }
        }

        // Initialize library
        FSErrorCode errorCode = [FSLibrary initialize:sn key:key];
        if (errorCode != FSErrSuccess) {
            NSLog(@"Library Initialize Error: %ld", errorCode);
            return -1;
        }

        NSString* input_file_name = @"AboutFoxit.pdf";
        NSString *input_file_path = [input_path stringByAppendingPathComponent:input_file_name];
        printf("Input file path: %s\n", [input_file_path UTF8String]);
        @try {
            // Add a PKCS7 signature and sign it.
            NSString* file_name = [input_file_name substringToIndex:(input_file_name.length-4)];
            NSString* signed_file_name = [file_name stringByAppendingString:@"_signed.pdf"];
            NSString* signed_pdf_path = [output_directory stringByAppendingPathComponent:signed_file_name];
            PKCS7Signature(input_file_path, signed_pdf_path);
            
            // Do LTV
            NSString* temp_substring = [signed_pdf_path substringToIndex:(signed_pdf_path.length-4)];
            NSString* saved_ltv_pdf_path = [temp_substring stringByAppendingString:@"_ltv.pdf"];
            DoLTV(signed_pdf_path, saved_ltv_pdf_path);
        }
        @catch (NSException *e) {
            NSLog(@"Unknown Exception, %@", e);
            return -1;
        }
    }
    [FSLibrary destroy];
    return 0;
}
