/*
 * Copyright (C) 2017 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.server.wifi.hotspot2;

import android.net.wifi.hotspot2.PasspointConfiguration;
import android.net.wifi.hotspot2.pps.Credential;
import android.net.wifi.hotspot2.pps.HomeSp;
import android.net.wifi.hotspot2.pps.Policy;
import android.net.wifi.hotspot2.pps.UpdateParameter;
import android.os.ParcelUuid;
import android.util.Log;

import com.android.modules.utils.build.SdkLevel;
import com.android.server.wifi.util.XmlUtil;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Utility class for serialize and deserialize Passpoint related configurations to/from XML string.
 */
public class PasspointXmlUtils {
    private static final String TAG = "PasspointXmlUtils";
    // XML section header tags.
    private static final String XML_TAG_SECTION_HEADER_HOMESP = "HomeSP";
    private static final String XML_TAG_SECTION_HEADER_CREDENTIAL = "Credential";
    private static final String XML_TAG_SECTION_HEADER_USER_CREDENTIAL = "UserCredential";
    private static final String XML_TAG_SECTION_HEADER_CERT_CREDENTIAL = "CertCredential";
    private static final String XML_TAG_SECTION_HEADER_SIM_CREDENTIAL = "SimCredential";
    private static final String XML_TAG_SECTION_HEADER_POLICY = "Policy";
    private static final String XML_TAG_SECTION_HEADER_PREFERRED_ROAMING_PARTNER_LIST =
            "RoamingPartnerList";
    private static final String XML_TAG_SECTION_HEADER_ROAMING_PARTNER = "RoamingPartner";
    private static final String XML_TAG_SECTION_HEADER_POLICY_UPDATE = "PolicyUpdate";
    private static final String XML_TAG_SECTION_HEADER_SUBSCRIPTION_UPDATE = "SubscriptionUpdate";
    private static final String XML_TAG_SECTION_HEADER_REQUIRED_PROTO_PORT_MAP =
            "RequiredProtoPortMap";
    private static final String XML_TAG_SECTION_HEADER_PROTO_PORT = "ProtoPort";
    private static final String XML_TAG_SECTION_HEADER_AAA_SERVER_TRUSTED_NAMES =
            "AAAServerTrustedNames";

    // XML value tags.
    private static final String XML_TAG_FQDN = "FQDN";
    private static final String XML_TAG_FRIENDLY_NAME = "FriendlyName";
    private static final String XML_TAG_FRIENDLY_NAME_LIST = "FriendlyNameList";
    private static final String XML_TAG_ICON_URL = "IconURL";
    private static final String XML_TAG_HOME_NETWORK_IDS = "HomeNetworkIDs";
    private static final String XML_TAG_MATCH_ALL_OIS = "MatchAllOIs";
    private static final String XML_TAG_MATCH_ANY_OIS = "MatchAnyOIs";
    private static final String XML_TAG_OTHER_HOME_PARTNERS = "OtherHomePartners";
    private static final String XML_TAG_ROAMING_CONSORTIUM_OIS = "RoamingConsortiumOIs";
    private static final String XML_TAG_CREATION_TIME = "CreationTime";
    private static final String XML_TAG_EXPIRATION_TIME = "ExpirationTime";
    private static final String XML_TAG_REALM = "Realm";
    private static final String XML_TAG_CHECK_AAA_SERVER_CERT_STATUS = "CheckAAAServerCertStatus";
    private static final String XML_TAG_USERNAME = "Username";
    private static final String XML_TAG_PASSWORD = "Password";
    private static final String XML_TAG_MACHINE_MANAGED = "MachineManaged";
    private static final String XML_TAG_SOFT_TOKEN_APP = "SoftTokenApp";
    private static final String XML_TAG_ABLE_TO_SHARE = "AbleToShare";
    private static final String XML_TAG_EAP_TYPE = "EAPType";
    private static final String XML_TAG_NON_EAP_INNER_METHOD = "NonEAPInnerMethod";
    private static final String XML_TAG_CERT_TYPE = "CertType";
    private static final String XML_TAG_CERT_SHA256_FINGERPRINT = "CertSHA256Fingerprint";
    private static final String XML_TAG_IMSI = "IMSI";
    private static final String XML_TAG_MIN_HOME_DOWNLINK_BANDWIDTH = "MinHomeDownlinkBandwidth";
    private static final String XML_TAG_MIN_HOME_UPLINK_BANDWIDTH = "MinHomeUplinkBandwidth";
    private static final String XML_TAG_MIN_ROAMING_DOWNLINK_BANDWIDTH =
            "MinRoamingDownlinkBandwidth";
    private static final String XML_TAG_MIN_ROAMING_UPLINK_BANDWIDTH =
            "MinRoamingUplinkBandwidth";
    private static final String XML_TAG_EXCLUDED_SSID_LIST = "ExcludedSSIDList";
    private static final String XML_TAG_PROTO = "Proto";
    private static final String XML_TAG_PORTS = "Ports";
    private static final String XML_TAG_MAXIMUM_BSS_LOAD_VALUE = "MaximumBSSLoadValue";
    private static final String XML_TAG_FQDN_EXACT_MATCH = "FQDNExactMatch";
    private static final String XML_TAG_PRIORITY = "Priority";
    private static final String XML_TAG_COUNTRIES = "Countries";
    private static final String XML_TAG_UPDATE_INTERVAL = "UpdateInterval";
    private static final String XML_TAG_UPDATE_METHOD = "UpdateMethod";
    private static final String XML_TAG_RESTRICTION = "Restriction";
    private static final String XML_TAG_SERVER_URI = "ServerURI";
    private static final String XML_TAG_TRUST_ROOT_CERT_URL = "TrustRootCertURL";
    private static final String XML_TAG_TRUST_ROOT_CERT_SHA256_FINGERPRINT =
            "TrustRootCertSHA256Fingerprint";
    private static final String XML_TAG_TRUST_ROOT_CERT_LIST = "TrustRootCertList";
    private static final String XML_TAG_UPDATE_IDENTIFIER = "UpdateIdentifier";
    private static final String XML_TAG_CREDENTIAL_PRIORITY = "CredentialPriority";
    private static final String XML_TAG_SUBSCRIPTION_CREATION_TIME = "SubscriptionCreationTime";
    private static final String XML_TAG_SUBSCRIPTION_EXPIRATION_TIME =
            "SubscriptionExpirationTime";
    private static final String XML_TAG_SUBSCRIPTION_TYPE = "SubscriptionType";
    private static final String XML_TAG_USAGE_LIMIT_TIME_PERIOD = "UsageLimitTimePeriod";
    private static final String XML_TAG_USAGE_LIMIT_START_TIME = "UsageLimitStartTime";
    private static final String XML_TAG_USAGE_LIMIT_DATA_LIMIT = "UsageLimitDataLimit";
    private static final String XML_TAG_USAGE_LIMIT_TIME_LIMIT = "UsageLimitTimeLimit";
    private static final String XML_TAG_CARRIER_ID = "CarrierId";
    private static final String XML_TAG_SUBSCRIPTION_ID = "SubscriptionId";
    private static final String XML_TAG_IS_AUTO_JOIN = "AutoJoinEnabled";
    private static final String XML_TAG_IS_MAC_RANDOMIZATION_ENABLED = "IsMacRandomizationEnabled";
    private static final String XML_TAG_METERED_OVERRIDE = "MeteredOverride";
    private static final String XML_TAG_IS_CARRIER_MERGED = "IsCarrierMerged";
    private static final String XML_TAG_IS_OEM_PAID = "IsOemPaid";
    private static final String XML_TAG_IS_OEM_PRIVATE = "IsOemPrivate";
    private static final String XML_TAG_DECORATED_IDENTITY_PREFIX = "DecoratedIdentityPrefix";
    private static final String XML_TAG_SUBSCRIPTION_GROUP = "SubscriptionGroup";
    private static final String XML_TAG_MINIMUM_TLS_VERSION = "MinimumTlsVersion";

