1
//! Permission manager — the cross-platform piece of the "permission-as-DOM"
2
//! architecture (`SUPER_PLAN_2.md` §1.5 and `scripts/research/08_permission_dom_nodes.md`).
3
//!
4
//! Stores per-capability state + a refcount keyed on bearing DOM nodes. Three
5
//! callers drive it:
6
//!
7
//! - The **layout pass** scans the styled DOM for permission-bearing
8
//!   NodeTypes (`GeolocationProbe`, `CameraPreview`, `SensorProbe`, etc.) and
9
//!   calls `subscribe` / `release` to maintain the refcount. The diff
10
//!   between consecutive layouts yields the [`PermissionDiffEvent`]s the
11
//!   platform backend translates into native subscribe/release operations.
12
//!
13
//! - The **platform backend** (`dll/src/desktop/extra/permission/<plat>.rs`)
14
//!   observes the diff events and issues the matching native call
15
//!   (`AVCaptureDevice.requestAccess` on iOS, `ActivityCompat.requestPermissions`
16
//!   on Android, etc.). When the OS callback fires it calls `set_status`,
17
//!   which is mirrored back into callback land via the `CallbackInfo`
18
//!   accessor `get_permission_status`.
19
//!
20
//! - **Callbacks** read `get_status(...)` synchronously to decide whether
21
//!   to mount a permission-bearing node or show a fallback (the
22
//!   "user-gesture-first" pattern in the research brief §8.3).
23
//!
24
//! The manager has no platform dependencies and is `no_std`-friendly (uses
25
//! `alloc::collections::BTreeMap` + `alloc::vec::Vec`).
26

            
27
use alloc::collections::btree_map::BTreeMap;
28
use alloc::vec::Vec;
29

            
30
use azul_core::dom::DomNodeId;
31

            
32
/// One closed enum covering every capability the framework can request.
33
///
34
/// The variant set deliberately omits fields like `facing` / `accuracy` /
35
/// `mode` from the research brief — those parameters belong on the bearing
36
/// `NodeType` (e.g. `NodeType::CameraPreview(CameraSource::Front)`) so they
37
/// can change between layout passes without forcing a re-prompt. The
38
/// `Reconfigure` diff event carries the new params when a node mutates.
39
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
40
#[repr(C)]
41
pub enum Capability {
42
    /// Camera access (front or back, declared per node).
43
    Camera,
44
    /// Microphone access. iOS gates this separately from camera.
45
    Microphone,
46
    /// Entire-screen or per-window capture.
47
    ScreenCapture,
48
    /// Geolocation (precise vs approximate is per-node, not per-capability).
49
    Geolocation,
50
    /// Background geolocation. A separate iOS / Android permission gate.
51
    GeolocationBackground,
52
    /// FaceID / TouchID / Hello / `BiometricPrompt`.
53
    Biometric,
54
    /// Motion sensor data (accelerometer + gyro + magnetometer).
55
    Motion,
56
    /// PhotoKit / MediaStore read.
57
    PhotoLibrary,
58
    /// PhotoKit add-only / MediaStore write.
59
    PhotoLibraryWrite,
60
    /// Contacts list.
61
    Contacts,
62
    /// Calendar entries.
63
    Calendars,
64
    /// Reminders (iOS only — Android collapses into Calendars).
65
    Reminders,
66
    /// Push / local notification scheduling.
67
    Notifications,
68
    /// Bluetooth foreground.
69
    Bluetooth,
70
    /// Bluetooth background. Separate iOS Info.plist key + Android permission.
71
    BluetoothBackground,
72
    /// Nearby Wi-Fi (Android 13+).
73
    NearbyWifi,
74
    /// Local network multicast (iOS 14+).
75
    LocalNetwork,
76
    /// iOS App Tracking Transparency (`IDFA` consent, iOS 14.5+).
77
    AppTrackingTransparency,
78
}
79

            
80
/// Quality of a granted permission. Matches research/08 §2's quality split.
81
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
82
#[repr(C)]
83
pub enum PermissionQuality {
84
    /// Full: precise location, full photo library, etc.
85
    Full,
86
    /// Reduced: approximate location, "Selected Photos" partial access, etc.
87
    Reduced,
88
}
89

            
90
/// State machine the manager tracks per-capability.
91
///
92
/// The five canonical states (`NotDetermined` / `Requested` / `Granted` /
93
/// `Denied` / `Restricted`) cover what every supported platform reports.
94
/// `EphemeralGranted` is the iOS 14+ "Allow Once" / Android 11+ one-time grant
95
/// — semantically a Granted that the OS will reset to `NotDetermined` at the
96
/// next activity launch.
97
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
98
#[repr(C, u8)]
99
pub enum PermissionState {
100
    /// Initial — no prompt has been shown.
101
    NotDetermined,
102
    /// OS prompt is currently visible / in-flight.
103
    Requested,
104
    /// User granted access.
105
    Granted {
106
        quality: PermissionQuality,
107
    },
108
    /// User denied access (with or without "don't ask again").
109
    Denied,
110
    /// MDM / parental controls / kiosk policy blocks the prompt entirely.
111
    Restricted,
112
    /// iOS "Allow Once" / Android one-time. Reverts on next app launch.
113
    EphemeralGranted {
114
        until_app_close: bool,
115
    },
116
}
117

            
118
impl PermissionState {
119
    /// `true` if the capability is currently usable, regardless of quality.
120
2
    pub fn is_granted(self) -> bool {
121
        matches!(
122
2
            self,
123
            PermissionState::Granted { .. } | PermissionState::EphemeralGranted { .. }
124
        )
125
2
    }
126

            
127
    /// `true` if a re-prompt could plausibly flip this to `Granted`.
128
    pub fn could_re_prompt(self) -> bool {
129
        matches!(self, PermissionState::NotDetermined)
130
    }
131
}
132

            
133
/// Diff event emitted at the end of each layout pass for the platform
134
/// backend to translate into native subscribe / release / reconfigure calls.
135
///
136
/// `Subscribe` fires the first time a capability's refcount transitions from
137
/// zero to one (i.e. the first permission-bearing node of its kind appears).
138
/// `Release` fires when the refcount drops back to zero. `Reconfigure` is
139
/// reserved for in-place parameter changes (e.g. camera-facing front → back)
140
/// once `CameraPreview` lands as a NodeType — kept in the enum so platform
141
/// backends can ignore it cleanly until then.
142
#[derive(Debug, Clone, PartialEq, Eq)]
143
#[repr(C, u8)]
144
pub enum PermissionDiffEvent {
145
    /// First appearance of `capability` in the layout. Refcount went 0 → 1.
146
    Subscribe {
147
        capability: Capability,
148
        node_id: DomNodeId,
149
    },
150
    /// Last bearing node left the layout. Refcount went 1 → 0.
151
    Release {
152
        capability: Capability,
153
    },
154
    /// Reserved for future use — currently never emitted. The diff path will
155
    /// fire it once `CameraPreview` etc. land with parameter fields.
156
    Reconfigure {
157
        capability: Capability,
158
    },
159
}
160

            
161
/// Per-capability state held across frames.
162
///
163
/// `refcount` is the number of distinct DOM nodes currently in the layout
164
/// that subscribed to this capability. `last_subscriber` is the node that
165
/// caused the most recent 0 → 1 transition; the platform backend uses it
166
/// to anchor permission-related events back to a node (so an
167
/// `On::CameraPermissionDenied` callback fires on the right `CameraPreview`).
168
#[derive(Debug, Clone, PartialEq, Eq)]
169
pub struct CapabilityEntry {
170
    pub state: PermissionState,
171
    pub refcount: u32,
172
    pub last_subscriber: Option<DomNodeId>,
173
}
174

            
175
impl CapabilityEntry {
176
9
    fn new() -> Self {
177
9
        Self {
178
9
            state: PermissionState::NotDetermined,
179
9
            refcount: 0,
180
9
            last_subscriber: None,
181
9
        }
182
9
    }
183
}
184

            
185
/// Cross-platform permission manager.
186
///
187
/// One per `App` (capabilities live at process scope, not per-window — a
188
/// camera session backing two windows multiplexes via a single capture
189
/// stream; cf. research/08 §8.6). `LayoutWindow` holds a borrow / `Arc`
190
/// reference, not an owned copy.
191
#[derive(Debug, Clone, PartialEq, Eq, Default)]
192
pub struct PermissionManager {
193
    /// Latest known state + refcount per capability.
194
    pub statuses: BTreeMap<Capability, CapabilityEntry>,
195
    /// Diff events emitted since the last call to `take_pending_events`.
196
    ///
197
    /// Held as a queue so the platform backend can drain it once per frame
198
    /// instead of receiving callbacks during the layout pass itself (the
199
    /// layout pass is on a hot path that should not block on FFI).
200
    pending_events: Vec<PermissionDiffEvent>,
201
}
202

            
203
impl PermissionManager {
204
2329
    pub fn new() -> Self {
205
2329
        Self::default()
206
2329
    }
207

            
208
    /// Read the most recently observed state for `capability`.
209
4
    pub fn get_status(&self, capability: Capability) -> PermissionState {
210
4
        self.statuses
211
4
            .get(&capability)
212
4
            .map(|e| e.state)
213
4
            .unwrap_or(PermissionState::NotDetermined)
214
4
    }
215

            
216
    /// Record that `node_id` now needs `capability`. The first subscriber
217
    /// (refcount 0 → 1) enqueues a `Subscribe` event for the platform layer
218
    /// to translate into a native prompt.
219
7
    pub fn subscribe(&mut self, capability: Capability, node_id: DomNodeId) {
220
7
        let entry = self
221
7
            .statuses
222
7
            .entry(capability)
223
7
            .or_insert_with(CapabilityEntry::new);
224
7
        entry.last_subscriber = Some(node_id);
225
7
        entry.refcount = entry.refcount.saturating_add(1);
226
7
        if entry.refcount == 1 {
227
4
            self.pending_events.push(PermissionDiffEvent::Subscribe {
228
4
                capability,
229
4
                node_id,
230
4
            });
231
4
        }
232
7
    }
233

            
234
    /// Drop one subscription. The last release (refcount 1 → 0) enqueues a
235
    /// `Release` event so the platform backend can tear the session down.
236
3
    pub fn release(&mut self, capability: Capability) {
237
3
        let Some(entry) = self.statuses.get_mut(&capability) else {
238
            return;
239
        };
240
3
        if entry.refcount == 0 {
241
            return;
242
3
        }
243
3
        entry.refcount -= 1;
244
3
        if entry.refcount == 0 {
245
2
            entry.last_subscriber = None;
246
2
            self.pending_events
247
2
                .push(PermissionDiffEvent::Release { capability });
248
2
        }
249
3
    }
250

            
251
    /// Force `capability`'s refcount down to zero. Used by `recheck_all` when
252
    /// the OS revokes a permission out from under us — we have to tear down
253
    /// the subscription regardless of how many DOM nodes still reference it.
254
1
    pub fn force_release(&mut self, capability: Capability) {
255
1
        let Some(entry) = self.statuses.get_mut(&capability) else {
256
            return;
257
        };
258
1
        if entry.refcount == 0 {
259
            return;
260
1
        }
261
1
        entry.refcount = 0;
262
1
        entry.last_subscriber = None;
263
1
        self.pending_events
264
1
            .push(PermissionDiffEvent::Release { capability });
265
1
    }
266

            
267
    /// Platform backend writes the OS-observed state back into the manager.
268
    ///
269
    /// Returns true if the state actually changed — the caller can use this
270
    /// signal to mark the window dirty for relayout (so a permission-aware
271
    /// callback gets a chance to render the new state).
272
5
    pub fn set_status(&mut self, capability: Capability, state: PermissionState) -> bool {
273
5
        let entry = self
274
5
            .statuses
275
5
            .entry(capability)
276
5
            .or_insert_with(CapabilityEntry::new);
277
5
        if entry.state == state {
278
1
            return false;
279
4
        }
280
4
        entry.state = state;
281
4
        true
282
5
    }
283

            
284
    /// Drain queued diff events. Platform backend calls this once per frame.
285
13
    pub fn take_pending_events(&mut self) -> Vec<PermissionDiffEvent> {
286
13
        core::mem::take(&mut self.pending_events)
287
13
    }
288

            
289
    /// Refcount snapshot — primarily for diagnostics and tests.
290
9
    pub fn refcount(&self, capability: Capability) -> u32 {
291
9
        self.statuses
292
9
            .get(&capability)
293
9
            .map(|e| e.refcount)
294
9
            .unwrap_or(0)
295
9
    }
296

            
297
    /// Pre-compute the next-frame refcount map from a closure that yields
298
    /// `(capability, node_id)` pairs for every permission-bearing node in
299
    /// the current styled DOM. Then diff against the existing refcounts and
300
    /// enqueue the matching Subscribe / Release events.
301
    ///
302
    /// This is the entry point the layout pass calls. It exists as a closure
303
    /// rather than a direct `StyledDom` walker because `StyledDom` lives in
304
    /// `azul_core::styled_dom` and would otherwise force a (tiny) cycle.
305
5
    pub fn diff_layout<F>(&mut self, mut for_each_bearing_node: F)
306
5
    where
307
5
        F: FnMut(&mut dyn FnMut(Capability, DomNodeId)),
308
    {
309
        // 1. Drain the new layout into (capability → (count, first_node)).
310
5
        let mut next: BTreeMap<Capability, (u32, Option<DomNodeId>)> = BTreeMap::new();
311
5
        for_each_bearing_node(&mut |cap, node| {
312
3
            let slot = next.entry(cap).or_insert((0, None));
313
3
            slot.0 = slot.0.saturating_add(1);
314
3
            if slot.1.is_none() {
315
3
                slot.1 = Some(node);
316
3
            }
317
3
        });
318

            
319
        // 2. Compute the new state map from the old one + the next layout.
320
        // Iterate every capability we know about plus any new ones.
321
5
        let mut all_caps: Vec<Capability> = self.statuses.keys().copied().collect();
322
5
        for cap in next.keys() {
323
3
            if !all_caps.contains(cap) {
324
2
                all_caps.push(*cap);
325
2
            }
326
        }
327

            
328
10
        for cap in all_caps {
329
5
            let (new_count, first_node) = next.get(&cap).copied().unwrap_or((0, None));
330
5
            let entry = self
331
5
                .statuses
332
5
                .entry(cap)
333
5
                .or_insert_with(CapabilityEntry::new);
334
5
            let old_count = entry.refcount;
335
5
            entry.refcount = new_count;
336
5
            if new_count == 0 && old_count > 0 {
337
2
                entry.last_subscriber = None;
338
2
                self.pending_events
339
2
                    .push(PermissionDiffEvent::Release { capability: cap });
340
3
            } else if new_count > 0 && old_count == 0 {
341
3
                let node = first_node.unwrap_or(DomNodeId::ROOT);
342
3
                entry.last_subscriber = first_node;
343
3
                self.pending_events.push(PermissionDiffEvent::Subscribe {
344
3
                    capability: cap,
345
3
                    node_id: node,
346
3
                });
347
3
            }
348
        }
349
5
    }
350
}
351

            
352
// ────────── Async result channel (platform backend → manager) ─────────
353
//
354
// When a `Subscribe` fires an OS prompt, the result arrives later on an
355
// arbitrary thread (an iOS completion handler / Android
356
// `onRequestPermissionsResult`) where there's no handle to the live
357
// `PermissionManager` (it lives inside the window's `LayoutWindow`). The
358
// platform backend parks the resolved state here; the layout pass drains
359
// it once per frame via [`drain_async_results`] and applies each through
360
// [`PermissionManager::set_status`]. Pure Rust — no platform dependency,
361
// so it satisfies SUPER_PLAN_2 §0.5's "no platform deps in azul-layout".
362

            
363
static ASYNC_RESULTS: std::sync::Mutex<Vec<(Capability, PermissionState)>> =
364
    std::sync::Mutex::new(Vec::new());
