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

package org.xbill.DNS;

import java.io.IOException;
import java.security.PublicKey;
import java.util.StringTokenizer;

/**
 * Key - contains a cryptographic public key. The data can be converted to objects implementing
 * java.security.interfaces.PublicKey
 *
 * @see DNSSEC
 * @see DNSKEYRecord
 * @author Brian Wellington
 * @see <a href="https://tools.ietf.org/html/rfc2535">RFC 2535: Domain Name System Security
 *     Extensions</a>
 * @see <a href="https://tools.ietf.org/html/rfc3755">RFC 3755: Legacy Resolver Compatibility for
 *     Delegation Signer (DS)</a>
 */
public class KEYRecord extends KEYBase {
  /** KEY protocol identifiers. */
  public static class Protocol {

    private Protocol() {}

    /** No defined protocol. */
    public static final int NONE = 0;

    /** Transaction Level Security */
    public static final int TLS = 1;

    /** Email */
    public static final int EMAIL = 2;

    /** DNSSEC */
    public static final int DNSSEC = 3;

    /** IPSEC Control */
    public static final int IPSEC = 4;

    /** Any protocol */
    public static final int ANY = 255;

    private static Mnemonic protocols = new Mnemonic("KEY protocol", Mnemonic.CASE_UPPER);

    static {
      protocols.setMaximum(0xFF);
      protocols.setNumericAllowed(true);

      protocols.add(NONE, "NONE");
      protocols.add(TLS, "TLS");
      protocols.add(EMAIL, "EMAIL");
      protocols.add(DNSSEC, "DNSSEC");
      protocols.add(IPSEC, "IPSEC");
      protocols.add(ANY, "ANY");
    }

    /** Converts an KEY protocol value into its textual representation */
    public static String string(int type) {
      return protocols.getText(type);
    }

    /**
     * Converts a textual representation of a KEY protocol into its numeric code. Integers in the
     * range 0..255 are also accepted.
     *
     * @param s The textual representation of the protocol
     * @return The protocol code, or -1 on error.
     */
    public static int value(String s) {
      return protocols.getValue(s);
    }
  }

  /** KEY flags identifiers. */
  public static class Flags {

    private Flags() {}

    /** KEY cannot be used for confidentiality */
    public static final int NOCONF = 0x4000;

    /** KEY cannot be used for authentication */
    public static final int NOAUTH = 0x8000;

    /** No key present */
    public static final int NOKEY = 0xC000;

    /** Bitmask of the use fields */
    public static final int USE_MASK = 0xC000;

    /** Flag 2 (unused) */
    public static final int FLAG2 = 0x2000;

    /** Flags extension */
    public static final int EXTEND = 0x1000;

    /** Flag 4 (unused) */
    public static final int FLAG4 = 0x0800;

    /** Flag 5 (unused) */
    public static final int FLAG5 = 0x0400;

    /** Key is owned by a user. */
    public static final int USER = 0x0000;

    /** Key is owned by a zone. */
    public static final int ZONE = 0x0100;

    /** Key is owned by a host. */
    public static final int HOST = 0x0200;

    /** Key owner type 3 (reserved). */
    public static final int NTYP3 = 0x0300;

    /** Key owner bitmask. */
    public static final int OWNER_MASK = 0x0300;

    /** Flag 8 (unused) */
    public static final int FLAG8 = 0x0080;

    /** Flag 9 (unused) */
    public static final int FLAG9 = 0x0040;

    /** Flag 10 (unused) */
    public static final int FLAG10 = 0x0020;

    /** Flag 11 (unused) */
    public static final int FLAG11 = 0x0010;

    /** Signatory value 0 */
    public static final int SIG0 = 0;

    /** Signatory value 1 */
    public static final int SIG1 = 1;

    /** Signatory value 2 */
    public static final int SIG2 = 2;

    /** Signatory value 3 */
    public static final int SIG3 = 3;

    /** Signatory value 4 */
    public static final int SIG4 = 4;

    /** Signatory value 5 */
    public static final int SIG5 = 5;

    /** Signatory value 6 */
    public static final int SIG6 = 6;

    /** Signatory value 7 */
    public static final int SIG7 = 7;

    /** Signatory value 8 */
    public static final int SIG8 = 8;

    /** Signatory value 9 */
    public static final int SIG9 = 9;

    /** Signatory value 10 */
    public static final int SIG10 = 10;

    /** Signatory value 11 */
    public static final int SIG11 = 11;

    /** Signatory value 12 */
    public static final int SIG12 = 12;

    /** Signatory value 13 */
    public static final int SIG13 = 13;

    /** Signatory value 14 */
    public static final int SIG14 = 14;

    /** Signatory value 15 */
    public static final int SIG15 = 15;

    private static Mnemonic flags = new Mnemonic("KEY flags", Mnemonic.CASE_UPPER);

