/*
 * Copyright (C) 2016 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.anqp;

import com.android.internal.annotations.VisibleForTesting;
import com.android.server.wifi.ByteBufferReader;

import java.net.ProtocolException;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

/**
 * Factory to build a collection of 802.11u ANQP elements from a byte buffer.
 */
public class ANQPParser {
    /**
     * The OI value for Hotspot 2.0 ANQP-element.
     */
    @VisibleForTesting
    public static final int VENDOR_SPECIFIC_HS20_OI = 0x506F9A;

    /**
     * The Type value for Hotspot 2.0 ANQP-element.
     */
    @VisibleForTesting
    public static final int VENDOR_SPECIFIC_HS20_TYPE = 0x11;

    /**
     * Parse an ANQP element from the pass-in byte buffer.
     *
     * Note: Each Hotspot 2.0 Release 2 element will be wrapped inside a Vendor Specific element
     * in the ANQP response from the AP.  However, the lower layer (e.g. wpa_supplicant) should
     * already take care of parsing those elements out of Vendor Specific elements.  To be safe,
     * we will parse the Vendor Specific elements for non-Hotspot 2.0 Release elements or in
     * the case they're not parsed by the lower layer.
     *
     * @param infoID The ANQP element type
     * @param payload The buffer to read from
     * @return {@link com.android.server.wifi.hotspot2.anqp.ANQPElement}
     * @throws BufferUnderflowException
     * @throws ProtocolException
     */
    public static ANQPElement parseElement(Constants.ANQPElementType infoID, ByteBuffer payload)
            throws ProtocolException {
        switch (infoID) {
            case ANQPVenueName:
                return VenueNameElement.parse(payload);
            case ANQPRoamingConsortium:
                return RoamingConsortiumElement.parse(payload);
            case ANQPIPAddrAvailability:
                return IPAddressTypeAvailabilityElement.parse(payload);
            case ANQPNAIRealm:
                return NAIRealmElement.parse(payload);
            case ANQP3GPPNetwork:
                return ThreeGPPNetworkElement.parse(payload);
            case ANQPDomName:
                return DomainNameElement.parse(payload);
            case ANQPVendorSpec:
                return parseVendorSpecificElement(payload);
            case ANQPVenueUrl:
                return VenueUrlElement.parse(payload);
            default:
                throw new ProtocolException("Unknown element ID: " + infoID);
        }
    }

    /**
     * Parse a Hotspot 2.0 Release 2 ANQP element from the pass-in byte buffer.
     *
     * @param infoID The ANQP element ID
     * @param payload The buffer to read from
     * @return {@link com.android.server.wifi.hotspot2.anqp.ANQPElement}
     * @throws BufferUnderflowException
     * @throws ProtocolException
     */
    public static ANQPElement parseHS20Element(Constants.ANQPElementType infoID,
            ByteBuffer payload) throws ProtocolException {
        switch (infoID) {
            case HSFriendlyName:
                return HSFriendlyNameElement.parse(payload);
            case HSWANMetrics:
                return HSWanMetricsElement.parse(payload);
            case HSConnCapability:
                return HSConnectionCapabilityElement.parse(payload);
            case HSOSUProviders:
                return HSOsuProvidersElement.parse(payload);
            default:
                throw new ProtocolException("Unknown element ID: " + infoID);
        }
    }

    /**
     * Parse the ANQP vendor specific element.  Currently only supports the vendor specific
     * element that contained Hotspot 2.0 ANQP-element.
     *
     * Format of a ANQP Vendor Specific element:
     * | OI | Type | Subtype | Reserved | Payload |
     *   3     1        1         1       variable
     *
     * @param payload The buffer to read from
     * @return {@link ANQPElement}
     * @throws BufferUnderflowException
     * @throws ProtocolException
     */
    private static ANQPElement parseVendorSpecificElement(ByteBuffer payload)
            throws ProtocolException {
        int oi = (int) ByteBufferReader.readInteger(payload, ByteOrder.BIG_ENDIAN, 3);
        int type = payload.get() & 0xFF;

        if (oi != VENDOR_SPECIFIC_HS20_OI || type != VENDOR_SPECIFIC_HS20_TYPE) {
            throw new ProtocolException("Unsupported vendor specific OI=" + oi + " type=" + type);
        }

        int subType = payload.get() & 0xFF;
        Constants.ANQPElementType hs20ID = Constants.mapHS20Element(subType);
        if (hs20ID == null) {
            throw new ProtocolException("Unsupported subtype: " + subType);
        }
        payload.get();     // Skip the reserved byte
        return parseHS20Element(hs20ID, payload);
    }
}
