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