/*
 * Copyright (C) 2020 The Android Open Source Project
 *
 * Licensed 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 com.android.internal.net.eap.test;

import static com.android.internal.net.TestUtils.hexStringToByteArray;
import static com.android.internal.net.eap.test.crypto.TlsSessionTest.RESULT_FINISHED_OK;
import static com.android.internal.net.eap.test.crypto.TlsSessionTest.RESULT_NEED_UNWRAP_OK;
import static com.android.internal.net.eap.test.crypto.TlsSessionTest.RESULT_NEED_WRAP_OK;
import static com.android.internal.net.eap.test.crypto.TlsSessionTest.RESULT_NOT_HANDSHAKING_OK;
import static com.android.internal.net.eap.test.message.EapTestMessageDefinitions.EAP_REQUEST_AKA_IDENTITY_PACKET;
import static com.android.internal.net.eap.test.message.EapTestMessageDefinitions.EAP_SUCCESS;

import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.argThat;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;

import android.net.eap.test.EapSessionConfig;

import com.android.internal.net.eap.test.crypto.TlsSession;
import com.android.internal.net.eap.test.crypto.TlsSessionFactory;
import com.android.internal.net.eap.test.statemachine.EapStateMachine;
import com.android.internal.net.eap.test.statemachine.EapTtlsMethodStateMachine;

import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentMatcher;

import java.nio.ByteBuffer;
import java.util.Arrays;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLSession;

/**
 * End-to-end tests for EAP-TTLS
 *
 * <p>Due to limitations where Conscrypt's SSLEngine offloads to native BoringSSL code, the TLS
 * client random is generated by hardware and CANNOT be seeded or mocked. As such, this test mocks
 * out the entire SSLEngine code.
 *
 * <p>The same is true for TLS keying material generation which is mocked via a TlsSession spy.
 */
// TODO(b/166781047): Add second SSLEngine to EAP-TTLS end-to-end testing
public class EapTtlsTest extends EapMethodEndToEndTest {
    private static final long AUTHENTICATOR_TIMEOUT_MILLIS = 250L;

    private static final int APPLICATION_BUFFER_SIZE_TLS_MESSAGE = 16384;
    private static final int PACKET_BUFFER_SIZE_TLS_MESSAGE = 16384;
    private static final byte[] EMPTY_BYTE_ARRAY = new byte[PACKET_BUFFER_SIZE_TLS_MESSAGE];

    public static final byte[] TLS_MSK =
            hexStringToByteArray(
                    "00112233445566778899AABBCCDDEEFF"
                            + "00112233445566778899AABBCCDDEEFF"
                            + "00112233445566778899AABBCCDDEEFF"
                            + "00112233445566778899AABBCCDDEEFF");

    public static final byte[] TLS_EMSK =
            hexStringToByteArray(
                    "FFEEDDCCBBAA99887766554433221100"
                            + "FFEEDDCCBBAA99887766554433221100"
                            + "FFEEDDCCBBAA99887766554433221100"
                            + "FFEEDDCCBBAA99887766554433221100");

    // TUNNELED MSCHAPV2 (Phase 2)

    private static final String MSCHAPV2_USERNAME = "mschapv2.android.net";
    private static final String MSCHAPV2_PASSWORD = "mschappwd";
    private static final byte[] PEER_CHALLENGE =
            hexStringToByteArray("6592788337ED192AA396532E0AE65579");
    private static final byte[] MSCHAPV2_MSK =
            hexStringToByteArray(
                    "FA90B81CFF52D4FE37E029C84EC1E8B442AE19E482B0CA63FAEFF833C2DC86E60000"
                            + "000000000000000000000000000000000000000000000000000000000000");
    private static final int EMSK_LEN = 64;
    private static final byte[] MSCHAPV2_EMSK = new byte[EMSK_LEN];

    // TLSv1.2 Handshake Messages

    private static final String CLIENT_HELLO_STRING =
            "1603010085010000810303DE76A65F038E90315BB25B49CB9AB4E2540586C3B25851604C8D6E"
                    + "FECA11C16D00001CC02BC02CCCA9C02FC030CCA8C009C00AC013C014009C009D00"
                    + "2F00350100003C00170000FF01000100000A00080006001D00170018000B000201"
                    + "00000500050100000000000D001400120403080404010503080505010806060102"
                    + "01";
    private static final byte[] CLIENT_HELLO_BYTES = hexStringToByteArray(CLIENT_HELLO_STRING);

