Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions core/src/main/java/org/bouncycastle/util/Pack.java
Original file line number Diff line number Diff line change
Expand Up @@ -192,8 +192,15 @@ public static byte[] longToBigEndian(long n)

public static void longToBigEndian(long n, byte[] bs, int off)
{
intToBigEndian((int)(n >>> 32), bs, off);
intToBigEndian((int)(n & 0xffffffffL), bs, off + 4);
// #2474: on JDK 23+, this code is ~9x faster than the previous implementation (cf. JDK-8318446)
bs[off] = (byte)(n >>> 56);
bs[++off] = (byte)(n >>> 48);
bs[++off] = (byte)(n >>> 40);
bs[++off] = (byte)(n >>> 32);
bs[++off] = (byte)(n >>> 24);
bs[++off] = (byte)(n >>> 16);
bs[++off] = (byte)(n >>> 8);
bs[++off] = (byte)(n);
}

public static byte[] longToBigEndian(long[] ns)
Expand Down Expand Up @@ -547,8 +554,15 @@ public static byte[] longToLittleEndian(long n)

public static void longToLittleEndian(long n, byte[] bs, int off)
{
intToLittleEndian((int)(n & 0xffffffffL), bs, off);
intToLittleEndian((int)(n >>> 32), bs, off + 4);
// #2474: on JDK 23+, this code is ~9x faster than the previous implementation (cf. JDK-8318446)
bs[off] = (byte)(n);
bs[++off] = (byte)(n >>> 8);
bs[++off] = (byte)(n >>> 16);
bs[++off] = (byte)(n >>> 24);
bs[++off] = (byte)(n >>> 32);
bs[++off] = (byte)(n >>> 40);
bs[++off] = (byte)(n >>> 48);
bs[++off] = (byte)(n >>> 56);
}

public static byte[] longToLittleEndian(long[] ns)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public static Test suite()
suite.addTestSuite(StringsTest.class);
suite.addTestSuite(StreamsTest.class);
suite.addTestSuite(AggregateRuntimeExceptionTest.class);
suite.addTestSuite(PackTest.class);
return new BCTestSetup(suite);
}

Expand Down
175 changes: 175 additions & 0 deletions core/src/test/java/org/bouncycastle/util/utiltest/PackTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
package org.bouncycastle.util.utiltest;

import java.util.Random;

import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.Pack;

import junit.framework.TestCase;

public class PackTest
extends TestCase
{
private static final byte FILL = (byte)0xA5;

private static final long[] EXTREMES = { 0L, -1L, Long.MIN_VALUE, Long.MAX_VALUE, 0x8080808080808080L,
0x0102030405060708L };

/*
* Enough calls for C2 to compile the methods under test, so that the merged stores it generates for them are
* checked as well as the interpreter.
*/
private static final int ITERATIONS = 200000;

public void testLongToBigEndian()
{
Random random = new Random(1);
byte[] bs = new byte[24];
for (int i = 0; i < ITERATIONS; ++i)
{
long n = nextValue(random, i);
int off = i % 9;

Arrays.fill(bs, FILL);
Pack.longToBigEndian(n, bs, off);
checkBytes(n, true, bs, off);
if (Pack.bigEndianToLong(bs, off) != n)
{
fail("round trip of " + Long.toHexString(n));
}
}

assertTrue(Arrays.areEqual(new byte[]{ 1, 2, 3, 4, 5, 6, 7, 8 }, Pack.longToBigEndian(0x0102030405060708L)));
}

public void testLongToLittleEndian()
{
Random random = new Random(2);
byte[] bs = new byte[24];
for (int i = 0; i < ITERATIONS; ++i)
{
long n = nextValue(random, i);
int off = i % 9;

Arrays.fill(bs, FILL);
Pack.longToLittleEndian(n, bs, off);
checkBytes(n, false, bs, off);
if (Pack.littleEndianToLong(bs, off) != n)
{
fail("round trip of " + Long.toHexString(n));
}
}

assertTrue(Arrays.areEqual(new byte[]{ 8, 7, 6, 5, 4, 3, 2, 1 }, Pack.longToLittleEndian(0x0102030405060708L)));
}

public void testLongArrays()
{
Random random = new Random(3);
for (int i = 0; i < 2000; ++i)
{
long[] ns = new long[1 + random.nextInt(25)];
for (int j = 0; j < ns.length; ++j)
{
ns[j] = nextValue(random, i + j);
}
int nsOff = random.nextInt(ns.length);
int nsLen = random.nextInt(ns.length - nsOff + 1);
int bsOff = random.nextInt(9);
int bsLen = bsOff + 8 * nsLen + random.nextInt(9);

byte[] be = new byte[bsLen];
Arrays.fill(be, FILL);
Pack.longToBigEndian(ns, nsOff, nsLen, be, bsOff);
byte[] le = new byte[bsLen];
Arrays.fill(le, FILL);
Pack.longToLittleEndian(ns, nsOff, nsLen, le, bsOff);
for (int j = 0; j < nsLen; ++j)
{
assertEquals(ns[nsOff + j], Pack.bigEndianToLong(be, bsOff + 8 * j));
assertEquals(ns[nsOff + j], Pack.littleEndianToLong(le, bsOff + 8 * j));
}
checkUntouched(be, 0, bsOff);
checkUntouched(be, bsOff + 8 * nsLen, bsLen);
checkUntouched(le, 0, bsOff);
checkUntouched(le, bsOff + 8 * nsLen, bsLen);

long[] part = Arrays.copyOfRange(ns, nsOff, nsOff + nsLen);
assertTrue(Arrays.areEqual(Arrays.copyOfRange(be, bsOff, bsOff + 8 * nsLen), Pack.longToBigEndian(part)));
assertTrue(Arrays.areEqual(Arrays.copyOfRange(le, bsOff, bsOff + 8 * nsLen), Pack.longToLittleEndian(part)));
}
}

public void testOutOfBounds()
{
for (int len = 0; len < 8; ++len)
{
try
{
Pack.longToBigEndian(-1L, new byte[len], 0);
fail("no exception for " + len + " bytes");
}
catch (ArrayIndexOutOfBoundsException e)
{
// expected
}
try
{
Pack.longToLittleEndian(-1L, new byte[len], 0);
fail("no exception for " + len + " bytes");
}
catch (ArrayIndexOutOfBoundsException e)
{
// expected
}
}
}

/**
* Check the 8 bytes at off against the definition of the encoding, one byte at a time, and that the bytes around
* them are untouched.
*/
private static void checkBytes(long n, boolean bigEndian, byte[] bs, int off)
{
for (int i = 0; i < 8; ++i)
{
int shift = bigEndian ? 56 - 8 * i : 8 * i;
if (bs[off + i] != (byte)(n >>> shift))
{
fail("byte " + i + " of " + Long.toHexString(n) + (bigEndian ? " big" : " little") + " endian");
}
}
checkUntouched(bs, 0, off);
checkUntouched(bs, off + 8, bs.length);
}

private static void checkUntouched(byte[] bs, int from, int to)
{
for (int i = from; i < to; ++i)
{
if (bs[i] != FILL)
{
fail("byte " + i + " overwritten");
}
}
}

/**
* Random values, values with a single byte set or clear at each position in turn, and extremes.
*/
private static long nextValue(Random random, int i)
{
long oneByte = (long)(1 + random.nextInt(255)) << (8 * ((i >> 2) & 7));
switch (i & 3)
{
case 0:
return random.nextLong();
case 1:
return oneByte;
case 2:
return ~oneByte;
default:
return EXTREMES[(i >> 2) % EXTREMES.length];
}
}
}
2 changes: 2 additions & 0 deletions docs/releasenotes.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ Date: 2026, TBD

