1 package org.djutils.decoderdumper;
2
3 import static org.junit.Assert.assertEquals;
4 import static org.junit.Assert.assertTrue;
5 import static org.junit.Assert.fail;
6
7 import java.io.BufferedOutputStream;
8 import java.io.ByteArrayOutputStream;
9 import java.io.IOException;
10 import java.io.InputStream;
11 import java.io.OutputStream;
12 import java.io.PrintStream;
13
14 import org.djutils.logger.CategoryLogger;
15 import org.junit.Test;
16
17
18
19
20
21
22
23
24
25
26
27
28 public class DecoderDumperTests
29 {
30
31
32
33
34
35
36 @Test
37 public final void testHexDumper() throws InterruptedException, IOException
38 {
39 assertEquals("Empty input yields empty output", "", HexDumper.hexDumper(new byte[] {}));
40 byte[] input = new byte[] { 1, 2 };
41 String output = HexDumper.hexDumper(input);
42 assertTrue("Output starts with address \"00000000: \"", output.startsWith("00000000: "));
43 for (int length = 1; length < 100; length++)
44 {
45 input = new byte[length];
46 assertTrue("Output ends on newline", HexDumper.hexDumper(input).endsWith("\n"));
47 }
48 input = new byte[1];
49 for (int value = 0; value < 256; value++)
50 {
51 input[0] = (byte) value;
52 output = HexDumper.hexDumper(input);
53
54 assertTrue("Output contains hex value of the only input byte embedded between spaces",
55 output.contains(String.format(" %02x ", value)));
56 }
57 assertEquals("output of 16 byte input fills one lines", 1,
58 HexDumper.hexDumper(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }).split("\n").length);
59 assertEquals(
60 "output of 17 byte input fills two lines",
61 2,
62 HexDumper.hexDumper(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }).split("\n").length);
63 assertTrue("address offset is printed at start of output", HexDumper.hexDumper(0x12345, new byte[] { 0, 1 })
64 .startsWith("00012340"));
65 Dumper<HexDumper> hd = new HexDumper(0x12345);
66 assertTrue("toString makes some sense", hd.toString().startsWith("HexDumper"));
67
68 ByteArrayOutputStream baos = new ByteArrayOutputStream();
69 hd = new HexDumper().setOutputStream(baos);
70 for (int i = 0; i < 100; i++)
71 {
72 hd.append((byte) i);
73
74 assertEquals("Number of lines check", Math.max(1, (i + 1) / 16), baos.toString().split("\n").length);
75 }
76
77 for (int i = 33; i < 127; i++)
78 {
79 String dump = HexDumper.hexDumper(new byte[] { (byte) i });
80 String letter = "" + (char) i;
81 String trimmed = dump.trim();
82 String lastLetter = trimmed.substring(trimmed.length() - 1);
83
84 assertEquals("Output ends with the provided printable character", letter, lastLetter);
85 }
86 baos.reset();
87 hd = new HexDumper().addDecoder(0, new TimeStamper()).setOutputStream(baos);
88 long startTimeStamp = System.currentTimeMillis();
89 hd.append((byte) 10);
90 long endTimeStamp = System.currentTimeMillis();
91 Thread.sleep(100);
92 hd.append((byte) 20);
93 hd.flush();
94 String result = baos.toString();
95 int spacePosition = result.indexOf(" ");
96 long recorded = Long.parseLong(result.substring(0, spacePosition).replace(".", "").replace(",", ""));
97 assertTrue("Time stamp should be within interval", startTimeStamp <= recorded);
98 assertTrue("Time stamp should be within interval", endTimeStamp >= recorded);
99 hd = new HexDumper().setOutputStream(new OutputStream()
100 {
101
102 @Override
103 public void write(final int b) throws IOException
104 {
105 throw new IOException("testing exception handling");
106 }
107 });
108 try
109 {
110 hd.append(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 });
111 fail("Writing sufficient number of bytes to output that throws an exception should have thrown an exception");
112 }
113 catch (Exception exception)
114 {
115
116 }
117 baos.reset();
118 hd = new HexDumper().setOutputStream(baos);
119 hd.append(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 });
120
121 assertTrue("ByteArrayOutputStream contains start of hex dump", baos.toString().startsWith("00000000: 00 "));
122 baos.reset();
123 PrintStream oldErrOutput = System.err;
124 PrintStream ps = new PrintStream(new BufferedOutputStream(baos));
125 System.setErr(ps);
126
127 hd = new HexDumper().setOutputStream(new OutputStream()
128 {
129
130 private StringBuilder sb = new StringBuilder();
131
132 @Override
133 public void write(final int b) throws IOException
134 {
135 if ('\n' == b)
136 {
137 CategoryLogger.always().error(this.sb.toString());
138 this.sb.setLength(0);
139 }
140 else
141 {
142 this.sb.append((char) b);
143 }
144 }
145 });
146 for (int value = 0; value < 256; value++)
147 {
148 input[0] = (byte) value;
149 hd.append(input);
150 }
151 Thread.sleep(200);
152 ps.close();
153 System.setErr(oldErrOutput);
154 result = baos.toString();
155 assertEquals("Result should be 16 lines", 16, result.split("\n").length);
156
157 baos.reset();
158 hd = new HexDumper().setOutputStream(baos).append(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 }, 4, 2);
159 hd.flush();
160 result = baos.toString();
161 assertTrue("start and length parameter select the correct bytes", result.startsWith("00000000: 05 06 "));
162 baos.reset();
163 hd = new HexDumper().setOutputStream(baos);
164 hd.append(new InputStream()
165 {
166 private int callCount = 0;
167
168 @Override
169 public int read() throws IOException
170 {
171 if (this.callCount < 10)
172 {
173 return this.callCount++;
174 }
175 return -1;
176 }
177 });
178 hd.flush();
179 result = baos.toString();
180
181 assertTrue("Ten bytes should have been accumulated",
182 result.startsWith("00000000: 00 01 02 03 04 05 06 07 08 09 "));
183 baos.reset();
184 hd = new HexDumper().setSuppressMultipleIdenticalLines(true).setOutputStream(baos);
185 for (int line = 0; line < 20; line++)
186 {
187 hd.append(new byte[] { 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57 });
188 }
189 hd.flush();
190
191 assertEquals("Suppression reduced the output to three lines", 3, baos.toString().split("\n").length);
192
193 baos.reset();
194 hd = new HexDumper().setSuppressMultipleIdenticalLines(true).setOutputStream(baos);
195 for (int line = 0; line < 20; line++)
196 {
197 hd.append(new byte[] { 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57 });
198 }
199 hd.append(new byte[] { 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56 });
200 hd.flush();
201 assertEquals("Suppression reduced the output to four lines", 4, baos.toString().split("\n").length);
202
203 baos.reset();
204 hd = new HexDumper().setSuppressMultipleIdenticalLines(true).setOutputStream(baos);
205 for (int line = 0; line < 20; line++)
206 {
207 hd.append(new byte[] { 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57 });
208 }
209 hd.append(new byte[] { 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 99 });
210 hd.flush();
211 assertEquals("Suppression reduced the output to four lines", 4, baos.toString().split("\n").length);
212
213
214 assertTrue("TimeStamper had decent toString method", new TimeStamper().toString().startsWith("TimeStamper ["));
215 }
216
217
218
219
220 @Test
221 public void testBase64Dumper()
222 {
223 assertEquals("Empty input yields empty output", "", Base64Dumper.base64Dumper(new byte[] {}));
224 byte[] input = new byte[] { 1, 2 };
225 String output = HexDumper.hexDumper(input);
226 assertTrue("Output starts with address \"00000000: \"", output.startsWith("00000000: "));
227 for (int length = 1; length < 100; length++)
228 {
229 input = new byte[length];
230 assertTrue("Output ends on newline (even though the input is invalid)", HexDumper.hexDumper(input)
231 .endsWith("\n"));
232 }
233
234 for (int pattern = 0; pattern < 256 * 256 * 256; pattern += 259)
235 {
236 input = new byte[4];
237 for (int index = 0; index < 4; index++)
238 {
239 input[index] = encode((pattern >> (18 - 6 * index)) & 0x3f);
240 }
241 output = Base64Dumper.base64Dumper(input).substring(30);
242
243 for (int index = 0; index < 3; index++)
244 {
245 int theByte = Integer.parseInt(output.substring(index * 3, index * 3 + 2), 16);
246 int expectedByte = (pattern >> (16 - 8 * index)) & 0xff;
247 assertEquals("Reconstructed byte matches corresponding byte in pattern", expectedByte, theByte);
248 }
249 }
250
251 for (int pattern = 0; pattern < 255; pattern++)
252 {
253 input = new byte[4];
254 for (int index = 0; index < 2; index++)
255 {
256 input[index] = encode(((pattern << 16) >> (18 - 6 * index)) & 0x3f);
257 }
258 input[2] = (byte) 61;
259 input[3] = (byte) 61;
260 output = Base64Dumper.base64Dumper(input).substring(30);
261
262 int theByte = Integer.parseInt(output.substring(0, 2), 16);
263 assertEquals("Reconstructed byte matches corresponding byte in patten", pattern, theByte);
264 assertTrue("Rest of result starts with at least 10 spaces", output.substring(2).startsWith(" "));
265 }
266
267 for (int pattern = 0; pattern < 255 * 255; pattern++)
268 {
269 input = new byte[4];
270 for (int index = 0; index < 3; index++)
271 {
272 input[index] = encode(((pattern << 8) >> (18 - 6 * index)) & 0x3f);
273 }
274 input[3] = (byte) 61;
275 output = Base64Dumper.base64Dumper(input).substring(30);
276
277 for (int index = 0; index < 2; index++)
278 {
279 int theByte = Integer.parseInt(output.substring(index * 3, index * 3 + 2), 16);
280 int expectedByte = (pattern >> (8 - 8 * index)) & 0xff;
281 assertEquals("Reconstructed byte matches corresponding byte in pattern", expectedByte, theByte);
282 }
283 assertTrue("Rest of result starts with at least 10 spaces", output.substring(5).startsWith(" "));
284 }
285 assertTrue("toString makes some sense", new Base64Dumper().toString().startsWith("Base64Dumper"));
286
287 String base64 = "c3VyZS4=";
288 String expectedResult = Base64Dumper.base64Dumper(base64.getBytes()).substring(30);
289
290 for (int pos = 0; pos <= base64.length(); pos++)
291 {
292 for (String insert : new String[] { " ", "\t", "\n", "\n\t" })
293 {
294 String modified = base64.substring(0, pos) + insert + base64.substring(pos);
295 String result = Base64Dumper.base64Dumper(modified.getBytes()).substring(30);
296
297 assertEquals("Extra space in input does not change output", expectedResult, result);
298 }
299 }
300
301 String result = Base64Dumper.base64Dumper(("!" + base64).getBytes()).substring(30);
302
303 assertEquals("bad char in input is (currently) ignored", expectedResult, result);
304 }
305
306
307
308
309
310
311 private byte encode(final int value)
312 {
313 if (value < 0 || value > 63)
314 {
315 throw new Error("Bad input: " + value);
316 }
317 if (value < 26)
318 {
319 return (byte) (value + 65);
320 }
321 else if (value < 52)
322 {
323 return (byte) (value - 26 + 97);
324 }
325 else if (value < 62)
326 {
327 return (byte) (value - 52 + 48);
328 }
329 else if (value == 62)
330 {
331 return (byte) 43;
332 }
333 else if (value == 63)
334 {
335 return (byte) 47;
336 }
337 throw new Error("Bad input: " + value);
338 }
339
340 }