-
Notifications
You must be signed in to change notification settings - Fork 50
Expand file tree
/
Copy pathFingerprintTransparency.cs
More file actions
277 lines (269 loc) · 12.9 KB
/
Copy pathFingerprintTransparency.cs
File metadata and controls
277 lines (269 loc) · 12.9 KB
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
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
// Part of SourceAFIS for .NET: https://sourceafis.machinezoo.com/net
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SourceAFIS.Engine.Features;
using SourceAFIS.Engine.Matcher;
using SourceAFIS.Engine.Primitives;
using SourceAFIS.Engine.Transparency;
// TODO: Consistent minutia type serialization. Emulate Java serialization using custom serializer.
// TODO: Port transparency ZIP from Java.
// TODO: Deterministic build & symbols nuget package. See report here: https://nuget.info/packages/SourceAFIS
// TODO: Port sourceafis-transparency from Java.
// TODO: Port sourceafis-visualization from Java.
namespace SourceAFIS
{
/// <summary>Algorithm transparency API that can capture all intermediate data structures produced by SourceAFIS algorithm.</summary>
/// <remarks>
/// <para>
/// See <see href="https://sourceafis.machinezoo.com/transparency/">algorithm transparency</see> pages
/// on SourceAFIS website for more information and a tutorial on how to use this class.
/// </para>
/// <para>
/// Applications can subclass <c>FingerprintTransparency</c> and override
/// <see cref="Take(string,string,byte[])" /> method to define new transparency data logger.
/// Applications can control what transparency data gets produced by overriding <see cref="Accepts(string)" />.
/// </para>
/// <para>
/// <c>FingerprintTransparency</c> instance should be created in a <c>using</c> statement.
/// It will be capturing transparency data from all operations on current thread
/// between invocation of the constructor and invocation of <see cref="Dispose()" /> method,
/// which is called automatically by the <c>using</c> statement.
/// </para>
/// </remarks>
/// <seealso href="https://sourceafis.machinezoo.com/transparency/">Algorithm transparency in SourceAFIS</seealso>
public abstract partial class FingerprintTransparency : IDisposable
{
FingerprintTransparency outer;
bool disposed;
[ThreadStatic]
static FingerprintTransparency current;
internal static FingerprintTransparency Current => current ?? NoTransparency.Instance;
/// <summary>Creates an instance of <c>FingerprintTransparency</c> and activates it.</summary>
/// <remarks>
/// <para>
/// Activation places the new <c>FingerprintTransparency</c> instance in thread-local storage,
/// which causes all operations executed by current thread to log data to this <c>FingerprintTransparency</c> instance.
/// If activations are nested, data is only logged to the currently innermost <c>FingerprintTransparency</c>.
/// </para>
/// <para>
/// Deactivation happens in <see cref="Dispose()" /> method.
/// Instances of <c>FingerprintTransparency</c> should be created in <c>using</c> statement
/// to ensure that <see cref="Dispose()" /> is always called.
/// </para>
/// <para>
/// <c>FingerprintTransparency</c> is an abstract class.
/// This constructor is only called by subclasses.
/// </para>
/// </remarks>
/// <seealso cref="Dispose()" />
protected FingerprintTransparency()
{
outer = current;
current = this;
}
/// <summary>Deactivates transparency logging and releases system resources held by this instance if any.</summary>
/// <remarks>
/// <para>
/// This method is normally called automatically when <c>FingerprintTransparency</c> is used in <c>using</c> statement.
/// </para>
/// <para>
/// Deactivation stops transparency data logging to this instance of <c>FingerprintTransparency</c>.
/// Logging thus takes place between invocation of constructor (<see cref="FingerprintTransparency()" />) and invocation of this method.
/// If activations were nested, this method reactivates the outer <c>FingerprintTransparency</c>.
/// </para>
/// <para>
/// Subclasses can override this method to perform cleanup.
/// Default implementation of this method performs deactivation.
/// It must be called by overriding methods for deactivation to work correctly.
/// </para>
/// </remarks>
/// <seealso cref="FingerprintTransparency()" />
public void Dispose()
{
if (!disposed)
{
disposed = true;
current = outer;
outer = null;
}
}
/// <summary>Filters transparency data keys that can be passed to <see cref="Take(string,string,byte[])" />.</summary>
/// <remarks>
/// <para>
/// Default implementation always returns <c>true</c>, i.e. all transparency data is passed to <see cref="Take(string,string,byte[])" />.
/// Implementation can override this method to filter some keys out, which improves performance.
/// </para>
/// <para>
/// This method should always return the same result for the same key.
/// Result may be cached and this method might not be called every time something is about to be logged.
/// </para>
/// </remarks>
/// <param name="key">Transparency data key as used in <see cref="Take(string,string,byte[])" />.</param>
/// <returns>Boolean status indicating whether transparency data under given key should be logged.</returns>
/// <seealso cref="Take(string,string,byte[])" />
public virtual bool Accepts(string key) => true;
/// <summary>Records transparency data.</summary>
/// <remarks>
/// <para>
/// Subclasses must override this method, because the default implementation does nothing.
/// While this <c>FingerprintTransparency</c> object is active (between call to the constructor and call to <see cref="Dispose()" />),
/// this method is called with transparency data in its parameters.
/// </para>
/// <para>
/// Parameter <paramref name="key" /> specifies the kind of transparency data being logged,
/// usually corresponding to some stage in the algorithm.
/// Parameter <paramref name="data" /> then contains the actual transparency data.
/// This method may be called multiple times with the same <paramref name="key" />
/// if the algorithm produces that kind of transparency data repeatedly.
/// See <see href="https://sourceafis.machinezoo.com/transparency/">algorithm transparency</see>
/// on SourceAFIS website for documentation of the structure of the transparency data.
/// </para>
/// <para>
/// Transparency data is offered only if <see cref="Accepts(string)" /> returns <c>true</c> for the same <paramref name="key" />.
/// This allows applications to efficiently collect only transparency data that is actually needed.
/// </para>
/// <para>
/// MIME type of the transparency data is provided, which may be useful for generic implementations,
/// for example transparency data browser app that changes type of visualization based on the MIME type.
/// Most transparency data is serialized in <see href="https://cbor.io/">CBOR</see> format (MIME application/cbor).
/// </para>
/// <para>
/// Implementations of this method should be synchronized. Although the current SourceAFIS algorithm is single-threaded,
/// future versions of SourceAFIS might run some parts of the algorithm in parallel, which would result in concurrent calls to this method.
/// </para>
/// <para>
/// If this method throws, exception is propagated through SourceAFIS code.
/// </para>
/// </remarks>
/// <param name="key">Specifies the kind of transparency data being logged.</param>
/// <param name="mime">MIME type of the transparency data in <paramref name="data" /> parameter.</param>
/// <param name="data">Transparency data being logged.</param>
/// <seealso href="https://sourceafis.machinezoo.com/transparency/">Algorithm transparency in SourceAFIS</seealso>
/// <seealso cref="Accepts(string)" />
public virtual void Take(string key, string mime, byte[] data) { }
volatile bool versionOffered;
void LogVersion()
{
if (!versionOffered)
{
bool offer = false;
lock (this)
{
if (!versionOffered)
{
versionOffered = true;
offer = true;
}
}
if (offer && Accepts("version"))
Take("version", "text/plain", Encoding.UTF8.GetBytes(FingerprintCompatibility.Version));
}
}
void Log(string key, string mime, Func<byte[]> supplier)
{
LogVersion();
if (Accepts(key))
{
try
{
Take(key, mime, supplier());
}
catch (IndexOutOfRangeException)
{
// Workaround for a bug in CBOR serializer that throws exceptions when it encounters empty array.
}
}
}
internal void Log<T>(string key, Func<T> supplier) => Log(key, "application/cbor", () => SerializationUtils.Serialize(supplier()));
internal void Log(string key, object data) => Log(key, "application/cbor", () => SerializationUtils.Serialize(data));
internal void LogSkeleton(string keyword, Skeleton skeleton) => Log(skeleton.Type.Prefix() + keyword, () => ConsistentSkeleton.Of(skeleton));
// https://sourceafis.machinezoo.com/transparency/edge-hash
internal void LogEdgeHash(Dictionary<int, List<IndexedEdge>> hash)
{
Log("edge-hash", () => (
from k in hash.Keys
orderby k
select new ConsistentHashEntry(k, hash[k])
).ToList());
}
volatile bool matcherOffered;
volatile bool acceptsRootPairs;
volatile bool acceptsPairing;
volatile bool acceptsBestPairing;
volatile bool acceptsScore;
volatile bool acceptsBestScore;
volatile bool acceptsBestMatch;
void OfferMatcher()
{
if (!matcherOffered)
{
acceptsRootPairs = Accepts("root-pairs");
acceptsPairing = Accepts("pairing");
acceptsBestPairing = Accepts("best-pairing");
acceptsScore = Accepts("score");
acceptsBestScore = Accepts("best-score");
acceptsBestMatch = Accepts("best-match");
matcherOffered = true;
}
}
// Expose fast method to check whether pairing should be logged, so that we can easily skip support edge logging.
// Do the same for best score, so that we can skip reevaluation of the best pairing.
internal bool AcceptsPairing()
{
OfferMatcher();
return acceptsPairing;
}
internal bool AcceptsBestPairing()
{
OfferMatcher();
return acceptsBestPairing;
}
internal bool AcceptsBestScore()
{
OfferMatcher();
return acceptsBestScore;
}
// https://sourceafis.machinezoo.com/transparency/roots
internal void LogRootPairs(int count, MinutiaPair[] roots)
{
OfferMatcher();
if (acceptsRootPairs)
Log("roots", () => (from p in roots select new ConsistentMinutiaPair(p.Probe, p.Candidate)).Take(count).ToList());
}
// https://sourceafis.machinezoo.com/transparency/pairing
internal void LogPairing(PairingGraph pairing)
{
OfferMatcher();
if (acceptsPairing)
Log("pairing", new ConsistentPairingGraph(pairing.Count, pairing.Tree, pairing.SupportEdges));
}
internal void LogBestPairing(PairingGraph pairing)
{
OfferMatcher();
if (acceptsBestPairing)
Log("best-pairing", new ConsistentPairingGraph(pairing.Count, pairing.Tree, pairing.SupportEdges));
}
// https://sourceafis.machinezoo.com/transparency/score
internal void LogScore(ScoringData score)
{
OfferMatcher();
if (acceptsScore)
Log("score", score);
}
internal void LogBestScore(ScoringData score)
{
OfferMatcher();
if (acceptsBestScore)
Log("best-score", score);
}
// https://sourceafis.machinezoo.com/transparency/best-match
internal void LogBestMatch(int nth)
{
OfferMatcher();
if (acceptsBestMatch)
Take("best-match", "text/plain", Encoding.UTF8.GetBytes(nth.ToString()));
}
}
}