    private static final String SERVER_HELLO_INITIAL_FRAGMENT_STRING =
            "16030304140200003603035F46FED4999A26EE4CA1E4DB034E3BADAD262359A36885E19187F8"
                    + "5454F08E9E105188276593B74D74C1E95C9779EE83E9002F000B00037300037000"
                    + "036D3082036930820251A0030201020208570C6688A0ABF919300D06092A864886"
                    + "F70D01010B05003042310B30090603550406130255533110300E060355040A1307"
                    + "416E64726F69643121301F06035504031318726F6F742E63612E746573742E616E"
                    + "64726F69642E6E6574301E170D3230303530393031323331305A170D3233303530"
                    + "393031323331305A3045310B30090603550406130255533110300E060355040A13"
                    + "07416E64726F6964312430220603550403131B7365727665722E746573742E696B"
                    + "652E616E64726F69642E6E657430820122300D06092A864886F70D010101050003"
                    + "82010F003082010A0282010100C66872A68B0CFDD8453EF987A62AA4F9B251DB27"
                    + "F0E4079686CA678EE2090755F7FD314245398B2EC43C5464509D2298940609DABE"
                    + "57A60610D3DAF80D801B1F02F6A1262D92D333A9AC76D89951EF7733647E03DD44"
                    + "ED2A4EC274BB5CD4D3C55D8166949F6E13F4C3D695EF5F08FDBDD629F2D56E96CD"
                    + "91BFE84136D80CE274F516AC8CA69D6CAF9B762C8771F96A434783C2F02EC71317"
                    + "AEC38C0E058EA65A9E2E12B5AA0C505F40D237773C29C9246D9EE3152925F941C8"
                    + "FA67E6EA2A6B408009D9A9DA0B7A09F8B016C42C3156D5060DAA9F5081D24EF4FF"
                    + "51DA523C2D9BCA71DDB207CCC9DBE24AD447E1F44AF386A16745CD41CDBB01C4A2"
                    + "416E9A595D0203010001A360305E301F0603551D23041830168014499FA84785D7"
                    + "8363F757A422947EB1584212801A30260603551D11041F301D821B736572766572"
                    + "2E746573742E696B652E616E64726F69642E6E657430130603551D25040C300A06"
                    + "082B06010505070301300D06092A864886F70D01010B050003820101007067F0B9"
                    + "9698CA73410D703C83D294646D8734EF92C6CCBBE5E80A37A2B57DDD471739A8E2"
                    + "5BF03C3BB571CEBA423B80B294AAD8341690A9094D680910057B36C5AF58431421"
                    + "AE038A71A56FC9EA1C1B160C6B97F7326B364A0A5033A818DC37EDE5031FCF7D75"
                    + "2683DAAFDE59FBB030F754F3D32FBCCD74D7A594967F2F8E2A921099689A61B7BF"
                    + "9678F48F817BCF873B04A45112A17A4818981E7BF83F1DCB038C000DDCFD372A9D"
                    + "00DADC7A43A5DA3C9C0BB36111844A72110CE7B3CED711705D5774B64C2419AAF7"
                    + "888BBBC093CA0B7CFA8E118279B2AE30C0D28E0EE203B1D9DE34C29E58A8BA50B5"
                    + "4CBB917CFFC87604C2646CFFB7511A557F951CDD4E0D00005B020140000E000C04"
                    + "0105010601030102010101004600443042310B3009060355040613025553311030"
                    + "0E060355040A1307416E64726F69643121301F";
    private static final byte[] SERVER_HELLO_INITIAL_FRAGMENT_BYTES =
            hexStringToByteArray(SERVER_HELLO_INITIAL_FRAGMENT_STRING);
    private static final String SERVER_HELLO_FINAL_FRAGMENT_STRING =
            "06035504031318726F6F742E63612E746573742E616E64726F69642E6E65740E000000";
    private static final byte[] SERVER_HELLO_FINAL_FRAGMENT_BYTES =
            hexStringToByteArray(SERVER_HELLO_FINAL_FRAGMENT_STRING);
    private static final byte[] SERVER_HELLO_BYTES =
            hexStringToByteArray(
                    SERVER_HELLO_INITIAL_FRAGMENT_STRING + SERVER_HELLO_FINAL_FRAGMENT_STRING);

    private static final String CLIENT_FINISHED_STRING =
            "16030300070B0000030000001603030106100001020100A061D18580E2ACA2FDBB714012D180C"
                    + "595CB51ED7B89EF394CFF7265C68756CF55CBEE47284A0992B3FC64A77F28E5E98F"
                    + "B4F681BD67A87CD4DA115D70C74F96EB7AFCA822C0000824AA6C73DAA1233535EE0"
                    + "B63D0E52F5EB645BC617170957B68ADEDE770559899813C0F174787F0C73ED87A2A"
                    + "ED88D3C4438D8990D16DE9D6125EA772342123609941DD99CDE4A79E24D6C300AE5"
                    + "245266275EA73B5FE4955ED7C25D34D60E7E925B33EDA75BE14A5B16FC45E7E6BDA"
                    + "11C449A21D7455DD42D6B9418D04210CF50FD7D0A2B79882A4BE3EE68C2E9149F65"
                    + "465432B483255B1D0076F74030D252131FE92DEB72B7FA40DE649F51AAE4BC28A31"
                    + "ED8670E3AC221403030001011603030040016BFABBF2C74A11DDCFAFC8D815FA7C0"
                    + "B4D8705A4BC44699242B5C4D413A1EEDBAAD2F1951713E35B35A99D9B3EA916F12F"
                    + "297CEEC349173FE75C1B87F1CD66";
    private static final byte[] CLIENT_FINISHED_BYTES =
            hexStringToByteArray(CLIENT_FINISHED_STRING);

    private static final String SERVER_FINISHED_STRING =
            "14030300010116030300404570EA39954A9A370FDFCADC5DEDC072B9C204974645FD9D3E22383"
                    + "26B76D2160F6EB88433C2E64FEB0F9204AC963D92399417166EC7CBF2E4F723C879"
                    + "0C1255";
    private static final byte[] SERVER_FINISHED_BYTES =
            hexStringToByteArray(SERVER_FINISHED_STRING);

    // Phase 2 Messages (EAP-MSCHAPV2)

    private static final String ENCRYPTED_EAP_IDENTITY_AVP_STRING =
            "170303004070739851715B5BA7CC0237522560F811BFB47A7877E6882B524A3225B30EED1C66E7"
                    + "BE834C94B411995CF01C824E19B51E9E6D17AB273E218733071390D16752";
    private static final byte[] ENCRYPTED_EAP_IDENTITY_AVP_BYTES =
            hexStringToByteArray(ENCRYPTED_EAP_IDENTITY_AVP_STRING);
    private static final String DECRYPTED_EAP_IDENTITY_AVP_STRING =
            "0000004F" + "40" + "00000D" // AVP Code | AVP Flags | AVP Length
                    + "0210000501" // EAP-Response/Identity
                    + "000000"; // Padding
    private static final byte[] DECRYPTED_EAP_IDENTITY_AVP_BYTES =
            hexStringToByteArray(DECRYPTED_EAP_IDENTITY_AVP_STRING);

