Signal-iOS/SignalServiceKit/Util/StreamTransform/Input/NonceHeaderInputStreamTransform.swift
Max Radermacher 78436e61dd
Consolidate code for parsing varints
Co-authored-by: Sasha Weiss <sasha@signal.org>
2026-03-11 18:05:08 -05:00

90 lines
2.7 KiB
Swift

//
// Copyright 2025 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import Foundation
public class NonceHeaderInputStreamTransform: StreamTransform, BufferedStreamTransform {
private var buffer = Data()
private var headerLength: Int?
private var hasFinishedReadingHeader: Bool
private var needMoreData: Bool = true
public var hasPendingBytes: Bool { return !needMoreData && (headerLength == nil || buffer.count > headerLength!) }
public func readBufferedData() throws -> Data {
let returnedData = try skipPastMetadataHeader()
buffer = Data()
return returnedData
}
init(source: BackupImportSource) {
switch source {
case .remote:
// Remote backups have the header,
// we have to finish reading it first
self.hasFinishedReadingHeader = false
case .linkNsync:
// Link'N'Sync backups have no header; finish immediately.
self.hasFinishedReadingHeader = true
}
}
public func transform(data: Data) throws -> Data {
if hasFinishedReadingHeader {
return data
}
if data.count > 0 {
needMoreData = false
buffer.append(data)
}
return try skipPastMetadataHeader()
}
/// Decode the next chunk of data, if enough data is present in the buffer.
private func skipPastMetadataHeader() throws -> Data {
var buffer = self.buffer
guard buffer.starts(with: BackupNonce.magicFileSignature) else {
// We dont have enough data to decode the signature, return for now.
needMoreData = true
return Data()
}
buffer.removeFirst(BackupNonce.magicFileSignature.count)
// decode the next variable length int
let dataSize = try? buffer.removeFirstVarint()
guard let dataSize else {
// Don't have enough data to decode an int, so return for now
return Data()
}
guard dataSize > 0 else {
needMoreData = true
// The varint is zero, so return for now?
return Data()
}
// Only advance if there is enough data present to both
// decode the variable length integer and skip past the specified
// number of bytes.
guard buffer.count >= dataSize else {
needMoreData = true
return Data()
}
buffer.removeFirst(Int(dataSize))
// Return any data past the header, skipping the header portion.
let returnBuffer = buffer
headerLength = self.buffer.count - buffer.count
hasFinishedReadingHeader = true
needMoreData = false
self.buffer = Data()
return returnBuffer
}
}