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