|
19 | 19 | import java.util.Collections; |
20 | 20 | import java.util.Date; |
21 | 21 | import java.util.HashSet; |
| 22 | +import java.util.LinkedList; |
22 | 23 | import java.util.List; |
| 24 | +import java.util.Random; |
23 | 25 |
|
24 | 26 | import junit.framework.TestCase; |
25 | 27 | import org.bouncycastle.asn1.ASN1EncodableVector; |
|
68 | 70 | import org.bouncycastle.tsp.ers.ERSException; |
69 | 71 | import org.bouncycastle.tsp.ers.ERSFileData; |
70 | 72 | import org.bouncycastle.tsp.ers.ERSInputStreamData; |
| 73 | +import org.bouncycastle.tsp.ers.SortedHashList; |
71 | 74 | import org.bouncycastle.util.Arrays; |
72 | 75 | import org.bouncycastle.util.Store; |
73 | 76 | import org.bouncycastle.util.Strings; |
@@ -1355,6 +1358,150 @@ private int compare(byte[] a, byte[] b) |
1355 | 1358 | return new BigInteger(1, a).compareTo(new BigInteger(1, b)); |
1356 | 1359 | } |
1357 | 1360 |
|
| 1361 | + /** |
| 1362 | + * SortedHashList used to find each hash's insertion point by walking a LinkedList with |
| 1363 | + * get(index). The order it produced is the one recorded in every existing evidence record, |
| 1364 | + * so it is reproduced here from the original algorithm and compared against the list's |
| 1365 | + * output, over pseudo-random input including duplicates and arrays of differing lengths. |
| 1366 | + */ |
| 1367 | + public void testSortedHashListOrder() |
| 1368 | + { |
| 1369 | + Random random = new Random(0x5eed); |
| 1370 | + List<byte[]> input = new ArrayList<byte[]>(); |
| 1371 | + |
| 1372 | + for (int i = 0; i != 1000; i++) |
| 1373 | + { |
| 1374 | + byte[] value = new byte[random.nextInt(33)]; |
| 1375 | + random.nextBytes(value); |
| 1376 | + input.add(value); |
| 1377 | + } |
| 1378 | + // duplicates, and values sharing a prefix with a longer one |
| 1379 | + for (int i = 0; i != 100; i++) |
| 1380 | + { |
| 1381 | + input.add((byte[])input.get(i)); |
| 1382 | + input.add(Arrays.copyOfRange((byte[])input.get(i + 100), 0, ((byte[])input.get(i + 100)).length / 2)); |
| 1383 | + } |
| 1384 | + |
| 1385 | + SortedHashList list = new SortedHashList(); |
| 1386 | + for (int i = 0; i != input.size(); i++) |
| 1387 | + { |
| 1388 | + list.add((byte[])input.get(i)); |
| 1389 | + } |
| 1390 | + |
| 1391 | + List<byte[]> expected = insertionSorted(input); |
| 1392 | + List<byte[]> actual = list.toList(); |
| 1393 | + |
| 1394 | + assertEquals(input.size(), list.size()); |
| 1395 | + assertEquals(expected.size(), actual.size()); |
| 1396 | + for (int i = 0; i != expected.size(); i++) |
| 1397 | + { |
| 1398 | + assertTrue("differs at " + i, Arrays.areEqual((byte[])expected.get(i), (byte[])actual.get(i))); |
| 1399 | + } |
| 1400 | + assertTrue(Arrays.areEqual((byte[])expected.get(0), list.getFirst())); |
| 1401 | + } |
| 1402 | + |
| 1403 | + /** |
| 1404 | + * The order SortedHashList produced before sorting was deferred to the accessors. |
| 1405 | + */ |
| 1406 | + private List<byte[]> insertionSorted(List<byte[]> hashes) |
| 1407 | + { |
| 1408 | + LinkedList<byte[]> baseList = new LinkedList<byte[]>(); |
| 1409 | + |
| 1410 | + for (int h = 0; h != hashes.size(); h++) |
| 1411 | + { |
| 1412 | + byte[] hash = (byte[])hashes.get(h); |
| 1413 | + |
| 1414 | + if (baseList.size() == 0) |
| 1415 | + { |
| 1416 | + baseList.addFirst(hash); |
| 1417 | + } |
| 1418 | + else if (compareUnsigned(hash, (byte[])baseList.get(0)) < 0) |
| 1419 | + { |
| 1420 | + baseList.addFirst(hash); |
| 1421 | + } |
| 1422 | + else |
| 1423 | + { |
| 1424 | + int index = 1; |
| 1425 | + while (index < baseList.size() && compareUnsigned((byte[])baseList.get(index), hash) <= 0) |
| 1426 | + { |
| 1427 | + index++; |
| 1428 | + } |
| 1429 | + |
| 1430 | + if (index == baseList.size()) |
| 1431 | + { |
| 1432 | + baseList.add(hash); |
| 1433 | + } |
| 1434 | + else |
| 1435 | + { |
| 1436 | + baseList.add(index, hash); |
| 1437 | + } |
| 1438 | + } |
| 1439 | + } |
| 1440 | + |
| 1441 | + return baseList; |
| 1442 | + } |
| 1443 | + |
| 1444 | + private int compareUnsigned(byte[] left, byte[] right) |
| 1445 | + { |
| 1446 | + for (int i = 0; i < left.length && i < right.length; i++) |
| 1447 | + { |
| 1448 | + int a = (left[i] & 0xff); |
| 1449 | + int b = (right[i] & 0xff); |
| 1450 | + |
| 1451 | + if (a != b) |
| 1452 | + { |
| 1453 | + return a - b; |
| 1454 | + } |
| 1455 | + } |
| 1456 | + return left.length - right.length; |
| 1457 | + } |
| 1458 | + |
| 1459 | + /** |
| 1460 | + * A reduced hash tree over a large number of data objects. This reaches both sorted lists - |
| 1461 | + * SortedIndexedHashList from ERSArchiveTimeStampGenerator.getPartialHashtrees(), and |
| 1462 | + * SortedHashList from BinaryTreeRootCalculator.computeRootHash() - and took about 2.5 |
| 1463 | + * seconds for these 2,000 objects when the insertion point was found by walking a |
| 1464 | + * LinkedList, rising by roughly a factor of eight per doubling (10,000 objects took 347 |
| 1465 | + * seconds). The root is also checked to be independent of the order the objects were added |
| 1466 | + * in, which is what the sorting is there for. |
| 1467 | + */ |
| 1468 | + public void testLargeDataObjectSet() |
| 1469 | + throws Exception |
| 1470 | + { |
| 1471 | + DigestCalculatorProvider digestCalculatorProvider = new JcaDigestCalculatorProviderBuilder().build(); |
| 1472 | + |
| 1473 | + List<ERSData> dataObjects = new ArrayList<ERSData>(); |
| 1474 | + for (int i = 0; i != 2000; i++) |
| 1475 | + { |
| 1476 | + dataObjects.add(new ERSByteData(Strings.toByteArray("document " + i))); |
| 1477 | + } |
| 1478 | + |
| 1479 | + byte[] ascending = rootOf(dataObjects, digestCalculatorProvider); |
| 1480 | + |
| 1481 | + List<ERSData> reversed = new ArrayList<ERSData>(dataObjects); |
| 1482 | + Collections.reverse(reversed); |
| 1483 | + |
| 1484 | + assertTrue(Arrays.areEqual(ascending, rootOf(reversed, digestCalculatorProvider))); |
| 1485 | + } |
| 1486 | + |
| 1487 | + private byte[] rootOf(List<ERSData> dataObjects, DigestCalculatorProvider digestCalculatorProvider) |
| 1488 | + throws Exception |
| 1489 | + { |
| 1490 | + ERSArchiveTimeStampGenerator ersGen = new ERSArchiveTimeStampGenerator( |
| 1491 | + digestCalculatorProvider.get(new AlgorithmIdentifier(NISTObjectIdentifiers.id_sha256))); |
| 1492 | + |
| 1493 | + for (int i = 0; i != dataObjects.size(); i++) |
| 1494 | + { |
| 1495 | + ersGen.addData((ERSData)dataObjects.get(i)); |
| 1496 | + } |
| 1497 | + |
| 1498 | + TimeStampRequestGenerator tspReqGen = new TimeStampRequestGenerator(); |
| 1499 | + |
| 1500 | + tspReqGen.setCertReq(true); |
| 1501 | + |
| 1502 | + return ersGen.generateTimeStampRequest(tspReqGen).getMessageImprintDigest(); |
| 1503 | + } |
| 1504 | + |
1358 | 1505 | public void testReducedHashTrees() |
1359 | 1506 | throws Exception |
1360 | 1507 | { |
|
0 commit comments