    /**
     * Serialize a {@link PasspointConfiguration} to the output stream as a XML block.
     *
     * @param out The output stream to serialize to
     * @param config The configuration to serialize
     * @throws XmlPullParserException
     * @throws IOException
     */
    public static void serializePasspointConfiguration(XmlSerializer out,
            PasspointConfiguration config) throws XmlPullParserException, IOException {
        XmlUtil.writeNextValue(out, XML_TAG_UPDATE_IDENTIFIER, config.getUpdateIdentifier());
        XmlUtil.writeNextValue(out, XML_TAG_CREDENTIAL_PRIORITY, config.getCredentialPriority());
        XmlUtil.writeNextValue(out, XML_TAG_TRUST_ROOT_CERT_LIST, config.getTrustRootCertList());
        XmlUtil.writeNextValue(out, XML_TAG_SUBSCRIPTION_CREATION_TIME,
                config.getSubscriptionCreationTimeInMillis());
        XmlUtil.writeNextValue(out, XML_TAG_SUBSCRIPTION_EXPIRATION_TIME,
                config.getSubscriptionExpirationTimeMillis());
        XmlUtil.writeNextValue(out, XML_TAG_SUBSCRIPTION_TYPE, config.getSubscriptionType());
        XmlUtil.writeNextValue(out, XML_TAG_USAGE_LIMIT_TIME_PERIOD,
                config.getUsageLimitUsageTimePeriodInMinutes());
        XmlUtil.writeNextValue(out, XML_TAG_USAGE_LIMIT_START_TIME,
                config.getUsageLimitStartTimeInMillis());
        XmlUtil.writeNextValue(out, XML_TAG_USAGE_LIMIT_DATA_LIMIT,
                config.getUsageLimitDataLimit());
        XmlUtil.writeNextValue(out, XML_TAG_USAGE_LIMIT_TIME_LIMIT,
                config.getUsageLimitTimeLimitInMinutes());
        serializeHomeSp(out, config.getHomeSp());
        serializeCredential(out, config.getCredential());
        serializePolicy(out, config.getPolicy());
        serializeUpdateParameter(out, XML_TAG_SECTION_HEADER_SUBSCRIPTION_UPDATE,
                config.getSubscriptionUpdate());
        if (config.getServiceFriendlyNames() != null) {
            XmlUtil.writeNextValue(out, XML_TAG_FRIENDLY_NAME_LIST,
                    config.getServiceFriendlyNames());
        }
        XmlUtil.writeNextValue(out, XML_TAG_CARRIER_ID, config.getCarrierId());
        XmlUtil.writeNextValue(out, XML_TAG_SUBSCRIPTION_ID, config.getSubscriptionId());
        XmlUtil.writeNextValue(out, XML_TAG_IS_AUTO_JOIN, config.isAutojoinEnabled());
        XmlUtil.writeNextValue(out, XML_TAG_IS_MAC_RANDOMIZATION_ENABLED,
                config.isMacRandomizationEnabled());
        XmlUtil.writeNextValue(out, XML_TAG_METERED_OVERRIDE, config.getMeteredOverride());
        XmlUtil.writeNextValue(out, XML_TAG_IS_CARRIER_MERGED, config.isCarrierMerged());
        XmlUtil.writeNextValue(out, XML_TAG_IS_OEM_PAID, config.isOemPaid());
        XmlUtil.writeNextValue(out, XML_TAG_IS_OEM_PRIVATE, config.isOemPrivate());
        if (config.getSubscriptionGroup() != null) {
            XmlUtil.writeNextValue(out, XML_TAG_SUBSCRIPTION_GROUP,
                    config.getSubscriptionGroup().toString());
        }
        if (SdkLevel.isAtLeastS()) {
            XmlUtil.writeNextValue(out, XML_TAG_DECORATED_IDENTITY_PREFIX,
                    config.getDecoratedIdentityPrefix());
        }
        serializeAaaServerTrustedNames(out, config.getAaaServerTrustedNames());
    }

