x11rb/cursor/
parse_cursor.rs

1//! Parse the contents of a cursor file
2
3// This code is loosely based on parse_cursor_file.c from libxcb-cursor, which is:
4//   Copyright © 2013 Michael Stapelberg
5// and is covered by MIT/X Consortium License
6
7use std::io::{Read, Seek, SeekFrom};
8
9const FILE_MAGIC: u32 = 0x7275_6358;
10const IMAGE_TYPE: u32 = 0xfffd_0002;
11const IMAGE_MAX_SIZE: u16 = 0x7fff;
12
13/// An error that occurred while parsing
14#[derive(Debug)]
15pub(crate) enum Error {
16    /// An I/O error occurred
17    Io,
18
19    /// The file did not begin with the expected magic
20    InvalidMagic,
21
22    /// The file contained unrealistically many images
23    TooManyEntries,
24
25    /// The file contains no images
26    NoImages,
27
28    /// Some image's entry is corrupt and not self-consistent
29    CorruptImage,
30
31    /// An entry is larger than the maximum size
32    ImageTooLarge,
33}
34
35impl From<std::io::Error> for Error {
36    fn from(_: std::io::Error) -> Self {
37        Error::Io
38    }
39}
40
41/// Read a single `u32` from a cursor file
42///
43/// The file stores these entries in little endian.
44fn read_u32<R: Read>(read: &mut R) -> Result<u32, Error> {
45    let mut buffer = [0; 4];
46    read.read_exact(&mut buffer)?;
47    Ok(u32::from_le_bytes(buffer))
48}
49
50/// A single cursor image
51#[derive(Debug)]
52pub(crate) struct Image {
53    pub(crate) width: u16,
54    pub(crate) height: u16,
55    pub(crate) x_hot: u16,
56    pub(crate) y_hot: u16,
57    pub(crate) delay: u32,
58    pub(crate) pixels: Vec<u32>,
59}
60
61impl Image {
62    /// Read an `Image` from a reader
63    fn read<R: Read>(read: &mut R, expected_kind: u32, expected_size: u32) -> Result<Self, Error> {
64        let (_header, kind, size, _version) = (
65            read_u32(read)?,
66            read_u32(read)?,
67            read_u32(read)?,
68            read_u32(read)?,
69        );
70        if (kind, size) != (expected_kind, expected_size) {
71            return Err(Error::CorruptImage);
72        }
73
74        fn convert_size(size: u32) -> Result<u16, Error> {
75            size.try_into()
76                .ok()
77                .filter(|&size| size <= IMAGE_MAX_SIZE)
78                .ok_or(Error::ImageTooLarge)
79        }
80
81        let (width, height) = (
82            convert_size(read_u32(read)?)?,
83            convert_size(read_u32(read)?)?,
84        );
85        let (x_hot, y_hot) = (read_u32(read)?, read_u32(read)?);
86        let delay = read_u32(read)?;
87        let x_hot = x_hot.try_into().or(Err(Error::ImageTooLarge))?;
88        let y_hot = y_hot.try_into().or(Err(Error::ImageTooLarge))?;
89
90        let num_pixels = u32::from(width) * u32::from(height);
91        let pixels = (0..num_pixels)
92            .map(|_| read_u32(read))
93            .collect::<Result<Vec<_>, _>>()?;
94
95        Ok(Image {
96            width,
97            height,
98            x_hot,
99            y_hot,
100            delay,
101            pixels,
102        })
103    }
104}
105
106/// A TOC entry in a cursor file
107#[derive(Debug)]
108struct TocEntry {
109    kind: u32,
110    size: u32,
111    position: u32,
112}
113
114impl TocEntry {
115    /// Read a `TocEntry` from a reader
116    fn read<R: Read>(read: &mut R) -> Result<Self, Error> {
117        Ok(TocEntry {
118            kind: read_u32(read)?,
119            size: read_u32(read)?,
120            position: read_u32(read)?,
121        })
122    }
123}
124
125/// Find the size of the image in the toc with a size as close as possible to the desired size.
126fn find_best_size(toc: &[TocEntry], desired_size: u32) -> Result<u32, Error> {
127    fn distance(a: u32, b: u32) -> u32 {
128        a.max(b) - a.min(b)
129    }
130
131    fn is_better(desired_size: u32, entry: &TocEntry, result: &Result<u32, Error>) -> bool {
132        match result {
133            Err(_) => true,
134            Ok(size) => distance(entry.size, desired_size) < distance(*size, desired_size),
135        }
136    }
137
138    let mut result = Err(Error::NoImages);
139    for entry in toc {
140        // If this is better than the best so far, replace best
141        if entry.kind == IMAGE_TYPE && is_better(desired_size, entry, &result) {
142            result = Ok(entry.size)
143        }
144    }
145    result
146}
147
148/// Parse a complete cursor file
149pub(crate) fn parse_cursor<R: Read + Seek>(
150    input: &mut R,
151    desired_size: u32,
152) -> Result<Vec<Image>, Error> {
153    let (magic, header, _version, ntoc) = (
154        read_u32(input)?,
155        read_u32(input)?,
156        read_u32(input)?,
157        read_u32(input)?,
158    );
159
160    if magic != FILE_MAGIC {
161        return Err(Error::InvalidMagic);
162    }
163
164    if ntoc > 0x1_0000 {
165        return Err(Error::TooManyEntries);
166    }
167
168    // Read the table of contents
169    let _ = input.seek(SeekFrom::Start(header.into()))?;
170    let toc = (0..ntoc)
171        .map(|_| TocEntry::read(input))
172        .collect::<Result<Vec<_>, _>>()?;
173
174    // Find the cursor size that we should load
175    let size = find_best_size(&toc, desired_size)?;
176
177    // Now load all cursors that have the right size
178    let mut result = Vec::new();
179    for entry in toc {
180        if entry.kind != IMAGE_TYPE || entry.size != size {
181            continue;
182        }
183        let _ = input.seek(SeekFrom::Start(entry.position.into()))?;
184        result.push(Image::read(input, entry.kind, entry.size)?);
185    }
186
187    Ok(result)
188}
189
190#[cfg(test)]
191mod test {
192    use super::{find_best_size, parse_cursor, Error, Image, TocEntry, IMAGE_TYPE};
193    use std::io::Cursor;
194
195    #[test]
196    fn read_3x5_image() {
197        let data = [
198            0x00, 0x00, 0x00, 0x00, // header(?)
199            0x02, 0x00, 0xfd, 0xff, // IMAGE_TYPE
200            0x04, 0x00, 0x00, 0x00, // size 4
201            0x00, 0x00, 0x00, 0x00, // version(?)
202            0x03, 0x00, 0x00, 0x00, // width 3
203            0x05, 0x00, 0x00, 0x00, // height 5
204            0x07, 0x00, 0x00, 0x00, // x_hot 7
205            0x08, 0x00, 0x00, 0x00, // y_hot 8
206            0x2a, 0x00, 0x00, 0x00, // delay 42
207            // pixels
208            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
209            0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c,
210            0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a,
211            0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
212            0x39, 0x3a, 0x3b, 0x3c,
213        ];
214        let image = Image::read(&mut Cursor::new(&data[..]), IMAGE_TYPE, 4).unwrap();
215        assert_eq!(image.width, 3);
216        assert_eq!(image.height, 5);
217        assert_eq!(image.x_hot, 7);
218        assert_eq!(image.y_hot, 8);
219        assert_eq!(image.delay, 42);
220        assert_eq!(
221            image.pixels,
222            &[
223                0x0403_0201,
224                0x0807_0605,
225                0x0c0b_0a09,
226                0x100f_0e0d,
227                0x1413_1211,
228                0x1817_1615,
229                0x1c1b_1a19,
230                0x201f_1e1d,
231                0x2423_2221,
232                0x2827_2625,
233                0x2c2b_2a29,
234                0x302f_2e2d,
235                0x3433_3231,
236                0x3837_3635,
237                0x3c3b_3a39,
238            ]
239        );
240    }
241
242    #[test]
243    fn read_corrupt_image_wrong_kind() {
244        let data = [
245            0x00, 0x00, 0x00, 0x00, // header(?)
246            0x01, 0x00, 0xfd, 0xff, // IMAGE_TYPE - 1
247            0x04, 0x00, 0x00, 0x00, // size 4
248            0x00, 0x00, 0x00, 0x00, // version(?)
249            0x00, 0x00, 0x00, 0x00, // width 0
250            0x00, 0x00, 0x00, 0x00, // height 0
251            0x00, 0x00, 0x00, 0x00, // x_hot 0
252            0x00, 0x00, 0x00, 0x00, // y_hot 0
253            0x00, 0x00, 0x00, 0x00, // delay 0
254        ];
255        match Image::read(&mut Cursor::new(&data[..]), IMAGE_TYPE, 4) {
256            Err(Error::CorruptImage) => {}
257            r => panic!("Unexpected result {:?}", r),
258        }
259    }
260
261    #[test]
262    fn read_corrupt_image_wrong_size() {
263        let data = [
264            0x00, 0x00, 0x00, 0x00, // header(?)
265            0x02, 0x00, 0xfd, 0xff, // IMAGE_TYPE
266            0x04, 0x00, 0x00, 0x00, // size 4
267            0x00, 0x00, 0x00, 0x00, // version(?)
268            0x00, 0x00, 0x00, 0x00, // width 0
269            0x00, 0x00, 0x00, 0x00, // height 0
270            0x00, 0x00, 0x00, 0x00, // x_hot 0
271            0x00, 0x00, 0x00, 0x00, // y_hot 0
272            0x00, 0x00, 0x00, 0x00, // delay 0
273        ];
274        match Image::read(&mut Cursor::new(&data[..]), IMAGE_TYPE, 42) {
275            Err(Error::CorruptImage) => {}
276            r => panic!("Unexpected result {:?}", r),
277        }
278    }
279
280    #[test]
281    fn read_image_too_large_width() {
282        let data = [
283            0x00, 0x00, 0x00, 0x00, // header(?)
284            0x02, 0x00, 0xfd, 0xff, // IMAGE_TYPE
285            0x04, 0x00, 0x00, 0x00, // size 4
286            0x00, 0x00, 0x00, 0x00, // version(?)
287            0x00, 0x80, 0x00, 0x00, // width IMAGE_MAX_SIZE + 1
288            0x00, 0x00, 0x00, 0x00, // height 0
289            0x00, 0x00, 0x00, 0x00, // x_hot 0
290            0x00, 0x00, 0x00, 0x00, // y_hot 0
291            0x00, 0x00, 0x00, 0x00, // delay 0
292        ];
293        match Image::read(&mut Cursor::new(&data[..]), IMAGE_TYPE, 4) {
294            Err(Error::ImageTooLarge) => {}
295            r => panic!("Unexpected result {:?}", r),
296        }
297    }
298
299    #[test]
300    fn read_image_too_large_height() {
301        let data = [
302            0x00, 0x00, 0x00, 0x00, // header(?)
303            0x02, 0x00, 0xfd, 0xff, // IMAGE_TYPE
304            0x04, 0x00, 0x00, 0x00, // size 4
305            0x00, 0x00, 0x00, 0x00, // version(?)
306            0x00, 0x00, 0x00, 0x00, // width 0
307            0x00, 0x80, 0x00, 0x00, // height IMAGE_MAX_SIZE + 1
308            0x00, 0x00, 0x00, 0x00, // x_hot 0
309            0x00, 0x00, 0x00, 0x00, // y_hot 0
310            0x00, 0x00, 0x00, 0x00, // delay 0
311        ];
312        match Image::read(&mut Cursor::new(&data[..]), IMAGE_TYPE, 4) {
313            Err(Error::ImageTooLarge) => {}
314            r => panic!("Unexpected result {:?}", r),
315        }
316    }
317
318    #[test]
319    fn read_image_too_short() {
320        let data = [];
321        match Image::read(&mut Cursor::new(&data[..]), IMAGE_TYPE, 4) {
322            Err(Error::Io) => {}
323            r => panic!("Unexpected result {:?}", r),
324        }
325    }
326
327    #[test]
328    fn find_best_size_empty_input() {
329        let res = find_best_size(&[], 42);
330        match res {
331            Err(Error::NoImages) => {}
332            r => panic!("Unexpected result {:?}", r),
333        }
334    }
335
336    #[test]
337    fn find_best_size_one_input() {
338        let input = [TocEntry {
339            kind: IMAGE_TYPE,
340            size: 42,
341            position: 42,
342        }];
343        assert_eq!(42, find_best_size(&input, 10).unwrap());
344    }
345
346    #[test]
347    fn find_best_size_selects_better() {
348        let input = [
349            TocEntry {
350                kind: IMAGE_TYPE,
351                size: 42,
352                position: 42,
353            },
354            TocEntry {
355                kind: IMAGE_TYPE,
356                size: 32,
357                position: 42,
358            },
359            TocEntry {
360                kind: IMAGE_TYPE,
361                size: 3,
362                position: 42,
363            },
364            TocEntry {
365                kind: IMAGE_TYPE,
366                size: 22,
367                position: 42,
368            },
369            TocEntry {
370                kind: 0,
371                size: 10,
372                position: 42,
373            },
374        ];
375        assert_eq!(3, find_best_size(&input, 10).unwrap());
376    }
377
378    #[test]
379    fn parse_cursor_too_short() {
380        let data = [];
381        match parse_cursor(&mut Cursor::new(&data[..]), 10) {
382            Err(Error::Io) => {}
383            r => panic!("Unexpected result {:?}", r),
384        }
385    }
386
387    #[test]
388    fn parse_cursor_incorrect_magic() {
389        let data = [
390            0x00, 0x00, 0x00, 0x00, // magic
391            0x00, 0x00, 0x00, 0x00, // header file offset
392            0x00, 0x00, 0x00, 0x00, // version(?)
393            0x00, 0x00, 0x00, 0x00, // num TOC entries
394        ];
395        match parse_cursor(&mut Cursor::new(&data[..]), 10) {
396            Err(Error::InvalidMagic) => {}
397            r => panic!("Unexpected result {:?}", r),
398        }
399    }
400
401    #[test]
402    fn parse_cursor_too_many_entries() {
403        let data = [
404            b'X', b'c', b'u', b'r', // magic
405            0x00, 0x00, 0x00, 0x00, // header file offset
406            0x00, 0x00, 0x00, 0x00, // version(?)
407            0x01, 0x00, 0x01, 0x00, // num TOC entries, limit + 1
408        ];
409        match parse_cursor(&mut Cursor::new(&data[..]), 10) {
410            Err(Error::TooManyEntries) => {}
411            r => panic!("Unexpected result {:?}", r),
412        }
413    }
414
415    #[test]
416    fn parse_cursor_empty_toc() {
417        let data = [
418            b'X', b'c', b'u', b'r', // magic
419            0x10, 0x00, 0x00, 0x00, // header file offset (16)
420            0x00, 0x00, 0x00, 0x00, // version(?)
421            0x00, 0x00, 0x00, 0x00, // num TOC entries, 0
422        ];
423        match parse_cursor(&mut Cursor::new(&data[..]), 10) {
424            Err(Error::NoImages) => {}
425            r => panic!("Unexpected result {:?}", r),
426        }
427    }
428
429    #[test]
430    fn parse_cursor_one_image() {
431        let data = [
432            b'X', b'c', b'u', b'r', // magic
433            0x10, 0x00, 0x00, 0x00, // header file offset (16)
434            0x00, 0x00, 0x00, 0x00, // version(?)
435            0x01, 0x00, 0x00, 0x00, // num TOC entries, 1
436            // TOC
437            0x02, 0x00, 0xfd, 0xff, // IMAGE_TYPE
438            0x04, 0x00, 0x00, 0x00, // size 4
439            0x1c, 0x00, 0x00, 0x00, // image offset (28)
440            // image
441            0x00, 0x00, 0x00, 0x00, // header(?)
442            0x02, 0x00, 0xfd, 0xff, // IMAGE_TYPE
443            0x04, 0x00, 0x00, 0x00, // size 4
444            0x00, 0x00, 0x00, 0x00, // version(?)
445            0x00, 0x00, 0x00, 0x00, // width 0
446            0x00, 0x00, 0x00, 0x00, // height 0
447            0x00, 0x00, 0x00, 0x00, // x_hot 0
448            0x00, 0x00, 0x00, 0x00, // y_hot 0
449            0x00, 0x00, 0x00, 0x00, // delay 0
450        ];
451        let expected = [Image {
452            width: 0,
453            height: 0,
454            x_hot: 0,
455            y_hot: 0,
456            delay: 0,
457            pixels: vec![],
458        }];
459        let actual = parse_cursor(&mut Cursor::new(&data[..]), 10).unwrap();
460        assert_same_images(&expected, &actual);
461    }
462
463    #[test]
464    fn parse_cursor_two_images_plus_one_ignored() {
465        let data = [
466            b'X', b'c', b'u', b'r', // magic
467            0x10, 0x00, 0x00, 0x00, // header file offset (16)
468            0x00, 0x00, 0x00, 0x00, // version(?)
469            0x03, 0x00, 0x00, 0x00, // num TOC entries, 3
470            // TOC
471            0x02, 0x00, 0xfd, 0xff, // IMAGE_TYPE
472            0x04, 0x00, 0x00, 0x00, // size 4
473            0x34, 0x00, 0x00, 0x00, // image offset (52)
474            0x02, 0x00, 0xfd, 0xff, // IMAGE_TYPE
475            0x03, 0x00, 0x00, 0x00, // size 3
476            0x34, 0x00, 0x00, 0x00, // image offset (52)
477            0x02, 0x00, 0xfd, 0xff, // IMAGE_TYPE
478            0x04, 0x00, 0x00, 0x00, // size 4
479            0x34, 0x00, 0x00, 0x00, // image offset (52)
480            // image
481            0x00, 0x00, 0x00, 0x00, // header(?)
482            0x02, 0x00, 0xfd, 0xff, // IMAGE_TYPE
483            0x04, 0x00, 0x00, 0x00, // size 4
484            0x00, 0x00, 0x00, 0x00, // version(?)
485            0x00, 0x00, 0x00, 0x00, // width 0
486            0x00, 0x00, 0x00, 0x00, // height 0
487            0x00, 0x00, 0x00, 0x00, // x_hot 0
488            0x00, 0x00, 0x00, 0x00, // y_hot 0
489            0x00, 0x00, 0x00, 0x00, // delay 0
490        ];
491        let expected = [
492            Image {
493                width: 0,
494                height: 0,
495                x_hot: 0,
496                y_hot: 0,
497                delay: 0,
498                pixels: vec![],
499            },
500            Image {
501                width: 0,
502                height: 0,
503                x_hot: 0,
504                y_hot: 0,
505                delay: 0,
506                pixels: vec![],
507            },
508        ];
509        let actual = parse_cursor(&mut Cursor::new(&data[..]), 10).unwrap();
510        assert_same_images(&expected, &actual);
511    }
512
513    fn assert_same_images(a: &[Image], b: &[Image]) {
514        assert_eq!(a.len(), b.len(), "{:?} == {:?}", a, b);
515        for (i, (im1, im2)) in a.iter().zip(b.iter()).enumerate() {
516            assert_eq!(
517                im1.width,
518                im2.width,
519                "Width image {}: {} == {}",
520                i + 1,
521                im1.width,
522                im2.width
523            );
524            assert_eq!(
525                im1.height,
526                im2.height,
527                "Height image {}: {} == {}",
528                i + 1,
529                im1.height,
530                im2.height
531            );
532            assert_eq!(
533                im1.x_hot,
534                im2.x_hot,
535                "X-hot image {}: {} == {}",
536                i + 1,
537                im1.x_hot,
538                im2.x_hot
539            );
540            assert_eq!(
541                im1.y_hot,
542                im2.y_hot,
543                "Y-hot image {}: {} == {}",
544                i + 1,
545                im1.y_hot,
546                im2.y_hot
547            );
548            assert_eq!(
549                im1.delay,
550                im2.delay,
551                "Delay image {}: {} == {}",
552                i + 1,
553                im1.delay,
554                im2.delay
555            );
556            assert_eq!(
557                im1.pixels,
558                im2.pixels,
559                "Pixels image {}: {:?} == {:?}",
560                i + 1,
561                im1.pixels,
562                im2.pixels
563            );
564        }
565    }
566}