package org.apache.commons.net.ntp;
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import java.net.DatagramPacket;

/***
 * Implementation of NtpV3Packet with methods converting Java objects to/from
 * the Network Time Protocol (NTP) data message header format described in RFC-1305.
 *
 * @version $Revision: 1697280 $
 */
public class NtpV3Impl implements NtpV3Packet
{

    private static final int MODE_INDEX = 0;
    private static final int MODE_SHIFT = 0;

    private static final int VERSION_INDEX = 0;
    private static final int VERSION_SHIFT = 3;

    private static final int LI_INDEX = 0;
    private static final int LI_SHIFT = 6;

    private static final int STRATUM_INDEX = 1;
    private static final int POLL_INDEX = 2;
    private static final int PRECISION_INDEX = 3;

    private static final int ROOT_DELAY_INDEX = 4;
    private static final int ROOT_DISPERSION_INDEX = 8;
    private static final int REFERENCE_ID_INDEX = 12;

    private static final int REFERENCE_TIMESTAMP_INDEX = 16;
    private static final int ORIGINATE_TIMESTAMP_INDEX = 24;
    private static final int RECEIVE_TIMESTAMP_INDEX = 32;
    private static final int TRANSMIT_TIMESTAMP_INDEX = 40;

//    private static final int KEY_IDENTIFIER_INDEX = 48;
//    private static final int MESSAGE_DIGEST = 54; /* len 16 bytes */

    private final byte[] buf = new byte[48];

    private volatile DatagramPacket dp;

    /** Creates a new instance of NtpV3Impl */
    public NtpV3Impl()
    {
    }

    /***
     * Returns mode as defined in RFC-1305 which is a 3-bit integer
     * whose value is indicated by the MODE_xxx parameters.
     *
     * @return mode as defined in RFC-1305.
     */
//    @Override
    public int getMode()
    {
        return (ui(buf[MODE_INDEX]) >> MODE_SHIFT) & 0x7;
    }

    /***
     * Return human-readable name of message mode type as described in
     * RFC 1305.
     * @return mode name as string.
     */
//    @Override
    public String getModeName()
    {
        return NtpUtils.getModeName(getMode());
    }

    /***
     * Set mode as defined in RFC-1305.
     *
     * @param mode the mode to set
     */
//    @Override
    public void setMode(int mode)
    {
        buf[MODE_INDEX] = (byte) (buf[MODE_INDEX] & 0xF8 | mode & 0x7);
    }

    /***
     * Returns leap indicator as defined in RFC-1305 which is a two-bit code:
     *  0=no warning
     *  1=last minute has 61 seconds
     *  2=last minute has 59 seconds
     *  3=alarm condition (clock not synchronized)
     *
     * @return leap indicator as defined in RFC-1305.
     */
//    @Override
    public int getLeapIndicator()
    {
        return (ui(buf[LI_INDEX]) >> LI_SHIFT) & 0x3;
    }

    /***
     * Set leap indicator as defined in RFC-1305.
     *
     * @param li leap indicator.
     */
//    @Override
    public void setLeapIndicator(int li)
    {
        buf[LI_INDEX] = (byte) (buf[LI_INDEX] & 0x3F | ((li & 0x3) << LI_SHIFT));
    }

    /***
     * Returns poll interval as defined in RFC-1305, which is an eight-bit
     * signed integer indicating the maximum interval between successive
     * messages, in seconds to the nearest power of two (e.g. value of six
     * indicates an interval of 64 seconds. The values that can appear in
     * this field range from NTP_MINPOLL to NTP_MAXPOLL inclusive.
     *
     * @return poll interval as defined in RFC-1305.
     */
//    @Override
    public int getPoll()
    {
        return buf[POLL_INDEX];
    }

    /***
     * Set poll interval as defined in RFC-1305.
     *
     * @param poll poll interval.
     */
//    @Override
    public void setPoll(int poll)
    {
        buf[POLL_INDEX] = (byte) (poll & 0xFF);
    }

    /***
     * Returns precision as defined in RFC-1305 encoded as an 8-bit signed
     * integer (seconds to nearest power of two).
     * Values normally range from -6 to -20.
     *
     * @return precision as defined in RFC-1305.
     */
//    @Override
    public int getPrecision()
    {
        return buf[PRECISION_INDEX];
    }

    /***
     * Set precision as defined in RFC-1305.
     * @param precision the precision to set
     * @since 3.4
     */
    public void setPrecision(int precision)
    {
        buf[PRECISION_INDEX] = (byte) (precision & 0xFF);
    }

