Skip to main content

walletkit_core/flamingo/
errors.rs

1use flamingo_verifier_sealed_types::{
2    ComparisonRole, FailureReason, ImageFailureReason, ImageRole, InputFailureReason,
3    ValidationTarget,
4};
5use thiserror::Error;
6
7/// A rejection reported inside encryption; not a signed statement.
8#[derive(Debug, Clone, Copy, PartialEq, Eq, uniffi::Enum)]
9pub enum FlamingoMatchRejection {
10    /// Malformed encrypted request.
11    MalformedInputs,
12    /// Invalid PCP hashes file.
13    InvalidHashesJson,
14    /// Orb image did not match its PCP commitment.
15    ThumbnailHashMismatch,
16    /// Threshold was not a finite normalized cosine value.
17    InvalidThreshold,
18    /// Exact malformed-input reason and the location/limit supplied by the service.
19    InputRejected {
20        /// Failed constraint.
21        reason: FlamingoInputFailureReason,
22        /// Semantic image role, if supplied.
23        image: Option<FlamingoImageRole>,
24        /// Size limit, when supplied.
25        limit_bytes: Option<u64>,
26    },
27    /// A comparison did not meet the threshold.
28    MatchBelowThreshold {
29        /// The comparison that failed.
30        comparison: FlamingoComparison,
31    },
32    /// A named image could not pass analysis.
33    ImageRejected {
34        /// Image bytes or semantic image role.
35        image: FlamingoImageRole,
36        /// Approved validation reason.
37        reason: FlamingoImageFailureReason,
38        /// Frame/pair target, if the failure came from validation.
39        target: Option<FlamingoValidationTarget>,
40    },
41    /// A named comparison failed.
42    MatchingFailed {
43        /// The comparison that failed.
44        comparison: FlamingoComparison,
45    },
46    /// An infrastructure failure, not a biological rejection.
47    Internal,
48}
49/// Comparison names match the worker protocol.
50#[derive(Debug, Clone, Copy, PartialEq, Eq, uniffi::Enum)]
51pub enum FlamingoComparison {
52    /// Orb credential versus live selfie.
53    OrbSelfie,
54    /// Orb credential versus RTMS challenge.
55    OrbChallenge,
56    /// Live selfie versus RTMS challenge.
57    SelfieChallenge,
58}
59/// Image roles match the worker protocol.
60#[derive(Debug, Clone, Copy, PartialEq, Eq, uniffi::Enum)]
61pub enum FlamingoImageRole {
62    /// Orb credential image.
63    OrbCredential,
64    /// Live capture.
65    LiveSelfie,
66    /// RTMS challenge image.
67    RtmsChallenge,
68}
69/// Failures while configuring or performing a match request.
70#[derive(Debug, Error, uniffi::Error)]
71pub enum FlamingoError {
72    /// A caller-supplied value cannot form a valid match request.
73    #[error("invalid {attribute}: {reason}")]
74    InvalidInput {
75        /// Name of the invalid field.
76        attribute: String,
77        /// Why the value was rejected.
78        reason: String,
79        /// Stable constraint code; applications need not parse the message.
80        kind: FlamingoInputFailureKind,
81        /// Byte limit when applicable.
82        limit_bytes: Option<u64>,
83    },
84    /// The verifier configuration was not valid.
85    #[error("invalid Flamingo verifier configuration: {0}")]
86    Configuration(String),
87    /// The host returned a machine-readable service error.
88    #[error("Flamingo service error ({code})")]
89    Service {
90        /// Exact host code.
91        code: String,
92        /// Host retry hint, not an automatic retry policy.
93        allow_retry: bool,
94    },
95    /// Configured exchange deadline expired.
96    #[error("Flamingo request timed out")]
97    Timeout,
98    /// Connection closed, handshake or network I/O failed.
99    #[error("Flamingo transport failed: {details}")]
100    Transport {
101        /// Supplementary diagnostic text.
102        details: String,
103    },
104    /// Assignment, host message or decrypted result was malformed.
105    #[error("invalid Flamingo response at {stage:?}")]
106    InvalidResponse {
107        /// Protocol stage.
108        stage: FlamingoResponseStage,
109    },
110    /// Signing-key attestation did not verify.
111    #[error("Flamingo attestation failed: {details}")]
112    Attestation {
113        /// Supplementary diagnostic text.
114        details: String,
115    },
116    /// Channel attestation, binding, sealing or opening failed.
117    #[error("Flamingo channel failed: {details}")]
118    Channel {
119        /// Supplementary diagnostic text.
120        details: String,
121    },
122    /// Attested signing key was invalid.
123    #[error("invalid Flamingo signing key")]
124    InvalidSigningKey,
125    /// Token signature or request commitments did not verify.
126    #[error("invalid Flamingo match statement")]
127    StatementInvalid,
128    /// The one internal reassignment retry was exhausted.
129    #[error("Flamingo reassignment retry exhausted")]
130    ReassignmentRequired,
131}
132
133impl From<FailureReason> for FlamingoMatchRejection {
134    fn from(value: FailureReason) -> Self {
135        match value {
136            FailureReason::MalformedInputs => Self::MalformedInputs,
137            FailureReason::InvalidHashesJson => Self::InvalidHashesJson,
138            FailureReason::ThumbnailHashMismatch => Self::ThumbnailHashMismatch,
139            FailureReason::InvalidThreshold => Self::InvalidThreshold,
140            FailureReason::InputRejected {
141                reason,
142                image,
143                limit_bytes,
144            } => Self::InputRejected {
145                reason: reason.into(),
146                image: image.map(Into::into),
147                limit_bytes,
148            },
149            FailureReason::Internal => Self::Internal,
150            FailureReason::MatchBelowThreshold(comparison) => {
151                Self::MatchBelowThreshold {
152                    comparison: comparison.into(),
153                }
154            }
155            FailureReason::MatchingFailed(comparison) => Self::MatchingFailed {
156                comparison: comparison.into(),
157            },
158            FailureReason::ImageRejected {
159                image,
160                reason,
161                target,
162            } => Self::ImageRejected {
163                image: image.into(),
164                reason: reason.into(),
165                target: target.map(Into::into),
166            },
167        }
168    }
169}
170impl From<ComparisonRole> for FlamingoComparison {
171    fn from(value: ComparisonRole) -> Self {
172        match value {
173            ComparisonRole::OrbSelfie => Self::OrbSelfie,
174            ComparisonRole::OrbChallenge => Self::OrbChallenge,
175            ComparisonRole::SelfieChallenge => Self::SelfieChallenge,
176        }
177    }
178}
179impl From<ImageRole> for FlamingoImageRole {
180    fn from(value: ImageRole) -> Self {
181        match value {
182            ImageRole::OrbCredential => Self::OrbCredential,
183            ImageRole::LiveSelfie => Self::LiveSelfie,
184            ImageRole::RtmsChallenge => Self::RtmsChallenge,
185        }
186    }
187}
188/// Image rejection reasons, without raw engine diagnostics.
189#[derive(Debug, Clone, Copy, PartialEq, Eq, uniffi::Enum)]
190pub enum FlamingoImageFailureReason {
191    /// Image decoding or dimension validation failed.
192    InvalidImage,
193    /// Embedding generation failed.
194    TemplateFailed,
195    /// Too many faces.
196    TooManyFaces,
197    /// Image too dark.
198    ImageTooDark,
199    /// Image too bright.
200    ImageTooBright,
201    /// Illumination variance.
202    IlluminationVariance,
203    /// Face too small.
204    FaceTooSmall,
205    /// Face too big.
206    FaceTooBig,
207    /// Face resolution too low.
208    FaceResolutionTooLow,
209    /// Face too high.
210    FaceTooHigh,
211    /// Face too low.
212    FaceTooLow,
213    /// Face too far left.
214    FaceTooFarLeft,
215    /// Face too far right.
216    FaceTooFarRight,
217    /// Head pose yaw.
218    HeadPoseYaw,
219    /// Head pose pitch too high.
220    HeadPosePitchTooHigh,
221    /// Head pose pitch too low.
222    HeadPosePitchTooLow,
223    /// Head pose roll.
224    HeadPoseRoll,
225    /// Low quality.
226    LowQuality,
227    /// Sunglasses occlusion detected.
228    SunglassesOcclusionDetected,
229    /// Glasses occlusion detected.
230    GlassesOcclusionDetected,
231    /// Mask occlusion detected.
232    MaskOcclusionDetected,
233    /// Other occlusion detected.
234    OtherOcclusionDetected,
235    /// Hair occlusion detected.
236    HairOcclusionDetected,
237    /// Fas occlusion detected.
238    FasOcclusionDetected,
239    /// Spoof detected.
240    SpoofDetected,
241    /// Depth spoof detected.
242    DepthSpoofDetected,
243    /// Thermal spoof detected.
244    ThermalSpoofDetected,
245    /// Age below threshold.
246    AgeBelowThreshold,
247    /// No face detected.
248    NoFaceDetected,
249    /// Eyes closed.
250    EyesClosed,
251    /// Non neutral expression.
252    NonNeutralExpression,
253    /// Landmarks alignment.
254    LandmarksAlignment,
255    /// Face overexposed.
256    FaceOverexposed,
257    /// Face underexposed.
258    FaceUnderexposed,
259    /// Segmentation occlusion proportion.
260    SegmentationOcclusionProportion,
261    /// Bright artifacts.
262    BrightArtifacts,
263    /// Light guard score too low.
264    LightGuardScoreTooLow,
265    /// Low contrast.
266    LowContrast,
267    /// Mesh expression score.
268    MeshExpressionScore,
269    /// High color distortion.
270    HighColorDistortion,
271    /// Uneven lighting.
272    UnevenLighting,
273    /// Blurry face.
274    BlurryFace,
275    /// Noisy thermal image.
276    NoisyThermalImage,
277}
278impl From<ImageFailureReason> for FlamingoImageFailureReason {
279    fn from(value: ImageFailureReason) -> Self {
280        match value {
281            ImageFailureReason::InvalidImage => Self::InvalidImage,
282            ImageFailureReason::TemplateFailed => Self::TemplateFailed,
283            ImageFailureReason::TooManyFaces => Self::TooManyFaces,
284            ImageFailureReason::ImageTooDark => Self::ImageTooDark,
285            ImageFailureReason::ImageTooBright => Self::ImageTooBright,
286            ImageFailureReason::IlluminationVariance => Self::IlluminationVariance,
287            ImageFailureReason::FaceTooSmall => Self::FaceTooSmall,
288            ImageFailureReason::FaceTooBig => Self::FaceTooBig,
289            ImageFailureReason::FaceResolutionTooLow => Self::FaceResolutionTooLow,
290            ImageFailureReason::FaceTooHigh => Self::FaceTooHigh,
291            ImageFailureReason::FaceTooLow => Self::FaceTooLow,
292            ImageFailureReason::FaceTooFarLeft => Self::FaceTooFarLeft,
293            ImageFailureReason::FaceTooFarRight => Self::FaceTooFarRight,
294            ImageFailureReason::HeadPoseYaw => Self::HeadPoseYaw,
295            ImageFailureReason::HeadPosePitchTooHigh => Self::HeadPosePitchTooHigh,
296            ImageFailureReason::HeadPosePitchTooLow => Self::HeadPosePitchTooLow,
297            ImageFailureReason::HeadPoseRoll => Self::HeadPoseRoll,
298            ImageFailureReason::LowQuality => Self::LowQuality,
299            ImageFailureReason::SunglassesOcclusionDetected => {
300                Self::SunglassesOcclusionDetected
301            }
302            ImageFailureReason::GlassesOcclusionDetected => {
303                Self::GlassesOcclusionDetected
304            }
305            ImageFailureReason::MaskOcclusionDetected => Self::MaskOcclusionDetected,
306            ImageFailureReason::OtherOcclusionDetected => Self::OtherOcclusionDetected,
307            ImageFailureReason::HairOcclusionDetected => Self::HairOcclusionDetected,
308            ImageFailureReason::FasOcclusionDetected => Self::FasOcclusionDetected,
309            ImageFailureReason::SpoofDetected => Self::SpoofDetected,
310            ImageFailureReason::DepthSpoofDetected => Self::DepthSpoofDetected,
311            ImageFailureReason::ThermalSpoofDetected => Self::ThermalSpoofDetected,
312            ImageFailureReason::AgeBelowThreshold => Self::AgeBelowThreshold,
313            ImageFailureReason::NoFaceDetected => Self::NoFaceDetected,
314            ImageFailureReason::EyesClosed => Self::EyesClosed,
315            ImageFailureReason::NonNeutralExpression => Self::NonNeutralExpression,
316            ImageFailureReason::LandmarksAlignment => Self::LandmarksAlignment,
317            ImageFailureReason::FaceOverexposed => Self::FaceOverexposed,
318            ImageFailureReason::FaceUnderexposed => Self::FaceUnderexposed,
319            ImageFailureReason::SegmentationOcclusionProportion => {
320                Self::SegmentationOcclusionProportion
321            }
322            ImageFailureReason::BrightArtifacts => Self::BrightArtifacts,
323            ImageFailureReason::LightGuardScoreTooLow => Self::LightGuardScoreTooLow,
324            ImageFailureReason::LowContrast => Self::LowContrast,
325            ImageFailureReason::MeshExpressionScore => Self::MeshExpressionScore,
326            ImageFailureReason::HighColorDistortion => Self::HighColorDistortion,
327            ImageFailureReason::UnevenLighting => Self::UnevenLighting,
328            ImageFailureReason::BlurryFace => Self::BlurryFace,
329            ImageFailureReason::NoisyThermalImage => Self::NoisyThermalImage,
330        }
331    }
332}
333
334/// Capture validation targets exported to mobile callers.
335#[derive(Debug, Clone, Copy, PartialEq, Eq, uniffi::Enum)]
336pub enum FlamingoValidationTarget {
337    /// A single image.
338    Image,
339    /// The illuminated frame.
340    IlluminatedFrame,
341    /// The unilluminated frame.
342    UnilluminatedFrame,
343    /// The complete challenge-response pair.
344    LightGuardPair,
345}
346impl From<ValidationTarget> for FlamingoValidationTarget {
347    fn from(value: ValidationTarget) -> Self {
348        match value {
349            ValidationTarget::Image => Self::Image,
350            ValidationTarget::IlluminatedFrame => Self::IlluminatedFrame,
351            ValidationTarget::UnilluminatedFrame => Self::UnilluminatedFrame,
352            ValidationTarget::LightGuardPair => Self::LightGuardPair,
353        }
354    }
355}
356
357/// Worker input constraints exported to mobile callers.
358#[derive(Debug, Clone, Copy, PartialEq, Eq, uniffi::Enum)]
359pub enum FlamingoInputFailureReason {
360    /// A required image is missing.
361    MissingImage,
362    /// A capture source is missing.
363    MissingSource,
364    /// The selected matching frame is invalid.
365    InvalidMatchingFrame,
366    /// An image buffer is empty.
367    EmptyImage,
368    /// One image exceeded its size limit.
369    ImageTooLarge,
370    /// All image bytes exceeded the combined limit.
371    TotalImagesTooLarge,
372}
373impl From<InputFailureReason> for FlamingoInputFailureReason {
374    fn from(value: InputFailureReason) -> Self {
375        match value {
376            InputFailureReason::MissingImage => Self::MissingImage,
377            InputFailureReason::MissingSource => Self::MissingSource,
378            InputFailureReason::InvalidMatchingFrame => Self::InvalidMatchingFrame,
379            InputFailureReason::EmptyImage => Self::EmptyImage,
380            InputFailureReason::ImageTooLarge => Self::ImageTooLarge,
381            InputFailureReason::TotalImagesTooLarge => Self::TotalImagesTooLarge,
382        }
383    }
384}
385
386/// Local input constraints exported to mobile callers.
387#[derive(Debug, Clone, Copy, PartialEq, Eq, uniffi::Enum)]
388pub enum FlamingoInputFailureKind {
389    /// The field is empty.
390    Empty,
391    /// The field exceeded its size limit.
392    TooLarge,
393    /// The images exceeded the combined limit.
394    TotalTooLarge,
395    /// The threshold is nonfinite or outside `[0, 1]`.
396    InvalidThreshold,
397}
398
399/// Response stages exported to mobile callers.
400#[derive(Debug, Clone, Copy, PartialEq, Eq, uniffi::Enum)]
401pub enum FlamingoResponseStage {
402    /// The assignment document or public key.
403    Assignment,
404    /// A host protocol message.
405    HostMessage,
406    /// The decrypted match response.
407    MatchResult,
408}