This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new 5e5961308abe CAMEL-25318: camel-smpp - split an 8-bit message by its
length in bytes, into at most 255 segments (#27394)
5e5961308abe is described below
commit 5e5961308abe8caad4a3db72cd0d71e0896238ad
Author: allthingssecurity <[email protected]>
AuthorDate: Tue Oct 6 14:52:44 2026 +0530
CAMEL-25318: camel-smpp - split an 8-bit message by its length in bytes,
into at most 255 segments (#27394)
* CAMEL-25318: camel-smpp - split an 8-bit message by its length in bytes,
into at most 255 segments
An 8-bit short message holds 140 octets, and the splitter splits the bytes
of
the message, but SmppSmCommand created the 8-bit splitter with the length of
the body read as a String. A body whose bytes decode to fewer characters
(non
ASCII text in the UTF-8 exchange charset, or binary data with valid UTF-8
sequences) was sent as one short message of more than 140 octets.
SmppSplitter.split applied the 255 segment limit before counting the last
partial segment, so a message of 255 full segments and a few bytes more was
split into 256 segments, and the total number in the UDH was written as 0.
Create the 8-bit splitter with the length of the encoded message, and count
the last partial segment before applying the limit.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* CAMEL-25318: camel-smpp - at most 255 segments with the national language
splitter too, and upgrade guide note
SmppNLSTSplitter.split had the same clamp before the remainder: a message
of exactly 255 full segments and a remainder
gave 256 segments, with 0 as the total in the UDH. The segment count is now
a shared helper of SmppSplitter used by
both splitters.
Review nits: createSplitter takes the already encoded body (no second
conversion), and SmppSplitBodyTest sets the
charset of the exchange to UTF-8.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* CAMEL-25318: camel-smpp - Keep the createSplitter(Message) signature so
overriding subclasses keep working
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
---
.../camel/component/smpp/SmppNLSTSplitter.java | 14 +--
.../apache/camel/component/smpp/SmppSmCommand.java | 4 +-
.../apache/camel/component/smpp/SmppSplitter.java | 26 +++--
.../camel/component/smpp/SmppNLSTSplitterTest.java | 20 ++++
.../camel/component/smpp/SmppSplitBodyTest.java | 119 +++++++++++++++++++++
.../ROOT/pages/camel-4x-upgrade-guide-4_23.adoc | 10 ++
6 files changed, 170 insertions(+), 23 deletions(-)
diff --git
a/components/camel-smpp/src/main/java/org/apache/camel/component/smpp/SmppNLSTSplitter.java
b/components/camel-smpp/src/main/java/org/apache/camel/component/smpp/SmppNLSTSplitter.java
index d903ec7f9e59..7311156e55a6 100644
---
a/components/camel-smpp/src/main/java/org/apache/camel/component/smpp/SmppNLSTSplitter.java
+++
b/components/camel-smpp/src/main/java/org/apache/camel/component/smpp/SmppNLSTSplitter.java
@@ -93,17 +93,9 @@ public class SmppNLSTSplitter extends SmppSplitter {
int segmentLength = getSegmentLength();
- // determine how many messages
- int segmentNum = message.length / segmentLength;
- int messageLength = message.length;
- if (segmentNum > MAX_SEG_COUNT) {
- // this is too long, can't fit, so chop
- segmentNum = MAX_SEG_COUNT;
- messageLength = segmentNum * segmentLength;
- }
- if ((messageLength % segmentLength) > 0) {
- segmentNum++;
- }
+ // determine how many messages (a message that is too long is chopped)
+ int segmentNum = getSegmentCount(message.length, segmentLength);
+ int messageLength = Math.min(message.length, segmentNum *
segmentLength);
byte[][] segments = new byte[segmentNum][];
diff --git
a/components/camel-smpp/src/main/java/org/apache/camel/component/smpp/SmppSmCommand.java
b/components/camel-smpp/src/main/java/org/apache/camel/component/smpp/SmppSmCommand.java
index 360cd0f7e152..e60628f0dd3b 100644
---
a/components/camel-smpp/src/main/java/org/apache/camel/component/smpp/SmppSmCommand.java
+++
b/components/camel-smpp/src/main/java/org/apache/camel/component/smpp/SmppSmCommand.java
@@ -92,7 +92,9 @@ public abstract class SmppSmCommand extends
AbstractSmppCommand {
String body = message.getBody(String.class);
if (SmppUtils.is8Bit(alphabet)) {
- splitter = new Smpp8BitSplitter(body.length());
+ // an 8-bit message is split by its length in bytes (the signature
of this method is kept, so subclasses
+ // that override it keep working, at the cost of encoding the body
again)
+ splitter = new Smpp8BitSplitter(getShortMessage(message).length);
} else if (alphabet == Alphabet.ALPHA_UCS2) {
splitter = new SmppUcs2Splitter(body.length());
} else {
diff --git
a/components/camel-smpp/src/main/java/org/apache/camel/component/smpp/SmppSplitter.java
b/components/camel-smpp/src/main/java/org/apache/camel/component/smpp/SmppSplitter.java
index 57068ed99005..651df692199b 100644
---
a/components/camel-smpp/src/main/java/org/apache/camel/component/smpp/SmppSplitter.java
+++
b/components/camel-smpp/src/main/java/org/apache/camel/component/smpp/SmppSplitter.java
@@ -138,17 +138,9 @@ public class SmppSplitter {
int segmentLength = getSegmentLength();
- // determine how many messages
- int segmentNum = message.length / segmentLength;
- int messageLength = message.length;
- if (segmentNum > MAX_SEG_COUNT) {
- // this is too long, can't fit, so chop
- segmentNum = MAX_SEG_COUNT;
- messageLength = segmentNum * segmentLength;
- }
- if ((messageLength % segmentLength) > 0) {
- segmentNum++;
- }
+ // determine how many messages (a message that is too long is chopped)
+ int segmentNum = getSegmentCount(message.length, segmentLength);
+ int messageLength = Math.min(message.length, segmentNum *
segmentLength);
byte[][] segments = new byte[segmentNum][];
@@ -184,6 +176,18 @@ public class SmppSplitter {
return segments;
}
+ /**
+ * The number of segments for a message of the given length: one more for
a partial last segment, and at most
+ * {@link #MAX_SEG_COUNT}, as the UDH holds the total in one octet.
+ */
+ protected static int getSegmentCount(int messageLength, int segmentLength)
{
+ int segmentNum = messageLength / segmentLength;
+ if (messageLength % segmentLength > 0) {
+ segmentNum++;
+ }
+ return Math.min(segmentNum, MAX_SEG_COUNT);
+ }
+
protected boolean isSplitRequired() {
return getCurrentLength() > getMessageLength();
}
diff --git
a/components/camel-smpp/src/test/java/org/apache/camel/component/smpp/SmppNLSTSplitterTest.java
b/components/camel-smpp/src/test/java/org/apache/camel/component/smpp/SmppNLSTSplitterTest.java
index 702d0558d1b0..c4d8db2f64ce 100644
---
a/components/camel-smpp/src/test/java/org/apache/camel/component/smpp/SmppNLSTSplitterTest.java
+++
b/components/camel-smpp/src/test/java/org/apache/camel/component/smpp/SmppNLSTSplitterTest.java
@@ -16,6 +16,8 @@
*/
package org.apache.camel.component.smpp;
+import java.util.Arrays;
+
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
@@ -87,4 +89,22 @@ public class SmppNLSTSplitterTest {
result[1].length -
SmppNLSTSplitter.UDHIE_NLI_MULTI_MSG_HEADER_REAL_LENGTH);
assertEquals(message, firstShortMessage + secondShortMessage);
}
+
+ @Test
+ public void splitIntoAtMost255Segments() {
+ // one byte more than 255 full segments
+ byte[] message = new byte[255 * SmppNLSTSplitter.MAX_SEG_BYTE_SIZE +
1];
+ Arrays.fill(message, (byte) '1');
+
+ SmppSplitter splitter = new SmppNLSTSplitter(message.length, (byte)
0x01);
+ byte[][] result = splitter.split(message);
+
+ assertEquals(255, result.length, "a long message is truncated to 255
segments");
+ for (int i = 0; i < result.length; i++) {
+ assertEquals((byte) 255, result[i][4], "total number of segments
in the UDH of segment " + (i + 1));
+ assertEquals((byte) (i + 1), result[i][5], "number of segment " +
(i + 1) + " in its UDH");
+
assertEquals(SmppNLSTSplitter.UDHIE_NLI_MULTI_MSG_HEADER_REAL_LENGTH +
SmppNLSTSplitter.MAX_SEG_BYTE_SIZE,
+ result[i].length, "length of segment " + (i + 1));
+ }
+ }
}
diff --git
a/components/camel-smpp/src/test/java/org/apache/camel/component/smpp/SmppSplitBodyTest.java
b/components/camel-smpp/src/test/java/org/apache/camel/component/smpp/SmppSplitBodyTest.java
new file mode 100644
index 000000000000..056d1d85571f
--- /dev/null
+++
b/components/camel-smpp/src/test/java/org/apache/camel/component/smpp/SmppSplitBodyTest.java
@@ -0,0 +1,119 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.smpp;
+
+import java.io.ByteArrayOutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+
+import org.apache.camel.Exchange;
+import org.apache.camel.ExchangePattern;
+import org.apache.camel.impl.DefaultCamelContext;
+import org.apache.camel.support.DefaultExchange;
+import org.jsmpp.bean.Alphabet;
+import org.jsmpp.session.SMPPSession;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+
+/**
+ * An 8-bit short message holds 140 octets: the body must be split by its
length in bytes, into at most 255 segments.
+ */
+class SmppSplitBodyTest {
+
+ private SmppSubmitSmCommand command;
+
+ @BeforeEach
+ void setUp() {
+ command = new SmppSubmitSmCommand(mock(SMPPSession.class), new
SmppConfiguration());
+ }
+
+ @Test
+ void eightBitBinaryBodyOfMoreThan140BytesIsSplit() throws Exception {
+ // 150 bytes, which happen to be 50 valid UTF-8 sequences (50
characters when read as a String)
+ byte[] body = new byte[150];
+ for (int i = 0; i < body.length; i += 3) {
+ body[i] = (byte) 0xE2;
+ body[i + 1] = (byte) 0x82;
+ body[i + 2] = (byte) 0xAC;
+ }
+
+ byte[][] segments = splitEightBit(body);
+
+ assertEquals(2, segments.length, "a body of 150 bytes needs two
segments");
+ assertSegments(body, segments);
+ }
+
+ @Test
+ void eightBitTextBodyOfMoreThan140BytesIsSplit() throws Exception {
+ // 100 characters, 200 bytes in UTF-8 (the charset of the exchange)
+ String text = "é".repeat(100);
+
+ byte[][] segments = splitEightBit(text);
+
+ assertEquals(2, segments.length, "a body of 200 bytes needs two
segments");
+ assertSegments(text.getBytes(StandardCharsets.UTF_8), segments);
+ }
+
+ @Test
+ void eightBitBodyIsNotSplitIntoMoreThan255Segments() throws Exception {
+ // one byte more than 255 full segments
+ byte[] body = new byte[255 * Smpp8BitSplitter.MAX_SEG_BYTE_SIZE + 1];
+ Arrays.fill(body, (byte) 'a');
+
+ byte[][] segments = splitEightBit(body);
+
+ assertEquals(255, segments.length, "a long message is truncated to 255
segments");
+ for (int i = 0; i < segments.length; i++) {
+ assertEquals((byte) 255, segments[i][4], "total number of segments
in the UDH of segment " + (i + 1));
+ assertEquals((byte) (i + 1), segments[i][5], "number of segment "
+ (i + 1) + " in its UDH");
+ }
+ }
+
+ @Test
+ void eightBitBodyOf140BytesIsNotSplit() throws Exception {
+ byte[] body = new byte[140];
+ Arrays.fill(body, (byte) 'a');
+
+ byte[][] segments = splitEightBit(body);
+
+ assertEquals(1, segments.length);
+ assertArrayEquals(body, segments[0]);
+ }
+
+ private byte[][] splitEightBit(Object body) throws Exception {
+ Exchange exchange = new DefaultExchange(new DefaultCamelContext(),
ExchangePattern.InOut);
+ // the charset that a String body is converted with
+ exchange.setProperty(Exchange.CHARSET_NAME,
StandardCharsets.UTF_8.name());
+ exchange.getIn().setHeader(SmppConstants.ALPHABET,
Alphabet.ALPHA_8_BIT.value());
+ exchange.getIn().setBody(body);
+ return command.splitBody(exchange.getIn());
+ }
+
+ private static void assertSegments(byte[] body, byte[][] segments) {
+ ByteArrayOutputStream data = new ByteArrayOutputStream();
+ for (byte[] segment : segments) {
+ assertTrue(segment.length <= 140, "a segment holds at most 140
bytes, was " + segment.length);
+ data.write(segment, SmppSplitter.UDHIE_HEADER_REAL_LENGTH,
segment.length - SmppSplitter.UDHIE_HEADER_REAL_LENGTH);
+ }
+ assertArrayEquals(body, data.toByteArray());
+ }
+}
diff --git
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
index d7961fa10ba7..d14da98a6000 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
@@ -648,6 +648,16 @@ Alert notifications already worked this way. Two
consequences:
`SmppEndpoint#createOnAcceptDeliverSmExchange` and
`SmppEndpoint#createOnAcceptDataSm` are deprecated. They are no
longer used by the component; build such an exchange through the consumer
instead.
+==== 8-bit messages are split by their length in bytes
+
+With an 8-bit alphabet (`alphabet=4`, or an 8-bit data coding), the producer
now decides whether to split a message by
+the length in bytes of the body as it is sent, instead of the number of
characters of the body read as a String. A
+body of at most 140 characters but more than 140 bytes (for example text with
multi-byte characters, or binary data)
+is now sent as several segments; with `splittingPolicy=TRUNCATE` it is
truncated to 140 bytes, and with
+`splittingPolicy=REJECT` it is rejected, where it was previously sent as one
oversize short message. A message that
+needs more than 255 segments is cut to 255 segments: with exactly 255 full
segments and a remainder, the total number
+of segments in the UDH was written as `0` (also with the national language
splitter).
+
=== camel-console
The `context` developer console no longer counts routes created by Kamelets in
its `routesTotal` and `routesStarted`