1 package org.djutils.decoderdumper;
2
3 import static org.junit.jupiter.api.Assertions.assertEquals;
4 import static org.junit.jupiter.api.Assertions.assertTrue;
5 import static org.junit.jupiter.api.Assertions.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 import java.util.Locale;
14
15 import org.junit.jupiter.api.Test;
16
17
18
19
20
21
22
23
24
25
26 public class DecoderDumperTests
27 {
28
29
30
31
32
33
34 @SuppressWarnings("checkstyle:methodlength")
35 @Test
36 public final void testHexDumper() throws InterruptedException, IOException
37 {
38 Locale.setDefault(Locale.US);
39 assertEquals("", HexDumper.hexDumper(new byte[] {}), "Empty input yields empty output");
40 byte[] input = new byte[] {1, 2};
41 String output = HexDumper.hexDumper(input);
42 assertTrue(output.startsWith("00000000: "), "Output starts with address \"00000000: \"");
43 for (int length = 1; length < 100; length++)
44 {
45 input = new byte[length];
46 assertTrue(HexDumper.hexDumper(input).endsWith("\n"), "Output ends on newline");
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(String.format(" %02x ", value)),
55 "Output contains hex value of the only input byte embedded between spaces");
56 }
57 assertEquals(1, HexDumper.hexDumper(new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}).split("\n").length,
58 "output of 16 byte input fills one lines");
59 assertEquals(2, 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 "output of 17 byte input fills two lines");
61 assertTrue(HexDumper.hexDumper(0x12345, new byte[] {0, 1}).startsWith("00012340"),
62 "address offset is printed at start of output");
63 Dumper<HexDumper> hd = new HexDumper(0x12345);
64 assertTrue(hd.toString().startsWith("HexDumper"), "toString makes some sense");
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(Math.max(1, (i + 1) / 16), baos.toString().split("\n").length, "Number of lines check");
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(letter, lastLetter, "Output ends with the provided printable character");
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(startTimeStamp <= recorded, "Time stamp should be within interval");
96 assertTrue(endTimeStamp >= recorded, "Time stamp should be within interval");
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(baos.toString().startsWith("00000000: 00 "), "ByteArrayOutputStream contains start of hex dump");
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 System.err.println(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(16, result.split("\n").length, "Result should be 16 lines");
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(result.startsWith("00000000: 05 06 "), "start and length parameter select the correct bytes");
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(result.startsWith("00000000: 00 01 02 03 04 05 06 07 08 09 "), "Ten bytes should have been accumulated");
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(3, baos.toString().split("\n").length, "Suppression reduced the output to three lines");
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(4, baos.toString().split("\n").length, "Suppression reduced the output to four lines");
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(4, baos.toString().split("\n").length, "Suppression reduced the output to four lines");
209
210
211 assertTrue(new TimeStamper().toString().startsWith("TimeStamper ["), "TimeStamper had decent toString method");
212 }
213
214
215
216
217 @Test
218 public void testBase64Dumper()
219 {
220 assertEquals("", Base64Dumper.base64Dumper(new byte[] {}), "Empty input yields empty output");
221 byte[] input = new byte[] {1, 2};
222 String output = HexDumper.hexDumper(input);
223 assertTrue(output.startsWith("00000000: "), "Output starts with address \"00000000: \"");
224 for (int length = 1; length < 100; length++)
225 {
226 input = new byte[length];
227 assertTrue(HexDumper.hexDumper(input).endsWith("\n"), "Output ends on newline (even though the input is invalid)");
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(expectedByte, theByte, "Reconstructed byte matches corresponding byte in pattern");
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(pattern, theByte, "Reconstructed byte matches corresponding byte in patten");
260 assertTrue(output.substring(2).startsWith(" "), "Rest of result starts with at least 10 spaces");
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(expectedByte, theByte, "Reconstructed byte matches corresponding byte in pattern");
278 }
279 assertTrue(output.substring(5).startsWith(" "), "Rest of result starts with at least 10 spaces");
280 }
281 assertTrue(new Base64Dumper().toString().startsWith("Base64Dumper"), "toString makes some sense");
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(expectedResult, result, "Extra space in input does not change output");
294 }
295 }
296
297 String result = Base64Dumper.base64Dumper(("!" + base64).getBytes()).substring(30);
298
299 assertEquals(expectedResult, result, "bad char in input is (currently) ignored");
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 }