diff --git a/Sources/LZ77/Deflator/LZ77.Deflator.In.swift b/Sources/LZ77/Deflator/LZ77.Deflator.In.swift index 2d0b4c23..8f5b563b 100644 --- a/Sources/LZ77/Deflator/LZ77.Deflator.In.swift +++ b/Sources/LZ77/Deflator/LZ77.Deflator.In.swift @@ -14,26 +14,25 @@ extension LZ77.Deflator var storage:ManagedBuffer private - var integral:(single:UInt32, double:UInt32) + var integral:LZ77.MRC32 } } extension LZ77.Deflator.In { init() { - var capacity:Int = 0 + var capacity:Int = 0 self.storage = .create(minimumCapacity: 0) { capacity = $0.capacity - return () } // self.startIndex = 0 // self.endIndex = 0 - self.startIndex = 4 - self.endIndex = 4 - self.capacity = capacity + self.startIndex = 4 + self.endIndex = 4 + self.capacity = capacity - self.integral = (1, 0) + self.integral = .init() } var count:Int @@ -160,10 +159,11 @@ extension LZ77.Deflator.In // rebase without reallocating self.storage.withUnsafeMutablePointerToElements { - self.integral = LZ77.MRC32.update(self.integral, - from: $0 + 4, count: self.startIndex - 4) - $0.update( from: $0 - 4 + self.startIndex, count: self.count + 4) - self.endIndex = 4 + self.count + self.integral.update(from: $0 + 4, count: self.startIndex - 4) + + $0.update(from: $0 - 4 + self.startIndex, count: self.count + 4) + + self.endIndex = 4 + self.count self.startIndex = 4 } } @@ -181,11 +181,11 @@ extension LZ77.Deflator.In new.withUnsafeMutablePointerToElements { - self.integral = LZ77.MRC32.update(self.integral, - from: body + 4, count: self.startIndex - 4) - $0.update( from: body - 4 + self.startIndex, count: self.count + 4) + self.integral.update(from: body + 4, count: self.startIndex - 4) + + $0.update(from: body - 4 + self.startIndex, count: self.count + 4) } - self.endIndex = 4 + self.count + self.endIndex = 4 + self.count self.startIndex = 4 return new } @@ -198,10 +198,8 @@ extension LZ77.Deflator.In // everything still in the storage buffer has not yet been integrated self.storage.withUnsafeMutablePointerToElements { - let (single, double):(UInt32, UInt32) = - //LZ77.MRC32.update(self.integral, from: $0, count: self.endIndex) - LZ77.MRC32.update(self.integral, from: $0 + 4, count: self.endIndex - 4) - return double << 16 | single + self.integral.update(from: $0 + 4, count: self.endIndex - 4) + return self.integral.checksum } } diff --git a/Sources/LZ77/Inflator/LZ77.DecompressionError.swift b/Sources/LZ77/Inflator/LZ77.DecompressionError.swift index f3d7106c..69bb6b8c 100644 --- a/Sources/LZ77/Inflator/LZ77.DecompressionError.swift +++ b/Sources/LZ77/Inflator/LZ77.DecompressionError.swift @@ -22,26 +22,6 @@ extension LZ77 public enum DecompressionError:Error, Equatable, Sendable { - /// A compressed data stream had an invalid compression method code. - /// - /// The compression method code should always be `8`. - case invalidStreamCompressionMethodCode(UInt8) - - /// A compressed data stream specified an invalid window size. - /// - /// The window size exponent should be in the range `8 ... 15`. - case invalidStreamWindowSize(exponent:Int) - - /// A compressed data stream had invalid header check bits. - /// - /// The header check bits should not be confused with the modular redundancy checksum, - /// which corresponds to the ``invalidStreamChecksum(declared:computed:)`` error case. - case invalidStreamHeaderCheckBits - - /// A compressed data stream contains a stream dictionary, which is not allowed in a - /// compressed PNG data stream. - case unexpectedStreamDictionary - /// The modular redundancy checksum computed on the uncompressed data did not match the /// checksum declared in the compressed data stream footer. /// diff --git a/Sources/LZ77/Inflator/LZ77.Inflator.Out.swift b/Sources/LZ77/Inflator/LZ77.Inflator.Out.swift index f0a4e799..a44577e2 100644 --- a/Sources/LZ77/Inflator/LZ77.Inflator.Out.swift +++ b/Sources/LZ77/Inflator/LZ77.Inflator.Out.swift @@ -18,18 +18,17 @@ extension LZ77.Inflator var storage:ManagedBuffer private - var integral:(single:UInt32, double:UInt32) + var integral:LZ77.MRC32 } } extension LZ77.Inflator.Out { init() { - var capacity:Int = 0 + var capacity:Int = 0 self.storage = .create(minimumCapacity: 0) { capacity = $0.capacity - return () } self.window = 0 self.startIndex = 0 @@ -37,7 +36,7 @@ extension LZ77.Inflator.Out self.endIndex = 0 self.capacity = capacity - self.integral = (1, 0) + self.integral = .init() } mutating @@ -163,9 +162,10 @@ extension LZ77.Inflator.Out // rebase without reallocating self.storage.withUnsafeMutablePointerToElements { - self.integral = LZ77.MRC32.update(self.integral, - from: $0, count: self.startIndex) - $0.update( from: $0 + self.startIndex, count: count) + self.integral.update(from: $0, count: self.startIndex) + + $0.update(from: $0 + self.startIndex, count: count) + self.currentIndex -= self.startIndex self.endIndex -= self.startIndex self.startIndex = 0 @@ -185,9 +185,9 @@ extension LZ77.Inflator.Out new.withUnsafeMutablePointerToElements { - self.integral = LZ77.MRC32.update(self.integral, - from: body, count: self.startIndex) - $0.update( from: body + self.startIndex, count: count) + self.integral.update(from: body, count: self.startIndex) + + $0.update(from: body + self.startIndex, count: count) } self.currentIndex -= self.startIndex self.endIndex -= self.startIndex @@ -203,9 +203,8 @@ extension LZ77.Inflator.Out // everything still in the storage buffer has not yet been integrated self.storage.withUnsafeMutablePointerToElements { - let (single, double):(UInt32, UInt32) = - LZ77.MRC32.update(self.integral, from: $0, count: self.endIndex) - return double << 16 | single + self.integral.update(from: $0, count: self.endIndex) + return self.integral.checksum } } } diff --git a/Sources/LZ77/Inflator/LZ77.Inflator.Stream.swift b/Sources/LZ77/Inflator/LZ77.Inflator.Stream.swift index 613974c1..10f34dd9 100644 --- a/Sources/LZ77/Inflator/LZ77.Inflator.Stream.swift +++ b/Sources/LZ77/Inflator/LZ77.Inflator.Stream.swift @@ -48,7 +48,7 @@ extension LZ77.Inflator.Stream mutating func start() throws -> Int? { - if case .ios = self.format + if case .ios = self.format { return 1 << 15 } @@ -65,26 +65,26 @@ extension LZ77.Inflator.Stream case 8: break case let code: - throw LZ77.DecompressionError.invalidStreamCompressionMethodCode(code) + throw LZ77.StreamHeaderError.invalidCompressionMethod(code) } let exponent:Int = self.input[self.b + 4, count: 4, as: Int.self] guard exponent < 8 else { - throw LZ77.DecompressionError.invalidStreamWindowSize(exponent: exponent + 8) + throw LZ77.StreamHeaderError.invalidWindowSize(exponent: exponent + 8) } let flags:Int = self.input[self.b + 8, count: 8, as: Int.self] guard (exponent << 12 | 8 << 8 + flags) % 31 == 0 else { - throw LZ77.DecompressionError.invalidStreamHeaderCheckBits + throw LZ77.StreamHeaderError.invalidCheckBits } guard flags & 0x20 == 0 else { - throw LZ77.DecompressionError.unexpectedStreamDictionary + throw LZ77.StreamHeaderError.unexpectedDictionary } self.b += 16 @@ -158,12 +158,13 @@ extension LZ77.Inflator.Stream } var lengths:[Int] = .init(repeating: 0, count: 19) - for (i, d):(Int, Int) in - zip(0 ..< codelengths, [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]) + for (i, d):(Int, Int) in zip(0 ..< codelengths, + [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]) { lengths[d] = self.input[self.b + 17 + 3 * i, count: 3, as: Int.self] } - guard let tree:LZ77.HuffmanTree = .validate(symbols: 0 ... 18, lengths: lengths) + guard + let tree:LZ77.HuffmanTree = .validate(symbols: 0 ... 18, lengths: lengths) else { throw LZ77.DecompressionError.invalidHuffmanCodelengthHuffmanTable @@ -226,7 +227,8 @@ extension LZ77.Inflator.Stream continue codelengths case 16: - guard let last:Int = self.lengths.last + guard + let last:Int = self.lengths.last else { throw LZ77.DecompressionError.invalidHuffmanCodelengthSequence @@ -286,12 +288,13 @@ extension LZ77.Inflator.Stream } #endif - guard let runliteral:LZ77.HuffmanTree = .validate( - symbols: 0 ... 287, - lengths: self.lengths.prefix(runliterals)), - let distance:LZ77.HuffmanTree = .validate( - symbols: 0 ... 31, - normalizing: self.lengths.dropFirst(runliterals)) + guard + let runliteral:LZ77.HuffmanTree = .validate( + symbols: 0 ... 287, + lengths: self.lengths.prefix(runliterals)), + let distance:LZ77.HuffmanTree = .validate( + symbols: 0 ... 31, + normalizing: self.lengths.dropFirst(runliterals)) else { throw LZ77.DecompressionError.invalidHuffmanTable @@ -449,7 +452,7 @@ extension LZ77.Inflator.Stream print(String.init(histogram: self.statistics.symbols, size: (29, 30), pad: 4)) #endif - if case .ios = self.format + if case .ios = self.format { return () } diff --git a/Sources/LZ77/Inflator/LZ77.Inflator.swift b/Sources/LZ77/Inflator/LZ77.Inflator.swift index 5417bf2b..8a2c2aa3 100644 --- a/Sources/LZ77/Inflator/LZ77.Inflator.swift +++ b/Sources/LZ77/Inflator/LZ77.Inflator.swift @@ -63,7 +63,8 @@ extension LZ77.Inflator switch self.state { case .streamStart: - guard let window:Int = try self.stream.start() + guard + let window:Int = try self.stream.start() else { return nil @@ -72,8 +73,8 @@ extension LZ77.Inflator self.state = .blockStart case .blockStart: - guard let (final, compression):(Bool, Stream.Compression) = - try self.stream.blockStart() + guard + let (final, compression):(Bool, Stream.Compression) = try self.stream.blockStart() else { return nil @@ -99,7 +100,8 @@ extension LZ77.Inflator #endif case .blockTables(final: let final, runliterals: let runliterals, distances: let distances): - guard let (runliteral, distance):(LZ77.HuffmanTree, LZ77.HuffmanTree) = + guard + let (runliteral, distance):(LZ77.HuffmanTree, LZ77.HuffmanTree) = try self.stream.blockTables(runliterals: runliterals, distances: distances) else { @@ -110,7 +112,8 @@ extension LZ77.Inflator semistatic: .init(runliteral: runliteral, distance: distance)) case .blockUncompressed(final: let final, end: let end): - guard let _:Void = try self.stream.blockUncompressed(end: end) + guard + let _:Void = try self.stream.blockUncompressed(end: end) else { return nil @@ -118,7 +121,8 @@ extension LZ77.Inflator self.state = final ? .streamChecksum : .blockStart case .blockCompressed(final: let final, semistatic: let semistatic): - guard let _:Void = try self.stream.blockCompressed(semistatic: semistatic) + guard + let _:Void = try self.stream.blockCompressed(semistatic: semistatic) else { return nil @@ -126,7 +130,8 @@ extension LZ77.Inflator self.state = final ? .streamChecksum : .blockStart case .streamChecksum: - guard let _:Void = try self.stream.checksum() + guard + let _:Void = try self.stream.checksum() else { return nil diff --git a/Sources/LZ77/Inflator/LZ77.StreamHeaderError.swift b/Sources/LZ77/Inflator/LZ77.StreamHeaderError.swift new file mode 100644 index 00000000..69b20d1c --- /dev/null +++ b/Sources/LZ77/Inflator/LZ77.StreamHeaderError.swift @@ -0,0 +1,28 @@ +extension LZ77 +{ + /// Errors that can occur when decompressing a DEFLATE stream embedded in the ‘zlib’ wrapper + /// format. + public + enum StreamHeaderError:Error, Equatable + { + /// A compressed data stream had an invalid compression method code. + /// + /// The compression method code should always be `8`. + case invalidCompressionMethod(UInt8) + + /// A compressed data stream specified an invalid window size. + /// + /// The window size exponent should be in the range `8 ... 15`. + case invalidWindowSize(exponent:Int) + + /// A compressed data stream had invalid header check bits. + /// + /// The header check bits should not be confused with the modular redundancy checksum, + /// which corresponds to the ``invalidStreamChecksum(declared:computed:)`` error case. + case invalidCheckBits + + /// A compressed data stream contains a stream dictionary, which is not allowed in a + /// compressed PNG data stream. + case unexpectedDictionary + } +} diff --git a/Sources/LZ77/LZ77.MRC32.swift b/Sources/LZ77/LZ77.MRC32.swift index aa8057fd..0bb52647 100644 --- a/Sources/LZ77/LZ77.MRC32.swift +++ b/Sources/LZ77/LZ77.MRC32.swift @@ -1,8 +1,19 @@ extension LZ77 { /// Modular redundancy check (similar to ``CRC32``) - enum MRC32 + @frozen public + struct MRC32 { + private + var single:UInt32 + private + var double:UInt32 + + init() + { + self.single = 1 + self.double = 0 + } } } extension LZ77.MRC32 @@ -10,28 +21,29 @@ extension LZ77.MRC32 // software.intel.com/content/www/us/en/develop/articles/fast-computation-of-adler32-checksums // link also says to use simd vectorization, but that just seems to slow // things down (probably because llvm is already autovectorizing it) - static - func update(_ checksum:(single:UInt32, double:UInt32), - from start:UnsafePointer, count:Int) - -> (single:UInt32, double:UInt32) + mutating + func update(from start:UnsafePointer, count:Int) { let (q, r):(Int, Int) = count.quotientAndRemainder(dividingBy: 5552) - var (single, double):(UInt32, UInt32) = checksum for i:Int in 0 ..< q { for j:Int in 5552 * i ..< 5552 * (i + 1) { - single &+= .init(start[j]) - double &+= single + self.single &+= .init(start[j]) + self.double &+= self.single } - single %= 65521 - double %= 65521 + self.single %= 65521 + self.double %= 65521 } for j:Int in 5552 * q ..< 5552 * q + r { - single &+= .init(start[j]) - double &+= single + self.single &+= .init(start[j]) + self.double &+= self.single } - return (single % 65521, double % 65521) + + self.single %= 65521 + self.double %= 65521 } + + var checksum:UInt32 { self.double << 16 | self.single } } diff --git a/Sources/PNG/LZ77.DecompressionError (ext).swift b/Sources/PNG/LZ77.DecompressionError (ext).swift index 4f8e0845..016d1708 100644 --- a/Sources/PNG/LZ77.DecompressionError (ext).swift +++ b/Sources/PNG/LZ77.DecompressionError (ext).swift @@ -2,11 +2,11 @@ import LZ77 extension LZ77.DecompressionError:PNG.Error { - /// The string `"decompression error"`. + /// The string `"Decompression error"`. public static var namespace:String { - "decompression error" + "Decompression error" } /// A human-readable summary of this error. public @@ -14,14 +14,6 @@ extension LZ77.DecompressionError:PNG.Error { switch self { - case .invalidStreamCompressionMethodCode: - return "invalid rfc-1950 stream compression method code" - case .invalidStreamWindowSize: - return "invalid rfc-1950 stream window size" - case .invalidStreamHeaderCheckBits: - return "invalid rfc-1950 stream header check bits" - case .unexpectedStreamDictionary: - return "unexpected rfc-1950 stream dictionary" case .invalidStreamChecksum: return "invalid rfc-1950 checksum" case .invalidBlockTypeCode: @@ -46,25 +38,28 @@ extension LZ77.DecompressionError:PNG.Error { switch self { - case .invalidStreamCompressionMethodCode(let code): - return "(\(code)) is not a valid compression method code" - case .invalidStreamWindowSize(exponent: let exponent): - return "base-2 log of stream window size (\(exponent)) must be in the range 8 ... 15" - case .invalidStreamHeaderCheckBits, - .unexpectedStreamDictionary, - .invalidHuffmanCodelengthHuffmanTable, - .invalidHuffmanCodelengthSequence, - .invalidHuffmanTable, - .invalidStringReference: - return nil - case .invalidStreamChecksum(declared: let declared, computed: let computed): - return "computed mrc-32 checksum (\(computed)) does not match declared checksum (\(declared))" - case .invalidBlockTypeCode(let code): - return "(\(code)) is not a valid block type code" - case .invalidBlockElementCountParity(let l, let m): - return "inverted block element count (\(String.init(~l, radix: 2))) does not match declared parity bits (\(String.init(m, radix: 2)))" - case .invalidHuffmanRunLiteralSymbolCount(let count): - return "run-literal symbol count (\(count)) must be in the range 257 ... 286" + case .invalidHuffmanCodelengthHuffmanTable: + nil + case .invalidHuffmanCodelengthSequence: + nil + case .invalidHuffmanTable: + nil + case .invalidStringReference: + nil + case .invalidStreamChecksum(declared: let declared, computed: let computed): + """ + computed mrc-32 checksum (\(computed)) \ + does not match declared checksum (\(declared)) + """ + case .invalidBlockTypeCode(let code): + "(\(code)) is not a valid block type code" + case .invalidBlockElementCountParity(let l, let m): + """ + inverted block element count (\(String.init(~l, radix: 2))) does not match \ + declared parity bits (\(String.init(m, radix: 2))) + """ + case .invalidHuffmanRunLiteralSymbolCount(let count): + "run-literal symbol count (\(count)) must be in the range 257 ... 286" } } } diff --git a/Sources/PNG/LZ77.StreamHeaderError (ext).swift b/Sources/PNG/LZ77.StreamHeaderError (ext).swift new file mode 100644 index 00000000..5c45aa9e --- /dev/null +++ b/Sources/PNG/LZ77.StreamHeaderError (ext).swift @@ -0,0 +1,44 @@ +import LZ77 + +extension LZ77.StreamHeaderError:PNG.Error +{ + /// The string `"Stream header error"`. + public static + var namespace:String + { + "Stream header error" + } + + /// A human-readable summary of this error. + public + var message:String + { + switch self + { + case .invalidCompressionMethod: + "invalid rfc-1950 stream compression method code" + case .invalidWindowSize: + "invalid rfc-1950 stream window size" + case .invalidCheckBits: + "invalid rfc-1950 stream header check bits" + case .unexpectedDictionary: + "unexpected rfc-1950 stream dictionary" + } + } + /// An optional human-readable string providing additional details about this error. + public + var details:String? + { + switch self + { + case .invalidCompressionMethod(let code): + "(\(code)) is not a valid compression method code" + case .invalidWindowSize(exponent: let exponent): + "base-2 log of stream window size (\(exponent)) must be in the range 8 ... 15" + case .invalidCheckBits: + nil + case .unexpectedDictionary: + nil + } + } +}