Jelajahi Sumber

Format code

maxep 5 tahun lalu
induk
melakukan
6be5fc4645

+ 16 - 26
Sources/Binary/Bytes.swift

@@ -26,13 +26,9 @@ public struct Bytes: RawRepresentable {
 
     public var rawValue: [UInt8]
 
-    public var lenght: Int {
-        rawValue.count
-    }
+    public var lenght: Int { rawValue.count }
 
-    public var data: Data {
-        Data(rawValue)
-    }
+    public var data: Data { Data(rawValue) }
 
     public var hexa: String {
         rawValue.map { .init(format: "%02x", $0) }.joined()
@@ -106,12 +102,12 @@ public struct Bytes: RawRepresentable {
     }
 
     public subscript (index: Int) -> UInt8 {
-        get { return rawValue[index] }
+        get { rawValue[index] }
         set { rawValue[index] = newValue }
     }
 
     public subscript (range: CountableRange<Int>) -> Bytes {
-        return Bytes(slice: rawValue[range])
+        Bytes(slice: rawValue[range])
     }
 
     public mutating func append(_ byte: UInt8) {
@@ -128,20 +124,20 @@ public struct Bytes: RawRepresentable {
 
     @discardableResult
     public func withBytes<T>(_ body: ([UInt8]) -> T) -> T {
-        return body(rawValue)
+        body(rawValue)
     }
 
     @discardableResult
     public mutating func withMutableBytes<T>(_ body: (inout [UInt8]) -> T) -> T {
-        return body(&rawValue)
+        body(&rawValue)
     }
 
     public func map<T>() -> [T] where T: BinaryInteger {
-        return map { T($0) }
+        map { T($0) }
     }
 
     public static func + (lhs: Bytes, rhs: Bytes) -> Bytes {
-        return Bytes(rawValue: lhs.rawValue + rhs.rawValue)
+        Bytes(rawValue: lhs.rawValue + rhs.rawValue)
     }
 
     public static func += (lhs: inout Bytes, rhs: Bytes) {
@@ -149,7 +145,7 @@ public struct Bytes: RawRepresentable {
     }
 
     public static func + (lhs: Bytes, rhs: UInt8) -> Bytes {
-        return Bytes(rawValue: lhs.rawValue + [rhs])
+        Bytes(rawValue: lhs.rawValue + [rhs])
     }
 
     public static func += (lhs: inout Bytes, rhs: UInt8) {
@@ -174,7 +170,7 @@ extension Bytes: Sequence {
     /// - Complexity: O(1), except if the sequence also conforms to `Collection`.
     ///   In this case, see the documentation of `Collection.underestimatedCount`.
     public var underestimatedCount: Int {
-        return rawValue.underestimatedCount
+        rawValue.underestimatedCount
     }
 
     /// Call `body(p)`, where `p` is a pointer to the collection's
@@ -188,7 +184,7 @@ extension Bytes: Sequence {
     /// can be generated by advancing the pointer by the distance to the
     /// slice's `startIndex`.
     public func withContiguousStorageIfAvailable<R>(_ body: (UnsafeBufferPointer<UInt8>) throws -> R) rethrows -> R? {
-        return try rawValue.withContiguousStorageIfAvailable(body)
+        try rawValue.withContiguousStorageIfAvailable(body)
     }
 
 }
@@ -209,27 +205,21 @@ extension Bytes: ContiguousBytes {
     /// - warning: The buffer argument to the body should not be stored or used
     ///            outside of the lifetime of the call to the closure.
     public func withUnsafeBytes<R>(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R {
-        return try rawValue.withUnsafeBytes(body)
+        try rawValue.withUnsafeBytes(body)
     }
 
     public mutating func withUnsafeMutableBytes<T>(_ body: (UnsafeMutableRawBufferPointer) throws -> T) rethrows -> T {
-        return try rawValue.withUnsafeMutableBytes(body)
+        try rawValue.withUnsafeMutableBytes(body)
     }
 }
 
 extension Bytes: DataProtocol {
 
-    public var regions: CollectionOfOne<[UInt8]> {
-        rawValue.regions
-    }
+    public var regions: CollectionOfOne<[UInt8]> { rawValue.regions }
 
-    public var startIndex: Int {
-        rawValue.startIndex
-    }
+    public var startIndex: Int { rawValue.startIndex }
 
-    public var endIndex: Int {
-        rawValue.endIndex
-    }
+    public var endIndex: Int { rawValue.endIndex }
 
 }
 

+ 11 - 6
Sources/Binary/BytesRepresentable.swift

@@ -39,7 +39,6 @@ extension Bool: BytesRepresentable {
     public var bytes: Bytes {
         withUnsafeBytes(of: self) { Bytes($0) }
     }
-
 }
 
 // MARK: - Streamable Integer
@@ -54,7 +53,6 @@ extension BytesRepresentable where Self: BinaryInteger {
     public var bytes: Bytes {
         withUnsafeBytes(of: self) { Bytes($0) }
     }
-
 }
 
 extension Int: BytesRepresentable { }
@@ -89,7 +87,6 @@ extension BytesRepresentable where Self: FloatingPoint {
     public var bytes: Bytes {
         withUnsafeBytes(of: self) { Bytes(rawValue: Array($0)) }
     }
-
 }
 
 extension Double: BytesRepresentable { }
@@ -125,7 +122,7 @@ extension Bytes: BytesRepresentable {
 extension Array where Element == Bytes {
 
     subscript<T>(_ index: Int) -> T? where T: BytesRepresentable {
-        return try? T(self[index])
+        try? T(self[index])
     }
 }
 
@@ -149,7 +146,6 @@ extension String: BytesRepresentable {
         guard let string = String(bytes: bytes, encoding: .utf8) else { throw BinaryError.invalidValue }
         self = string
     }
-
 }
 
 // MARK: - Data Bytes
@@ -161,7 +157,6 @@ extension Data: BytesRepresentable {
     public init(_ bytes: Bytes) throws {
         self = Data(bytes.rawValue)
     }
-
 }
 
 // MARK: - UUID Bytes
@@ -177,5 +172,15 @@ extension UUID: BytesRepresentable {
         let uuid = bytes.withUnsafeBytes { $0.load(as: uuid_t.self) }
         self = UUID(uuid: uuid)
     }
+}
 
+extension Optional where Wrapped: BytesRepresentable {
+
+    public init(_ bytes: Bytes?) {
+        if let bytes = bytes, let wrapped = try? Wrapped(bytes) {
+            self = .some(wrapped)
+        } else {
+            self = .none
+        }
+    }
 }

+ 1 - 3
Sources/Binary/Input.swift

@@ -24,9 +24,7 @@ public class Input {
 
     public private(set) var offset = 0
 
-    public var remaining: Bytes {
-        bytes.suffix(from: offset)
-    }
+    public var remaining: Bytes { bytes.suffix(from: offset) }
 
     public var hasBytesAvailable: Bool { stream.hasBytesAvailable }
 

+ 4 - 4
Sources/Binary/Streamable.swift

@@ -132,7 +132,7 @@ extension Readable where Self: OptionSet, RawValue: Readable {
 extension Array: Writable where Element: Writable {
 
     public func write(to output: Output) throws {
-        try forEach { try output.write($0) }
+        try forEach(output.write)
     }
 
 }
@@ -141,13 +141,13 @@ extension Array: Writable where Element: Writable {
 
 extension String: Streamable {
 
-    public init?(bytes: Bytes, encoding: String.Encoding) {
+    public init?(bytes: Bytes, encoding: String.Encoding = .utf8) {
         let data = Data(bytes.rawValue)
         self.init(data: data, encoding: encoding)
     }
 
-    public func bytes(using: String.Encoding) -> Bytes? {
-        return data(using: .utf8)?.bytes
+    public func bytes(using encoding: String.Encoding = .utf8) -> Bytes? {
+        data(using: encoding)?.bytes
     }
 
 }

+ 30 - 16
Sources/Binary/TLV.swift

@@ -18,28 +18,28 @@
 
 import Foundation
 
-public protocol TLVProtocol {
+public protocol TypeLenghtValue {
 
-    associatedtype `Type`
+    associatedtype Type_
 
     associatedtype Lenght: BinaryInteger
 
     associatedtype Value
 
-    var type: Type { get }
+    var type: Type_ { get }
 
-    var value: Value { get set }
+    var value: Value { get }
 }
 
-public protocol Endable {
-    var isAtEnd: Bool { get }
+public protocol Endable: Equatable {
+    static var endValue: Self { get }
 }
 
-public struct TLV<Type, Lenght>: TLVProtocol where Lenght: BinaryInteger {
+public struct TLV<Type, Lenght>: TypeLenghtValue where Lenght: BinaryInteger {
 
     public let type: Type
 
-    public var value: Bytes
+    public private(set) var value: Bytes
 
     public init(type: Type, value: Bytes) {
         self.type = type
@@ -47,7 +47,7 @@ public struct TLV<Type, Lenght>: TLVProtocol where Lenght: BinaryInteger {
     }
 
     public func get<T>() throws -> T where T: BytesRepresentable {
-        return try T(value)
+        try T(value)
     }
 
     public mutating func set<T>(_ value: T?) throws where T: BytesRepresentable {
@@ -75,28 +75,28 @@ extension TLV: Writable where Type: Writable, Lenght: Writable {
 
 }
 
-extension Sequence where Element: TLVProtocol, Element.`Type`: Equatable, Element.Value == Bytes {
+extension Sequence where Element: TypeLenghtValue, Element.Type_: Equatable, Element.Value == Bytes {
 
-    public func first<T>(valueOf type: Element.`Type`) throws -> T? where T: BytesRepresentable {
+    public func first<T>(valueOf type: Element.Type_) throws -> T? where T: BytesRepresentable {
         return try first(where: { $0.type == type }).map { try T($0.value) }
     }
 
-    public func first<T>(where type: Element.`Type`, _ predicate: (T) throws -> Bool) throws -> Element? where T: BytesRepresentable {
+    public func first<T>(where type: Element.Type_, _ predicate: (T) throws -> Bool) throws -> Element? where T: BytesRepresentable {
         return try first(where: { try predicate(try T($0.value)) })
     }
 
-    public func sorted<T>(field: Element.`Type`, by areInIncreasingOrder: (T, T) throws -> Bool) throws -> [Self.Element] where T: BytesRepresentable {
+    public func sorted<T>(field: Element.Type_, by areInIncreasingOrder: (T, T) throws -> Bool) throws -> [Self.Element] where T: BytesRepresentable {
         return try sorted(by: { try areInIncreasingOrder(try T($0.value), try T($1.value)) })
     }
 
-    public subscript<T>(_ type: Element.`Type`) -> T? where T: BytesRepresentable {
+    public subscript<T>(_ type: Element.Type_) -> T? where T: BytesRepresentable {
         try? first(valueOf: type)
     }
 }
 
-extension RangeReplaceableCollection where Element: TLVProtocol, Element.`Type`: Equatable {
+extension RangeReplaceableCollection where Element: TypeLenghtValue, Element.Type_: Equatable {
 
-    public mutating func removeAll(_ type: Element.`Type`) {
+    public mutating func removeAll(_ type: Element.Type_) {
         removeAll(where: { $0.type == type })
     }
 
@@ -107,5 +107,19 @@ extension TLV: CustomDebugStringConvertible {
     public var debugDescription: String {
         "(T:\(type) L:\(value.lenght))"
     }
+}
+
+extension Array: Readable where Element: TypeLenghtValue & Readable, Element.Type_: Endable {
+
+    public init(from input: Input) throws {
+        var fields: [Element] = []
+
+        while true {
+            let field: Element = try input.read()
+            fields.append(field)
+            if field.type == Element.Type_.endValue { break }
+        }
 
+        self = fields
+    }
 }

+ 1 - 1
Sources/Crypto/ChaCha20.swift

@@ -51,6 +51,6 @@ extension ChaCha20: Cipher {
     }
 
     public func decrypt(data: Bytes) throws -> Bytes {
-        return try encrypt(data: data)
+        try encrypt(data: data)
     }
 }

+ 1 - 1
Sources/Crypto/Salsa20.swift

@@ -50,7 +50,7 @@ extension Salsa20: Cipher {
     }
 
     public func decrypt(data: Bytes) throws -> Bytes {
-        return try encrypt(data: data)
+        try encrypt(data: data)
     }
 
 }

+ 1 - 5
Sources/Crypto/Twofish.swift

@@ -20,7 +20,7 @@ import Foundation
 import Binary
 import Twofish
 
-public final class Twofish {
+public final class Twofish: Cipher {
 
     private var context: twofish_context
 
@@ -37,11 +37,7 @@ public final class Twofish {
         context = twofish_context()
         twofish_setup(&context, key.rawValue, iv.rawValue, twofish_options_default)
     }
-    
-}
 
-extension Twofish: Cipher {
-    
     public func encrypt(data: Bytes) throws -> Bytes {
         let output_length = twofish_get_output_length(&context, lenght(data))
         var out = Bytes(lenght: Int(output_length))

+ 38 - 10
Sources/KDB/Database.swift

@@ -32,7 +32,7 @@ public class Database {
     public required init(from input: Input, compositeKey: CompositeKey) throws {
         header = try input.read()
 
-        let data = try input.read() as Bytes
+        let data: Bytes = try input.read()
         let key = try header.masterKey(from: compositeKey)
 
         let cipher: Cipher
@@ -56,7 +56,6 @@ public class Database {
     }
 
     public convenience init(from file: URL, compositeKey: CompositeKey) throws {
-
         let bytes = try Bytes(contentsOf: file)
         let stream = Input(bytes: bytes)
 
@@ -68,13 +67,39 @@ public class Database {
         try self.init(from: stream, compositeKey: compositeKey)
     }
 
-}
+    public func write(to output: Output, compositeKey: CompositeKey) throws {
+
+        func groups(in group: Group) -> UInt32 {
+            let count = UInt32(group.childs.count)
+            return group.childs.reduce(count) { $0 + groups(in: $1) }
+        }
 
-extension Database: Writable {
+        func entries(in group: Group) -> UInt32 {
+            let count = UInt32(group.entries.count)
+            return group.childs.reduce(count) { $0 + entries(in: $1) }
+        }
 
-    public func write(to output: Output) throws {
+        let key = try header.masterKey(from: compositeKey)
+        let cipher: Cipher
+
+        if header.cipher.contains(.aes) {
+            cipher = try AESCipher(key: key, iv: header.initialVector)
+        } else if header.cipher.contains(.twofish) {
+            cipher = try Twofish(key: key, iv: header.initialVector)
+        } else {
+            throw KDBError.unsupportedCipher
+        }
+
+        let content = try Data(from: root)
+
+        var header = header
+        header.contentHash = SHA256.hash(content)
+        header.groups = groups(in: root)
+        header.entries = entries(in: root)
         try output.write(header)
-        fatalError()
+
+        let data = try cipher.encrypt(data: content)
+        try output.write(data)
     }
 
 }
@@ -91,7 +116,7 @@ private func Root(from input: Input, groups: Int, entries: Int) throws -> Group
         guard let level1: UInt16 = group[.groupLevel] else { throw KDBError.corruptedDatabase }
 
         if level1 == 0 {
-            root.childs.append(group)
+            root.add(group)
             continue
         }
 
@@ -100,7 +125,7 @@ private func Root(from input: Input, groups: Int, entries: Int) throws -> Group
 
             if level2 < level1 {
                 guard (level1 - level2) == 1 else { throw KDBError.corruptedDatabase }
-                parent.childs.append(group)
+                parent.add(group)
                 break
             }
 
@@ -111,10 +136,13 @@ private func Root(from input: Input, groups: Int, entries: Int) throws -> Group
 
     for entry in entries {
         guard let groupID: UInt32 = entry[.groupID] else { throw KDBError.corruptedDatabase }
-
-        let group = try groups.first(where: .groupID, { $0 == groupID }) ?? root
+        let group = try groups.first(column: .groupID, where: { $0 == groupID }) ?? root
         group.add(entry)
     }
 
     return root
 }
+
+private func Data(from root: Group) throws -> Bytes {
+    fatalError()
+}

+ 14 - 48
Sources/KDB/Entry.swift

@@ -19,31 +19,9 @@
 import Foundation
 import Binary
 
-let MetaEntryBinaryDescription   = "bin-stream"
-let MetaEntryTitle               = "Meta-Info"
-let MetaEntryUsername            = "SYSTEM"
-let MetaEntryURL                 = "$"
-
-let MetaEntryUIState                         = "Simple UI State"
-let MetaEntryDefaultUsername                 = "Default User Name"
-let MetaEntrySearchHistoryItem               = "Search History Item"
-let MetaEntryCustomKVP                       = "Custom KVP"
-let MetaEntryDatabaseColor                   = "Database Color"
-let MetaEntryKeePassXCustomIcon              = "KPX_CUSTOM_ICONS_2"
-let MetaEntryKeePassXCustomIcon2             = "KPX_CUSTOM_ICONS_4"
-let MetaEntryKeePassXGroupTreeState          = "KPX_GROUP_TREE_STATE"
-let MetaEntryKeePassKitGroupUUIDs            = "KeePassKit Group UUIDs"
-let MetaEntryKeePassKitDeletedObjects        = "KeePassKit Deleted Objects"
-let MetaEntryKeePassKitDatabaseName          = "KeePassKit Database Name"
-let MetaEntryKeePassKitDatabaseDescription   = "KeePassKit Database Description"
-let MetaEntryKeePassKitTrash                 = "KeePassKit Trash"
-let MetaEntryKeePassKitUserTemplates         = "KeePassKit User Templates"
-
 public final class Entry: Row, Streamable {
 
-    public static let End = Type.end
-
-    public enum `Type`: UInt16, Streamable {
+    public enum Column: UInt16, Streamable, Endable {
         case reserved           = 0x0000
         case uuid               = 0x0001
         case groupID            = 0x0002
@@ -60,53 +38,41 @@ public final class Entry: Row, Streamable {
         case binaryDesc         = 0x000D
         case binaryData         = 0x000E
         case end                = 0xFFFF
+
+        public static var endValue: Self { .end }
     }
 
-    var parent: Group?
+    public internal(set) weak var parent: Group?
 
-    public var properties: [Property<Type>]
+    public var properties: [TLV<Column, UInt32>]
 
-    public required init() {
+    public init() {
         properties = []
-    }    
-}
-
-extension Entry {
-
-    public var creationDate: Date {
-        date(at: .creationTime) ?? Date.distantPast
-    }
-
-    public var lastModifiedDate: Date {
-        get { date(at: .lastModifiedTime) ?? Date.distantPast }
-        set { set(newValue, at: .lastModifiedTime) }
     }
 
-    public var lastAccessDate: Date {
-        get { date(at: .lastAccessTime) ?? Date.distantPast }
-        set { set(newValue, at: .lastAccessTime) }
+    public init(from input: Input) throws {
+        properties = try input.read()
     }
+}
 
-    var isMetaEntry: Bool {
-        return false
-    }
+extension Entry {
 
     public func removeFromParent() {
         parent?.entries.removeAll(where: { $0 == self })
         self[.groupID] = -1
+        parent = nil
     }
 }
 
 extension Entry: Hashable {
 
     public static func == (lhs: Entry, rhs: Entry) -> Bool {
-        guard let lhs = lhs[.uuid], let rhs = rhs[.uuid] else { return false }
-        return lhs == rhs
+        lhs[.uuid] == rhs[.uuid]
     }
 
     public func hash(into hasher: inout Hasher) {
-        if let uuid = self[.uuid] { hasher.combine(uuid) }
-        else if let title = self[.title] { hasher.combine(title) }
+        hasher.combine(self[.uuid])
+        hasher.combine(self[.title])
     }
 
 }

+ 23 - 44
Sources/KDB/Group.swift

@@ -21,9 +21,7 @@ import Binary
 
 public final class Group: Row, Streamable {
 
-    public static let End = Type.end
-
-    public enum `Type`: UInt16, Streamable {
+    public enum Column: UInt16, Streamable, Endable {
         case reserved           = 0x0000
         case groupID            = 0x0001
         case name               = 0x0002
@@ -35,82 +33,63 @@ public final class Group: Row, Streamable {
         case groupLevel         = 0x0008
         case groupFlags         = 0x0009
         case end                = 0xFFFF
+
+        public static var endValue: Self { .end }
     }
 
-    var parent: Group?
+    public internal(set) weak var parent: Group?
 
-    public var properties: [Property<Type>]
+    public internal(set) var childs: [Group]
 
-    public var childs: [Group]
+    public internal(set) var entries: [Entry]
 
-    public var entries: [Entry]
+    public var properties: [TLV<Column, UInt32>]
 
-    public required init() {
+    public init() {
         properties = []
         childs = []
         entries = []
     }
-}
-
-extension Group {
-
-    public var name: String {
-        get { self[.name] ?? "" }
-        set { self[.name] = newValue }
-    }
 
-    public var level: Int {
-        get { self[.groupLevel] ?? 0 }
-        set { self[.groupLevel] = newValue }
-    }
-
-    public var icon: Int {
-        get { self[.iconID] ?? 0 }
-        set { self[.iconID] = newValue }
-    }
-
-    public var creationDate: Date {
-        date(at: .creationTime) ?? Date.distantPast
-    }
-
-    public var lastModifiedDate: Date {
-        get { date(at: .lastModifiedTime) ?? Date.distantPast }
-        set { set(newValue, at: .lastModifiedTime) }
+    public init(from input: Input) throws {
+        properties = try input.read()
+        childs = []
+        entries = []
     }
+}
 
-    public var lastAccessDate: Date {
-        get { date(at: .lastAccessTime) ?? Date.distantPast }
-        set { set(newValue, at: .lastAccessTime) }
-    }
+extension Group {
 
     public func removeFromParent() {
         parent?.childs.removeAll(where: { $0 == self })
+        parent = nil
     }
 
     public func add(_ entry: Entry) {
         entry.removeFromParent()
         entries.append(entry)
         entry[.groupID] = self[.groupID]
+        entry.parent = self
     }
 
     public func add(_ group: Group) {
         group.removeFromParent()
         childs.append(group)
-        group.level = level + 1
+        group[.groupLevel] = self[.groupLevel] ?? 0 + 1
+        group.parent = self
     }
 }
 
 extension Group: Hashable {
 
     public static func == (lhs: Group, rhs: Group) -> Bool {
-        guard let lhs = lhs[.groupLevel], let rhs = rhs[.groupLevel] else { return false }
-        return lhs == rhs
+        lhs[.groupID] == rhs[.groupID] &&
+        lhs[.groupLevel] == rhs[.groupLevel]
     }
 
     public func hash(into hasher: inout Hasher) {
-        if let groupID = self[.groupID] { hasher.combine(groupID) }
-        if let name = self[.name] { hasher.combine(name) }
-        if let groupLevel = self[.groupLevel] { hasher.combine(groupLevel) }
+        hasher.combine(self[.groupID])
+        hasher.combine(self[.name])
+        hasher.combine(self[.groupLevel])
     }
-
 }

+ 3 - 4
Sources/KDB/Header.swift

@@ -25,9 +25,9 @@ struct Header {
     let version: UInt32
     let masterSeed: Bytes
     let initialVector: Bytes
-    let groups: UInt32
-    let entries: UInt32
-    let contentHash: Bytes
+    var groups: UInt32
+    var entries: UInt32
+    var contentHash: Bytes
     let transformSeed: Bytes
     let transformRounds: UInt32
 }
@@ -66,7 +66,6 @@ extension Header: Streamable {
         try output.write(transformSeed)
         try output.write(transformRounds)
     }
-
 }
 
 extension Header {

+ 33 - 59
Sources/KDB/Row.swift

@@ -19,94 +19,68 @@
 import Foundation
 import Binary
 
-public typealias Property<Type> = TLV<Type, UInt32>
-
 public protocol Row: AnyObject {
+    associatedtype Column
 
-    associatedtype `Type`: Streamable, Equatable
-
-    static var End: Type { get }
+    var properties: [TLV<Column, UInt32>] { get set }
+}
 
-    var properties: [Property<Type>] { get set }
+extension Row where Self: Writable, Column: Writable {
 
-    init()
+    public func write(to output: Output) throws {
+        try output.write(properties)
+    }
 }
 
-extension Row {
+extension Row where Column: Equatable {
 
-    public subscript(_ type: Type) -> Bytes? {
-        get { properties.first(where: { $0.type == type })?.value }
+    public subscript(_ column: Column) -> Bytes? {
+        get { properties.first(where: { $0.type == column })?.value }
         set {
-            properties.removeAll(type)
+            properties.removeAll(column)
             guard let value = newValue else { return }
-            let tlv = Property(type: type, value: value)
+            let tlv = TLV<Column, UInt32>(type: column, value: value)
             properties.insert(tlv, at: 0)
         }
     }
 
-    public func set(_ field: Property<Type>) {
-        properties.removeAll(field.type)
-        properties.insert(field, at: 0)
+    public func set(_ property: TLV<Column, UInt32>) {
+        properties.removeAll(property.type)
+        properties.insert(property, at: 0)
     }
 
-    public func set(_ date: Date, at type: Type) {
-        self[type] = Database.bytes(from: date)
+    public func set(_ date: Date, at column: Column) {
+        self[column] = Database.bytes(from: date)
     }
 
-    public func date(at type: Type) -> Date? {
-        guard let bytes = self[type] else { return nil }
+    public func date(at column: Column) -> Date? {
+        guard let bytes = self[column] else { return nil }
         return Database.date(from: bytes)
     }
 
-    public subscript<T>(_ type: Type) -> T? where T: BytesRepresentable {
-        get {
-            guard let bytes = self[type] else { return nil }
-            return try? T(bytes)
-        }
-        set { self[type] = newValue?.bytes }
+    public subscript<T>(_ column: Column) -> T? where T: BytesRepresentable {
+        get { T?(self[column]) }
+        set { self[column] = newValue?.bytes }
     }
 
-    public func remove(_ type: Type) {
-        properties.removeAll(type)
-    }
-}
-
-extension Readable where Self: Row {
-
-    public init(from input: Input) throws {
-        self.init()
-        while true {
-            let field = try input.read() as Property<Type>
-            guard field.type != Self.End else { break }
-            properties.append(field)
-        }
-    }
-
-}
-
-extension Writable where Self: Row {
-
-    public func write(to output: Output) throws {
-        try output.write(properties)
-        let end = Property(type: Self.End, value: [])
-        try output.write(end)
+    public func remove(_ column: Column) {
+        properties.removeAll(column)
     }
-    
 }
 
-extension Sequence where Element: Row {
+extension Sequence where Element: Row, Element.Column: Equatable {
 
-    public func first<T>(where type: Element.`Type`, _ predicate: (T) throws -> Bool) throws -> Element? where T: BytesRepresentable {
-        return try first(where: {
-            guard let bytes = $0[type] else { return false }
-            return try predicate(try T(bytes))
+    public func first<T>(column: Element.Column, where predicate: (T) throws -> Bool) throws -> Element? where T: BytesRepresentable {
+        try first(where: {
+            guard let bytes = $0[column] else { return false }
+            return try predicate(T(bytes))
         })
     }
 
-    public func sorted<T>(field: Element.`Type`, by areInIncreasingOrder: (T, T) throws -> Bool) throws -> [Self.Element] where T: BytesRepresentable {
-        return try sorted(by: {
-            guard let rhs = $0[field], let lhs = $1[field] else { return false }
-            return try areInIncreasingOrder(try T(rhs), try T(lhs))
+    public func sorted<T>(column: Element.Column, by areInIncreasingOrder: (T, T) throws -> Bool) throws -> [Element] where T: BytesRepresentable {
+        try sorted(by: {
+            guard let rhs = $0[column], let lhs = $1[column] else { return false }
+            return try areInIncreasingOrder(T(rhs),T(lhs))
         })
     }
 

+ 3 - 0
Sources/KDBX/Database.swift

@@ -17,9 +17,12 @@
 // along with KeePassKit. If not, see <https://www.gnu.org/licenses/>.
 
 import Foundation
+import Binary
 import XML
 
 public protocol Database {
 
     var document: Document { get }
+
+    func write(to output: Output, compositeKey: CompositeKey) throws
 }

+ 1 - 5
Sources/KDBX/Database0.swift

@@ -30,11 +30,7 @@ class Database0: Database {
         document = try XML.Document(xml: input.bytes.data, options: options)
     }
 
-}
-
-extension Database0: Writable {
-
-    func write(to output: Output) throws {
+    func write(to output: Output, compositeKey: CompositeKey) throws {
         try output.write(document.xml)
     }
 }

+ 1 - 6
Sources/KDBX/Database3.swift

@@ -73,12 +73,7 @@ class Database3: Database {
         document = try XML.Document(xml: content.data, options: options)
     }
 
-}
-
-extension Database3: Writable {
-
-    func write(to output: Output) throws {
-        try output.write(header)
+    func write(to output: Output, compositeKey: CompositeKey) throws {
         fatalError()
     }
 }

+ 30 - 36
Sources/KDBX/Database4.swift

@@ -36,9 +36,9 @@ class Database4: Database {
 
         // Get outer header bytes to verify the hash
         let header = input.bytes.prefix(input.offset)
-        var content = try Database4.unhash(header: header,
-                                           data: try input.read(),
-                                           key: masterKey)
+        var content = try Unhash(header: header,
+                                 data: try input.read(),
+                                 key: masterKey)
 
         let key = SHA256.hash( masterKey )
         let cipher = try outerHeader.cipher(key: key)
@@ -56,50 +56,44 @@ class Database4: Database {
 
         document = try XML.Document(xml: stream.remaining.data, options: options)
     }
+    func write(to output: Output, compositeKey: CompositeKey) throws {
+        fatalError()
+    }
 
-    class func unhash(header: Bytes, data: Bytes, key: Bytes) throws -> Bytes {
-        let stream = Input(bytes: data)
-
-        let key = SHA512.hash( key + 1 )
-        let hmacKey = HmacKey(block: .max, key: key)
-
-        guard
-            try stream.read(lenght: SHA256.Lenght) == SHA256.hash( header ),
-            try stream.read(lenght: HMACSHA256.Lenght) == HMACSHA256.authenticate(header, key: hmacKey)
-        else { throw KDBXError.invalidCompositeKey }
-
-        var index: UInt64 = 0
-        var content = Bytes()
+}
 
-        while stream.hasBytesAvailable {
+private func Unhash(header: Bytes, data: Bytes, key: Bytes) throws -> Bytes {
+    let stream = Input(bytes: data)
 
-            let hmac = try stream.read(lenght: HMACSHA256.Lenght)
-            let size = try CFSwapInt32LittleToHost(stream.read())
-            let block = try stream.read(lenght: Int(size))
+    let key = SHA512.hash( key + 1 )
+    let hmacKey = HmacKey(block: .max, key: key)
 
-            let hmacKey = HmacKey(block: index, key: key)
-            let hash = HMACSHA256(key: hmacKey)
-            hash.update(CFSwapInt64HostToLittle(index).bytes)
-            hash.update(CFSwapInt32HostToLittle(size).bytes)
-            hash.update(block)
+    guard
+        try stream.read(lenght: SHA256.Lenght) == SHA256.hash( header ),
+        try stream.read(lenght: HMACSHA256.Lenght) == HMACSHA256.authenticate(header, key: hmacKey)
+    else { throw KDBXError.invalidCompositeKey }
 
-            guard hash.final == hmac else { throw KDBXError.corruptedDatabase }
-            content += block
-            index += 1
-        }
+    var index: UInt64 = 0
+    var content = Bytes()
 
-        return content
-    }
+    while stream.hasBytesAvailable {
 
-}
+        let hmac = try stream.read(lenght: HMACSHA256.Lenght)
+        let size = try CFSwapInt32LittleToHost(stream.read())
+        let block = try stream.read(lenght: Int(size))
 
-extension Database4: Writable {
+        let hmacKey = HmacKey(block: index, key: key)
+        let hash = HMACSHA256(key: hmacKey)
+        hash.update(CFSwapInt64HostToLittle(index).bytes)
+        hash.update(CFSwapInt32HostToLittle(size).bytes)
+        hash.update(block)
 
-    func write(to output: Output) throws {
-        try output.write(outerHeader)
-        fatalError()
+        guard hash.final == hmac else { throw KDBXError.corruptedDatabase }
+        content += block
+        index += 1
     }
 
+    return content
 }
 
 func HmacKey(block index: UInt64, key: Bytes) -> Bytes {

+ 4 - 6
Sources/KDBX/File.swift

@@ -34,7 +34,7 @@ public class File {
 
     public let version: Version
 
-    public let database: Database & Writable
+    public let database: Database
 
     public required init(from input: Input) throws {
         version = Version(major: 0, minor: 0)
@@ -72,13 +72,11 @@ public class File {
 
         try self.init(from: stream, compositeKey: compositeKey)
     }
-}
-
-extension File: Writable {
 
-    public func write(to output: Output) throws {
+    public func write(to output: Output, compositeKey: CompositeKey) throws {
+        try output.write(FileSignature)
+        try output.write(BetaFileFormat)
         try output.write(version)
-        try database.write(to: output)
     }
 
 }

+ 3 - 18
Sources/KDBX/Header.swift

@@ -37,7 +37,7 @@ enum OuterHeader: UInt8, Streamable, Endable {
     case kdfParameters       = 11
     case publicCustomData    = 12
 
-    var isAtEnd: Bool { self == .end }
+    public static var endValue: OuterHeader { .end }
 }
 
 enum InnerHeader: UInt8, Streamable, Endable {
@@ -46,7 +46,7 @@ enum InnerHeader: UInt8, Streamable, Endable {
     case innerRandomStreamKey = 2
     case binary               = 3
 
-    var isAtEnd: Bool { self == .end }
+    public static var endValue: InnerHeader { .end }
 }
 
 enum Compression: UInt32, BytesRepresentable {
@@ -63,7 +63,7 @@ enum RandomStream: UInt32, BytesRepresentable {
     case count      = 4
 }
 
-extension Array where Element: TLVProtocol, Element.`Type` == OuterHeader, Element.Value == Bytes {
+extension Array where Element: TypeLenghtValue, Element.Type_ == OuterHeader, Element.Value == Bytes {
 
     func cipher(key: Bytes) throws -> Cipher {
         guard
@@ -117,18 +117,3 @@ extension Array where Element: TLVProtocol, Element.`Type` == OuterHeader, Eleme
         }
     }
 }
-
-extension Array: Readable where Element: TLVProtocol & Readable, Element.`Type`: Endable {
-
-    public init(from input: Input) throws {
-        var fields: [Element] = []
-
-        while true {
-            let field: Element = try input.read()
-            fields.append(field)
-            if field.type.isAtEnd { break }
-        }
-
-        self = fields
-    }
-}

+ 0 - 6
Sources/KeePass/CompositeKey.swift

@@ -20,8 +20,6 @@ import Foundation
 import Binary
 import Crypto
 import XML
-import KDB
-import KDBX
 
 public struct CompositeKey {
 
@@ -64,7 +62,3 @@ public struct CompositeKey {
     }
 
 }
-
-extension CompositeKey: KDB.CompositeKey { }
-
-extension CompositeKey: KDBX.CompositeKey { }

+ 20 - 0
Sources/KeePass/Entry.swift

@@ -24,6 +24,26 @@ public let EntryFieldPassword = "Password"
 public let EntryFieldURL      = "URL"
 public let EntryFieldNotes    = "Notes"
 
+public let MetaEntryBinaryDescription   = "bin-stream"
+public let MetaEntryTitle               = "Meta-Info"
+public let MetaEntryUsername            = "SYSTEM"
+public let MetaEntryURL                 = "$"
+
+public let MetaEntryUIState                         = "Simple UI State"
+public let MetaEntryDefaultUsername                 = "Default User Name"
+public let MetaEntrySearchHistoryItem               = "Search History Item"
+public let MetaEntryCustomKVP                       = "Custom KVP"
+public let MetaEntryDatabaseColor                   = "Database Color"
+public let MetaEntryKeePassXCustomIcon              = "KPX_CUSTOM_ICONS_2"
+public let MetaEntryKeePassXCustomIcon2             = "KPX_CUSTOM_ICONS_4"
+public let MetaEntryKeePassXGroupTreeState          = "KPX_GROUP_TREE_STATE"
+public let MetaEntryKeePassKitGroupUUIDs            = "KeePassKit Group UUIDs"
+public let MetaEntryKeePassKitDeletedObjects        = "KeePassKit Deleted Objects"
+public let MetaEntryKeePassKitDatabaseName          = "KeePassKit Database Name"
+public let MetaEntryKeePassKitDatabaseDescription   = "KeePassKit Database Description"
+public let MetaEntryKeePassKitTrash                 = "KeePassKit Trash"
+public let MetaEntryKeePassKitUserTemplates         = "KeePassKit User Templates"
+
 public protocol Entry {
 
     associatedtype Fields: RandomAccessCollection where Fields.Element == Field

+ 27 - 8
Sources/KeePass/KDB.swift

@@ -20,11 +20,11 @@ import Foundation
 import Binary
 import KDB
 
-extension KDB.Database: Database {
+extension CompositeKey: KDB.CompositeKey { }
 
-}
+extension KDB.Database: Database {}
 
-extension KDB.Property where Type == KDB.Entry.`Type` {
+extension TLV where Type == KDB.Entry.Column {
 
     init?(_ field: Field) {
 
@@ -53,8 +53,13 @@ extension KDB.Property where Type == KDB.Entry.`Type` {
 extension KDB.Group: Group {
 
     public var title: String {
-        get { name }
-        set { name = newValue }
+        get { self[.name] ?? "" }
+        set { self[.name] = newValue }
+    }
+
+    public var icon: Int {
+        get { self[.iconID] ?? 0 }
+        set { self[.iconID] =  newValue }
     }
 
     public var groups: [KDB.Group] { childs }
@@ -65,13 +70,13 @@ extension KDB.Entry: Entry {
     public var times: Timestamp {
          return self
     }
-    
+
     public var fields: [Field] {
         properties.compactMap { Field($0) }
     }
 
     public func set(_ field: Field) {
-        guard let field = KDB.Property(field) else { return }
+        guard let field = TLV<Column, UInt32>(field) else { return }
         set(field)
     }
 
@@ -79,6 +84,20 @@ extension KDB.Entry: Entry {
 
 extension KDB.Entry: Timestamp {
 
+    public var creationDate: Date {
+        date(at: .creationTime) ?? Date.distantPast
+    }
+
+    public var lastModifiedDate: Date {
+        get { date(at: .lastModifiedTime) ?? Date.distantPast }
+        set { set(newValue, at: .lastModifiedTime) }
+    }
+
+    public var lastAccessDate: Date {
+        get { date(at: .lastAccessTime) ?? Date.distantPast }
+        set { set(newValue, at: .lastAccessTime) }
+    }
+
     public var expirationDate: Date? {
         get { nil }
         set { }
@@ -88,7 +107,7 @@ extension KDB.Entry: Timestamp {
 
 extension Field {
 
-    init?(_ field: KDB.Property<KDB.Entry.`Type`>) {
+    init?(_ field: TLV<KDB.Entry.Column, UInt32>) {
 
         switch field.type {
         case .title:

+ 2 - 0
Sources/KeePass/KDBX.swift

@@ -23,6 +23,8 @@ import KDBX
 
 let DateFormatter = ISO8601DateFormatter()
 
+extension CompositeKey: KDBX.CompositeKey { }
+
 extension KDBX.File: Database {
     public var root: Element { database.document.root.KeePassFile.Root }
 }

+ 8 - 4
Tests/KeePassTests/KeePassTests.swift

@@ -3,25 +3,29 @@ import XCTest
 
 final class KeePassTests: XCTestCase {
 
+    func testKDB() throws {
+        let file = Bundle.module.url(forResource: "test-password-1234", withExtension: "kdb")!
+        let key = CompositeKey(password: "1234")
+        let db = try KeePass.open(contentOf: file, compositeKey: key)
+        print(db)
+    }
+
     func testKDBX3() throws {
         let file = Bundle.module.url(forResource: "test-password-1234", withExtension: "kdbx")!
-
         let key = CompositeKey(password: "1234")
-
         let db = try KeePass.open(contentOf: file, compositeKey: key)
         print(db)
     }
 
     func testKDBX4() throws {
         let file = Bundle.module.url(forResource: "argon2-kdf-AES-cipher", withExtension: "kdbx")!
-
         let key = CompositeKey(password: "test")
-
         let db = try KeePass.open(contentOf: file, compositeKey: key)
         print(db)
     }
 
     static var allTests = [
+        ("testKDB", testKDB),
         ("testKDBX3", testKDBX3),
         ("testKDBX4", testKDBX4),
     ]