1use std::{
12 collections::{BTreeMap, BTreeSet},
13 io::{Cursor, Read, Seek, SeekFrom},
14 mem,
15};
16
17const ABSENT_ENTRY: i32 = -1;
18const CANCELED_ENTRY: i32 = -2;
19
20const BOOL_NAMES: [&str; 44] = [
21 "bw", "am", "xsb", "xhp", "xenl", "eo", "gn", "hc", "km", "hs", "in", "db", "da", "mir",
22 "msgr", "os", "eslok", "xt", "hz", "ul", "xon", "nxon", "mc5i", "chts", "nrrmc", "npc",
23 "ndscr", "ccc", "bce", "hls", "xhpa", "crxm", "daisy", "xvpa", "sam", "cpix", "lpix", "OTbs",
24 "OTns", "OTnc", "OTMT", "OTNL", "OTpt", "OTxr",
25];
26
27const NUMBER_NAMES: [&str; 39] = [
28 "cols", "it", "lines", "lm", "xmc", "pb", "vt", "wsl", "nlab", "lh", "lw", "ma", "wnum",
29 "colors", "pairs", "ncv", "bufsz", "spinv", "spinh", "maddr", "mjump", "mcs", "mls", "npins",
30 "orc", "orl", "orhi", "orvi", "cps", "widcs", "btns", "bitwin", "bitype", "UTug", "OTdC",
31 "OTdN", "OTdB", "OTdT", "OTkn",
32];
33
34const STRING_NAMES: [&str; 414] = [
35 "cbt", "bel", "cr", "csr", "tbc", "clear", "el", "ed", "hpa", "cmdch", "cup", "cud1", "home",
36 "civis", "cub1", "mrcup", "cnorm", "cuf1", "ll", "cuu1", "cvvis", "dch1", "dl1", "dsl", "hd",
37 "smacs", "blink", "bold", "smcup", "smdc", "dim", "smir", "invis", "prot", "rev", "smso",
38 "smul", "ech", "rmacs", "sgr0", "rmcup", "rmdc", "rmir", "rmso", "rmul", "flash", "ff", "fsl",
39 "is1", "is2", "is3", "if", "ich1", "il1", "ip", "kbs", "ktbc", "kclr", "kctab", "kdch1",
40 "kdl1", "kcud1", "krmir", "kel", "ked", "kf0", "kf1", "kf10", "kf2", "kf3", "kf4", "kf5",
41 "kf6", "kf7", "kf8", "kf9", "khome", "kich1", "kil1", "kcub1", "kll", "knp", "kpp", "kcuf1",
42 "kind", "kri", "khts", "kcuu1", "rmkx", "smkx", "lf0", "lf1", "lf10", "lf2", "lf3", "lf4",
43 "lf5", "lf6", "lf7", "lf8", "lf9", "rmm", "smm", "nel", "pad", "dch", "dl", "cud", "ich",
44 "indn", "il", "cub", "cuf", "rin", "cuu", "pfkey", "pfloc", "pfx", "mc0", "mc4", "mc5", "rep",
45 "rs1", "rs2", "rs3", "rf", "rc", "vpa", "sc", "ind", "ri", "sgr", "hts", "wind", "ht", "tsl",
46 "uc", "hu", "iprog", "ka1", "ka3", "kb2", "kc1", "kc3", "mc5p", "rmp", "acsc", "pln", "kcbt",
47 "smxon", "rmxon", "smam", "rmam", "xonc", "xoffc", "enacs", "smln", "rmln", "kbeg", "kcan",
48 "kclo", "kcmd", "kcpy", "kcrt", "kend", "kent", "kext", "kfnd", "khlp", "kmrk", "kmsg", "kmov",
49 "knxt", "kopn", "kopt", "kprv", "kprt", "krdo", "kref", "krfr", "krpl", "krst", "kres", "ksav",
50 "kspd", "kund", "kBEG", "kCAN", "kCMD", "kCPY", "kCRT", "kDC", "kDL", "kslt", "kEND", "kEOL",
51 "kEXT", "kFND", "kHLP", "kHOM", "kIC", "kLFT", "kMSG", "kMOV", "kNXT", "kOPT", "kPRV", "kPRT",
52 "kRDO", "kRPL", "kRIT", "kRES", "kSAV", "kSPD", "kUND", "rfi", "kf11", "kf12", "kf13", "kf14",
53 "kf15", "kf16", "kf17", "kf18", "kf19", "kf20", "kf21", "kf22", "kf23", "kf24", "kf25", "kf26",
54 "kf27", "kf28", "kf29", "kf30", "kf31", "kf32", "kf33", "kf34", "kf35", "kf36", "kf37", "kf38",
55 "kf39", "kf40", "kf41", "kf42", "kf43", "kf44", "kf45", "kf46", "kf47", "kf48", "kf49", "kf50",
56 "kf51", "kf52", "kf53", "kf54", "kf55", "kf56", "kf57", "kf58", "kf59", "kf60", "kf61", "kf62",
57 "kf63", "el1", "mgc", "smgl", "smgr", "fln", "sclk", "dclk", "rmclk", "cwin", "wingo", "hup",
58 "dial", "qdial", "tone", "pulse", "hook", "pause", "wait", "u0", "u1", "u2", "u3", "u4", "u5",
59 "u6", "u7", "u8", "u9", "op", "oc", "initc", "initp", "scp", "setf", "setb", "cpi", "lpi",
60 "chr", "cvr", "defc", "swidm", "sdrfq", "sitm", "slm", "smicm", "snlq", "snrmq", "sshm",
61 "ssubm", "ssupm", "sum", "rwidm", "ritm", "rlm", "rmicm", "rshm", "rsubm", "rsupm", "rum",
62 "mhpa", "mcud1", "mcub1", "mcuf1", "mvpa", "mcuu1", "porder", "mcud", "mcub", "mcuf", "mcuu",
63 "scs", "smgb", "smgbp", "smglp", "smgrp", "smgt", "smgtp", "sbim", "scsd", "rbim", "rcsd",
64 "subcs", "supcs", "docr", "zerom", "csnm", "kmous", "minfo", "reqmp", "getm", "setaf", "setab",
65 "pfxl", "devt", "csin", "s0ds", "s1ds", "s2ds", "s3ds", "smglr", "smgtb", "birep", "binel",
66 "bicr", "colornm", "defbi", "endbi", "setcolor", "slines", "dispc", "smpch", "rmpch", "smsc",
67 "rmsc", "pctrm", "scesc", "scesa", "ehhlm", "elhlm", "elohlm", "erhlm", "ethlm", "evhlm",
68 "sgr1", "slength", "OTi2", "OTrs", "OTnl", "OTbs", "OTko", "OTma", "OTG2", "OTG3", "OTG1",
69 "OTG4", "OTGR", "OTGL", "OTGU", "OTGD", "OTGH", "OTGV", "OTGC", "meml", "memu", "box1",
70];
71
72#[repr(u16)]
73enum TerminfoMagic {
74 Magic1 = 0x011a,
76 Magic2 = 0x021e,
78}
79
80#[derive(thiserror::Error, Debug)]
82#[non_exhaustive]
83pub enum Error {
84 #[error("Unknown magic number")]
86 BadMagic,
87 #[error("String without final NUL")]
89 UnterminatedString,
90 #[error("Unsupported terminfo format")]
92 UnsupportedFormat,
93 #[error("Invalid boolean value {0}")]
95 InvalidBooleanValue(u8),
96 #[error("I/O error")]
98 IO(#[from] std::io::Error),
99 #[error("Invalid UTF-8 string")]
101 Utf8(#[from] std::str::Utf8Error),
102}
103
104pub fn parse(buffer: &[u8]) -> Result<Terminfo<'_>, Error> {
108 let mut terminfo = Terminfo::new();
109 let mut reader = Cursor::new(buffer);
110 terminfo.parse_base(&mut reader)?;
111 match terminfo.parse_extended(&mut reader) {
112 Ok(()) | Err(Error::IO(_)) => {} Err(err) => return Err(err),
114 }
115 Ok(terminfo)
116}
117
118fn read_u8(reader: &mut impl Read) -> Result<u8, Error> {
119 let mut buffer = [0u8; 1];
120 reader.read_exact(&mut buffer)?;
121 Ok(buffer[0])
122}
123
124fn read_le16(reader: &mut impl Read) -> Result<u16, Error> {
125 let mut buffer = [0u8; 2];
126 reader.read_exact(&mut buffer)?;
127 let value = u16::from_le_bytes(buffer);
128 Ok(value)
129}
130
131fn read_slice<'a>(reader: &mut Cursor<&'a [u8]>, size: usize) -> Result<&'a [u8], Error> {
132 let start = reader.position() as usize;
133 let end = reader.seek(SeekFrom::Current(size as i64))? as usize;
134 let buffer = &reader.get_ref();
135 match buffer.get(start..end) {
136 Some(slice) => Ok(slice),
137 None => Err(Error::UnsupportedFormat),
138 }
139}
140
141fn get_string(string_table: &[u8], offset: usize) -> Result<&[u8], Error> {
142 let Some(string_slice) = string_table.get(offset..) else {
143 return Err(Error::UnsupportedFormat);
144 };
145 if let Some(string_length) = &string_slice.iter().position(|c| *c == b'\0') {
146 Ok(&string_table[offset..offset + string_length])
147 } else {
148 Err(Error::UnterminatedString)
149 }
150}
151
152fn check_offset(size: u16) -> Option<usize> {
154 match i32::from(size as i16) {
155 ABSENT_ENTRY | CANCELED_ENTRY => None,
156 _ => Some(usize::from(size)),
157 }
158}
159
160fn align_cursor(reader: &mut Cursor<&[u8]>) -> Result<(), Error> {
162 let position = reader.position();
163 if position & 1 == 1 {
164 reader.seek_relative(1)?;
165 }
166 Ok(())
167}
168
169#[derive(Debug)]
171pub struct Terminfo<'a> {
172 pub booleans: BTreeSet<&'a str>,
173 pub numbers: BTreeMap<&'a str, i32>,
174 pub strings: BTreeMap<&'a str, &'a [u8]>,
175 number_size: usize,
176}
177
178impl<'a> Terminfo<'a> {
179 fn new() -> Self {
180 Self {
181 booleans: BTreeSet::default(),
182 numbers: BTreeMap::default(),
183 strings: BTreeMap::default(),
184 number_size: 0,
185 }
186 }
187
188 fn read_number(&self, reader: &mut Cursor<&'a [u8]>) -> Result<Option<i32>, Error> {
189 let value = if self.number_size == 4 {
190 let mut buffer = [0u8; 4];
191 reader.read_exact(&mut buffer)?;
192 i32::from_le_bytes(buffer)
193 } else {
194 let mut buffer = [0u8; 2];
195 reader.read_exact(&mut buffer)?;
196 i32::from(i16::from_le_bytes(buffer))
197 };
198 if value > 0 { Ok(Some(value)) } else { Ok(None) }
199 }
200
201 fn parse_base(&mut self, mut reader: &mut Cursor<&'a [u8]>) -> Result<(), Error> {
203 let magic = read_le16(&mut reader)?;
204 let name_size = usize::from(read_le16(&mut reader)?);
205 let bool_count = usize::from(read_le16(&mut reader)?);
206 let num_count = usize::from(read_le16(&mut reader)?);
207 let str_count = usize::from(read_le16(&mut reader)?);
208 let str_size = usize::from(read_le16(&mut reader)?);
209
210 self.number_size = match magic {
211 val if val == TerminfoMagic::Magic1 as u16 => 2,
212 val if val == TerminfoMagic::Magic2 as u16 => 4,
213 _ => return Err(Error::BadMagic),
214 };
215
216 if bool_count > BOOL_NAMES.len()
217 || num_count > NUMBER_NAMES.len()
218 || str_count > STRING_NAMES.len()
219 {
220 return Err(Error::UnsupportedFormat);
221 }
222
223 reader.seek_relative(name_size as i64)?;
225
226 for name in BOOL_NAMES.iter().take(bool_count) {
227 let value = read_u8(&mut reader)?;
228 match value {
229 0 => continue,
230 1 => {}
231 value => return Err(Error::InvalidBooleanValue(value)),
232 }
233 self.booleans.insert(*name);
234 }
235
236 align_cursor(reader)?;
237
238 for name in NUMBER_NAMES.iter().take(num_count) {
239 if let Some(number) = self.read_number(reader)? {
240 self.numbers.insert(*name, number);
241 }
242 }
243
244 let str_offsets = read_slice(reader, mem::size_of::<u16>() * str_count)?;
245 let mut str_offsets_reader = Cursor::new(str_offsets);
246
247 let str_table = read_slice(reader, str_size)?;
248
249 for name in STRING_NAMES.iter().take(str_count) {
250 let offset = read_le16(&mut str_offsets_reader)?;
251 let Some(offset) = check_offset(offset) else {
252 continue;
253 };
254 let value = get_string(str_table, offset)?;
255 self.strings.insert(*name, value);
256 }
257
258 Ok(())
259 }
260
261 fn parse_extended(&mut self, mut reader: &mut Cursor<&'a [u8]>) -> Result<(), Error> {
263 align_cursor(reader)?;
264
265 let bool_count = usize::from(read_le16(&mut reader)?);
266 let num_count = usize::from(read_le16(&mut reader)?);
267 let str_count = usize::from(read_le16(&mut reader)?);
268 let _ext_str_usage = usize::from(read_le16(&mut reader)?);
269 let str_limit = usize::from(read_le16(&mut reader)?);
270
271 let bools = read_slice(reader, bool_count)?;
272 let mut bools_reader = Cursor::new(bools);
273 align_cursor(reader)?;
274
275 let nums = read_slice(reader, self.number_size * num_count)?;
276 let mut nums_reader = Cursor::new(nums);
277
278 let strs = read_slice(reader, mem::size_of::<u16>() * str_count)?;
279 let mut strs_reader = Cursor::new(strs);
280
281 let name_count = bool_count + num_count + str_count;
282 let names = read_slice(reader, mem::size_of::<u16>() * name_count)?;
283 let mut names_reader = Cursor::new(names);
284
285 let str_table = read_slice(reader, str_limit)?;
286
287 let mut names_base = 0;
288 loop {
289 let Ok(offset) = read_le16(&mut strs_reader) else {
290 break;
291 };
292 let Some(offset) = check_offset(offset) else {
293 continue;
294 };
295 names_base += get_string(str_table, offset)?.len() + 1;
296 }
297
298 let Some(names_table) = &str_table.get(names_base..) else {
299 return Err(Error::UnsupportedFormat);
300 };
301
302 loop {
303 let Ok(value) = read_u8(&mut bools_reader) else {
304 break;
305 };
306 let Ok(name_offset) = read_le16(&mut names_reader) else {
307 return Err(Error::UnsupportedFormat);
308 };
309 match value {
310 0 => continue,
311 1 => {}
312 value => return Err(Error::InvalidBooleanValue(value)),
313 }
314 let Some(name_offset) = check_offset(name_offset) else {
315 return Err(Error::UnsupportedFormat);
316 };
317 let name = get_string(names_table, name_offset)?;
318 self.booleans.insert(str::from_utf8(name)?);
319 }
320
321 loop {
322 let Ok(value) = self.read_number(&mut nums_reader) else {
323 break;
324 };
325 let Ok(name_offset) = read_le16(&mut names_reader) else {
326 return Err(Error::UnsupportedFormat);
327 };
328 let Some(value) = value else {
329 continue;
330 };
331 let Some(name_offset) = check_offset(name_offset) else {
332 return Err(Error::UnsupportedFormat);
333 };
334 let name = get_string(names_table, name_offset)?;
335 self.numbers.insert(str::from_utf8(name)?, value);
336 }
337
338 strs_reader.set_position(0);
339 loop {
340 let Ok(str_offset) = read_le16(&mut strs_reader) else {
341 break;
342 };
343 let Ok(name_offset) = read_le16(&mut names_reader) else {
344 return Err(Error::UnsupportedFormat);
345 };
346 if let (Some(str_offset), Some(name_offset)) =
347 (check_offset(str_offset), check_offset(name_offset))
348 {
349 let value = get_string(str_table, str_offset)?;
350 let name = get_string(names_table, name_offset)?;
351 self.strings.insert(str::from_utf8(name)?, value);
352 }
353 }
354
355 Ok(())
356 }
357}
358
359#[cfg(test)]
360mod test {
361 use collection_literals::collection;
362
363 use super::*;
364
365 #[derive(Clone, Copy, PartialEq)]
366 enum NumberType {
367 U16,
368 U32,
369 }
370
371 #[derive(Clone, PartialEq)]
372 enum StringValue {
373 Present(Vec<u8>),
374 Absent,
375 Canceled,
376 }
377
378 impl<'a> From<&'a StringValue> for Option<&'a [u8]> {
380 fn from(value: &'a StringValue) -> Self {
381 match value {
382 StringValue::Present(value) => Some(value),
383 _ => None,
384 }
385 }
386 }
387
388 impl<'a> IntoIterator for &'a StringValue {
390 type Item = &'a [u8];
391 type IntoIter = std::option::IntoIter<Self::Item>;
392
393 fn into_iter(self) -> Self::IntoIter {
394 Option::<&'a [u8]>::from(self).into_iter()
395 }
396 }
397
398 impl<const N: usize> From<&[u8; N]> for StringValue {
399 fn from(value: &[u8; N]) -> Self {
400 Self::Present(value.to_vec())
401 }
402 }
403
404 fn memlen(byte_string: &[u8]) -> u16 {
406 byte_string.len() as u16 + 1
407 }
408
409 struct DataSet {
410 number_type: NumberType,
411 term_name: Vec<u8>,
412 base_booleans: Vec<u8>,
413 base_numbers: Vec<i32>,
414 base_strings: Vec<StringValue>,
415 ext_booleans: Vec<(&'static [u8], u8)>,
416 ext_numbers: Vec<(&'static [u8], i32)>,
417 ext_strings: Vec<(&'static [u8], StringValue)>,
418 }
419
420 impl Default for DataSet {
421 fn default() -> Self {
422 Self {
423 number_type: NumberType::U16,
424 term_name: b"myterm".to_vec(),
425 base_booleans: vec![1, 0, 0, 0, 1],
426 base_numbers: vec![80, -2, 25, -1, -10, 0x10005],
427 base_strings: vec![
428 StringValue::Absent,
429 StringValue::from(b"Hello"),
430 StringValue::Canceled,
431 StringValue::Absent,
432 StringValue::from(b"World!"),
433 ],
434 ext_booleans: vec![
435 (b"Curly", 1),
436 (b"Italic", 1),
437 (b"Invisible", 0),
438 (b"Semi-bold", 1),
439 ],
440 ext_numbers: vec![(b"Shades", 1100), (b"Variants", 2200)],
441 ext_strings: vec![
442 (b"Colors", StringValue::from(b"A lot")),
443 (b"Ideas", StringValue::Absent),
444 (b"Luminosity", StringValue::from(b"Positive")),
445 ],
446 }
447 }
448 }
449
450 fn make_buffer(data_set: &DataSet, add_ext: bool) -> Vec<u8> {
451 let magic = match data_set.number_type {
452 NumberType::U16 => 0x011a,
453 NumberType::U32 => 0x021e,
454 };
455 let str_size = data_set.base_strings.iter().flatten().map(memlen).sum();
456
457 let mut buffer = vec![];
458 buffer.extend_from_slice(&u16::to_le_bytes(magic));
459 buffer.extend_from_slice(&u16::to_le_bytes(memlen(&data_set.term_name)));
460 buffer.extend_from_slice(&u16::to_le_bytes(data_set.base_booleans.len() as u16));
461 buffer.extend_from_slice(&u16::to_le_bytes(data_set.base_numbers.len() as u16));
462 buffer.extend_from_slice(&u16::to_le_bytes(data_set.base_strings.len() as u16));
463 buffer.extend_from_slice(&u16::to_le_bytes(str_size));
464 buffer.extend_from_slice(&data_set.term_name);
465 buffer.push(0);
466 buffer.extend_from_slice(&data_set.base_booleans);
467 if !buffer.len().is_multiple_of(2) {
468 buffer.push(0);
469 }
470 for number in &data_set.base_numbers {
471 match data_set.number_type {
472 NumberType::U16 => buffer.extend_from_slice(&u16::to_le_bytes(*number as u16)),
473 NumberType::U32 => buffer.extend_from_slice(&u32::to_le_bytes(*number as u32)),
474 }
475 }
476 let mut offset = 0;
477 for string in &data_set.base_strings {
478 match string {
479 StringValue::Present(string) => {
480 buffer.extend_from_slice(&u16::to_le_bytes(offset));
481 offset += memlen(string);
482 }
483 StringValue::Absent => buffer.extend_from_slice(&u16::to_le_bytes(0xffff)),
484 StringValue::Canceled => buffer.extend_from_slice(&u16::to_le_bytes(0xfffe)),
485 }
486 }
487 for string in data_set.base_strings.iter().flatten() {
488 buffer.extend_from_slice(string);
489 buffer.push(0);
490 }
491 if add_ext {
492 if !buffer.len().is_multiple_of(2) {
493 buffer.push(0);
494 }
495 buffer.append(&mut make_ext_buffer(data_set));
496 }
497 buffer
498 }
499
500 fn make_ext_buffer(data_set: &DataSet) -> Vec<u8> {
501 let booleans = &data_set.ext_booleans;
502 let numbers = &data_set.ext_numbers;
503 let strings = &data_set.ext_strings;
504
505 let boolean_name_size: u16 = booleans.iter().map(|x| memlen(x.0)).sum();
506 let number_name_size: u16 = numbers.iter().map(|x| memlen(x.0)).sum();
507 let string_name_size: u16 = strings.iter().map(|x| memlen(x.0)).sum();
508 let string_value_size: u16 = strings.iter().flat_map(|x| &x.1).map(memlen).sum();
509 let name_size = boolean_name_size + number_name_size + string_name_size;
510 let string_size = name_size + string_value_size;
511
512 let mut buffer = vec![];
513
514 buffer.extend_from_slice(&u16::to_le_bytes(booleans.len() as u16));
520 buffer.extend_from_slice(&u16::to_le_bytes(numbers.len() as u16));
521 buffer.extend_from_slice(&u16::to_le_bytes(strings.len() as u16));
522 buffer.extend_from_slice(&u16::to_le_bytes(0u16)); buffer.extend_from_slice(&u16::to_le_bytes(string_size));
524
525 for boolean in booleans {
526 buffer.push(boolean.1);
527 }
528 if !buffer.len().is_multiple_of(2) {
529 buffer.push(0);
530 }
531 for number in numbers {
532 match data_set.number_type {
533 NumberType::U16 => buffer.extend_from_slice(&u16::to_le_bytes(number.1 as u16)),
534 NumberType::U32 => buffer.extend_from_slice(&u32::to_le_bytes(number.1 as u32)),
535 }
536 }
537 let mut offset = 0;
538 for string in strings {
539 match &string.1 {
540 StringValue::Present(string) => {
541 buffer.extend_from_slice(&u16::to_le_bytes(offset));
542 offset += memlen(string);
543 }
544 StringValue::Absent => buffer.extend_from_slice(&u16::to_le_bytes(0xffff)),
545 StringValue::Canceled => buffer.extend_from_slice(&u16::to_le_bytes(0xfffe)),
546 }
547 }
548
549 offset = 0;
550 for boolean in booleans {
551 buffer.extend_from_slice(&u16::to_le_bytes(offset));
552 offset += memlen(boolean.0);
553 }
554 for number in numbers {
555 buffer.extend_from_slice(&u16::to_le_bytes(offset));
556 offset += memlen(number.0);
557 }
558 for string in strings {
559 buffer.extend_from_slice(&u16::to_le_bytes(offset));
560 offset += memlen(string.0);
561 }
562
563 for string in strings {
564 if let StringValue::Present(string) = &string.1 {
565 buffer.extend_from_slice(string);
566 buffer.push(0);
567 }
568 }
569
570 for boolean in booleans {
571 buffer.extend_from_slice(boolean.0);
572 buffer.push(0);
573 }
574 for number in numbers {
575 buffer.extend_from_slice(number.0);
576 buffer.push(0);
577 }
578 for string in strings {
579 buffer.extend_from_slice(string.0);
580 buffer.push(0);
581 }
582
583 buffer
584 }
585
586 #[test]
587 fn empty_buffer() {
588 let terminfo = parse(b"");
589 assert!(matches!(terminfo.unwrap_err(), Error::IO(_)));
590 }
591
592 #[test]
593 fn base_16_bit() {
594 let data_set = DataSet::default();
595 let buffer = make_buffer(&data_set, false);
596 let terminfo = parse(buffer.as_slice()).unwrap();
597 assert_eq!(terminfo.booleans, collection!("bw", "xenl"));
598 assert_eq!(
599 terminfo.numbers,
600 collection!(
601 "cols" => 80,
602 "lines" => 25,
603 "pb" => 5,
604 )
605 );
606 assert_eq!(
607 terminfo.strings,
608 collection!(
609 "bel" => b"Hello".as_slice(),
610 "tbc" => b"World!",
611 )
612 );
613 }
614
615 #[test]
616 fn base_32_bit() {
617 let mut data_set = DataSet {
618 number_type: NumberType::U32,
619 ..Default::default()
620 };
621 data_set.base_numbers[5] = 0x7fff_ffff;
622
623 let buffer = make_buffer(&data_set, false);
624 let terminfo = parse(buffer.as_slice()).unwrap();
625 assert_eq!(terminfo.booleans, collection!("bw", "xenl"));
626 assert_eq!(
627 terminfo.numbers,
628 collection!(
629 "cols" => 80,
630 "lines" => 25,
631 "pb" => 0x7fff_ffff,
632 )
633 );
634 assert_eq!(
635 terminfo.strings,
636 collection!(
637 "bel" => b"Hello".as_slice(),
638 "tbc" => b"World!",
639 )
640 );
641 }
642
643 #[test]
644 fn bad_magic() {
645 let data_set = DataSet::default();
646 let mut buffer = make_buffer(&data_set, false);
647 buffer[1] = 3;
648 let terminfo = parse(buffer.as_slice());
649 assert!(matches!(terminfo.unwrap_err(), Error::BadMagic));
650 }
651
652 #[test]
653 fn base_truncated() {
654 let data_set = DataSet::default();
655 let mut buffer = make_buffer(&data_set, false);
656 buffer.pop();
657 let terminfo = parse(buffer.as_slice());
658 assert!(matches!(terminfo.unwrap_err(), Error::UnsupportedFormat));
659 }
660
661 #[test]
662 fn base_unterminated_string() {
663 let data_set = DataSet::default();
664 let mut buffer = make_buffer(&data_set, false);
665 let buffer_size = buffer.len();
666 buffer[buffer_size - 1] = b'!';
667 let terminfo = parse(buffer.as_slice());
668 assert!(matches!(terminfo.unwrap_err(), Error::UnterminatedString));
669 }
670
671 #[test]
672 fn base_bad_string_offset() {
673 let data_set = DataSet {
674 term_name: b"123".to_vec(),
675 base_booleans: vec![],
676 base_numbers: vec![],
677 base_strings: vec![StringValue::from(b"Hello")],
678 ..Default::default()
679 };
680 let mut buffer = make_buffer(&data_set, false);
681 let mut offset = 6 * mem::size_of::<u16>();
682 offset += &data_set.term_name.len() + 1;
683 buffer[offset] = 0xff;
684 let terminfo = parse(buffer.as_slice());
685 assert!(matches!(terminfo.unwrap_err(), Error::UnsupportedFormat));
686 }
687
688 #[test]
689 fn base_bad_boolean_count() {
690 let data_set = DataSet::default();
691 let mut buffer = make_buffer(&data_set, false);
692 let offset = 2 * mem::size_of::<u16>();
693 let patch = u16::to_le_bytes(BOOL_NAMES.len() as u16 + 1);
694 buffer[offset] = patch[0];
695 buffer[offset + 1] = patch[1];
696 let terminfo = parse(buffer.as_slice());
697 assert!(matches!(terminfo.unwrap_err(), Error::UnsupportedFormat));
698 }
699
700 #[test]
701 fn base_bad_number_count() {
702 let data_set = DataSet::default();
703 let mut buffer = make_buffer(&data_set, false);
704 let offset = 3 * mem::size_of::<u16>();
705 let patch = u16::to_le_bytes(NUMBER_NAMES.len() as u16 + 1);
706 buffer[offset] = patch[0];
707 buffer[offset + 1] = patch[1];
708 let terminfo = parse(buffer.as_slice());
709 assert!(matches!(terminfo.unwrap_err(), Error::UnsupportedFormat));
710 }
711
712 #[test]
713 fn base_bad_string_count() {
714 let data_set = DataSet::default();
715 let mut buffer = make_buffer(&data_set, false);
716 let offset = 4 * mem::size_of::<u16>();
717 let patch = u16::to_le_bytes(STRING_NAMES.len() as u16 + 1);
718 buffer[offset] = patch[0];
719 buffer[offset + 1] = patch[1];
720 let terminfo = parse(buffer.as_slice());
721 assert!(matches!(terminfo.unwrap_err(), Error::UnsupportedFormat));
722 }
723
724 #[test]
725 fn base_bad_boolean() {
726 let data_set = DataSet {
727 base_booleans: vec![1, 0, 42],
728 ..Default::default()
729 };
730 let buffer = make_buffer(&data_set, false);
731 let terminfo = parse(buffer.as_slice());
732 assert!(matches!(
733 terminfo.unwrap_err(),
734 Error::InvalidBooleanValue(42)
735 ));
736 }
737
738 #[test]
739 fn extended_16_bit() {
740 let data_set = DataSet::default();
741 let buffer = make_buffer(&data_set, true);
742 let terminfo = parse(buffer.as_slice()).unwrap();
743 assert_eq!(
744 terminfo.booleans,
745 collection!("Curly", "Italic", "Semi-bold", "bw", "xenl")
746 );
747 assert_eq!(
748 terminfo.numbers,
749 collection!(
750 "Shades" => 1100,
751 "Variants" => 2200,
752 "cols" => 80,
753 "lines" => 25,
754 "pb" => 5,
755 )
756 );
757 assert_eq!(
758 terminfo.strings,
759 collection!(
760 "Colors" => b"A lot".as_slice(),
761 "Luminosity" => b"Positive",
762 "bel" => b"Hello",
763 "tbc" => b"World!",
764 )
765 );
766 }
767
768 #[test]
769 fn extended_32_bit() {
770 let mut data_set = DataSet {
771 number_type: NumberType::U32,
772 ..Default::default()
773 };
774 data_set.base_numbers[5] = 0x7fff_ffff;
775
776 let buffer = make_buffer(&data_set, true);
777 let terminfo = parse(buffer.as_slice()).unwrap();
778 assert_eq!(
779 terminfo.booleans,
780 collection!("Curly", "Italic", "Semi-bold", "bw", "xenl")
781 );
782 assert_eq!(
783 terminfo.numbers,
784 collection!(
785 "Shades" => 1100,
786 "Variants" => 2200,
787 "cols" => 80,
788 "lines" => 25,
789 "pb" => 0x7fff_ffff,
790 )
791 );
792 assert_eq!(
793 terminfo.strings,
794 collection!(
795 "Colors" => b"A lot".as_slice(),
796 "Luminosity" => b"Positive",
797 "bel" => b"Hello",
798 "tbc" => b"World!",
799 )
800 );
801 }
802
803 #[test]
804 fn extended_unterminated_string() {
805 let data_set = DataSet::default();
806 let mut buffer = make_buffer(&data_set, true);
807 let buffer_size = buffer.len();
808 buffer[buffer_size - 1] = b'!';
809 let terminfo = parse(buffer.as_slice());
810 assert!(matches!(terminfo.unwrap_err(), Error::UnterminatedString));
811 }
812
813 #[test]
814 fn extended_bad_boolean() {
815 let data_set = DataSet {
816 ext_booleans: vec![(b"True", 1), (b"False", 0), (b"Incorrect", 67)],
817 ..Default::default()
818 };
819 let buffer = make_buffer(&data_set, true);
820 let terminfo = parse(buffer.as_slice());
821 assert!(matches!(
822 terminfo.unwrap_err(),
823 Error::InvalidBooleanValue(67)
824 ));
825 }
826}