[
https://issues.apache.org/jira/browse/CAMEL-23066?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Claus Ibsen updated CAMEL-23066:
--------------------------------
Summary: camel-as2 - AS2Utils.createMessageId() produces duplicate
Message-IDs after JVM restart (nanoTime-based) (was:
AS2Utils.createMessageId() produces duplicate Message-IDs after JVM restart
(nanoTime-based))
> camel-as2 - AS2Utils.createMessageId() produces duplicate Message-IDs after
> JVM restart (nanoTime-based)
> --------------------------------------------------------------------------------------------------------
>
> Key: CAMEL-23066
> URL: https://issues.apache.org/jira/browse/CAMEL-23066
> Project: Camel
> Issue Type: Bug
> Components: camel-as2
> Affects Versions: 4.18.0
> Reporter: Reto Peter
> Priority: Minor
> Attachments: AS2Utils.java
>
>
> {panel:title=Problem}
> \{{AS2Utils.createMessageId()}} generates Message-IDs using
> \{{System.nanoTime()}} combined with a random number. \{{nanoTime()}} is
> relative to an arbitrary JVM start time and resets on every JVM restart. This
> produces duplicate Message-IDs that violate the AS2
> specification (RFC 4130) requirement for globally unique message
> identifiers. Receiving AS2 servers that use the Message-ID as a primary key
> (e.g., OpenAS2) reject the message with a duplicate key violation.
> \{panel}
> h3. Root Cause
> The current implementation in \{{AS2Utils.java}}:
> \{code:java|title=Current code in AS2Utils.java (Camel 4.18.0)}
> private static SecureRandom generator = new SecureRandom();
> public static String createMessageId(String fqdn) {
> return "<" + Long.toString(System.nanoTime(), 36) + "."
> + Long.toString(generator.nextLong(), 36) + "@" + fqdn + ">";
> }
> \{code}
> The problem is \{{System.nanoTime()}}:
> - It returns a value relative to an arbitrary origin that resets on each
> JVM start
> - After a server restart, the first few messages will have the same
> nanoTime range as messages sent during a previous JVM lifetime
> - Combined with the random part, there is a significant collision
> probability across restarts because the nanoTime part provides the
> "sequential" component
> Example: Server sends messages, restarts, sends messages again. The
> nanoTime values from the second run overlap with values from the first run.
> If the random part happens to be similar, duplicate Message-IDs are produced.
> h3. Steps to Reproduce
> Configure a Camel AS2 endpoint and send several messages to a partner
> Restart the JVM (e.g., application server restart, deployment)
> Send messages again immediately after restart
> Observe: some Message-IDs may collide with IDs generated in the previous
> JVM lifetime
> Partners that store Message-IDs as unique keys (e.g., OpenAS2) reject the
> message with a primary key violation
> h3. Proposed Fix
> Replace the \{{nanoTime}}-based generation with UUID v7 (RFC 9562), which
> embeds a 48-bit millisecond wall-clock timestamp plus 74 bits of
> cryptographic randomness. This guarantees uniqueness across JVM restarts and
> provides lexicographic sortability by
> creation time.
> \{code:diff}
> - private static SecureRandom generator = new SecureRandom();
> - private static final SecureRandom RANDOM = new SecureRandom();
> - public static String createMessageId(String fqdn) {
> return "<" + Long.toString(System.nanoTime(), 36) + "."
> + Long.toString(generator.nextLong(), 36) + "@" + fqdn + ">";
> // UUID v7 (RFC 9562): 48-bit unix_ts_ms + 4-bit version + 12-bit random
> + 2-bit variant + 62-bit random
> long timestamp = System.currentTimeMillis();
> long msb = ((timestamp & 0xFFFF_FFFF_FFFFL) << 16) // 48-bit timestamp →
> bits 63-16
> | (0x7L << 12) // version 7
> → bits 15-12
> | (RANDOM.nextLong() & 0xFFFL); // rand_a 12 bits
> → bits 11-0
> long lsb = (RANDOM.nextLong() & 0x3FFF_FFFF_FFFF_FFFFL) // rand_b 62
> bits → bits 61-0
> | 0x8000_0000_0000_0000L; // variant 10
> → bits 63-62
> UUID uuidV7 = new UUID(msb, lsb);
> return "<" + uuidV7 + "@" + fqdn + ">";
> - }
> \{code}
> h4. Additional import needed
> \{code:java}
> import java.util.UUID;
> \{code}
> h3. Why UUID v7
>
> ┌────────────────────────────┬──────────────────────────────┬──────────────────────┬────────────────────────┐
> │ │ nanoTime (current) │ UUID v4
> (randomUUID) │ UUID v7 (proposed fix) │
>
> ├────────────────────────────┼──────────────────────────────┼──────────────────────┼────────────────────────┤
> │ Unique across JVM restarts │ No │ Yes
> │ Yes │
>
> ├────────────────────────────┼──────────────────────────────┼──────────────────────┼────────────────────────┤
> │ Sortable by creation time │ No │ No
> │ Yes │
>
> ├────────────────────────────┼──────────────────────────────┼──────────────────────┼────────────────────────┤
> │ Cryptographically random │ Partially (random part only) │ Yes
> │ Yes (74 random bits) │
>
> ├────────────────────────────┼──────────────────────────────┼──────────────────────┼────────────────────────┤
> │ RFC standard │ No │ RFC 9562
> │ RFC 9562 │
>
> └────────────────────────────┴──────────────────────────────┴──────────────────────┴────────────────────────┘
> Note: \{{UUID.randomUUID()}} (v4) would also solve the uniqueness problem.
> UUID v7 is preferred because it additionally provides time-based sortability,
> which is useful for log analysis and debugging. Both are acceptable solutions.
> h3. Example Output
> Before (nanoTime-based, restarts cause collisions):
> \{code}
> [email protected]
> \{code}
> After (UUID v7, globally unique):
> \{code}
> [email protected]
> \{code}
> h3. Test Scenario
> Tested with OpenAS2 as the receiving partner. Before the fix, after a JVM
> restart OpenAS2 rejected messages with duplicate Message-ID errors. After the
> fix, all Message-IDs are unique across restarts.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)