import Foundation enum Endianness { case big case little } extension Data { func readUInt8(at offset: Int) -> UInt8? { guard offset >= 0, offset + MemoryLayout.size <= self.count else { return nil } return self[offset] } func readUInt16(at offset: Int, endianness: Endianness = .big) -> UInt16? { guard offset >= 0, offset + MemoryLayout.size <= self.count else { return nil } let value = self.subdata(in: offset..<(offset + MemoryLayout.size)).withUnsafeBytes { $0.load(as: UInt16.self) } return (endianness == .big) ? value.bigEndian : value.littleEndian } func readUInt32(at offset: Int, endianness: Endianness = .big) -> UInt32? { guard offset >= 0, offset + MemoryLayout.size <= self.count else { return nil } let value = self.subdata(in: offset..<(offset + MemoryLayout.size)).withUnsafeBytes { $0.load(as: UInt32.self) } return (endianness == .big) ? value.bigEndian : value.littleEndian } func readData(at offset: Int, length: Int) -> Data? { guard offset >= 0, offset + length <= self.count else { return nil } return self[offset..<(offset + length)] } func readString(at offset: Int, length: Int, encoding: String.Encoding) -> String? { return String(data: self[offset..<(offset + length)], encoding: encoding) } mutating func appendUInt8(_ value: UInt8, endianness: Endianness = .big) { var val = endianness == .big ? value.bigEndian : value.littleEndian append(&val, count: MemoryLayout.size) } mutating func appendUInt16(_ value: UInt16, endianness: Endianness = .big) { var val = endianness == .big ? value.bigEndian : value.littleEndian Swift.withUnsafeBytes(of: &val) { buffer in append(buffer.bindMemory(to: UInt8.self)) } // append(&val, count: MemoryLayout.size) } mutating func appendUInt32(_ value: UInt32, endianness: Endianness = .big) { var val = endianness == .big ? value.bigEndian : value.littleEndian Swift.withUnsafeBytes(of: &val) { buffer in append(buffer.bindMemory(to: UInt8.self)) } // append(&val, count: MemoryLayout.size) } }