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