Programming Language

Nocter

A self-contained systems language built around simplicity, encapsulation, and foolproof design.

inspection.nct

see ./index.nct

see ./source.nct

see ./storage.nct

use std/archive
use std/archive.{TarEntryKind, TarStream, TarStreamStep}
use std/compress.BlockingGzipReader
use std/io.BlockingReader
use std/set.Set
use std/string.String
use std/vec.Vec

const DECODE_BUFFER_BYTES: usize = 8192

noalloc func entry_limit_error(): error {
    return error.new("archive-inspect.entry_limit", "archive entry count exceeds its configured limit")
}

noalloc func path_limit_error(): error {
    return error.new("archive-inspect.path_limit", "archive path exceeds its configured limit")
}

noalloc func duplicate_path_error(): error {
    return error.new("archive-inspect.duplicate_path", "archive contains a duplicate normalized path")
}

noalloc func unsupported_entry_error(): error {
    return error.new("archive-inspect.unsupported_entry", "archive entry kind is not inspectable")
}

noalloc func missing_entry_error(): error {
    return error.new("archive-inspect.invalid_progress", "archive published an entry event without metadata")
}

blocking func inspect<R>(source: R, limits: ArchiveLimits): ArchiveReport! where R impl BlockingReader {
    let compressed = LimitedSource.compressed(move source, limits.input_bytes)
    let gzip = BlockingGzipReader.with_capacity(move compressed, DECODE_BUFFER_BYTES)
    var decoded = LimitedSource.decoded(move gzip, limits.decoded_bytes)
    var tar = TarStream.with_capacity(DECODE_BUFFER_BYTES)
    var output = source_buffer(DECODE_BUFFER_BYTES)
    var paths: Set<String> = Set.empty()
    var entries: Vec<InspectedEntry> = Vec.empty()
    var declared_body_bytes: u64 = 0

    loop {
        let step = archive.next_blocking(&+tar, &+decoded, &+output)?
        match step {
            TarStreamStep.entry {
                if entries.len() >= limits.entries { return entry_limit_error() }
                let entry = tar.entry() otherwise { return missing_entry_error() }
                let path = entry.path()
                if path.len() > limits.path_bytes { return path_limit_error() }
                let candidate = String.copy(path)
                if paths.contains(&candidate) { return duplicate_path_error() }
                if entry.size() > limits.decoded_bytes - declared_body_bytes {
                    return decoded_output_limit_error()
                }
                var directory = false
                match entry.kind() {
                    TarEntryKind.file {}
                    TarEntryKind.directory {
                        if entry.size() != 0 { return unsupported_entry_error() }
                        directory = true
                    }
                    TarEntryKind.unsupported(_) { return unsupported_entry_error() }
                }
                declared_body_bytes += entry.size()
                let stored_path = String.copy(path)
                let _ = paths.insert(move candidate)
                entries.push(
                    InspectedEntry {
                        path: move stored_path,
                        size: entry.size(),
                        directory: directory,
                    },
                )
            }
            TarStreamStep.body(_) {}
            TarStreamStep.finished { break }
        }
    }
    return ArchiveReport { entries: move entries }
}

instance ArchiveReport {
    noalloc method &self.entry_count(): usize { return self.entries.len() }

    noalloc method &self.entry_path(index: usize): &str? from self {
        let entry = self.entries.get(index)?
        return &entry.path as &str
    }

    noalloc method &self.entry_size(index: usize): u64? {
        let entry = self.entries.get(index)?
        return entry.size
    }

    noalloc method &self.entry_is_directory(index: usize): bool? {
        let entry = self.entries.get(index)?
        return entry.directory
    }
}