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#[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#[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 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 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 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 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 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}