package backupformat import ( "time" ) // ContainerHeader steht am Anfang jedes Containers. // // Er enthält genau die Angaben, die zum Deuten des restlichen Inhalts nötig // sind. Alles Weitere steht in den Abschnitten. Diese Trennung hält den Header // klein — er wird bei jedem Öffnen gelesen, auch wenn nur die Version // interessiert (SYNCOVA_ARCHITECTURE.md §8). type ContainerHeader struct { // BackupID ist die Kennung des enthaltenen Backups. BackupID string `json:"backup_id"` // ChainID verbindet das Backup mit seiner Kette. ChainID string `json:"chain_id"` // ParentBackupID benennt das Elternbackup einer Zusatzsicherung. // // Ohne diese Angabe liesse sich eine Kette aus einzelnen Containern nicht // rekonstruieren. ParentBackupID string `json:"parent_backup_id,omitempty"` // SourceID ist die Kennung der gesicherten Quelle. SourceID string `json:"source_id"` // SourceType benennt die Art der Quelle, z. B. proxmox_vm oder filesystem. SourceType string `json:"source_type"` // CreatedAt ist der Zeitpunkt der Erzeugung in UTC. CreatedAt time.Time `json:"created_at"` // CreatedByVersion ist die erzeugende Programmversion. // // Bei einer Notfallanalyse lässt sich damit feststellen, welche Software // den Container geschrieben hat. CreatedByVersion string `json:"created_by_version"` // RepositoryID benennt das Ursprungs-Repository. RepositoryID string `json:"repository_id,omitempty"` // Encryption beschreibt die Verschlüsselung des Inhalts. Encryption EncryptionMetadata `json:"encryption"` // Compression beschreibt die Kompression des Inhalts. Compression CompressionMetadata `json:"compression"` } // EncryptionMetadata beschreibt die Verschlüsselung eines Containers. // // Die Angaben stehen bewusst im Klartext im Header: ohne sie liesse sich nicht // einmal feststellen, welcher Schlüssel benötigt wird. Sie verraten nichts über // den Inhalt (PROMPT.md §12). type EncryptionMetadata struct { // Algorithm benennt das Verfahren; leer bedeutet unverschlüsselt. Algorithm string `json:"algorithm,omitempty"` // KeyVersion benennt den zur Entschlüsselung nötigen Schlüssel. // // Ohne diese Angabe wäre ein Container nach einer Schlüsselrotation nicht // mehr zuzuordnen und damit unlesbar (PROMPT.md §143). KeyVersion string `json:"key_version,omitempty"` // KeyDerivation benennt das Verfahren der Schlüsselableitung. KeyDerivation string `json:"key_derivation,omitempty"` } // IsEncrypted meldet, ob der Inhalt verschlüsselt ist. func (encryptionMetadata EncryptionMetadata) IsEncrypted() bool { return encryptionMetadata.Algorithm != "" } // CompressionMetadata beschreibt die Kompression eines Containers. type CompressionMetadata struct { // Algorithm benennt das Verfahren; leer bedeutet unkomprimiert. Algorithm string `json:"algorithm,omitempty"` // Level ist die verwendete Stufe. Level int `json:"level,omitempty"` } // IsCompressed meldet, ob der Inhalt komprimiert ist. func (compressionMetadata CompressionMetadata) IsCompressed() bool { return compressionMetadata.Algorithm != "" } // ChunkIndexEntry beschreibt einen Chunk im Container. // // Das Verzeichnis erlaubt es, einen einzelnen Chunk zu finden, ohne den // gesamten Datenbereich zu lesen — Voraussetzung für eine Wiederherstellung // einzelner Dateien aus einem großen Backup. type ChunkIndexEntry struct { // Identifier ist der Inhaltshash des Chunks. Identifier string `json:"id"` // ContainerOffset ist die Position im Datenbereich des Containers. ContainerOffset int64 `json:"container_offset"` // StoredLength ist die Länge der abgelegten Daten in Byte. StoredLength int64 `json:"stored_length"` // LogicalLength ist die Länge der ursprünglichen Daten in Byte. LogicalLength int64 `json:"logical_length"` } // ChunkIndex ist das Verzeichnis aller Chunks eines Containers. type ChunkIndex struct { // Entries sind die Chunks in ihrer Ablagereihenfolge. Entries []ChunkIndexEntry `json:"entries"` } // FindByIdentifier sucht einen Chunk anhand seiner Kennung. func (chunkIndex *ChunkIndex) FindByIdentifier(chunkIdentifier string) (ChunkIndexEntry, bool) { for _, indexEntry := range chunkIndex.Entries { if indexEntry.Identifier == chunkIdentifier { return indexEntry, true } } return ChunkIndexEntry{}, false } // TotalStoredBytes liefert die Gesamtgröße der abgelegten Chunks. func (chunkIndex *ChunkIndex) TotalStoredBytes() int64 { var totalBytes int64 for _, indexEntry := range chunkIndex.Entries { totalBytes += indexEntry.StoredLength } return totalBytes } // ContainerFooter schließt einen Container ab. // // Er steht am Ende, weil erst dort alle Prüfsummen feststehen. Genau darin // liegt seine Aussagekraft: ein abgebrochener Schreib- oder Übertragungsvorgang // hinterlässt einen Container ohne Footer, der damit zuverlässig als // unvollständig erkannt wird (SYNCOVA_ARCHITECTURE.md §8). type ContainerFooter struct { // ManifestHash ist die Prüfsumme des Manifestabschnitts. ManifestHash string `json:"manifest_hash"` // ContentHash ist die Prüfsumme über Header und alle Abschnitte. // // Sie belegt, dass der Container als Ganzes unverändert ist — auch dann, // wenn ein Angreifer einen einzelnen Abschnitt samt seiner Prüfsumme // ausgetauscht hätte. ContentHash string `json:"content_hash"` // SectionCount ist die Zahl der geschriebenen Abschnitte. // // Sie deckt einen entfernten Abschnitt auf, selbst wenn der Rest stimmig wäre. SectionCount int `json:"section_count"` // TotalBytes ist die Gesamtgröße des Containers ohne den Footer. TotalBytes int64 `json:"total_bytes"` // CompletedAt ist der Abschlusszeitpunkt in UTC. CompletedAt time.Time `json:"completed_at"` // Complete ist der ausdrückliche Abschlussvermerk. // // Ein Container ohne diesen Vermerk beschreibt kein verwendbares Backup // (PROMPT.md §140). Complete bool `json:"complete"` }