    private static final String EAP_MSCHAP_V2_ENCRYPTED_CHALLENGE_REQUEST_AVP_STRING =
            "1703030060AB010671AA326B4421D4F7336C476D0595E7C9104ADD9975FAC10CDA63DB82DB989F7"
                    + "6A6E41C2FAFE6C4C3BA4D4F49F08FF1FC88F0AD47BA9913D962A2D6F0C95D68AE879E"
                    + "A305FFE6A9D999C79A4B20D3B4C067A0E17F848D44C16AF61B27F5";
    private static final byte[] EAP_MSCHAP_V2_ENCRYPTED_CHALLENGE_REQUEST_AVP_BYTES =
            hexStringToByteArray(EAP_MSCHAP_V2_ENCRYPTED_CHALLENGE_REQUEST_AVP_STRING);
    private static final String EAP_MSCHAP_V2_DECRYPTED_CHALLENGE_REQUEST_AVP_STRING =
            "0000004F" + "40" + "00002C" // AVP Code | AVP Flags | AVP Length
                    + "01640024" // EAP-Request | ID | length in bytes
                    + "1A0164" // EAP-MSCHAPv2 | Request | MSCHAPv2 ID
                    + "001F10" // MS length | Value Size (0x10)
                    + "DCB648175C0A8200F226F94F0964F9DE" // Authenticator-Challenge
                    + "6D736368617074657374"; // Server-Name hex("mschaptest")
    private static final byte[] EAP_MSCHAP_V2_DECRYPTED_CHALLENGE_REQUEST_AVP_BYTES =
            hexStringToByteArray(EAP_MSCHAP_V2_DECRYPTED_CHALLENGE_REQUEST_AVP_STRING);

    private static final String EAP_MSCHAP_V2_ENCRYPTED_CHALLENGE_RESPONSE_AVP_STRING =
            "17030300805D5941EECB4D1F040B3C199FBB078393220526B3C7A7F73D16C0D45EBE56CBA1E80E0"
                    + "1EB5B56E367B27B211C05D3713E389516B14568FB95679F960D61B5620F23D49B2A8F"
                    + "999C308004B389111F49B56F3AFDFEE4765ADF2ADBBB82E7D5AE8D4E25FB10D67091E"
                    + "E39E3A39F0F1AFB720231E3D824565349550BC7988C4E2E39";
    private static final byte[] EAP_MSCHAP_V2_ENCRYPTED_CHALLENGE_RESPONSE_AVP_BYTES =
            hexStringToByteArray(EAP_MSCHAP_V2_ENCRYPTED_CHALLENGE_RESPONSE_AVP_STRING);
    private static final String EAP_MSCHAP_V2_DECRYPTED_CHALLENGE_RESPONSE_AVP_STRING =
            "0000004F" + "40" + "000057" // AVP Code | AVP Flags | AVP Length
                    + "0264004F" // EAP-Response | ID | length in bytes
                    + "1A0264" // EAP-MSCHAPv2 | Response | MSCHAPv2 ID
                    + "004A31" // MS length | Value Size (0x31)
                    + "6592788337ED192AA396532E0AE65579" // Peer-Challenge
                    + "0000000000000000" // 8B (reserved)
                    + "6027E628F0090D596D4FF5FE451FC537CD54F7BD70F05C73" // NT-Response
                    + "00" // Flags
                    + "6D736368617076322E616E64726F69642E6E657400"; // hex(USERNAME)
    private static final byte[] EAP_MSCHAP_V2_DECRYPTED_CHALLENGE_RESPONSE_AVP_BYTES =
            hexStringToByteArray(EAP_MSCHAP_V2_DECRYPTED_CHALLENGE_RESPONSE_AVP_STRING);

    private static final String EAP_MSCHAP_V2_ENCRYPTED_SUCCESS_REQUEST_AVP_STRING =
            "17030300800A7516313DA811E690BAF1E76B5C25A1B57B891FC03AECDE89B5C75044B3111966EF91"
                    + "49ADA96F0720C055C9A124001097F1BD5E9728A38CA160BA433A95077B5B5367EDF8E3"
                    + "2EAAD7CDDED43BBDAEC4C1AD2CC919D591B3A744CCE1868295AD5F0115E7443E74AEA4"
                    + "38CFF96E13ED36F0E539537CE676E251B82BA9B1153569";
    private static final byte[] EAP_MSCHAP_V2_ENCRYPTED_SUCCESS_REQUEST_AVP_BYTES =
            hexStringToByteArray(EAP_MSCHAP_V2_ENCRYPTED_SUCCESS_REQUEST_AVP_STRING);
    private static final String EAP_MSCHAP_V2_DECRYPTED_SUCCESS_REQUEST_AVP_STRING =
            "0000004F" + "40" + "000051" // AVP Code | AVP Flags | AVP Length
                    + "01650049" // EAP-Request | ID | length in bytes
                    + "1A03640044" // EAP-MSCHAPv2 | Success | MSCHAPv2 ID | MS length
                    + "533D" // hex("S=")
                    + "3744354237394335353736334632433341323442"
                    + "4345334134323845353245364430314146444636" // hex("<auth_string>")
                    + "204D3D" // hex(" M=")
                    + "57656C636F6D65326561706D73636861703200000000"; // hex("Welcome2eapmschap2")
    private static final byte[] EAP_MSCHAP_V2_DECRYPTED_SUCCESS_REQUEST_AVP_BYTES =
            hexStringToByteArray(EAP_MSCHAP_V2_DECRYPTED_SUCCESS_REQUEST_AVP_STRING);

    private static final String EAP_MSCHAP_V2_ENCRYPTED_SUCCESS_RESPONSE_AVP_STRING =
            "1703030040CD43A46C500065962396E4BEDA72CD43B3316F923AB2108DF93ECFA70192A852485E6D"
                    + "69105B0C57E2C57780C9C8D74BE705CC87F5C862FF30C1138390C8BE73";
    private static final byte[] EAP_MSCHAP_V2_ENCRYPTED_SUCCESS_RESPONSE_AVP_BYTES =
            hexStringToByteArray(EAP_MSCHAP_V2_ENCRYPTED_SUCCESS_RESPONSE_AVP_STRING);
    private static final String EAP_MSCHAP_V2_DECRYPTED_SUCCESS_RESPONSE_AVP_STRING =
            "0000004F" + "40" + "00000E" // AVP Code | AVP Flags | AVP Length
                    + "02650006" // EAP-Response | ID | length in bytes
                    + "1A030000"; // EAP-MSCHAPv2 | Success
    private static final byte[] EAP_MSCHAP_V2_DECRYPTED_SUCCESS_RESPONSE_AVP_BYTES =
            hexStringToByteArray(EAP_MSCHAP_V2_DECRYPTED_SUCCESS_RESPONSE_AVP_STRING);