    /**
     * Deserialize a {@link PasspointConfiguration} from an input stream containing XML block.
     *
     * @param in The input stream to read from
     * @param outerTagDepth The tag depth of the current XML section
     * @return {@link PasspointConfiguration}
     * @throws XmlPullParserException
     * @throws IOException
     */
    public static PasspointConfiguration deserializePasspointConfiguration(XmlPullParser in,
            int outerTagDepth) throws XmlPullParserException, IOException {
        PasspointConfiguration config = new PasspointConfiguration();
        while (XmlUtil.nextElementWithin(in, outerTagDepth)) {
            if (isValueElement(in)) {
                // Value elements.
                String[] name = new String[1];
                Object value = XmlUtil.readCurrentValue(in, name);
                switch (name[0]) {
                    case XML_TAG_UPDATE_IDENTIFIER:
                        config.setUpdateIdentifier((int) value);
                        break;
                    case XML_TAG_CREDENTIAL_PRIORITY:
                        config.setCredentialPriority((int) value);
                        break;
                    case XML_TAG_TRUST_ROOT_CERT_LIST:
                        config.setTrustRootCertList((Map<String, byte[]>) value);
                        break;
                    case XML_TAG_SUBSCRIPTION_CREATION_TIME:
                        config.setSubscriptionCreationTimeInMillis((long) value);
                        break;
                    case XML_TAG_SUBSCRIPTION_EXPIRATION_TIME:
                        config.setSubscriptionExpirationTimeInMillis((long) value);
                        break;
                    case XML_TAG_SUBSCRIPTION_TYPE:
                        config.setSubscriptionType((String) value);
                        break;
                    case XML_TAG_USAGE_LIMIT_TIME_PERIOD:
                        config.setUsageLimitUsageTimePeriodInMinutes((long) value);
                        break;
                    case XML_TAG_USAGE_LIMIT_START_TIME:
                        config.setUsageLimitStartTimeInMillis((long) value);
                        break;
                    case XML_TAG_USAGE_LIMIT_DATA_LIMIT:
                        config.setUsageLimitDataLimit((long) value);
                        break;
                    case XML_TAG_USAGE_LIMIT_TIME_LIMIT:
                        config.setUsageLimitTimeLimitInMinutes((long) value);
                        break;
                    case XML_TAG_FRIENDLY_NAME_LIST:
                        config.setServiceFriendlyNames((Map<String, String>) value);
                        break;
                    case XML_TAG_CARRIER_ID:
                        config.setCarrierId((int) value);
                        break;
                    case XML_TAG_SUBSCRIPTION_ID:
                        config.setSubscriptionId((int) value);
                        break;
                    case XML_TAG_IS_AUTO_JOIN:
                        config.setAutojoinEnabled((boolean) value);
                        break;
                    case XML_TAG_IS_MAC_RANDOMIZATION_ENABLED:
                        config.setMacRandomizationEnabled((boolean) value);
                        break;
                    case XML_TAG_METERED_OVERRIDE:
                        config.setMeteredOverride((int) value);
                        break;
                    case XML_TAG_IS_CARRIER_MERGED:
                        config.setCarrierMerged((boolean) value);
                        break;
                    case XML_TAG_IS_OEM_PAID:
                        config.setOemPaid((boolean) value);
                        break;
                    case XML_TAG_IS_OEM_PRIVATE:
                        config.setOemPrivate((boolean) value);
                        break;
                    case XML_TAG_DECORATED_IDENTITY_PREFIX:
                        if (SdkLevel.isAtLeastS()) {
                            config.setDecoratedIdentityPrefix((String) value);
                        }
                        break;
                    case XML_TAG_SUBSCRIPTION_GROUP:
                        config.setSubscriptionGroup(ParcelUuid.fromString((String) value));
                        break;
                    default:
                        Log.w(TAG, "Unknown value under " + "PasspointConfiguration: "
                                + in.getName());
                }
            } else {
                // Section elements.
                switch (in.getName()) {
                    case XML_TAG_SECTION_HEADER_HOMESP:
                        config.setHomeSp(deserializeHomeSP(in, outerTagDepth + 1));
                        break;
                    case XML_TAG_SECTION_HEADER_CREDENTIAL:
                        config.setCredential(deserializeCredential(in, outerTagDepth + 1));
                        break;
                    case XML_TAG_SECTION_HEADER_POLICY:
                        config.setPolicy(deserializePolicy(in, outerTagDepth + 1));
                        break;
                    case XML_TAG_SECTION_HEADER_SUBSCRIPTION_UPDATE:
                        config.setSubscriptionUpdate(
                                deserializeUpdateParameter(in, outerTagDepth + 1));
                        break;
                    case XML_TAG_SECTION_HEADER_AAA_SERVER_TRUSTED_NAMES:
                        config.setAaaServerTrustedNames(deserializeAaaServerTrustedNames(
                                in, outerTagDepth + 1));
                        break;
                    default:
                        Log.w(TAG, "Unknown section under "
                                + "PasspointConfiguration: " + in.getName());
                }
            }
        }
        return config;
    }

