-
-
Notifications
You must be signed in to change notification settings - Fork 20
/
DelegatedStorage.swift
255 lines (222 loc) · 7.66 KB
/
DelegatedStorage.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
import CryptoKit
import Foundation
/// Error closure to handle `StorageDelegate` errors.
public typealias StorageErrorClosure = (Error) -> Void
/// Class with the main `CryptoKit` logic.
open class DelegatedStorage: Storage {
/// `StorageDelegate` that stores `StorageData`.
public let delegate: StorageDelegate?
private let symmetricKey: SymmetricKey?
private let authenticationTag: Data?
/// Error closure to handle `StorageDelegate` errors.
open var errorClosure: StorageErrorClosure?
/**
Create a `DelegatedStorage`.
- Parameter delegate: `StorageDelegate` that stores `StorageData`.
- Parameter symmetricKey: A cryptographic key used to seal the message.
- Parameter authenticationTag: Custom additional `Data` to be authenticated.
- Parameter errorClosure: Closure to handle `StorageDelegate` errors.
*/
public init(_ delegate: StorageDelegate? = nil,
symmetricKey: SymmetricKey? = nil,
authenticationTag: Data? = nil,
errorClosure: StorageErrorClosure? = nil) {
self.delegate = delegate
self.symmetricKey = symmetricKey
self.authenticationTag = authenticationTag
self.errorClosure = errorClosure
}
open func register(defaults registrationDictionary: [StoreKey: Any]) {
for (key, value) in registrationDictionary {
if let _: Data = data(forKey: key) {
continue
}
set(value, forKey: key)
}
}
open func value<V>(forKey key: StoreKey) -> V? {
do {
return try object(forKey: key) as? V
} catch {
errorClosure?(error)
return nil
}
}
/**
Returns the `NSCoding` conforming object associated with the specified `StoreKey`.
- Parameter key: A `StoreKey` in storage.
*/
open func object(forKey key: StoreKey) throws -> Any? {
guard let data: Data = data(forKey: key),
let object = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) else {
return nil
}
return object
}
open func decodable<D: Decodable>(forKey key: StoreKey) -> D? {
do {
guard let data: Data = data(forKey: key) else {
return nil
}
return try data.decode(D.self)
} catch {
errorClosure?(error)
return nil
}
}
open func string(forKey key: StoreKey) -> String? {
guard let data: Data = data(forKey: key),
let string = String(data) else {
return nil
}
return string
}
open func array(forKey key: StoreKey) -> [Any]? {
value(forKey: key)
}
open func set(forKey key: StoreKey) -> Set<AnyHashable>? {
value(forKey: key)
}
open func dictionary(forKey key: StoreKey) -> [String: Any]? {
value(forKey: key)
}
open func stringArray(forKey key: StoreKey) -> [String]? {
value(forKey: key)
}
open func integer(forKey key: StoreKey) -> Int {
guard let data: Data = data(forKey: key),
let integer = Int(data) else {
return 0
}
return integer
}
open func float(forKey key: StoreKey) -> Float {
guard let data: Data = data(forKey: key),
let float = Float(data) else {
return 0
}
return float
}
open func double(forKey key: StoreKey) -> Double {
guard let data: Data = data(forKey: key),
let double = Double(data) else {
return 0
}
return double
}
open func bool(forKey key: StoreKey) -> Bool {
guard let data: Data = data(forKey: key),
let bool = Bool(data) else {
return false
}
return bool
}
open func url(forKey key: StoreKey) -> URL? {
guard let data: Data = data(forKey: key) else {
return nil
}
return URL(dataRepresentation: data, relativeTo: nil)
}
open func data<D: StorageData>(forKey key: StoreKey) -> D? {
do {
guard let data: Data = try delegate?.data(forKey: hash(key)),
let symmetricKey else {
return nil
}
let sealedBox = try AES.GCM.SealedBox(combined: data)
if let authenticationTag {
return try AES.GCM.open(sealedBox,
using: symmetricKey,
authenticating: authenticationTag) as? D
}
return try AES.GCM.open(sealedBox,
using: symmetricKey) as? D
} catch {
errorClosure?(error)
return nil
}
}
open func set(_ value: Int, forKey key: StoreKey) {
try? set(value.data, forKey: key)
}
open func set(_ value: Float, forKey key: StoreKey) {
try? set(value.data, forKey: key)
}
open func set(_ value: Double, forKey key: StoreKey) {
try? set(value.data, forKey: key)
}
open func set(_ value: Bool, forKey key: StoreKey) {
try? set(value.data, forKey: key)
}
open func set(_ url: URL?, forKey key: StoreKey) {
try? set(url?.dataRepresentation, forKey: key)
}
open func set(_ string: String, forKey key: StoreKey) {
try? set(string.data, forKey: key)
}
open func set(_ value: (some Any)?, forKey key: StoreKey) {
do {
try set(object: value, forKey: key)
} catch {
guard let encodable = value as? Encodable else {
errorClosure?(error)
return
}
set(encodable: encodable, forKey: key)
}
}
/**
Sets the value of the specified `StoreKey` to the specified `NSCoding` conforming object.
- Parameter value: `NSCoding` conforming object.
- Parameter key: The `StoreKey` with which to associate the value.
*/
open func set(object: Any?, forKey key: StoreKey) throws {
guard let object else {
remove(forKey: key)
return
}
let data = try NSKeyedArchiver.archivedData(withRootObject: object,
requiringSecureCoding: object is NSSecureCoding)
try set(data, forKey: key)
}
open func set(encodable: Encodable?, forKey key: StoreKey) {
guard let encodable else {
remove(forKey: key)
return
}
do {
let data = try encodable.encode()
try set(data, forKey: key)
} catch {
errorClosure?(error)
}
}
open func set(_ data: (some StorageData)?, forKey key: StoreKey) throws {
guard let bytes = data,
let symmetricKey else {
remove(forKey: key)
return
}
if let authenticationTag {
let sealedBox = try AES.GCM.seal(bytes.data,
using: symmetricKey,
authenticating: authenticationTag)
try delegate?.set(sealedBox.combined, forKey: hash(key))
} else {
let sealedBox = try AES.GCM.seal(bytes.data,
using: symmetricKey)
try delegate?.set(sealedBox.combined, forKey: hash(key))
}
}
open func remove(forKey key: StoreKey) {
do {
try delegate?.remove(forKey: hash(key))
} catch {
errorClosure?(error)
}
}
/// Hash `StoreKey` using SHA-512.
public func hash(_ key: StoreKey) -> String {
SHA512.hash(string: key)
}
}