    /***
     * Returns NTP version number as defined in RFC-1305.
     *
     * @return NTP version number.
     */
//    @Override
    public int getVersion()
    {
        return (ui(buf[VERSION_INDEX]) >> VERSION_SHIFT) & 0x7;
    }

    /***
     * Set NTP version as defined in RFC-1305.
     *
     * @param version NTP version.
     */
//    @Override
    public void setVersion(int version)
    {
        buf[VERSION_INDEX] = (byte) (buf[VERSION_INDEX] & 0xC7 | ((version & 0x7) << VERSION_SHIFT));
    }

    /***
     * Returns Stratum as defined in RFC-1305, which indicates the stratum level
     * of the local clock, with values defined as follows: 0=unspecified,
     * 1=primary ref clock, and all others a secondary reference (via NTP).
     *
     * @return Stratum level as defined in RFC-1305.
     */
//    @Override
    public int getStratum()
    {
        return ui(buf[STRATUM_INDEX]);
    }

    /***
     * Set stratum level as defined in RFC-1305.
     *
     * @param stratum stratum level.
     */
//    @Override
    public void setStratum(int stratum)
    {
        buf[STRATUM_INDEX] = (byte) (stratum & 0xFF);
    }

    /***
     * Return root delay as defined in RFC-1305, which is the total roundtrip delay
     * to the primary reference source, in seconds. Values can take positive and
     * negative values, depending on clock precision and skew.
     *
     * @return root delay as defined in RFC-1305.
     */
//    @Override
    public int getRootDelay()
    {
        return getInt(ROOT_DELAY_INDEX);
    }

    /***
     * Set root delay as defined in RFC-1305.
     *
     * @param delay root delay
     * @since 3.4
     */
//    @Override
    public void setRootDelay(int delay)
    {
        setInt(ROOT_DELAY_INDEX, delay);
    }

    /**
     * Return root delay as defined in RFC-1305 in milliseconds, which is
     * the total roundtrip delay to the primary reference source, in
     * seconds. Values can take positive and negative values, depending
     * on clock precision and skew.
     *
     * @return root delay in milliseconds
     */
//    @Override
    public double getRootDelayInMillisDouble()
    {
        double l = getRootDelay();
        return l / 65.536;
    }

    /***
     * Returns root dispersion as defined in RFC-1305.
     * @return root dispersion.
     */
//    @Override
    public int getRootDispersion()
    {
        return getInt(ROOT_DISPERSION_INDEX);
    }

    /***
     * Set root dispersion as defined in RFC-1305.
     *
     * @param dispersion root dispersion
     * @since 3.4
     */
//    @Override
    public void setRootDispersion(int dispersion)
    {
        setInt(ROOT_DISPERSION_INDEX, dispersion);
    }

    /***
     * Returns root dispersion (as defined in RFC-1305) in milliseconds.
     *
     * @return root dispersion in milliseconds
     */
//    @Override
    public long getRootDispersionInMillis()
    {
        long l = getRootDispersion();
        return (l * 1000) / 65536L;
    }

    /***
     * Returns root dispersion (as defined in RFC-1305) in milliseconds
     * as double precision value.
     *
     * @return root dispersion in milliseconds
     */
//    @Override
    public double getRootDispersionInMillisDouble()
    {
        double l = getRootDispersion();
        return l / 65.536;
    }

    /***
     * Set reference clock identifier field with 32-bit unsigned integer value.
     * See RFC-1305 for description.
     *
     * @param refId reference clock identifier.
     */
//    @Override
    public void setReferenceId(int refId)
    {
        setInt(REFERENCE_ID_INDEX, refId);
    }

    /***
     * Returns the reference id as defined in RFC-1305, which is
     * a 32-bit integer whose value is dependent on several criteria.
     *
     * @return the reference id as defined in RFC-1305.
     */
//    @Override
    public int getReferenceId()
    {
        return getInt(REFERENCE_ID_INDEX);
    }

    /***
     * Returns the reference id string. String cannot be null but
     * value is dependent on the version of the NTP spec supported
     * and stratum level. Value can be an empty string, clock type string,
     * IP address, or a hex string.
     *
     * @return the reference id string.
     */
//    @Override
    public String getReferenceIdString()
    {
        int version = getVersion();
        int stratum = getStratum();
        if (version == VERSION_3 || version == VERSION_4) {
            if (stratum == 0 || stratum == 1) {
                return idAsString(); // 4-character ASCII string (e.g. GPS, USNO)
            }
            // in NTPv4 servers this is latest transmit timestamp of ref source
            if (version == VERSION_4) {
                return idAsHex();
            }
        }

        // Stratum 2 and higher this is a four-octet IPv4 address
        // of the primary reference host.
        if (stratum >= 2) {
            return idAsIPAddress();
        }
        return idAsHex();
    }

