///|
/// Raw attribute names and values; values are never interpreted as text.
pub(all) struct Xattr {
  name : Bytes
  value : Bytes
} derive(Debug, Eq)

///|
priv struct Cursor {
  image : Image
  mut block : Int
  mut offset : Int
}

///|
fn Cursor::take(self : Cursor, size : Int) -> Bytes raise SquashError {
  guard size >= 0 && size <= 16777216 else { raise Limit("attribute size") }
  let out : Array[Byte] = []
  while out.length() < size {
    let (b, next) = self.image.metadata_block(self.block)
    guard self.offset >= 0 && self.offset <= b.length() else {
      raise Invalid(self.block, "cursor offset")
    }
    let n = Int::min(size - out.length(), b.length() - self.offset)
    append_bytes(out, region(b, self.offset, n))
    self.offset += n
    if self.offset == b.length() {
      self.block = next
      self.offset = 0
    }
  }
  Bytes::from_array(out)
}

///|
fn Image::xattr_header(self : Image) -> (Int, Int, Int) raise SquashError {
  let table = index(self.superblock.xattr_table, 56)
  let b = self.range(table, 16)
  let start = index(u64(b, 0), table)
  let count = index(u32(b, 8).to_uint64(), table)
  guard count > 0 &&
    count <= 1000000 &&
    start >= self.superblock.id_table &&
    start < table &&
    u32(b, 12) == 0U else {
    raise Invalid(table, "xattr table header")
  }
  ignore(self.range(table + 16, (count + 511) / 512 * 8))
  (start, count, table + 16)
}

///|
fn Image::xattr_id(self : Image, id : UInt) -> Array[Xattr] raise SquashError {
  if id == 0xffffffffU {
    return []
  }
  let (start, count, table) = self.xattr_header()
  guard id.to_uint64() < count.to_uint64() else {
    raise Invalid(table, "xattr ID out of range")
  }
  let n = id.reinterpret_as_int()
  let record = self.metadata(
    self.pointer(table + n / 512 * 8),
    n % 512 * 16,
    16,
  )
  let reference = u64(record, 0)
  let attrs = index(u32(record, 8).to_uint64(), table)
  let declared = index(u32(record, 12).to_uint64(), table)
  guard attrs <= 65536 && declared <= 16777216 else {
    raise Limit("xattr list budget")
  }
  let cursor : Cursor = {
    image: self,
    block: start + index(reference >> 16, table),
    offset: (reference & 0xffffUL).to_int(),
  }
  let result : Array[Xattr] = []
  let seen : Map[Bytes, Bool] = Map([])
  let mut total = 0
  for _ in 0.. b"user."
      1 => b"trusted."
      2 => b"security."
      _ => raise Unsupported("xattr namespace")
    }
    let size = u16(head, 2)
    guard size > 0 && size + prefix.length() <= 255 else {
      raise Invalid(table, "xattr name length")
    }
    let name = prefix + cursor.take(size)
    guard !name.iter().any(b => b == 0) && !seen.contains(name) else {
      raise Invalid(table, "duplicate or invalid xattr name")
    }
    seen[name] = true
    let length = index(u32(cursor.take(4), 0).to_uint64(), table)
    let value = if (kind & 256) != 0 {
      guard length == 8 else {
        raise Invalid(table, "out-of-line xattr reference size")
      }
      let value_ref = u64(cursor.take(8), 0)
      let other : Cursor = {
        image: self,
        block: start + index(value_ref >> 16, table),
        offset: (value_ref & 0xffffUL).to_int(),
      }
      let len = index(u32(other.take(4), 0).to_uint64(), table)
      other.take(len)
    } else {
      cursor.take(length)
    }
    guard value.length() <= 16777216 - total - name.length() - 1 else {
      raise Limit("xattr total budget")
    }
    total += name.length() + 1 + value.length()
    result.push({ name, value, })
  }
  guard total == declared else {
    raise Invalid(table, "xattr list size mismatch")
  }
  result
}

///|
pub fn Image::xattrs(
  self : Image,
  inode : Inode,
) -> Array[Xattr] raise SquashError {
  self.xattr_id(inode.xattr)
}

///|
/// Resolve the optional NFS export index by inode number.
pub fn Image::export_inode(
  self : Image,
  number : Int,
) -> Inode raise SquashError {
  guard number >= 1 && number <= self.superblock.inodes else {
    raise Invalid(number, "export inode number")
  }
  if self.superblock.export_table == 0xffffffffffffffffUL {
    raise Unsupported("image has no export table")
  }
  let table = index(self.superblock.export_table, 88)
  let n = number - 1
  let record = self.metadata(
    self.pointer(table + n / 1024 * 8),
    n % 1024 * 8,
    8,
  )
  let inode = self.inode(u64(record, 0))
  guard inode.number == number else {
    raise Invalid(table, "export index identity mismatch")
  }
  inode
}