##// END OF EJS Templates
node: manually implement Debug...
Augie Fackler -
r50070:34decbaf default
parent child Browse files
Show More
@@ -1,421 +1,430 b''
1 1 // Copyright 2019-2020 Georges Racinet <georges.racinet@octobus.net>
2 2 //
3 3 // This software may be used and distributed according to the terms of the
4 4 // GNU General Public License version 2 or any later version.
5 5
6 6 //! Definitions and utilities for Revision nodes
7 7 //!
8 8 //! In Mercurial code base, it is customary to call "a node" the binary SHA
9 9 //! of a revision.
10 10
11 11 use crate::errors::HgError;
12 12 use bytes_cast::BytesCast;
13 13 use std::convert::{TryFrom, TryInto};
14 14 use std::fmt;
15 15
16 16 /// The length in bytes of a `Node`
17 17 ///
18 18 /// This constant is meant to ease refactors of this module, and
19 19 /// are private so that calling code does not expect all nodes have
20 20 /// the same size, should we support several formats concurrently in
21 21 /// the future.
22 22 pub const NODE_BYTES_LENGTH: usize = 20;
23 23
24 24 /// Id of the null node.
25 25 ///
26 26 /// Used to indicate the absence of node.
27 27 pub const NULL_NODE_ID: [u8; NODE_BYTES_LENGTH] = [0u8; NODE_BYTES_LENGTH];
28 28
29 29 /// The length in bytes of a `Node`
30 30 ///
31 31 /// see also `NODES_BYTES_LENGTH` about it being private.
32 32 const NODE_NYBBLES_LENGTH: usize = 2 * NODE_BYTES_LENGTH;
33 33
34 34 /// Default for UI presentation
35 35 const SHORT_PREFIX_DEFAULT_NYBBLES_LENGTH: u8 = 12;
36 36
37 37 /// Private alias for readability and to ease future change
38 38 type NodeData = [u8; NODE_BYTES_LENGTH];
39 39
40 40 /// Binary revision SHA
41 41 ///
42 42 /// ## Future changes of hash size
43 43 ///
44 44 /// To accomodate future changes of hash size, Rust callers
45 45 /// should use the conversion methods at the boundaries (FFI, actual
46 46 /// computation of hashes and I/O) only, and only if required.
47 47 ///
48 48 /// All other callers outside of unit tests should just handle `Node` values
49 49 /// and never make any assumption on the actual length, using [`nybbles_len`]
50 50 /// if they need a loop boundary.
51 51 ///
52 52 /// All methods that create a `Node` either take a type that enforces
53 53 /// the size or return an error at runtime.
54 54 ///
55 55 /// [`nybbles_len`]: #method.nybbles_len
56 #[derive(Copy, Clone, Debug, PartialEq, BytesCast, derive_more::From)]
56 #[derive(Copy, Clone, PartialEq, BytesCast, derive_more::From)]
57 57 #[repr(transparent)]
58 58 pub struct Node {
59 59 data: NodeData,
60 60 }
61 61
62 impl fmt::Debug for Node {
63 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64 let n = format!("{:x?}", self.data);
65 // We're using debug_tuple because it makes the output a little
66 // more compact without losing data.
67 f.debug_tuple("Node").field(&n).finish()
68 }
69 }
70
62 71 /// The node value for NULL_REVISION
63 72 pub const NULL_NODE: Node = Node {
64 73 data: [0; NODE_BYTES_LENGTH],
65 74 };
66 75
67 76 /// Return an error if the slice has an unexpected length
68 77 impl<'a> TryFrom<&'a [u8]> for &'a Node {
69 78 type Error = ();
70 79
71 80 #[inline]
72 81 fn try_from(bytes: &'a [u8]) -> Result<Self, Self::Error> {
73 82 match Node::from_bytes(bytes) {
74 83 Ok((node, rest)) if rest.is_empty() => Ok(node),
75 84 _ => Err(()),
76 85 }
77 86 }
78 87 }
79 88
80 89 /// Return an error if the slice has an unexpected length
81 90 impl TryFrom<&'_ [u8]> for Node {
82 91 type Error = std::array::TryFromSliceError;
83 92
84 93 #[inline]
85 94 fn try_from(bytes: &'_ [u8]) -> Result<Self, Self::Error> {
86 95 let data = bytes.try_into()?;
87 96 Ok(Self { data })
88 97 }
89 98 }
90 99
91 100 impl From<&'_ NodeData> for Node {
92 101 #[inline]
93 102 fn from(data: &'_ NodeData) -> Self {
94 103 Self { data: *data }
95 104 }
96 105 }
97 106
98 107 impl fmt::LowerHex for Node {
99 108 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
100 109 for &byte in &self.data {
101 110 write!(f, "{:02x}", byte)?
102 111 }
103 112 Ok(())
104 113 }
105 114 }
106 115
107 116 #[derive(Debug)]
108 117 pub struct FromHexError;
109 118
110 119 /// Low level utility function, also for prefixes
111 120 fn get_nybble(s: &[u8], i: usize) -> u8 {
112 121 if i % 2 == 0 {
113 122 s[i / 2] >> 4
114 123 } else {
115 124 s[i / 2] & 0x0f
116 125 }
117 126 }
118 127
119 128 impl Node {
120 129 /// Retrieve the `i`th half-byte of the binary data.
121 130 ///
122 131 /// This is also the `i`th hexadecimal digit in numeric form,
123 132 /// also called a [nybble](https://en.wikipedia.org/wiki/Nibble).
124 133 pub fn get_nybble(&self, i: usize) -> u8 {
125 134 get_nybble(&self.data, i)
126 135 }
127 136
128 137 /// Length of the data, in nybbles
129 138 pub fn nybbles_len(&self) -> usize {
130 139 // public exposure as an instance method only, so that we can
131 140 // easily support several sizes of hashes if needed in the future.
132 141 NODE_NYBBLES_LENGTH
133 142 }
134 143
135 144 /// Convert from hexadecimal string representation
136 145 ///
137 146 /// Exact length is required.
138 147 ///
139 148 /// To be used in FFI and I/O only, in order to facilitate future
140 149 /// changes of hash format.
141 150 pub fn from_hex(hex: impl AsRef<[u8]>) -> Result<Node, FromHexError> {
142 151 let prefix = NodePrefix::from_hex(hex)?;
143 152 if prefix.nybbles_len() == NODE_NYBBLES_LENGTH {
144 153 Ok(Self { data: prefix.data })
145 154 } else {
146 155 Err(FromHexError)
147 156 }
148 157 }
149 158
150 159 /// `from_hex`, but for input from an internal file of the repository such
151 160 /// as a changelog or manifest entry.
152 161 ///
153 162 /// An error is treated as repository corruption.
154 163 pub fn from_hex_for_repo(hex: impl AsRef<[u8]>) -> Result<Node, HgError> {
155 164 Self::from_hex(hex.as_ref()).map_err(|FromHexError| {
156 165 HgError::CorruptedRepository(format!(
157 166 "Expected a full hexadecimal node ID, found {}",
158 167 String::from_utf8_lossy(hex.as_ref())
159 168 ))
160 169 })
161 170 }
162 171
163 172 /// Provide access to binary data
164 173 ///
165 174 /// This is needed by FFI layers, for instance to return expected
166 175 /// binary values to Python.
167 176 pub fn as_bytes(&self) -> &[u8] {
168 177 &self.data
169 178 }
170 179
171 180 pub fn short(&self) -> NodePrefix {
172 181 NodePrefix {
173 182 nybbles_len: SHORT_PREFIX_DEFAULT_NYBBLES_LENGTH,
174 183 data: self.data,
175 184 }
176 185 }
177 186
178 187 pub fn pad_to_256_bits(&self) -> [u8; 32] {
179 188 let mut bits = [0; 32];
180 189 bits[..NODE_BYTES_LENGTH].copy_from_slice(&self.data);
181 190 bits
182 191 }
183 192 }
184 193
185 194 /// The beginning of a binary revision SHA.
186 195 ///
187 196 /// Since it can potentially come from an hexadecimal representation with
188 197 /// odd length, it needs to carry around whether the last 4 bits are relevant
189 198 /// or not.
190 199 #[derive(Debug, PartialEq, Copy, Clone)]
191 200 pub struct NodePrefix {
192 201 /// In `1..=NODE_NYBBLES_LENGTH`
193 202 nybbles_len: u8,
194 203 /// The first `4 * length_in_nybbles` bits are used (considering bits
195 204 /// within a bytes in big-endian: most significant first), the rest
196 205 /// are zero.
197 206 data: NodeData,
198 207 }
199 208
200 209 impl NodePrefix {
201 210 /// Convert from hexadecimal string representation
202 211 ///
203 212 /// Similarly to `hex::decode`, can be used with Unicode string types
204 213 /// (`String`, `&str`) as well as bytes.
205 214 ///
206 215 /// To be used in FFI and I/O only, in order to facilitate future
207 216 /// changes of hash format.
208 217 pub fn from_hex(hex: impl AsRef<[u8]>) -> Result<Self, FromHexError> {
209 218 let hex = hex.as_ref();
210 219 let len = hex.len();
211 220 if len > NODE_NYBBLES_LENGTH || len == 0 {
212 221 return Err(FromHexError);
213 222 }
214 223
215 224 let mut data = [0; NODE_BYTES_LENGTH];
216 225 let mut nybbles_len = 0;
217 226 for &ascii_byte in hex {
218 227 let nybble = match char::from(ascii_byte).to_digit(16) {
219 228 Some(digit) => digit as u8,
220 229 None => return Err(FromHexError),
221 230 };
222 231 // Fill in the upper half of a byte first, then the lower half.
223 232 let shift = if nybbles_len % 2 == 0 { 4 } else { 0 };
224 233 data[nybbles_len as usize / 2] |= nybble << shift;
225 234 nybbles_len += 1;
226 235 }
227 236 Ok(Self { data, nybbles_len })
228 237 }
229 238
230 239 pub fn nybbles_len(&self) -> usize {
231 240 self.nybbles_len as _
232 241 }
233 242
234 243 pub fn is_prefix_of(&self, node: &Node) -> bool {
235 244 let full_bytes = self.nybbles_len() / 2;
236 245 if self.data[..full_bytes] != node.data[..full_bytes] {
237 246 return false;
238 247 }
239 248 if self.nybbles_len() % 2 == 0 {
240 249 return true;
241 250 }
242 251 let last = self.nybbles_len() - 1;
243 252 self.get_nybble(last) == node.get_nybble(last)
244 253 }
245 254
246 255 /// Retrieve the `i`th half-byte from the prefix.
247 256 ///
248 257 /// This is also the `i`th hexadecimal digit in numeric form,
249 258 /// also called a [nybble](https://en.wikipedia.org/wiki/Nibble).
250 259 pub fn get_nybble(&self, i: usize) -> u8 {
251 260 assert!(i < self.nybbles_len());
252 261 get_nybble(&self.data, i)
253 262 }
254 263
255 264 fn iter_nybbles(&self) -> impl Iterator<Item = u8> + '_ {
256 265 (0..self.nybbles_len()).map(move |i| get_nybble(&self.data, i))
257 266 }
258 267
259 268 /// Return the index first nybble that's different from `node`
260 269 ///
261 270 /// If the return value is `None` that means that `self` is
262 271 /// a prefix of `node`, but the current method is a bit slower
263 272 /// than `is_prefix_of`.
264 273 ///
265 274 /// Returned index is as in `get_nybble`, i.e., starting at 0.
266 275 pub fn first_different_nybble(&self, node: &Node) -> Option<usize> {
267 276 self.iter_nybbles()
268 277 .zip(NodePrefix::from(*node).iter_nybbles())
269 278 .position(|(a, b)| a != b)
270 279 }
271 280 }
272 281
273 282 impl fmt::LowerHex for NodePrefix {
274 283 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
275 284 let full_bytes = self.nybbles_len() / 2;
276 285 for &byte in &self.data[..full_bytes] {
277 286 write!(f, "{:02x}", byte)?
278 287 }
279 288 if self.nybbles_len() % 2 == 1 {
280 289 let last = self.nybbles_len() - 1;
281 290 write!(f, "{:x}", self.get_nybble(last))?
282 291 }
283 292 Ok(())
284 293 }
285 294 }
286 295
287 296 /// A shortcut for full `Node` references
288 297 impl From<&'_ Node> for NodePrefix {
289 298 fn from(node: &'_ Node) -> Self {
290 299 NodePrefix {
291 300 nybbles_len: node.nybbles_len() as _,
292 301 data: node.data,
293 302 }
294 303 }
295 304 }
296 305
297 306 /// A shortcut for full `Node` references
298 307 impl From<Node> for NodePrefix {
299 308 fn from(node: Node) -> Self {
300 309 NodePrefix {
301 310 nybbles_len: node.nybbles_len() as _,
302 311 data: node.data,
303 312 }
304 313 }
305 314 }
306 315
307 316 impl PartialEq<Node> for NodePrefix {
308 317 fn eq(&self, other: &Node) -> bool {
309 318 Self::from(*other) == *self
310 319 }
311 320 }
312 321
313 322 #[cfg(test)]
314 323 mod tests {
315 324 use super::*;
316 325
317 326 const SAMPLE_NODE_HEX: &str = "0123456789abcdeffedcba9876543210deadbeef";
318 327 const SAMPLE_NODE: Node = Node {
319 328 data: [
320 329 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xfe, 0xdc, 0xba,
321 330 0x98, 0x76, 0x54, 0x32, 0x10, 0xde, 0xad, 0xbe, 0xef,
322 331 ],
323 332 };
324 333
325 334 /// Pad an hexadecimal string to reach `NODE_NYBBLES_LENGTH`
326 335 /// The padding is made with zeros.
327 336 pub fn hex_pad_right(hex: &str) -> String {
328 337 let mut res = hex.to_string();
329 338 while res.len() < NODE_NYBBLES_LENGTH {
330 339 res.push('0');
331 340 }
332 341 res
333 342 }
334 343
335 344 #[test]
336 345 fn test_node_from_hex() {
337 346 let not_hex = "012... oops";
338 347 let too_short = "0123";
339 348 let too_long = format!("{}0", SAMPLE_NODE_HEX);
340 349 assert_eq!(Node::from_hex(SAMPLE_NODE_HEX).unwrap(), SAMPLE_NODE);
341 350 assert!(Node::from_hex(not_hex).is_err());
342 351 assert!(Node::from_hex(too_short).is_err());
343 352 assert!(Node::from_hex(&too_long).is_err());
344 353 }
345 354
346 355 #[test]
347 356 fn test_node_encode_hex() {
348 357 assert_eq!(format!("{:x}", SAMPLE_NODE), SAMPLE_NODE_HEX);
349 358 }
350 359
351 360 #[test]
352 361 fn test_prefix_from_to_hex() -> Result<(), FromHexError> {
353 362 assert_eq!(format!("{:x}", NodePrefix::from_hex("0e1")?), "0e1");
354 363 assert_eq!(format!("{:x}", NodePrefix::from_hex("0e1a")?), "0e1a");
355 364 assert_eq!(
356 365 format!("{:x}", NodePrefix::from_hex(SAMPLE_NODE_HEX)?),
357 366 SAMPLE_NODE_HEX
358 367 );
359 368 Ok(())
360 369 }
361 370
362 371 #[test]
363 372 fn test_prefix_from_hex_errors() {
364 373 assert!(NodePrefix::from_hex("testgr").is_err());
365 374 let mut long = format!("{:x}", NULL_NODE);
366 375 long.push('c');
367 376 assert!(NodePrefix::from_hex(&long).is_err())
368 377 }
369 378
370 379 #[test]
371 380 fn test_is_prefix_of() -> Result<(), FromHexError> {
372 381 let mut node_data = [0; NODE_BYTES_LENGTH];
373 382 node_data[0] = 0x12;
374 383 node_data[1] = 0xca;
375 384 let node = Node::from(node_data);
376 385 assert!(NodePrefix::from_hex("12")?.is_prefix_of(&node));
377 386 assert!(!NodePrefix::from_hex("1a")?.is_prefix_of(&node));
378 387 assert!(NodePrefix::from_hex("12c")?.is_prefix_of(&node));
379 388 assert!(!NodePrefix::from_hex("12d")?.is_prefix_of(&node));
380 389 Ok(())
381 390 }
382 391
383 392 #[test]
384 393 fn test_get_nybble() -> Result<(), FromHexError> {
385 394 let prefix = NodePrefix::from_hex("dead6789cafe")?;
386 395 assert_eq!(prefix.get_nybble(0), 13);
387 396 assert_eq!(prefix.get_nybble(7), 9);
388 397 Ok(())
389 398 }
390 399
391 400 #[test]
392 401 fn test_first_different_nybble_even_prefix() {
393 402 let prefix = NodePrefix::from_hex("12ca").unwrap();
394 403 let mut node = Node::from([0; NODE_BYTES_LENGTH]);
395 404 assert_eq!(prefix.first_different_nybble(&node), Some(0));
396 405 node.data[0] = 0x13;
397 406 assert_eq!(prefix.first_different_nybble(&node), Some(1));
398 407 node.data[0] = 0x12;
399 408 assert_eq!(prefix.first_different_nybble(&node), Some(2));
400 409 node.data[1] = 0xca;
401 410 // now it is a prefix
402 411 assert_eq!(prefix.first_different_nybble(&node), None);
403 412 }
404 413
405 414 #[test]
406 415 fn test_first_different_nybble_odd_prefix() {
407 416 let prefix = NodePrefix::from_hex("12c").unwrap();
408 417 let mut node = Node::from([0; NODE_BYTES_LENGTH]);
409 418 assert_eq!(prefix.first_different_nybble(&node), Some(0));
410 419 node.data[0] = 0x13;
411 420 assert_eq!(prefix.first_different_nybble(&node), Some(1));
412 421 node.data[0] = 0x12;
413 422 assert_eq!(prefix.first_different_nybble(&node), Some(2));
414 423 node.data[1] = 0xca;
415 424 // now it is a prefix
416 425 assert_eq!(prefix.first_different_nybble(&node), None);
417 426 }
418 427 }
419 428
420 429 #[cfg(test)]
421 430 pub use tests::hex_pad_right;
General Comments 0
You need to be logged in to leave comments. Login now