    /**
     * Serialize a {@link HomeSp} to an output stream as a XML block.
     *
     * @param out The output stream to serialize data to
     * @param homeSp The {@link HomeSp} to serialize
     * @throws XmlPullParserException
     * @throws IOException
     */
    private static void serializeHomeSp(XmlSerializer out, HomeSp homeSp)
            throws XmlPullParserException, IOException {
        if (homeSp == null) {
            return;
        }
        XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_HOMESP);
        XmlUtil.writeNextValue(out, XML_TAG_FQDN, homeSp.getFqdn());
        XmlUtil.writeNextValue(out, XML_TAG_FRIENDLY_NAME, homeSp.getFriendlyName());
        XmlUtil.writeNextValue(out, XML_TAG_ICON_URL, homeSp.getIconUrl());
        XmlUtil.writeNextValue(out, XML_TAG_HOME_NETWORK_IDS, homeSp.getHomeNetworkIds());
        XmlUtil.writeNextValue(out, XML_TAG_MATCH_ALL_OIS, homeSp.getMatchAllOis());
        XmlUtil.writeNextValue(out, XML_TAG_MATCH_ANY_OIS, homeSp.getMatchAnyOis());
        XmlUtil.writeNextValue(out, XML_TAG_OTHER_HOME_PARTNERS, homeSp.getOtherHomePartners());
        XmlUtil.writeNextValue(out, XML_TAG_ROAMING_CONSORTIUM_OIS,
                homeSp.getRoamingConsortiumOis());
        XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_HOMESP);
    }

    /**
     * Serialize a {@link Credential} to an output stream as a XML block.
     *
     * @param out The output stream to serialize to
     * @param credential The {@link Credential} to serialize
     * @throws XmlPullParserException
     * @throws IOException
     */
    private static void serializeCredential(XmlSerializer out, Credential credential)
            throws XmlPullParserException, IOException {
        if (credential == null) {
            return;
        }
        XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_CREDENTIAL);
        XmlUtil.writeNextValue(out, XML_TAG_CREATION_TIME, credential.getCreationTimeInMillis());
        XmlUtil.writeNextValue(out, XML_TAG_EXPIRATION_TIME,
                credential.getExpirationTimeInMillis());
        XmlUtil.writeNextValue(out, XML_TAG_REALM, credential.getRealm());
        XmlUtil.writeNextValue(out, XML_TAG_CHECK_AAA_SERVER_CERT_STATUS,
                credential.getCheckAaaServerCertStatus());
        XmlUtil.writeNextValue(out, XML_TAG_MINIMUM_TLS_VERSION,
                credential.getMinimumTlsVersion());
        serializeUserCredential(out, credential.getUserCredential());
        serializeCertCredential(out, credential.getCertCredential());
        serializeSimCredential(out, credential.getSimCredential());
        XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_CREDENTIAL);
    }

    /**
     * Serialize a {@link Policy} to an output stream as a XML block.
     *
     * @param out The output stream to serialize to
     * @param policy The {@link Policy} to serialize
     * @throws XmlPullParserException
     * @throws IOException
     */
    private static void serializePolicy(XmlSerializer out, Policy policy)
            throws XmlPullParserException, IOException {
        if (policy == null) {
            return;
        }
        XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_POLICY);
        XmlUtil.writeNextValue(out, XML_TAG_MIN_HOME_DOWNLINK_BANDWIDTH,
                policy.getMinHomeDownlinkBandwidth());
        XmlUtil.writeNextValue(out, XML_TAG_MIN_HOME_UPLINK_BANDWIDTH,
                policy.getMinHomeUplinkBandwidth());
        XmlUtil.writeNextValue(out, XML_TAG_MIN_ROAMING_DOWNLINK_BANDWIDTH,
                policy.getMinRoamingDownlinkBandwidth());
        XmlUtil.writeNextValue(out, XML_TAG_MIN_ROAMING_UPLINK_BANDWIDTH,
                policy.getMinRoamingUplinkBandwidth());
        XmlUtil.writeNextValue(out, XML_TAG_EXCLUDED_SSID_LIST, policy.getExcludedSsidList());
        XmlUtil.writeNextValue(out, XML_TAG_MAXIMUM_BSS_LOAD_VALUE,
                policy.getMaximumBssLoadValue());
        serializeProtoPortMap(out, policy.getRequiredProtoPortMap());
        serializeUpdateParameter(out, XML_TAG_SECTION_HEADER_POLICY_UPDATE,
                policy.getPolicyUpdate());
        serializePreferredRoamingPartnerList(out, policy.getPreferredRoamingPartnerList());
        XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_POLICY);
    }

    /**
     * Serialize a {@link android.net.wifi.hotspot2.pps.Credential.UserCredential} to an output
     * stream as a XML block.
     *
     * @param out The output stream to serialize data to
     * @param userCredential The UserCredential to serialize
     * @throws XmlPullParserException
     * @throws IOException
     */
    private static void serializeUserCredential(XmlSerializer out,
            Credential.UserCredential userCredential) throws XmlPullParserException, IOException {
        if (userCredential == null) {
            return;
        }
        XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_USER_CREDENTIAL);
        XmlUtil.writeNextValue(out, XML_TAG_USERNAME, userCredential.getUsername());
        XmlUtil.writeNextValue(out, XML_TAG_PASSWORD, userCredential.getPassword());
        XmlUtil.writeNextValue(out, XML_TAG_MACHINE_MANAGED, userCredential.getMachineManaged());
        XmlUtil.writeNextValue(out, XML_TAG_SOFT_TOKEN_APP, userCredential.getSoftTokenApp());
        XmlUtil.writeNextValue(out, XML_TAG_ABLE_TO_SHARE, userCredential.getAbleToShare());
        XmlUtil.writeNextValue(out, XML_TAG_EAP_TYPE, userCredential.getEapType());
        XmlUtil.writeNextValue(out, XML_TAG_NON_EAP_INNER_METHOD,
                userCredential.getNonEapInnerMethod());
        XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_USER_CREDENTIAL);
    }

    /**
     * Serialize a {@link android.net.wifi.hotspot2.pps.Credential.CertificateCredential} to an
     * output stream as a XML block.
     *
     * @param out The output stream to serialize data to
     * @param certCredential The CertificateCredential to serialize
     * @throws XmlPullParserException
     * @throws IOException
     */
    private static void serializeCertCredential(XmlSerializer out,
            Credential.CertificateCredential certCredential)
                    throws XmlPullParserException, IOException {
        if (certCredential == null) {
            return;
        }
        XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_CERT_CREDENTIAL);
        XmlUtil.writeNextValue(out, XML_TAG_CERT_TYPE, certCredential.getCertType());
        XmlUtil.writeNextValue(out, XML_TAG_CERT_SHA256_FINGERPRINT,
                certCredential.getCertSha256Fingerprint());
        XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_CERT_CREDENTIAL);
    }

    /**
     * Serialize a {@link android.net.wifi.hotspot2.pps.Credential.SimCredential} to an
     * output stream as a XML block.
     *
     * @param out The output stream to serialize data to
     * @param simCredential The SimCredential to serialize
     * @throws XmlPullParserException
     * @throws IOException
     */
    private static void serializeSimCredential(XmlSerializer out,
            Credential.SimCredential simCredential) throws XmlPullParserException, IOException {
        if (simCredential == null) {
            return;
        }
        XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_SIM_CREDENTIAL);
        XmlUtil.writeNextValue(out, XML_TAG_IMSI, simCredential.getImsi());
        XmlUtil.writeNextValue(out, XML_TAG_EAP_TYPE, simCredential.getEapType());
        XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_SIM_CREDENTIAL);
    }

    /**
     * Serialize a preferred roaming partner list to an output stream as a XML block.
     *
     * @param out The output stream to serialize data to
     * @param preferredRoamingPartnerList The partner list to serialize
     * @throws XmlPullParserException
     * @throws IOException
     */
    private static void serializePreferredRoamingPartnerList(XmlSerializer out,
            List<Policy.RoamingPartner> preferredRoamingPartnerList)
                    throws XmlPullParserException, IOException {
        if (preferredRoamingPartnerList == null) {
            return;
        }
        XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_PREFERRED_ROAMING_PARTNER_LIST);
        for (Policy.RoamingPartner partner : preferredRoamingPartnerList) {
            XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_ROAMING_PARTNER);
            XmlUtil.writeNextValue(out, XML_TAG_FQDN, partner.getFqdn());
            XmlUtil.writeNextValue(out, XML_TAG_FQDN_EXACT_MATCH, partner.getFqdnExactMatch());
            XmlUtil.writeNextValue(out, XML_TAG_PRIORITY, partner.getPriority());
            XmlUtil.writeNextValue(out, XML_TAG_COUNTRIES, partner.getCountries());
            XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_ROAMING_PARTNER);
        }
        XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_PREFERRED_ROAMING_PARTNER_LIST);
    }

    /**
     * Serialize a {@link UpdateParameter} to an output stream as a XML block.  The
     * {@link UpdateParameter} are used for describing Subscription Update and Policy Update.
     *
     * @param out The output stream to serialize data to
     * @param type The type the {@link UpdateParameter} is used for
     * @param param The {@link UpdateParameter} to serialize
     * @throws XmlPullParserException
     * @throws IOException
     */
    private static void serializeUpdateParameter(XmlSerializer out, String type,
            UpdateParameter param) throws XmlPullParserException, IOException {
        if (param == null) {
            return;
        }
        XmlUtil.writeNextSectionStart(out, type);
        XmlUtil.writeNextValue(out, XML_TAG_UPDATE_INTERVAL, param.getUpdateIntervalInMinutes());
        XmlUtil.writeNextValue(out, XML_TAG_UPDATE_METHOD, param.getUpdateMethod());
        XmlUtil.writeNextValue(out, XML_TAG_RESTRICTION, param.getRestriction());
        XmlUtil.writeNextValue(out, XML_TAG_SERVER_URI, param.getServerUri());
        XmlUtil.writeNextValue(out, XML_TAG_USERNAME, param.getUsername());
        XmlUtil.writeNextValue(out, XML_TAG_PASSWORD, param.getBase64EncodedPassword());
        XmlUtil.writeNextValue(out, XML_TAG_TRUST_ROOT_CERT_URL, param.getTrustRootCertUrl());
        XmlUtil.writeNextValue(out, XML_TAG_TRUST_ROOT_CERT_SHA256_FINGERPRINT,
                param.getTrustRootCertSha256Fingerprint());
        XmlUtil.writeNextSectionEnd(out, type);
    }

    /**
     * Serialize a Protocol-to-Ports map to an output stream as a XML block.  We're not able
     * to use {@link XmlUtil#writeNextValue} to write this map, since that function only works for
     * maps with String key.
     *
     * @param out The output stream to serialize data to
     * @param protoPortMap The proto port map to serialize
     * @throws XmlPullParserException
     * @throws IOException
     */
    private static void serializeProtoPortMap(XmlSerializer out, Map<Integer, String> protoPortMap)
            throws XmlPullParserException, IOException {
        if (protoPortMap == null) {
            return;
        }
        XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_REQUIRED_PROTO_PORT_MAP);
        for (Map.Entry<Integer, String> entry : protoPortMap.entrySet()) {
            XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_PROTO_PORT);
            XmlUtil.writeNextValue(out, XML_TAG_PROTO, entry.getKey());
            XmlUtil.writeNextValue(out, XML_TAG_PORTS, entry.getValue());
            XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_PROTO_PORT);
        }
        XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_REQUIRED_PROTO_PORT_MAP);
    }

    /**
     * Serialize a AAA server trusted name list to an output stream as a XML block.
     *
     * @param out The output stream to serialize data to
     * @param aaaServerTrustedNames The name list to serialize
     * @throws XmlPullParserException
     * @throws IOException
     */
    private static void serializeAaaServerTrustedNames(
            XmlSerializer out, String[] aaaServerTrustedNames)
            throws XmlPullParserException, IOException {
        if (null == aaaServerTrustedNames) return;
        if (aaaServerTrustedNames.length == 0) return;

        XmlUtil.writeNextSectionStart(out, XML_TAG_SECTION_HEADER_AAA_SERVER_TRUSTED_NAMES);
        for (String fqdn: aaaServerTrustedNames) {
            XmlUtil.writeNextValue(out, XML_TAG_FQDN, fqdn);
        }
        XmlUtil.writeNextSectionEnd(out, XML_TAG_SECTION_HEADER_AAA_SERVER_TRUSTED_NAMES);
    }

    /**
     * Deserialize a {@link HomeSp} from an input stream.
     *
     * @param in The input stream to read data from
     * @param outerTagDepth The tag depth of the current XML section
     * @return {@link HomeSp}
     * @throws XmlPullParserException
     * @throws IOException
     */
    private static HomeSp deserializeHomeSP(XmlPullParser in, int outerTagDepth)
            throws XmlPullParserException, IOException {
        HomeSp homeSp = new HomeSp();
        while (!XmlUtil.isNextSectionEnd(in, outerTagDepth)) {
            String[] valueName = new String[1];
            Object value = XmlUtil.readCurrentValue(in, valueName);
            if (valueName[0] == null) {
                throw new XmlPullParserException("Missing value name");
            }
            switch (valueName[0]) {
                case XML_TAG_FQDN:
                    homeSp.setFqdn((String) value);
                    break;
                case XML_TAG_FRIENDLY_NAME:
                    homeSp.setFriendlyName((String) value);
                    break;
                case XML_TAG_ICON_URL:
                    homeSp.setIconUrl((String) value);
                    break;
                case XML_TAG_HOME_NETWORK_IDS:
                    homeSp.setHomeNetworkIds((Map<String, Long>) value);
                    break;
                case XML_TAG_MATCH_ALL_OIS:
                    homeSp.setMatchAllOis((long[]) value);
                    break;
                case XML_TAG_MATCH_ANY_OIS:
                    homeSp.setMatchAnyOis((long[]) value);
                    break;
                case XML_TAG_ROAMING_CONSORTIUM_OIS:
                    homeSp.setRoamingConsortiumOis((long[]) value);
                    break;
                case XML_TAG_OTHER_HOME_PARTNERS:
                    homeSp.setOtherHomePartners((String[]) value);
                    break;
                default:
                    Log.w(TAG, "Unknown data under HomeSP: " + valueName[0]);
            }
        }
        return homeSp;
    }

    /**
     * Deserialize a {@link Credential} from an input stream.
     *
     * @param in The input stream to read data from
     * @param outerTagDepth The tag depth of the current XML section
     * @return {@link Credential}
     * @throws XmlPullParserException
     * @throws IOException
     */
    private static Credential deserializeCredential(XmlPullParser in, int outerTagDepth)
            throws XmlPullParserException, IOException {
        Credential credential = new Credential();
        while (XmlUtil.nextElementWithin(in, outerTagDepth)) {
            if (isValueElement(in)) {
                // Value elements.
                String[] name = new String[1];
                Object value = XmlUtil.readCurrentValue(in, name);
                switch (name[0]) {
                    case XML_TAG_CREATION_TIME:
                        credential.setCreationTimeInMillis((long) value);
                        break;
                    case XML_TAG_EXPIRATION_TIME:
                        credential.setExpirationTimeInMillis((long) value);
                        break;
                    case XML_TAG_REALM:
                        credential.setRealm((String) value);
                        break;
                    case XML_TAG_CHECK_AAA_SERVER_CERT_STATUS:
                        credential.setCheckAaaServerCertStatus((boolean) value);
                        break;
                    case XML_TAG_MINIMUM_TLS_VERSION:
                        credential.setMinimumTlsVersion((int) value);
                        break;
                    default:
                        Log.w(TAG, "Unknown value under Credential: "
                            + name[0]);
                }
            } else {
                // Subsection elements.
                switch (in.getName()) {
                    case XML_TAG_SECTION_HEADER_USER_CREDENTIAL:
                        credential.setUserCredential(
                                deserializeUserCredential(in, outerTagDepth + 1));
                        break;
                    case XML_TAG_SECTION_HEADER_CERT_CREDENTIAL:
                        credential.setCertCredential(
                                deserializeCertCredential(in, outerTagDepth + 1));
                        break;
                    case XML_TAG_SECTION_HEADER_SIM_CREDENTIAL:
                        credential.setSimCredential(
                                deserializeSimCredential(in, outerTagDepth + 1));
                        break;
                    default:
                        Log.w(TAG, "Unknown section under Credential: "
                                + in.getName());
                }
            }
        }
        return credential;
    }

    /**
     * Deserialize a {@link Policy} from an input stream.
     *
     * @param in The input stream to read data from
     * @param outerTagDepth The tag depth of the current XML section
     * @return {@link Policy}
     * @throws XmlPullParserException
     * @throws IOException
     */
    private static Policy deserializePolicy(XmlPullParser in, int outerTagDepth)
            throws XmlPullParserException, IOException {
        Policy policy = new Policy();
        while (XmlUtil.nextElementWithin(in, outerTagDepth)) {
            if (isValueElement(in)) {
                // Value elements.
                String[] name = new String[1];
                Object value = XmlUtil.readCurrentValue(in, name);
                switch (name[0]) {
                    case XML_TAG_MIN_HOME_DOWNLINK_BANDWIDTH:
                        policy.setMinHomeDownlinkBandwidth((long) value);
                        break;
                    case XML_TAG_MIN_HOME_UPLINK_BANDWIDTH:
                        policy.setMinHomeUplinkBandwidth((long) value);
                        break;
                    case XML_TAG_MIN_ROAMING_DOWNLINK_BANDWIDTH:
                        policy.setMinRoamingDownlinkBandwidth((long) value);
                        break;
                    case XML_TAG_MIN_ROAMING_UPLINK_BANDWIDTH:
                        policy.setMinRoamingUplinkBandwidth((long) value);
                        break;
                    case XML_TAG_EXCLUDED_SSID_LIST:
                        policy.setExcludedSsidList((String[]) value);
                        break;
                    case XML_TAG_MAXIMUM_BSS_LOAD_VALUE:
                        policy.setMaximumBssLoadValue((int) value);
                        break;
                }
            } else {
                // Subsection elements.
                switch (in.getName()) {
                    case XML_TAG_SECTION_HEADER_REQUIRED_PROTO_PORT_MAP:
                        policy.setRequiredProtoPortMap(
                                deserializeProtoPortMap(in, outerTagDepth + 1));
                        break;
                    case XML_TAG_SECTION_HEADER_POLICY_UPDATE:
                        policy.setPolicyUpdate(deserializeUpdateParameter(in, outerTagDepth + 1));
                        break;
                    case XML_TAG_SECTION_HEADER_PREFERRED_ROAMING_PARTNER_LIST:
                        policy.setPreferredRoamingPartnerList(
                                deserializePreferredRoamingPartnerList(in, outerTagDepth + 1));
                        break;
                    default:
                        Log.w(TAG, "Unknown section under Policy: "
                                + in.getName());
                }
            }
        }
        return policy;
    }

    /**
     * Deserialize a {@link android.net.wifi.hotspot2.pps.Credential.UserCredential} from an
     * input stream.
     *
     * @param in The input stream to read data from
     * @param outerTagDepth The tag depth of the current XML section
     * @return {@link android.net.wifi.hotspot2.pps.Credential.UserCredential}
     * @throws XmlPullParserException
     * @throws IOException
     */
    private static Credential.UserCredential deserializeUserCredential(XmlPullParser in,
            int outerTagDepth) throws XmlPullParserException, IOException {
        Credential.UserCredential userCredential = new Credential.UserCredential();
        while (!XmlUtil.isNextSectionEnd(in, outerTagDepth)) {
            String[] valueName = new String[1];
            Object value = XmlUtil.readCurrentValue(in, valueName);
            if (valueName[0] == null) {
                throw new XmlPullParserException("Missing value name");
            }
            switch (valueName[0]) {
                case XML_TAG_USERNAME:
                    userCredential.setUsername((String) value);
                    break;
                case XML_TAG_PASSWORD:
                    userCredential.setPassword((String) value);
                    break;
                case XML_TAG_MACHINE_MANAGED:
                    userCredential.setMachineManaged((boolean) value);
                    break;
                case XML_TAG_SOFT_TOKEN_APP:
                    userCredential.setSoftTokenApp((String) value);
                    break;
                case XML_TAG_ABLE_TO_SHARE:
                    userCredential.setAbleToShare((boolean) value);
                    break;
                case XML_TAG_EAP_TYPE:
                    userCredential.setEapType((int) value);
                    break;
                case XML_TAG_NON_EAP_INNER_METHOD:
                    userCredential.setNonEapInnerMethod((String) value);
                    break;
                default:
                    Log.w(TAG, "Unknown value under UserCredential: "
                            + valueName[0]);
            }
        }
        return userCredential;
    }

    /**
     * Deserialize a {@link android.net.wifi.hotspot2.pps.Credential.CertificateCredential}
     * from an input stream.
     *
     * @param in The input stream to read data from
     * @param outerTagDepth The tag depth of the current XML section
     * @return {@link android.net.wifi.hotspot2.pps.Credential.CertificateCredential}
     * @throws XmlPullParserException
     * @throws IOException
     */
    private static Credential.CertificateCredential deserializeCertCredential(XmlPullParser in,
            int outerTagDepth) throws XmlPullParserException, IOException {
        Credential.CertificateCredential certCredential = new Credential.CertificateCredential();
        while (!XmlUtil.isNextSectionEnd(in, outerTagDepth)) {
            String[] valueName = new String[1];
            Object value = XmlUtil.readCurrentValue(in, valueName);
            if (valueName[0] == null) {
                throw new XmlPullParserException("Missing value name");
            }
            switch (valueName[0]) {
                case XML_TAG_CERT_TYPE:
                    certCredential.setCertType((String) value);
                    break;
                case XML_TAG_CERT_SHA256_FINGERPRINT:
                    certCredential.setCertSha256Fingerprint((byte[]) value);
                    break;
                default:
                    Log.w(TAG, "Unknown value under CertCredential: "
                            + valueName[0]);
            }
        }
        return certCredential;
    }

    /**
     * Deserialize a {@link android.net.wifi.hotspot2.pps.Credential.SimCredential} from an
     * input stream.
     *
     * @param in The input stream to read data from
     * @param outerTagDepth The tag depth of the current XML section
     * @return {@link android.net.wifi.hotspot2.pps.Credential.SimCredential}
     * @throws XmlPullParserException
     * @throws IOException
     */
    private static Credential.SimCredential deserializeSimCredential(XmlPullParser in,
            int outerTagDepth) throws XmlPullParserException, IOException {
        Credential.SimCredential simCredential = new Credential.SimCredential();
        while (!XmlUtil.isNextSectionEnd(in, outerTagDepth)) {
            String[] valueName = new String[1];
            Object value = XmlUtil.readCurrentValue(in, valueName);
            if (valueName[0] == null) {
                throw new XmlPullParserException("Missing value name");
            }
            switch (valueName[0]) {
                case XML_TAG_IMSI:
                    simCredential.setImsi((String) value);
                    break;
                case XML_TAG_EAP_TYPE:
                    simCredential.setEapType((int) value);
                    break;
                default:
                    Log.w(TAG, "Unknown value under CertCredential: "
                            + valueName[0]);
            }
        }
        return simCredential;
    }

    /**
     * Deserialize a list of {@link android.net.wifi.hotspot2.pps.Policy.RoamingPartner} from an
     * input stream.
     *
     * @param in The input stream to read data from
     * @param outerTagDepth The tag depth of the current XML section
     * @return List of {@link android.net.wifi.hotspot2.pps.Policy.RoamingPartner}
     * @throws XmlPullParserException
     * @throws IOException
     */
    private static List<Policy.RoamingPartner> deserializePreferredRoamingPartnerList(
            XmlPullParser in, int outerTagDepth) throws XmlPullParserException, IOException {
        List<Policy.RoamingPartner> roamingPartnerList = new ArrayList<>();
        while (XmlUtil.gotoNextSectionWithNameOrEnd(in, XML_TAG_SECTION_HEADER_ROAMING_PARTNER,
                outerTagDepth)) {
            roamingPartnerList.add(deserializeRoamingPartner(in, outerTagDepth + 1));
        }
        return roamingPartnerList;
    }

    /**
     * Deserialize a {@link android.net.wifi.hotspot2.pps.Policy.RoamingPartner} from an input
     * stream.
     *
     * @param in The input stream to read data from
     * @param outerTagDepth The tag depth of the current XML section
     * @return {@link android.net.wifi.hotspot2.pps.Policy.RoamingPartner}
     * @throws XmlPullParserException
     * @throws IOException
     */
    private static Policy.RoamingPartner deserializeRoamingPartner(XmlPullParser in,
            int outerTagDepth) throws XmlPullParserException, IOException {
        Policy.RoamingPartner partner = new Policy.RoamingPartner();
        while (!XmlUtil.isNextSectionEnd(in, outerTagDepth)) {
            String[] valueName = new String[1];
            Object value = XmlUtil.readCurrentValue(in, valueName);
            if (valueName[0] == null) {
                throw new XmlPullParserException("Missing value name");
            }
            switch (valueName[0]) {
                case XML_TAG_FQDN:
                    partner.setFqdn((String) value);
                    break;
                case XML_TAG_FQDN_EXACT_MATCH:
                    partner.setFqdnExactMatch((boolean) value);
                    break;
                case XML_TAG_PRIORITY:
                    partner.setPriority((int) value);
                    break;
                case XML_TAG_COUNTRIES:
                    partner.setCountries((String) value);
                    break;
                default:
                    Log.w(TAG, "Unknown value under RoamingPartner: "
                            + valueName[0]);
            }
        }
        return partner;
    }

    /**
     * Deserialize a {@link UpdateParameter} from an input stream.
     *
     * @param in The input stream to read data from
     * @param outerTagDepth The tag depth of the current XML section
     * @return {@link UpdateParameter}
     * @throws XmlPullParserException
     * @throws IOException
     */
    private static UpdateParameter deserializeUpdateParameter(XmlPullParser in,
            int outerTagDepth) throws XmlPullParserException, IOException {
        UpdateParameter param = new UpdateParameter();
        while (!XmlUtil.isNextSectionEnd(in, outerTagDepth)) {
            String[] valueName = new String[1];
            Object value = XmlUtil.readCurrentValue(in, valueName);
            if (valueName[0] == null) {
                throw new XmlPullParserException("Missing value name");
            }
            switch (valueName[0]) {
                case XML_TAG_UPDATE_INTERVAL:
                    param.setUpdateIntervalInMinutes((long) value);
                    break;
                case XML_TAG_UPDATE_METHOD:
                    param.setUpdateMethod((String) value);
                    break;
                case XML_TAG_RESTRICTION:
                    param.setRestriction((String) value);
                    break;
                case XML_TAG_SERVER_URI:
                    param.setServerUri((String) value);
                    break;
                case XML_TAG_USERNAME:
                    param.setUsername((String) value);
                    break;
                case XML_TAG_PASSWORD:
                    param.setBase64EncodedPassword((String) value);
                    break;
                case XML_TAG_TRUST_ROOT_CERT_URL:
                    param.setTrustRootCertUrl((String) value);
                    break;
                case XML_TAG_TRUST_ROOT_CERT_SHA256_FINGERPRINT:
                    param.setTrustRootCertSha256Fingerprint((byte[]) value);
                    break;
                default:
                    Log.w(TAG, "Unknown value under UpdateParameter: "
                            + valueName[0]);
            }
        }
        return param;
    }

    /**
     * Deserialize a Protocol-Port map from an input stream.
     *
     * @param in The input stream to read data from
     * @param outerTagDepth The tag depth of the current XML section
     * @return Proocol-Port map
     * @throws XmlPullParserException
     * @throws IOException
     */
    private static Map<Integer, String> deserializeProtoPortMap(XmlPullParser in,
            int outerTagDepth) throws XmlPullParserException, IOException {
        Map<Integer, String> protoPortMap = new HashMap<>();
        while (XmlUtil.gotoNextSectionWithNameOrEnd(in, XML_TAG_SECTION_HEADER_PROTO_PORT,
                outerTagDepth)) {
            int proto = (int) XmlUtil.readNextValueWithName(in, XML_TAG_PROTO);
            String ports = (String) XmlUtil.readNextValueWithName(in, XML_TAG_PORTS);
            protoPortMap.put(proto, ports);
        }
        return protoPortMap;
    }

    /**
     * Deserializen a AAA server trusted name list from an input stream.
     *
     * @param in The input stream to read data from
     * @param outerTagDepth The tag depth of the current XML section
     * @return a name array
     * @throws XmlPullParserException
     * @throws IOException
     */
    private static String[] deserializeAaaServerTrustedNames(XmlPullParser in, int outerTagDepth)
            throws XmlPullParserException, IOException {
        ArrayList<String> list = new ArrayList<>();
        while (!XmlUtil.isNextSectionEnd(in, outerTagDepth)) {
            String[] valueName = new String[1];
            Object value = XmlUtil.readCurrentValue(in, valueName);
            if (valueName[0] == null) {
                throw new XmlPullParserException("Missing value name");
            }
            switch (valueName[0]) {
                case XML_TAG_FQDN:
                    list.add((String) value);
                    break;
                default:
                    Log.w(TAG, "Unknown data under "
                            + XML_TAG_SECTION_HEADER_AAA_SERVER_TRUSTED_NAMES
                            + ": " + valueName[0]);
            }
        }
        return list.size() > 0 ? list.toArray(new String[0]) : null;
    }

    /**
     * Determine if the current element is a value or a section.  The "name" attribute of the
     * element is used as the indicator, when it is present, the element is considered a value
     * element.
     *
     * Value element:
     * <int name="test">12</int>
     *
     * Section element:
     * <Test>
     * ...
     * </Test>
     *
     * @param in XML input stream
     * @return true if the current element is a value
     */
    private static boolean isValueElement(XmlPullParser in) {
        return in.getAttributeValue(null, "name") != null;
    }
}
