1use anyhow::{Context, bail};
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 ("some-version", "1", "invalid digit found in string"),
820 ("-1", "1", "invalid digit found in string"),
821 (
822 "1",
823 "some-revision",
824 "invalid value: string \"some-revision\", expected an unsigned integer at line 1 column 113",
825 ),
826 (
827 "1",
828 "-1",
829 "invalid value: string \"-1\", expected an unsigned integer at line 1 column 102",
830 ),
831 ] {
832 let expected_bytes = serde_json::to_vec(&serde_json::json!({
833 "data": {
834 "name": VERSION_HISTORY_NAME,
835 "type": VERSION_HISTORY_TYPE,
836 "api_levels": {
837 api_level:{
838 "abi_revision": abi_revision,
839 "status": Status::InDevelopment,
840 }
841 },
842 "special_api_levels": {},
843 },
844 "schema_id": VERSION_HISTORY_SCHEMA_ID,
845 }))
846 .unwrap();
847
848 assert_eq!(parse_version_history(&expected_bytes[..]).unwrap_err().to_string(), err);
849 }
850 }
851
852 #[test]
853 fn test_parse_history_rejects_bogus_special_levels() {
854 let input = br#"{
855 "data": {
856 "name": "Platform version map",
857 "type": "version_history",
858 "api_levels": {},
859 "special_api_levels": {
860 "WACKY_WAVING_INFLATABLE_ARM_FLAILING_TUBE_MAN": {
861 "as_u32": 1234,
862 "abi_revision": "0x50cbc6e8a39e1e2c",
863 "status": "in-development"
864 }
865 }
866 },
867 "schema_id": "https://fuchsia.dev/schema/version_history.json"
868 }"#;
869
870 assert_eq!(
871 parse_version_history(&input[..]).unwrap_err().to_string(),
872 "Unknown special API level: WACKY_WAVING_INFLATABLE_ARM_FLAILING_TUBE_MAN"
873 );
874 }
875
876 #[test]
877 fn test_parse_history_rejects_misnumbered_special_levels() {
878 let input = br#"{
879 "data": {
880 "name": "Platform version map",
881 "type": "version_history",
882 "api_levels": {},
883 "special_api_levels": {
884 "HEAD": {
885 "as_u32": 1234,
886 "abi_revision": "0x50cbc6e8a39e1e2c",
887 "status": "in-development"
888 }
889 }
890 },
891 "schema_id": "https://fuchsia.dev/schema/version_history.json"
892 }"#;
893
894 assert_eq!(
895 parse_version_history(&input[..]).unwrap_err().to_string(),
896 "Special API level HEAD had unexpected numerical value 1234 (Expected 4292870144)"
897 );
898 }
899
900 #[test]
901 fn test_serialize_abi_revision() {
902 {
903 let abi = AbiRevision::from_u64(16);
904 let str = "\"0x0000000000000010\"";
905 assert_eq!(serde_json::to_string(&abi).unwrap(), str);
906 assert_eq!(serde_json::from_str::<AbiRevision>(str).unwrap(), abi);
907 }
908 {
909 let abi = AbiRevision::from_u64(0x58ea445e942a0004);
910 let str = "\"0x58ea445e942a0004\"";
911 assert_eq!(serde_json::to_string(&abi).unwrap(), str);
912 assert_eq!(serde_json::from_str::<AbiRevision>(str).unwrap(), abi);
913 }
914 }
915
916 pub const FAKE_VERSION_HISTORY: VersionHistory = VersionHistory {
917 versions: &[
918 Version {
919 api_level: ApiLevel::from_u32(4),
920 abi_revision: AbiRevision::from_u64(0x58ea445e942a0004),
921 status: Status::Unsupported,
922 },
923 Version {
924 api_level: ApiLevel::from_u32(5),
925 abi_revision: AbiRevision::from_u64(0x58ea445e942a0005),
926 status: Status::Supported,
927 },
928 Version {
929 api_level: ApiLevel::from_u32(6),
930 abi_revision: AbiRevision::from_u64(0x58ea445e942a0006),
931 status: Status::Supported,
932 },
933 Version {
934 api_level: ApiLevel::from_u32(7),
935 abi_revision: AbiRevision::from_u64(0x58ea445e942a0007),
936 status: Status::Supported,
937 },
938 Version {
939 api_level: ApiLevel::NEXT,
940 abi_revision: AbiRevision::from_u64(0xFF00_8C4D_000B_4751),
941 status: Status::InDevelopment,
942 },
943 Version {
944 api_level: ApiLevel::HEAD,
945 abi_revision: AbiRevision::from_u64(0xFF00_8C4D_000B_4751),
946 status: Status::InDevelopment,
947 },
948 Version {
949 api_level: ApiLevel::PLATFORM,
950 abi_revision: AbiRevision::from_u64(0xFF01_8C4D_000B_4751),
951 status: Status::InDevelopment,
952 },
953 ],
954 };
955
956 #[test]
957 fn test_check_abi_revision() {
958 let supported_versions =
959 VersionVec(FAKE_VERSION_HISTORY.versions[1..4].iter().cloned().collect());
960
961 assert_eq!(
962 FAKE_VERSION_HISTORY.check_abi_revision_for_runtime(0xFF01_ABCD_000B_2224.into()),
963 Err(AbiRevisionError::PlatformMismatch {
964 abi_revision: 0xFF01_ABCD_000B_2224.into(),
965 package_date: NaiveDate::from_ymd_opt(1998, 9, 4).unwrap(),
966 package_commit_hash: 0xABCD,
967 platform_date: NaiveDate::from_ymd_opt(2024, 9, 24).unwrap(),
968 platform_commit_hash: 0x8C4D,
969 })
970 );
971
972 assert_eq!(
973 FAKE_VERSION_HISTORY.check_abi_revision_for_runtime(0xFF00_ABCD_000B_2224.into()),
974 Err(AbiRevisionError::UnstableMismatch {
975 abi_revision: 0xFF00_ABCD_000B_2224.into(),
976 package_sdk_date: NaiveDate::from_ymd_opt(1998, 9, 4).unwrap(),
977 package_sdk_commit_hash: 0xABCD,
978 platform_date: NaiveDate::from_ymd_opt(2024, 9, 24).unwrap(),
979 platform_commit_hash: 0x8C4D,
980 supported_versions: supported_versions.clone()
981 })
982 );
983
984 assert_eq!(
985 FAKE_VERSION_HISTORY.check_abi_revision_for_runtime(0x1234.into()),
986 Err(AbiRevisionError::TooNew {
987 abi_revision: 0x1234.into(),
988 supported_versions: supported_versions.clone()
989 })
990 );
991
992 assert_eq!(
993 FAKE_VERSION_HISTORY.check_abi_revision_for_runtime(0x58ea445e942a0004.into()),
994 Err(AbiRevisionError::Retired {
995 version: FAKE_VERSION_HISTORY.versions[0].clone(),
996
997 supported_versions: supported_versions.clone()
998 })
999 );
1000
1001 assert_eq!(
1002 FAKE_VERSION_HISTORY.check_abi_revision_for_runtime(AbiRevision::INVALID),
1003 Err(AbiRevisionError::Invalid)
1004 );
1005
1006 assert_eq!(
1007 FAKE_VERSION_HISTORY.check_abi_revision_for_runtime(0xFF02_0000_0000_0000.into()),
1008 Err(AbiRevisionError::Malformed { abi_revision: 0xFF02_0000_0000_0000.into() })
1009 );
1010
1011 FAKE_VERSION_HISTORY
1012 .check_abi_revision_for_runtime(0x58ea445e942a0005.into())
1013 .expect("level 5 should be supported");
1014 FAKE_VERSION_HISTORY
1015 .check_abi_revision_for_runtime(0x58ea445e942a0007.into())
1016 .expect("level 7 should be supported");
1017 }
1018
1019 #[test]
1020 fn test_pretty_print_abi_error() {
1021 let supported_versions =
1022 VersionVec(FAKE_VERSION_HISTORY.versions[1..4].iter().cloned().collect());
1023
1024 assert_eq!(
1025 AbiRevisionError::PlatformMismatch {
1026 abi_revision: 0xFF01_ABCD_000B_2224.into(),
1027 package_date: NaiveDate::from_ymd_opt(1998, 9, 4).unwrap(),
1028 package_commit_hash: 0xABCD,
1029 platform_date: NaiveDate::from_ymd_opt(2024, 9, 24).unwrap(),
1030 platform_commit_hash: 0x8C4D,
1031 }
1032 .to_string(),
1033 "Unknown platform ABI revision: 0xff01abcd000b2224.
1034Found platform component from 1998-09-04 (Git hash abcd).
1035Platform components must be from 2024-09-24 (Git hash 8c4d)"
1036 );
1037
1038 assert_eq!(
1039 AbiRevisionError::UnstableMismatch {
1040 abi_revision: 0xFF00_ABCD_000B_2224.into(),
1041 package_sdk_date: NaiveDate::from_ymd_opt(1998, 9, 4).unwrap(),
1042 package_sdk_commit_hash: 0xABCD,
1043 platform_date: NaiveDate::from_ymd_opt(2024, 9, 24).unwrap(),
1044 platform_commit_hash: 0x8C4D,
1045 supported_versions: supported_versions.clone()
1046 }
1047 .to_string(),
1048 "Unknown NEXT or HEAD ABI revision: 0xff00abcd000b2224.
1049SDK is from 1998-09-04 (Git hash abcd).
1050Expected 2024-09-24 (Git hash 8c4d)
1051The following API levels are stable and supported:
1052└── 5 (0x58ea445e942a0005)
1053└── 6 (0x58ea445e942a0006)
1054└── 7 (0x58ea445e942a0007)"
1055 );
1056
1057 assert_eq!(
1058 AbiRevisionError::TooNew {
1059 abi_revision: 0x1234.into(),
1060 supported_versions: supported_versions.clone()
1061 }
1062 .to_string(),
1063 "Unknown ABI revision 0x1234. It was probably created after
1064this platform or tool was built. The following API levels are stable and
1065supported:
1066└── 5 (0x58ea445e942a0005)
1067└── 6 (0x58ea445e942a0006)
1068└── 7 (0x58ea445e942a0007)"
1069 );
1070 assert_eq!(
1071 AbiRevisionError::Retired {
1072 version: FAKE_VERSION_HISTORY.versions[0].clone(),
1073 supported_versions: supported_versions.clone()
1074 }
1075 .to_string(),
1076 "Retired API level 4 (0x58ea445e942a0004) cannot run.
1077The following API levels are stable and supported:
1078└── 5 (0x58ea445e942a0005)
1079└── 6 (0x58ea445e942a0006)
1080└── 7 (0x58ea445e942a0007)"
1081 );
1082
1083 assert_eq!(
1084 AbiRevisionError::Invalid.to_string(),
1085 "Invalid ABI revision 0xffffffffffffffff."
1086 );
1087
1088 assert_eq!(
1089 AbiRevisionError::Malformed { abi_revision: 0xFF02_0000_0000_0000.into() }.to_string(),
1090 "ABI revision 0xff02000000000000 has an unrecognized format."
1091 );
1092 }
1093
1094 #[test]
1095 fn test_pretty_print_api_error() {
1096 let supported: Vec<ApiLevel> =
1097 vec![5.into(), 6.into(), 7.into(), ApiLevel::NEXT, ApiLevel::HEAD, ApiLevel::PLATFORM];
1098
1099 assert_eq!(
1100 ApiLevelError::Unknown { api_level: 42.into(), supported: supported.clone() }
1101 .to_string(),
1102 "Unknown target API level: 42. Is the SDK too old to support it?
1103The following API levels are supported: 5, 6, 7, NEXT, HEAD, PLATFORM",
1104 );
1105 assert_eq!(
1106 ApiLevelError::Unsupported {
1107 version: FAKE_VERSION_HISTORY.versions[0].clone(),
1108 supported: supported.clone()
1109 }
1110 .to_string(),
1111 "The SDK no longer supports API level 4.
1112The following API levels are supported: 5, 6, 7, NEXT, HEAD, PLATFORM"
1113 );
1114 }
1115
1116 #[test]
1117 fn test_check_api_level() {
1118 let supported: Vec<ApiLevel> =
1119 vec![5.into(), 6.into(), 7.into(), ApiLevel::NEXT, ApiLevel::HEAD, ApiLevel::PLATFORM];
1120
1121 assert_eq!(
1122 FAKE_VERSION_HISTORY.check_api_level_for_build(42.into()),
1123 Err(ApiLevelError::Unknown { api_level: 42.into(), supported: supported.clone() })
1124 );
1125
1126 assert_eq!(
1127 FAKE_VERSION_HISTORY.check_api_level_for_build(4.into()),
1128 Err(ApiLevelError::Unsupported {
1129 version: FAKE_VERSION_HISTORY.versions[0].clone(),
1130 supported: supported.clone()
1131 })
1132 );
1133 assert_eq!(
1134 FAKE_VERSION_HISTORY.check_api_level_for_build(6.into()),
1135 Ok(0x58ea445e942a0006.into())
1136 );
1137 assert_eq!(
1138 FAKE_VERSION_HISTORY.check_api_level_for_build(7.into()),
1139 Ok(0x58ea445e942a0007.into())
1140 );
1141 assert_eq!(
1142 FAKE_VERSION_HISTORY.check_api_level_for_build(ApiLevel::NEXT),
1143 Ok(0xFF00_8C4D_000B_4751.into())
1144 );
1145 assert_eq!(
1146 FAKE_VERSION_HISTORY.check_api_level_for_build(ApiLevel::HEAD),
1147 Ok(0xFF00_8C4D_000B_4751.into())
1148 );
1149 assert_eq!(
1150 FAKE_VERSION_HISTORY.check_api_level_for_build(ApiLevel::PLATFORM),
1151 Ok(0xFF01_8C4D_000B_4751.into())
1152 );
1153 }
1154
1155 #[test]
1156 fn test_various_getters() {
1157 assert_eq!(
1158 FAKE_VERSION_HISTORY.get_abi_revision_for_platform_components(),
1159 0xFF01_8C4D_000B_4751.into()
1160 );
1161 assert_eq!(
1162 FAKE_VERSION_HISTORY.get_misleading_version_for_ffx(),
1163 FAKE_VERSION_HISTORY.versions[3].clone()
1164 );
1165 assert_eq!(FAKE_VERSION_HISTORY.get_misleading_version_for_ffx().api_level, 7.into());
1166 assert_eq!(
1167 FAKE_VERSION_HISTORY.get_example_supported_version_for_tests(),
1168 FAKE_VERSION_HISTORY.versions[FAKE_VERSION_HISTORY.versions.len() - 4].clone()
1169 );
1170 assert_eq!(
1171 FAKE_VERSION_HISTORY.get_example_supported_version_for_tests().api_level,
1172 7.into()
1173 );
1174 }
1175
1176 #[test]
1177 fn test_explanations() {
1178 let exp = |abi_revision| AbiRevision::from_u64(abi_revision).explanation();
1179
1180 assert_eq!(exp(0x1234_5678_9abc_deff), AbiRevisionExplanation::Normal);
1181 assert_eq!(
1182 exp(0xFF01_abcd_00ab_eeee),
1183 AbiRevisionExplanation::Platform {
1184 date: chrono::NaiveDate::from_num_days_from_ce_opt(0xab_eeee).unwrap(),
1185 git_revision: 0xabcd
1186 }
1187 );
1188 assert_eq!(
1189 exp(0xFF00_1234_0012_3456),
1190 AbiRevisionExplanation::Unstable {
1191 date: chrono::NaiveDate::from_num_days_from_ce_opt(0x12_3456).unwrap(),
1192 git_revision: 0x1234
1193 }
1194 );
1195 assert_eq!(
1198 exp(0xFF00_1234_abcd_9876),
1199 AbiRevisionExplanation::Unstable {
1200 date: chrono::NaiveDate::default(),
1201 git_revision: 0x1234
1202 }
1203 );
1204 assert_eq!(
1205 exp(0xFF01_1234_abcd_9876),
1206 AbiRevisionExplanation::Platform {
1207 date: chrono::NaiveDate::default(),
1208 git_revision: 0x1234
1209 }
1210 );
1211 assert_eq!(exp(0xFFFF_FFFF_FFFF_FFFF), AbiRevisionExplanation::Invalid);
1212 assert_eq!(exp(0xFFFF_FFFF_FFFF_FFFE), AbiRevisionExplanation::Malformed);
1213 }
1214}