    private static final String EAP_MSCHAP_V2_ENCRYPTED_FAILURE_REQUEST_AVP_STRING =
            "1703030040CD43A46C500065962396E4BEDA72CD43B3316F923AB2108DF93ECFA70192A852485E6D"
                    + "69105B0C57E2C57780C9C8D74BE705CC87F5C862FF30C1138390C8BE73";
    private static final byte[] EAP_MSCHAP_V2_ENCRYPTED_FAILURE_REQUEST_AVP_BYTES =
            hexStringToByteArray(EAP_MSCHAP_V2_ENCRYPTED_FAILURE_REQUEST_AVP_STRING);
    private static final String EAP_MSCHAP_V2_DECRYPTED_FAILURE_REQUEST_AVP_STRING =
            "0000004F" + "40" + "000055" // AVP Code | AVP Flags | AVP Length
                    + "0113004D" // EAP-Request | ID | length in bytes
                    + "1A04420044" // EAP-MSCHAPv2 | Failure | MSCHAPv2 ID | MS length
                    + "453D363437" // hex("E=647")
                    + "20523D31" // hex(" R=1")
                    + "20433D" // hex(" C=")
                    + "30303031303230333034303530363037"
                    + "30383039304130423043304430453046" // hex("<authenticator challenge>")
                    + "20563D33" // hex(" V=3")
                    + "204D3D" // hex(" M=")
                    + "57656C636F6D65326561706D7363686170320000"; // hex("Welcome2eapmschap2")
    private static final byte[] EAP_MSCHAP_V2_DECRYPTED_FAILURE_REQUEST_AVP_BYTES =
            hexStringToByteArray(EAP_MSCHAP_V2_DECRYPTED_FAILURE_REQUEST_AVP_STRING);

    private static final String EAP_MSCHAP_V2_ENCRYPTED_FAILURE_RESPONSE_AVP_STRING =
            "1703030040CD43A46C500065962396E4BEDA72CD43B3316F923AB21074BE705CC87F5C862F85E6D83"
                    + "69105B0C57E2C57780CDA72CD43B3316F923AB21074BE70CC87F5C862F85E862F";
    private static final byte[] EAP_MSCHAP_V2_ENCRYPTED_FAILURE_RESPONSE_AVP_BYTES =
            hexStringToByteArray(EAP_MSCHAP_V2_ENCRYPTED_FAILURE_RESPONSE_AVP_STRING);
    private static final String EAP_MSCHAP_V2_DECRYPTED_FAILURE_RESPONSE_AVP_STRING =
            "0000004F" + "40" + "00000E" // AVP Code | AVP Flags | AVP Length
                    + "02130006" // EAP-Response | ID | length in bytes
                    + "1A040000"; // EAP-MSCHAPv2 | Failure
    private static final byte[] EAP_MSCHAP_V2_DECRYPTED_FAILURE_RESPONSE_AVP_BYTES =
            hexStringToByteArray(EAP_MSCHAP_V2_DECRYPTED_FAILURE_RESPONSE_AVP_STRING);

    private static final String ENCRYPTED_EAP_AKA_IDENTITY_REQUEST_AVP_STRING =
            "1703030040CD43A46C500065962396E4BEDA72CD43B3316F923AB21074BE705CC87F5C862F85E6D83"
                    + "F9F56678251443C56";
    private static final byte[] ENCRYPTED_EAP_AKA_IDENTITY_REQUEST_AVP_BYTES =
            hexStringToByteArray(ENCRYPTED_EAP_AKA_IDENTITY_REQUEST_AVP_STRING);
    private static final String DECRYPTED_EAP_AKA_IDENTITY_REQUEST_AVP_STRING =
            "0000004F" + "40" + "00000D" // AVP Code | AVP Flags | AVP Length
                    + "0110000517000000"; // AKA EAP-Identity Request
    private static final byte[] DECRYPTED_EAP_AKA_IDENTITY_REQUEST_AVP_BYTES =
            hexStringToByteArray(DECRYPTED_EAP_AKA_IDENTITY_REQUEST_AVP_STRING);

    private static final String ENCRYPTED_NAK_RESPONSE_AVP_STRING =
            "1703030040CD43A46C500065962396E4BEDA72CD43B3316F923AB21074BE705CC87F5C862F85E6D83"
                    + "605B0C57E2C577923";
    private static final byte[] ENCRYPTED_NAK_RESPONSE_AVP_BYTES =
            hexStringToByteArray(ENCRYPTED_NAK_RESPONSE_AVP_STRING);
    private static final String DECRYPTED_NAK_RESPONSE_AVP_STRING =
            "0000004F" + "40" + "00000E" // AVP Code | AVP Flags | AVP Length
                    + "02100006031A0000"; // NAK
    private static final byte[] DECRYPTED_NAK_RESPONSE_AVP_BYTES =
            hexStringToByteArray(DECRYPTED_NAK_RESPONSE_AVP_STRING);


    private static final String ENCRYPTED_EAP_NOTIFICATION_REQUEST_AVP_STRING =
            "1703030040CD43A46C500065962396E4BEDA72CD43B3316F923AB21074BE705CC87F5C862F85E6D83"
                    + "17E15E7443E74AEA4";
    private static final byte[] ENCRYPTED_EAP_NOTIFICATION_REQUEST_AVP_BYTES =
            hexStringToByteArray(ENCRYPTED_EAP_NOTIFICATION_REQUEST_AVP_STRING);
    private static final String DECRYPTED_EAP_NOTIFICATION_REQUEST_AVP_STRING =
            "0000004F" + "40" + "000010" // AVP Code | AVP Flags | AVP Length
                    + "0110000802AABBCC"; // Notification Request
    private static final byte[] DECRYPTED_EAP_NOTIFICATION_REQUEST_AVP_BYTES =
            hexStringToByteArray(DECRYPTED_EAP_NOTIFICATION_REQUEST_AVP_STRING);

    private static final String ENCRYPTED_EAP_NOTIFICATION_RESPONSE_AVP_STRING =
            "1703030040CD43A46C500065962396E4BEDA72CD43B3317F923AB21074BE705CC87F1C862F85E6D83"
                    + "107FAA4BE705CCBE8";
    private static final byte[] ENCRYPTED_EAP_NOTIFICATION_RESPONSE_AVP_BYTES =
            hexStringToByteArray(ENCRYPTED_EAP_NOTIFICATION_RESPONSE_AVP_STRING);
    private static final String DECRYPTED_EAP_NOTIFICATION_RESPONSE_AVP_STRING =
            "0000004F" + "40" + "00000D" // AVP Code | AVP Flags | AVP Length
                    + "0210000502000000"; // Notification Response
    private static final byte[] DECRYPTED_EAP_NOTIFICATION_RESPONSE_AVP_BYTES =
            hexStringToByteArray(DECRYPTED_EAP_NOTIFICATION_RESPONSE_AVP_STRING);

