Skip to content

Commit

Permalink
node-name needs to be truncated because the byte array is not base64
Browse files Browse the repository at this point in the history
  • Loading branch information
marcosgopen committed Oct 18, 2024
1 parent 7097ad7 commit e6d0325
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 2 deletions.
8 changes: 8 additions & 0 deletions docs/src/main/asciidoc/transaction.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,14 @@ The node name identifier needs to be unique per transaction manager deployment.
And the node identifier needs to be stable over the transaction manager restarts.

The node name identifier may be configured via the property `quarkus.transaction-manager.node-name`.
[NOTE]
====
The node-name property has a length limitation, it cannot be longer than 28 bytes.
The length limitation can possibly be solved by enabling the property 'quarkus.transaction-manager.shortenNodeNameIfNecessary' which, when enabled, will shorten names longer than 28 bytes.
The shortening process is done by applying the 'SHA-224' algorithm, encoding to Base64 and then truncating the result. Therefore this may result in the node-name ending as an unpredictable duplicate string and this breaks the uniqueness that is mandatory for safe transaction usage.
For more information about the potential for conflict see https://github.com/quarkusio/quarkus/issues/30491
====

[[transaction-scope]]
== Using `@TransactionScoped` to bind CDI beans to the transaction lifecycle
Expand Down
11 changes: 11 additions & 0 deletions extensions/narayana-jta/runtime/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,17 @@
<groupId>org.jboss.narayana.jts</groupId>
<artifactId>narayana-jts-integration</artifactId>
</dependency>
<!-- Test Dependencies -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5-internal</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Base64;
import java.util.Collections;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -60,7 +61,15 @@ private static void shortenNodeName(TransactionManagerConfiguration transactions
HASH_ALGORITHM_FOR_SHORTENING);
final byte[] nodeNameAsBytes = originalNodeName.getBytes();
MessageDigest messageDigest224 = MessageDigest.getInstance(HASH_ALGORITHM_FOR_SHORTENING);
transactions.nodeName = new String(messageDigest224.digest(nodeNameAsBytes), StandardCharsets.UTF_8);
byte[] hashedByteArray = messageDigest224.digest(nodeNameAsBytes);

//Encode the byte array in Base64
//encoding the array might result in a longer array
byte[] base64Result = Base64.getEncoder().encode(hashedByteArray);
//truncate the array
byte[] slice = Arrays.copyOfRange(base64Result, 0, 28);

transactions.nodeName = new String(slice, StandardCharsets.UTF_8);
log.warnf("New node name is \"%s\"", transactions.nodeName);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public final class TransactionManagerConfiguration {
* Whether the node name should be shortened if necessary.
* The node name must not exceed a length of 28 bytes. If this property is set to {@code true}, and the node name exceeds 28
* bytes, the node name is shortened by calculating the <a href="https://en.wikipedia.org/wiki/SHA-2">SHA-224</a> hash,
* which has a length of 28 bytes.
* which has a length of 28 bytes, encoded to Base64 format and then shorten to 28 bytes.
*
* @see #nodeName
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package io.quarkus.narayana.jta.runtime;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.nio.charset.StandardCharsets;

import org.junit.jupiter.api.Test;

public class NarayanaJtaRecorderTest {

//this string has been chosen as when hashed and Base64 encoded the resulted byte array will have a length > 28, so it will be trimmed too.
public static final String NODE_NAME_TO_SHORTEN = "dfe2420d-b12e-4ec3-92c0-ee7c4";

@Test
void testByteLengthWithLongerString() {
TransactionManagerConfiguration transactions = new TransactionManagerConfiguration();
transactions.shortenNodeNameIfNecessary = true;
// create nodeNames larger than 28 bytes
assertTrue(NODE_NAME_TO_SHORTEN.getBytes(StandardCharsets.UTF_8).length > 28);
NarayanaJtaRecorder r = new NarayanaJtaRecorder();
transactions.nodeName = NODE_NAME_TO_SHORTEN;
r.setNodeName(transactions);
int numberOfBytes = transactions.nodeName.getBytes(StandardCharsets.UTF_8).length;
assertEquals(28, numberOfBytes,
"node name bytes was not 28 bytes limit, number of bytes is " + numberOfBytes);
assertFalse(numberOfBytes > 28,
"node name bytes still exceeded 28 bytes limit, number of bytes is " + numberOfBytes);
}

@Test
void testPredictableConversion() {
TransactionManagerConfiguration transactions = new TransactionManagerConfiguration();
transactions.shortenNodeNameIfNecessary = true;
assertTrue(NODE_NAME_TO_SHORTEN.getBytes(StandardCharsets.UTF_8).length > 28);
NarayanaJtaRecorder r = new NarayanaJtaRecorder();
transactions.nodeName = NODE_NAME_TO_SHORTEN;
r.setNodeName(transactions);
int numberOfBytes = transactions.nodeName.getBytes(StandardCharsets.UTF_8).length;
assertEquals(28, numberOfBytes,
"node name bytes was not 28 bytes limit, number of bytes is " + numberOfBytes);
String firstConversion = transactions.nodeName;
transactions.nodeName = NODE_NAME_TO_SHORTEN;
r.setNodeName(transactions);
String secondConversion = transactions.nodeName;
numberOfBytes = transactions.nodeName.getBytes(StandardCharsets.UTF_8).length;
assertEquals(28, numberOfBytes,
"node name bytes was not 28 bytes limit, number of bytes is " + numberOfBytes);
assertEquals(firstConversion, secondConversion,
"Node names were shortened differently: " + firstConversion + " " + secondConversion);
}
}

0 comments on commit e6d0325

Please sign in to comment.