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