///|
/// Read only real files into the callback index space. BEP 47 padding belongs
/// to the SHA-1 storage stream, never to the filesystem or real-file indices.
fn read_v1_layout(
  info : BNode,
  name : String,
  piece : Int64,
) -> (Array[SourceFile], Array[DataEntry], Bool, Int64) raise CrateError {
  let single = lookup(info, "length") is Some(_)
  let nodes = if single {
    if lookup(info, "files") is Some(_) {
      reject("metainfo", "length and files are mutually exclusive")
    }
    [info]
  } else {
    as_list(require_key(info, "files"))
  }
  if nodes.is_empty() || nodes.length() > 65536 {
    reject("limit", "expected 1..65536 v1 storage entries including padding")
  }
  let files : Array[SourceFile] = []
  let entries : Array[DataEntry] = []
  let mut total = 0L
  for node in nodes {
    let attr = match lookup(node, "attr") {
      Some(value) => as_text(value)
      None => ""
    }
    if (attr != "" && (attr != "p" || single)) ||
      lookup(node, "symlink path") is Some(_) {
      reject("metainfo", "unsupported v1 file attribute or symlink")
    }
    let length = as_int(require_key(node, "length"))
    if length < 0L || length > 9007199254740991L - total {
      reject("limit", "invalid v1 storage length")
    }
    // The extension permits an omitted padding path. Present paths still use
    // our safe component subset, but are not resolved, opened or deduplicated.
    let path : Array[String] = if single {
      [name]
    } else {
      match lookup(node, "path") {
        None => {
          if attr != "p" {
            reject("metainfo", "missing real-file path")
          }
          []
        }
        Some(value) => {
          let parts : Array[String] = []
          for component in as_list(value) {
            let part = as_text(component)
            check_component(part)
            parts.push(part)
          }
          if parts.is_empty() || parts.length() > 32 {
            reject("path", "expected 1..32 v1 path components")
          }
          parts
        }
      }
    }
    if attr == "p" {
      if length == 0L || length >= piece || total % piece + length != piece {
        reject("padding", "v1 padding must fill a partial piece boundary")
      }
      entries.push({ source: -1, length, padding: true, })
    } else {
      entries.push({ source: files.length(), length, padding: false, })
      files.push({ path, length, })
    }
    total += length
  }
  if (total + piece - 1L) / piece > 400000L {
    reject("limit", "too many v1 storage pieces including padding")
  }
  (files, entries, single, total)
}