    // EAP-TTLS Request/Responses

    private static final byte[] EAP_RESPONSE_NAK_PACKET_TTLS = hexStringToByteArray("021000060315");
    private static final byte[] EAP_RESPONSE_NAK_PACKET_MSCHAPV2 =
            hexStringToByteArray("02080006031D");

    // Phase 1 (Handshake)

    private static final byte[] EAP_TTLS_START_REQUEST =
            hexStringToByteArray(
                    "01" + "10" + "0006" // EAP-Request | ID | length in bytes
                            + "1520"); // EAP-TTLS | flags);
    private static final byte[] EAP_TTLS_CLIENT_HELLO_RESPONSE =
            hexStringToByteArray(
                    "02" + "10" + "0094" // EAP-Response | ID | length in bytes
                            + "15800000008A" // EAP-TTLS | Flags | message length
                            + CLIENT_HELLO_STRING);
    private static final byte[] EAP_TTLS_SERVER_HELLO_REQUEST_INITIAL_FRAGMENT =
            hexStringToByteArray(
                    "01" + "10" + "0400" // EAP-Request | ID | length in bytes
                            + "15C000000419" // EAP-TTLS | Flags | message length
                            + SERVER_HELLO_INITIAL_FRAGMENT_STRING);
    private static final byte[] EAP_TTLS_ACKNOWLEDGEMENT_RESPONSE_SERVER_HELLO_FRAGMENT =
            hexStringToByteArray(
                    "02" + "10" + "0006" // EAP-Response | ID | length in bytes
                            + "1500"); // EAP-TTLS | Flags
    private static final byte[] EAP_TTLS_SERVER_HELLO_REQUEST_FINAL_FRAGMENT =
            hexStringToByteArray(
                    "01" + "10" + "0029" // EAP-Request | ID | length in bytes
                            + "1500" // EAP-TTLS | Flags
                            + SERVER_HELLO_FINAL_FRAGMENT_STRING);
    private static final byte[] EAP_TTLS_CLIENT_FINISHED_RESPONSE =
            hexStringToByteArray(
                    "02" + "10" + "016C" // EAP-Response | ID | length in bytes
                            + "158000000162" // EAP-TTLS | Flags | message length
                            + CLIENT_FINISHED_STRING);
    private static final byte[] EAP_TTLS_SERVER_FINISHED_REQUEST =
            hexStringToByteArray(
                    "01" + "10" + "0055" // EAP-Request | ID | length in bytes
                            + "15800000004B" // EAP-TTLS | Flags | message length
                            + SERVER_FINISHED_STRING);

    // Phase 2 (Tunnel)

    private static final byte[] EAP_TTLS_TUNNELED_IDENTITY_RESPONSE =
            hexStringToByteArray(
                    "02" + "10" + "004F" // EAP-Response | ID | length in bytes
                            + "158000000045" // EAP-TTLS | Flags | message length
                            + ENCRYPTED_EAP_IDENTITY_AVP_STRING);
    private static final byte[] EAP_TTLS_TUNNELED_CHALLENGE_REQUEST =
            hexStringToByteArray(
                    "01" + "05" + "006F" // EAP-Request | ID | length in bytes
                            + "158000000065" // EAP-TTLS | Flags | message length
                            + EAP_MSCHAP_V2_ENCRYPTED_CHALLENGE_REQUEST_AVP_STRING);
    private static final byte[] EAP_TTLS_TUNNELED_CHALLENGE_RESPONSE =
            hexStringToByteArray(
                    "02" + "05" + "008F" // EAP-Response | ID | length in bytes
                            + "158000000085" // EAP-TTLS | Flags | message length
                            + EAP_MSCHAP_V2_ENCRYPTED_CHALLENGE_RESPONSE_AVP_STRING);
    private static final byte[] EAP_TTLS_TUNNELED_SUCCESS_REQUEST =
            hexStringToByteArray(
                    "01" + "06" + "008F" // EAP-Request | ID | length in bytes
                            + "158000000085" // EAP-TTLS | Flags | message length
                            + EAP_MSCHAP_V2_ENCRYPTED_SUCCESS_REQUEST_AVP_STRING);
    private static final byte[] EAP_TTLS_TUNNELED_SUCCESS_RESPONSE =
            hexStringToByteArray(
                    "02" + "06" + "004F" // EAP-Response | ID | length in bytes
                            + "158000000045" // EAP-TTLS | Flags | message length
                            + EAP_MSCHAP_V2_ENCRYPTED_SUCCESS_RESPONSE_AVP_STRING);
    private static final byte[] EAP_TTLS_TUNNELED_FAILURE_REQUEST =
            hexStringToByteArray(
                    "01" + "07" + "004F" // EAP-Request | ID | length in bytes
                            + "158000000045" // EAP-TTLS | Flags | message length
                            + EAP_MSCHAP_V2_ENCRYPTED_FAILURE_REQUEST_AVP_STRING);
    private static final byte[] EAP_TTLS_TUNNELED_FAILURE_RESPONSE =
            hexStringToByteArray(
                    "02" + "07" + "0053" // EAP-Response | ID | length in bytes
                            + "158000000049" // EAP-TTLS | Flags | message length
                            + EAP_MSCHAP_V2_ENCRYPTED_FAILURE_RESPONSE_AVP_STRING);
    private static final byte[] EAP_TTLS_TUNNELED_AKA_IDENTITY_AVP_REQUEST =
            hexStringToByteArray(
                    "01" + "08" + "003B" // EAP-Request | ID | length in bytes
                            + "158000000031" // EAP-TTLS | Flags | message length
                            + ENCRYPTED_EAP_AKA_IDENTITY_REQUEST_AVP_STRING);
    private static final byte[] EAP_TTLS_TUNNELED_NAK_RESPONSE =
            hexStringToByteArray(
                    "02" + "08"  + "003B" // EAP-Response | ID | length in bytes
                            + "158000000031" // EAP-TTLS | Flags | message length
                            + ENCRYPTED_NAK_RESPONSE_AVP_STRING);
    private static final byte[] EAP_TTLS_TUNNELED_EAP_NOTIFICATION_REQUEST =
            hexStringToByteArray(
                    "01" + "08" + "003B" // EAP-Response | ID | length in bytes
                            + "158000000031" // EAP-TTLS | Flags | message length
                            + ENCRYPTED_EAP_NOTIFICATION_REQUEST_AVP_STRING);
    private static final byte[] EAP_TTLS_TUNNELED_EAP_NOTIFICATION_RESPONSE =
            hexStringToByteArray(
                    "02" + "08" + "003B" // EAP-Response | ID | length in bytes
                            + "158000000031" // EAP-TTLS | Flags | message length
                            + ENCRYPTED_EAP_NOTIFICATION_RESPONSE_AVP_STRING);

