/src/hickory-dns/crates/proto/src/op/header.rs
Line | Count | Source |
1 | | // Copyright 2015-2021 Benjamin Fry <benjaminfry@me.com> |
2 | | // |
3 | | // Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or |
4 | | // https://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or |
5 | | // https://opensource.org/licenses/MIT>, at your option. This file may not be |
6 | | // copied, modified, or distributed except according to those terms. |
7 | | |
8 | | //! Message metadata |
9 | | |
10 | | #[cfg(test)] |
11 | | use alloc::vec::Vec; |
12 | | use core::{ |
13 | | convert::From, |
14 | | fmt, |
15 | | ops::{Deref, DerefMut}, |
16 | | }; |
17 | | |
18 | | #[cfg(feature = "serde")] |
19 | | use serde::{Deserialize, Serialize}; |
20 | | |
21 | | use crate::{ |
22 | | error::*, |
23 | | op::{op_code::OpCode, response_code::ResponseCode}, |
24 | | serialize::binary::*, |
25 | | }; |
26 | | |
27 | | /// Metadata for the `Message` struct. |
28 | | /// |
29 | | /// [RFC 1035, DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION, November 1987](https://tools.ietf.org/html/rfc1035) |
30 | | /// |
31 | | /// ```text |
32 | | /// 4.1.1. Header section format |
33 | | /// |
34 | | /// The header contains the following fields |
35 | | /// |
36 | | /// 1 1 1 1 1 1 |
37 | | /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 |
38 | | /// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ |
39 | | /// | ID | |
40 | | /// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ |
41 | | /// |QR| Opcode |AA|TC|RD|RA|ZZ|AD|CD| RCODE | /// AD and CD from RFC4035 |
42 | | /// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ |
43 | | /// | QDCOUNT / ZCOUNT | |
44 | | /// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ |
45 | | /// | ANCOUNT / PRCOUNT | |
46 | | /// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ |
47 | | /// | NSCOUNT / UPCOUNT | |
48 | | /// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ |
49 | | /// | ARCOUNT / ADCOUNT | |
50 | | /// +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ |
51 | | /// |
52 | | /// where |
53 | | /// |
54 | | /// Z Reserved for future use. Must be zero in all queries |
55 | | /// and responses. |
56 | | /// |
57 | | /// ``` |
58 | | /// |
59 | | #[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Hash)] |
60 | | #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] |
61 | | pub struct Header { |
62 | | /// The message metadata (ID, flags, response and op code) |
63 | | #[cfg_attr(feature = "serde", serde(flatten))] |
64 | | pub metadata: Metadata, |
65 | | /// Record counts for the message, for use during encoding/decoding |
66 | | #[cfg_attr(feature = "serde", serde(flatten))] |
67 | | pub counts: HeaderCounts, |
68 | | } |
69 | | |
70 | | impl BinEncodable for Header { |
71 | 6.66k | fn emit(&self, encoder: &mut BinEncoder<'_>) -> ProtoResult<()> { |
72 | | // Id |
73 | 6.66k | self.id.emit(encoder)?; |
74 | | |
75 | | // IsQuery, OpCode, Authoritative, Truncation, RecursionDesired |
76 | 6.66k | let mut q_opcd_a_t_r = if let MessageType::Response = self.message_type { |
77 | 1.45k | 0x80 |
78 | | } else { |
79 | 5.20k | 0x00 |
80 | | }; |
81 | 6.66k | q_opcd_a_t_r |= u8::from(self.op_code) << 3; |
82 | 6.66k | q_opcd_a_t_r |= if self.authoritative { 0x4 } else { 0x0 }; |
83 | 6.66k | q_opcd_a_t_r |= if self.truncation { 0x2 } else { 0x0 }; |
84 | 6.66k | q_opcd_a_t_r |= if self.recursion_desired { 0x1 } else { 0x0 }; |
85 | 6.66k | q_opcd_a_t_r.emit(encoder)?; |
86 | | |
87 | | // IsRecursionAvailable, Triple 0's, ResponseCode |
88 | 6.66k | let mut r_z_ad_cd_rcod = if self.recursion_available { |
89 | 1.04k | 0b1000_0000 |
90 | | } else { |
91 | 5.62k | 0b0000_0000 |
92 | | }; |
93 | 6.66k | r_z_ad_cd_rcod |= if self.authentic_data { |
94 | 2.22k | 0b0010_0000 |
95 | | } else { |
96 | 4.44k | 0b0000_0000 |
97 | | }; |
98 | 6.66k | r_z_ad_cd_rcod |= if self.checking_disabled { |
99 | 2.32k | 0b0001_0000 |
100 | | } else { |
101 | 4.34k | 0b0000_0000 |
102 | | }; |
103 | 6.66k | r_z_ad_cd_rcod |= self.response_code.low(); |
104 | 6.66k | r_z_ad_cd_rcod.emit(encoder)?; |
105 | | |
106 | 6.66k | self.counts.queries.emit(encoder)?; |
107 | 6.66k | self.counts.answers.emit(encoder)?; |
108 | 6.66k | self.counts.authorities.emit(encoder)?; |
109 | 6.66k | self.counts.additionals.emit(encoder)?; |
110 | | |
111 | 6.66k | Ok(()) |
112 | 6.66k | } |
113 | | } |
114 | | |
115 | | impl<'r> BinDecodable<'r> for Header { |
116 | 17.7k | fn read(decoder: &mut BinDecoder<'r>) -> Result<Self, DecodeError> { |
117 | 17.7k | let id = decoder.read_u16()?.unverified(/*it is valid for this to be any u16*/); |
118 | | |
119 | 17.6k | let q_opcd_a_t_r = decoder.pop()?.unverified(/*used as a bitfield, this is safe*/); |
120 | | // if the first bit is set |
121 | 17.6k | let message_type = if (0b1000_0000 & q_opcd_a_t_r) == 0b1000_0000 { |
122 | 3.69k | MessageType::Response |
123 | | } else { |
124 | 13.9k | MessageType::Query |
125 | | }; |
126 | | // the 4bit opcode, masked and then shifted right 3bits for the u8... |
127 | 17.6k | let op_code = OpCode::from_u8((0b0111_1000 & q_opcd_a_t_r) >> 3); |
128 | 17.6k | let authoritative = (0b0000_0100 & q_opcd_a_t_r) == 0b0000_0100; |
129 | 17.6k | let truncation = (0b0000_0010 & q_opcd_a_t_r) == 0b0000_0010; |
130 | 17.6k | let recursion_desired = (0b0000_0001 & q_opcd_a_t_r) == 0b0000_0001; |
131 | | |
132 | 17.6k | let r_z_ad_cd_rcod = decoder.pop()?.unverified(/*used as a bitfield, this is safe*/); // fail fast... |
133 | | |
134 | 17.6k | let recursion_available = (0b1000_0000 & r_z_ad_cd_rcod) == 0b1000_0000; |
135 | 17.6k | let authentic_data = (0b0010_0000 & r_z_ad_cd_rcod) == 0b0010_0000; |
136 | 17.6k | let checking_disabled = (0b0001_0000 & r_z_ad_cd_rcod) == 0b0001_0000; |
137 | 17.6k | let response_code: u8 = 0b0000_1111 & r_z_ad_cd_rcod; |
138 | 17.6k | let response_code = ResponseCode::from_low(response_code); |
139 | | |
140 | 17.6k | let metadata = Metadata { |
141 | 17.6k | id, |
142 | 17.6k | message_type, |
143 | 17.6k | op_code, |
144 | 17.6k | authoritative, |
145 | 17.6k | truncation, |
146 | 17.6k | recursion_desired, |
147 | 17.6k | recursion_available, |
148 | 17.6k | authentic_data, |
149 | 17.6k | checking_disabled, |
150 | 17.6k | response_code, |
151 | 17.6k | }; |
152 | | |
153 | 17.6k | let counts = HeaderCounts { |
154 | 17.6k | queries: decoder.read_u16()?.unverified(/*this must be verified when reading queries*/), |
155 | 17.6k | answers: decoder.read_u16()?.unverified(/*this must be verified when reading answers*/), |
156 | 17.6k | authorities: decoder.read_u16()?.unverified(/*this must be verified when reading answers*/), |
157 | 17.6k | additionals: decoder.read_u16()?.unverified(/*this must be verified when reading answers*/), |
158 | | }; |
159 | | |
160 | 17.6k | Ok(Self { metadata, counts }) |
161 | 17.7k | } |
162 | | } |
163 | | |
164 | | impl EncodedSize for Header { |
165 | | const LEN: usize = 12; |
166 | | } |
167 | | |
168 | | impl Deref for Header { |
169 | | type Target = Metadata; |
170 | | |
171 | 66.6k | fn deref(&self) -> &Self::Target { |
172 | 66.6k | &self.metadata |
173 | 66.6k | } |
174 | | } |
175 | | |
176 | | impl DerefMut for Header { |
177 | 0 | fn deref_mut(&mut self) -> &mut Self::Target { |
178 | 0 | &mut self.metadata |
179 | 0 | } |
180 | | } |
181 | | |
182 | | /// Message metadata, including the message ID, flags, response code, and op code. |
183 | | #[non_exhaustive] |
184 | | #[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Hash)] |
185 | | #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] |
186 | | pub struct Metadata { |
187 | | /// ```text |
188 | | /// ID A 16 bit identifier assigned by the program that |
189 | | /// generates any kind of query. This identifier is copied |
190 | | /// the corresponding reply and can be used by the requester |
191 | | /// to match up replies to outstanding queries. |
192 | | /// ``` |
193 | | pub id: u16, |
194 | | /// ```text |
195 | | /// QR A one bit field that specifies whether this message is a |
196 | | /// query (0), or a response (1). |
197 | | /// ``` |
198 | | pub message_type: MessageType, |
199 | | /// ```text |
200 | | /// OPCODE A four bit field that specifies kind of query in this |
201 | | /// message. This value is set by the originator of a query |
202 | | /// and copied into the response. The values are: <see super::op_code> |
203 | | /// ``` |
204 | | pub op_code: OpCode, |
205 | | /// ```text |
206 | | /// AA Authoritative Answer - this bit is valid in responses, |
207 | | /// and specifies that the responding name server is an |
208 | | /// authority for the domain name in question section. |
209 | | /// |
210 | | /// Note that the contents of the answer section may have |
211 | | /// multiple owner names because of aliases. The AA bit |
212 | | /// corresponds to the name which matches the query name, or |
213 | | /// the first owner name in the answer section. |
214 | | /// ``` |
215 | | pub authoritative: bool, |
216 | | /// ```text |
217 | | /// TC TrunCation - specifies that this message was truncated |
218 | | /// due to length greater than that permitted on the |
219 | | /// transmission channel. |
220 | | /// ``` |
221 | | pub truncation: bool, |
222 | | /// ```text |
223 | | /// RD Recursion Desired - this bit may be set in a query and |
224 | | /// is copied into the response. If RD is set, it directs |
225 | | /// the name server to pursue the query recursively. |
226 | | /// Recursive query support is optional. |
227 | | /// ``` |
228 | | pub recursion_desired: bool, |
229 | | /// ```text |
230 | | /// RA Recursion Available - this be is set or cleared in a |
231 | | /// response, and denotes whether recursive query support is |
232 | | /// available in the name server. |
233 | | /// ``` |
234 | | pub recursion_available: bool, |
235 | | /// [RFC 4035, DNSSEC Resource Records, March 2005](https://tools.ietf.org/html/rfc4035#section-3.1.6) |
236 | | /// |
237 | | /// ```text |
238 | | /// |
239 | | /// 3.1.6. The AD and CD Bits in an Authoritative Response |
240 | | /// |
241 | | /// The CD and AD bits are designed for use in communication between |
242 | | /// security-aware resolvers and security-aware recursive name servers. |
243 | | /// These bits are for the most part not relevant to query processing by |
244 | | /// security-aware authoritative name servers. |
245 | | /// |
246 | | /// A security-aware name server does not perform signature validation |
247 | | /// for authoritative data during query processing, even when the CD bit |
248 | | /// is clear. A security-aware name server SHOULD clear the CD bit when |
249 | | /// composing an authoritative response. |
250 | | /// |
251 | | /// A security-aware name server MUST NOT set the AD bit in a response |
252 | | /// unless the name server considers all RRsets in the Answer and |
253 | | /// Authority sections of the response to be authentic. A security-aware |
254 | | /// name server's local policy MAY consider data from an authoritative |
255 | | /// zone to be authentic without further validation. However, the name |
256 | | /// server MUST NOT do so unless the name server obtained the |
257 | | /// authoritative zone via secure means (such as a secure zone transfer |
258 | | /// mechanism) and MUST NOT do so unless this behavior has been |
259 | | /// configured explicitly. |
260 | | /// |
261 | | /// A security-aware name server that supports recursion MUST follow the |
262 | | /// rules for the CD and AD bits given in Section 3.2 when generating a |
263 | | /// response that involves data obtained via recursion. |
264 | | /// ``` |
265 | | pub authentic_data: bool, |
266 | | /// See [`Metadata::authentic_data`] for more information on the CD bit. |
267 | | pub checking_disabled: bool, |
268 | | /// ```text |
269 | | /// RCODE Response code - this 4 bit field is set as part of |
270 | | /// responses. The values have the following |
271 | | /// interpretation: <see super::response_code> |
272 | | /// ``` |
273 | | pub response_code: ResponseCode, |
274 | | } |
275 | | |
276 | | impl Metadata { |
277 | | /// Construct a new `Metadata` with the given `id`, `message_type`, and `op_code`. |
278 | 0 | pub const fn new(id: u16, message_type: MessageType, op_code: OpCode) -> Self { |
279 | 0 | Self { |
280 | 0 | id, |
281 | 0 | message_type, |
282 | 0 | op_code, |
283 | 0 | authoritative: false, |
284 | 0 | truncation: false, |
285 | 0 | recursion_desired: false, |
286 | 0 | recursion_available: false, |
287 | 0 | authentic_data: false, |
288 | 0 | checking_disabled: false, |
289 | 0 | response_code: ResponseCode::NoError, |
290 | 0 | } |
291 | 0 | } |
292 | | |
293 | | /// Construct new metadata based off the request metadata. |
294 | | /// |
295 | | /// This copies over the RD (recursion-desired) and CD (checking-disabled), |
296 | | /// as well as the op_code and id of the request. |
297 | | /// |
298 | | /// See <https://datatracker.ietf.org/doc/html/rfc6895#section-2> |
299 | | /// |
300 | | /// ```text |
301 | | /// The AA, TC, RD, RA, and CD bits are each theoretically meaningful |
302 | | /// only in queries or only in responses, depending on the bit. The AD |
303 | | /// bit was only meaningful in responses but is expected to have a |
304 | | /// separate but related meaning in queries (see Section 5.7 of |
305 | | /// [RFC6840]). Only the RD and CD bits are expected to be copied from |
306 | | /// the query to the response; however, some DNS implementations copy all |
307 | | /// the query header as the initial value of the response header. Thus, |
308 | | /// any attempt to use a "query" bit with a different meaning in a |
309 | | /// response or to define a query meaning for a "response" bit may be |
310 | | /// dangerous, given the existing implementation. Meanings for these |
311 | | /// bits may only be assigned by a Standards Action. |
312 | | /// ``` |
313 | 0 | pub fn response_from_request(req: &Self) -> Self { |
314 | 0 | Self { |
315 | 0 | id: req.id, |
316 | 0 | message_type: MessageType::Response, |
317 | 0 | op_code: req.op_code, |
318 | 0 | authoritative: false, |
319 | 0 | truncation: false, |
320 | 0 | recursion_desired: req.recursion_desired, |
321 | 0 | recursion_available: false, |
322 | 0 | authentic_data: false, |
323 | 0 | checking_disabled: req.checking_disabled, |
324 | 0 | response_code: ResponseCode::default(), |
325 | 0 | } |
326 | 0 | } |
327 | | |
328 | | /// A method to get all header flags (useful for Display purposes) |
329 | 0 | pub fn flags(&self) -> Flags { |
330 | 0 | Flags { |
331 | 0 | authoritative: self.authoritative, |
332 | 0 | authentic_data: self.authentic_data, |
333 | 0 | checking_disabled: self.checking_disabled, |
334 | 0 | recursion_available: self.recursion_available, |
335 | 0 | recursion_desired: self.recursion_desired, |
336 | 0 | truncation: self.truncation, |
337 | 0 | } |
338 | 0 | } |
339 | | |
340 | | /// This combines the high and low response code values to form the complete ResponseCode from the EDNS record. |
341 | | /// The existing high order bits will be overwritten (if set), and `high_response_code` will be merge with |
342 | | /// the existing low order bits. |
343 | | /// |
344 | | /// This is intended for use during decoding. |
345 | | #[doc(hidden)] |
346 | 1.95k | pub fn merge_response_code(&mut self, high_response_code: u8) { |
347 | 1.95k | self.response_code = ResponseCode::from(high_response_code, self.response_code.low()); |
348 | 1.95k | } |
349 | | } |
350 | | |
351 | | impl fmt::Display for Metadata { |
352 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { |
353 | 0 | write!( |
354 | 0 | f, |
355 | 0 | "{id}:{message_type}:{flags}:{code:?}:{op_code}", |
356 | | id = self.id, |
357 | | message_type = self.message_type, |
358 | 0 | flags = self.flags(), |
359 | | code = self.response_code, |
360 | | op_code = self.op_code, |
361 | | ) |
362 | 0 | } |
363 | | } |
364 | | |
365 | | /// Tracks the counts of the records in the Message. |
366 | | /// |
367 | | /// This is only used internally during serialization. |
368 | | #[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd, Eq, Hash)] |
369 | | #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] |
370 | | pub struct HeaderCounts { |
371 | | /// The number of queries in the Message |
372 | | pub queries: u16, |
373 | | /// The number of answer records in the Message |
374 | | pub answers: u16, |
375 | | /// The number of authority records in the Message |
376 | | pub authorities: u16, |
377 | | /// The number of additional records in the Message |
378 | | pub additionals: u16, |
379 | | } |
380 | | |
381 | | /// Message types are either Query (also Update) or Response |
382 | | #[derive(Debug, PartialEq, Eq, PartialOrd, Copy, Clone, Hash)] |
383 | | #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] |
384 | | pub enum MessageType { |
385 | | /// Queries are Client requests, these are either Queries or Updates |
386 | | Query, |
387 | | /// Response message from the Server or upstream Resolver |
388 | | Response, |
389 | | } |
390 | | |
391 | | impl fmt::Display for MessageType { |
392 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { |
393 | 0 | let s = match self { |
394 | 0 | Self::Query => "QUERY", |
395 | 0 | Self::Response => "RESPONSE", |
396 | | }; |
397 | | |
398 | 0 | f.write_str(s) |
399 | 0 | } |
400 | | } |
401 | | |
402 | | /// All the flags of the request/response header |
403 | | #[derive(Clone, Copy, PartialEq, Eq, Hash)] |
404 | | pub struct Flags { |
405 | | authoritative: bool, |
406 | | truncation: bool, |
407 | | recursion_desired: bool, |
408 | | recursion_available: bool, |
409 | | authentic_data: bool, |
410 | | checking_disabled: bool, |
411 | | } |
412 | | |
413 | | /// We are following the `dig` commands display format for the header flags |
414 | | /// |
415 | | /// Example: "RD,AA,RA;" is Recursion-Desired, Authoritative-Answer, Recursion-Available. |
416 | | impl fmt::Display for Flags { |
417 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { |
418 | | const SEPARATOR: &str = ","; |
419 | | |
420 | 0 | let flags = [ |
421 | 0 | (self.recursion_desired, "RD"), |
422 | 0 | (self.checking_disabled, "CD"), |
423 | 0 | (self.truncation, "TC"), |
424 | 0 | (self.authoritative, "AA"), |
425 | 0 | (self.recursion_available, "RA"), |
426 | 0 | (self.authentic_data, "AD"), |
427 | 0 | ]; |
428 | | |
429 | 0 | let mut iter = flags |
430 | 0 | .iter() |
431 | 0 | .cloned() |
432 | 0 | .filter_map(|(flag, s)| if flag { Some(s) } else { None }); |
433 | | |
434 | | // print first without a separator, then print the rest. |
435 | 0 | if let Some(s) = iter.next() { |
436 | 0 | f.write_str(s)? |
437 | 0 | } |
438 | 0 | for s in iter { |
439 | 0 | f.write_str(SEPARATOR)?; |
440 | 0 | f.write_str(s)?; |
441 | | } |
442 | | |
443 | 0 | Ok(()) |
444 | 0 | } |
445 | | } |
446 | | |
447 | | #[cfg(test)] |
448 | | mod tests { |
449 | | use super::*; |
450 | | |
451 | | #[test] |
452 | | fn test_parse() { |
453 | | let byte_vec = vec![ |
454 | | 0x01, 0x10, 0xAA, 0x83, // 0b1010 1010 1000 0011 |
455 | | 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, |
456 | | ]; |
457 | | |
458 | | let mut decoder = BinDecoder::new(&byte_vec); |
459 | | |
460 | | let expect = Header { |
461 | | metadata: Metadata { |
462 | | id: 0x0110, |
463 | | message_type: MessageType::Response, |
464 | | op_code: OpCode::Update, |
465 | | authoritative: false, |
466 | | truncation: true, |
467 | | recursion_desired: false, |
468 | | recursion_available: true, |
469 | | authentic_data: false, |
470 | | checking_disabled: false, |
471 | | response_code: ResponseCode::NXDomain, |
472 | | }, |
473 | | counts: HeaderCounts { |
474 | | queries: 0x8877, |
475 | | answers: 0x6655, |
476 | | authorities: 0x4433, |
477 | | additionals: 0x2211, |
478 | | }, |
479 | | }; |
480 | | |
481 | | let got = Header::read(&mut decoder).unwrap(); |
482 | | |
483 | | assert_eq!(got, expect); |
484 | | } |
485 | | |
486 | | #[test] |
487 | | fn test_write() { |
488 | | let header = Header { |
489 | | metadata: Metadata { |
490 | | id: 0x0110, |
491 | | message_type: MessageType::Response, |
492 | | op_code: OpCode::Update, |
493 | | authoritative: false, |
494 | | truncation: true, |
495 | | recursion_desired: false, |
496 | | recursion_available: true, |
497 | | authentic_data: false, |
498 | | checking_disabled: false, |
499 | | response_code: ResponseCode::NXDomain, |
500 | | }, |
501 | | counts: HeaderCounts { |
502 | | queries: 0x8877, |
503 | | answers: 0x6655, |
504 | | authorities: 0x4433, |
505 | | additionals: 0x2211, |
506 | | }, |
507 | | }; |
508 | | |
509 | | let expect = vec![ |
510 | | 0x01, 0x10, 0xAA, 0x83, // 0b1010 1010 1000 0011 |
511 | | 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, |
512 | | ]; |
513 | | |
514 | | let mut bytes = Vec::with_capacity(512); |
515 | | { |
516 | | let mut encoder = BinEncoder::new(&mut bytes); |
517 | | header.emit(&mut encoder).unwrap(); |
518 | | } |
519 | | |
520 | | assert_eq!(bytes, expect); |
521 | | } |
522 | | } |