/*
 * Copyright (C) 2018 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 android.net.dhcp;

import static com.android.net.module.util.Inet4AddressUtils.inet4AddressToIntHTH;

import android.net.MacAddress;
import android.os.SystemClock;
import android.text.TextUtils;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.android.internal.util.HexDump;

import java.net.Inet4Address;
import java.util.Arrays;
import java.util.Objects;

/**
 * An IPv4 address assignment done through DHCPv4.
 * @hide
 */
public class DhcpLease {
    public static final long EXPIRATION_NEVER = Long.MAX_VALUE;
    public static final String HOSTNAME_NONE = null;

    @Nullable
    private final byte[] mClientId;
    @NonNull
    private final MacAddress mHwAddr;
    @NonNull
    private final Inet4Address mNetAddr;
    private final int mPrefixLength;
    /**
     * Expiration time for the lease, to compare with {@link SystemClock#elapsedRealtime()}.
     */
    private final long mExpTime;
    @Nullable
    private final String mHostname;

    public DhcpLease(@Nullable byte[] clientId, @NonNull MacAddress hwAddr,
            @NonNull Inet4Address netAddr, int prefixLength, long expTime,
            @Nullable String hostname) {
        mClientId = (clientId == null ? null : Arrays.copyOf(clientId, clientId.length));
        mHwAddr = hwAddr;
        mNetAddr = netAddr;
        mPrefixLength = prefixLength;
        mExpTime = expTime;
        mHostname = hostname;
    }

    /**
     * Get the clientId associated with this lease, if any.
     *
     * <p>If the lease is not associated to a clientId, this returns null.
     */
    @Nullable
    public byte[] getClientId() {
        if (mClientId == null) {
            return null;
        }
        return Arrays.copyOf(mClientId, mClientId.length);
    }

    @NonNull
    public MacAddress getHwAddr() {
        return mHwAddr;
    }

    @Nullable
    public String getHostname() {
        return mHostname;
    }

    @NonNull
    public Inet4Address getNetAddr() {
        return mNetAddr;
    }

    public int getPrefixLength() {
        return mPrefixLength;
    }

    public long getExpTime() {
        return mExpTime;
    }

    /**
     * Push back the expiration time of this lease. If the provided time is sooner than the original
     * expiration time, the lease time will not be updated.
     *
     * <p>The lease hostname is updated with the provided one if set.
     * @return A {@link DhcpLease} with expiration time set to max(expTime, currentExpTime)
     */
    public DhcpLease renewedLease(long expTime, @Nullable String hostname) {
        return new DhcpLease(mClientId, mHwAddr, mNetAddr, mPrefixLength,
                Math.max(expTime, mExpTime),
                (hostname == null ? mHostname : hostname));
    }

    /**
     * Determine whether this lease matches a client with the specified parameters.
     * @param clientId clientId of the client if any, or null otherwise.
     * @param hwAddr Hardware address of the client.
     */
    public boolean matchesClient(@Nullable byte[] clientId, @NonNull MacAddress hwAddr) {
        if (mClientId != null) {
            return Arrays.equals(mClientId, clientId);
        } else {
            return clientId == null && mHwAddr.equals(hwAddr);
        }
    }

    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof DhcpLease)) {
            return false;
        }
        final DhcpLease other = (DhcpLease) obj;
        return Arrays.equals(mClientId, other.mClientId)
                && mHwAddr.equals(other.mHwAddr)
                && mNetAddr.equals(other.mNetAddr)
                && mPrefixLength == other.mPrefixLength
                && mExpTime == other.mExpTime
                && TextUtils.equals(mHostname, other.mHostname);
    }

    @Override
    public int hashCode() {
        return Objects.hash(Arrays.hashCode(mClientId), mHwAddr, mNetAddr, mPrefixLength,
                mHostname, mExpTime);
    }

    static String clientIdToString(byte[] bytes) {
        if (bytes == null) {
            return "null";
        }
        return HexDump.toHexString(bytes);
    }

    static String inet4AddrToString(@Nullable Inet4Address addr) {
        return (addr == null) ? "null" : addr.getHostAddress();
    }

    @Override
    public String toString() {
        return String.format("clientId: %s, hwAddr: %s, netAddr: %s/%d, expTime: %d,"
                        + "hostname: %s",
                clientIdToString(mClientId), mHwAddr.toString(), inet4AddrToString(mNetAddr),
                mPrefixLength, mExpTime, mHostname);
    }

    /**
     * Create a {@link DhcpLeaseParcelable} containing the information held in this lease.
     */
    public DhcpLeaseParcelable toParcelable() {
        final DhcpLeaseParcelable p = new DhcpLeaseParcelable();
        p.clientId = mClientId == null ? null : Arrays.copyOf(mClientId, mClientId.length);
        p.hwAddr = mHwAddr.toByteArray();
        p.netAddr = inet4AddressToIntHTH(mNetAddr);
        p.prefixLength = mPrefixLength;
        p.expTime = mExpTime;
        p.hostname = mHostname;

        return p;
    }
}