    private final TlsSessionFactory mMockTlsSessionFactory = mock(TlsSessionFactory.class);
    private final SSLEngine mMockSslEngine = mock(SSLEngine.class);
    private final SSLSession mMockSslSession = mock(SSLSession.class);
    private TlsSession mTlsSessionSpy;

    @Before
    @Override
    public void setUp() {
        super.setUp();
        EapSessionConfig innerEapSessionConfig =
                new EapSessionConfig.Builder()
                        .setEapMsChapV2Config(MSCHAPV2_USERNAME, MSCHAPV2_PASSWORD)
                        .build();
        mEapSessionConfig =
                new EapSessionConfig.Builder()
                        .setEapTtlsConfig(null, innerEapSessionConfig)
                        .build();
        mEapAuthenticator =
                new EapAuthenticator(
                        mTestLooper.getLooper(),
                        mMockCallback,
                        new EapStateMachine(mMockContext, mEapSessionConfig, mMockSecureRandom),
                        Runnable::run,
                        AUTHENTICATOR_TIMEOUT_MILLIS);

        when(mMockSslSession.getApplicationBufferSize())
                .thenReturn(APPLICATION_BUFFER_SIZE_TLS_MESSAGE);
        when(mMockSslSession.getPacketBufferSize()).thenReturn(PACKET_BUFFER_SIZE_TLS_MESSAGE);

        // TODO(b/165823103): Switch EAP-TTLS to use CorePlatformApi for
        // Conscrypt#exportKeyingMaterial
        mTlsSessionSpy =
                spy(
                        new TlsSession(
                                mock(SSLContext.class),
                                mMockSslEngine,
                                mMockSslSession,
                                mMockSecureRandom));
        when(mTlsSessionSpy.generateKeyingMaterial())
                .thenReturn(mTlsSessionSpy.new EapTtlsKeyingMaterial(TLS_MSK, TLS_EMSK));

        EapTtlsMethodStateMachine.sTlsSessionFactory = mMockTlsSessionFactory;
        try {
            when(mMockTlsSessionFactory.newInstance(eq(null), eq(mMockSecureRandom)))
                    .thenReturn(mTlsSessionSpy);
        } catch (Exception e) {
            throw new AssertionError("TLS Session setup failed", e);
        }
    }

    @AfterClass
    public static void teardown() {
        EapTtlsMethodStateMachine.sTlsSessionFactory = new TlsSessionFactory();
    }

    @Test
    public void testEapTtlsEndToEndSuccess() throws Exception {
        processAndVerifyStartRequest();
        processAndVerifyServerHello_initialFragment();
        processAndVerifyServerHello_finalFragment();
        processAndVerifyServerFinished();
        processAndVerifyMsChapV2ChallengeRequest();
        processAndVerifyMsChapV2SuccessRequest();
        processAndVerifyEapSuccess(TLS_MSK, TLS_EMSK);
    }

    @Test
    public void testEapTtlsWithEapNotifications() throws Exception {
        verifyEapNotification(1);
        processAndVerifyStartRequest();

        verifyEapNotification(2);
        processAndVerifyServerHello_initialFragment();
        processAndVerifyServerHello_finalFragment();

        verifyEapNotification(3);
        processAndVerifyServerFinished();

        verifyEapNotification(4);
        processAndVerifyMsChapV2ChallengeRequest();

        verifyEapNotification(5);
        processAndVerifyMsChapV2SuccessRequest();

        verifyEapNotification(6);
        processAndVerifyEapSuccess(TLS_MSK, TLS_EMSK);
    }

    @Test
    public void testEapTtlsWithTunneledEapNotifications() throws Exception {
        processAndVerifyStartRequest();
        processAndVerifyServerHello_initialFragment();
        processAndVerifyServerHello_finalFragment();
        processAndVerifyServerFinished();

        processAndVerifyTunneledEapNotification(1);
        processAndVerifyMsChapV2ChallengeRequest();

        processAndVerifyTunneledEapNotification(2);
        processAndVerifyMsChapV2SuccessRequest();

        processAndVerifyTunneledEapNotification(3);
        processAndVerifyEapSuccess(TLS_MSK, TLS_EMSK);
    }

    @Test
    public void testEapMsChapV2EndToEndFailure() throws Exception {
        processAndVerifyStartRequest();
        processAndVerifyServerHello_initialFragment();
        processAndVerifyServerHello_finalFragment();
        processAndVerifyServerFinished();
        processAndVerifyMsChapV2ChallengeRequest();
        processMessageAndVerifyMsChapV2FailureRequest();
        verifyEapFailure();
    }

    @Test
    public void testEapTtlsUnsupportedType() throws Exception {
        verifyUnsupportedType(EAP_REQUEST_AKA_IDENTITY_PACKET, EAP_RESPONSE_NAK_PACKET_TTLS);

        processAndVerifyStartRequest();
        processAndVerifyServerHello_initialFragment();
        processAndVerifyServerHello_finalFragment();
        processAndVerifyServerFinished();
        processAndVerifyMsChapV2ChallengeRequest();
        processAndVerifyMsChapV2SuccessRequest();
        processAndVerifyEapSuccess(TLS_MSK, TLS_EMSK);
    }

