Skip to content

Commit

Permalink
add support for stored DEFLATE blocks, fixing #64
Browse files Browse the repository at this point in the history
  • Loading branch information
tayloraswift committed Apr 3, 2024
1 parent aa3c213 commit c7080fe
Show file tree
Hide file tree
Showing 8 changed files with 144 additions and 36 deletions.
76 changes: 72 additions & 4 deletions Sources/LZ77/Deflator/LZ77.DeflatorBuffers.Stream.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,40 @@ extension LZ77.DeflatorBuffers
extension LZ77.DeflatorBuffers.Stream
{
mutating
func compressBlocks(final:Bool)
{
guard final
else
{
while let _:Void = self.compress(all: false)
{
self.writeBlock()
}

return
}

let finalType:LZ77.BlockType

switch self.input.count
{
case 3...:
while let _:Void = self.compress(all: true)
{
self.writeBlock()
}

finalType = .dynamic

// It does not make sense to perform matching on data that is shorter than 3 bytes.
case let count:
finalType = .bytes(count: count)
}

self.writeBlock(finalType: finalType)
}

private mutating
func compress(all:Bool) -> Void?
{
// -3 -2 -1 0 1 2 3 4 5 6
Expand Down Expand Up @@ -369,8 +403,41 @@ extension LZ77.DeflatorBuffers.Stream
return nil
}

private mutating
func writeBlock(finalType:LZ77.BlockType)
{
switch finalType
{
case .dynamic:
self.writeBlock(final: true)

case .fixed:
fatalError("unsupported")

case .bytes(let count):
// this is an uncompressed block (type = 0)
// ~v
self.output.append(0b00_1, count: 3)
// ^
// this is a final block (final = 1)
self.output.pad(to: UInt8.self)

let l:UInt16 = .init(count)
let m:UInt16 = ~l

self.output.append(l, count: 16)
self.output.append(m, count: 16)

for _:Int in 0 ..< count
{
self.output.append(UInt16.init(self.input.dequeue()), count: 8)
}
}
}

/// Emits a dynamic (type = 2) DEFLATE block.
mutating
func writeBlock(final:Bool)
func writeBlock(final:Bool = false)
{
let tree:
(
Expand Down Expand Up @@ -484,7 +551,7 @@ extension LZ77.DeflatorBuffers.Stream

tree.meta = .init(frequencies: frequencies, limit: 7)

self.writeBlockMetadata(tree: tree.meta,
self.writeBlockMetadata(dynamic: tree.meta,
literals: r,
distances: d,
final: final)
Expand All @@ -505,8 +572,9 @@ extension LZ77.DeflatorBuffers.Stream
}
extension LZ77.DeflatorBuffers.Stream
{
/// Writes metadata for a dynamic (type = 2) DEFLATE block.
private mutating
func writeBlockMetadata(tree:LZ77.HuffmanTree<UInt8>,
func writeBlockMetadata(dynamic tree:LZ77.HuffmanTree<UInt8>,
literals:Int,
distances:Int,
final:Bool)
Expand All @@ -532,7 +600,7 @@ extension LZ77.DeflatorBuffers.Stream
$1 = max(4, $0.reversed().drop{ $0 == 0 }.count)
}

self.output.append(final ? 0b10_1 : 0b10_0, count: 3)
self.output.append(final ? 0b10_1 : 0b10_0, count: 3)

self.output.append(.init(literals - 257), count: 5)
self.output.append(.init(distances - 1), count: 5)
Expand Down
41 changes: 14 additions & 27 deletions Sources/LZ77/Deflator/LZ77.DeflatorBuffers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,23 +78,17 @@ extension LZ77.DeflatorBuffers<LZ77.Format>
return
}

while let _:Void = self.stream.compress(all: last)
{
self.stream.writeBlock(final: false)
}
if last
self.stream.compressBlocks(final: last)

guard case .zlib = self.format
else
{
self.stream.writeBlock(final: true)

if case .ios = self.format
{
return
}
// checksum is written big-endian, which means it has to go into the
// bitstream msb-first
let checksum:UInt32 = self.stream.input.checksum()
self.stream.writeBigEndianUInt32(checksum)
return
}
// checksum is written big-endian, which means it has to go into the
// bitstream msb-first
let checksum:UInt32 = self.stream.input.checksum()
self.stream.writeBigEndianUInt32(checksum)
}
}
// TODO: this currently only supports one member.
Expand Down Expand Up @@ -127,19 +121,12 @@ extension LZ77.DeflatorBuffers<Gzip.Format>
return
}