- The BCJSSE provider adds an org.bouncycastle.jsse.BCSSLContext interface exposing extended functionality of its SSLContext, obtained with org.bouncycastle.jsse.util.ContextUtil.getBCSSLContext() by way of the new BCSSLSessionContext interface the context's session contexts implement. Its getDefaultParameters(boolean) and getSupportedParameters(boolean) return the context's default and supported parameters as a BCSSLParameters for either client or server mode, including the BC-specific properties, where SSLContext.getDefaultSSLParameters() and getSupportedSSLParameters() report client mode only and cannot carry those properties. A BCSSLContext describes the initialization of the SSLContext it was obtained from, and is not updated if the SSLContext is re-initialized.

- org.bouncycastle.util.Pack.longToBigEndian() and longToLittleEndian() rewritten so it can be optimized by JDK 23+'s JIT. JDK 25 measurements show packing longs into bytes is ~9x as fast, which makes squeezing SHAKE output about 8% faster per block, a 64-byte SHA-512 or BLAKE2b digest 6-7% faster and ML-DSA key generation and verification 3-5% faster; JDK 17 and 21 performance decreases slighty, within 3%.

### 2.1.4 Additional Notes

- The sources and javadoc jars of the Ant-built distributions (jdk14, jdk15to18 and jdk13) no longer carry test material. Each module's javadoc target copies the package documentation it needs - org/bouncycastle/<area>/**/*.html - back into the module source directory that has already been compiled from, and zip-src zips that directory afterwards, so every test package's package.html arrived in the sources jar by that route; javadoc-util additionally copied org/bouncycastle/asn1/isismtt/**/*.java, which put test classes into the bcutil javadoc as generated pages, and javadoc-pg deliberately copied the gpg and bcpg test sources in order to document them. Separately the source copies excluded test material only one directory deep and only for *.java, because Ant reads ** as an any-depth wildcard just where it is a whole path segment, so anything nested further or with another extension - the PEM certificate fixtures under org/bouncycastle/est/test/san corrected in 1.86, and an ICAO master list under org/bouncycastle/asn1/icao/test - went through. The source and javadoc copies of every module now exclude test directories at any depth, and javadoc-pg no longer documents the test packages. org.bouncycastle.util.test is unaffected and still ships in the bcprov binary, sources and javadoc jars, as it does from the Gradle build: it is the SimpleTest framework the light-weight API's own test classes are written against, not test material of the distribution. No binary changes - the classes and resources of every Ant-built jar are identical to those of the 1.86 release - and the Gradle-built jdk18on artifacts never carried any of this.
Expand Down