    @Test
    public void testEapTtlsTunneledUnsupportedType() throws Exception {
        processAndVerifyStartRequest();
        processAndVerifyServerHello_initialFragment();
        processAndVerifyServerHello_finalFragment();
        processAndVerifyServerFinished();

        processAndVerifyTunneledUnsupportedType();

        processAndVerifyMsChapV2ChallengeRequest();
        processAndVerifyMsChapV2SuccessRequest();
        processAndVerifyEapSuccess(TLS_MSK, TLS_EMSK);
    }

    private void processAndVerifyStartRequest() throws Exception {
        setupWrap(EMPTY_BYTE_ARRAY, CLIENT_HELLO_BYTES, RESULT_NEED_UNWRAP_OK);

        mEapAuthenticator.processEapMessage(EAP_TTLS_START_REQUEST);
        mTestLooper.dispatchAll();

        // TODO(b/166794957): Verify SSLEngine wrap/unwrap in EAP-TTLS end-to-end tests
        verify(mMockCallback)
                .onResponse(eq(EAP_TTLS_CLIENT_HELLO_RESPONSE), eq(EAP_RESPONSE_FLAGS_NOT_SET));
        verifyNoMoreInteractions(mMockCallback);
    }

    private void processAndVerifyServerHello_initialFragment() throws Exception {
        mEapAuthenticator.processEapMessage(EAP_TTLS_SERVER_HELLO_REQUEST_INITIAL_FRAGMENT);
        mTestLooper.dispatchAll();

        verify(mMockCallback)
                .onResponse(
                        eq(EAP_TTLS_ACKNOWLEDGEMENT_RESPONSE_SERVER_HELLO_FRAGMENT),
                        eq(EAP_RESPONSE_FLAGS_NOT_SET));
        verifyNoMoreInteractions(mMockCallback);
    }

    private void processAndVerifyServerHello_finalFragment() throws Exception {
        doReturn(RESULT_NEED_WRAP_OK)
                .when(mMockSslEngine)
                .unwrap(
                        argThat(containsData(SERVER_HELLO_BYTES)),
                        argThat(containsData(DECRYPTED_EAP_IDENTITY_AVP_BYTES)));
        setupWrap(DECRYPTED_EAP_IDENTITY_AVP_BYTES, CLIENT_FINISHED_BYTES, RESULT_NEED_UNWRAP_OK);

        mEapAuthenticator.processEapMessage(EAP_TTLS_SERVER_HELLO_REQUEST_FINAL_FRAGMENT);
        mTestLooper.dispatchAll();

        // TODO(b/166794957): Verify SSLEngine wrap/unwrap in EAP-TTLS end-to-end tests
        verify(mMockCallback)
                .onResponse(eq(EAP_TTLS_CLIENT_FINISHED_RESPONSE), eq(EAP_RESPONSE_FLAGS_NOT_SET));
        verifyNoMoreInteractions(mMockCallback);
    }

    private void processAndVerifyServerFinished() throws Exception {
        doReturn(RESULT_NEED_UNWRAP_OK, RESULT_NEED_WRAP_OK)
                .when(mMockSslEngine)
                .unwrap(
                        argThat(containsData(SERVER_FINISHED_BYTES)),
                        argThat(containsData(DECRYPTED_EAP_IDENTITY_AVP_BYTES)));
        setupWrap(
                DECRYPTED_EAP_IDENTITY_AVP_BYTES,
                ENCRYPTED_EAP_IDENTITY_AVP_BYTES,
                RESULT_FINISHED_OK);

        mEapAuthenticator.processEapMessage(EAP_TTLS_SERVER_FINISHED_REQUEST);
        mTestLooper.dispatchAll();

        // TODO(b/166794957): Verify SSLEngine wrap/unwrap in EAP-TTLS end-to-end tests
        verify(mMockCallback)
                .onResponse(
                        eq(EAP_TTLS_TUNNELED_IDENTITY_RESPONSE), eq(EAP_RESPONSE_FLAGS_NOT_SET));
        verifyNoMoreInteractions(mMockCallback);
    }

    private void processAndVerifyMsChapV2ChallengeRequest() throws Exception {
        doAnswer(invocation -> {
            byte[] dst = invocation.getArgument(0);
            System.arraycopy(PEER_CHALLENGE, 0, dst, 0, PEER_CHALLENGE.length);
            return null;
        })
            .when(mMockSecureRandom)
            .nextBytes(argThat(arr -> arr.length == PEER_CHALLENGE.length));
        setupUnwrap(
                EAP_MSCHAP_V2_DECRYPTED_CHALLENGE_REQUEST_AVP_BYTES,
                EAP_MSCHAP_V2_ENCRYPTED_CHALLENGE_REQUEST_AVP_BYTES,
                RESULT_NOT_HANDSHAKING_OK);
        setupWrap(
                EAP_MSCHAP_V2_DECRYPTED_CHALLENGE_RESPONSE_AVP_BYTES,
                EAP_MSCHAP_V2_ENCRYPTED_CHALLENGE_RESPONSE_AVP_BYTES,
                RESULT_NOT_HANDSHAKING_OK);

        mEapAuthenticator.processEapMessage(EAP_TTLS_TUNNELED_CHALLENGE_REQUEST);
        mTestLooper.dispatchAll();

        // TODO(b/166794957): Verify SSLEngine wrap/unwrap in EAP-TTLS end-to-end tests
        verify(mMockCallback)
                .onResponse(
                        eq(EAP_TTLS_TUNNELED_CHALLENGE_RESPONSE), eq(EAP_RESPONSE_FLAGS_NOT_SET));
        verify(mMockSecureRandom).nextBytes(argThat(arr -> arr.length == PEER_CHALLENGE.length));
        verifyNoMoreInteractions(mMockCallback);
    }

