Poniżej znajduje się przykład procedury dekompresji w języku Swift, która jest funkcjonalnie odpowiednikiem pokazanej wcześniej implementacji w C#. Kod ten zakłada istnienie klasy BitsReader analogicznej do tej z przykładu C#, a także identyczną logikę dekompresji danych NRV2E.
import Foundation
class BitsReader { private var data: [UInt8] private var dataPosition: Int private var currentByte: Int private var bitPosition: Int
init(_ data: [UInt8], startIdx: Int = 0) {
self.data = data
self.dataPosition = startIdx
self.currentByte = 0
self.bitPosition = 0
}
var isDataAvailable: Bool {
return dataPosition < data.count
}
func readBit() throws -> UInt8 {
guard isDataAvailable else { throw NSError(domain: "BitsReader", code: 1, userInfo: [NSLocalizedDescriptionKey: "No more data available!"]) }
if bitPosition == 0 {
guard isDataAvailable else { throw NSError(domain: "BitsReader", code: 1, userInfo: [NSLocalizedDescriptionKey: "No more data available!"]) }
currentByte = Int(data[dataPosition] & 0xFF)
dataPosition += 1
bitPosition = 8
}
bitPosition -= 1
return UInt8((currentByte >> bitPosition) & 1)
}
func readByte() throws -> UInt8 {
guard isDataAvailable else { throw NSError(domain: "BitsReader", code: 1, userInfo: [NSLocalizedDescriptionKey: "No more data available!"]) }
let val = data[dataPosition] & 0xFF
dataPosition += 1
return val
}
}
public class NRV2EDecompressor { public func Decompress(_ src: [UInt8]) -> [UInt8] { // Pierwsze 4 bajty to rozmiar danych wyjściowych if src.count < 4 { return [] }
// Odczyt outputSize z 4 bajtów (little-endian)
var tmp: UInt32 = 0
tmp |= UInt32(src[0])
tmp |= UInt32(src[1]) << 8
tmp |= UInt32(src[2]) << 16
tmp |= UInt32(src[3]) << 24
let outputSize = Int32(bitPattern: tmp)
if outputSize < 0 || outputSize > Int32.max {
return [] // Nieprawidłowy rozmiar
}
let dstCount = Int(outputSize)
var dst = [UInt8](repeating: 0, count: dstCount)
guard let bitsReader = try? BitsReader(src, startIdx: 4) else {
return []
}
var olen: UInt32 = 0
var last_m_off: UInt32 = 1
decodeLoop: while true {
var m_off: UInt32 = 1
var m_len: UInt32
// Sprawdź czy są dane
if !bitsReader.isDataAvailable {
return dst
}
// Odczyt literali
do {
while try bitsReader.readBit() == 1 {
if olen >= UInt32(dstCount) { return dst }
dst[Int(olen)] = try bitsReader.readByte()
olen += 1
if olen >= UInt32(dstCount) {
return dst
}
}
} catch {
return dst
}
// Wyliczanie m_off
while true {
if !bitsReader.isDataAvailable {
return dst
}
do {
m_off = m_off * 2 + (try bitsReader.readBit())
if try bitsReader.readBit() == 1 {
break
}
m_off = (m_off - 1) * 2 + (try bitsReader.readBit())
} catch {
return dst
}
}
do {
if m_off == 2 {
m_off = last_m_off
m_len = try bitsReader.readBit()
} else {
let b = try bitsReader.readByte()
m_off = (m_off - 3) * 256 + UInt32(b)
if m_off == 1 {
// Koniec dekompresji
break decodeLoop
}
m_len = (m_off ^ 1) & 1
m_off >>= 1
last_m_off = m_off + 1
m_off = last_m_off
}
} catch {
return dst
}
if !bitsReader.isDataAvailable {
return dst
}
// Wyliczanie długości m_len
do {
if m_len > 0 {
// m_len = 1 + kolejny bit
m_len = 1 + (try bitsReader.readBit())
} else if try bitsReader.readBit() == 1 {
// m_len = 3 + kolejny bit
m_len = 3 + (try bitsReader.readBit())
} else {
m_len += 1
repeat {
if !bitsReader.isDataAvailable { return dst }
m_len = m_len * 2 + (try bitsReader.readBit())
if !bitsReader.isDataAvailable { return dst }
} while (try bitsReader.readBit() == 0)
m_len += 3
}
} catch {
return dst
}
if m_off > 0x500 {
m_len += 1
}
let m_pos = Int(olen - m_off)
if m_pos < 0 || m_pos >= dstCount {
// Odwołanie wykracza poza aktualnie zdekodowane dane - dane mogą być uszkodzone
return dst
}
if olen >= UInt32(dstCount) {
return dst
}
// Kopiowanie sekwencji
dst[Int(olen)] = dst[m_pos]
olen += 1
var m_copyPos = m_pos + 1
var length = m_len
while length > 0 {
if olen >= UInt32(dstCount) {
return dst
}
if m_copyPos >= dstCount {
// Niewłaściwe dane wejściowe - wyjście poza zakres
return dst
}
dst[Int(olen)] = dst[m_copyPos]
olen += 1
m_copyPos += 1
length -= 1
}
}
return dst
}
}
Komentarz do kodu
• Kod został przeportowany z C# na Swift niemal 1:1, zachowując logikę i strukturę.
• BitsReader w Swift działa analogicznie do wersji w C#: odczytuje pojedyncze bity i bajty z tablicy UInt8.
• Metoda Decompress(_ src: [UInt8]) -> [UInt8] implementuje tę samą logikę dekompresji NRV2E, co oryginał w C#.
• Zamiast rzutować wyjątków własnego typu, używamy tu NSError w BitsReader. Można to dostosować do własnych potrzeb (np. użyć throws z własnymi typami błędów).
• Wszelkie operacje na indeksach i warunkach zostały zachowane, w tym pętle i warunki wyjścia.
• Należy zwrócić uwagę, że do pełnej poprawności wymagane jest posiadanie poprawnych danych wejściowych. Niektóre ścieżki błędów wynikają z nierzetelnych lub uszkodzonych danych wejściowych.
import XCTest
// Zakładamy, że NRV2EDecompressor i BitsReader są dostępne w testowanym module // Jeżeli znajdują się w innym module, należy dodać import lub skopiować je do test targetu.
final class BitsReaderTests: XCTestCase {
func testReadBit() throws {
// Dane: 0xAA = 10101010 w binarnym
let data: [UInt8] = [0xAA]
let reader = BitsReader(data, startIdx: 0)
// Odczytujemy 8 bitów i sprawdzamy zgodność z 10101010
XCTAssertEqual(try reader.readBit(), 1)
XCTAssertEqual(try reader.readBit(), 0)
XCTAssertEqual(try reader.readBit(), 1)
XCTAssertEqual(try reader.readBit(), 0)
XCTAssertEqual(try reader.readBit(), 1)
XCTAssertEqual(try reader.readBit(), 0)
XCTAssertEqual(try reader.readBit(), 1)
XCTAssertEqual(try reader.readBit(), 0)
}
func testReadByte() throws {
let data: [UInt8] = [0x12, 0x34, 0x56]
let reader = BitsReader(data, startIdx: 0)
XCTAssertEqual(try reader.readByte(), 0x12)
XCTAssertEqual(try reader.readByte(), 0x34)
XCTAssertEqual(try reader.readByte(), 0x56)
XCTAssertFalse(reader.isDataAvailable)
}
func testNoDataThrowsError() {
let data: [UInt8] = [0xFF]
let reader = BitsReader(data, startIdx: 0)
XCTAssertEqual(try? reader.readByte(), 0xFF)
XCTAssertFalse(reader.isDataAvailable)
// Próba odczytu kolejnych bajtów lub bitów powinna rzucić błąd
XCTAssertThrowsError(try reader.readByte())
XCTAssertThrowsError(try reader.readBit())
}
}
final class NRV2EDecompressorTests: XCTestCase {
func testDecompressEmpty() {
// Pierwsze 4 bajty to rozmiar 0 - puste dane wyjściowe
let input: [UInt8] = [0x00, 0x00, 0x00, 0x00]
let decompressor = NRV2EDecompressor()
let result = decompressor.Decompress(input)
XCTAssertEqual(result.count, 0)
}
func testDecompressTruncatedData() {
// Rozmiar wyjściowy = 10, ale brak danych poza nagłówkiem
let input: [UInt8] = [0x0A, 0x00, 0x00, 0x00]
let decompressor = NRV2EDecompressor()
let result = decompressor.Decompress(input)
// Nie powinno rzucać błędu. Może zwrócić pustą lub częściową tablicę (wynik zależy od implementacji)
XCTAssertTrue(result.count <= 10)
}
func testDecompressKnownData() {
// W prawdziwym teście należy użyć znanych danych skompresowanych
// Tu przykład hipotetyczny:
// Załóżmy, że "Hello" (ASCII: 0x48 0x65 0x6C 0x6C 0x6F) zostało skompresowane
// i znamy gotowy strumień. Poniższy zestaw to fikcyjne dane przykładowe.
let compressedData: [UInt8] = [
// Rozmiar wyjścia = 5 (Hello)
0x05, 0x00, 0x00, 0x00,
// Tu wstawić prawdziwe dane skompresowane NRV2E
// Poniżej fikcyjne wartości - w realnym użyciu wstawić poprawne
0x90, 0xA2, 0xB3, 0x01
]
let decompressor = NRV2EDecompressor()
let result = decompressor.Decompress(compressedData)
// Ponieważ dane są fikcyjne, ten test prawdopodobnie nie przejdzie.
// W prawdziwym teście "expectedOutput" to oryginalny tekst "Hello"
let expectedOutput = "Hello".data(using: .ascii)!.map { $0 }
XCTAssertEqual(result, expectedOutput)
}
func testDecompressLiteralData() {
// Test sprawdza czy dane wyjściowe są poprawnie zwracane, gdy strumień zawiera same literaly.
// Rozmiar wyjścia = 3 (np. "ABC")
// Konfigurujemy bity tak, aby trzy bajty zostały odczytane dosłownie:
// Każdy literal wymaga bitu '1', potem bajtu danych.
// Używamy 0xFF (11111111), co daje nam mnóstwo bitów '1'. Pierwsze trzy '1' wystarczą.
let input: [UInt8] = [
0x03, 0x00, 0x00, 0x00, // Rozmiar 3 bajty
0xFF, // bity (1,1,1,...)
0x41, // 'A'
0x42, // 'B'
0x43 // 'C'
]
let decompressor = NRV2EDecompressor()
let result = decompressor.Decompress(input)
XCTAssertEqual(result, [0x41, 0x42, 0x43]) // "ABC"
}
func testDecompressInsufficientBits() {
// Test sprawdza sytuację, gdy dane deklarują pewien rozmiar, ale kończą się wcześniej.
let input: [UInt8] = [
0x04, 0x00, 0x00, 0x00, // Rozmiar = 4
0x80 // Tylko jeden bajt danych
]
let decompressor = NRV2EDecompressor()
let result = decompressor.Decompress(input)
// Nie powinien wystąpić wyjątek, ale wynik może być pusty lub częściowy.
XCTAssertNotNil(result)
XCTAssertTrue(result.count <= 4)
}
}
Omówienie testów
• **BitsReaderTests**:
Testujemy odczyt bitów i bajtów, brak danych oraz czy odpowiednio rzuca błędy w przypadku braku dostępnych danych. • NRV2EDecompressorTests: • testDecompressEmpty(): sprawdza dekompresję pustych danych. • testDecompressTruncatedData(): sprawdza sytuację, gdy deklarowany jest większy rozmiar wyjścia niż dostępne dane. • testDecompressKnownData(): przykładowy test, który w realnych warunkach używałby znanych testowych danych skompresowanych. • testDecompressLiteralData(): sprawdza, czy dane dosłowne (literalne) są odczytywane poprawnie. • testDecompressInsufficientBits(): testuje przypadek, w którym dane kończą się w trakcie dekompresji.
Powyższy zestaw można dowolnie rozszerzać, dodając kolejne scenariusze lub wykorzystując rzeczywiste dane testowe.