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