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