    /***
     * Returns Reference id as dotted IP address.
     * @return refId as IP address string.
     */
    private String idAsIPAddress()
    {
        return ui(buf[REFERENCE_ID_INDEX]) + "." +
                ui(buf[REFERENCE_ID_INDEX + 1]) + "." +
                ui(buf[REFERENCE_ID_INDEX + 2]) + "." +
                ui(buf[REFERENCE_ID_INDEX + 3]);
    }

    private String idAsString()
    {
        StringBuilder id = new StringBuilder();
        for (int i = 0; i <= 3; i++) {
            char c = (char) buf[REFERENCE_ID_INDEX + i];
            if (c == 0) {  // 0-terminated string
                break;
            }
            id.append(c);
        }
        return id.toString();
    }

    private String idAsHex()
    {
        return Integer.toHexString(getReferenceId());
    }

    /***
     * Returns the transmit timestamp as defined in RFC-1305.
     *
     * @return the transmit timestamp as defined in RFC-1305.
     * Never returns a null object.
     */
//    @Override
    public TimeStamp getTransmitTimeStamp()
    {
        return getTimestamp(TRANSMIT_TIMESTAMP_INDEX);
    }

    /***
     * Set transmit time with NTP timestamp.
     * If <code>ts</code> is null then zero time is used.
     *
     * @param ts NTP timestamp
     */
//    @Override
    public void setTransmitTime(TimeStamp ts)
    {
        setTimestamp(TRANSMIT_TIMESTAMP_INDEX, ts);
    }

    /***
     * Set originate timestamp given NTP TimeStamp object.
     * If <code>ts</code> is null then zero time is used.
     *
     * @param ts NTP timestamp
     */
//    @Override
    public void setOriginateTimeStamp(TimeStamp ts)
    {
        setTimestamp(ORIGINATE_TIMESTAMP_INDEX, ts);
    }

    /***
     * Returns the originate time as defined in RFC-1305.
     *
     * @return the originate time.
     * Never returns null.
     */
//    @Override
    public TimeStamp getOriginateTimeStamp()
    {
        return getTimestamp(ORIGINATE_TIMESTAMP_INDEX);
    }

    /***
     * Returns the reference time as defined in RFC-1305.
     *
     * @return the reference time as <code>TimeStamp</code> object.
     * Never returns null.
     */
//    @Override
    public TimeStamp getReferenceTimeStamp()
    {
        return getTimestamp(REFERENCE_TIMESTAMP_INDEX);
    }

    /***
     * Set Reference time with NTP timestamp. If <code>ts</code> is null
     * then zero time is used.
     *
     * @param ts NTP timestamp
     */
//    @Override
    public void setReferenceTime(TimeStamp ts)
    {
        setTimestamp(REFERENCE_TIMESTAMP_INDEX, ts);
    }

    /***
     * Returns receive timestamp as defined in RFC-1305.
     *
     * @return the receive time.
     * Never returns null.
     */
//    @Override
    public TimeStamp getReceiveTimeStamp()
    {
        return getTimestamp(RECEIVE_TIMESTAMP_INDEX);
    }

    /***
     * Set receive timestamp given NTP TimeStamp object.
     * If <code>ts</code> is null then zero time is used.
     *
     * @param ts timestamp
     */
//    @Override
    public void setReceiveTimeStamp(TimeStamp ts)
    {
        setTimestamp(RECEIVE_TIMESTAMP_INDEX, ts);
    }

    /***
     * Return type of time packet. The values (e.g. NTP, TIME, ICMP, ...)
     * correspond to the protocol used to obtain the timing information.
     *
     * @return packet type string identifier which in this case is "NTP".
     */
//    @Override
    public String getType()
    {
        return "NTP";
    }

    /***
     * @return 4 bytes as 32-bit int
     */
    private int getInt(int index)
    {
        int i = ui(buf[index]) << 24 |
                ui(buf[index + 1]) << 16 |
                ui(buf[index + 2]) << 8 |
                ui(buf[index + 3]);

        return i;
    }

    /***
     * Set integer value at index position.
     *
     * @param idx index position
     * @param value 32-bit int value
     */
    private void setInt(int idx, int value)
    {
        for (int i=3; i >= 0; i--) {
            buf[idx + i] = (byte) (value & 0xff);
            value >>>= 8; // shift right one-byte
        }
    }

    /**
     * Get NTP Timestamp at specified starting index.
     *
     * @param index index into data array
     * @return TimeStamp object for 64 bits starting at index
     */
    private TimeStamp getTimestamp(int index)
    {
        return new TimeStamp(getLong(index));
    }

