/* ====================================================================
   Licensed to the Apache Software Foundation (ASF) under one or more
   contributor license agreements.  See the NOTICE file distributed with
   this work for additional information regarding copyright ownership.
   The ASF licenses this file to You under the Apache License, Version 2.0
   (the "License"); you may not use this file except in compliance with
   the License.  You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
==================================================================== */

package org.apache.poi.hssf.record;

import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.io.ByteArrayInputStream;

import org.apache.poi.EncryptedDocumentException;
import org.apache.poi.hssf.record.crypto.Biff8EncryptionKey;
import org.apache.poi.util.HexRead;
import org.junit.jupiter.api.Test;

/**
 * Tests for {@link RecordFactoryInputStream}
 */
final class TestRecordFactoryInputStream {
    /**
     * Hex dump of a BOF record and most of a FILEPASS record.
     * A 16 byte saltHash should be added to complete the second record
     */
    private static final String COMMON_HEX_DATA = ""
        // BOF
        + "09 08 10 00"
        + "00 06  05 00  D3 10  CC 07  01 00 00 00  00 06 00 00"
        // FILEPASS
        + "2F 00 36 00"
        + "01 00  01 00  01 00"
        + "BAADF00D BAADF00D BAADF00D BAADF00D" // docId
        + "DEADBEEF DEADBEEF DEADBEEF DEADBEEF" // saltData
        ;

    /**
     * Hex dump of a sample WINDOW1 record
     */
    private static final String SAMPLE_WINDOW1 = "3D 00 12 00"
        + "00 00 00 00 40 38 55 23 38 00 00 00 00 00 01 00 58 02";


    /**
     * Makes sure that a default password mismatch condition is represented with {@link EncryptedDocumentException}
     */
    @Test
    void defaultPasswordWrong() {
        // This encodng depends on docId, password and stream position
        final String SAMPLE_WINDOW1_ENCR1 = "3D 00 12 00"
            + "C4, 9B, 02, 50, 86, E0, DF, 34, FB, 57, 0E, 8C, CE, 25, 45, E3, 80, 01";

        byte[] dataWrongDefault = HexRead.readFromString(""
                + COMMON_HEX_DATA
                + "00000000 00000000 00000000 00000001"
                + SAMPLE_WINDOW1_ENCR1
        );

        EncryptedDocumentException ex = assertThrows(
            EncryptedDocumentException.class,
            () -> createRFIS(dataWrongDefault)
        );
        assertTrue(ex.getMessage().contains("Default password is invalid for salt/verifier/verifierHash"));
    }

    @Test
    void defaultPasswordOK() {
        // This encodng depends on docId, password and stream position
        final String SAMPLE_WINDOW1_ENCR1 = "3D 00 12 00"
            + "C4, 9B, 02, 50, 86, E0, DF, 34, FB, 57, 0E, 8C, CE, 25, 45, E3, 80, 01";

        byte[] dataCorrectDefault = HexRead.readFromString(""
                + COMMON_HEX_DATA
                + "137BEF04 969A200B 306329DE 52254005" // correct saltHash for default password (and docId/saltHash)
                + SAMPLE_WINDOW1_ENCR1
        );

        RecordFactoryInputStream rfis = createRFIS(dataCorrectDefault);
        confirmReadInitialRecords(rfis);
    }


    /**
     * Makes sure that an incorrect user supplied password condition is represented with {@link EncryptedDocumentException}
     */
    @Test
    void suppliedPasswordWrong() {
        // This encoding depends on docId, password and stream position
        final String SAMPLE_WINDOW1_ENCR2 = "3D 00 12 00"
            + "45, B9, 90, FE, B6, C6, EC, 73, EE, 3F, 52, 45, 97, DB, E3, C1, D6, FE";

        byte[] dataWrongDefault = HexRead.readFromString(""
                + COMMON_HEX_DATA
                + "00000000 00000000 00000000 00000000"
                + SAMPLE_WINDOW1_ENCR2
        );

        Biff8EncryptionKey.setCurrentUserPassword("passw0rd");
        try {
            EncryptedDocumentException ex = assertThrows(
                EncryptedDocumentException.class,
                () -> createRFIS(dataWrongDefault)
            );
            assertEquals("Supplied password is invalid for salt/verifier/verifierHash", ex.getMessage());
        } finally {
            Biff8EncryptionKey.setCurrentUserPassword(null);
        }
    }

    @Test
    void suppliedPasswordOK() {
        // This encoding depends on docId, password and stream position
        final String SAMPLE_WINDOW1_ENCR2 = "3D 00 12 00"
            + "45, B9, 90, FE, B6, C6, EC, 73, EE, 3F, 52, 45, 97, DB, E3, C1, D6, FE";

        byte[] dataCorrectDefault = HexRead.readFromString(""
                + COMMON_HEX_DATA
                + "C728659A C38E35E0 568A338F C3FC9D70" // correct saltHash for supplied password (and docId/saltHash)
                + SAMPLE_WINDOW1_ENCR2
        );

        Biff8EncryptionKey.setCurrentUserPassword("passw0rd");
        try {
            RecordFactoryInputStream rfis = createRFIS(dataCorrectDefault);
            confirmReadInitialRecords(rfis);
        } finally {
            Biff8EncryptionKey.setCurrentUserPassword(null);
        }
    }


    /**
     * makes sure the record stream starts with {@link BOFRecord}, {@link FilePassRecord} and then {@link WindowOneRecord}
     * The third record is decrypted so this method also checks its content.
     */
    private void confirmReadInitialRecords(RecordFactoryInputStream rfis) {
        assertEquals(BOFRecord.class, rfis.nextRecord().getClass());
        FilePassRecord recFP = (FilePassRecord) rfis.nextRecord();
        WindowOneRecord rec1 = (WindowOneRecord) rfis.nextRecord();
        assertArrayEquals(HexRead.readFromString(SAMPLE_WINDOW1),rec1.serialize());
    }

    private static RecordFactoryInputStream createRFIS(byte[] data) {
        return new RecordFactoryInputStream(new ByteArrayInputStream(data), true);
    }
}
