Skip to content

QWK.NET ‐ Validation Modes

Agent 57951 edited this page Jan 19, 2026 · 4 revisions

Validation Modes

QWK.NET provides three validation modes to handle the reality that historical QWK packets may contain malformed data, missing fields, or non-standard extensions. This page explains what problem validation solves, how each mode behaves, and when to use them.

The Problem Validation Solves

Real-world QWK packets from historical BBS software often deviate from the specification:

  • Missing required fields - Some BBS implementations omitted fields or used shorter records
  • Format variations - Date formats, delimiters, and field lengths varied between implementations
  • Malformed data - Corruption from storage media, incomplete transfers, or software bugs
  • Non-standard extensions - Vendor-specific additions that don't match the specification

Without validation modes, the library would either:

  • Fail completely on any deviation (too strict for real-world packets)
  • Silently accept invalid data (too permissive for quality assurance)

Validation modes provide a balance: detect and report issues whilst allowing processing to continue when appropriate.

Validation Modes Overview

Mode Behaviour Default Values Exception Handling
Strict Throws immediately on any error None (fails before defaults) Throws QwkFormatException
Lenient Logs warnings, continues parsing Sensible defaults applied No exceptions, accumulates warnings
Salvage Best-effort recovery from damage Aggressive defaults No exceptions, accumulates warnings

Strict Mode

Purpose: Fail fast when packet correctness is critical.

Behaviour:

  • Throws QwkFormatException immediately upon encountering any structural error or missing required field
  • Processing stops at the first error
  • No default values are applied
  • No partial data is returned

Example:

try
{
    using QwkPacket packet = QwkPacket.Open("packet.qwk", ValidationMode.Strict);
    // If packet has any errors, execution never reaches here
    Console.WriteLine($"BBS: {packet.Control.BbsName}");
}
catch (QwkFormatException ex)
{
    // Exception thrown on first error encountered
    Console.WriteLine($"Validation failed: {ex.Message}");
}

Typical Use Cases:

  • Production systems requiring strict specification compliance
  • Quality assurance pipelines that must reject invalid packets
  • Packet generation tools that need to verify correct output
  • Automated systems where any deviation indicates a serious problem

Lenient Mode (Default - Recommended)

Purpose: Process packets with minor issues whilst recording warnings.

Behaviour:

  • Logs warnings for issues but continues parsing
  • Applies sensible default values for missing or invalid fields:
    • Missing required fields → empty string or appropriate default
    • Invalid date formats → DateTime.MinValue
    • Invalid numbers → 0
    • Empty BBS ID → "UNKNOWN"
  • Accumulates all validation issues in ValidationReport
  • Never throws exceptions during parsing

Example:

using QwkPacket packet = QwkPacket.Open("packet.qwk", ValidationMode.Lenient);
// Parsing always succeeds, even with issues

ValidationReport report = packet.ValidationReport;
if (!report.IsValid)
{
    // Check what issues were found
    foreach (var error in report.Errors)
    {
        Console.WriteLine($"Error: {error.Message} at {error.Location}");
    }
    foreach (var warning in report.Warnings)
    {
        Console.WriteLine($"Warning: {warning.Message} at {warning.Location}");
    }
}

// Packet data is still accessible, with defaults applied where needed
Console.WriteLine($"BBS: {packet.Control.BbsName}"); // May be "UNKNOWN" if missing

Typical Use Cases:

  • General-purpose packet reading applications
  • Offline mail readers that should display packets even with minor issues
  • Archive browsing tools
  • Most production scenarios where partial data is better than no data
  • Recommended for most scenarios

Salvage Mode

Purpose: Extract maximum data from damaged or highly suspect packets.

Behaviour:

  • Similar to Lenient mode but with more aggressive recovery strategies
  • Makes best-effort assumptions to extract as much data as possible
  • May attempt to recover from:
    • Truncated records
    • Corrupted index files
    • Missing structural elements
    • Severely malformed headers
  • Accumulates all validation issues in ValidationReport
  • Never throws exceptions during parsing

Example:

using QwkPacket packet = QwkPacket.Open("damaged-packet.qwk", ValidationMode.Salvage);

ValidationReport report = packet.ValidationReport;
Console.WriteLine($"Recovered {packet.Messages.Count} message(s)");
Console.WriteLine($"Errors: {report.Errors.Count}, Warnings: {report.Warnings.Count}");

// Even with many errors, some data may be recoverable
foreach (Message message in packet.Messages)
{
    // Some messages may have incomplete headers or bodies
    Console.WriteLine($"{message.From}{message.To}: {message.Subject}");
}

Typical Use Cases:

  • Digital archiving projects processing historical packets
  • Forensic analysis of corrupted packets
  • Recovery tools for damaged archives
  • Research projects where partial data is valuable
  • Processing packets from unreliable sources

Behavioural Differences

Missing Required Field

Strict: Throws QwkFormatException immediately
Lenient: Logs warning, uses empty string or appropriate default
Salvage: Logs warning, uses empty string or appropriate default (same as Lenient for this case)

Invalid Date Format

Strict: Throws QwkFormatException immediately
Lenient: Logs warning, sets date to DateTime.MinValue
Salvage: Logs warning, sets date to DateTime.MinValue (same as Lenient for this case)

Invalid Number Format

Strict: Throws QwkFormatException immediately
Lenient: Logs warning, uses 0
Salvage: Logs warning, uses 0 (same as Lenient for this case)

Corrupted Index File

Strict: Throws QwkFormatException when index cannot be parsed
Lenient: Logs warning, may skip index validation or use partial data
Salvage: Logs warning, attempts aggressive recovery (may reconstruct offsets, skip invalid entries)

Truncated Message Body

Strict: Throws QwkFormatException if message structure is invalid
Lenient: Logs warning, returns partial message body if possible
Salvage: Logs warning, attempts to recover partial body, may extract text even from corrupted structure

Choosing a Mode

Use Strict when:

  • You need guaranteed specification compliance
  • Invalid packets indicate a serious problem that must halt processing
  • You're generating packets and want to verify correctness
  • Your application cannot handle partial or default data

Use Lenient when:

  • You want to process packets with minor issues
  • Partial data is acceptable
  • You're building general-purpose tools
  • This is the recommended default for most scenarios

Use Salvage when:

  • Processing archival or damaged packets
  • Maximum data recovery is more important than correctness
  • You're doing forensic analysis
  • Packets are known to be corrupted or incomplete

Validation Reports

All modes (except Strict, which throws immediately) accumulate validation issues in a ValidationReport accessible via packet.ValidationReport. The report provides:

  • IsValid - Whether the packet passed all validation checks
  • Errors - List of error-level issues
  • Warnings - List of warning-level issues
  • Infos - List of informational messages
  • ToHumanReadableString() - Formatted report output
  • ToJson() - JSON export for automated processing

Further Reading

Clone this wiki locally