// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)

package org.xbill.DNS;

import java.io.*;
import java.util.*;

import org.xbill.DNS.utils.*;

/**
 * Transaction Signature - this record is automatically generated by the
 * resolver.  TSIG records provide transaction security between the
 * sender and receiver of a message, using a shared key.
 *
 * @author Brian Wellington
 * @see Resolver
 * @see TSIG
 */

public class TSIGRecord extends Record {

    private static final long serialVersionUID = -88820909016649306L;

    private Name alg;
    private Date timeSigned;
    private int fudge;
    private byte[] signature;
    private int originalID;
    private int error;
    private byte[] other;

    TSIGRecord() {
    }

    Record
    getObject() {
        return new TSIGRecord();
    }

    /**
     * Creates a TSIG Record from the given data.  This is normally called by
     * the TSIG class
     *
     * @param alg        The shared key's algorithm
     * @param timeSigned The time that this record was generated
     * @param fudge      The fudge factor for time - if the time that the message is
     *                   received is not in the range [now - fudge, now + fudge], the signature
     *                   fails
     * @param signature  The signature
     * @param originalID The message ID at the time of its generation
     * @param error      The extended error field.  Should be 0 in queries.
     * @param other      The other data field.  Currently used only in BADTIME
     *                   responses.
     * @see TSIG
     */
    public TSIGRecord(Name name, int dclass, long ttl, Name alg, Date timeSigned,
                      int fudge, byte[] signature, int originalID, int error,
                      byte other[]) {
        super(name, Type.TSIG, dclass, ttl);
        this.alg = checkName("alg", alg);
        this.timeSigned = timeSigned;
        this.fudge = checkU16("fudge", fudge);
        this.signature = signature;
        this.originalID = checkU16("originalID", originalID);
        this.error = checkU16("error", error);
        this.other = other;
    }

    void
    rrFromWire(DNSInput in) throws IOException {
        alg = new Name(in);

        long timeHigh = in.readU16();
        long timeLow = in.readU32();
        long time = (timeHigh << 32) + timeLow;
        timeSigned = new Date(time * 1000);
        fudge = in.readU16();

        int sigLen = in.readU16();
        signature = in.readByteArray(sigLen);

        originalID = in.readU16();
        error = in.readU16();

        int otherLen = in.readU16();
        if (otherLen > 0)
            other = in.readByteArray(otherLen);
        else
            other = null;
    }

    void
    rdataFromString(Tokenizer st, Name origin) throws IOException {
        throw st.exception("no text format defined for TSIG");
    }

    /**
     * Converts rdata to a String
     */
    String
    rrToString() {
        StringBuffer sb = new StringBuffer();
        sb.append(alg);
        sb.append(" ");
        if (Options.check("multiline"))
            sb.append("(\n\t");

        sb.append(timeSigned.getTime() / 1000);
        sb.append(" ");
        sb.append(fudge);
        sb.append(" ");
        sb.append(signature.length);
        if (Options.check("multiline")) {
            sb.append("\n");
            sb.append(base64.formatString(signature, 64, "\t", false));
        } else {
            sb.append(" ");
            sb.append(base64.toString(signature));
        }
        sb.append(" ");
        sb.append(Rcode.TSIGstring(error));
        sb.append(" ");
        if (other == null)
            sb.append(0);
        else {
            sb.append(other.length);
            if (Options.check("multiline"))
                sb.append("\n\n\n\t");
            else
                sb.append(" ");
            if (error == Rcode.BADTIME) {
                if (other.length != 6) {
                    sb.append("<invalid BADTIME other data>");
                } else {
                    long time = ((long) (other[0] & 0xFF) << 40) +
                            ((long) (other[1] & 0xFF) << 32) +
                            ((other[2] & 0xFF) << 24) +
                            ((other[3] & 0xFF) << 16) +
                            ((other[4] & 0xFF) << 8) +
                            ((other[5] & 0xFF));
                    sb.append("<server time: ");
                    sb.append(new Date(time * 1000));
                    sb.append(">");
                }
            } else {
                sb.append("<");
                sb.append(base64.toString(other));
                sb.append(">");
            }
        }
        if (Options.check("multiline"))
            sb.append(" )");
        return sb.toString();
    }

    /**
     * Returns the shared key's algorithm
     */
    public Name
    getAlgorithm() {
        return alg;
    }

    /**
     * Returns the time that this record was generated
     */
    public Date
    getTimeSigned() {
        return timeSigned;
    }

    /**
     * Returns the time fudge factor
     */
    public int
    getFudge() {
        return fudge;
    }

    /**
     * Returns the signature
     */
    public byte[]
    getSignature() {
        return signature;
    }

    /**
     * Returns the original message ID
     */
    public int
    getOriginalID() {
        return originalID;
    }

    /**
     * Returns the extended error
     */
    public int
    getError() {
        return error;
    }

    /**
     * Returns the other data
     */
    public byte[]
    getOther() {
        return other;
    }

    void
    rrToWire(DNSOutput out, Compression c, boolean canonical) {
        alg.toWire(out, null, canonical);

        long time = timeSigned.getTime() / 1000;
        int timeHigh = (int) (time >> 32);
        long timeLow = (time & 0xFFFFFFFFL);
        out.writeU16(timeHigh);
        out.writeU32(timeLow);
        out.writeU16(fudge);

        out.writeU16(signature.length);
        out.writeByteArray(signature);

        out.writeU16(originalID);
        out.writeU16(error);

        if (other != null) {
            out.writeU16(other.length);
            out.writeByteArray(other);
        } else
            out.writeU16(0);
    }

}
