output.rs 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736
  1. //! Text and JSON output.
  2. use std::fmt;
  3. use std::time::Duration;
  4. use dns::{Response, Query, Answer, QClass, ErrorCode, WireError, MandatedLength};
  5. use dns::record::{Record, RecordType, UnknownQtype, OPT};
  6. use dns_transport::Error as TransportError;
  7. use json::{object, JsonValue};
  8. use crate::colours::Colours;
  9. use crate::table::{Table, Section};
  10. /// How to format the output data.
  11. #[derive(PartialEq, Debug, Copy, Clone)]
  12. pub enum OutputFormat {
  13. /// Format the output as plain text, optionally adding ANSI colours.
  14. Text(UseColours, TextFormat),
  15. /// Format the output as one line of plain text.
  16. Short(TextFormat),
  17. /// Format the entries as JSON.
  18. JSON,
  19. }
  20. /// When to use colours in the output.
  21. #[derive(PartialEq, Debug, Copy, Clone)]
  22. pub enum UseColours {
  23. /// Always use colours.
  24. Always,
  25. /// Use colours if output is to a terminal; otherwise, do not.
  26. Automatic,
  27. /// Never use colours.
  28. Never,
  29. }
  30. /// Options that govern how text should be rendered in record summaries.
  31. #[derive(PartialEq, Debug, Copy, Clone)]
  32. pub struct TextFormat {
  33. /// Whether to format TTLs as hours, minutes, and seconds.
  34. pub format_durations: bool,
  35. }
  36. impl UseColours {
  37. /// Whether we should use colours or not. This checks whether the user has
  38. /// overridden the colour setting, and if not, whether output is to a
  39. /// terminal.
  40. pub fn should_use_colours(self) -> bool {
  41. self == Self::Always || (atty::is(atty::Stream::Stdout) && self != Self::Never)
  42. }
  43. /// Creates a palette of colours depending on the user’s wishes or whether
  44. /// output is to a terminal.
  45. pub fn palette(self) -> Colours {
  46. if self.should_use_colours() {
  47. Colours::pretty()
  48. }
  49. else {
  50. Colours::plain()
  51. }
  52. }
  53. }
  54. impl OutputFormat {
  55. /// Prints the entirety of the output, formatted according to the
  56. /// settings. If the duration has been measured, it should also be
  57. /// printed. Returns `false` if there were no results to print, and `true`
  58. /// otherwise.
  59. pub fn print(self, responses: Vec<Response>, duration: Option<Duration>) -> bool {
  60. match self {
  61. Self::Short(tf) => {
  62. let all_answers = responses.into_iter().flat_map(|r| r.answers).collect::<Vec<_>>();
  63. if all_answers.is_empty() {
  64. eprintln!("No results");
  65. return false;
  66. }
  67. for answer in all_answers {
  68. match answer {
  69. Answer::Standard { record, .. } => {
  70. println!("{}", tf.record_payload_summary(record))
  71. }
  72. Answer::Pseudo { opt, .. } => {
  73. println!("{}", tf.pseudo_record_payload_summary(opt))
  74. }
  75. }
  76. }
  77. }
  78. Self::JSON => {
  79. let mut rs = Vec::new();
  80. for response in responses {
  81. let json = object! {
  82. "queries": json_queries(response.queries),
  83. "answers": json_answers(response.answers),
  84. "authorities": json_answers(response.authorities),
  85. "additionals": json_answers(response.additionals),
  86. };
  87. rs.push(json);
  88. }
  89. if let Some(duration) = duration {
  90. let object = object! {
  91. "responses": rs,
  92. "duration": {
  93. "secs": duration.as_secs(),
  94. "millis": duration.subsec_millis(),
  95. },
  96. };
  97. println!("{}", object);
  98. }
  99. else {
  100. let object = object! {
  101. "responses": rs,
  102. };
  103. println!("{}", object);
  104. }
  105. }
  106. Self::Text(uc, tf) => {
  107. let mut table = Table::new(uc.palette(), tf);
  108. for response in responses {
  109. if let Some(rcode) = response.flags.error_code {
  110. print_error_code(rcode);
  111. }
  112. for a in response.answers {
  113. table.add_row(a, Section::Answer);
  114. }
  115. for a in response.authorities {
  116. table.add_row(a, Section::Authority);
  117. }
  118. for a in response.additionals {
  119. table.add_row(a, Section::Additional);
  120. }
  121. }
  122. table.print(duration);
  123. }
  124. }
  125. true
  126. }
  127. /// Print an error that’s ocurred while sending or receiving DNS packets
  128. /// to standard error.
  129. pub fn print_error(self, error: TransportError) {
  130. match self {
  131. Self::Short(..) | Self::Text(..) => {
  132. eprintln!("Error [{}]: {}", erroneous_phase(&error), error_message(error));
  133. }
  134. Self::JSON => {
  135. let object = object! {
  136. "error": true,
  137. "error_phase": erroneous_phase(&error),
  138. "error_message": error_message(error),
  139. };
  140. eprintln!("{}", object);
  141. }
  142. }
  143. }
  144. }
  145. impl TextFormat {
  146. /// Formats a summary of a record in a received DNS response. Each record
  147. /// type contains wildly different data, so the format of the summary
  148. /// depends on what record it’s for.
  149. pub fn record_payload_summary(self, record: Record) -> String {
  150. match record {
  151. Record::A(a) => {
  152. format!("{}", a.address)
  153. }
  154. Record::AAAA(aaaa) => {
  155. format!("{}", aaaa.address)
  156. }
  157. Record::CAA(caa) => {
  158. if caa.critical {
  159. format!("{} {} (critical)", Ascii(&caa.tag), Ascii(&caa.value))
  160. }
  161. else {
  162. format!("{} {} (non-critical)", Ascii(&caa.tag), Ascii(&caa.value))
  163. }
  164. }
  165. Record::CNAME(cname) => {
  166. format!("{:?}", cname.domain.to_string())
  167. }
  168. Record::EUI48(eui48) => {
  169. format!("{:?}", eui48.formatted_address())
  170. }
  171. Record::EUI64(eui64) => {
  172. format!("{:?}", eui64.formatted_address())
  173. }
  174. Record::HINFO(hinfo) => {
  175. format!("{} {}", Ascii(&hinfo.cpu), Ascii(&hinfo.os))
  176. }
  177. Record::LOC(loc) => {
  178. format!("{} ({}, {}) ({}, {}, {})",
  179. loc.size,
  180. loc.horizontal_precision,
  181. loc.vertical_precision,
  182. loc.latitude .map_or_else(|| "Out of range".into(), |e| e.to_string()),
  183. loc.longitude.map_or_else(|| "Out of range".into(), |e| e.to_string()),
  184. loc.altitude,
  185. )
  186. }
  187. Record::MX(mx) => {
  188. format!("{} {:?}", mx.preference, mx.exchange.to_string())
  189. }
  190. Record::NAPTR(naptr) => {
  191. format!("{} {} {} {} {} {:?}",
  192. naptr.order,
  193. naptr.preference,
  194. Ascii(&naptr.flags),
  195. Ascii(&naptr.service),
  196. Ascii(&naptr.regex),
  197. naptr.replacement.to_string(),
  198. )
  199. }
  200. Record::NS(ns) => {
  201. format!("{:?}", ns.nameserver.to_string())
  202. }
  203. Record::OPENPGPKEY(opgp) => {
  204. format!("{:?}", opgp.base64_key())
  205. }
  206. Record::PTR(ptr) => {
  207. format!("{:?}", ptr.cname.to_string())
  208. }
  209. Record::SSHFP(sshfp) => {
  210. format!("{} {} {}",
  211. sshfp.algorithm,
  212. sshfp.fingerprint_type,
  213. sshfp.hex_fingerprint(),
  214. )
  215. }
  216. Record::SOA(soa) => {
  217. format!("{:?} {:?} {} {} {} {} {}",
  218. soa.mname.to_string(),
  219. soa.rname.to_string(),
  220. soa.serial,
  221. self.format_duration(soa.refresh_interval),
  222. self.format_duration(soa.retry_interval),
  223. self.format_duration(soa.expire_limit),
  224. self.format_duration(soa.minimum_ttl),
  225. )
  226. }
  227. Record::SRV(srv) => {
  228. format!("{} {} {:?}:{}", srv.priority, srv.weight, srv.target.to_string(), srv.port)
  229. }
  230. Record::TLSA(tlsa) => {
  231. format!("{} {} {} {:?}",
  232. tlsa.certificate_usage,
  233. tlsa.selector,
  234. tlsa.matching_type,
  235. tlsa.hex_certificate_data(),
  236. )
  237. }
  238. Record::TXT(txt) => {
  239. let messages = txt.messages.iter().map(|t| Ascii(t).to_string()).collect::<Vec<_>>();
  240. messages.join(", ")
  241. }
  242. Record::URI(uri) => {
  243. format!("{} {} {}", uri.priority, uri.weight, Ascii(&uri.target))
  244. }
  245. Record::Other { bytes, .. } => {
  246. format!("{:?}", bytes)
  247. }
  248. }
  249. }
  250. /// Formats a summary of an OPT pseudo-record. Pseudo-records have a different
  251. /// structure than standard ones.
  252. pub fn pseudo_record_payload_summary(self, opt: OPT) -> String {
  253. format!("{} {} {} {} {:?}",
  254. opt.udp_payload_size,
  255. opt.higher_bits,
  256. opt.edns0_version,
  257. opt.flags,
  258. opt.data)
  259. }
  260. /// Formats a duration depending on whether it should be displayed as
  261. /// seconds, or as computed units.
  262. pub fn format_duration(self, seconds: u32) -> String {
  263. if self.format_durations {
  264. format_duration_hms(seconds)
  265. }
  266. else {
  267. format!("{}", seconds)
  268. }
  269. }
  270. }
  271. /// Formats a duration as days, hours, minutes, and seconds, skipping leading
  272. /// zero units.
  273. fn format_duration_hms(seconds: u32) -> String {
  274. if seconds < 60 {
  275. format!("{}s", seconds)
  276. }
  277. else if seconds < 60 * 60 {
  278. format!("{}m{:02}s",
  279. seconds / 60,
  280. seconds % 60)
  281. }
  282. else if seconds < 60 * 60 * 24 {
  283. format!("{}h{:02}m{:02}s",
  284. seconds / 3600,
  285. (seconds % 3600) / 60,
  286. seconds % 60)
  287. }
  288. else {
  289. format!("{}d{}h{:02}m{:02}s",
  290. seconds / 86400,
  291. (seconds % 86400) / 3600,
  292. (seconds % 3600) / 60,
  293. seconds % 60)
  294. }
  295. }
  296. /// Serialises multiple DNS queries as a JSON value.
  297. fn json_queries(queries: Vec<Query>) -> JsonValue {
  298. let queries = queries.iter().map(|q| {
  299. object! {
  300. "name": q.qname.to_string(),
  301. "class": json_class(q.qclass),
  302. "type": json_record_type_name(q.qtype),
  303. }
  304. }).collect::<Vec<_>>();
  305. queries.into()
  306. }
  307. /// Serialises multiple received DNS answers as a JSON value.
  308. fn json_answers(answers: Vec<Answer>) -> JsonValue {
  309. let answers = answers.into_iter().map(|a| {
  310. match a {
  311. Answer::Standard { qname, qclass, ttl, record } => {
  312. object! {
  313. "name": qname.to_string(),
  314. "class": json_class(qclass),
  315. "ttl": ttl,
  316. "type": json_record_name(&record),
  317. "data": json_record_data(record),
  318. }
  319. }
  320. Answer::Pseudo { qname, opt } => {
  321. object! {
  322. "name": qname.to_string(),
  323. "type": "OPT",
  324. "data": {
  325. "version": opt.edns0_version,
  326. "data": opt.data,
  327. },
  328. }
  329. }
  330. }
  331. }).collect::<Vec<_>>();
  332. answers.into()
  333. }
  334. fn json_class(class: QClass) -> JsonValue {
  335. match class {
  336. QClass::IN => "IN".into(),
  337. QClass::CH => "CH".into(),
  338. QClass::HS => "HS".into(),
  339. QClass::Other(n) => n.into(),
  340. }
  341. }
  342. /// Serialises a DNS record type name.
  343. fn json_record_type_name(record: RecordType) -> JsonValue {
  344. match record {
  345. RecordType::A => "A".into(),
  346. RecordType::AAAA => "AAAA".into(),
  347. RecordType::CAA => "CAA".into(),
  348. RecordType::CNAME => "CNAME".into(),
  349. RecordType::EUI48 => "EUI48".into(),
  350. RecordType::EUI64 => "EUI64".into(),
  351. RecordType::HINFO => "HINFO".into(),
  352. RecordType::LOC => "LOC".into(),
  353. RecordType::MX => "MX".into(),
  354. RecordType::NAPTR => "NAPTR".into(),
  355. RecordType::NS => "NS".into(),
  356. RecordType::OPENPGPKEY => "OPENPGPKEY".into(),
  357. RecordType::PTR => "PTR".into(),
  358. RecordType::SOA => "SOA".into(),
  359. RecordType::SRV => "SRV".into(),
  360. RecordType::SSHFP => "SSHFP".into(),
  361. RecordType::TLSA => "TLSA".into(),
  362. RecordType::TXT => "TXT".into(),
  363. RecordType::URI => "URI".into(),
  364. RecordType::Other(unknown) => {
  365. match unknown {
  366. UnknownQtype::HeardOf(name, _) => (*name).into(),
  367. UnknownQtype::UnheardOf(num) => (num).into(),
  368. }
  369. }
  370. }
  371. }
  372. /// Serialises a DNS record type name.
  373. fn json_record_name(record: &Record) -> JsonValue {
  374. match record {
  375. Record::A(_) => "A".into(),
  376. Record::AAAA(_) => "AAAA".into(),
  377. Record::CAA(_) => "CAA".into(),
  378. Record::CNAME(_) => "CNAME".into(),
  379. Record::EUI48(_) => "EUI48".into(),
  380. Record::EUI64(_) => "EUI64".into(),
  381. Record::HINFO(_) => "HINFO".into(),
  382. Record::LOC(_) => "LOC".into(),
  383. Record::MX(_) => "MX".into(),
  384. Record::NAPTR(_) => "NAPTR".into(),
  385. Record::NS(_) => "NS".into(),
  386. Record::OPENPGPKEY(_) => "OPENPGPKEY".into(),
  387. Record::PTR(_) => "PTR".into(),
  388. Record::SOA(_) => "SOA".into(),
  389. Record::SRV(_) => "SRV".into(),
  390. Record::SSHFP(_) => "SSHFP".into(),
  391. Record::TLSA(_) => "TLSA".into(),
  392. Record::TXT(_) => "TXT".into(),
  393. Record::URI(_) => "URI".into(),
  394. Record::Other { type_number, .. } => {
  395. match type_number {
  396. UnknownQtype::HeardOf(name, _) => (*name).into(),
  397. UnknownQtype::UnheardOf(num) => (*num).into(),
  398. }
  399. }
  400. }
  401. }
  402. /// Serialises a received DNS record as a JSON value.
  403. /// Even though DNS doesn’t specify a character encoding, strings are still
  404. /// converted from UTF-8, because JSON specifies UTF-8.
  405. fn json_record_data(record: Record) -> JsonValue {
  406. match record {
  407. Record::A(a) => {
  408. object! {
  409. "address": a.address.to_string(),
  410. }
  411. }
  412. Record::AAAA(aaaa) => {
  413. object! {
  414. "address": aaaa.address.to_string(),
  415. }
  416. }
  417. Record::CAA(caa) => {
  418. object! {
  419. "critical": caa.critical,
  420. "tag": String::from_utf8_lossy(&caa.tag).to_string(),
  421. "value": String::from_utf8_lossy(&caa.value).to_string(),
  422. }
  423. }
  424. Record::CNAME(cname) => {
  425. object! {
  426. "domain": cname.domain.to_string(),
  427. }
  428. }
  429. Record::EUI48(eui48) => {
  430. object! {
  431. "identifier": eui48.formatted_address(),
  432. }
  433. }
  434. Record::EUI64(eui64) => {
  435. object! {
  436. "identifier": eui64.formatted_address(),
  437. }
  438. }
  439. Record::HINFO(hinfo) => {
  440. object! {
  441. "cpu": String::from_utf8_lossy(&hinfo.cpu).to_string(),
  442. "os": String::from_utf8_lossy(&hinfo.os).to_string(),
  443. }
  444. }
  445. Record::LOC(loc) => {
  446. object! {
  447. "size": loc.size.to_string(),
  448. "precision": {
  449. "horizontal": loc.horizontal_precision,
  450. "vertical": loc.vertical_precision,
  451. },
  452. "point": {
  453. "latitude": loc.latitude.map(|e| e.to_string()),
  454. "longitude": loc.longitude.map(|e| e.to_string()),
  455. "altitude": loc.altitude.to_string(),
  456. },
  457. }
  458. }
  459. Record::MX(mx) => {
  460. object! {
  461. "preference": mx.preference,
  462. "exchange": mx.exchange.to_string(),
  463. }
  464. }
  465. Record::NAPTR(naptr) => {
  466. object! {
  467. "order": naptr.order,
  468. "flags": String::from_utf8_lossy(&naptr.flags).to_string(),
  469. "service": String::from_utf8_lossy(&naptr.service).to_string(),
  470. "regex": String::from_utf8_lossy(&naptr.regex).to_string(),
  471. "replacement": naptr.replacement.to_string(),
  472. }
  473. }
  474. Record::NS(ns) => {
  475. object! {
  476. "nameserver": ns.nameserver.to_string(),
  477. }
  478. }
  479. Record::OPENPGPKEY(opgp) => {
  480. object! {
  481. "key": opgp.base64_key(),
  482. }
  483. }
  484. Record::PTR(ptr) => {
  485. object! {
  486. "cname": ptr.cname.to_string(),
  487. }
  488. }
  489. Record::SSHFP(sshfp) => {
  490. object! {
  491. "algorithm": sshfp.algorithm,
  492. "fingerprint_type": sshfp.fingerprint_type,
  493. "fingerprint": sshfp.hex_fingerprint(),
  494. }
  495. }
  496. Record::SOA(soa) => {
  497. object! {
  498. "mname": soa.mname.to_string(),
  499. }
  500. }
  501. Record::SRV(srv) => {
  502. object! {
  503. "priority": srv.priority,
  504. "weight": srv.weight,
  505. "port": srv.port,
  506. "target": srv.target.to_string(),
  507. }
  508. }
  509. Record::TLSA(tlsa) => {
  510. object! {
  511. "certificate_usage": tlsa.certificate_usage,
  512. "selector": tlsa.selector,
  513. "matching_type": tlsa.matching_type,
  514. "certificate_data": tlsa.hex_certificate_data(),
  515. }
  516. }
  517. Record::TXT(txt) => {
  518. let ms = txt.messages.into_iter()
  519. .map(|txt| String::from_utf8_lossy(&txt).to_string())
  520. .collect::<Vec<_>>();
  521. object! {
  522. "messages": ms,
  523. }
  524. }
  525. Record::URI(uri) => {
  526. object! {
  527. "priority": uri.priority,
  528. "weight": uri.weight,
  529. "target": String::from_utf8_lossy(&uri.target).to_string(),
  530. }
  531. }
  532. Record::Other { bytes, .. } => {
  533. object! {
  534. "bytes": bytes,
  535. }
  536. }
  537. }
  538. }
  539. /// A wrapper around displaying characters that escapes quotes and
  540. /// backslashes, and writes control and upper-bit bytes as their number rather
  541. /// than their character. This is needed because even though such characters
  542. /// are not allowed in domain names, packets can contain anything, and we need
  543. /// a way to display the response, whatever it is.
  544. struct Ascii<'a>(&'a [u8]);
  545. impl fmt::Display for Ascii<'_> {
  546. fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
  547. write!(f, "\"")?;
  548. for byte in self.0.iter().copied() {
  549. if byte < 32 || byte >= 128 {
  550. write!(f, "\\{}", byte)?;
  551. }
  552. else if byte == b'"' {
  553. write!(f, "\\\"")?;
  554. }
  555. else if byte == b'\\' {
  556. write!(f, "\\\\")?;
  557. }
  558. else {
  559. write!(f, "{}", byte as char)?;
  560. }
  561. }
  562. write!(f, "\"")
  563. }
  564. }
  565. /// Prints a message describing the “error code” field of a DNS packet. This
  566. /// happens when the packet was received correctly, but the server indicated
  567. /// an error.
  568. pub fn print_error_code(rcode: ErrorCode) {
  569. match rcode {
  570. ErrorCode::FormatError => println!("Status: Format Error"),
  571. ErrorCode::ServerFailure => println!("Status: Server Failure"),
  572. ErrorCode::NXDomain => println!("Status: NXDomain"),
  573. ErrorCode::NotImplemented => println!("Status: Not Implemented"),
  574. ErrorCode::QueryRefused => println!("Status: Query Refused"),
  575. ErrorCode::BadVersion => println!("Status: Bad Version"),
  576. ErrorCode::Private(num) => println!("Status: Private Reason ({})", num),
  577. ErrorCode::Other(num) => println!("Status: Other Failure ({})", num),
  578. }
  579. }
  580. /// Returns the “phase” of operation where an error occurred. This gets shown
  581. /// to the user so they can debug what went wrong.
  582. fn erroneous_phase(error: &TransportError) -> &'static str {
  583. match error {
  584. TransportError::WireError(_) => "protocol",
  585. TransportError::TruncatedResponse |
  586. TransportError::NetworkError(_) => "network",
  587. #[cfg(feature = "with_nativetls")]
  588. TransportError::TlsError(_) |
  589. TransportError::TlsHandshakeError(_) => "tls",
  590. #[cfg(feature = "with_rustls")]
  591. TransportError::RustlsInvalidDnsNameError(_) => "tls", // TODO: Actually wrong, could be https
  592. #[cfg(feature = "with_https")]
  593. TransportError::HttpError(_) |
  594. TransportError::WrongHttpStatus(_,_) => "http",
  595. }
  596. }
  597. /// Formats an error into its human-readable message.
  598. fn error_message(error: TransportError) -> String {
  599. match error {
  600. TransportError::WireError(e) => wire_error_message(e),
  601. TransportError::TruncatedResponse => "Truncated response".into(),
  602. TransportError::NetworkError(e) => e.to_string(),
  603. #[cfg(feature = "with_nativetls")]
  604. TransportError::TlsError(e) => e.to_string(),
  605. #[cfg(feature = "with_nativetls")]
  606. TransportError::TlsHandshakeError(e) => e.to_string(),
  607. #[cfg(any(feature = "with_rustls"))]
  608. TransportError::RustlsInvalidDnsNameError(e) => e.to_string(),
  609. #[cfg(feature = "with_https")]
  610. TransportError::HttpError(e) => e.to_string(),
  611. #[cfg(feature = "with_https")]
  612. TransportError::WrongHttpStatus(t,r) => format!("Nameserver returned HTTP {} ({})", t, r.unwrap_or_else(|| "No reason".into()))
  613. }
  614. }
  615. /// Formats a wire error into its human-readable message, describing what was
  616. /// wrong with the packet we received.
  617. fn wire_error_message(error: WireError) -> String {
  618. match error {
  619. WireError::IO => {
  620. "Malformed packet: insufficient data".into()
  621. }
  622. WireError::WrongRecordLength { stated_length, mandated_length: MandatedLength::Exactly(len) } => {
  623. format!("Malformed packet: record length should be {}, got {}", len, stated_length )
  624. }
  625. WireError::WrongRecordLength { stated_length, mandated_length: MandatedLength::AtLeast(len) } => {
  626. format!("Malformed packet: record length should be at least {}, got {}", len, stated_length )
  627. }
  628. WireError::WrongLabelLength { stated_length, length_after_labels } => {
  629. format!("Malformed packet: length {} was specified, but read {} bytes", stated_length, length_after_labels)
  630. }
  631. WireError::TooMuchRecursion(indices) => {
  632. format!("Malformed packet: too much recursion: {:?}", indices)
  633. }
  634. WireError::OutOfBounds(index) => {
  635. format!("Malformed packet: out of bounds ({})", index)
  636. }
  637. WireError::WrongVersion { stated_version, maximum_supported_version } => {
  638. format!("Malformed packet: record specifies version {}, expected up to {}", stated_version, maximum_supported_version)
  639. }
  640. }
  641. }
  642. #[cfg(test)]
  643. mod test {
  644. use super::*;
  645. #[test]
  646. fn escape_quotes() {
  647. assert_eq!(Ascii(b"Mallard \"The Duck\" Fillmore").to_string(),
  648. "\"Mallard \\\"The Duck\\\" Fillmore\"");
  649. }
  650. #[test]
  651. fn escape_backslashes() {
  652. assert_eq!(Ascii(b"\\").to_string(),
  653. "\"\\\\\"");
  654. }
  655. #[test]
  656. fn escape_lows() {
  657. assert_eq!(Ascii(b"\n\r\t").to_string(),
  658. "\"\\10\\13\\9\"");
  659. }
  660. #[test]
  661. fn escape_highs() {
  662. assert_eq!(Ascii("pâté".as_bytes()).to_string(),
  663. "\"p\\195\\162t\\195\\169\"");
  664. }
  665. }