// Variant.swift // This file is part of KeePass.swift // // Copyright © 2021 Maxime Epain. All rights reserved. // // KeePass.swift is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // KeePass.swift is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with KeePass.swift. If not, see . import Binary import Foundation public enum Variant { struct Version { let major: UInt8 let minor: UInt8 } case End case Bool(Bool) case UInt32(UInt32) case UInt64(UInt64) case Int32(Int32) case Int64(Int64) case String(String) case Bytes(Bytes) enum ID: UInt8, Streamable { case End = 0x00 case Bool = 0x08 case UInt32 = 0x04 case UInt64 = 0x05 case Int32 = 0x0C case Int64 = 0x0D case String = 0x18 case Bytes = 0x42 } var id: ID { switch self { case .End: return .End case .Bool: return .Bool case .UInt32: return .UInt32 case .UInt64: return .UInt64 case .Int32: return .Int32 case .Int64: return .Int64 case .String: return .String case .Bytes: return .Bytes } } init() { self = .End } init(_ value: Bool) { self = .Bool(value) } init(_ value: UInt32) { self = .UInt32(value) } init(_ value: UInt64) { self = .UInt64(value) } init(_ value: Int32) { self = .Int32(value) } init(_ value: Int64) { self = .Int64(value) } init(_ value: String) { self = .String(value) } init(_ value: Bytes) { self = .Bytes(value) } func unwrap() throws -> Bool { guard case let .Bool(value) = self else { throw KDBXError.invalidValue } return value } func unwrap() throws -> UInt32 { guard case let .UInt32(value) = self else { throw KDBXError.invalidValue } return value } func unwrap() throws -> UInt64 { guard case let .UInt64(value) = self else { throw KDBXError.invalidValue } return value } func unwrap() throws -> Int32 { guard case let .Int32(value) = self else { throw KDBXError.invalidValue } return value } func unwrap() throws -> Int64 { guard case let .Int64(value) = self else { throw KDBXError.invalidValue } return value } func unwrap() throws -> String { guard case let .String(value) = self else { throw KDBXError.invalidValue } return value } func unwrap() throws -> Bytes { guard case let .Bytes(value) = self else { throw KDBXError.invalidValue } return value } func unwrap() throws -> T where T: LosslessBytesConvertible { guard case let .Bytes(bytes) = self else { throw KDBXError.invalidValue } return try T(bytes) } } extension Variant: Writable { init(id: ID, from input: Input) throws { let lenght: UInt32 = try input.read() switch id { case .Bool: guard lenght == MemoryLayout.size else { throw KDBXError.invalidValue } self = .Bool(try input.read()) case .UInt32: guard lenght == MemoryLayout.size else { throw KDBXError.invalidValue } self = .UInt32(try input.read()) case .UInt64: guard lenght == MemoryLayout.size else { throw KDBXError.invalidValue } self = .UInt64(try input.read()) case .Int32: guard lenght == MemoryLayout.size else { throw KDBXError.invalidValue } self = .Int32(try input.read()) case .Int64: guard lenght == MemoryLayout.size else { throw KDBXError.invalidValue } self = .Int64(try input.read()) case .String: self = .String(try input.read(lenght: Int(lenght))) case .Bytes: self = .Bytes(try input.read(lenght: Int(lenght))) default: throw KDBXError.invalidValue } } public func write(to output: Output) throws { switch self { case .End: try output.write(id) case let .Bool(value): try output.write(MemoryLayout.size) try output.write(value) case let .UInt32(value): try output.write(MemoryLayout.size) try output.write(value) case let .UInt64(value): try output.write(MemoryLayout.size) try output.write(value) case let .Int32(value): try output.write(MemoryLayout.size) try output.write(value) case let .Int64(value): try output.write(MemoryLayout.size) try output.write(value) case let .String(value): try output.write(Swift.UInt32(value.count)) try output.write(value) case let .Bytes(value): try output.write(Swift.UInt32(value.lenght)) try output.write(value) } } } extension Variant.Version: Streamable { init(from input: Input) throws { minor = try input.read() major = try input.read() } func write(to output: Output) throws { try output.write(minor) try output.write(major) } } extension Dictionary: Streamable where Key == String, Value == Variant { public init(from input: Input) throws { self.init() let version: Variant.Version = try input.read() guard version.major > 0 else { throw KDBXError.invalidValue } while true { let id: Variant.ID = try input.read() if id == .End { break } let lenght: UInt32 = try input.read() let key: String = try input.read(lenght: Int(lenght)) let value = try Variant(id: id, from: input) self[key] = value } } public func write(to output: Output) throws { let version = Variant.Version(major: 1, minor: 0) try output.write(version) try forEach { try output.write($0.value.id) try output.write(UInt32($0.key.count)) try output.write($0.key) try output.write($0.value) } try output.write(Variant.End) } } extension Dictionary: CustomBytesConvertible where Key == String, Value == Variant { public var bytes: Bytes { let output = Output() try? output.write(self) return output.bytes ?? [] } } extension Dictionary: LosslessBytesConvertible where Key == String, Value == Variant { public init(_ bytes: Bytes) throws { let input = Input(bytes: bytes) try self.init(from: input) } }