dns.rs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677
  1. #[cfg(feature = "async")]
  2. use core::task::Waker;
  3. use heapless::Vec;
  4. use managed::ManagedSlice;
  5. use crate::socket::{Context, PollAt};
  6. use crate::time::{Duration, Instant};
  7. use crate::wire::dns::{Flags, Opcode, Packet, Question, Rcode, Record, RecordData, Repr, Type};
  8. use crate::wire::{self, IpAddress, IpProtocol, IpRepr, UdpRepr};
  9. #[cfg(feature = "async")]
  10. use super::WakerRegistration;
  11. pub const MAX_ADDRESS_COUNT: usize = 4;
  12. pub const MAX_SERVER_COUNT: usize = 4;
  13. const DNS_PORT: u16 = 53;
  14. const MDNS_DNS_PORT: u16 = 5353;
  15. const MAX_NAME_LEN: usize = 255;
  16. const RETRANSMIT_DELAY: Duration = Duration::from_millis(1_000);
  17. const MAX_RETRANSMIT_DELAY: Duration = Duration::from_millis(10_000);
  18. const RETRANSMIT_TIMEOUT: Duration = Duration::from_millis(10_000); // Should generally be 2-10 secs
  19. #[cfg(feature = "proto-ipv6")]
  20. const MDNS_IPV6_ADDR: IpAddress = IpAddress::Ipv6(crate::wire::Ipv6Address([
  21. 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfb,
  22. ]));
  23. #[cfg(feature = "proto-ipv4")]
  24. const MDNS_IPV4_ADDR: IpAddress = IpAddress::Ipv4(crate::wire::Ipv4Address([224, 0, 0, 251]));
  25. /// Error returned by [`Socket::start_query`]
  26. #[derive(Debug, PartialEq, Eq, Clone, Copy)]
  27. #[cfg_attr(feature = "defmt", derive(defmt::Format))]
  28. pub enum StartQueryError {
  29. NoFreeSlot,
  30. InvalidName,
  31. NameTooLong,
  32. }
  33. /// Error returned by [`Socket::get_query_result`]
  34. #[derive(Debug, PartialEq, Eq, Clone, Copy)]
  35. #[cfg_attr(feature = "defmt", derive(defmt::Format))]
  36. pub enum GetQueryResultError {
  37. /// Query is not done yet.
  38. Pending,
  39. /// Query failed.
  40. Failed,
  41. }
  42. /// State for an in-progress DNS query.
  43. ///
  44. /// The only reason this struct is public is to allow the socket state
  45. /// to be allocated externally.
  46. #[derive(Debug)]
  47. pub struct DnsQuery {
  48. state: State,
  49. #[cfg(feature = "async")]
  50. waker: WakerRegistration,
  51. }
  52. impl DnsQuery {
  53. fn set_state(&mut self, state: State) {
  54. self.state = state;
  55. #[cfg(feature = "async")]
  56. self.waker.wake();
  57. }
  58. }
  59. #[derive(Debug)]
  60. #[allow(clippy::large_enum_variant)]
  61. enum State {
  62. Pending(PendingQuery),
  63. Completed(CompletedQuery),
  64. Failure,
  65. }
  66. #[derive(Debug)]
  67. struct PendingQuery {
  68. name: Vec<u8, MAX_NAME_LEN>,
  69. type_: Type,
  70. port: u16, // UDP port (src for request, dst for response)
  71. txid: u16, // transaction ID
  72. timeout_at: Option<Instant>,
  73. retransmit_at: Instant,
  74. delay: Duration,
  75. server_idx: usize,
  76. mdns: MulticastDns,
  77. }
  78. #[derive(Debug)]
  79. pub enum MulticastDns {
  80. Disabled,
  81. #[cfg(feature = "socket-mdns")]
  82. Enabled,
  83. }
  84. #[derive(Debug)]
  85. struct CompletedQuery {
  86. addresses: Vec<IpAddress, MAX_ADDRESS_COUNT>,
  87. }
  88. /// A handle to an in-progress DNS query.
  89. #[derive(Clone, Copy)]
  90. pub struct QueryHandle(usize);
  91. /// A Domain Name System socket.
  92. ///
  93. /// A UDP socket is bound to a specific endpoint, and owns transmit and receive
  94. /// packet buffers.
  95. #[derive(Debug)]
  96. pub struct Socket<'a> {
  97. servers: Vec<IpAddress, MAX_SERVER_COUNT>,
  98. queries: ManagedSlice<'a, Option<DnsQuery>>,
  99. /// The time-to-live (IPv4) or hop limit (IPv6) value used in outgoing packets.
  100. hop_limit: Option<u8>,
  101. }
  102. impl<'a> Socket<'a> {
  103. /// Create a DNS socket.
  104. ///
  105. /// # Panics
  106. ///
  107. /// Panics if `servers.len() > MAX_SERVER_COUNT`
  108. pub fn new<Q>(servers: &[IpAddress], queries: Q) -> Socket<'a>
  109. where
  110. Q: Into<ManagedSlice<'a, Option<DnsQuery>>>,
  111. {
  112. Socket {
  113. servers: Vec::from_slice(servers).unwrap(),
  114. queries: queries.into(),
  115. hop_limit: None,
  116. }
  117. }
  118. /// Update the list of DNS servers, will replace all existing servers
  119. ///
  120. /// # Panics
  121. ///
  122. /// Panics if `servers.len() > MAX_SERVER_COUNT`
  123. pub fn update_servers(&mut self, servers: &[IpAddress]) {
  124. self.servers = Vec::from_slice(servers).unwrap();
  125. }
  126. /// Return the time-to-live (IPv4) or hop limit (IPv6) value used in outgoing packets.
  127. ///
  128. /// See also the [set_hop_limit](#method.set_hop_limit) method
  129. pub fn hop_limit(&self) -> Option<u8> {
  130. self.hop_limit
  131. }
  132. /// Set the time-to-live (IPv4) or hop limit (IPv6) value used in outgoing packets.
  133. ///
  134. /// A socket without an explicitly set hop limit value uses the default [IANA recommended]
  135. /// value (64).
  136. ///
  137. /// # Panics
  138. ///
  139. /// This function panics if a hop limit value of 0 is given. See [RFC 1122 § 3.2.1.7].
  140. ///
  141. /// [IANA recommended]: https://www.iana.org/assignments/ip-parameters/ip-parameters.xhtml
  142. /// [RFC 1122 § 3.2.1.7]: https://tools.ietf.org/html/rfc1122#section-3.2.1.7
  143. pub fn set_hop_limit(&mut self, hop_limit: Option<u8>) {
  144. // A host MUST NOT send a datagram with a hop limit value of 0
  145. if let Some(0) = hop_limit {
  146. panic!("the time-to-live value of a packet must not be zero")
  147. }
  148. self.hop_limit = hop_limit
  149. }
  150. fn find_free_query(&mut self) -> Option<QueryHandle> {
  151. for (i, q) in self.queries.iter().enumerate() {
  152. if q.is_none() {
  153. return Some(QueryHandle(i));
  154. }
  155. }
  156. match self.queries {
  157. ManagedSlice::Borrowed(_) => None,
  158. #[cfg(feature = "alloc")]
  159. ManagedSlice::Owned(ref mut queries) => {
  160. queries.push(None);
  161. let index = queries.len() - 1;
  162. Some(QueryHandle(index))
  163. }
  164. }
  165. }
  166. /// Start a query.
  167. ///
  168. /// `name` is specified in human-friendly format, such as `"rust-lang.org"`.
  169. /// It accepts names both with and without trailing dot, and they're treated
  170. /// the same (there's no support for DNS search path).
  171. pub fn start_query(
  172. &mut self,
  173. cx: &mut Context,
  174. name: &str,
  175. query_type: Type,
  176. ) -> Result<QueryHandle, StartQueryError> {
  177. let mut name = name.as_bytes();
  178. if name.is_empty() {
  179. net_trace!("invalid name: zero length");
  180. return Err(StartQueryError::InvalidName);
  181. }
  182. // Remove trailing dot, if any
  183. if name[name.len() - 1] == b'.' {
  184. name = &name[..name.len() - 1];
  185. }
  186. let mut raw_name: Vec<u8, MAX_NAME_LEN> = Vec::new();
  187. let mut mdns = MulticastDns::Disabled;
  188. #[cfg(feature = "socket-mdns")]
  189. if name.split(|&c| c == b'.').last().unwrap() == b"local" {
  190. net_trace!("Starting a mDNS query");
  191. mdns = MulticastDns::Enabled;
  192. }
  193. for s in name.split(|&c| c == b'.') {
  194. if s.len() > 63 {
  195. net_trace!("invalid name: too long label");
  196. return Err(StartQueryError::InvalidName);
  197. }
  198. if s.is_empty() {
  199. net_trace!("invalid name: zero length label");
  200. return Err(StartQueryError::InvalidName);
  201. }
  202. // Push label
  203. raw_name
  204. .push(s.len() as u8)
  205. .map_err(|_| StartQueryError::NameTooLong)?;
  206. raw_name
  207. .extend_from_slice(s)
  208. .map_err(|_| StartQueryError::NameTooLong)?;
  209. }
  210. // Push terminator.
  211. raw_name
  212. .push(0x00)
  213. .map_err(|_| StartQueryError::NameTooLong)?;
  214. self.start_query_raw(cx, &raw_name, query_type, mdns)
  215. }
  216. /// Start a query with a raw (wire-format) DNS name.
  217. /// `b"\x09rust-lang\x03org\x00"`
  218. ///
  219. /// You probably want to use [`start_query`] instead.
  220. pub fn start_query_raw(
  221. &mut self,
  222. cx: &mut Context,
  223. raw_name: &[u8],
  224. query_type: Type,
  225. mdns: MulticastDns,
  226. ) -> Result<QueryHandle, StartQueryError> {
  227. let handle = self.find_free_query().ok_or(StartQueryError::NoFreeSlot)?;
  228. self.queries[handle.0] = Some(DnsQuery {
  229. state: State::Pending(PendingQuery {
  230. name: Vec::from_slice(raw_name).map_err(|_| StartQueryError::NameTooLong)?,
  231. type_: query_type,
  232. txid: cx.rand().rand_u16(),
  233. port: cx.rand().rand_source_port(),
  234. delay: RETRANSMIT_DELAY,
  235. timeout_at: None,
  236. retransmit_at: Instant::ZERO,
  237. server_idx: 0,
  238. mdns,
  239. }),
  240. #[cfg(feature = "async")]
  241. waker: WakerRegistration::new(),
  242. });
  243. Ok(handle)
  244. }
  245. /// Get the result of a query.
  246. ///
  247. /// If the query is completed, the query slot is automatically freed.
  248. ///
  249. /// # Panics
  250. /// Panics if the QueryHandle corresponds to a free slot.
  251. pub fn get_query_result(
  252. &mut self,
  253. handle: QueryHandle,
  254. ) -> Result<Vec<IpAddress, MAX_ADDRESS_COUNT>, GetQueryResultError> {
  255. let slot = &mut self.queries[handle.0];
  256. let q = slot.as_mut().unwrap();
  257. match &mut q.state {
  258. // Query is not done yet.
  259. State::Pending(_) => Err(GetQueryResultError::Pending),
  260. // Query is done
  261. State::Completed(q) => {
  262. let res = q.addresses.clone();
  263. *slot = None; // Free up the slot for recycling.
  264. Ok(res)
  265. }
  266. State::Failure => {
  267. *slot = None; // Free up the slot for recycling.
  268. Err(GetQueryResultError::Failed)
  269. }
  270. }
  271. }
  272. /// Cancels a query, freeing the slot.
  273. ///
  274. /// # Panics
  275. ///
  276. /// Panics if the QueryHandle corresponds to an already free slot.
  277. pub fn cancel_query(&mut self, handle: QueryHandle) {
  278. let slot = &mut self.queries[handle.0];
  279. if slot.is_none() {
  280. panic!("Canceling query in a free slot.")
  281. }
  282. *slot = None; // Free up the slot for recycling.
  283. }
  284. /// Assign a waker to a query slot
  285. ///
  286. /// The waker will be woken when the query completes, either successfully or failed.
  287. ///
  288. /// # Panics
  289. ///
  290. /// Panics if the QueryHandle corresponds to an already free slot.
  291. #[cfg(feature = "async")]
  292. pub fn register_query_waker(&mut self, handle: QueryHandle, waker: &Waker) {
  293. self.queries[handle.0]
  294. .as_mut()
  295. .unwrap()
  296. .waker
  297. .register(waker);
  298. }
  299. pub(crate) fn accepts(&self, ip_repr: &IpRepr, udp_repr: &UdpRepr) -> bool {
  300. (udp_repr.src_port == DNS_PORT
  301. && self
  302. .servers
  303. .iter()
  304. .any(|server| *server == ip_repr.src_addr()))
  305. || (udp_repr.src_port == MDNS_DNS_PORT)
  306. }
  307. pub(crate) fn process(
  308. &mut self,
  309. _cx: &mut Context,
  310. ip_repr: &IpRepr,
  311. udp_repr: &UdpRepr,
  312. payload: &[u8],
  313. ) {
  314. debug_assert!(self.accepts(ip_repr, udp_repr));
  315. let size = payload.len();
  316. net_trace!(
  317. "receiving {} octets from {:?}:{}",
  318. size,
  319. ip_repr.src_addr(),
  320. udp_repr.dst_port
  321. );
  322. let p = match Packet::new_checked(payload) {
  323. Ok(x) => x,
  324. Err(_) => {
  325. net_trace!("dns packet malformed");
  326. return;
  327. }
  328. };
  329. if p.opcode() != Opcode::Query {
  330. net_trace!("unwanted opcode {:?}", p.opcode());
  331. return;
  332. }
  333. if !p.flags().contains(Flags::RESPONSE) {
  334. net_trace!("packet doesn't have response bit set");
  335. return;
  336. }
  337. if p.question_count() != 1 {
  338. net_trace!("bad question count {:?}", p.question_count());
  339. return;
  340. }
  341. // Find pending query
  342. for q in self.queries.iter_mut().flatten() {
  343. if let State::Pending(pq) = &mut q.state {
  344. if udp_repr.dst_port != pq.port || p.transaction_id() != pq.txid {
  345. continue;
  346. }
  347. if p.rcode() == Rcode::NXDomain {
  348. net_trace!("rcode NXDomain");
  349. q.set_state(State::Failure);
  350. continue;
  351. }
  352. let payload = p.payload();
  353. let (mut payload, question) = match Question::parse(payload) {
  354. Ok(x) => x,
  355. Err(_) => {
  356. net_trace!("question malformed");
  357. return;
  358. }
  359. };
  360. if question.type_ != pq.type_ {
  361. net_trace!("question type mismatch");
  362. return;
  363. }
  364. match eq_names(p.parse_name(question.name), p.parse_name(&pq.name)) {
  365. Ok(true) => {}
  366. Ok(false) => {
  367. net_trace!("question name mismatch");
  368. return;
  369. }
  370. Err(_) => {
  371. net_trace!("dns question name malformed");
  372. return;
  373. }
  374. }
  375. let mut addresses = Vec::new();
  376. for _ in 0..p.answer_record_count() {
  377. let (payload2, r) = match Record::parse(payload) {
  378. Ok(x) => x,
  379. Err(_) => {
  380. net_trace!("dns answer record malformed");
  381. return;
  382. }
  383. };
  384. payload = payload2;
  385. match eq_names(p.parse_name(r.name), p.parse_name(&pq.name)) {
  386. Ok(true) => {}
  387. Ok(false) => {
  388. net_trace!("answer name mismatch: {:?}", r);
  389. continue;
  390. }
  391. Err(_) => {
  392. net_trace!("dns answer record name malformed");
  393. return;
  394. }
  395. }
  396. match r.data {
  397. #[cfg(feature = "proto-ipv4")]
  398. RecordData::A(addr) => {
  399. net_trace!("A: {:?}", addr);
  400. if addresses.push(addr.into()).is_err() {
  401. net_trace!("too many addresses in response, ignoring {:?}", addr);
  402. }
  403. }
  404. #[cfg(feature = "proto-ipv6")]
  405. RecordData::Aaaa(addr) => {
  406. net_trace!("AAAA: {:?}", addr);
  407. if addresses.push(addr.into()).is_err() {
  408. net_trace!("too many addresses in response, ignoring {:?}", addr);
  409. }
  410. }
  411. RecordData::Cname(name) => {
  412. net_trace!("CNAME: {:?}", name);
  413. // When faced with a CNAME, recursive resolvers are supposed to
  414. // resolve the CNAME and append the results for it.
  415. //
  416. // We update the query with the new name, so that we pick up the A/AAAA
  417. // records for the CNAME when we parse them later.
  418. // I believe it's mandatory the CNAME results MUST come *after* in the
  419. // packet, so it's enough to do one linear pass over it.
  420. if copy_name(&mut pq.name, p.parse_name(name)).is_err() {
  421. net_trace!("dns answer cname malformed");
  422. return;
  423. }
  424. }
  425. RecordData::Other(type_, data) => {
  426. net_trace!("unknown: {:?} {:?}", type_, data)
  427. }
  428. }
  429. }
  430. q.set_state(if addresses.is_empty() {
  431. State::Failure
  432. } else {
  433. State::Completed(CompletedQuery { addresses })
  434. });
  435. // If we get here, packet matched the current query, stop processing.
  436. return;
  437. }
  438. }
  439. // If we get here, packet matched with no query.
  440. net_trace!("no query matched");
  441. }
  442. pub(crate) fn dispatch<F, E>(&mut self, cx: &mut Context, emit: F) -> Result<(), E>
  443. where
  444. F: FnOnce(&mut Context, (IpRepr, UdpRepr, &[u8])) -> Result<(), E>,
  445. {
  446. let hop_limit = self.hop_limit.unwrap_or(64);
  447. for q in self.queries.iter_mut().flatten() {
  448. if let State::Pending(pq) = &mut q.state {
  449. // As per RFC 6762 any DNS query ending in .local. MUST be sent as mdns
  450. // so we internally overwrite the servers for any of those queries
  451. // in this function.
  452. let servers = match pq.mdns {
  453. #[cfg(feature = "socket-mdns")]
  454. MulticastDns::Enabled => &[
  455. #[cfg(feature = "proto-ipv6")]
  456. MDNS_IPV6_ADDR,
  457. #[cfg(feature = "proto-ipv4")]
  458. MDNS_IPV4_ADDR,
  459. ],
  460. MulticastDns::Disabled => self.servers.as_slice(),
  461. };
  462. let timeout = if let Some(timeout) = pq.timeout_at {
  463. timeout
  464. } else {
  465. let v = cx.now() + RETRANSMIT_TIMEOUT;
  466. pq.timeout_at = Some(v);
  467. v
  468. };
  469. // Check timeout
  470. if timeout < cx.now() {
  471. // DNS timeout
  472. pq.timeout_at = Some(cx.now() + RETRANSMIT_TIMEOUT);
  473. pq.retransmit_at = Instant::ZERO;
  474. pq.delay = RETRANSMIT_DELAY;
  475. // Try next server. We check below whether we've tried all servers.
  476. pq.server_idx += 1;
  477. }
  478. // Check if we've run out of servers to try.
  479. if pq.server_idx >= servers.len() {
  480. net_trace!("already tried all servers.");
  481. q.set_state(State::Failure);
  482. continue;
  483. }
  484. // Check so the IP address is valid
  485. if servers[pq.server_idx].is_unspecified() {
  486. net_trace!("invalid unspecified DNS server addr.");
  487. q.set_state(State::Failure);
  488. continue;
  489. }
  490. if pq.retransmit_at > cx.now() {
  491. // query is waiting for retransmit
  492. continue;
  493. }
  494. let repr = Repr {
  495. transaction_id: pq.txid,
  496. flags: Flags::RECURSION_DESIRED,
  497. opcode: Opcode::Query,
  498. question: Question {
  499. name: &pq.name,
  500. type_: pq.type_,
  501. },
  502. };
  503. let mut payload = [0u8; 512];
  504. let payload = &mut payload[..repr.buffer_len()];
  505. repr.emit(&mut Packet::new_unchecked(payload));
  506. let dst_port = match pq.mdns {
  507. #[cfg(feature = "socket-mdns")]
  508. MulticastDns::Enabled => MDNS_DNS_PORT,
  509. MulticastDns::Disabled => DNS_PORT,
  510. };
  511. let udp_repr = UdpRepr {
  512. src_port: pq.port,
  513. dst_port,
  514. };
  515. let dst_addr = servers[pq.server_idx];
  516. let src_addr = cx.get_source_address(dst_addr).unwrap(); // TODO remove unwrap
  517. let ip_repr = IpRepr::new(
  518. src_addr,
  519. dst_addr,
  520. IpProtocol::Udp,
  521. udp_repr.header_len() + payload.len(),
  522. hop_limit,
  523. );
  524. net_trace!(
  525. "sending {} octets to {} from port {}",
  526. payload.len(),
  527. ip_repr.dst_addr(),
  528. udp_repr.src_port
  529. );
  530. emit(cx, (ip_repr, udp_repr, payload))?;
  531. pq.retransmit_at = cx.now() + pq.delay;
  532. pq.delay = MAX_RETRANSMIT_DELAY.min(pq.delay * 2);
  533. return Ok(());
  534. }
  535. }
  536. // Nothing to dispatch
  537. Ok(())
  538. }
  539. pub(crate) fn poll_at(&self, _cx: &Context) -> PollAt {
  540. self.queries
  541. .iter()
  542. .flatten()
  543. .filter_map(|q| match &q.state {
  544. State::Pending(pq) => Some(PollAt::Time(pq.retransmit_at)),
  545. State::Completed(_) => None,
  546. State::Failure => None,
  547. })
  548. .min()
  549. .unwrap_or(PollAt::Ingress)
  550. }
  551. }
  552. fn eq_names<'a>(
  553. mut a: impl Iterator<Item = wire::Result<&'a [u8]>>,
  554. mut b: impl Iterator<Item = wire::Result<&'a [u8]>>,
  555. ) -> wire::Result<bool> {
  556. loop {
  557. match (a.next(), b.next()) {
  558. // Handle errors
  559. (Some(Err(e)), _) => return Err(e),
  560. (_, Some(Err(e))) => return Err(e),
  561. // Both finished -> equal
  562. (None, None) => return Ok(true),
  563. // One finished before the other -> not equal
  564. (None, _) => return Ok(false),
  565. (_, None) => return Ok(false),
  566. // Got two labels, check if they're equal
  567. (Some(Ok(la)), Some(Ok(lb))) => {
  568. if la != lb {
  569. return Ok(false);
  570. }
  571. }
  572. }
  573. }
  574. }
  575. fn copy_name<'a, const N: usize>(
  576. dest: &mut Vec<u8, N>,
  577. name: impl Iterator<Item = wire::Result<&'a [u8]>>,
  578. ) -> Result<(), wire::Error> {
  579. dest.truncate(0);
  580. for label in name {
  581. let label = label?;
  582. dest.push(label.len() as u8).map_err(|_| wire::Error)?;
  583. dest.extend_from_slice(label).map_err(|_| wire::Error)?;
  584. }
  585. // Write terminator 0x00
  586. dest.push(0).map_err(|_| wire::Error)?;
  587. Ok(())
  588. }