]>
Commit | Line | Data |
---|---|---|
5c19dc3a A |
1 | /* |
2 | * Copyright (c) 2015 Apple Inc. All Rights Reserved. | |
3 | * | |
4 | * @APPLE_LICENSE_HEADER_START@ | |
5 | * | |
6 | * This file contains Original Code and/or Modifications of Original Code | |
7 | * as defined in and that are subject to the Apple Public Source License | |
8 | * Version 2.0 (the 'License'). You may not use this file except in | |
9 | * compliance with the License. Please obtain a copy of the License at | |
10 | * http://www.opensource.apple.com/apsl/ and read it before using this | |
11 | * file. | |
12 | * | |
13 | * The Original Code and all software distributed under the License are | |
14 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER | |
15 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, | |
16 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, | |
17 | * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. | |
18 | * Please see the License for the specific language governing rights and | |
19 | * limitations under the License. | |
20 | * | |
21 | * @APPLE_LICENSE_HEADER_END@ | |
22 | */ | |
23 | ||
24 | /* | |
25 | * SOSChangeTracker.c - Implementation of a manifest caching change tracker that forwards changes to children | |
26 | */ | |
27 | ||
b54c578e A |
28 | #import "keychain/SecureObjectSync/SOSChangeTracker.h" |
29 | #include "keychain/SecureObjectSync/SOSDigestVector.h" | |
30 | #include "keychain/SecureObjectSync/SOSEnginePriv.h" | |
31 | #include "keychain/SecureObjectSync/SOSManifest.h" | |
32 | #include "keychain/SecureObjectSync/SOSInternal.h" | |
5c19dc3a A |
33 | #include <utilities/SecCFError.h> |
34 | #include <utilities/SecCFWrappers.h> | |
35 | ||
36 | CFStringRef SOSChangeCopyDescription(SOSChangeRef change) { | |
37 | CFTypeRef object = NULL; | |
38 | bool isAdd = SOSChangeGetObject(change, &object); | |
39 | // TODO: Print objects or digests | |
40 | return (isData(object) | |
41 | ? isAdd ? CFSTR("a") : CFSTR("d") | |
42 | : isAdd ? CFSTR("A") : CFSTR("D")); | |
43 | } | |
44 | ||
45 | CFDataRef SOSChangeCopyDigest(SOSDataSourceRef dataSource, SOSChangeRef change, bool *isDel, SOSObjectRef *object, CFErrorRef *error) { | |
46 | CFDataRef digest = NULL; | |
47 | if (isArray(change)) { | |
48 | if (CFArrayGetCount(change) != 1) { | |
49 | SecError(errSecDecode, error, CFSTR("change array count: %ld"), CFArrayGetCount(change)); | |
50 | return NULL; | |
51 | } | |
52 | change = CFArrayGetValueAtIndex(change, 0); | |
53 | *isDel = true; | |
54 | } else { | |
55 | *isDel = false; | |
56 | } | |
57 | ||
58 | // If the change is a CFData, this is the signal that it is a delete | |
59 | if (isData(change)) { | |
60 | digest = (CFDataRef)CFRetain(change); | |
61 | } else { | |
62 | digest = SOSObjectCopyDigest(dataSource, (SOSObjectRef)change, error); | |
63 | *object = (SOSObjectRef)change; | |
64 | } | |
65 | assert(digest && CFDataGetLength(digest) == CCSHA1_OUTPUT_SIZE); | |
66 | return digest; | |
67 | } | |
68 | ||
69 | CFStringRef SOSChangesCopyDescription(CFArrayRef changes) { | |
70 | CFMutableStringRef desc = CFStringCreateMutableCopy(kCFAllocatorDefault, 0, CFSTR("(")); | |
71 | CFTypeRef change; | |
72 | if (changes) CFArrayForEachC(changes, change) { | |
73 | CFStringRef changeDesc = SOSChangeCopyDescription(change); | |
74 | CFStringAppend(desc, changeDesc); | |
6b200bc3 | 75 | CFReleaseNull(changeDesc); |
5c19dc3a A |
76 | } |
77 | CFStringAppend(desc, CFSTR(")")); | |
78 | return desc; | |
79 | } | |
80 | ||
81 | ||
82 | /* SOSChangeTracker implementation. */ | |
83 | struct __OpaqueSOSChangeTracker { | |
84 | CFRuntimeBase _base; | |
85 | SOSManifestRef manifest; // Optional: Only concrete cts have a manifest | |
86 | CFMutableArrayRef changeChildren; // Optional: cts can have children | |
87 | CFMutableArrayRef manifestChildren; // Optional: cts can have children | |
88 | }; | |
89 | ||
90 | static CFStringRef SOSChangeTrackerCopyFormatDescription(CFTypeRef cf, CFDictionaryRef formatOptions) { | |
91 | SOSChangeTrackerRef ct = (SOSChangeTrackerRef)cf; | |
92 | CFStringRef desc = CFStringCreateWithFormat(kCFAllocatorDefault, formatOptions, CFSTR("<ChangeTracker %@ children %ld/%ld>"), | |
93 | ct->manifest ? ct->manifest : (SOSManifestRef)CFSTR("NonConcrete"), | |
94 | CFArrayGetCount(ct->changeChildren), CFArrayGetCount(ct->manifestChildren)); | |
95 | return desc; | |
96 | } | |
97 | ||
98 | static void SOSChangeTrackerDestroy(CFTypeRef cf) { | |
99 | SOSChangeTrackerRef ct = (SOSChangeTrackerRef)cf; | |
100 | CFReleaseSafe(ct->manifest); | |
101 | CFReleaseSafe(ct->changeChildren); | |
102 | CFReleaseSafe(ct->manifestChildren); | |
103 | } | |
104 | ||
105 | // Even though SOSChangeTracker instances are used as keys in dictionaries, they are treated as pointers when used as such | |
106 | // which is fine since the engine ensures instances are singletons. | |
107 | CFGiblisFor(SOSChangeTracker); | |
108 | ||
109 | SOSChangeTrackerRef SOSChangeTrackerCreate(CFAllocatorRef allocator, bool isConcrete, CFArrayRef changeChildren, CFErrorRef *error) { | |
110 | SOSChangeTrackerRef ct = NULL; | |
111 | ct = CFTypeAllocate(SOSChangeTracker, struct __OpaqueSOSChangeTracker, allocator); | |
112 | if (ct && isConcrete) { | |
113 | ct->manifest = SOSManifestCreateWithData(NULL, error); | |
114 | if (!ct->manifest) | |
115 | CFReleaseNull(ct); | |
116 | } | |
117 | if (ct) { | |
118 | if (changeChildren) | |
119 | ct->changeChildren = CFArrayCreateMutableCopy(kCFAllocatorDefault, 0, changeChildren); | |
120 | else | |
121 | ct->changeChildren = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault); | |
122 | ct->manifestChildren = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault); | |
123 | } | |
124 | ||
125 | return ct; | |
126 | } | |
127 | ||
128 | // Change the concreteness of the current ct (a non concrete ct does not support SOSChangeTrackerCopyManifest(). | |
129 | void SOSChangeTrackerSetConcrete(SOSChangeTrackerRef ct, bool isConcrete) { | |
130 | if (!isConcrete) | |
131 | CFReleaseNull(ct->manifest); | |
132 | else if (!ct->manifest) { | |
133 | ct->manifest = SOSManifestCreateWithData(NULL, NULL); | |
134 | } | |
135 | } | |
136 | ||
137 | // Add a child to the current ct | |
138 | void SOSChangeTrackerRegisterChangeUpdate(SOSChangeTrackerRef ct, SOSChangeTrackerUpdatesChanges child) { | |
139 | CFArrayAppendValue(ct->changeChildren, child); | |
140 | } | |
141 | ||
142 | void SOSChangeTrackerRegisterManifestUpdate(SOSChangeTrackerRef ct, SOSChangeTrackerUpdatesManifests child) { | |
143 | CFArrayAppendValue(ct->manifestChildren, child); | |
144 | } | |
145 | ||
146 | void SOSChangeTrackerResetRegistration(SOSChangeTrackerRef ct) { | |
147 | CFArrayRemoveAllValues(ct->changeChildren); | |
148 | CFArrayRemoveAllValues(ct->manifestChildren); | |
149 | } | |
150 | ||
151 | void SOSChangeTrackerSetManifest(SOSChangeTrackerRef ct, SOSManifestRef manifest) { | |
152 | CFRetainAssign(ct->manifest, manifest); | |
153 | } | |
154 | ||
155 | SOSManifestRef SOSChangeTrackerCopyManifest(SOSChangeTrackerRef ct, CFErrorRef *error) { | |
156 | if (ct->manifest) { | |
157 | return (SOSManifestRef)CFRetain(ct->manifest); | |
158 | } | |
159 | SOSErrorCreate(kSOSErrorNotConcreteError, error, NULL, CFSTR("ChangeTracker is not concrete")); | |
160 | return NULL; | |
161 | } | |
162 | ||
163 | static bool SOSChangeTrackerCreateManifestsWithChanges(SOSEngineRef engine, CFArrayRef changes, SOSManifestRef *removals, SOSManifestRef *additions, CFErrorRef *error) { | |
164 | bool ok = true; | |
165 | struct SOSDigestVector dvdels = SOSDigestVectorInit; | |
166 | struct SOSDigestVector dvadds = SOSDigestVectorInit; | |
167 | struct SOSDigestVector *dv; | |
168 | CFTypeRef change; | |
169 | CFArrayForEachC(changes, change) { | |
170 | CFDataRef digest, allocatedDigest = NULL; | |
171 | if (isArray(change)) { | |
172 | assert(CFArrayGetCount(change) == 1); | |
173 | change = CFArrayGetValueAtIndex(change, 0); | |
174 | dv = &dvdels; | |
175 | } else { | |
176 | dv = &dvadds; | |
177 | } | |
178 | ||
179 | if (isData(change)) { | |
180 | digest = (CFDataRef)change; | |
181 | } else { | |
182 | CFErrorRef digestError = NULL; | |
183 | digest = allocatedDigest = SOSObjectCopyDigest(SOSEngineGetDataSource(engine), (SOSObjectRef)change, &digestError); | |
184 | if (!digest) { | |
185 | secerror("change %@ SOSObjectCopyDigest: %@", change, digestError); | |
186 | CFReleaseNull(digestError); | |
187 | continue; | |
188 | } | |
189 | } | |
190 | ||
191 | if (CFDataGetLength(digest) == 20) { | |
192 | SOSDigestVectorAppend(dv, CFDataGetBytePtr(digest)); | |
193 | } else { | |
194 | secerror("change %@ bad length digest: %@", change, digest); | |
195 | } | |
196 | CFReleaseNull(allocatedDigest); | |
197 | } | |
198 | if (ok && removals) | |
199 | ok = *removals = SOSManifestCreateWithDigestVector(&dvdels, error); | |
200 | if (ok && additions) | |
201 | ok = *additions = SOSManifestCreateWithDigestVector(&dvadds, error); | |
202 | ||
203 | SOSDigestVectorFree(&dvadds); | |
204 | SOSDigestVectorFree(&dvdels); | |
205 | ||
206 | return ok; | |
207 | } | |
208 | ||
209 | bool SOSChangeTrackerTrackChanges(SOSChangeTrackerRef ct, SOSEngineRef engine, SOSTransactionRef txn, SOSDataSourceTransactionSource source, SOSDataSourceTransactionPhase phase, CFArrayRef changes, CFErrorRef *error) { | |
210 | bool ok = true; | |
211 | if (changes && CFArrayGetCount(changes)) { | |
212 | CFStringRef changesDesc = SOSChangesCopyDescription(changes); | |
866f8763 A |
213 | secnotice("tracker", "%@ %s %s changes: %@", ct, phase == kSOSDataSourceTransactionWillCommit ? "will-commit" : phase == kSOSDataSourceTransactionDidCommit ? "did-commit" : "did-rollback", |
214 | source == kSOSDataSourceSOSTransaction ? "sos" : | |
215 | source == kSOSDataSourceCKKSTransaction ? "ckks" : | |
216 | source == kSOSDataSourceAPITransaction ? "api" : | |
217 | "unknown", | |
218 | changesDesc); | |
5c19dc3a A |
219 | CFReleaseSafe(changesDesc); |
220 | if (ct->manifest || ct->manifestChildren) { | |
221 | SOSManifestRef additions = NULL; | |
222 | SOSManifestRef removals = NULL; | |
223 | ok &= SOSChangeTrackerCreateManifestsWithChanges(engine, changes, &removals, &additions, error); | |
224 | if (ok) { | |
225 | if (ct->manifest) { | |
226 | SOSManifestRef updatedManifest = SOSManifestCreateWithPatch(ct->manifest, removals, additions, error); | |
227 | if (updatedManifest){ | |
228 | CFTransferRetained(ct->manifest, updatedManifest); | |
229 | } | |
230 | } | |
231 | if (ct->manifestChildren) { | |
232 | SOSChangeTrackerUpdatesManifests child; | |
233 | CFArrayForEachC(ct->manifestChildren, child) { | |
234 | ok = ok && child(ct, engine, txn, source, phase, removals, additions, error); | |
235 | } | |
236 | } | |
237 | } | |
238 | CFReleaseSafe(removals); | |
239 | CFReleaseSafe(additions); | |
240 | // TODO: Potentially filter changes to eliminate any changes that were already in our manifest | |
241 | // Backup Peers and the like would probably enjoy this so they don't have to do it themselves. | |
242 | } | |
243 | ||
244 | if (ct->changeChildren) { | |
245 | SOSChangeTrackerUpdatesChanges child; | |
246 | CFArrayForEachC(ct->changeChildren, child) { | |
247 | ok = ok && child(ct, engine, txn, source, phase, changes, error); | |
248 | } | |
249 | } | |
250 | } | |
251 | ||
252 | return ok; | |
253 | } | |
254 |