Skip to main content

solana_version/
v4.rs

1use {
2    crate::{client_ids::ClientId, compute_commit},
3    rand::{Rng, rng},
4    serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error as _, ser::Error as _},
5    solana_sanitize::Sanitize,
6    solana_serde_varint as serde_varint,
7    solana_wincode_varint::Leb128Int,
8    std::{convert::TryInto, fmt, mem::MaybeUninit, str::FromStr},
9    wincode::{
10        ReadError, ReadResult, SchemaRead, SchemaWrite, WriteError, WriteResult,
11        config::Config,
12        io::{Reader, Writer},
13    },
14};
15
16#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
17#[derive(Clone, Debug, PartialEq, Eq)]
18pub enum Prerelease {
19    Stable,
20    ReleaseCandidate(u16),
21    Beta(u16),
22    Alpha(u16),
23}
24
25impl Prerelease {
26    const ENCODE_TAG_STABLE: u16 = 0;
27    const ENCODE_TAG_RELEASE_CANDIDATE: u16 = 1;
28    const ENCODE_TAG_BETA: u16 = 2;
29    const ENCODE_TAG_ALPHA: u16 = 3;
30    const IDENTIFIER_RELEASE_CANDIDATE: &str = "rc";
31    const IDENTIFIER_BETA: &str = "beta";
32    const IDENTIFIER_ALPHA: &str = "alpha";
33
34    pub fn patch_is_valid(&self, patch: u16) -> bool {
35        *self == Self::Stable || patch == 0
36    }
37}
38
39#[derive(Clone, Debug, PartialEq)]
40pub enum ParsePrereleaseError {
41    DotSeparatorMissing,
42    NumericPartNotAU16,
43    UnknownIdentifier,
44}
45
46impl fmt::Display for Prerelease {
47    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48        let (identifier, number) = match &self {
49            Self::Stable => return Ok(()),
50            Self::ReleaseCandidate(rc) => (Self::IDENTIFIER_RELEASE_CANDIDATE, rc),
51            Self::Beta(beta) => (Self::IDENTIFIER_BETA, beta),
52            Self::Alpha(alpha) => (Self::IDENTIFIER_ALPHA, alpha),
53        };
54        write!(f, "{identifier}.{number}")
55    }
56}
57
58impl FromStr for Prerelease {
59    type Err = ParsePrereleaseError;
60    fn from_str(s: &str) -> Result<Self, Self::Err> {
61        if s.is_empty() {
62            Ok(Self::Stable)
63        } else {
64            let mut parts = s.rsplitn(2, '.');
65            let num_part = parts
66                .next()
67                .expect("rsplitn returns at least one empty string");
68            let identifier = parts
69                .next()
70                .ok_or(ParsePrereleaseError::DotSeparatorMissing)?;
71            assert_eq!(
72                parts.next(),
73                None,
74                "Safety: rsplitn(2, ...) produces no more than two parts"
75            );
76            let num =
77                u16::from_str(num_part).map_err(|_| ParsePrereleaseError::NumericPartNotAU16)?;
78            match identifier {
79                Self::IDENTIFIER_RELEASE_CANDIDATE => Ok(Self::ReleaseCandidate(num)),
80                Self::IDENTIFIER_BETA => Ok(Self::Beta(num)),
81                Self::IDENTIFIER_ALPHA => Ok(Self::Alpha(num)),
82                _ => Err(ParsePrereleaseError::UnknownIdentifier),
83            }
84        }
85    }
86}
87
88#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
89#[derive(Clone, Debug, PartialEq, Deserialize, Serialize, SchemaRead, SchemaWrite)]
90#[serde(transparent)]
91struct PackedMinor(
92    #[serde(with = "serde_varint")]
93    #[wincode(with = "Leb128Int<u16>")]
94    u16,
95);
96
97#[derive(Clone, Debug, PartialEq)]
98pub enum PackedMinorPackError {
99    MinorTooLarge,
100    InvalidPatchForPrerelease,
101}
102
103#[derive(Clone, Debug, PartialEq)]
104pub enum PackedMinorUnpackError {
105    ReservedBitsSet,
106}
107
108impl PackedMinor {
109    const PRERELEASE_BITS_OFFSET: u32 = 14;
110    const PRERELEASE_MASK_BITS: u32 = 2;
111    const PRERELEASE_FIRST_UNMASKED_BIT: u16 = 1 << Self::PRERELEASE_MASK_BITS;
112    const PRERELEASE_MASK: u16 = Self::PRERELEASE_FIRST_UNMASKED_BIT - 1;
113    const PRERELEASE_MINOR_MAX: u16 = (1 << Self::PRERELEASE_BITS_OFFSET) - 1;
114
115    fn try_pack(
116        minor: u16,
117        patch: u16,
118        prerelease: &Prerelease,
119    ) -> Result<(Self, u16), PackedMinorPackError> {
120        if minor > Self::PRERELEASE_MINOR_MAX {
121            return Err(PackedMinorPackError::MinorTooLarge);
122        }
123        if !prerelease.patch_is_valid(patch) {
124            return Err(PackedMinorPackError::InvalidPatchForPrerelease);
125        }
126        let (prerelease_encode_tag, patch) = match *prerelease {
127            Prerelease::Stable => (Prerelease::ENCODE_TAG_STABLE, patch),
128            Prerelease::ReleaseCandidate(rc) => (Prerelease::ENCODE_TAG_RELEASE_CANDIDATE, rc),
129            Prerelease::Beta(beta) => (Prerelease::ENCODE_TAG_BETA, beta),
130            Prerelease::Alpha(alpha) => (Prerelease::ENCODE_TAG_ALPHA, alpha),
131        };
132        let packed_minor = minor | prerelease_encode_tag << Self::PRERELEASE_BITS_OFFSET;
133        Ok((Self(packed_minor), patch))
134    }
135
136    fn try_unpack(self, patch: u16) -> Result<(u16, u16, Prerelease), PackedMinorUnpackError> {
137        let Self(packed_minor) = self;
138        let shifted_prerelease_bits = packed_minor >> Self::PRERELEASE_BITS_OFFSET;
139
140        let reserved_bits = shifted_prerelease_bits & !Self::PRERELEASE_MASK;
141        if reserved_bits != 0 {
142            return Err(PackedMinorUnpackError::ReservedBitsSet);
143        }
144
145        let prerelease_variant = shifted_prerelease_bits & Self::PRERELEASE_MASK;
146        let minor = packed_minor & !(Self::PRERELEASE_MASK << Self::PRERELEASE_BITS_OFFSET);
147
148        let (patch, prerelease) = match prerelease_variant {
149            Prerelease::ENCODE_TAG_STABLE => (patch, Prerelease::Stable),
150            Prerelease::ENCODE_TAG_RELEASE_CANDIDATE => (0, Prerelease::ReleaseCandidate(patch)),
151            Prerelease::ENCODE_TAG_BETA => (0, Prerelease::Beta(patch)),
152            Prerelease::ENCODE_TAG_ALPHA => (0, Prerelease::Alpha(patch)),
153            Self::PRERELEASE_FIRST_UNMASKED_BIT..=u16::MAX => unreachable!(),
154        };
155        Ok((minor, patch, prerelease))
156    }
157}
158
159#[cfg_attr(
160    feature = "frozen-abi",
161    derive(StableAbi),
162    frozen_abi(
163        abi_digest = "CAvtbh3st7PCvB93NjvDDQj1tBz82BmYPL4cNXMByfLX",
164        abi_serializer = ["bincode", "wincode"],
165        test_roundtrip = "eq_and_wire",
166    )
167)]
168#[derive(Debug, Clone, PartialEq, Eq)]
169pub struct Version {
170    major: u16,
171    minor: u16,
172    patch: u16,
173    commit: u32,
174    feature_set: u32,
175    client: ClientId,
176    prerelease: Prerelease,
177}
178
179impl Version {
180    fn new_from_parts(
181        major: u16,
182        minor: u16,
183        patch: u16,
184        commit: u32,
185        feature_set: u32,
186        client: ClientId,
187        prerelease: Prerelease,
188    ) -> Self {
189        assert!(prerelease.patch_is_valid(patch));
190        Self {
191            major,
192            minor,
193            patch,
194            commit,
195            feature_set,
196            client,
197            prerelease,
198        }
199    }
200
201    pub fn this_build() -> Self {
202        Self::new_from_parts(
203            env!("CARGO_PKG_VERSION_MAJOR").parse().unwrap(),
204            env!("CARGO_PKG_VERSION_MINOR").parse().unwrap(),
205            env!("CARGO_PKG_VERSION_PATCH").parse().unwrap(),
206            compute_commit(option_env!("CI_COMMIT"))
207                .or(compute_commit(option_env!("AGAVE_GIT_COMMIT_HASH")))
208                .unwrap_or_else(|| rng().random::<u32>()),
209            u32::from_le_bytes(agave_feature_set::ID.as_ref()[..4].try_into().unwrap()),
210            ClientId::this_client(),
211            Prerelease::from_str(env!("CARGO_PKG_VERSION_PRE")).unwrap(),
212        )
213    }
214
215    pub fn major(&self) -> u16 {
216        self.major
217    }
218
219    pub fn minor(&self) -> u16 {
220        self.minor
221    }
222
223    pub fn patch(&self) -> u16 {
224        self.patch
225    }
226
227    pub fn commit(&self) -> u32 {
228        self.commit
229    }
230
231    pub fn feature_set(&self) -> u32 {
232        self.feature_set
233    }
234
235    pub fn client(&self) -> &ClientId {
236        &self.client
237    }
238
239    pub fn prerelease(&self) -> &Prerelease {
240        &self.prerelease
241    }
242
243    pub fn as_semver_string(&self) -> String {
244        format!("{self}")
245    }
246
247    pub fn as_detailed_string(&self) -> String {
248        format!(
249            "{} (src:{:08x}; feat:{:08x}, client:{:?})",
250            self, self.commit, self.feature_set, self.client,
251        )
252    }
253
254    pub fn as_semver_version(&self) -> semver::Version {
255        let major = u64::from(self.major);
256        let minor = u64::from(self.minor);
257        let patch = u64::from(self.patch);
258        let pre = semver::Prerelease::new(&self.prerelease.to_string())
259            .expect("solana_version::Prerelease is semver::Prerelease-compatible");
260        let build = semver::BuildMetadata::EMPTY;
261        semver::Version {
262            major,
263            minor,
264            patch,
265            pre,
266            build,
267        }
268    }
269}
270
271impl Default for Version {
272    fn default() -> Self {
273        Self::this_build()
274    }
275}
276
277impl fmt::Display for Version {
278    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
279        let sep = if self.prerelease == Prerelease::Stable {
280            ""
281        } else {
282            "-"
283        };
284        write!(
285            f,
286            "{}.{}.{}{}{}",
287            self.major, self.minor, self.patch, sep, self.prerelease
288        )
289    }
290}
291
292impl Sanitize for Version {}
293
294unsafe impl<C: Config> SchemaWrite<C> for Version {
295    type Src = Version;
296
297    fn size_of(src: &Version) -> WriteResult<usize> {
298        let serialized = Self::to_serialized(src)?;
299        <SerializedVersion as SchemaWrite<C>>::size_of(&serialized)
300    }
301
302    fn write(writer: impl Writer, src: &Version) -> WriteResult<()> {
303        let serialized = Self::to_serialized(src)?;
304        <SerializedVersion as SchemaWrite<C>>::write(writer, &serialized)
305    }
306}
307
308impl Version {
309    fn to_serialized(src: &Version) -> WriteResult<SerializedVersion> {
310        let (packed_minor, patch) = PackedMinor::try_pack(src.minor, src.patch, &src.prerelease)
311            .map_err(|_| WriteError::Custom("Version: invalid minor/patch/prerelease"))?;
312        let client = u16::try_from(src.client.clone())
313            .map_err(|_| WriteError::Custom("Version: invalid client ID"))?;
314        Ok(SerializedVersion {
315            major: src.major,
316            packed_minor,
317            patch,
318            commit: src.commit,
319            feature_set: src.feature_set,
320            client,
321        })
322    }
323}
324
325unsafe impl<'de, C: Config> SchemaRead<'de, C> for Version {
326    type Dst = Version;
327
328    fn read(reader: impl Reader<'de>, dst: &mut MaybeUninit<Self::Dst>) -> ReadResult<()> {
329        let SerializedVersion {
330            major,
331            packed_minor,
332            patch,
333            commit,
334            feature_set,
335            client,
336        } = <SerializedVersion as SchemaRead<'de, C>>::get(reader)?;
337        let (minor, patch, prerelease) = packed_minor
338            .try_unpack(patch)
339            .map_err(|_| ReadError::Custom("Version: invalid packed minor"))?;
340        dst.write(Version {
341            major,
342            minor,
343            patch,
344            commit,
345            feature_set,
346            client: ClientId::from(client),
347            prerelease,
348        });
349        Ok(())
350    }
351}
352
353#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
354#[derive(Deserialize, Serialize, SchemaRead, SchemaWrite)]
355struct SerializedVersion {
356    #[serde(with = "serde_varint")]
357    #[wincode(with = "Leb128Int<u16>")]
358    major: u16,
359    #[serde(rename = "minor")]
360    packed_minor: PackedMinor,
361    #[serde(with = "serde_varint")]
362    #[wincode(with = "Leb128Int<u16>")]
363    patch: u16,
364    commit: u32,
365    feature_set: u32,
366    #[serde(with = "serde_varint")]
367    #[wincode(with = "Leb128Int<u16>")]
368    client: u16,
369}
370
371impl Serialize for Version {
372    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
373    where
374        S: Serializer,
375    {
376        let &Version {
377            major,
378            minor,
379            patch,
380            commit,
381            feature_set,
382            ref client,
383            ref prerelease,
384        } = self;
385
386        let (packed_minor, patch) = PackedMinor::try_pack(minor, patch, prerelease)
387            .map_err(|err| S::Error::custom(format!("{err:?}")))?;
388        let client = u16::try_from(client.clone()).map_err(S::Error::custom)?;
389
390        let serialized_version = SerializedVersion {
391            major,
392            packed_minor,
393            patch,
394            commit,
395            feature_set,
396            client,
397        };
398
399        SerializedVersion::serialize(&serialized_version, serializer)
400    }
401}
402
403impl<'de> Deserialize<'de> for Version {
404    fn deserialize<D>(deserializer: D) -> Result<Version, D::Error>
405    where
406        D: Deserializer<'de>,
407    {
408        let SerializedVersion {
409            major,
410            packed_minor,
411            patch,
412            commit,
413            feature_set,
414            client,
415        } = SerializedVersion::deserialize(deserializer)?;
416
417        let (minor, patch, prerelease) = packed_minor
418            .try_unpack(patch)
419            .map_err(|err| D::Error::custom(format!("{err:?}")))?;
420        let client = ClientId::from(client);
421
422        Ok(Version {
423            major,
424            minor,
425            patch,
426            commit,
427            feature_set,
428            client,
429            prerelease,
430        })
431    }
432}
433
434// Generates a random, always-serializable `Version`. `Version` has a packed
435// wire format with cross-field invariants (`minor` fits in 14 bits, non-stable
436// prereleases force `patch == 0`; see `PackedMinor::try_pack`), so the fields
437// cannot be sampled independently. Used by tests and as the `StableAbi` sampler.
438#[cfg(any(test, feature = "frozen-abi"))]
439fn random_version<R: Rng + ?Sized>(rng: &mut R) -> Version {
440    let minor = rng.random::<u16>() & PackedMinor::PRERELEASE_MINOR_MAX;
441    let (prerelease, patch) = match rng.random::<u8>() % 4 {
442        0 => (Prerelease::Stable, rng.random::<u16>()),
443        1 => (Prerelease::ReleaseCandidate(rng.random()), 0),
444        2 => (Prerelease::Beta(rng.random()), 0),
445        _ => (Prerelease::Alpha(rng.random()), 0),
446    };
447    Version::new_from_parts(
448        rng.random(),
449        minor,
450        patch,
451        rng.random(),
452        rng.random(),
453        ClientId::from(rng.random::<u16>()),
454        prerelease,
455    )
456}
457
458// `StableAbiSample` cannot be derived here because it samples fields
459// independently; `random_version` upholds the cross-field invariants instead.
460#[cfg(feature = "frozen-abi")]
461impl solana_frozen_abi::rand::distr::Distribution<Version>
462    for solana_frozen_abi::rand::distr::StandardUniform
463{
464    fn sample<R: solana_frozen_abi::rand::Rng + ?Sized>(&self, rng: &mut R) -> Version {
465        random_version(rng)
466    }
467}
468
469#[cfg(test)]
470mod tests {
471    use {super::*, crate::v3};
472
473    #[test]
474    fn test_wincode_compatibility() {
475        let mut rng = rand::rng();
476        for _ in 0..1000 {
477            let version = random_version(&mut rng);
478
479            // Serialize with bincode, deserialize with wincode, check results agree.
480            let bincode_bytes = bincode::serialize(&version).unwrap();
481            let wincode_decoded: Version = wincode::deserialize(&bincode_bytes).unwrap();
482            assert_eq!(version, wincode_decoded);
483
484            // Serialize with wincode, deserialize with bincode, check results agree.
485            let wincode_bytes = wincode::serialize(&version).unwrap();
486            let bincode_decoded: Version = bincode::deserialize(&wincode_bytes).unwrap();
487            assert_eq!(version, bincode_decoded);
488
489            assert_eq!(bincode_bytes, wincode_bytes);
490        }
491    }
492
493    #[test]
494    fn test_prerelease_patch_is_valid() {
495        assert!(Prerelease::Alpha(0).patch_is_valid(0));
496        assert!(Prerelease::Beta(0).patch_is_valid(0));
497        assert!(Prerelease::ReleaseCandidate(0).patch_is_valid(0));
498        assert!(Prerelease::Stable.patch_is_valid(0));
499        assert!(Prerelease::Stable.patch_is_valid(1));
500        assert!(Prerelease::Stable.patch_is_valid(u16::MAX));
501
502        assert!(!Prerelease::ReleaseCandidate(0).patch_is_valid(1));
503        assert!(!Prerelease::Beta(0).patch_is_valid(1));
504        assert!(!Prerelease::Alpha(0).patch_is_valid(1));
505    }
506
507    #[test]
508    fn test_prerelease_display() {
509        assert!(format!("{}", Prerelease::Stable).is_empty());
510        assert_eq!(format!("{}", Prerelease::ReleaseCandidate(0)), "rc.0",);
511        assert_eq!(format!("{}", Prerelease::Beta(1)), "beta.1",);
512        assert_eq!(format!("{}", Prerelease::Alpha(2)), "alpha.2",);
513        assert_eq!(format!("{}", Prerelease::Alpha(u16::MAX)), "alpha.65535",);
514    }
515
516    #[test]
517    fn test_prerelease_from_str() {
518        assert_eq!(Prerelease::from_str(""), Ok(Prerelease::Stable));
519        assert_eq!(
520            Prerelease::from_str("rc.0"),
521            Ok(Prerelease::ReleaseCandidate(0))
522        );
523        assert_eq!(Prerelease::from_str("beta.1"), Ok(Prerelease::Beta(1)));
524        assert_eq!(Prerelease::from_str("alpha.2"), Ok(Prerelease::Alpha(2)));
525        assert_eq!(
526            Prerelease::from_str("alpha.65535"),
527            Ok(Prerelease::Alpha(u16::MAX))
528        );
529        assert_eq!(
530            Prerelease::from_str("rc0"),
531            Err(ParsePrereleaseError::DotSeparatorMissing),
532        );
533        assert_eq!(
534            Prerelease::from_str("rc.a"),
535            Err(ParsePrereleaseError::NumericPartNotAU16),
536        );
537        assert_eq!(
538            Prerelease::from_str("rc.-1"),
539            Err(ParsePrereleaseError::NumericPartNotAU16),
540        );
541        assert_eq!(
542            Prerelease::from_str("rc.65536"),
543            Err(ParsePrereleaseError::NumericPartNotAU16),
544        );
545        assert_eq!(
546            Prerelease::from_str("unknown.0"),
547            Err(ParsePrereleaseError::UnknownIdentifier),
548        );
549    }
550
551    #[test]
552    fn test_prerelease_roundtrips() {
553        let test_cases = [
554            ("", Prerelease::Stable),
555            ("rc.0", Prerelease::ReleaseCandidate(0)),
556            ("beta.1", Prerelease::Beta(1)),
557            ("alpha.2", Prerelease::Alpha(2)),
558        ];
559        for (test_str, test_pr) in test_cases.into_iter() {
560            let gen_pr = Prerelease::from_str(test_str).unwrap();
561            let gen_str = test_pr.to_string();
562            let regen_pr = Prerelease::from_str(&gen_str).unwrap();
563            let regen_str = gen_pr.to_string();
564
565            assert_eq!(test_str, gen_str);
566            assert_eq!(test_str, regen_str);
567            assert_eq!(test_pr, gen_pr);
568            assert_eq!(test_pr, regen_pr);
569        }
570    }
571
572    #[test]
573    fn test_prerelease_compatible_with_semver_prerelease() {
574        let test_cases = [
575            ("", Prerelease::Stable),
576            ("rc.0", Prerelease::ReleaseCandidate(0)),
577            ("beta.1", Prerelease::Beta(1)),
578            ("alpha.2", Prerelease::Alpha(2)),
579        ];
580        for (test_str, test_pr) in test_cases.into_iter() {
581            let svpr = semver::Prerelease::new(test_pr.to_string().as_str()).unwrap();
582            assert_eq!(svpr.as_str(), test_str);
583        }
584    }
585
586    #[test]
587    fn test_packed_minor_try_pack() {
588        assert_eq!(
589            PackedMinor::try_pack(0, 0, &Prerelease::Stable),
590            Ok((PackedMinor(0), 0)),
591        );
592        assert_eq!(
593            PackedMinor::try_pack(
594                PackedMinor::PRERELEASE_MINOR_MAX,
595                u16::MAX,
596                &Prerelease::Stable,
597            ),
598            Ok((PackedMinor(0x3FFF), u16::MAX)),
599        );
600        assert_eq!(
601            PackedMinor::try_pack(0, 0, &Prerelease::ReleaseCandidate(0)),
602            Ok((PackedMinor(0x4000), 0)),
603        );
604        assert_eq!(
605            PackedMinor::try_pack(
606                PackedMinor::PRERELEASE_MINOR_MAX,
607                0,
608                &Prerelease::ReleaseCandidate(u16::MAX),
609            ),
610            Ok((PackedMinor(0x7FFF), u16::MAX)),
611        );
612        assert_eq!(
613            PackedMinor::try_pack(0, 0, &Prerelease::Beta(0)),
614            Ok((PackedMinor(0x8000), 0)),
615        );
616        assert_eq!(
617            PackedMinor::try_pack(
618                PackedMinor::PRERELEASE_MINOR_MAX,
619                0,
620                &Prerelease::Beta(u16::MAX),
621            ),
622            Ok((PackedMinor(0xBFFF), u16::MAX)),
623        );
624        assert_eq!(
625            PackedMinor::try_pack(0, 0, &Prerelease::Alpha(0)),
626            Ok((PackedMinor(0xC000), 0)),
627        );
628        assert_eq!(
629            PackedMinor::try_pack(
630                PackedMinor::PRERELEASE_MINOR_MAX,
631                0,
632                &Prerelease::Alpha(u16::MAX),
633            ),
634            Ok((PackedMinor(0xFFFF), u16::MAX)),
635        );
636
637        assert_eq!(
638            PackedMinor::try_pack(
639                PackedMinor::PRERELEASE_MINOR_MAX + 1,
640                0,
641                &Prerelease::Stable
642            ),
643            Err(PackedMinorPackError::MinorTooLarge),
644        );
645        assert_eq!(
646            PackedMinor::try_pack(0, 1, &Prerelease::Beta(0),),
647            Err(PackedMinorPackError::InvalidPatchForPrerelease),
648        );
649    }
650
651    #[test]
652    fn test_packed_minor_try_unpack() {
653        assert_eq!(
654            PackedMinor(0x0000).try_unpack(0),
655            Ok((0, 0, Prerelease::Stable))
656        );
657        assert_eq!(
658            PackedMinor(0x3FFF).try_unpack(u16::MAX),
659            Ok((
660                PackedMinor::PRERELEASE_MINOR_MAX,
661                u16::MAX,
662                Prerelease::Stable
663            )),
664        );
665        assert_eq!(
666            PackedMinor(0x4000).try_unpack(0),
667            Ok((0, 0, Prerelease::ReleaseCandidate(0)))
668        );
669        assert_eq!(
670            PackedMinor(0x7FFF).try_unpack(u16::MAX),
671            Ok((
672                PackedMinor::PRERELEASE_MINOR_MAX,
673                0,
674                Prerelease::ReleaseCandidate(u16::MAX)
675            )),
676        );
677        assert_eq!(
678            PackedMinor(0x8000).try_unpack(0),
679            Ok((0, 0, Prerelease::Beta(0)))
680        );
681        assert_eq!(
682            PackedMinor(0xBFFF).try_unpack(u16::MAX),
683            Ok((
684                PackedMinor::PRERELEASE_MINOR_MAX,
685                0,
686                Prerelease::Beta(u16::MAX)
687            )),
688        );
689        assert_eq!(
690            PackedMinor(0xC000).try_unpack(0),
691            Ok((0, 0, Prerelease::Alpha(0)))
692        );
693        assert_eq!(
694            PackedMinor(0xFFFF).try_unpack(u16::MAX),
695            Ok((
696                PackedMinor::PRERELEASE_MINOR_MAX,
697                0,
698                Prerelease::Alpha(u16::MAX)
699            )),
700        );
701
702        // minor bits above the the prerelease mask are reserved. test that
703        // the aren't set if they exist
704        if PackedMinor::PRERELEASE_BITS_OFFSET + PackedMinor::PRERELEASE_MASK_BITS < u16::BITS {
705            unimplemented!("current configuration leaves no bits reserved");
706        }
707    }
708
709    fn prerelease_stable(_v: u16, patch: u16) -> (Prerelease, u16) {
710        (Prerelease::Stable, patch)
711    }
712
713    fn prerelease_release_candidate(v: u16, _patch: u16) -> (Prerelease, u16) {
714        (Prerelease::ReleaseCandidate(v), 0)
715    }
716
717    fn prerelease_beta(v: u16, _patch: u16) -> (Prerelease, u16) {
718        (Prerelease::Beta(v), 0)
719    }
720
721    fn prerelease_alpha(v: u16, _patch: u16) -> (Prerelease, u16) {
722        (Prerelease::Alpha(v), 0)
723    }
724
725    #[test]
726    fn test_packed_minor_roundtrips() {
727        let test_cases = [
728            prerelease_stable,
729            prerelease_release_candidate,
730            prerelease_beta,
731            prerelease_alpha,
732        ];
733        for prerelease_ctor in &test_cases {
734            for minor in [0, 1, PackedMinor::PRERELEASE_MINOR_MAX] {
735                for patch in [0, 1, u16::MAX] {
736                    for prerelease in [0, 1, u16::MAX] {
737                        let (prerelease, patch) = prerelease_ctor(prerelease, patch);
738                        let (packed_minor, packed_patch) =
739                            PackedMinor::try_pack(minor, patch, &prerelease).unwrap();
740                        let unpacked = packed_minor.try_unpack(packed_patch).unwrap();
741                        assert_eq!((minor, patch, prerelease), unpacked);
742                    }
743                }
744            }
745        }
746    }
747
748    #[test]
749    fn test_v3_and_v4_same_size() {
750        // smallest
751        let v3_version = v3::Version {
752            major: 0,
753            minor: 0,
754            patch: 0,
755            commit: 0,
756            feature_set: 0,
757            client: 0,
758        };
759        let v4_version =
760            Version::new_from_parts(0, 0, 0, 0, 0, ClientId::Agave, Prerelease::Stable);
761        assert_eq!(
762            bincode::serialized_size(&v3_version).unwrap(),
763            bincode::serialized_size(&v4_version).unwrap(),
764        );
765
766        // largest
767        let v3_version = v3::Version {
768            major: u16::MAX,
769            minor: u16::MAX,
770            patch: u16::MAX,
771            commit: u32::MAX,
772            feature_set: u32::MAX,
773            client: u16::MAX,
774        };
775        let v4_version = Version::new_from_parts(
776            u16::MAX,
777            PackedMinor::PRERELEASE_MINOR_MAX,
778            0,
779            u32::MAX,
780            u32::MAX,
781            ClientId::Unknown(u16::MAX),
782            Prerelease::Alpha(u16::MAX),
783        );
784        assert_eq!(
785            bincode::serialized_size(&v3_version).unwrap(),
786            bincode::serialized_size(&v4_version).unwrap(),
787        );
788    }
789
790    #[test]
791    fn test_serde() {
792        let version =
793            Version::new_from_parts(0, 0, 0, 0, 0, ClientId::SolanaLabs, Prerelease::Stable);
794
795        let bytes = bincode::serialize(&version).unwrap();
796        assert_eq!(bytes, [0u8; 12]);
797
798        let de_version: Version = bincode::deserialize(&bytes).unwrap();
799        assert_eq!(version, de_version);
800
801        let version = Version::new_from_parts(
802            u16::MAX,
803            PackedMinor::PRERELEASE_MINOR_MAX,
804            0,
805            u32::MAX,
806            u32::MAX,
807            ClientId::Unknown(u16::MAX),
808            Prerelease::Alpha(u16::MAX),
809        );
810
811        let bytes = bincode::serialize(&version).unwrap();
812        assert_eq!(
813            bytes,
814            [
815                0xff, 0xff, 0x03, 0xff, 0xff, 0x03, 0xff, 0xff, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff,
816                0xff, 0xff, 0xff, 0xff, 0xff, 0x03,
817            ]
818        );
819
820        let de_version: Version = bincode::deserialize(&bytes).unwrap();
821        assert_eq!(version, de_version);
822    }
823
824    #[test]
825    fn test_version_as_detailed_string() {
826        let version =
827            Version::new_from_parts(0, 0, 0, 0, 0, ClientId::this_client(), Prerelease::Stable);
828        assert_eq!(
829            version.as_detailed_string(),
830            "0.0.0 (src:00000000; feat:00000000, client:Agave)",
831        );
832
833        let version = Version::new_from_parts(
834            0,
835            0,
836            0,
837            0,
838            0,
839            ClientId::this_client(),
840            Prerelease::ReleaseCandidate(0),
841        );
842        assert_eq!(
843            version.as_detailed_string(),
844            "0.0.0-rc.0 (src:00000000; feat:00000000, client:Agave)",
845        );
846    }
847}