// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)

package org.xbill.DNS;

import java.io.IOException;
import java.time.Duration;
import java.time.Instant;
import java.util.Date;
import org.xbill.DNS.utils.base64;

/**
 * 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.
 *
 * @see Resolver
 * @see TSIG
 * @see <a href="https://datatracker.ietf.org/doc/html/rfc2845">RFC 2845: Secret Key Transaction
 *     Authentication for DNS (TSIG)</a>
 * @author Brian Wellington
 */
public class TSIGRecord extends Record {
  private Name alg;
  private Instant timeSigned;
  private Duration fudge;
  private byte[] signature;
  private int originalID;
  private int error;
  private byte[] other;

  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
   * @deprecated use {@link #TSIGRecord(Name, int, long, Name, Instant, Duration, byte[], int, int,
   *     byte[])}
   */
  @Deprecated
  public TSIGRecord(
      Name name,
      int dclass,
      long ttl,
      Name alg,
      Date timeSigned,
      int fudge,
      byte[] signature,
      int originalID,
      int error,
      byte[] other) {
    this(
        name,
        dclass,
        ttl,
        alg,
        timeSigned.toInstant(),
        Duration.ofSeconds(fudge),
        signature,
        originalID,
        error,
        other);
  }

  /**
   * 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,
      Instant timeSigned,
      Duration fudge,
      byte[] signature,
      int originalID,
      int error,
      byte[] other) {
    super(name, Type.TSIG, dclass, ttl);
    this.alg = checkName("alg", alg);
    this.timeSigned = timeSigned;
    checkU16("fudge", (int) fudge.getSeconds());
    this.fudge = fudge;
    this.signature = signature;
    this.originalID = checkU16("originalID", originalID);
    this.error = checkU16("error", error);
    this.other = other;
  }

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

    long timeHigh = in.readU16();
    long timeLow = in.readU32();
    long time = (timeHigh << 32) + timeLow;
    timeSigned = Instant.ofEpochSecond(time);
    fudge = Duration.ofSeconds(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;
    }
  }

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

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

    sb.append(timeSigned.getEpochSecond());
    sb.append(" ");
    sb.append((int) fudge.getSeconds());
    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(Instant.ofEpochSecond(time));
          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 Instant getTimeSigned() {
    return timeSigned;
  }

  /** Returns the time fudge factor */
  public Duration 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;
  }

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

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

    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);
    }
  }
}
