]>
Commit | Line | Data |
---|---|---|
1a4d82fc JJ |
1 | // Copyright 2013-2014 The Rust Project Developers. See the COPYRIGHT |
2 | // file at the top-level directory of this distribution and at | |
3 | // http://rust-lang.org/COPYRIGHT. | |
4 | // | |
5 | // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | |
6 | // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license | |
7 | // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your | |
8 | // option. This file may not be copied, modified, or distributed | |
9 | // except according to those terms. | |
1a4d82fc JJ |
10 | |
11 | //! Hex binary-to-text encoding | |
12 | ||
13 | pub use self::FromHexError::*; | |
14 | ||
15 | use std::fmt; | |
16 | use std::error; | |
17 | ||
18 | /// A trait for converting a value to hexadecimal encoding | |
19 | pub trait ToHex { | |
20 | /// Converts the value of `self` to a hex value, returning the owned | |
21 | /// string. | |
22 | fn to_hex(&self) -> String; | |
23 | } | |
24 | ||
c34b1796 | 25 | const CHARS: &'static [u8] = b"0123456789abcdef"; |
1a4d82fc JJ |
26 | |
27 | impl ToHex for [u8] { | |
28 | /// Turn a vector of `u8` bytes into a hexadecimal string. | |
29 | /// | |
c34b1796 | 30 | /// # Examples |
1a4d82fc | 31 | /// |
c34b1796 | 32 | /// ``` |
c1a9b12d SL |
33 | /// #![feature(rustc_private)] |
34 | /// | |
1a4d82fc JJ |
35 | /// extern crate serialize; |
36 | /// use serialize::hex::ToHex; | |
37 | /// | |
38 | /// fn main () { | |
39 | /// let str = [52,32].to_hex(); | |
40 | /// println!("{}", str); | |
41 | /// } | |
42 | /// ``` | |
43 | fn to_hex(&self) -> String { | |
44 | let mut v = Vec::with_capacity(self.len() * 2); | |
85aaf69f | 45 | for &byte in self { |
c34b1796 AL |
46 | v.push(CHARS[(byte >> 4) as usize]); |
47 | v.push(CHARS[(byte & 0xf) as usize]); | |
1a4d82fc JJ |
48 | } |
49 | ||
50 | unsafe { | |
51 | String::from_utf8_unchecked(v) | |
52 | } | |
53 | } | |
54 | } | |
55 | ||
56 | /// A trait for converting hexadecimal encoded values | |
57 | pub trait FromHex { | |
58 | /// Converts the value of `self`, interpreted as hexadecimal encoded data, | |
59 | /// into an owned vector of bytes, returning the vector. | |
60 | fn from_hex(&self) -> Result<Vec<u8>, FromHexError>; | |
61 | } | |
62 | ||
63 | /// Errors that can occur when decoding a hex encoded string | |
c34b1796 | 64 | #[derive(Copy, Clone, Debug)] |
1a4d82fc JJ |
65 | pub enum FromHexError { |
66 | /// The input contained a character not part of the hex format | |
c34b1796 | 67 | InvalidHexCharacter(char, usize), |
1a4d82fc JJ |
68 | /// The input had an invalid length |
69 | InvalidHexLength, | |
70 | } | |
71 | ||
85aaf69f | 72 | impl fmt::Display for FromHexError { |
1a4d82fc JJ |
73 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
74 | match *self { | |
75 | InvalidHexCharacter(ch, idx) => | |
76 | write!(f, "Invalid character '{}' at position {}", ch, idx), | |
77 | InvalidHexLength => write!(f, "Invalid input length"), | |
78 | } | |
79 | } | |
80 | } | |
81 | ||
82 | impl error::Error for FromHexError { | |
83 | fn description(&self) -> &str { | |
84 | match *self { | |
85 | InvalidHexCharacter(_, _) => "invalid character", | |
86 | InvalidHexLength => "invalid length", | |
87 | } | |
88 | } | |
1a4d82fc JJ |
89 | } |
90 | ||
91 | ||
92 | impl FromHex for str { | |
93 | /// Convert any hexadecimal encoded string (literal, `@`, `&`, or `~`) | |
94 | /// to the byte values it encodes. | |
95 | /// | |
96 | /// You can use the `String::from_utf8` function to turn a | |
97 | /// `Vec<u8>` into a string with characters corresponding to those values. | |
98 | /// | |
c34b1796 | 99 | /// # Examples |
1a4d82fc JJ |
100 | /// |
101 | /// This converts a string literal to hexadecimal and back. | |
102 | /// | |
c34b1796 | 103 | /// ``` |
c1a9b12d SL |
104 | /// #![feature(rustc_private)] |
105 | /// | |
1a4d82fc JJ |
106 | /// extern crate serialize; |
107 | /// use serialize::hex::{FromHex, ToHex}; | |
108 | /// | |
109 | /// fn main () { | |
110 | /// let hello_str = "Hello, World".as_bytes().to_hex(); | |
111 | /// println!("{}", hello_str); | |
85aaf69f | 112 | /// let bytes = hello_str.from_hex().unwrap(); |
1a4d82fc JJ |
113 | /// println!("{:?}", bytes); |
114 | /// let result_str = String::from_utf8(bytes).unwrap(); | |
115 | /// println!("{}", result_str); | |
116 | /// } | |
117 | /// ``` | |
118 | fn from_hex(&self) -> Result<Vec<u8>, FromHexError> { | |
119 | // This may be an overestimate if there is any whitespace | |
120 | let mut b = Vec::with_capacity(self.len() / 2); | |
85aaf69f | 121 | let mut modulus = 0; |
c34b1796 | 122 | let mut buf = 0; |
1a4d82fc JJ |
123 | |
124 | for (idx, byte) in self.bytes().enumerate() { | |
125 | buf <<= 4; | |
126 | ||
127 | match byte { | |
128 | b'A'...b'F' => buf |= byte - b'A' + 10, | |
129 | b'a'...b'f' => buf |= byte - b'a' + 10, | |
130 | b'0'...b'9' => buf |= byte - b'0', | |
131 | b' '|b'\r'|b'\n'|b'\t' => { | |
132 | buf >>= 4; | |
133 | continue | |
134 | } | |
54a0048b SL |
135 | _ => { |
136 | let ch = self[idx..].chars().next().unwrap(); | |
137 | return Err(InvalidHexCharacter(ch, idx)) | |
138 | } | |
1a4d82fc JJ |
139 | } |
140 | ||
141 | modulus += 1; | |
142 | if modulus == 2 { | |
143 | modulus = 0; | |
144 | b.push(buf); | |
145 | } | |
146 | } | |
147 | ||
148 | match modulus { | |
149 | 0 => Ok(b.into_iter().collect()), | |
150 | _ => Err(InvalidHexLength), | |
151 | } | |
152 | } | |
153 | } | |
154 | ||
155 | #[cfg(test)] | |
156 | mod tests { | |
157 | extern crate test; | |
158 | use self::test::Bencher; | |
159 | use hex::{FromHex, ToHex}; | |
160 | ||
161 | #[test] | |
162 | pub fn test_to_hex() { | |
163 | assert_eq!("foobar".as_bytes().to_hex(), "666f6f626172"); | |
164 | } | |
165 | ||
166 | #[test] | |
167 | pub fn test_from_hex_okay() { | |
168 | assert_eq!("666f6f626172".from_hex().unwrap(), | |
169 | b"foobar"); | |
170 | assert_eq!("666F6F626172".from_hex().unwrap(), | |
171 | b"foobar"); | |
172 | } | |
173 | ||
174 | #[test] | |
175 | pub fn test_from_hex_odd_len() { | |
176 | assert!("666".from_hex().is_err()); | |
177 | assert!("66 6".from_hex().is_err()); | |
178 | } | |
179 | ||
180 | #[test] | |
181 | pub fn test_from_hex_invalid_char() { | |
182 | assert!("66y6".from_hex().is_err()); | |
183 | } | |
184 | ||
185 | #[test] | |
186 | pub fn test_from_hex_ignores_whitespace() { | |
187 | assert_eq!("666f 6f6\r\n26172 ".from_hex().unwrap(), | |
188 | b"foobar"); | |
189 | } | |
190 | ||
191 | #[test] | |
192 | pub fn test_to_hex_all_bytes() { | |
85aaf69f | 193 | for i in 0..256 { |
c34b1796 | 194 | assert_eq!([i as u8].to_hex(), format!("{:02x}", i as usize)); |
1a4d82fc JJ |
195 | } |
196 | } | |
197 | ||
198 | #[test] | |
199 | pub fn test_from_hex_all_bytes() { | |
85aaf69f | 200 | for i in 0..256 { |
1a4d82fc | 201 | let ii: &[u8] = &[i as u8]; |
c34b1796 | 202 | assert_eq!(format!("{:02x}", i as usize).from_hex() |
1a4d82fc JJ |
203 | .unwrap(), |
204 | ii); | |
c34b1796 | 205 | assert_eq!(format!("{:02X}", i as usize).from_hex() |
1a4d82fc JJ |
206 | .unwrap(), |
207 | ii); | |
208 | } | |
209 | } | |
210 | ||
211 | #[bench] | |
212 | pub fn bench_to_hex(b: &mut Bencher) { | |
213 | let s = "イロハニホヘト チリヌルヲ ワカヨタレソ ツネナラム \ | |
214 | ウヰノオクヤマ ケフコエテ アサキユメミシ ヱヒモセスン"; | |
215 | b.iter(|| { | |
216 | s.as_bytes().to_hex(); | |
217 | }); | |
218 | b.bytes = s.len() as u64; | |
219 | } | |
220 | ||
221 | #[bench] | |
222 | pub fn bench_from_hex(b: &mut Bencher) { | |
223 | let s = "イロハニホヘト チリヌルヲ ワカヨタレソ ツネナラム \ | |
224 | ウヰノオクヤマ ケフコエテ アサキユメミシ ヱヒモセスン"; | |
225 | let sb = s.as_bytes().to_hex(); | |
226 | b.iter(|| { | |
227 | sb.from_hex().unwrap(); | |
228 | }); | |
229 | b.bytes = sb.len() as u64; | |
230 | } | |
231 | } |