    static {
      flags.setMaximum(0xFFFF);
      flags.setNumericAllowed(false);

      flags.add(NOCONF, "NOCONF");
      flags.add(NOAUTH, "NOAUTH");
      flags.add(NOKEY, "NOKEY");
      flags.add(FLAG2, "FLAG2");
      flags.add(EXTEND, "EXTEND");
      flags.add(FLAG4, "FLAG4");
      flags.add(FLAG5, "FLAG5");
      flags.add(USER, "USER");
      flags.add(ZONE, "ZONE");
      flags.add(HOST, "HOST");
      flags.add(NTYP3, "NTYP3");
      flags.add(FLAG8, "FLAG8");
      flags.add(FLAG9, "FLAG9");
      flags.add(FLAG10, "FLAG10");
      flags.add(FLAG11, "FLAG11");
      flags.add(SIG0, "SIG0");
      flags.add(SIG1, "SIG1");
      flags.add(SIG2, "SIG2");
      flags.add(SIG3, "SIG3");
      flags.add(SIG4, "SIG4");
      flags.add(SIG5, "SIG5");
      flags.add(SIG6, "SIG6");
      flags.add(SIG7, "SIG7");
      flags.add(SIG8, "SIG8");
      flags.add(SIG9, "SIG9");
      flags.add(SIG10, "SIG10");
      flags.add(SIG11, "SIG11");
      flags.add(SIG12, "SIG12");
      flags.add(SIG13, "SIG13");
      flags.add(SIG14, "SIG14");
      flags.add(SIG15, "SIG15");
    }

    /**
     * Converts a textual representation of KEY flags into its numeric code. Integers in the range
     * 0..65535 are also accepted.
     *
     * @param s The textual representation of the protocol
     * @return The protocol code, or -1 on error.
     */
    public static int value(String s) {
      int value;
      try {
        value = Integer.parseInt(s);
        if (value >= 0 && value <= 0xFFFF) {
          return value;
        }
        return -1;
      } catch (NumberFormatException e) {
      }
      StringTokenizer st = new StringTokenizer(s, "|");
      value = 0;
      while (st.hasMoreTokens()) {
        int val = flags.getValue(st.nextToken());
        if (val < 0) {
          return -1;
        }
        value |= val;
      }
      return value;
    }
  }

  /* flags */
  /** This key cannot be used for confidentiality (encryption) */
  public static final int FLAG_NOCONF = Flags.NOCONF;

  /** This key cannot be used for authentication */
  public static final int FLAG_NOAUTH = Flags.NOAUTH;

  /** This key cannot be used for authentication or confidentiality */
  public static final int FLAG_NOKEY = Flags.NOKEY;

  /** A zone key */
  public static final int OWNER_ZONE = Flags.ZONE;

  /** A host/end entity key */
  public static final int OWNER_HOST = Flags.HOST;

  /** A user key */
  public static final int OWNER_USER = Flags.USER;

  /* protocols */
  /** Key was created for use with transaction level security */
  public static final int PROTOCOL_TLS = Protocol.TLS;

  /** Key was created for use with email */
  public static final int PROTOCOL_EMAIL = Protocol.EMAIL;

  /** Key was created for use with DNSSEC */
  public static final int PROTOCOL_DNSSEC = Protocol.DNSSEC;

  /** Key was created for use with IPSEC */
  public static final int PROTOCOL_IPSEC = Protocol.IPSEC;

  /** Key was created for use with any protocol */
  public static final int PROTOCOL_ANY = Protocol.ANY;

  KEYRecord() {}

  /**
   * Creates a KEY Record from the given data
   *
   * @param flags Flags describing the key's properties
   * @param proto The protocol that the key was created for
   * @param alg The key's algorithm
   * @param key Binary data representing the key
   */
  public KEYRecord(Name name, int dclass, long ttl, int flags, int proto, int alg, byte[] key) {
    super(name, Type.KEY, dclass, ttl, flags, proto, alg, key);
  }

  /**
   * Creates a KEY Record from the given data
   *
   * @param flags Flags describing the key's properties
   * @param proto The protocol that the key was created for
   * @param alg The key's algorithm
   * @param key The key as a PublicKey
   * @throws DNSSEC.DNSSECException The PublicKey could not be converted into DNS format.
   */
  public KEYRecord(Name name, int dclass, long ttl, int flags, int proto, int alg, PublicKey key)
      throws DNSSEC.DNSSECException {
    super(name, Type.KEY, dclass, ttl, flags, proto, alg, DNSSEC.fromPublicKey(key, alg));
    publicKey = key;
  }

  @Override
  protected void rdataFromString(Tokenizer st, Name origin) throws IOException {
    String flagString = st.getIdentifier();
    flags = Flags.value(flagString);
    if (flags < 0) {
      throw st.exception("Invalid flags: " + flagString);
    }
    String protoString = st.getIdentifier();
    proto = Protocol.value(protoString);
    if (proto < 0) {
      throw st.exception("Invalid protocol: " + protoString);
    }
    String algString = st.getIdentifier();
    alg = DNSSEC.Algorithm.value(algString);
    if (alg < 0) {
      throw st.exception("Invalid algorithm: " + algString);
    }
    /* If this is a null KEY, there's no key data */
    if ((flags & Flags.USE_MASK) == Flags.NOKEY) {
      key = null;
    } else {
      key = st.getBase64();
    }
  }
}