while let _:Void = self.stream.compress(all: last)
{
self.stream.writeBlock(final: false)
}
if last
{
self.stream.writeBlock(final: true)
self.stream.compressBlocks(final: last)

let checksum:UInt32 = self.stream.input.checksum()
let bytes:UInt32 = self.stream.input.integral.bytes
self.stream.writeLittleEndianUInt32(checksum)
self.stream.writeLittleEndianUInt32(bytes)
}
let checksum:UInt32 = self.stream.input.checksum()
let bytes:UInt32 = self.stream.input.integral.bytes
self.stream.writeLittleEndianUInt32(checksum)
self.stream.writeLittleEndianUInt32(bytes)
}
}
extension LZ77.DeflatorBuffers
Expand Down
9 changes: 9 additions & 0 deletions Sources/LZ77/Inflator/LZ77.BlockShape.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
extension LZ77
{
enum BlockShape
{
case dynamic (final:Bool, literals:Int, distances:Int)
case fixed (final:Bool)
case bytes (final:Bool, count:Int)
}
}
6 changes: 3 additions & 3 deletions Sources/LZ77/Inflator/LZ77.BlockType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ extension LZ77
{
enum BlockType
{
case dynamic (final:Bool, literals:Int, distances:Int)
case fixed (final:Bool)
case bytes (final:Bool, count:Int)
case dynamic
case fixed
case bytes(count:Int)
}
}
2 changes: 1 addition & 1 deletion Sources/LZ77/Inflator/LZ77.InflatorBuffers.Stream.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ extension LZ77.InflatorBuffers.Stream
extension LZ77.InflatorBuffers.Stream
{
mutating
func readBlockMetadata(into metadata:inout LZ77.BlockMetadata) throws -> LZ77.BlockType?
func readBlockMetadata(into metadata:inout LZ77.BlockMetadata) throws -> LZ77.BlockShape?
{
guard self.b + 3 <= self.input.count
else
Expand Down
2 changes: 1 addition & 1 deletion Sources/LZ77/Inflator/LZ77.InflatorBuffers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ extension LZ77.InflatorBuffers
switch state
{
case .metadata:
if let block:LZ77.BlockType = try self.stream.readBlockMetadata(
if let block:LZ77.BlockShape = try self.stream.readBlockMetadata(
into: &self.metadata)
{
#if DUMP_LZ77_BLOCKS
Expand Down
42 changes: 42 additions & 0 deletions Sources/LZ77Tests/Main.CompressionMicro.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import LZ77
import Testing

extension Main
{
enum CompressionMicro
{
}
}
extension Main.CompressionMicro:TestBattery
{
static
func run(tests:TestGroup)
{
if let tests:TestGroup = tests / "Empty"
{
Self.roundtrip(bytes: [], with: tests)
}
if let tests:TestGroup = tests / "OneByte"
{
Self.roundtrip(bytes: [1], with: tests)
}
if let tests:TestGroup = tests / "TwoBytes"
{
Self.roundtrip(bytes: [2, 3], with: tests)
}
if let tests:TestGroup = tests / "ThreeBytes"
{
Self.roundtrip(bytes: [4, 5, 6], with: tests)
}
}

private static
func roundtrip(bytes:[UInt8], with tests:TestGroup)
{
let archive:[UInt8] = Gzip.archive(bytes: bytes[...], level: 10)
tests.do
{
tests.expect(try Gzip.extract(from: archive[...]) ..? bytes)
}
}
}
2 changes: 2 additions & 0 deletions Sources/LZ77Tests/Main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ enum Main:TestMain
F14.self,
Bitstreams.self,
Matching.self,
CompressionMicro.self,
Compression.self,
]
#else
static
let all:[any TestBattery.Type] =
[
CompressionMicro.self,
Compression.self
]
#endif
Expand Down

0 comments on commit c7080fe

Please sign in to comment.