1use anyhow::{bail, Context};
6use chrono::NaiveDate;
7use itertools::Itertools;
8use log::error;
9use serde::de::{Error, Unexpected};
10use serde::{Deserialize, Deserializer, Serialize};
11use std::array::TryFromSliceError;
12use std::collections::BTreeMap;
13use std::fmt;
14
15const VERSION_HISTORY_SCHEMA_ID: &str = "https://fuchsia.dev/schema/version_history.json";
16const VERSION_HISTORY_NAME: &str = "Platform version map";
17const VERSION_HISTORY_TYPE: &str = "version_history";
18
19#[derive(Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone)]
21pub struct ApiLevel(u32);
22
23impl ApiLevel {
24 pub const NEXT: ApiLevel = ApiLevel(4291821568);
27
28 pub const HEAD: ApiLevel = ApiLevel(4292870144);
30
31 pub const PLATFORM: ApiLevel = ApiLevel(4293918720);
33
34 pub const fn from_u32(value: u32) -> Self {
35 Self(value)
36 }
37
38 pub fn as_u32(&self) -> u32 {
39 self.0
40 }
41
42 #[deprecated = "API levels are 32-bits as of RFC-0246"]
43 pub fn as_u64(&self) -> u64 {
44 self.0 as u64
45 }
46}
47
48impl fmt::Debug for ApiLevel {
49 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50 match *self {
51 ApiLevel::NEXT => write!(f, "ApiLevel::NEXT"),
52 ApiLevel::HEAD => write!(f, "ApiLevel::HEAD"),
53 ApiLevel::PLATFORM => write!(f, "ApiLevel::PLATFORM"),
54 ApiLevel(l) => f.debug_tuple("ApiLevel").field(&l).finish(),
55 }
56 }
57}
58
59impl fmt::Display for ApiLevel {
60 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61 match *self {
62 ApiLevel::NEXT => write!(f, "NEXT"),
63 ApiLevel::HEAD => write!(f, "HEAD"),
64 ApiLevel::PLATFORM => write!(f, "PLATFORM"),
65 ApiLevel(l) => write!(f, "{}", l),
66 }
67 }
68}
69
70impl std::str::FromStr for ApiLevel {
71 type Err = std::num::ParseIntError;
72
73 fn from_str(s: &str) -> Result<Self, Self::Err> {
74 match s {
75 "NEXT" => Ok(ApiLevel::NEXT),
76 "HEAD" => Ok(ApiLevel::HEAD),
77 "PLATFORM" => Ok(ApiLevel::PLATFORM),
78 s => Ok(ApiLevel::from_u32(s.parse()?)),
79 }
80 }
81}
82
83impl From<u32> for ApiLevel {
84 fn from(api_level: u32) -> ApiLevel {
85 ApiLevel(api_level)
86 }
87}
88
89impl From<&u32> for ApiLevel {
90 fn from(api_level: &u32) -> ApiLevel {
91 ApiLevel(*api_level)
92 }
93}
94
95impl From<ApiLevel> for u32 {
96 fn from(api_level: ApiLevel) -> u32 {
97 api_level.0
98 }
99}
100
101#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone)]
104pub struct AbiRevision(u64);
105
106#[derive(Debug, Copy, Clone, Eq, PartialEq)]
112pub enum AbiRevisionExplanation {
113 Normal,
116
117 Platform {
119 date: chrono::NaiveDate,
122 git_revision: u16,
125 },
126
127 Unstable {
130 date: chrono::NaiveDate,
133 git_revision: u16,
136 },
137
138 Invalid,
140
141 Malformed,
144}
145
146impl AbiRevision {
147 pub const INVALID: AbiRevision = AbiRevision(0xFFFF_FFFF_FFFF_FFFF);
150
151 pub const PATH: &'static str = "meta/fuchsia.abi/abi-revision";
152
153 pub fn from_bytes(b: [u8; 8]) -> Self {
155 AbiRevision(u64::from_le_bytes(b))
156 }
157
158 pub fn as_bytes(&self) -> [u8; 8] {
160 self.0.to_le_bytes()
161 }
162
163 pub const fn from_u64(u: u64) -> AbiRevision {
164 AbiRevision(u)
165 }
166
167 pub fn as_u64(&self) -> u64 {
168 self.0
169 }
170
171 pub fn explanation(self) -> AbiRevisionExplanation {
172 const SPECIAL_ABI_REVISION_PREFIX: u64 = 0xFF;
174 const UNSTABLE_ABI_REVISION_PREFIX: u64 = 0xFF00;
175 const PLATFORM_ABI_REVISION_PREFIX: u64 = 0xFF01;
176
177 let get_date = || {
179 const DATE_MASK: u64 = 0x0000_0000_FFFF_FFFF;
182 let date_ordinal = (self.as_u64() & DATE_MASK) as i32;
183 chrono::NaiveDate::from_num_days_from_ce_opt(date_ordinal).unwrap_or_default()
184 };
185
186 let get_git_revision = || {
189 const GIT_HASH_MASK: u64 = 0x0000_FFFF_0000_0000;
192 ((self.as_u64() & GIT_HASH_MASK) >> 32) as u16
193 };
194
195 if self.as_u64() >> 56 != SPECIAL_ABI_REVISION_PREFIX {
196 AbiRevisionExplanation::Normal
197 } else if self.as_u64() >> 48 == UNSTABLE_ABI_REVISION_PREFIX {
198 AbiRevisionExplanation::Unstable { date: get_date(), git_revision: get_git_revision() }
199 } else if self.as_u64() >> 48 == PLATFORM_ABI_REVISION_PREFIX {
200 AbiRevisionExplanation::Platform { date: get_date(), git_revision: get_git_revision() }
201 } else if self == AbiRevision::INVALID {
202 AbiRevisionExplanation::Invalid
203 } else {
204 AbiRevisionExplanation::Malformed
205 }
206 }
207}
208
209impl fmt::Display for AbiRevision {
210 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
211 write!(f, "{:x}", self.0)
213 }
214}
215
216impl std::str::FromStr for AbiRevision {
217 type Err = std::num::ParseIntError;
218
219 fn from_str(s: &str) -> Result<Self, Self::Err> {
220 let parsed = if let Some(s) = s.strip_prefix("0x") {
221 u64::from_str_radix(&s, 16)?
222 } else {
223 u64::from_str_radix(&s, 10)?
224 };
225 Ok(AbiRevision::from_u64(parsed))
226 }
227}
228
229impl<'de> Deserialize<'de> for AbiRevision {
230 fn deserialize<D>(deserializer: D) -> Result<AbiRevision, D::Error>
231 where
232 D: Deserializer<'de>,
233 {
234 let s = String::deserialize(deserializer)?;
235 s.parse().map_err(|_| D::Error::invalid_value(Unexpected::Str(&s), &"an unsigned integer"))
236 }
237}
238
239impl Serialize for AbiRevision {
240 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
241 where
242 S: serde::Serializer,
243 {
244 let string = format!("0x{:016x}", self.0);
245 serializer.serialize_str(&string)
246 }
247}
248
249impl From<u64> for AbiRevision {
250 fn from(abi_revision: u64) -> AbiRevision {
251 AbiRevision(abi_revision)
252 }
253}
254
255impl From<&u64> for AbiRevision {
256 fn from(abi_revision: &u64) -> AbiRevision {
257 AbiRevision(*abi_revision)
258 }
259}
260
261impl From<AbiRevision> for u64 {
262 fn from(abi_revision: AbiRevision) -> u64 {
263 abi_revision.0
264 }
265}
266
267impl From<[u8; 8]> for AbiRevision {
268 fn from(abi_revision: [u8; 8]) -> AbiRevision {
269 AbiRevision::from_bytes(abi_revision)
270 }
271}
272
273impl TryFrom<&[u8]> for AbiRevision {
274 type Error = TryFromSliceError;
275
276 fn try_from(abi_revision: &[u8]) -> Result<AbiRevision, Self::Error> {
277 let abi_revision: [u8; 8] = abi_revision.try_into()?;
278 Ok(AbiRevision::from_bytes(abi_revision))
279 }
280}
281
282#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
288pub struct Version {
289 pub api_level: ApiLevel,
291
292 pub abi_revision: AbiRevision,
294
295 pub status: Status,
297}
298
299impl Version {
300 fn is_runnable(&self) -> bool {
303 match self.status {
304 Status::InDevelopment | Status::Supported => true,
305 Status::Unsupported => false,
306 }
307 }
308}
309
310#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord, Hash)]
311pub enum Status {
312 #[serde(rename = "in-development")]
313 InDevelopment,
314 #[serde(rename = "supported")]
315 Supported,
316 #[serde(rename = "unsupported")]
317 Unsupported,
318}
319
320#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
323pub struct VersionHistory {
324 versions: &'static [Version],
325}
326
327impl VersionHistory {
328 pub const fn new(versions: &'static [Version]) -> Self {
336 VersionHistory { versions }
337 }
338
339 pub fn get_abi_revision_for_platform_components(&self) -> AbiRevision {
352 self.version_from_api_level(ApiLevel::PLATFORM)
353 .expect("API Level PLATFORM not found!")
354 .abi_revision
355 }
356
357 pub fn get_misleading_version_for_ffx(&self) -> Version {
365 self.runnable_versions()
366 .filter(|v| {
367 v.api_level != ApiLevel::NEXT
368 && v.api_level != ApiLevel::HEAD
369 && v.api_level != ApiLevel::PLATFORM
370 })
371 .last()
372 .unwrap()
373 }
374
375 pub fn get_example_supported_version_for_tests(&self) -> Version {
380 self.runnable_versions()
381 .filter(|v| {
382 v.api_level != ApiLevel::NEXT
383 && v.api_level != ApiLevel::HEAD
384 && v.api_level != ApiLevel::PLATFORM
385 })
386 .last()
387 .unwrap()
388 }
389
390 pub fn check_api_level_for_build(
394 &self,
395 api_level: ApiLevel,
396 ) -> Result<AbiRevision, ApiLevelError> {
397 let Some(version) = self.version_from_api_level(api_level) else {
398 return Err(ApiLevelError::Unknown {
399 api_level,
400 supported: self.runnable_versions().map(|v| v.api_level).collect(),
401 });
402 };
403
404 if version.is_runnable() {
405 Ok(version.abi_revision)
406 } else {
407 Err(ApiLevelError::Unsupported {
408 version,
409 supported: self.runnable_versions().map(|v| v.api_level).collect(),
410 })
411 }
412 }
413
414 pub fn check_abi_revision_for_runtime(
417 &self,
418 abi_revision: AbiRevision,
419 ) -> Result<(), AbiRevisionError> {
420 if let Some(version) = self.version_from_abi_revision(abi_revision) {
421 if version.is_runnable() {
422 Ok(())
423 } else {
424 Err(AbiRevisionError::Retired {
425 version,
426 supported_versions: self.supported_versions(),
427 })
428 }
429 } else {
430 match abi_revision.explanation() {
433 AbiRevisionExplanation::Platform { date, git_revision } => {
434 let (platform_date, platform_commit_hash) = self.platform_abi_info();
435 Err(AbiRevisionError::PlatformMismatch {
436 abi_revision,
437 package_date: date,
438 package_commit_hash: git_revision,
439 platform_date,
440 platform_commit_hash,
441 })
442 }
443 AbiRevisionExplanation::Unstable { date, git_revision } => {
444 let (platform_date, platform_commit_hash) = self.platform_abi_info();
445 Err(AbiRevisionError::UnstableMismatch {
446 abi_revision,
447 package_sdk_date: date,
448 package_sdk_commit_hash: git_revision,
449 platform_date,
450 platform_commit_hash,
451 supported_versions: self.supported_versions(),
452 })
453 }
454 AbiRevisionExplanation::Malformed => {
455 Err(AbiRevisionError::Malformed { abi_revision })
456 }
457 AbiRevisionExplanation::Invalid => Err(AbiRevisionError::Invalid),
458 AbiRevisionExplanation::Normal => Err(AbiRevisionError::TooNew {
459 abi_revision,
460 supported_versions: self.supported_versions(),
461 }),
462 }
463 }
464 }
465
466 fn runnable_versions(&self) -> impl Iterator<Item = Version> + '_ {
467 self.versions.iter().filter(|v| v.is_runnable()).cloned()
468 }
469
470 fn supported_versions(&self) -> VersionVec {
471 VersionVec(
472 self.versions
473 .iter()
474 .filter(|v| match v.status {
475 Status::InDevelopment => false,
476 Status::Supported => true,
477 Status::Unsupported => false,
478 })
479 .cloned()
480 .collect(),
481 )
482 }
483
484 fn version_from_abi_revision(&self, abi_revision: AbiRevision) -> Option<Version> {
485 self.versions.iter().find(|v| v.abi_revision == abi_revision).cloned()
486 }
487
488 pub fn version_from_api_level(&self, api_level: ApiLevel) -> Option<Version> {
489 self.versions.iter().find(|v| v.api_level == api_level).cloned()
490 }
491
492 fn platform_abi_info(&self) -> (NaiveDate, u16) {
493 if let Some(platform_version) = self.version_from_api_level(ApiLevel::PLATFORM) {
494 if let AbiRevisionExplanation::Platform { date, git_revision } =
495 platform_version.abi_revision.explanation()
496 {
497 return (date, git_revision);
498 }
499 }
500 error!(
501 "No PLATFORM API level found. This should never happen.
502Returning bogus data instead of panicking."
503 );
504 (NaiveDate::default(), 0)
505 }
506}
507
508#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
509pub enum AbiRevisionError {
510 #[error(
511 "Unknown platform ABI revision: 0x{abi_revision}.
512Found platform component from {package_date} (Git hash {package_commit_hash:x}).
513Platform components must be from {platform_date} (Git hash {platform_commit_hash:x})"
514 )]
515 PlatformMismatch {
516 abi_revision: AbiRevision,
517 package_date: NaiveDate,
518 package_commit_hash: u16,
519 platform_date: NaiveDate,
520 platform_commit_hash: u16,
521 },
522 #[error(
523 "Unknown NEXT or HEAD ABI revision: 0x{abi_revision}.
524SDK is from {package_sdk_date} (Git hash {package_sdk_commit_hash:x}).
525Expected {platform_date} (Git hash {platform_commit_hash:x})
526The following API levels are stable and supported:{supported_versions}"
527 )]
528 UnstableMismatch {
529 abi_revision: AbiRevision,
530 package_sdk_date: NaiveDate,
531 package_sdk_commit_hash: u16,
532 platform_date: NaiveDate,
533 platform_commit_hash: u16,
534 supported_versions: VersionVec,
535 },
536
537 #[error(
538 "Unknown ABI revision 0x{abi_revision}. It was probably created after
539this platform or tool was built. The following API levels are stable and
540supported:{supported_versions}"
541 )]
542 TooNew { abi_revision: AbiRevision, supported_versions: VersionVec },
543
544 #[error("Retired API level {} (0x{}) cannot run.
545The following API levels are stable and supported:{supported_versions}",
546 .version.api_level, .version.abi_revision)]
547 Retired { version: Version, supported_versions: VersionVec },
548
549 #[error("Invalid ABI revision 0x{}.", AbiRevision::INVALID)]
550 Invalid,
551
552 #[error("ABI revision 0x{abi_revision} has an unrecognized format.")]
553 Malformed { abi_revision: AbiRevision },
554}
555
556#[derive(Debug, Clone, PartialEq, Eq)]
558pub struct VersionVec(pub Vec<Version>);
559
560impl std::fmt::Display for VersionVec {
561 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
562 for version in self.0.iter() {
563 write!(f, "\n└── {} (0x{})", version.api_level, version.abi_revision)?;
564 }
565 Ok(())
566 }
567}
568
569#[derive(Debug, Clone, PartialEq, Eq)]
570pub enum ApiLevelError {
571 Unknown { api_level: ApiLevel, supported: Vec<ApiLevel> },
574
575 Unsupported { version: Version, supported: Vec<ApiLevel> },
578}
579
580impl std::error::Error for ApiLevelError {}
581
582impl std::fmt::Display for ApiLevelError {
583 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
584 match self {
585 ApiLevelError::Unknown { api_level, supported } => {
586 write!(
587 f,
588 "Unknown target API level: {}. Is the SDK too old to support it?
589The following API levels are supported: {}",
590 api_level,
591 supported.iter().join(", ")
592 )
593 }
594 ApiLevelError::Unsupported { version, supported } => {
595 write!(
596 f,
597 "The SDK no longer supports API level {}.
598The following API levels are supported: {}",
599 version.api_level,
600 supported.iter().join(", ")
601 )
602 }
603 }
604 }
605}
606
607#[derive(Deserialize)]
608struct VersionHistoryDataJson {
609 name: String,
610 #[serde(rename = "type")]
611 element_type: String,
612 api_levels: BTreeMap<String, ApiLevelJson>,
613 special_api_levels: BTreeMap<String, SpecialApiLevelJson>,
614}
615
616#[derive(Deserialize)]
617struct VersionHistoryJson {
618 schema_id: String,
619 data: VersionHistoryDataJson,
620}
621
622#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Deserialize)]
623struct ApiLevelJson {
624 pub abi_revision: AbiRevision,
625 pub status: Status,
626}
627
628#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Deserialize)]
629struct SpecialApiLevelJson {
630 pub as_u32: u32,
631 pub abi_revision: AbiRevision,
632 pub status: Status,
633}
634
635pub fn parse_version_history(bytes: &[u8]) -> anyhow::Result<Vec<Version>> {
636 let v: VersionHistoryJson = serde_json::from_slice(bytes)?;
637
638 if v.schema_id != VERSION_HISTORY_SCHEMA_ID {
639 bail!("expected schema_id = {:?}; got {:?}", VERSION_HISTORY_SCHEMA_ID, v.schema_id)
640 }
641 if v.data.name != VERSION_HISTORY_NAME {
642 bail!("expected data.name = {:?}; got {:?}", VERSION_HISTORY_NAME, v.data.name)
643 }
644 if v.data.element_type != VERSION_HISTORY_TYPE {
645 bail!("expected data.type = {:?}; got {:?}", VERSION_HISTORY_TYPE, v.data.element_type,)
646 }
647
648 let mut versions = Vec::new();
649
650 for (key, value) in v.data.api_levels {
651 versions.push(Version {
652 api_level: key.parse()?,
653 abi_revision: value.abi_revision,
654 status: value.status,
655 });
656 }
657
658 for (key, value) in v.data.special_api_levels {
659 let api_level: ApiLevel =
660 key.parse().with_context(|| format!("Unknown special API level: {}", key))?;
661 if api_level.as_u32() != value.as_u32 {
662 bail!(
663 "Special API level {} had unexpected numerical value {} (Expected {})",
664 api_level,
665 value.as_u32,
666 api_level.as_u32()
667 )
668 }
669 versions.push(Version {
670 api_level,
671 abi_revision: value.abi_revision,
672 status: value.status,
673 });
674 }
675
676 versions.sort_by_key(|s| s.api_level);
677
678 let Some(latest_api_version) = versions.last() else {
679 bail!("there must be at least one API level")
680 };
681
682 if latest_api_version.status == Status::Unsupported {
683 bail!("most recent API level must not be 'unsupported'")
684 }
685
686 Ok(versions)
687}
688
689#[cfg(test)]
690mod tests {
691 use super::*;
692
693 #[test]
694 fn test_parse_history_works() {
695 let expected_bytes = br#"{
696 "data": {
697 "name": "Platform version map",
698 "type": "version_history",
699 "api_levels": {
700 "1":{
701 "abi_revision":"0xBEBE3F5CAAA046D2",
702 "status":"supported"
703 },
704 "2":{
705 "abi_revision":"0x50cbc6e8a39e1e2c",
706 "status":"in-development"
707 }
708 },
709 "special_api_levels": {
710 "NEXT": {
711 "as_u32": 4291821568,
712 "abi_revision": "0xFF0038FA031D0347",
713 "status": "in-development"
714 },
715 "HEAD": {
716 "as_u32": 4292870144,
717 "abi_revision": "0xFF0038FA031D0348",
718 "status": "in-development"
719 },
720 "PLATFORM": {
721 "as_u32": 4293918720,
722 "abi_revision": "0xFF01A9328DA0C138",
723 "status": "in-development"
724 }
725 }
726 },
727 "schema_id": "https://fuchsia.dev/schema/version_history.json"
728 }"#;
729
730 assert_eq!(
731 parse_version_history(&expected_bytes[..]).unwrap(),
732 vec![
733 Version {
734 api_level: 1.into(),
735 abi_revision: 0xBEBE3F5CAAA046D2.into(),
736 status: Status::Supported
737 },
738 Version {
739 api_level: 2.into(),
740 abi_revision: 0x50CBC6E8A39E1E2C.into(),
741 status: Status::InDevelopment
742 },
743 Version {
744 api_level: ApiLevel::NEXT,
745 abi_revision: 0xFF0038FA031D0347.into(),
746 status: Status::InDevelopment
747 },
748 Version {
749 api_level: ApiLevel::HEAD,
750 abi_revision: 0xFF0038FA031D0348.into(),
751 status: Status::InDevelopment
752 },
753 Version {
754 api_level: ApiLevel::PLATFORM,
755 abi_revision: 0xFF01A9328DA0C138.into(),
756 status: Status::InDevelopment
757 },
758 ],
759 );
760 }
761
762 #[test]
763 fn test_parse_history_rejects_invalid_schema() {
764 let expected_bytes = br#"{
765 "data": {
766 "name": "Platform version map",
767 "type": "version_history",
768 "api_levels": {},
769 "special_api_levels": {}
770 },
771 "schema_id": "some-schema"
772 }"#;
773
774 assert_eq!(
775 &parse_version_history(&expected_bytes[..]).unwrap_err().to_string(),
776 r#"expected schema_id = "https://fuchsia.dev/schema/version_history.json"; got "some-schema""#
777 );
778 }
779
780 #[test]
781 fn test_parse_history_rejects_invalid_name() {
782 let expected_bytes = br#"{
783 "data": {
784 "name": "some-name",
785 "type": "version_history",
786 "api_levels": {},
787 "special_api_levels": {}
788 },
789 "schema_id": "https://fuchsia.dev/schema/version_history.json"
790 }"#;
791
792 assert_eq!(
793 &parse_version_history(&expected_bytes[..]).unwrap_err().to_string(),
794 r#"expected data.name = "Platform version map"; got "some-name""#
795 );
796 }
797
798 #[test]
799 fn test_parse_history_rejects_invalid_type() {
800 let expected_bytes = br#"{
801 "data": {
802 "name": "Platform version map",
803 "type": "some-type",
804 "api_levels": {},
805 "special_api_levels": {}
806 },
807 "schema_id": "https://fuchsia.dev/schema/version_history.json"
808 }"#;
809
810 assert_eq!(
811 &parse_version_history(&expected_bytes[..]).unwrap_err().to_string(),
812 r#"expected data.type = "version_history"; got "some-type""#
813 );
814 }
815
816 #[test]
817 fn test_parse_history_rejects_invalid_versions() {
818 for (api_level, abi_revision, err) in [
819 (
820 "some-version",
821 "1",
822 "invalid digit found in string" ,
823 ),
824 (
825 "-1",
826 "1",
827 "invalid digit found in string" ,
828 ),
829 (
830 "1",
831 "some-revision",
832 "invalid value: string \"some-revision\", expected an unsigned integer at line 1 column 58",
833 ),
834 (
835 "1",
836 "-1",
837 "invalid value: string \"-1\", expected an unsigned integer at line 1 column 47",
838 ),
839 ] {
840 let expected_bytes = serde_json::to_vec(&serde_json::json!({
841 "data": {
842 "name": VERSION_HISTORY_NAME,
843 "type": VERSION_HISTORY_TYPE,
844 "api_levels": {
845 api_level:{
846 "abi_revision": abi_revision,
847 "status": Status::InDevelopment,
848 }
849 },
850 "special_api_levels": {},
851 },
852 "schema_id": VERSION_HISTORY_SCHEMA_ID,
853 }))
854 .unwrap();
855
856 assert_eq!(parse_version_history(&expected_bytes[..]).unwrap_err().to_string(), err);
857 }
858 }
859
860 #[test]
861 fn test_parse_history_rejects_bogus_special_levels() {
862 let input = br#"{
863 "data": {
864 "name": "Platform version map",
865 "type": "version_history",
866 "api_levels": {},
867 "special_api_levels": {
868 "WACKY_WAVING_INFLATABLE_ARM_FLAILING_TUBE_MAN": {
869 "as_u32": 1234,
870 "abi_revision": "0x50cbc6e8a39e1e2c",
871 "status": "in-development"
872 }
873 }
874 },
875 "schema_id": "https://fuchsia.dev/schema/version_history.json"
876 }"#;
877
878 assert_eq!(
879 parse_version_history(&input[..]).unwrap_err().to_string(),
880 "Unknown special API level: WACKY_WAVING_INFLATABLE_ARM_FLAILING_TUBE_MAN"
881 );
882 }
883
884 #[test]
885 fn test_parse_history_rejects_misnumbered_special_levels() {
886 let input = br#"{
887 "data": {
888 "name": "Platform version map",
889 "type": "version_history",
890 "api_levels": {},
891 "special_api_levels": {
892 "HEAD": {
893 "as_u32": 1234,
894 "abi_revision": "0x50cbc6e8a39e1e2c",
895 "status": "in-development"
896 }
897 }
898 },
899 "schema_id": "https://fuchsia.dev/schema/version_history.json"
900 }"#;
901
902 assert_eq!(
903 parse_version_history(&input[..]).unwrap_err().to_string(),
904 "Special API level HEAD had unexpected numerical value 1234 (Expected 4292870144)"
905 );
906 }
907
908 #[test]
909 fn test_serialize_abi_revision() {
910 {
911 let abi = AbiRevision::from_u64(16);
912 let str = "\"0x0000000000000010\"";
913 assert_eq!(serde_json::to_string(&abi).unwrap(), str);
914 assert_eq!(serde_json::from_str::<AbiRevision>(str).unwrap(), abi);
915 }
916 {
917 let abi = AbiRevision::from_u64(0x58ea445e942a0004);
918 let str = "\"0x58ea445e942a0004\"";
919 assert_eq!(serde_json::to_string(&abi).unwrap(), str);
920 assert_eq!(serde_json::from_str::<AbiRevision>(str).unwrap(), abi);
921 }
922 }
923
924 pub const FAKE_VERSION_HISTORY: VersionHistory = VersionHistory {
925 versions: &[
926 Version {
927 api_level: ApiLevel::from_u32(4),
928 abi_revision: AbiRevision::from_u64(0x58ea445e942a0004),
929 status: Status::Unsupported,
930 },
931 Version {
932 api_level: ApiLevel::from_u32(5),
933 abi_revision: AbiRevision::from_u64(0x58ea445e942a0005),
934 status: Status::Supported,
935 },
936 Version {
937 api_level: ApiLevel::from_u32(6),
938 abi_revision: AbiRevision::from_u64(0x58ea445e942a0006),
939 status: Status::Supported,
940 },
941 Version {
942 api_level: ApiLevel::from_u32(7),
943 abi_revision: AbiRevision::from_u64(0x58ea445e942a0007),
944 status: Status::Supported,
945 },
946 Version {
947 api_level: ApiLevel::NEXT,
948 abi_revision: AbiRevision::from_u64(0xFF00_8C4D_000B_4751),
949 status: Status::InDevelopment,
950 },
951 Version {
952 api_level: ApiLevel::HEAD,
953 abi_revision: AbiRevision::from_u64(0xFF00_8C4D_000B_4751),
954 status: Status::InDevelopment,
955 },
956 Version {
957 api_level: ApiLevel::PLATFORM,
958 abi_revision: AbiRevision::from_u64(0xFF01_8C4D_000B_4751),
959 status: Status::InDevelopment,
960 },
961 ],
962 };
963
964 #[test]
965 fn test_check_abi_revision() {
966 let supported_versions =
967 VersionVec(FAKE_VERSION_HISTORY.versions[1..4].iter().cloned().collect());
968
969 assert_eq!(
970 FAKE_VERSION_HISTORY.check_abi_revision_for_runtime(0xFF01_ABCD_000B_2224.into()),
971 Err(AbiRevisionError::PlatformMismatch {
972 abi_revision: 0xFF01_ABCD_000B_2224.into(),
973 package_date: NaiveDate::from_ymd_opt(1998, 9, 4).unwrap(),
974 package_commit_hash: 0xABCD,
975 platform_date: NaiveDate::from_ymd_opt(2024, 9, 24).unwrap(),
976 platform_commit_hash: 0x8C4D,
977 })
978 );
979
980 assert_eq!(
981 FAKE_VERSION_HISTORY.check_abi_revision_for_runtime(0xFF00_ABCD_000B_2224.into()),
982 Err(AbiRevisionError::UnstableMismatch {
983 abi_revision: 0xFF00_ABCD_000B_2224.into(),
984 package_sdk_date: NaiveDate::from_ymd_opt(1998, 9, 4).unwrap(),
985 package_sdk_commit_hash: 0xABCD,
986 platform_date: NaiveDate::from_ymd_opt(2024, 9, 24).unwrap(),
987 platform_commit_hash: 0x8C4D,
988 supported_versions: supported_versions.clone()
989 })
990 );
991
992 assert_eq!(
993 FAKE_VERSION_HISTORY.check_abi_revision_for_runtime(0x1234.into()),
994 Err(AbiRevisionError::TooNew {
995 abi_revision: 0x1234.into(),
996 supported_versions: supported_versions.clone()
997 })
998 );
999
1000 assert_eq!(
1001 FAKE_VERSION_HISTORY.check_abi_revision_for_runtime(0x58ea445e942a0004.into()),
1002 Err(AbiRevisionError::Retired {
1003 version: FAKE_VERSION_HISTORY.versions[0].clone(),
1004
1005 supported_versions: supported_versions.clone()
1006 })
1007 );
1008
1009 assert_eq!(
1010 FAKE_VERSION_HISTORY.check_abi_revision_for_runtime(AbiRevision::INVALID),
1011 Err(AbiRevisionError::Invalid)
1012 );
1013
1014 assert_eq!(
1015 FAKE_VERSION_HISTORY.check_abi_revision_for_runtime(0xFF02_0000_0000_0000.into()),
1016 Err(AbiRevisionError::Malformed { abi_revision: 0xFF02_0000_0000_0000.into() })
1017 );
1018
1019 FAKE_VERSION_HISTORY
1020 .check_abi_revision_for_runtime(0x58ea445e942a0005.into())
1021 .expect("level 5 should be supported");
1022 FAKE_VERSION_HISTORY
1023 .check_abi_revision_for_runtime(0x58ea445e942a0007.into())
1024 .expect("level 7 should be supported");
1025 }
1026
1027 #[test]
1028 fn test_pretty_print_abi_error() {
1029 let supported_versions =
1030 VersionVec(FAKE_VERSION_HISTORY.versions[1..4].iter().cloned().collect());
1031
1032 assert_eq!(
1033 AbiRevisionError::PlatformMismatch {
1034 abi_revision: 0xFF01_ABCD_000B_2224.into(),
1035 package_date: NaiveDate::from_ymd_opt(1998, 9, 4).unwrap(),
1036 package_commit_hash: 0xABCD,
1037 platform_date: NaiveDate::from_ymd_opt(2024, 9, 24).unwrap(),
1038 platform_commit_hash: 0x8C4D,
1039 }
1040 .to_string(),
1041 "Unknown platform ABI revision: 0xff01abcd000b2224.
1042Found platform component from 1998-09-04 (Git hash abcd).
1043Platform components must be from 2024-09-24 (Git hash 8c4d)"
1044 );
1045
1046 assert_eq!(
1047 AbiRevisionError::UnstableMismatch {
1048 abi_revision: 0xFF00_ABCD_000B_2224.into(),
1049 package_sdk_date: NaiveDate::from_ymd_opt(1998, 9, 4).unwrap(),
1050 package_sdk_commit_hash: 0xABCD,
1051 platform_date: NaiveDate::from_ymd_opt(2024, 9, 24).unwrap(),
1052 platform_commit_hash: 0x8C4D,
1053 supported_versions: supported_versions.clone()
1054 }
1055 .to_string(),
1056 "Unknown NEXT or HEAD ABI revision: 0xff00abcd000b2224.
1057SDK is from 1998-09-04 (Git hash abcd).
1058Expected 2024-09-24 (Git hash 8c4d)
1059The following API levels are stable and supported:
1060└── 5 (0x58ea445e942a0005)
1061└── 6 (0x58ea445e942a0006)
1062└── 7 (0x58ea445e942a0007)"
1063 );
1064
1065 assert_eq!(
1066 AbiRevisionError::TooNew {
1067 abi_revision: 0x1234.into(),
1068 supported_versions: supported_versions.clone()
1069 }
1070 .to_string(),
1071 "Unknown ABI revision 0x1234. It was probably created after
1072this platform or tool was built. The following API levels are stable and
1073supported:
1074└── 5 (0x58ea445e942a0005)
1075└── 6 (0x58ea445e942a0006)
1076└── 7 (0x58ea445e942a0007)"
1077 );
1078 assert_eq!(
1079 AbiRevisionError::Retired {
1080 version: FAKE_VERSION_HISTORY.versions[0].clone(),
1081 supported_versions: supported_versions.clone()
1082 }
1083 .to_string(),
1084 "Retired API level 4 (0x58ea445e942a0004) cannot run.
1085The following API levels are stable and supported:
1086└── 5 (0x58ea445e942a0005)
1087└── 6 (0x58ea445e942a0006)
1088└── 7 (0x58ea445e942a0007)"
1089 );
1090
1091 assert_eq!(
1092 AbiRevisionError::Invalid.to_string(),
1093 "Invalid ABI revision 0xffffffffffffffff."
1094 );
1095
1096 assert_eq!(
1097 AbiRevisionError::Malformed { abi_revision: 0xFF02_0000_0000_0000.into() }.to_string(),
1098 "ABI revision 0xff02000000000000 has an unrecognized format."
1099 );
1100 }
1101
1102 #[test]
1103 fn test_pretty_print_api_error() {
1104 let supported: Vec<ApiLevel> =
1105 vec![5.into(), 6.into(), 7.into(), ApiLevel::NEXT, ApiLevel::HEAD, ApiLevel::PLATFORM];
1106
1107 assert_eq!(
1108 ApiLevelError::Unknown { api_level: 42.into(), supported: supported.clone() }
1109 .to_string(),
1110 "Unknown target API level: 42. Is the SDK too old to support it?
1111The following API levels are supported: 5, 6, 7, NEXT, HEAD, PLATFORM",
1112 );
1113 assert_eq!(
1114 ApiLevelError::Unsupported {
1115 version: FAKE_VERSION_HISTORY.versions[0].clone(),
1116 supported: supported.clone()
1117 }
1118 .to_string(),
1119 "The SDK no longer supports API level 4.
1120The following API levels are supported: 5, 6, 7, NEXT, HEAD, PLATFORM"
1121 );
1122 }
1123
1124 #[test]
1125 fn test_check_api_level() {
1126 let supported: Vec<ApiLevel> =
1127 vec![5.into(), 6.into(), 7.into(), ApiLevel::NEXT, ApiLevel::HEAD, ApiLevel::PLATFORM];
1128
1129 assert_eq!(
1130 FAKE_VERSION_HISTORY.check_api_level_for_build(42.into()),
1131 Err(ApiLevelError::Unknown { api_level: 42.into(), supported: supported.clone() })
1132 );
1133
1134 assert_eq!(
1135 FAKE_VERSION_HISTORY.check_api_level_for_build(4.into()),
1136 Err(ApiLevelError::Unsupported {
1137 version: FAKE_VERSION_HISTORY.versions[0].clone(),
1138 supported: supported.clone()
1139 })
1140 );
1141 assert_eq!(
1142 FAKE_VERSION_HISTORY.check_api_level_for_build(6.into()),
1143 Ok(0x58ea445e942a0006.into())
1144 );
1145 assert_eq!(
1146 FAKE_VERSION_HISTORY.check_api_level_for_build(7.into()),
1147 Ok(0x58ea445e942a0007.into())
1148 );
1149 assert_eq!(
1150 FAKE_VERSION_HISTORY.check_api_level_for_build(ApiLevel::NEXT),
1151 Ok(0xFF00_8C4D_000B_4751.into())
1152 );
1153 assert_eq!(
1154 FAKE_VERSION_HISTORY.check_api_level_for_build(ApiLevel::HEAD),
1155 Ok(0xFF00_8C4D_000B_4751.into())
1156 );
1157 assert_eq!(
1158 FAKE_VERSION_HISTORY.check_api_level_for_build(ApiLevel::PLATFORM),
1159 Ok(0xFF01_8C4D_000B_4751.into())
1160 );
1161 }
1162
1163 #[test]
1164 fn test_various_getters() {
1165 assert_eq!(
1166 FAKE_VERSION_HISTORY.get_abi_revision_for_platform_components(),
1167 0xFF01_8C4D_000B_4751.into()
1168 );
1169 assert_eq!(
1170 FAKE_VERSION_HISTORY.get_misleading_version_for_ffx(),
1171 FAKE_VERSION_HISTORY.versions[3].clone()
1172 );
1173 assert_eq!(FAKE_VERSION_HISTORY.get_misleading_version_for_ffx().api_level, 7.into());
1174 assert_eq!(
1175 FAKE_VERSION_HISTORY.get_example_supported_version_for_tests(),
1176 FAKE_VERSION_HISTORY.versions[FAKE_VERSION_HISTORY.versions.len() - 4].clone()
1177 );
1178 assert_eq!(
1179 FAKE_VERSION_HISTORY.get_example_supported_version_for_tests().api_level,
1180 7.into()
1181 );
1182 }
1183
1184 #[test]
1185 fn test_explanations() {
1186 let exp = |abi_revision| AbiRevision::from_u64(abi_revision).explanation();
1187
1188 assert_eq!(exp(0x1234_5678_9abc_deff), AbiRevisionExplanation::Normal);
1189 assert_eq!(
1190 exp(0xFF01_abcd_00ab_eeee),
1191 AbiRevisionExplanation::Platform {
1192 date: chrono::NaiveDate::from_num_days_from_ce_opt(0xab_eeee).unwrap(),
1193 git_revision: 0xabcd
1194 }
1195 );
1196 assert_eq!(
1197 exp(0xFF00_1234_0012_3456),
1198 AbiRevisionExplanation::Unstable {
1199 date: chrono::NaiveDate::from_num_days_from_ce_opt(0x12_3456).unwrap(),
1200 git_revision: 0x1234
1201 }
1202 );
1203 assert_eq!(
1206 exp(0xFF00_1234_abcd_9876),
1207 AbiRevisionExplanation::Unstable {
1208 date: chrono::NaiveDate::default(),
1209 git_revision: 0x1234
1210 }
1211 );
1212 assert_eq!(
1213 exp(0xFF01_1234_abcd_9876),
1214 AbiRevisionExplanation::Platform {
1215 date: chrono::NaiveDate::default(),
1216 git_revision: 0x1234
1217 }
1218 );
1219 assert_eq!(exp(0xFFFF_FFFF_FFFF_FFFF), AbiRevisionExplanation::Invalid);
1220 assert_eq!(exp(0xFFFF_FFFF_FFFF_FFFE), AbiRevisionExplanation::Malformed);
1221 }
1222}