    /***
     * Get Long value represented by bits starting at specified index.
     *
     * @return 8 bytes as 64-bit long
     */
    private long getLong(int index)
    {
        long i = ul(buf[index]) << 56 |
                ul(buf[index + 1]) << 48 |
                ul(buf[index + 2]) << 40 |
                ul(buf[index + 3]) << 32 |
                ul(buf[index + 4]) << 24 |
                ul(buf[index + 5]) << 16 |
                ul(buf[index + 6]) << 8 |
                ul(buf[index + 7]);
        return i;
    }

    /***
     * Sets the NTP timestamp at the given array index.
     *
     * @param index index into the byte array.
     * @param t TimeStamp.
     */
    private void setTimestamp(int index, TimeStamp t)
    {
        long ntpTime = (t == null) ? 0 : t.ntpValue();
        // copy 64-bits from Long value into 8 x 8-bit bytes of array
        // one byte at a time shifting 8-bits for each position.
        for (int i = 7; i >= 0; i--) {
            buf[index + i] = (byte) (ntpTime & 0xFF);
            ntpTime >>>= 8; // shift to next byte
        }
        // buf[index] |= 0x80;  // only set if 1900 baseline....
    }

    /***
     * Returns the datagram packet with the NTP details already filled in.
     *
     * @return a datagram packet.
     */
//    @Override
    public synchronized DatagramPacket getDatagramPacket()
    {
        if (dp == null) {
            dp = new DatagramPacket(buf, buf.length);
            dp.setPort(NTP_PORT);
        }
        return dp;
    }

    /***
     * Set the contents of this object from source datagram packet.
     *
     * @param srcDp source DatagramPacket to copy contents from, never null.
     * @throws IllegalArgumentException if srcDp is null or byte length is less than minimum length of 48 bytes
     */
//    @Override
    public void setDatagramPacket(DatagramPacket srcDp)
    {
        if (srcDp == null || srcDp.getLength() < buf.length) {
            throw new IllegalArgumentException();
        }
        byte[] incomingBuf = srcDp.getData();
        int len = srcDp.getLength();
        if (len > buf.length) {
            len = buf.length;
        }
        System.arraycopy(incomingBuf, 0, buf, 0, len);
        DatagramPacket dp = getDatagramPacket();
        dp.setAddress(srcDp.getAddress());
        int port = srcDp.getPort();
        dp.setPort(port > 0 ? port : NTP_PORT);
        dp.setData(buf);
    }

    /***
     * Compares this object against the specified object.
     * The result is <code>true</code> if and only if the argument is
     * not <code>null</code> and is a <code>NtpV3Impl</code> object that
     * contains the same values as this object.
     *
     * @param   obj   the object to compare with.
     * @return  <code>true</code> if the objects are the same;
     *          <code>false</code> otherwise.
     * @since 3.4
     */
    @Override
    public boolean equals(Object obj)
    {
        if (this == obj) {
            return true;
        }
        if (obj == null || getClass() != obj.getClass()) {
            return false;
        }
        NtpV3Impl other = (NtpV3Impl) obj;
        return java.util.Arrays.equals(buf, other.buf);
    }

    /***
     * Computes a hashcode for this object. The result is the exclusive
     * OR of the values of this object stored as a byte array.
     *
     * @return  a hash code value for this object.
     * @since 3.4
     */
    @Override
    public int hashCode()
    {
        return java.util.Arrays.hashCode(buf);
    }

    /***
     * Convert byte to unsigned integer.
     * Java only has signed types so we have to do
     * more work to get unsigned ops.
     *
     * @param b input byte
     * @return unsigned int value of byte
     */
    protected static final int ui(byte b)
    {
        int i = b & 0xFF;
        return i;
    }

    /***
     * Convert byte to unsigned long.
     * Java only has signed types so we have to do
     * more work to get unsigned ops
     *
     * @param b input byte
     * @return unsigned long value of byte
     */
    protected static final long ul(byte b)
    {
        long i = b & 0xFF;
        return i;
    }

    /***
     * Returns details of NTP packet as a string.
     *
     * @return details of NTP packet as a string.
     */
    @Override
    public String toString()
    {
        return "[" +
                "version:" + getVersion() +
                ", mode:" + getMode() +
                ", poll:" + getPoll() +
                ", precision:" + getPrecision() +
                ", delay:" + getRootDelay() +
                ", dispersion(ms):" + getRootDispersionInMillisDouble() +
                ", id:" + getReferenceIdString() +
                ", xmitTime:" + getTransmitTimeStamp().toDateString() +
                " ]";
    }

}