365

            
366
/// Park an async permission result. Called by a platform backend (in the
367
/// dll) when an OS prompt resolves. Thread-safe; recovers from a poisoned
368
/// lock so one panicking applier can't wedge delivery forever.
369
2
pub fn push_async_result(capability: Capability, state: PermissionState) {
370
2
    let mut q = ASYNC_RESULTS.lock().unwrap_or_else(|e| e.into_inner());
371
2
    q.push((capability, state));
372
2
}
373

            
374
/// Drain everything parked by [`push_async_result`], in arrival order.
375
/// Called once per layout pass; the caller applies each result through
376
/// [`PermissionManager::set_status`] and relayouts if any changed.
377
3
pub fn drain_async_results() -> Vec<(Capability, PermissionState)> {
378
3
    let mut q = ASYNC_RESULTS.lock().unwrap_or_else(|e| e.into_inner());
379
3
    core::mem::take(&mut *q)
380
3
}
381

            
382
#[cfg(test)]
383
mod tests {
384
    use super::*;
385
    use azul_core::dom::{DomId, NodeId};
386

            
387
10
    fn node(idx: usize) -> DomNodeId {
388
10
        DomNodeId {
389
10
            dom: DomId::ROOT_ID,
390
10
            node: NodeId::from_usize(idx).into(),
391
10
        }
392
10
    }
393

            
394
    #[test]
395
1
    fn subscribe_release_round_trip_emits_paired_events() {
396
1
        let mut mgr = PermissionManager::new();
397
1
        assert_eq!(mgr.get_status(Capability::Geolocation), PermissionState::NotDetermined);
398
1
        assert_eq!(mgr.refcount(Capability::Geolocation), 0);
399

            
400
1
        mgr.subscribe(Capability::Geolocation, node(1));
401
1
        assert_eq!(mgr.refcount(Capability::Geolocation), 1);
402
1
        let events = mgr.take_pending_events();
403
1
        assert_eq!(events.len(), 1);
404
1
        assert!(matches!(
405
1
            events[0],
406
            PermissionDiffEvent::Subscribe { capability: Capability::Geolocation, .. }
407
        ));
408

            
409
1
        mgr.release(Capability::Geolocation);
410
1
        assert_eq!(mgr.refcount(Capability::Geolocation), 0);
411
1
        let events = mgr.take_pending_events();
412
1
        assert_eq!(events.len(), 1);
413
1
        assert!(matches!(
414
1
            events[0],
415
            PermissionDiffEvent::Release { capability: Capability::Geolocation }
416
        ));
417
1
    }
418

            
419
    #[test]
420
1
    fn second_subscriber_does_not_re_emit_subscribe() {
421
1
        let mut mgr = PermissionManager::new();
422
1
        mgr.subscribe(Capability::Camera, node(1));
423
1
        mgr.subscribe(Capability::Camera, node(2));
424
1
        assert_eq!(mgr.refcount(Capability::Camera), 2);
425
1
        let events = mgr.take_pending_events();
426
        // Exactly one Subscribe should have been emitted across both subscribes.
427
1
        assert_eq!(events.len(), 1);
428
1
    }
429

            
430
    #[test]
431
1
    fn release_only_after_last_subscriber_drops() {
432
1
        let mut mgr = PermissionManager::new();
433
1
        mgr.subscribe(Capability::Microphone, node(1));
434
1
        mgr.subscribe(Capability::Microphone, node(2));
435
        // Drain the initial Subscribe so the assertion below isolates Release.
436
1
        let _ = mgr.take_pending_events();
437

            
438
1
        mgr.release(Capability::Microphone);
439
1
        assert_eq!(mgr.refcount(Capability::Microphone), 1);
440
1
        assert!(mgr.take_pending_events().is_empty());
441

            
442
1
        mgr.release(Capability::Microphone);
443
1
        assert_eq!(mgr.refcount(Capability::Microphone), 0);
444
1
        let events = mgr.take_pending_events();
445
1
        assert_eq!(events.len(), 1);
446
1
        assert!(matches!(
447
1
            events[0],
448
            PermissionDiffEvent::Release { capability: Capability::Microphone }
449
        ));
450
1
    }
451

            
452
    #[test]
453
1
    fn force_release_drops_refcount_and_emits_event() {
454
1
        let mut mgr = PermissionManager::new();
455
1
        mgr.subscribe(Capability::Camera, node(1));
456
1
        mgr.subscribe(Capability::Camera, node(2));
457
1
        let _ = mgr.take_pending_events();
458

            
459
1
        mgr.force_release(Capability::Camera);
460
1
        assert_eq!(mgr.refcount(Capability::Camera), 0);
461
1
        let events = mgr.take_pending_events();
462
1
        assert_eq!(events.len(), 1);
463
1
        assert!(matches!(
464
1
            events[0],
465
            PermissionDiffEvent::Release { capability: Capability::Camera }
466
        ));
467
1
    }
468

            
469
    #[test]
470
1
    fn set_status_returns_change_flag() {
471
1
        let mut mgr = PermissionManager::new();
472
1
        assert!(mgr.set_status(Capability::Camera, PermissionState::Requested));
473
1
        assert!(!mgr.set_status(Capability::Camera, PermissionState::Requested));
474
1
        assert!(mgr.set_status(
475
1
            Capability::Camera,
476
1
            PermissionState::Granted { quality: PermissionQuality::Full }
477
        ));
478
1
        assert!(mgr.get_status(Capability::Camera).is_granted());
479
1
    }
480

            
481
    #[test]
482
1
    fn diff_layout_picks_up_appearing_node_and_releases_it_next_frame() {
483
1
        let mut mgr = PermissionManager::new();
484

            
485
        // Frame 1: GeolocationProbe present.
486
1
        mgr.diff_layout(|emit| {
487
1
            emit(Capability::Geolocation, node(7));
488
1
        });
489
1
        assert_eq!(mgr.refcount(Capability::Geolocation), 1);
490
1
        let events = mgr.take_pending_events();
491
1
        assert_eq!(events.len(), 1);
492
1
        assert!(matches!(
493
1
            events[0],
494
            PermissionDiffEvent::Subscribe { capability: Capability::Geolocation, .. }
495
        ));
496

            
497
        // Frame 2: probe removed.
498
1
        mgr.diff_layout(|_emit| { /* no bearing nodes this frame */ });
499
1
        assert_eq!(mgr.refcount(Capability::Geolocation), 0);
500
1
        let events = mgr.take_pending_events();
501
1
        assert_eq!(events.len(), 1);
502
1
        assert!(matches!(
503
1
            events[0],
504
            PermissionDiffEvent::Release { capability: Capability::Geolocation }
505
        ));
506
1
    }
507

            
508
    #[test]
509
1
    fn diff_layout_re_emits_subscribe_after_release_cycle() {
510
1
        let mut mgr = PermissionManager::new();
511

            
512
1
        mgr.diff_layout(|emit| emit(Capability::Camera, node(1)));
513
1
        let _ = mgr.take_pending_events();
514

            
515
1
        mgr.diff_layout(|_emit| {});
516
1
        let _ = mgr.take_pending_events();
517

            
518
        // Same capability reappears — must emit Subscribe again because the
519
        // platform tore the session down on the prior Release.
520
1
        mgr.diff_layout(|emit| emit(Capability::Camera, node(2)));
521
1
        let events = mgr.take_pending_events();
522
1
        assert_eq!(events.len(), 1);
523
1
        assert!(matches!(
524
1
            events[0],
525
            PermissionDiffEvent::Subscribe { capability: Capability::Camera, .. }
526
        ));
527
1
    }
528

            
529
    #[test]
530
1
    fn async_results_round_trip_through_manager() {
531
        // The channel is a process-global; clear anything a prior test or
532
        // ordering left behind so this test is self-contained.
533
1
        let _ = drain_async_results();
534

            
535
1
        push_async_result(
536
1
            Capability::Camera,
537
1
            PermissionState::Granted {
538
1
                quality: PermissionQuality::Full,
539
1
            },
540
        );
541
1
        push_async_result(Capability::Geolocation, PermissionState::Denied);
542

            
543
1
        let drained = drain_async_results();
544
1
        assert_eq!(drained.len(), 2, "both parked results drain in order");
545
        // Arrival order preserved.
546
1
        assert_eq!(drained[0].0, Capability::Camera);
547
1
        assert_eq!(drained[1].0, Capability::Geolocation);
548

            
549
        // Applying them through the manager reflects in get_status — this is
550
        // exactly what the dll layout pass does each frame.
551
1
        let mut mgr = PermissionManager::new();
552
3
        for (cap, state) in drained {
553
2
            mgr.set_status(cap, state);
554
2
        }
555
1
        assert!(mgr.get_status(Capability::Camera).is_granted());
556
1
        assert_eq!(mgr.get_status(Capability::Geolocation), PermissionState::Denied);
557

            
558
        // A second drain is empty — the queue was taken, not copied.
559
1
        assert!(drain_async_results().is_empty());
560
1
    }
561
}