eix/
lib.rs

1//! EIX Portage Database Format - Rust Parser
2//!
3//! The portage.eix file is a binary database for fast access
4//! to Gentoo Portage ebuild information.
5
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8use std::fs::File;
9use std::io::{self, BufReader, Read};
10use std::path::Path;
11
12/* Basic types */
13pub type UChar = u8;
14pub type UNumber = u32;
15pub type Catsize = u32;
16pub type Treesize = u32;
17pub type OffsetType = i64;
18
19/* Mask Flags constants */
20pub const MASK_NONE: u8 = 0x00;
21pub const MASK_PACKAGE: u8 = 0x01;
22pub const MASK_PROFILE: u8 = 0x02;
23pub const MASK_HARD: u8 = MASK_PACKAGE | MASK_PROFILE;
24pub const MASK_SYSTEM: u8 = 0x04;
25pub const MASK_WORLD: u8 = 0x08;
26pub const MASK_WORLD_SETS: u8 = 0x10;
27pub const MASK_IN_PROFILE: u8 = 0x20;
28pub const MASK_MARKED: u8 = 0x40;
29
30/* Magic Number and Version */
31pub const MAGICNUMCHAR: u8 = 0xFF;
32
33// The file starts with "eix" followed by a newline (0x0A)
34pub const DB_MAGIC: &[u8] = b"eix\n";
35
36// Current database version
37pub const DB_VERSION_CURRENT: DBVersion = 39;
38
39/*
40 * DBHeader - The main structure for the database header
41 *
42 *
43 * Offset 0x00: Magic "eix\n" (4 bytes)
44 * Offset 0x04: Version as byte (e.g. 0x27 = 39)
45 * Offset 0x05: Number of categories as compressed number
46 * Then: Number of overlays as compressed number
47 * Then: Overlay data (path, label for each overlay)
48 * Then: String hashes (EAPI, License, Keywords, IUSE, Slot, Depend)
49 * Then: Feature flags (bitmask)
50 * Then: World sets
51*/
52
53#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct DBHeader {
55    // Current database version
56    pub version: DBVersion,
57
58    // Number of categories
59    pub size: Catsize,
60
61    // Overlays (repository directories)
62    pub overlays: Vec<OverlayIdent>,
63
64    // String hash tables for compression
65    #[serde(skip)]
66    pub eapi_hash: StringHash,
67    #[serde(skip)]
68    pub license_hash: StringHash,
69    #[serde(skip)]
70    pub keywords_hash: StringHash,
71    #[serde(skip)]
72    pub iuse_hash: StringHash,
73    #[serde(skip)]
74    pub slot_hash: StringHash,
75    #[serde(skip)]
76    pub depend_hash: StringHash,
77
78    // Feature flags (SAVE_BITMASK)
79    pub use_depend: bool,       // SAVE_BITMASK_DEP
80    pub use_required_use: bool, // SAVE_BITMASK_REQUIRED_USE
81    pub use_src_uri: bool,      // SAVE_BITMASK_SRC_URI
82
83    // World sets
84    pub world_sets: Vec<String>,
85}
86
87pub type DBVersion = u32;
88
89/*
90 * OverlayIdent - Identification of an overlay/repository
91 */
92#[derive(Debug, Clone, Serialize, Deserialize)]
93pub struct OverlayIdent {
94    pub path: String,  // Path to the overlay (e.g. "/usr/portage")
95    pub label: String, // Label of the overlay (e.g. "gentoo")
96    pub priority: i32, // Priority of the overlay
97}
98
99/*
100 * StringHash - Hash table for string compression
101 */
102#[derive(Debug, Clone, Default)]
103pub struct StringHash {
104    index_to_string: Vec<String>,
105    string_to_index: HashMap<String, usize>,
106}
107
108impl StringHash {
109    pub fn new() -> Self {
110        StringHash::default()
111    }
112
113    pub fn get_index(&self, s: &str) -> Option<usize> {
114        self.string_to_index.get(s).copied()
115    }
116
117    pub fn get_string(&self, index: usize) -> Option<&str> {
118        self.index_to_string.get(index).map(|s| s.as_str())
119    }
120
121    pub fn add(&mut self, s: String) -> usize {
122        if let Some(&idx) = self.string_to_index.get(&s) {
123            return idx;
124        }
125        let idx = self.index_to_string.len();
126        self.string_to_index.insert(s.clone(), idx);
127        self.index_to_string.push(s);
128        idx
129    }
130
131    pub fn len(&self) -> usize {
132        self.index_to_string.len()
133    }
134}
135
136/*
137 * Bitmask for saved features
138 */
139pub type SaveBitmask = UNumber;
140
141pub const SAVE_BITMASK_DEP: SaveBitmask = 0x01;
142pub const SAVE_BITMASK_REQUIRED_USE: SaveBitmask = 0x02;
143pub const SAVE_BITMASK_SRC_URI: SaveBitmask = 0x04;
144
145/*
146 * BasicPart - A part of a version string
147 */
148#[derive(Debug, Clone, Serialize)]
149pub struct BasicPart {
150    pub part_type: PartType,
151    pub part_content: String,
152}
153
154#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
155pub enum PartType {
156    Garbage = 0,
157    Alpha = 1,
158    Beta = 2,
159    Pre = 3,
160    Rc = 4,
161    Revision = 5,
162    InterRev = 6,
163    Patch = 7,
164    Character = 8,
165    Primary = 9,
166    First = 10,
167}
168
169impl PartType {
170    pub fn from_u64(v: u64) -> Self {
171        match v {
172            1 => PartType::Alpha,
173            2 => PartType::Beta,
174            3 => PartType::Pre,
175            4 => PartType::Rc,
176            5 => PartType::Revision,
177            6 => PartType::InterRev,
178            7 => PartType::Patch,
179            8 => PartType::Character,
180            9 => PartType::Primary,
181            10 => PartType::First,
182            _ => PartType::Garbage,
183        }
184    }
185}
186
187/*
188 * Package - Representation of a package
189 */
190#[derive(Debug, Clone, Serialize, Deserialize)]
191pub struct Package {
192    pub category: String,
193    pub name: String,
194    pub description: String,
195    pub homepage: String,
196    pub licenses: String,
197    pub versions: Vec<Version>,
198}
199
200/*
201 * Version - A specific version of a package
202 */
203#[derive(Debug, Clone, Serialize, Deserialize)]
204pub struct Version {
205    #[serde(rename = "version")]
206    pub version_string: String,
207    #[serde(skip)]
208    pub parts: Vec<BasicPart>,
209    pub eapi: String,
210    pub mask_flags: u8,
211    pub properties_flags: u8,
212    pub restrict_flags: u64,
213    pub keywords: Vec<String>,
214    pub slot: String,
215    pub overlay_key: u64,
216    pub reponame: String,
217    pub priority: i32,
218    pub iuse: Vec<String>,
219    pub required_use: Vec<String>,
220    pub depend: Option<Depend>,
221    pub src_uri: Option<String>,
222}
223
224impl Version {
225    pub fn get_full_version(&self) -> String {
226        let mut s = String::new();
227        for part in &self.parts {
228            match part.part_type {
229                PartType::First | PartType::Character | PartType::Garbage => {
230                    s.push_str(&part.part_content);
231                }
232                PartType::Alpha => {
233                    s.push_str("_alpha");
234                    s.push_str(&part.part_content);
235                }
236                PartType::Beta => {
237                    s.push_str("_beta");
238                    s.push_str(&part.part_content);
239                }
240                PartType::Pre => {
241                    s.push_str("_pre");
242                    s.push_str(&part.part_content);
243                }
244                PartType::Rc => {
245                    s.push_str("_rc");
246                    s.push_str(&part.part_content);
247                }
248                PartType::Patch => {
249                    s.push_str("_p");
250                    s.push_str(&part.part_content);
251                }
252                PartType::Revision => {
253                    s.push_str("-r");
254                    s.push_str(&part.part_content);
255                }
256                PartType::InterRev | PartType::Primary => {
257                    s.push('.');
258                    s.push_str(&part.part_content);
259                }
260            }
261        }
262        s
263    }
264}
265
266/*
267 * Depend - Dependencies of a package
268 */
269#[derive(Debug, Clone, Serialize, Deserialize)]
270pub struct Depend {
271    pub depend: Vec<String>,
272    pub rdepend: Vec<String>,
273    pub pdepend: Vec<String>,
274    pub bdepend: Vec<String>,
275    pub idepend: Vec<String>,
276}
277
278/*
279 * Database - The main I/O class
280 */
281pub struct Database {
282    reader: BufReader<File>,
283}
284
285impl Database {
286    /// Opens a database for reading
287    pub fn open_read<P: AsRef<Path>>(path: P) -> io::Result<Self> {
288        let file = File::open(path)?;
289        let reader = BufReader::new(file);
290        Ok(Database { reader })
291    }
292
293    /// Reads a single byte
294    pub fn read_uchar(&mut self) -> io::Result<UChar> {
295        let mut buf = [0u8; 1];
296        self.reader.read_exact(&mut buf)?;
297        Ok(buf[0])
298    }
299
300    /// Reads a number in eix format (variable length)
301    ///
302    /// Format:
303    /// - Values 0-254: directly as one byte
304    /// - Value 255 (0xFF = MAGICNUMCHAR): escape for multi-byte
305    /// - After 0xFF: further 0xFF = more bytes follow
306    /// - After 0xFF: 0x00 = means the value is 255 itself
307    /// - After 0xFF: other value = start of the multi-byte number
308    pub fn read_num(&mut self) -> io::Result<u64> {
309        let ch = self.read_uchar()?;
310
311        // Most common case: number < 255
312        if ch != MAGICNUMCHAR {
313            return Ok(ch as u64);
314        }
315
316        // Multi-byte case
317        let mut to_get = 1usize;
318        let mut result: u64;
319
320        // Count further MAGICNUMCHAR
321        loop {
322            let c = self.read_uchar()?;
323
324            if c == MAGICNUMCHAR {
325                to_get += 1;
326                continue;
327            }
328
329            if c != 0 {
330                result = c as u64;
331            } else {
332                // Leading 0 after MAGICNUMCHAR means MAGICNUMCHAR itself
333                result = MAGICNUMCHAR as u64;
334                to_get -= 1;
335            }
336            break;
337        }
338
339        // Read remaining bytes
340        for _ in 0..to_get {
341            let byte = self.read_uchar()?;
342            result = (result << 8) | (byte as u64);
343        }
344
345        Ok(result)
346    }
347
348    /// Reads a string (length + data)
349    /// Format: <length> <data bytes>
350    /// where length is encoded in eix number format
351    pub fn read_string(&mut self) -> io::Result<String> {
352        let len = self.read_num()? as usize;
353        if len == 0 {
354            return Ok(String::new());
355        }
356
357        let mut buf = vec![0u8; len];
358        self.reader.read_exact(&mut buf)?;
359
360        String::from_utf8(buf).map_err(|e| {
361            io::Error::new(
362                io::ErrorKind::InvalidData,
363                format!("Invalid UTF-8 in string: {}", e),
364            )
365        })
366    }
367
368    /// Reads a string from a hash (index → string)
369    pub fn read_hash_string(&mut self, hash: &StringHash) -> io::Result<String> {
370        let index = self.read_num()? as usize;
371        hash.get_string(index)
372            .map(|s| s.to_string())
373            .ok_or_else(|| {
374                io::Error::new(
375                    io::ErrorKind::InvalidData,
376                    format!("Invalid hash index: {} (hash size: {})", index, hash.len()),
377                )
378            })
379    }
380
381    /// Reads a string hash (list of strings)
382    /// Format <number> <1st string>  ... <nth string>
383    /// <number> is the number of strings in the hash
384    /// where <number> is encoded in eix number format
385    fn read_hash(&mut self) -> io::Result<StringHash> {
386        let count = self.read_num()? as usize;
387        let mut hash = StringHash::new();
388
389        for _ in 0..count {
390            let s = self.read_string()?;
391            hash.add(s);
392        }
393
394        Ok(hash)
395    }
396
397    /// Reads a list of strings from a hash (WordVec)
398    pub fn read_hash_words(&mut self, hash: &StringHash) -> io::Result<Vec<String>> {
399        let count = self.read_num()? as usize;
400        let mut words = Vec::with_capacity(count);
401        for _ in 0..count {
402            words.push(self.read_hash_string(hash)?);
403        }
404        Ok(words)
405    }
406
407    /// Reads a single part of a version
408    pub fn read_part(&mut self) -> io::Result<BasicPart> {
409        let val = self.read_num()?;
410        let part_type = PartType::from_u64(val % 32);
411        let len = (val / 32) as usize;
412        let mut part_content = String::new();
413        if len > 0 {
414            let mut buf = vec![0u8; len];
415            self.reader.read_exact(&mut buf)?;
416            part_content = String::from_utf8(buf).map_err(|e| {
417                io::Error::new(
418                    io::ErrorKind::InvalidData,
419                    format!("Invalid UTF-8 in Part: {}", e),
420                )
421            })?;
422        }
423        Ok(BasicPart {
424            part_type,
425            part_content,
426        })
427    }
428
429    /// Reads the database header
430    pub fn read_header(&mut self, min_version: DBVersion) -> io::Result<DBHeader> {
431        // 1. Read magic string (4 bytes)
432        let mut magic = vec![0u8; DB_MAGIC.len()];
433        self.reader.read_exact(&mut magic)?;
434        if magic != DB_MAGIC {
435            return Err(io::Error::new(
436                io::ErrorKind::InvalidData,
437                format!("Invalid magic: expected {:?}, got {:?}", DB_MAGIC, magic),
438            ));
439        }
440
441        // 2. Read version (eix compressed number)
442        let version = self.read_num()? as DBVersion;
443        if version < min_version {
444            return Err(io::Error::new(
445                io::ErrorKind::InvalidData,
446                format!(
447                    "Database version {} too old (minimum: {})",
448                    version, min_version
449                ),
450            ));
451        }
452
453        // 3. Read number of categories (eix compressed number)
454        let size = self.read_num()? as Catsize;
455
456        // 4. Read number of overlays (compressed number)
457        let overlay_count = self.read_num()? as usize;
458
459        // 5. Read overlays
460        let mut overlays = Vec::with_capacity(overlay_count);
461        for i in 0..overlay_count {
462            let path = self.read_string()?;
463            let label = self.read_string()?;
464            overlays.push(OverlayIdent {
465                path,
466                label,
467                priority: i as i32,
468            });
469        }
470
471        // 6-10. Read string hashes
472        let eapi_hash = self.read_hash()?;
473        let license_hash = self.read_hash()?;
474        let keywords_hash = self.read_hash()?;
475        let iuse_hash = self.read_hash()?;
476        let slot_hash = self.read_hash()?;
477
478        // 11. Read world sets (IMPORTANT: before the bitmask!)
479        let world_set_count = self.read_num()? as usize;
480        let mut world_sets = Vec::with_capacity(world_set_count);
481        for _ in 0..world_set_count {
482            world_sets.push(self.read_string()?);
483        }
484
485        // 12. Read feature flags
486        let bitmask = self.read_num()? as SaveBitmask;
487        let use_depend = (bitmask & SAVE_BITMASK_DEP) != 0;
488        let use_required_use = (bitmask & SAVE_BITMASK_REQUIRED_USE) != 0;
489        let use_src_uri = (bitmask & SAVE_BITMASK_SRC_URI) != 0;
490
491        // 13. Read depend hash (only if enabled in bitmask)
492        let depend_hash = if use_depend {
493            // eix writes a length (offset) before the hash here
494            let _len = self.read_num()?;
495            self.read_hash()?
496        } else {
497            StringHash::new()
498        };
499
500        Ok(DBHeader {
501            version,
502            size,
503            overlays,
504            eapi_hash,
505            license_hash,
506            keywords_hash,
507            iuse_hash,
508            slot_hash,
509            depend_hash,
510            use_depend,
511            use_required_use,
512            use_src_uri,
513            world_sets,
514        })
515    }
516}
517
518/*
519 * PackageReader - Iterator over packages in the database
520 */
521pub struct PackageReader {
522    db: Database,
523    header: DBHeader,
524    frames: Treesize,
525    cat_size: Treesize,
526    cat_name: String,
527}
528
529impl Database {
530    pub fn read_version(&mut self, hdr: &DBHeader) -> io::Result<Version> {
531        let mut eapi = String::new();
532        if hdr.version >= 36 {
533            eapi = self.read_hash_string(&hdr.eapi_hash)?;
534        }
535
536        let mask_flags = self.read_uchar()?;
537        let properties_flags = self.read_uchar()?;
538        let restrict_flags = self.read_num()?;
539
540        // HashedWords  Full keywords string of the ebuild
541        let keywords = self.read_hash_words(&hdr.keywords_hash)?;
542
543        // Vector       VersionPart_\s
544        let part_count = self.read_num()? as usize;
545        let mut parts = Vec::with_capacity(part_count);
546        for _ in 0..part_count {
547            parts.push(self.read_part()?);
548        }
549
550        // HashedString Slot name. The slot name "0" is stored as ""
551        let slot = self.read_hash_string(&hdr.slot_hash)?;
552
553        // Number       Index of the portage overlay (in the overlays block)
554        let overlay_key = self.read_num()?;
555
556        let overlay = hdr.overlays.get(overlay_key as usize).ok_or_else(|| {
557            io::Error::new(
558                io::ErrorKind::InvalidData,
559                format!("Invalid overlay key: {}", overlay_key),
560            )
561        })?;
562        let reponame = overlay.label.clone();
563        let priority = overlay.priority;
564
565        // HashedWords  Useflags of this version
566        let iuse = self.read_hash_words(&hdr.iuse_hash)?;
567
568        // The following occurs only if REQUIRED_USE is stored
569
570        // HashedWords  REQUIRED_USE of this version
571        let mut required_use = Vec::new();
572        if hdr.use_required_use {
573            required_use = self.read_hash_words(&hdr.iuse_hash)?;
574        }
575
576        // The following occurs only if dependencies are stored
577
578        let mut depend = None;
579        if hdr.use_depend {
580            // Number       Length of the next four entries in bytes
581            let _len = self.read_num()?; // Offset
582            let mut dep = Depend {
583                depend: self.read_hash_words(&hdr.depend_hash)?,
584                rdepend: self.read_hash_words(&hdr.depend_hash)?,
585                pdepend: self.read_hash_words(&hdr.depend_hash)?,
586                bdepend: Vec::new(),
587                idepend: Vec::new(),
588            };
589            if hdr.version > 31 {
590                dep.bdepend = self.read_hash_words(&hdr.depend_hash)?;
591            }
592            if hdr.version > 38 {
593                dep.idepend = self.read_hash_words(&hdr.depend_hash)?;
594            }
595            depend = Some(dep);
596        }
597
598        // The following occurs only if SRC_URI is stored
599
600        // String       SRC_URI
601        let mut src_uri = None;
602        if hdr.use_src_uri {
603            src_uri = Some(self.read_string()?);
604        }
605
606        // finished reading version
607
608        Ok(Version {
609            version_string: String::new(),
610            parts,
611            eapi,
612            mask_flags,
613            properties_flags,
614            restrict_flags,
615            keywords,
616            slot,
617            overlay_key,
618            reponame,
619            priority,
620            iuse,
621            required_use,
622            depend,
623            src_uri,
624        })
625    }
626}
627
628impl PackageReader {
629    pub fn new(db: Database, header: DBHeader) -> Self {
630        let frames = header.size;
631        PackageReader {
632            db,
633            header,
634            frames,
635            cat_size: 0,
636            cat_name: String::new(),
637        }
638    }
639
640    /// Moves to the next category
641    pub fn next_category(&mut self) -> io::Result<bool> {
642        if self.frames == 0 {
643            return Ok(false);
644        }
645
646        self.cat_name = self.db.read_string()?;
647        self.cat_size = self.db.read_num()? as Treesize;
648        self.frames -= 1;
649
650        Ok(true)
651    }
652
653    pub fn current_category(&self) -> &str {
654        &self.cat_name
655    }
656
657    /// Reads the next package in the current category
658    pub fn read_package(&mut self) -> io::Result<Option<Package>> {
659        if self.cat_size == 0 {
660            return Ok(None);
661        }
662
663        // eix writes a length (offset) before each package
664        let _pkg_len = self.db.read_num()?;
665
666        let name = self.db.read_string()?;
667        let description = self.db.read_string()?;
668        let homepage = self.db.read_string()?;
669        let licenses = self.db.read_hash_string(&self.header.license_hash)?;
670
671        let version_count = self.db.read_num()? as usize;
672        let mut versions = Vec::with_capacity(version_count);
673        for _ in 0..version_count {
674            let mut v = self.db.read_version(&self.header)?;
675            v.version_string = v.get_full_version();
676            versions.push(v);
677        }
678
679        self.cat_size -= 1;
680
681        Ok(Some(Package {
682            name,
683            description,
684            homepage,
685            licenses,
686            versions,
687            category: self.cat_name.clone(),
688        }))
689    }
690}
691
692// For tests
693#[cfg(test)]
694mod tests {
695    use super::*;
696
697    #[test]
698    fn test_magic_bytes() {
699        assert_eq!(DB_MAGIC, b"eix\n");
700        assert_eq!(DB_MAGIC.len(), 4);
701    }
702
703    #[test]
704    fn test_version() {
705        assert_eq!(DB_VERSION_CURRENT, 39);
706    }
707
708    #[test]
709    fn test_string_hash() {
710        let mut hash = StringHash::new();
711        let idx1 = hash.add("test".to_string());
712        let idx2 = hash.add("another".to_string());
713        let idx3 = hash.add("test".to_string());
714
715        assert_eq!(idx1, 0);
716        assert_eq!(idx2, 1);
717        assert_eq!(idx1, idx3);
718        assert_eq!(hash.len(), 2);
719
720        assert_eq!(hash.get_string(0), Some("test"));
721        assert_eq!(hash.get_string(1), Some("another"));
722        assert_eq!(hash.get_string(2), None);
723
724        assert_eq!(hash.get_index("test"), Some(0));
725        assert_eq!(hash.get_index("another"), Some(1));
726        assert_eq!(hash.get_index("nonexistent"), None);
727    }
728
729    #[test]
730    fn test_part_type_from_u64() {
731        assert_eq!(PartType::from_u64(1), PartType::Alpha);
732        assert_eq!(PartType::from_u64(5), PartType::Revision);
733        assert_eq!(PartType::from_u64(10), PartType::First);
734        assert_eq!(PartType::from_u64(0), PartType::Garbage);
735        assert_eq!(PartType::from_u64(99), PartType::Garbage);
736    }
737
738    // Mock Database for testing read_num and other methods
739    struct MockDatabase {
740        data: Vec<u8>,
741        pos: usize,
742    }
743
744    impl MockDatabase {
745        fn new(data: Vec<u8>) -> Self {
746            MockDatabase { data, pos: 0 }
747        }
748
749        fn read_uchar(&mut self) -> io::Result<u8> {
750            if self.pos >= self.data.len() {
751                return Err(io::Error::new(io::ErrorKind::UnexpectedEof, "EOF"));
752            }
753            let val = self.data[self.pos];
754            self.pos += 1;
755            Ok(val)
756        }
757
758        // Adapted read_num for tests without needing a real file
759        fn read_num(&mut self) -> io::Result<u64> {
760            let ch = self.read_uchar()?;
761            if ch != MAGICNUMCHAR {
762                return Ok(ch as u64);
763            }
764            let mut to_get = 1usize;
765            let mut result: u64;
766            loop {
767                let c = self.read_uchar()?;
768                if c == MAGICNUMCHAR {
769                    to_get += 1;
770                    continue;
771                }
772                if c != 0 {
773                    result = c as u64;
774                } else {
775                    result = MAGICNUMCHAR as u64;
776                    to_get -= 1;
777                }
778                break;
779            }
780            for _ in 0..to_get {
781                let byte = self.read_uchar()?;
782                result = (result << 8) | (byte as u64);
783            }
784            Ok(result)
785        }
786    }
787
788    #[test]
789    fn test_read_num() {
790        let cases = vec![
791            (0x00, vec![0x00]),
792            (0xFE, vec![0xFE]),
793            (0xFF, vec![0xFF, 0x00]),
794            (0x0100, vec![0xFF, 0x01, 0x00]),
795            (0x01FF, vec![0xFF, 0x01, 0xFF]),
796            (0xFEFF, vec![0xFF, 0xFE, 0xFF]),
797            (0xFF00, vec![0xFF, 0xFF, 0x00, 0x00]),
798            (0xFF01, vec![0xFF, 0xFF, 0x00, 0x01]),
799            (0x010000, vec![0xFF, 0xFF, 0x01, 0x00, 0x00]),
800            (0xABCDEF, vec![0xFF, 0xFF, 0xAB, 0xCD, 0xEF]),
801            (0xFFABCD, vec![0xFF, 0xFF, 0xFF, 0x00, 0xAB, 0xCD]),
802            (0x01ABCDEF, vec![0xFF, 0xFF, 0xFF, 0x01, 0xAB, 0xCD, 0xEF]),
803        ];
804
805        for (expected, bytes) in cases {
806            let mut db = MockDatabase::new(bytes.clone());
807
808            let result = db.read_num().expect(&format!("Failed to read {:?}", bytes));
809            assert_eq!(
810                result, expected,
811                "Case {:?} failed: expected 0x{:X}, got 0x{:X}",
812                bytes, expected, result
813            );
814        }
815    }
816
817    #[test]
818    fn test_version_full_string() {
819        let v = Version {
820            version_string: "1.2.3".to_string(),
821            parts: vec![
822                BasicPart {
823                    part_type: PartType::First,
824                    part_content: "1".to_string(),
825                },
826                BasicPart {
827                    part_type: PartType::Primary,
828                    part_content: "2".to_string(),
829                },
830                BasicPart {
831                    part_type: PartType::Primary,
832                    part_content: "3".to_string(),
833                },
834                BasicPart {
835                    part_type: PartType::Alpha,
836                    part_content: "1".to_string(),
837                },
838                BasicPart {
839                    part_type: PartType::Revision,
840                    part_content: "1".to_string(),
841                },
842            ],
843            eapi: "8".to_string(),
844            mask_flags: 0,
845            properties_flags: 0,
846            restrict_flags: 0,
847            keywords: vec![],
848            slot: "0".to_string(),
849            overlay_key: 0,
850            reponame: "gentoo".to_string(),
851            priority: 0,
852            iuse: vec![],
853            required_use: vec![],
854            depend: None,
855            src_uri: None,
856        };
857        assert_eq!(v.get_full_version(), "1.2.3_alpha1-r1");
858    }
859}