    private void processAndVerifyMsChapV2SuccessRequest() throws Exception {
        setupUnwrap(
                EAP_MSCHAP_V2_DECRYPTED_SUCCESS_REQUEST_AVP_BYTES,
                EAP_MSCHAP_V2_ENCRYPTED_SUCCESS_REQUEST_AVP_BYTES,
                RESULT_NOT_HANDSHAKING_OK);
        setupWrap(
                EAP_MSCHAP_V2_DECRYPTED_SUCCESS_RESPONSE_AVP_BYTES,
                EAP_MSCHAP_V2_ENCRYPTED_SUCCESS_RESPONSE_AVP_BYTES,
                RESULT_NOT_HANDSHAKING_OK);

        mEapAuthenticator.processEapMessage(EAP_TTLS_TUNNELED_SUCCESS_REQUEST);
        mTestLooper.dispatchAll();

        // TODO(b/166794957): Verify SSLEngine wrap/unwrap in EAP-TTLS end-to-end tests
        verify(mMockCallback)
                .onResponse(eq(EAP_TTLS_TUNNELED_SUCCESS_RESPONSE), eq(EAP_RESPONSE_FLAGS_NOT_SET));
        verifyNoMoreInteractions(mMockCallback);
    }

    private void processAndVerifyTunneledEapNotification(int callsToVerify) throws Exception {
        setupUnwrap(
                DECRYPTED_EAP_NOTIFICATION_REQUEST_AVP_BYTES,
                ENCRYPTED_EAP_NOTIFICATION_REQUEST_AVP_BYTES,
                RESULT_NOT_HANDSHAKING_OK);
        setupWrap(
                DECRYPTED_EAP_NOTIFICATION_RESPONSE_AVP_BYTES,
                ENCRYPTED_EAP_NOTIFICATION_RESPONSE_AVP_BYTES,
                RESULT_NOT_HANDSHAKING_OK);

        mEapAuthenticator.processEapMessage(EAP_TTLS_TUNNELED_EAP_NOTIFICATION_REQUEST);
        mTestLooper.dispatchAll();

        // TODO(b/166794957): Verify SSLEngine wrap/unwrap in EAP-TTLS end-to-end tests
        verify(mMockCallback, times(callsToVerify))
                .onResponse(
                        eq(EAP_TTLS_TUNNELED_EAP_NOTIFICATION_RESPONSE),
                        eq(EAP_RESPONSE_FLAGS_NOT_SET));
        verifyNoMoreInteractions(mMockCallback);
    }

    private void processMessageAndVerifyMsChapV2FailureRequest() throws Exception {
        setupUnwrap(
                EAP_MSCHAP_V2_DECRYPTED_FAILURE_REQUEST_AVP_BYTES,
                EAP_MSCHAP_V2_ENCRYPTED_FAILURE_REQUEST_AVP_BYTES,
                RESULT_NOT_HANDSHAKING_OK);
        setupWrap(
                EAP_MSCHAP_V2_DECRYPTED_FAILURE_RESPONSE_AVP_BYTES,
                EAP_MSCHAP_V2_ENCRYPTED_FAILURE_RESPONSE_AVP_BYTES,
                RESULT_NOT_HANDSHAKING_OK);

        mEapAuthenticator.processEapMessage(EAP_TTLS_TUNNELED_FAILURE_REQUEST);
        mTestLooper.dispatchAll();

        // TODO(b/166794957): Verify SSLEngine wrap/unwrap in EAP-TTLS end-to-end tests
        verify(mMockCallback)
                .onResponse(eq(EAP_TTLS_TUNNELED_FAILURE_RESPONSE), eq(EAP_RESPONSE_FLAGS_NOT_SET));
        verifyNoMoreInteractions(mMockCallback);
    }

    private void processAndVerifyTunneledUnsupportedType()
            throws Exception {
        setupUnwrap(
                DECRYPTED_EAP_AKA_IDENTITY_REQUEST_AVP_BYTES,
                ENCRYPTED_EAP_AKA_IDENTITY_REQUEST_AVP_BYTES,
                RESULT_NOT_HANDSHAKING_OK);
        setupWrap(
                DECRYPTED_NAK_RESPONSE_AVP_BYTES,
                ENCRYPTED_NAK_RESPONSE_AVP_BYTES,
                RESULT_NOT_HANDSHAKING_OK);

        mEapAuthenticator.processEapMessage(EAP_TTLS_TUNNELED_AKA_IDENTITY_AVP_REQUEST);
        mTestLooper.dispatchAll();

        // TODO(b/166794957): Verify SSLEngine wrap/unwrap in EAP-TTLS end-to-end tests
        // verify EAP-Response/Nak returned
        verify(mMockCallback)
                .onResponse(eq(EAP_TTLS_TUNNELED_NAK_RESPONSE), eq(EAP_RESPONSE_FLAGS_NOT_SET));
        verifyNoMoreInteractions(mMockCallback);
    }

    private void processAndVerifyEapSuccess(byte[] msk, byte[] emsk) throws Exception {
        // EAP-Success
        mEapAuthenticator.processEapMessage(EAP_SUCCESS);
        mTestLooper.dispatchAll();

        // verify that onSuccess callback made
        verify(mMockCallback).onSuccess(eq(msk), eq(emsk), eq(null));
        verify(mTlsSessionSpy).generateKeyingMaterial();
        verifyNoMoreInteractions(mMockContext, mMockSecureRandom, mMockCallback);
    }

    private void setupUnwrap(
            byte[] applicationData, byte[] packetData, SSLEngineResult result) throws Exception {
        doAnswer(invocation -> {
            ByteBuffer buffer = invocation.getArgument(1);
            buffer.put(applicationData);
            return result;
        })
                .when(mMockSslEngine)
                .unwrap(argThat(containsData(packetData)), any(ByteBuffer.class));
    }

    void setupWrap(byte[] applicationData, byte[] packetData, SSLEngineResult result)
            throws Exception {
        doAnswer(invocation -> {
            ByteBuffer buffer = invocation.getArgument(1);
            buffer.put(packetData);
            return result;
        })
                .when(mMockSslEngine)
                .wrap(argThat(containsData(applicationData)), any(ByteBuffer.class));
    }

    private ArgumentMatcher<ByteBuffer> containsData(byte[] data) {
        return buffer -> Arrays.equals(getByteArrayFromBufferLimit(buffer), data);
    }

    // The ByteBuffer is always initialized by ByteBuffer#allocate
    @SuppressWarnings("ByteBufferBackingArray")
    private byte[] getByteArrayFromBufferLimit(ByteBuffer buffer) {
        return Arrays.copyOfRange(buffer.array(), 0, buffer.limit());
    }
}
