/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import { Diagnostic } from "#log/Diagnostic.js";
import { Logger } from "#log/Logger.js";
import { Seconds } from "#time/TimeUnit.js";
import { isObject } from "#util/index.js";
import { InternalError, NotImplementedError, UnexpectedDataError } from "../MatterError.js";
import { Bytes } from "../util/Bytes.js";
import { DataReader } from "../util/DataReader.js";
import { DataWriter } from "../util/DataWriter.js";
import { ipv4BytesToString, ipv4ToBytes, ipv6BytesToString, ipv6ToBytes, isIPv4, isIPv6 } from "../util/Ip.js";
const logger = Logger.get("DnsCodec");
const DEFAULT_MDNS_TTL = Seconds(120);
const MAX_MDNS_MESSAGE_SIZE = 1232;
const PtrRecord = (name, ptr, ttl = DEFAULT_MDNS_TTL, flushCache = false) => ({
  name,
  value: ptr,
  ttl,
  recordType: 12 /* PTR */,
  recordClass: 1 /* IN */,
  flushCache
});
const ARecord = (name, ip, ttl = DEFAULT_MDNS_TTL, flushCache = false) => ({
  name,
  value: ip,
  ttl,
  recordType: 1 /* A */,
  recordClass: 1 /* IN */,
  flushCache
});
const AAAARecord = (name, ip, ttl = DEFAULT_MDNS_TTL, flushCache = false) => ({
  name,
  value: ip,
  ttl,
  recordType: 28 /* AAAA */,
  recordClass: 1 /* IN */,
  flushCache
});
const TxtRecord = (name, entries, ttl = DEFAULT_MDNS_TTL, flushCache = false) => ({
  name,
  value: entries.map((e) => typeof e === "string" ? Bytes.fromString(e) : Bytes.of(e)),
  ttl,
  recordType: 16 /* TXT */,
  recordClass: 1 /* IN */,
  flushCache
});
const SrvRecord = (name, srv, ttl = DEFAULT_MDNS_TTL, flushCache = false) => ({
  name,
  value: srv,
  ttl,
  recordType: 33 /* SRV */,
  recordClass: 1 /* IN */,
  flushCache
});
var DnsMessageTypeFlag = /* @__PURE__ */ ((DnsMessageTypeFlag2) => {
  DnsMessageTypeFlag2[DnsMessageTypeFlag2["QR"] = 32768] = "QR";
  DnsMessageTypeFlag2[DnsMessageTypeFlag2["OPCODE"] = 30720] = "OPCODE";
  DnsMessageTypeFlag2[DnsMessageTypeFlag2["AA"] = 1024] = "AA";
  DnsMessageTypeFlag2[DnsMessageTypeFlag2["TC"] = 512] = "TC";
  DnsMessageTypeFlag2[DnsMessageTypeFlag2["RD"] = 256] = "RD";
  DnsMessageTypeFlag2[DnsMessageTypeFlag2["RA"] = 128] = "RA";
  DnsMessageTypeFlag2[DnsMessageTypeFlag2["AD"] = 32] = "AD";
  DnsMessageTypeFlag2[DnsMessageTypeFlag2["CD"] = 16] = "CD";
  DnsMessageTypeFlag2[DnsMessageTypeFlag2["RCODE"] = 15] = "RCODE";
  return DnsMessageTypeFlag2;
})(DnsMessageTypeFlag || {});
var DnsMessageType = /* @__PURE__ */ ((DnsMessageType2) => {
  DnsMessageType2[DnsMessageType2["Query"] = 0] = "Query";
  DnsMessageType2[DnsMessageType2["Response"] = 33792] = "Response";
  return DnsMessageType2;
})(DnsMessageType || {});
((DnsMessageType2) => {
  function isQuery(type) {
    return (type & 32768 /* QR */) === 0;
  }
  DnsMessageType2.isQuery = isQuery;
  function isResponse(type) {
    return (type & 32768 /* QR */) !== 0;
  }
  DnsMessageType2.isResponse = isResponse;
})(DnsMessageType || (DnsMessageType = {}));
var DnsRecordType = /* @__PURE__ */ ((DnsRecordType2) => {
  DnsRecordType2[DnsRecordType2["A"] = 1] = "A";
  DnsRecordType2[DnsRecordType2["PTR"] = 12] = "PTR";
  DnsRecordType2[DnsRecordType2["TXT"] = 16] = "TXT";
  DnsRecordType2[DnsRecordType2["AAAA"] = 28] = "AAAA";
  DnsRecordType2[DnsRecordType2["SRV"] = 33] = "SRV";
  DnsRecordType2[DnsRecordType2["NSEC"] = 47] = "NSEC";
  DnsRecordType2[DnsRecordType2["ANY"] = 255] = "ANY";
  return DnsRecordType2;
})(DnsRecordType || {});
var DnsRecordClass = /* @__PURE__ */ ((DnsRecordClass2) => {
  DnsRecordClass2[DnsRecordClass2["IN"] = 1] = "IN";
  DnsRecordClass2[DnsRecordClass2["ANY"] = 255] = "ANY";
  return DnsRecordClass2;
})(DnsRecordClass || {});
class DnsCodec {
  static decode(message) {
    try {
      const reader = new DataReader(message);
      const transactionId = reader.readUInt16();
      const messageType = reader.readUInt16();
      const queriesCount = reader.readUInt16();
      const answersCount = reader.readUInt16();
      const authoritiesCount = reader.readUInt16();
      const additionalRecordsCount = reader.readUInt16();
      const queries = new Array();
      for (let i = 0; i < queriesCount; i++) {
        queries.push(this.decodeQuery(reader, message));
      }
      const answers = new Array();
      for (let i = 0; i < answersCount; i++) {
        const record = this.decodeRecord(reader, message);
        if (record !== void 0) {
          answers.push(record);
        }
      }
      const authorities = new Array();
      for (let i = 0; i < authoritiesCount; i++) {
        const record = this.decodeRecord(reader, message);
        if (record !== void 0) {
          authorities.push(record);
        }
      }
      const additionalRecords = new Array();
      for (let i = 0; i < additionalRecordsCount; i++) {
        const record = this.decodeRecord(reader, message);
        if (record !== void 0) {
          additionalRecords.push(record);
        }
      }
      return { transactionId, messageType, queries, answers, authorities, additionalRecords };
    } catch (error) {
      return void 0;
    }
  }
  static decodeQuery(reader, message) {
    const name = this.decodeQName(reader, message);
    const recordType = reader.readUInt16();
    const classInt = reader.readUInt16();
    const uniCastResponse = (classInt & 32768) !== 0;
    const recordClass = classInt & 32767;
    return { name, recordType, recordClass, uniCastResponse };
  }
  static decodeRecord(reader, message) {
    const name = this.decodeQName(reader, message);
    const recordType = reader.readUInt16();
    const classInt = reader.readUInt16();
    const flushCache = (classInt & 32768) !== 0;
    const recordClass = classInt & 32767;
    const ttl = Seconds(reader.readUInt32());
    const valueLength = reader.readUInt16();
    const valueBytes = reader.readByteArray(valueLength);
    const value = this.decodeRecordValue(valueBytes, recordType, message);
    if (recordType === void 0 || value === void 0) {
      return void 0;
    }
    return { name, recordType, recordClass, ttl, value, flushCache };
  }
  static decodeQName(reader, message, visited = /* @__PURE__ */ new Set()) {
    if (visited.has(reader.offset)) {
      throw new UnexpectedDataError(`QNAME pointer loop detected. Index ${reader.offset} visited twice.`);
    }
    visited.add(reader.offset);
    const messageReader = new DataReader(message);
    const qNameItems = new Array();
    while (true) {
      const itemLength = reader.readUInt8();
      if (itemLength === 0) break;
      if ((itemLength & 192) !== 0) {
        if (reader.remainingBytesCount < 1) {
          throw new UnexpectedDataError("QNAME pointer exceeds remaining bytes.");
        }
        const indexInMessage = reader.readUInt8() | (itemLength & 63) << 8;
        if (indexInMessage >= messageReader.length) {
          throw new UnexpectedDataError("Invalid compressed QNAME pointer pointing to out of bounds index.");
        }
        messageReader.offset = indexInMessage;
        qNameItems.push(this.decodeQName(messageReader, message, visited));
        break;
      } else if (reader.remainingBytesCount < itemLength + 1) {
        throw new UnexpectedDataError(`QNAME item length ${itemLength} exceeds remaining bytes.`);
      }
      qNameItems.push(reader.readUtf8String(itemLength));
    }
    return qNameItems.join(".");
  }
  static decodeRecordValue(valueBytes, recordType, message) {
    switch (recordType) {
      case 12 /* PTR */:
        return this.decodeQName(new DataReader(valueBytes), message);
      case 33 /* SRV */:
        return this.decodeSrvRecord(valueBytes, message);
      case 16 /* TXT */:
        return this.decodeTxtRecord(valueBytes);
      case 28 /* AAAA */:
        return this.decodeAaaaRecord(valueBytes);
      case 1 /* A */:
        return this.decodeARecord(valueBytes);
      default:
        return valueBytes;
    }
  }
  static decodeSrvRecord(valueBytes, message) {
    const reader = new DataReader(valueBytes);
    const priority = reader.readUInt16();
    const weight = reader.readUInt16();
    const port = reader.readUInt16();
    const target = this.decodeQName(reader, message);
    return { priority, weight, port, target };
  }
  static decodeTxtRecord(valueBytes) {
    const reader = new DataReader(valueBytes);
    const result = new Array();
    let bytesRead = 0;
    while (bytesRead < valueBytes.byteLength) {
      const length = reader.readUInt8();
      result.push(reader.readByteArray(length).slice());
      bytesRead += length + 1;
    }
    return result;
  }
  static decodeAaaaRecord(valueBytes) {
    const reader = new DataReader(valueBytes);
    return ipv6BytesToString(reader.readByteArray(16));
  }
  static decodeARecord(valueBytes) {
    const reader = new DataReader(valueBytes);
    return ipv4BytesToString(reader.readByteArray(4));
  }
  static encode({
    messageType,
    transactionId = 0,
    queries = [],
    answers = [],
    authorities = [],
    additionalRecords = []
  }) {
    if (messageType === void 0) throw new InternalError("Message type must be specified!");
    if (queries.length > 0 && !DnsMessageType.isQuery(messageType))
      throw new InternalError("Queries can only be included in query messages!");
    if (authorities.length > 0) throw new NotImplementedError("Authority answers are not supported yet!");
    const writer = new DataWriter();
    writer.writeUInt16(transactionId);
    writer.writeUInt16(messageType);
    writer.writeUInt16(queries.length);
    writer.writeUInt16(answers.length);
    writer.writeUInt16(authorities.length);
    writer.writeUInt16(additionalRecords.length);
    queries.forEach(({ name, recordClass, recordType, uniCastResponse = false }) => {
      writer.writeByteArray(this.encodeQName(name));
      writer.writeUInt16(recordType);
      writer.writeUInt16(recordClass | (uniCastResponse ? 32768 : 0));
    });
    [...answers, ...authorities, ...additionalRecords].forEach((record) => {
      if (Bytes.isBytes(record)) {
        writer.writeByteArray(record);
      } else {
        writer.writeByteArray(this.encodeRecord(record));
      }
    });
    return writer.toByteArray();
  }
  static encodeQuery(query) {
    const { name, recordType, recordClass, uniCastResponse = false } = query;
    const writer = new DataWriter();
    writer.writeByteArray(this.encodeQName(name));
    writer.writeUInt16(recordType);
    writer.writeUInt16(recordClass | (uniCastResponse ? 32768 : 0));
    return writer.toByteArray();
  }
  static encodeRecord(record) {
    if (!isObject(record) || Object.keys(record).length === 0) {
      logger.debug("Skipping record encoding: record is empty or not an object");
      return new Uint8Array(0);
    }
    const { name, recordType, recordClass, ttl, value, flushCache = false } = record;
    if (recordType === void 0 || value === void 0) {
      logger.warn("Skipping record encoding due to missing type or value.", Diagnostic.dict(record));
      return new Uint8Array(0);
    }
    const writer = new DataWriter();
    writer.writeByteArray(this.encodeQName(name));
    writer.writeUInt16(recordType);
    writer.writeUInt16(recordClass | (flushCache ? 32768 : 0));
    writer.writeUInt32(Seconds.of(ttl));
    const encodedValue = this.encodeRecordValue(value, recordType);
    writer.writeUInt16(encodedValue.byteLength);
    writer.writeByteArray(encodedValue);
    return writer.toByteArray();
  }
  static encodeRecordValue(value, recordType) {
    switch (recordType) {
      case 12 /* PTR */:
        return this.encodeQName(value);
      case 33 /* SRV */:
        return this.encodeSrvRecord(value);
      case 16 /* TXT */:
        return this.encodeTxtRecord(value);
      case 28 /* AAAA */:
        return this.encodeAaaaRecord(value);
      case 1 /* A */:
        return this.encodeARecord(value);
      default:
        if (Bytes.isBytes(value)) return value;
        throw new UnexpectedDataError(`Unsupported record type ${recordType}`);
    }
  }
  static encodeARecord(ip) {
    if (!isIPv4(ip)) throw new UnexpectedDataError(`Invalid A Record value: ${ip}`);
    return ipv4ToBytes(ip);
  }
  static encodeAaaaRecord(ip) {
    if (!isIPv6(ip)) throw new UnexpectedDataError(`Invalid AAAA Record value: ${ip}`);
    return ipv6ToBytes(ip);
  }
  /**
   * Encode TXT record entries (RFC 6763 §6).
   *
   * Accepts `(Bytes | string)[]` for ergonomic ASCII senders; the `string` arm exists to spare callers a per-entry
   * `Bytes.fromString` wrap.
   */
  static encodeTxtRecord(entries) {
    const writer = new DataWriter();
    if (entries.length === 0) {
      writer.writeUInt8(0);
      return writer.toByteArray();
    }
    entries.forEach((entry) => {
      let bytes = typeof entry === "string" ? Bytes.fromString(entry) : Bytes.of(entry);
      if (bytes.byteLength > 255) {
        logger.warn(
          `TXT record entry length ${bytes.byteLength} exceeds the RFC 6763 \xA76.1 limit of 255 bytes; truncating.`
        );
        bytes = Bytes.of(bytes).subarray(0, 255);
      }
      writer.writeUInt8(bytes.byteLength);
      writer.writeByteArray(bytes);
    });
    return writer.toByteArray();
  }
  static encodeSrvRecord({ priority, weight, port, target }) {
    const writer = new DataWriter();
    writer.writeUInt16(priority);
    writer.writeUInt16(weight);
    writer.writeUInt16(port);
    writer.writeByteArray(this.encodeQName(target));
    return writer.toByteArray();
  }
  static encodeQName(qname) {
    const writer = new DataWriter();
    if (qname !== void 0 && qname.length > 0) {
      qname.split(".").forEach((label) => {
        const labelData = Bytes.fromString(label);
        writer.writeUInt8(labelData.byteLength);
        writer.writeByteArray(labelData);
      });
    }
    writer.writeUInt8(0);
    return writer.toByteArray();
  }
}
export {
  AAAARecord,
  ARecord,
  DEFAULT_MDNS_TTL,
  DnsCodec,
  DnsMessageType,
  DnsMessageTypeFlag,
  DnsRecordClass,
  DnsRecordType,
  MAX_MDNS_MESSAGE_SIZE,
  PtrRecord,
  SrvRecord,
  TxtRecord
};
//# sourceMappingURL=DnsCodec.js.map
