Browse Source

Merge 98cbd340b5 into be70822bc6

pull/15477/merge
Oleksandra Matviienko 2 days ago
committed by GitHub
parent
commit
89ac98ebd0
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 35
      application/src/main/java/org/thingsboard/server/service/sms/smpp/SmppSmsSender.java
  2. 105
      application/src/test/java/org/thingsboard/server/service/sms/smpp/SmppSmsSenderTest.java

35
application/src/main/java/org/thingsboard/server/service/sms/smpp/SmppSmsSender.java

@ -33,6 +33,7 @@ import org.smpp.pdu.PDUException;
import org.smpp.pdu.SubmitSM;
import org.smpp.pdu.SubmitSMResp;
import org.thingsboard.rule.engine.api.sms.exception.SmsException;
import org.thingsboard.rule.engine.api.sms.exception.SmsSendException;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.sms.config.SmppSmsProviderConfiguration;
import org.thingsboard.server.service.sms.AbstractSmsSender;
@ -85,8 +86,9 @@ public class SmppSmsSender extends AbstractSmsSender {
request.setSourceAddr(new Address(config.getSourceTon(), config.getSourceNpi(), config.getSourceAddress()));
}
request.setDestAddr(new Address(config.getDestinationTon(), config.getDestinationNpi(), prepareNumber(numberTo)));
request.setShortMessage(message);
request.setDataCoding(Optional.ofNullable(config.getCodingScheme()).orElse((byte) 0));
byte dataCoding = Optional.ofNullable(config.getCodingScheme()).orElse((byte) 0);
request.setShortMessage(message, charsetFor(dataCoding));
request.setDataCoding(dataCoding);
request.setReplaceIfPresentFlag((byte) 0);
request.setEsmClass((byte) 0);
request.setProtocolId((byte) 0);
@ -98,12 +100,39 @@ public class SmppSmsSender extends AbstractSmsSender {
log.debug("SMPP submit command status: {}", response.getCommandStatus());
} catch (Exception e) {
throw new RuntimeException(e);
throw new SmsSendException("Failed to send SMS message - " + ExceptionUtils.getRootCauseMessage(e), e);
}
return countMessageSegments(message);
}
private static String charsetFor(byte dataCoding) {
return switch (dataCoding) {
case 0 -> Data.ENC_GSM7BIT; // SMSC Default Alphabet (GSM 7-bit) — opensmpp-charset SPI charset "X-Gsm7Bit"
case 1 -> "US-ASCII"; // IA5 (CCITT T.50) / ASCII
// DCS 2, 4 are "Octet unspecified" (binary) — ISO-8859-1 is bijective for bytes 0-255, so it passes bytes through unchanged.
case 2 -> "ISO-8859-1"; // Octet unspecified (binary pass-through)
case 3 -> "ISO-8859-1"; // Latin 1
case 4 -> "ISO-8859-1"; // Octet unspecified (binary pass-through)
case 5 -> "Shift_JIS"; // JIS
case 6 -> "ISO-8859-5"; // Cyrillic
case 7 -> "ISO-8859-8"; // Latin/Hebrew
case 8 -> "UTF-16BE"; // UCS-2
// FIXME: Pictogram encoding not natively supported, falling back to ISO-8859-1.
case 9 -> "ISO-8859-1"; // Pictogram
case 10 -> "ISO-2022-JP"; // Music Codes
case 13 -> "JIS_X0212-1990"; // Extended Kanji JIS
case 14 -> "EUC-KR"; // KS C 5601
default -> {
// Unknown/unsupported data_coding (the @Schema allowableValues are documentation only and not enforced,
// so out-of-range values may be persisted). Fall back to a byte-transparent charset instead of failing the
// send, since SMS backs 2FA and notifications. ISO-8859-1 is bijective for bytes 0-255 and never substitutes.
log.warn("Unsupported SMPP data_coding {}, falling back to ISO-8859-1 encoding", dataCoding);
yield "ISO-8859-1";
}
};
}
private synchronized void checkSmppSession() {
if (smppSession == null || !smppSession.isOpened()) {
smppSession = initSmppSession();

105
application/src/test/java/org/thingsboard/server/service/sms/smpp/SmppSmsSenderTest.java

@ -18,8 +18,10 @@ package org.thingsboard.server.service.sms.smpp;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.junit.jupiter.MockitoExtension;
import org.smpp.Session;
import org.smpp.pdu.SubmitSM;
import org.smpp.pdu.SubmitSMResp;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.sms.config.SmppSmsProviderConfiguration;
@ -27,6 +29,8 @@ import org.thingsboard.server.common.data.sms.config.SmppSmsProviderConfiguratio
import java.lang.reflect.Constructor;
import static org.junit.Assert.fail;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.mock;
@ -91,6 +95,107 @@ public class SmppSmsSenderTest {
}));
}
@Test
public void testSendSms_dcs8_ucs2() throws Exception {
// "Привіт" as UCS-2 (UTF-16BE) — exact wire bytes, two octets per code point.
byte[] expected = {0x04, 0x1F, 0x04, 0x40, 0x04, 0x38, 0x04, 0x32, 0x04, 0x56, 0x04, 0x42};
assertEncodedBytes((byte) 8, "Привіт", expected);
}
@Test
public void testSendSms_dcs6_cyrillic() throws Exception {
// "Привіт" as ISO-8859-5 — exact single-byte Cyrillic wire bytes.
byte[] expected = {(byte) 0xBF, (byte) 0xE0, (byte) 0xD8, (byte) 0xD2, (byte) 0xF6, (byte) 0xE2};
assertEncodedBytes((byte) 6, "Привіт", expected);
}
@Test
public void testSendSms_dcs3_latin1() throws Exception {
assertEncoded((byte) 3, "naïve café", "ISO-8859-1");
}
@Test
public void testSendSms_dcs0_gsm7() throws Exception {
// DCS 0 is the SMSC default alphabet (GSM 7-bit). 'é' is in the GSM alphabet (0x05); under a plain US-ASCII
// fallback it would be lost to '?' (0x3F), so these bytes guard against that regression.
byte[] expected = {0x63, 0x61, 0x66, 0x05}; // café
assertEncodedBytes((byte) 0, "café", expected);
}
@Test
public void testSendSms_dcs1_ia5() throws Exception {
assertEncoded((byte) 1, "Hi", "US-ASCII");
}
@Test
public void testSendSms_dcs2_octet() throws Exception {
assertEncoded((byte) 2, "café", "ISO-8859-1");
}
@Test
public void testSendSms_dcs4_octet() throws Exception {
assertEncoded((byte) 4, "café", "ISO-8859-1");
}
@Test
public void testSendSms_dcs5_jis() throws Exception {
assertEncoded((byte) 5, "こんにちは", "Shift_JIS");
}
@Test
public void testSendSms_dcs7_hebrew() throws Exception {
assertEncoded((byte) 7, "שלום", "ISO-8859-8");
}
@Test
public void testSendSms_dcs9_pictogram() throws Exception {
assertEncoded((byte) 9, "café", "ISO-8859-1");
}
@Test
public void testSendSms_dcs10_music() throws Exception {
assertEncoded((byte) 10, "こんにちは", "ISO-2022-JP");
}
@Test
public void testSendSms_dcs13_extendedKanji() throws Exception {
// "Hi" triggers substitute bytes in JIS_X0212 (no ASCII mapping), which distinguishes it from a US-ASCII fallback.
assertEncoded((byte) 13, "Hi", "JIS_X0212-1990");
}
@Test
public void testSendSms_dcs14_korean() throws Exception {
assertEncoded((byte) 14, "안녕하세요", "EUC-KR");
}
// Verifies the DCS -> charset mapping. The expected side mirrors the production charset, so it locks the mapping
// but not the exact wire bytes; use assertEncodedBytes for charsets where a silent substitution could hide a bug.
private void assertEncoded(byte dataCoding, String message, String expectedCharset) throws Exception {
SubmitSM sent = captureSentPdu(dataCoding, message);
assertArrayEquals(message.getBytes(expectedCharset), sent.getShortMessageData().getBuffer());
assertEquals(dataCoding, sent.getDataCoding());
}
// Locks the exact bytes written into the SubmitSM PDU against a known-good sequence (independent of the production code).
private void assertEncodedBytes(byte dataCoding, String message, byte[] expectedBytes) throws Exception {
SubmitSM sent = captureSentPdu(dataCoding, message);
assertArrayEquals(expectedBytes, sent.getShortMessageData().getBuffer());
assertEquals(dataCoding, sent.getDataCoding());
}
private SubmitSM captureSentPdu(byte dataCoding, String message) throws Exception {
when(smppSession.isOpened()).thenReturn(true);
when(smppSession.submit(any())).thenReturn(new SubmitSMResp());
setDefaultSmppConfig();
smppConfig.setCodingScheme(dataCoding);
smppSmsSender.sendSms("123545", message);
ArgumentCaptor<SubmitSM> captor = ArgumentCaptor.forClass(SubmitSM.class);
verify(smppSession).submit(captor.capture());
return captor.getValue();
}
private void setDefaultSmppConfig() {
smppConfig.setProtocolVersion("3.3");
smppConfig.setHost("smpphost");

Loading…
Cancel
Save