1use crate::core::firmware::FirmwareVersion;
24use crate::core::model::Model;
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub enum PushMode {
30 Full,
31 DeltaOnly,
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub enum CameraTransport {
37 Rtsp322,
39 JpegTcp6000,
41 None,
43}
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47pub enum ChamberTemperature {
48 RealSensor,
50 ReportedSynthetic,
53 Unsupported,
55}
56
57#[derive(Debug, Clone, Copy, PartialEq, Eq)]
59pub struct HardwareFeatures {
60 pub lidar: bool,
63 pub chamber_temperature: ChamberTemperature,
64 pub aux_fan: bool,
65 pub chamber_fan: bool,
66}
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
71pub enum DeveloperMode {
72 Available,
73 Unavailable,
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq)]
78pub enum AcsPolicy {
79 Required,
81 NotRequired,
83}
84
85#[derive(Debug, Clone, Copy, PartialEq, Eq)]
88pub enum EvidenceGrade {
89 Observed,
91 VendorSpec,
93 DocSpec,
95 Inferred,
97}
98
99#[derive(Debug, Clone, Copy, PartialEq, Eq)]
101pub enum RegistryStatus {
102 Supported,
104 FirmwareNewerThanKnown,
106 UnknownModel,
108}
109
110#[derive(Debug, Clone, Copy, PartialEq, Eq)]
112pub enum ControlPermit {
113 Allowed,
115 RequiresDeveloperMode,
117}
118
119#[derive(Debug, Clone, Copy, PartialEq, Eq)]
122pub enum ControlRefusal {
123 UnknownModel,
125 FirmwareNewerThanKnown,
128 DeveloperModeUnavailable,
130 UnknownControlBoundary,
132}
133
134#[derive(Debug, Clone, PartialEq, Eq)]
136pub struct Capabilities {
137 pub model: Model,
138 pub push_mode: Option<PushMode>,
139 pub camera_transport: Option<CameraTransport>,
140 pub hardware: Option<HardwareFeatures>,
141 pub developer_mode: Option<DeveloperMode>,
142 pub acs_policy: Option<AcsPolicy>,
143 pub evidence: Option<EvidenceGrade>,
144 pub registry_status: RegistryStatus,
145}
146
147impl Capabilities {
148 pub fn control_permission(&self) -> Result<ControlPermit, ControlRefusal> {
154 match self.registry_status {
155 RegistryStatus::UnknownModel => return Err(ControlRefusal::UnknownModel),
156 RegistryStatus::FirmwareNewerThanKnown => {
157 return Err(ControlRefusal::FirmwareNewerThanKnown);
158 }
159 RegistryStatus::Supported => {}
160 }
161 match (self.acs_policy, self.developer_mode) {
162 (Some(AcsPolicy::NotRequired), _) => Ok(ControlPermit::Allowed),
163 (Some(AcsPolicy::Required), Some(DeveloperMode::Available)) => {
164 Ok(ControlPermit::RequiresDeveloperMode)
165 }
166 (Some(AcsPolicy::Required), Some(DeveloperMode::Unavailable)) => {
167 Err(ControlRefusal::DeveloperModeUnavailable)
168 }
169 _ => Err(ControlRefusal::UnknownControlBoundary),
171 }
172 }
173
174 pub fn control_allowed(&self) -> bool {
176 self.control_permission().is_ok()
177 }
178
179 pub fn control_assessment(&self) -> ControlAssessment {
190 match self.registry_status {
191 RegistryStatus::FirmwareNewerThanKnown => ControlAssessment::NewerFirmwareUntested,
192 RegistryStatus::UnknownModel => {
193 ControlAssessment::Refused(ControlRefusal::UnknownModel)
194 }
195 RegistryStatus::Supported => match self.control_permission() {
196 Ok(ControlPermit::Allowed) => ControlAssessment::Allowed,
197 Ok(ControlPermit::RequiresDeveloperMode) => {
198 ControlAssessment::RequiresDeveloperMode
199 }
200 Err(refusal) => ControlAssessment::Refused(refusal),
201 },
202 }
203 }
204}
205
206#[derive(Debug, Clone, Copy, PartialEq, Eq)]
210pub enum ControlAssessment {
211 Allowed,
213 RequiresDeveloperMode,
215 NewerFirmwareUntested,
218 Refused(ControlRefusal),
221}
222
223#[derive(Debug, Clone, PartialEq, Eq)]
226struct ControlBoundary {
227 developer_mode_since: FirmwareVersion,
229 acs_policy: AcsPolicy,
230}
231
232struct ModelProfile {
234 model: Model,
235 push_mode: PushMode,
237 camera_transport: CameraTransport,
238 hardware: HardwareFeatures,
239 evidence: EvidenceGrade,
240 max_known_firmware: FirmwareVersion,
242 control_boundary: Option<ControlBoundary>,
245}
246
247pub struct CapabilityRegistry {
250 profiles: Vec<ModelProfile>,
251}
252
253impl CapabilityRegistry {
254 fn profile(&self, model: &Model) -> Option<&ModelProfile> {
255 self.profiles.iter().find(|p| &p.model == model)
256 }
257}
258
259pub fn resolve(
262 registry: &CapabilityRegistry,
263 model: &Model,
264 firmware: &FirmwareVersion,
265) -> Capabilities {
266 let Some(profile) = registry.profile(model) else {
267 return Capabilities {
268 model: model.clone(),
269 push_mode: None,
270 camera_transport: None,
271 hardware: None,
272 developer_mode: None,
273 acs_policy: None,
274 evidence: None,
275 registry_status: RegistryStatus::UnknownModel,
276 };
277 };
278
279 let registry_status = if firmware > &profile.max_known_firmware {
280 RegistryStatus::FirmwareNewerThanKnown
281 } else {
282 RegistryStatus::Supported
283 };
284
285 let (developer_mode, acs_policy) = match &profile.control_boundary {
286 Some(cb) => {
287 let dev = if firmware >= &cb.developer_mode_since {
288 DeveloperMode::Available
289 } else {
290 DeveloperMode::Unavailable
291 };
292 (Some(dev), Some(cb.acs_policy))
293 }
294 None => (None, None),
295 };
296
297 Capabilities {
298 model: model.clone(),
299 push_mode: Some(profile.push_mode),
300 camera_transport: Some(profile.camera_transport),
301 hardware: Some(profile.hardware),
302 developer_mode,
303 acs_policy,
304 evidence: Some(profile.evidence),
305 registry_status,
306 }
307}
308
309pub fn default_registry() -> CapabilityRegistry {
316 let fw = |s: &str| FirmwareVersion::parse(s).expect("valid firmware literal");
317
318 let a1_family_hw = HardwareFeatures {
319 lidar: false,
320 chamber_temperature: ChamberTemperature::ReportedSynthetic, aux_fan: false,
322 chamber_fan: false,
323 };
324 let p1_hw = HardwareFeatures {
325 lidar: false,
326 chamber_temperature: ChamberTemperature::ReportedSynthetic,
327 aux_fan: true,
328 chamber_fan: true,
329 };
330 let x1_hw = HardwareFeatures {
331 lidar: true,
332 chamber_temperature: ChamberTemperature::RealSensor,
333 aux_fan: true,
334 chamber_fan: true,
335 };
336
337 let a1_boundary = || {
338 Some(ControlBoundary {
339 developer_mode_since: fw("01.05.00"),
340 acs_policy: AcsPolicy::Required,
341 })
342 };
343 let p1_boundary = || {
344 Some(ControlBoundary {
345 developer_mode_since: fw("01.08.02"),
346 acs_policy: AcsPolicy::Required,
347 })
348 };
349 let x1_boundary = || {
350 Some(ControlBoundary {
351 developer_mode_since: fw("01.08.03"),
352 acs_policy: AcsPolicy::Required,
353 })
354 };
355
356 CapabilityRegistry {
357 profiles: vec![
358 ModelProfile {
360 model: Model::A1Mini,
361 push_mode: PushMode::DeltaOnly,
362 camera_transport: CameraTransport::JpegTcp6000,
363 hardware: a1_family_hw,
364 evidence: EvidenceGrade::Observed,
365 max_known_firmware: fw("01.07.02"),
366 control_boundary: a1_boundary(),
367 },
368 ModelProfile {
370 model: Model::A1,
371 push_mode: PushMode::DeltaOnly,
372 camera_transport: CameraTransport::JpegTcp6000,
373 hardware: a1_family_hw,
374 evidence: EvidenceGrade::VendorSpec,
375 max_known_firmware: fw("01.07.02"),
376 control_boundary: a1_boundary(),
377 },
378 ModelProfile {
379 model: Model::P1P,
380 push_mode: PushMode::DeltaOnly,
381 camera_transport: CameraTransport::JpegTcp6000,
382 hardware: p1_hw,
383 evidence: EvidenceGrade::VendorSpec,
384 max_known_firmware: fw("01.08.04"),
385 control_boundary: p1_boundary(),
386 },
387 ModelProfile {
388 model: Model::P1S,
389 push_mode: PushMode::DeltaOnly,
390 camera_transport: CameraTransport::JpegTcp6000,
391 hardware: p1_hw,
392 evidence: EvidenceGrade::VendorSpec,
393 max_known_firmware: fw("01.08.04"),
394 control_boundary: p1_boundary(),
395 },
396 ModelProfile {
397 model: Model::X1Carbon,
398 push_mode: PushMode::Full,
399 camera_transport: CameraTransport::Rtsp322,
400 hardware: x1_hw,
401 evidence: EvidenceGrade::VendorSpec,
402 max_known_firmware: fw("01.08.05"),
403 control_boundary: x1_boundary(),
404 },
405 ModelProfile {
406 model: Model::X1E,
407 push_mode: PushMode::Full,
408 camera_transport: CameraTransport::Rtsp322,
409 hardware: x1_hw,
410 evidence: EvidenceGrade::VendorSpec,
411 max_known_firmware: fw("01.08.05"),
412 control_boundary: x1_boundary(),
413 },
414 ModelProfile {
417 model: Model::H2D,
418 push_mode: PushMode::Full,
419 camera_transport: CameraTransport::Rtsp322,
420 hardware: HardwareFeatures {
421 lidar: false,
422 chamber_temperature: ChamberTemperature::RealSensor,
423 aux_fan: true,
424 chamber_fan: true,
425 },
426 evidence: EvidenceGrade::Inferred,
427 max_known_firmware: fw("01.02.00"),
428 control_boundary: None,
429 },
430 ],
431 }
432}
433
434#[cfg(test)]
435mod tests {
436 use super::*;
437
438 fn fw(s: &str) -> FirmwareVersion {
439 FirmwareVersion::parse(s).unwrap()
440 }
441
442 #[test]
443 fn observed_a1mini_descriptive_facts() {
444 let reg = default_registry();
445 let caps = resolve(®, &Model::A1Mini, &fw("01.07.02"));
446 assert_eq!(caps.push_mode, Some(PushMode::DeltaOnly));
447 assert_eq!(caps.camera_transport, Some(CameraTransport::JpegTcp6000));
448 assert_eq!(caps.evidence, Some(EvidenceGrade::Observed));
449 assert_eq!(caps.registry_status, RegistryStatus::Supported);
450 let hw = caps.hardware.unwrap();
451 assert!(!hw.lidar);
452 assert_eq!(
453 hw.chamber_temperature,
454 ChamberTemperature::ReportedSynthetic
455 );
456 assert!(!hw.aux_fan && !hw.chamber_fan);
457 }
458
459 #[test]
460 fn a1mini_real_firmware_is_supported_not_newer_than_known() {
461 let reg = default_registry();
464 let caps = resolve(®, &Model::A1Mini, &fw("01.07.02.00"));
465 assert_eq!(caps.registry_status, RegistryStatus::Supported);
466 assert_eq!(
467 caps.control_permission(),
468 Ok(ControlPermit::RequiresDeveloperMode)
469 );
470 }
471
472 #[test]
473 fn developer_mode_threshold_and_control_below_it() {
474 let reg = default_registry();
475 let below = resolve(®, &Model::A1Mini, &fw("01.04.99"));
476 assert_eq!(below.developer_mode, Some(DeveloperMode::Unavailable));
477 assert_eq!(
478 below.control_permission(),
479 Err(ControlRefusal::DeveloperModeUnavailable)
480 );
481 }
482
483 #[test]
484 fn unknown_model_refuses_control() {
485 let reg = default_registry();
486 let caps = resolve(®, &Model::Unknown("z9".into()), &fw("01.00.00"));
487 assert_eq!(caps.registry_status, RegistryStatus::UnknownModel);
488 assert_eq!(caps.push_mode, None);
489 assert_eq!(caps.control_permission(), Err(ControlRefusal::UnknownModel));
490 }
491
492 #[test]
493 fn firmware_newer_than_known_refuses_control() {
494 let reg = default_registry();
495 let caps = resolve(®, &Model::A1Mini, &fw("01.99.00"));
496 assert_eq!(caps.registry_status, RegistryStatus::FirmwareNewerThanKnown);
497 assert_eq!(
498 caps.control_permission(),
499 Err(ControlRefusal::FirmwareNewerThanKnown)
500 );
501 assert_eq!(caps.push_mode, Some(PushMode::DeltaOnly));
503 }
504
505 #[test]
506 fn control_assessment_degrades_newer_firmware_to_a_warning_not_a_wall() {
507 let reg = default_registry();
508 let newer = resolve(®, &Model::A1Mini, &fw("01.99.00"));
511 assert_eq!(
512 newer.control_permission(),
513 Err(ControlRefusal::FirmwareNewerThanKnown)
514 );
515 assert_eq!(
516 newer.control_assessment(),
517 ControlAssessment::NewerFirmwareUntested
518 );
519
520 let ok = resolve(®, &Model::A1Mini, &fw("01.07.02"));
522 assert_eq!(
523 ok.control_assessment(),
524 ControlAssessment::RequiresDeveloperMode
525 );
526
527 let unknown = resolve(®, &Model::Unknown("z9".into()), &fw("01.00.00"));
529 assert_eq!(
530 unknown.control_assessment(),
531 ControlAssessment::Refused(ControlRefusal::UnknownModel)
532 );
533
534 let below = resolve(®, &Model::A1Mini, &fw("01.04.99"));
536 assert_eq!(
537 below.control_assessment(),
538 ControlAssessment::Refused(ControlRefusal::DeveloperModeUnavailable)
539 );
540 }
541
542 #[test]
543 fn x1_carbon_is_full_push_rtsp_and_has_lidar() {
544 let reg = default_registry();
545 let caps = resolve(®, &Model::X1Carbon, &fw("01.08.03"));
546 assert_eq!(caps.push_mode, Some(PushMode::Full));
547 assert_eq!(caps.camera_transport, Some(CameraTransport::Rtsp322));
548 let hw = caps.hardware.unwrap();
549 assert!(hw.lidar);
550 assert_eq!(hw.chamber_temperature, ChamberTemperature::RealSensor);
551 assert_eq!(
552 caps.control_permission(),
553 Ok(ControlPermit::RequiresDeveloperMode)
554 );
555 }
556
557 #[test]
558 fn inferred_h2d_keeps_descriptive_info_but_refuses_control() {
559 let reg = default_registry();
563 let caps = resolve(®, &Model::H2D, &fw("01.01.05"));
564 assert_eq!(caps.registry_status, RegistryStatus::Supported);
565 assert_eq!(caps.push_mode, Some(PushMode::Full)); assert_eq!(caps.evidence, Some(EvidenceGrade::Inferred));
567 assert_eq!(caps.developer_mode, None);
568 assert_eq!(
569 caps.control_permission(),
570 Err(ControlRefusal::UnknownControlBoundary)
571 );
572 }
573}