// // Copyright 2023 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only // import Wire extension MessageBackup { public enum ProtoOutputStreamWriteResult { case success /// Unable to serialize the provided proto object. /// Should never happen, and catastrophic if it does. case protoSerializationError(Swift.Error) /// Failure writing at file I/O level. case fileIOError(Swift.Error) } } /** * Output stream for reading and writing a backup file on disk. * * The backup file is just a sequence of serialized proto bytes, back to back, delimited by varint * byte sizes so we know how much to read into memory to deserialize the next proto. * The output stream abstracts over this, and allows callers to just think in terms of "frames", * the individual proto objects that we write one at a time. */ public protocol MessageBackupProtoOutputStream { /// Write a header (BakckupInfo) to the backup file. /// It is the caller's responsibility to ensure this is always written, and is the first thing written, /// in order to produce a valid backup file. func writeHeader(_ header: BackupProtoBackupInfo) -> MessageBackup.ProtoOutputStreamWriteResult /// Write a frame to the backup file. func writeFrame(_ frame: BackupProtoFrame) -> MessageBackup.ProtoOutputStreamWriteResult /// Closes the output stream. /// - Returns: URL of the file written to. func closeFileStream() -> URL } internal class MessageBackupProtoOutputStreamImpl: MessageBackupProtoOutputStream { private var outputStream: OutputStreamable private var outputStreamDelegate: StreamDelegate private var fileUrl: URL internal init( outputStream: OutputStreamable, outputStreamDelegate: StreamDelegate, fileURL: URL ) { self.outputStream = outputStream self.outputStreamDelegate = outputStreamDelegate self.fileUrl = fileURL } internal func writeHeader(_ header: BackupProtoBackupInfo) -> MessageBackup.ProtoOutputStreamWriteResult { let bytes: Data do { bytes = try ProtoEncoder().encode(header) } catch { return .protoSerializationError(error) } do { try outputStream.write(data: bytes) } catch { return .fileIOError(error) } return .success } internal func writeFrame(_ frame: BackupProtoFrame) -> MessageBackup.ProtoOutputStreamWriteResult { let bytes: Data do { bytes = try ProtoEncoder().encode(frame) } catch { return .protoSerializationError(error) } do { try outputStream.write(data: bytes) } catch { return .fileIOError(error) } return .success } public func closeFileStream() -> URL { try? outputStream.close() return fileUrl } }