Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/mobile
Path: blob/master/test/jdk/com/sun/crypto/provider/Cipher/ChaCha20/ChaCha20Poly1305ParamTest.java
41161 views
1
/*
2
* Copyright (c) 2018, 2021, Oracle and/or its affiliates. All rights reserved.
3
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4
*
5
* This code is free software; you can redistribute it and/or modify it
6
* under the terms of the GNU General Public License version 2 only, as
7
* published by the Free Software Foundation.
8
*
9
* This code is distributed in the hope that it will be useful, but WITHOUT
10
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12
* version 2 for more details (a copy is included in the LICENSE file that
13
* accompanied this code).
14
*
15
* You should have received a copy of the GNU General Public License version
16
* 2 along with this work; if not, write to the Free Software Foundation,
17
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18
*
19
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20
* or visit www.oracle.com if you need additional information or have any
21
* questions.
22
*/
23
24
/**
25
* @test
26
* @bug 8153029 8257769
27
* @library /test/lib
28
* @run main ChaCha20Poly1305ParamTest
29
* @summary ChaCha20 Cipher Implementation (parameters)
30
*/
31
32
import java.util.*;
33
import java.io.IOException;
34
import java.security.GeneralSecurityException;
35
import javax.crypto.Cipher;
36
import javax.crypto.SecretKey;
37
import javax.crypto.spec.ChaCha20ParameterSpec;
38
import javax.crypto.spec.SecretKeySpec;
39
import javax.crypto.AEADBadTagException;
40
import java.security.spec.AlgorithmParameterSpec;
41
import java.security.AlgorithmParameters;
42
import java.security.NoSuchAlgorithmException;
43
import java.nio.ByteBuffer;
44
import java.security.InvalidKeyException;
45
import java.security.MessageDigest;
46
import java.security.spec.InvalidParameterSpecException;
47
import javax.crypto.spec.IvParameterSpec;
48
import jdk.test.lib.Convert;
49
50
public class ChaCha20Poly1305ParamTest {
51
public static class TestData {
52
public TestData(String name, String keyStr, String nonceStr, int ctr,
53
int dir, String inputStr, String aadStr, String outStr) {
54
testName = Objects.requireNonNull(name);
55
HexFormat hex = HexFormat.of();
56
key = hex.parseHex(keyStr);
57
nonce = hex.parseHex(nonceStr);
58
if ((counter = ctr) < 0) {
59
throw new IllegalArgumentException(
60
"counter must be 0 or greater");
61
}
62
direction = dir;
63
if ((direction != Cipher.ENCRYPT_MODE) &&
64
(direction != Cipher.DECRYPT_MODE)) {
65
throw new IllegalArgumentException(
66
"Direction must be ENCRYPT_MODE or DECRYPT_MODE");
67
}
68
input = hex.parseHex(inputStr);
69
aad = (aadStr != null) ? hex.parseHex(aadStr) : null;
70
expOutput = hex.parseHex(outStr);
71
}
72
73
public final String testName;
74
public final byte[] key;
75
public final byte[] nonce;
76
public final int counter;
77
public final int direction;
78
public final byte[] input;
79
public final byte[] aad;
80
public final byte[] expOutput;
81
}
82
83
public static final List<TestData> aeadTestList =
84
new LinkedList<TestData>() {{
85
add(new TestData("RFC 7539 Sample AEAD Test Vector",
86
"808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f",
87
"070000004041424344454647",
88
1, Cipher.ENCRYPT_MODE,
89
"4c616469657320616e642047656e746c656d656e206f662074686520636c6173" +
90
"73206f66202739393a204966204920636f756c64206f6666657220796f75206f" +
91
"6e6c79206f6e652074697020666f7220746865206675747572652c2073756e73" +
92
"637265656e20776f756c642062652069742e",
93
"50515253c0c1c2c3c4c5c6c7",
94
"d31a8d34648e60db7b86afbc53ef7ec2a4aded51296e08fea9e2b5a736ee62d6" +
95
"3dbea45e8ca9671282fafb69da92728b1a71de0a9e060b2905d6a5b67ecd3b36" +
96
"92ddbd7f2d778b8c9803aee328091b58fab324e4fad675945585808b4831d7bc" +
97
"3ff4def08e4b7a9de576d26586cec64b61161ae10b594f09e26a7e902ecbd060" +
98
"0691"));
99
add(new TestData("RFC 7539 A.5 Sample Decryption",
100
"1c9240a5eb55d38af333888604f6b5f0473917c1402b80099dca5cbc207075c0",
101
"000000000102030405060708",
102
1, Cipher.DECRYPT_MODE,
103
"64a0861575861af460f062c79be643bd5e805cfd345cf389f108670ac76c8cb2" +
104
"4c6cfc18755d43eea09ee94e382d26b0bdb7b73c321b0100d4f03b7f355894cf" +
105
"332f830e710b97ce98c8a84abd0b948114ad176e008d33bd60f982b1ff37c855" +
106
"9797a06ef4f0ef61c186324e2b3506383606907b6a7c02b0f9f6157b53c867e4" +
107
"b9166c767b804d46a59b5216cde7a4e99040c5a40433225ee282a1b0a06c523e" +
108
"af4534d7f83fa1155b0047718cbc546a0d072b04b3564eea1b422273f548271a" +
109
"0bb2316053fa76991955ebd63159434ecebb4e466dae5a1073a6727627097a10" +
110
"49e617d91d361094fa68f0ff77987130305beaba2eda04df997b714d6c6f2c29" +
111
"a6ad5cb4022b02709beead9d67890cbb22392336fea1851f38",
112
"f33388860000000000004e91",
113
"496e7465726e65742d4472616674732061726520647261667420646f63756d65" +
114
"6e74732076616c696420666f722061206d6178696d756d206f6620736978206d" +
115
"6f6e74687320616e64206d617920626520757064617465642c207265706c6163" +
116
"65642c206f72206f62736f6c65746564206279206f7468657220646f63756d65" +
117
"6e747320617420616e792074696d652e20497420697320696e617070726f7072" +
118
"6961746520746f2075736520496e7465726e65742d4472616674732061732072" +
119
"65666572656e6365206d6174657269616c206f7220746f206369746520746865" +
120
"6d206f74686572207468616e206173202fe2809c776f726b20696e2070726f67" +
121
"726573732e2fe2809d"));
122
}};
123
124
// 12-byte nonce DER-encoded as an OCTET_STRING
125
public static final byte[] NONCE_OCTET_STR_12 = {
126
4, 12, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8
127
};
128
129
// Invalid 16-byte nonce DER-encoded as an OCTET_STRING
130
public static final byte[] NONCE_OCTET_STR_16 = {
131
4, 16, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
132
};
133
134
// Throwaway key for default init tests
135
public static final SecretKey DEF_KEY = new SecretKeySpec(new byte[]
136
{
137
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
138
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31
139
}, "ChaCha20");
140
141
public static void main(String args[]) throws Exception {
142
int testsPassed = 0;
143
int testNumber = 0;
144
145
// Try some default initializations
146
testDefaultAlgParams("ChaCha20", Cipher.ENCRYPT_MODE, true);
147
testDefaultAlgParams("ChaCha20-Poly1305", Cipher.ENCRYPT_MODE, true);
148
testDefaultAlgParamSpec("ChaCha20", Cipher.ENCRYPT_MODE, true);
149
testDefaultAlgParamSpec("ChaCha20-Poly1305", Cipher.ENCRYPT_MODE, true);
150
testDefaultAlgParams("ChaCha20", Cipher.DECRYPT_MODE, false);
151
testDefaultAlgParams("ChaCha20-Poly1305", Cipher.DECRYPT_MODE, false);
152
testDefaultAlgParamSpec("ChaCha20", Cipher.DECRYPT_MODE, false);
153
testDefaultAlgParamSpec("ChaCha20-Poly1305", Cipher.DECRYPT_MODE,
154
false);
155
156
// Try (and hopefully fail) to create a ChaCha20 AlgorithmParameterSpec
157
System.out.println(
158
"*** Test: Try to make ChaCha20 AlgorithmParameterSpec");
159
try {
160
ChaCha20ParameterSpec badChaCha20Spec =
161
new ChaCha20ParameterSpec(NONCE_OCTET_STR_16, 1);
162
throw new RuntimeException("ChaCha20 AlgorithmParameterSpec " +
163
"with 16 byte nonce should fail");
164
} catch (IllegalArgumentException iae) {
165
System.out.println("Caught expected exception: " + iae);
166
}
167
168
// Try (and hopefully fail) to create a ChaCha20 AlgorithmParameters
169
System.out.println(
170
"*** Test: Try to make ChaCha20 AlgorithmParameters");
171
try {
172
AlgorithmParameters apsNoChaCha20 =
173
AlgorithmParameters.getInstance("ChaCha20");
174
throw new RuntimeException(
175
"ChaCha20 AlgorithmParameters should fail");
176
} catch (NoSuchAlgorithmException nsae) {
177
System.out.println("Caught expected exception: " + nsae);
178
}
179
180
// Create the AlgorithmParameters object from a valid encoding
181
System.out.println("*** Test: Create and init ChaCha20-Poly1305 APS");
182
AlgorithmParameters apsGood =
183
AlgorithmParameters.getInstance("ChaCha20-Poly1305");
184
apsGood.init(NONCE_OCTET_STR_12);
185
System.out.println("Test Passed");
186
187
// Pull an AlgorithmParameters object out of the initialized cipher
188
// and compare its value against the original.
189
System.out.println("*** Test: Init ChaCha20-Poly1305 Cipher using " +
190
"AP, retrieve AP and compare");
191
Cipher cc20p1305 = Cipher.getInstance("ChaCha20-Poly1305");
192
cc20p1305.init(Cipher.ENCRYPT_MODE, DEF_KEY, apsGood);
193
AlgorithmParameters pulledParams = cc20p1305.getParameters();
194
byte[] apsGoodData = apsGood.getEncoded();
195
byte[] pulledParamsData = pulledParams.getEncoded();
196
if (!Arrays.equals(apsGoodData, pulledParamsData)) {
197
throw new RuntimeException(
198
"Retrieved parameters do not match those used to init cipher");
199
}
200
System.out.println("Test Passed");
201
202
// Try the same test with ChaCha20. It should always be null.
203
System.out.println("*** Test: Init ChaCha20 Cipher using " +
204
"AP, retrieve AP and compare");
205
Cipher cc20 = Cipher.getInstance("ChaCha20");
206
cc20.init(Cipher.ENCRYPT_MODE, DEF_KEY);
207
pulledParams = cc20.getParameters();
208
if (pulledParams != null) {
209
throw new RuntimeException("Unexpected non-null " +
210
"AlgorithmParameters from ChaCha20 cipiher");
211
}
212
System.out.println("Test Passed");
213
214
// Create and try to init using invalid encoding
215
AlgorithmParameters apsBad =
216
AlgorithmParameters.getInstance("ChaCha20-Poly1305");
217
System.out.println("*** Test: Use invalid encoding scheme");
218
try {
219
apsBad.init(NONCE_OCTET_STR_12, "OraclePrivate");
220
throw new RuntimeException("Allowed unsupported encoding scheme: " +
221
apsBad.getAlgorithm());
222
} catch (IOException iae) {
223
System.out.println("Caught expected exception: " + iae);
224
}
225
226
// Try to init using supported scheme but invalid length
227
System.out.println("*** Test: Use supported scheme, nonce too large");
228
try {
229
apsBad.init(NONCE_OCTET_STR_16, "ASN.1");
230
throw new RuntimeException("Allowed invalid encoded length");
231
} catch (IOException ioe) {
232
System.out.println("Caught expected exception: " + ioe);
233
}
234
235
// The next set of tests cover cases where ChaCha20-Poly1305 cipher
236
// objects have the getParameters() call executed after instantiation
237
// but before initialization.
238
System.out.println("*** Test: getParameters before init");
239
cc20p1305 = Cipher.getInstance("ChaCha20-Poly1305");
240
AlgorithmParameters algParams = cc20p1305.getParameters();
241
byte[] preInitNonce = getNonceFromParams(algParams);
242
// A second pre-init getParameters() call should return a new set of
243
// random parameters.
244
AlgorithmParameters algParamsTwo = cc20p1305.getParameters();
245
byte[] secondNonce = getNonceFromParams(algParamsTwo);
246
if (MessageDigest.isEqual(preInitNonce, secondNonce)) {
247
throw new RuntimeException("Unexpected nonce match between " +
248
"two pre-init getParameters() calls");
249
}
250
251
// Next we will initialize the Cipher object using a form of init
252
// that doesn't take AlgorithmParameters or AlgorithmParameterSpec.
253
// The nonce created using the pre-init getParameters() call should
254
// be overwritten by a freshly generated set of random parameters.
255
cc20p1305.init(Cipher.ENCRYPT_MODE, DEF_KEY);
256
AlgorithmParameters postInitAps = cc20p1305.getParameters();
257
byte[] postInitNonce = getNonceFromParams(postInitAps);
258
if (MessageDigest.isEqual(preInitNonce, postInitNonce)) {
259
throw new RuntimeException("Unexpected nonce match between " +
260
"pre and post-init getParameters() calls");
261
}
262
System.out.println("Test Passed");
263
264
// After an initialization, subsequent calls to getParameters() should
265
// return the same parameter value until the next initialization takes
266
// place.
267
System.out.println("*** Test: getParameters after init");
268
AlgorithmParameters postInitApsTwo = cc20p1305.getParameters();
269
byte[] postInitNonceTwo = getNonceFromParams(postInitApsTwo);
270
if (!MessageDigest.isEqual(postInitNonce, postInitNonceTwo)) {
271
throw new RuntimeException("Unexpected nonce mismatch between " +
272
"two post-init getParameters() calls");
273
}
274
System.out.println("Test Passed");
275
276
// Test reinitialization use cases.
277
// First test: instantiate, init(no param), encrypt. Get params
278
// and attempt to reinit with same parameters. Should fail.
279
System.out.println("*** Test: Init w/ random nonce, init 2nd time");
280
cc20p1305 = Cipher.getInstance("ChaCha20-Poly1305");
281
cc20p1305.init(Cipher.ENCRYPT_MODE, DEF_KEY);
282
algParams = cc20p1305.getParameters();
283
preInitNonce = getNonceFromParams(algParams);
284
// Perform a simple encryption operation
285
cc20p1305.doFinal(aeadTestList.get(0).input);
286
try {
287
// Now try to reinitialize using the same parameters
288
cc20p1305.init(Cipher.ENCRYPT_MODE, DEF_KEY, algParams);
289
throw new RuntimeException("Illegal key/nonce reuse");
290
} catch (InvalidKeyException ike) {
291
System.out.println("Caught expected exception: " + ike);
292
}
293
294
// Test the reinit guard using an AlgorithmParameterSpec with the
295
// Same nonce value. This should also be a failure.
296
try {
297
cc20p1305.init(Cipher.ENCRYPT_MODE, DEF_KEY,
298
new IvParameterSpec(preInitNonce));
299
throw new RuntimeException("Illegal key/nonce reuse");
300
} catch (InvalidKeyException ike) {
301
System.out.println("Caught expected exception: " + ike);
302
}
303
304
// Try one more time, this time providing a new 12-byte nonce, which
305
// should be allowed even if the key is the same.
306
cc20p1305.init(Cipher.ENCRYPT_MODE, DEF_KEY,
307
new IvParameterSpec(NONCE_OCTET_STR_12, 2, 12));
308
System.out.println("Test Passed");
309
310
// Reinit test: instantiate, init(no param), getParam, encrypt,
311
// then init(no param). Should work and the parameters should be
312
// different after each init.
313
cc20p1305 = Cipher.getInstance("ChaCha20-Poly1305");
314
cc20p1305.init(Cipher.ENCRYPT_MODE, DEF_KEY);
315
byte[] paramInitOne = getNonceFromParams(cc20p1305.getParameters());
316
// Perform a simple encryption operation
317
cc20p1305.doFinal(aeadTestList.get(0).input);
318
// reinit (no params)
319
cc20p1305.init(Cipher.ENCRYPT_MODE, DEF_KEY);
320
byte[] paramInitTwo = getNonceFromParams(cc20p1305.getParameters());
321
if (MessageDigest.isEqual(paramInitOne, paramInitTwo)) {
322
throw new RuntimeException("Unexpected nonce match between " +
323
"pre and post-init getParameters() calls");
324
}
325
System.out.println("Test Passed");
326
327
// Reinit test: instantiate, init(no param), doFinal, then doFinal
328
// again without intervening init. Should fail due to no-reuse
329
// protections.
330
try {
331
cc20p1305 = Cipher.getInstance("ChaCha20-Poly1305");
332
cc20p1305.init(Cipher.ENCRYPT_MODE, DEF_KEY);
333
cc20p1305.doFinal(aeadTestList.get(0).input);
334
cc20p1305.doFinal(aeadTestList.get(0).input);
335
throw new RuntimeException("Illegal key/nonce reuse");
336
} catch (IllegalStateException ise) {
337
System.out.println("Caught expected exception: " + ise);
338
}
339
340
System.out.println("----- AEAD Tests -----");
341
for (TestData test : aeadTestList) {
342
System.out.println("*** Test " + ++testNumber + ": " +
343
test.testName);
344
if (runAEADTest(test)) {
345
testsPassed++;
346
}
347
}
348
System.out.println();
349
350
System.out.println("Total tests: " + testNumber +
351
", Passed: " + testsPassed + ", Failed: " +
352
(testNumber - testsPassed));
353
if (testsPassed != testNumber) {
354
throw new RuntimeException("One or more tests failed. " +
355
"Check output for details");
356
}
357
}
358
359
/**
360
* Attempt default inits with null AlgorithmParameters
361
*
362
* @param alg the algorithm (ChaCha20, ChaCha20-Poly1305)
363
* @param mode the Cipher mode (ENCRYPT_MODE, etc.)
364
*/
365
private static void testDefaultAlgParams(String alg, int mode,
366
boolean shouldPass) {
367
byte[] ivOne = null, ivTwo = null;
368
System.out.println("Test default AlgorithmParameters: Cipher = " +
369
alg + ", mode = " + mode);
370
try {
371
AlgorithmParameters params = null;
372
Cipher cipher = Cipher.getInstance(alg);
373
cipher.init(mode, DEF_KEY, params, null);
374
ivOne = cipher.getIV();
375
cipher.init(mode, DEF_KEY, params, null);
376
ivTwo = cipher.getIV();
377
if (!shouldPass) {
378
throw new RuntimeException(
379
"Did not receive expected exception");
380
}
381
} catch (GeneralSecurityException gse) {
382
if (shouldPass) {
383
throw new RuntimeException(gse);
384
}
385
System.out.println("Caught expected exception: " + gse);
386
return;
387
}
388
if (Arrays.equals(ivOne, ivTwo)) {
389
throw new RuntimeException(
390
"FAIL! Two inits generated same nonces");
391
} else {
392
System.out.println("IV 1:\n" + dumpHexBytes(ivOne, 16, "\n", " "));
393
System.out.println("IV 1:\n" + dumpHexBytes(ivTwo, 16, "\n", " "));
394
}
395
}
396
397
/**
398
* Attempt default inits with null AlgorithmParameters
399
*
400
* @param alg the algorithm (ChaCha20, ChaCha20-Poly1305)
401
* @param mode the Cipher mode (ENCRYPT_MODE, etc.)
402
*/
403
private static void testDefaultAlgParamSpec(String alg, int mode,
404
boolean shouldPass) {
405
byte[] ivOne = null, ivTwo = null;
406
System.out.println("Test default AlgorithmParameterSpec: Cipher = " +
407
alg + ", mode = " + mode);
408
try {
409
AlgorithmParameterSpec params = null;
410
Cipher cipher = Cipher.getInstance(alg);
411
cipher.init(mode, DEF_KEY, params, null);
412
ivOne = cipher.getIV();
413
cipher.init(mode, DEF_KEY, params, null);
414
ivTwo = cipher.getIV();
415
if (!shouldPass) {
416
throw new RuntimeException(
417
"Did not receive expected exception");
418
}
419
} catch (GeneralSecurityException gse) {
420
if (shouldPass) {
421
throw new RuntimeException(gse);
422
}
423
System.out.println("Caught expected exception: " + gse);
424
return;
425
}
426
if (Arrays.equals(ivOne, ivTwo)) {
427
throw new RuntimeException(
428
"FAIL! Two inits generated same nonces");
429
} else {
430
System.out.println("IV 1:\n" + dumpHexBytes(ivOne, 16, "\n", " "));
431
System.out.println("IV 2:\n" + dumpHexBytes(ivTwo, 16, "\n", " "));
432
}
433
}
434
435
private static boolean runAEADTest(TestData testData)
436
throws GeneralSecurityException, IOException {
437
boolean result = false;
438
439
Cipher mambo = Cipher.getInstance("ChaCha20-Poly1305");
440
SecretKeySpec mamboKey = new SecretKeySpec(testData.key, "ChaCha20");
441
AlgorithmParameters mamboParams =
442
AlgorithmParameters.getInstance("ChaCha20-Poly1305");
443
444
// Put the nonce into ASN.1 ChaCha20-Poly1305 parameter format
445
byte[] derNonce = new byte[testData.nonce.length + 2];
446
derNonce[0] = 0x04;
447
derNonce[1] = (byte)testData.nonce.length;
448
System.arraycopy(testData.nonce, 0, derNonce, 2,
449
testData.nonce.length);
450
mamboParams.init(derNonce);
451
452
mambo.init(testData.direction, mamboKey, mamboParams);
453
454
byte[] out = new byte[mambo.getOutputSize(testData.input.length)];
455
int outOff = 0;
456
try {
457
mambo.updateAAD(testData.aad);
458
outOff += mambo.update(testData.input, 0, testData.input.length,
459
out, outOff);
460
outOff += mambo.doFinal(out, outOff);
461
} catch (AEADBadTagException abte) {
462
// If we get a bad tag or derive a tag mismatch, log it
463
// and register it as a failure
464
System.out.println("FAIL: " + abte);
465
return false;
466
}
467
468
if (!Arrays.equals(out, testData.expOutput)) {
469
System.out.println("ERROR - Output Mismatch!");
470
System.out.println("Expected:\n" +
471
dumpHexBytes(testData.expOutput, 16, "\n", " "));
472
System.out.println("Actual:\n" +
473
dumpHexBytes(out, 16, "\n", " "));
474
System.out.println();
475
} else {
476
result = true;
477
}
478
479
return result;
480
}
481
482
private static byte[] getNonceFromParams(AlgorithmParameters params)
483
throws InvalidParameterSpecException {
484
return params.getParameterSpec(IvParameterSpec.class).getIV();
485
}
486
487
/**
488
* Dump the hex bytes of a buffer into string form.
489
*
490
* @param data The array of bytes to dump to stdout.
491
* @param itemsPerLine The number of bytes to display per line
492
* if the {@code lineDelim} character is blank then all bytes
493
* will be printed on a single line.
494
* @param lineDelim The delimiter between lines
495
* @param itemDelim The delimiter between bytes
496
*
497
* @return The hexdump of the byte array
498
*/
499
private static String dumpHexBytes(byte[] data, int itemsPerLine,
500
String lineDelim, String itemDelim) {
501
return dumpHexBytes(ByteBuffer.wrap(data), itemsPerLine, lineDelim,
502
itemDelim);
503
}
504
505
private static String dumpHexBytes(ByteBuffer data, int itemsPerLine,
506
String lineDelim, String itemDelim) {
507
StringBuilder sb = new StringBuilder();
508
if (data != null) {
509
data.mark();
510
int i = 0;
511
while (data.remaining() > 0) {
512
if (i % itemsPerLine == 0 && i != 0) {
513
sb.append(lineDelim);
514
}
515
sb.append(String.format("%02X", data.get())).append(itemDelim);
516
i++;
517
}
518
data.reset();
519
}
520
521
return sb.toString();
522
}
523
}
524
525
526