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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ SMS Tool for 3G/4G/5G modem

* sms tool for various of 3g/4g modem
* read sms as raw pdu or decoded text
* support ucs2 decoding
* support ucs2 decoding and UTF-8/UCS-2 sending

Usage:
----------------
Expand Down
8 changes: 6 additions & 2 deletions pdu_lib/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ ucs2_to_utf8:
pdu_decoder: pdu.o pdu_decoder.o ucs2_to_utf8
$(CC) $(CFLAGS) ucs2_to_utf8.o pdu.o pdu_decoder.o -lm -o pdu_decoder
clean:
rm -f *.o pdu_decoder
test: clean ALL
rm -f *.o pdu_decoder pdu_test
test: clean ALL pdu_test
echo "0891683108501405F8240BA10156686616F60008414090912385235C6D4191CF6C4752A860015BC67801FF1A00350030003900360036FF0C4EB2FF0C8BB05F9762BD59566BCF592990FD8981676554E6FF0C611F89C9597D76848BDD63A883507ED960A87684670B53CBFF019884795D60A84E2D5956FF01"|./pdu_decoder
echo "0891683108501405F8640BA10156686616F6000841400100957423830608048A3002026B21767B5F556D4191CF6C475373900100356D4191CF5E01FF0853EF63620035004D6D4191CFFF09FF0C731B623394FE63A5FF1A0068007400740070003A002F002F007300680061006B0065002E00730064002E006300680069006E0061006D006F00620069006C0065002E0063006F006D30025C714E1C79FB52A8"|./pdu_decoder
./pdu_test

pdu_test: pdu.o pdu_test.o
$(CC) $(CFLAGS) pdu.o pdu_test.o -lm -o pdu_test
117 changes: 107 additions & 10 deletions pdu_lib/pdu.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ enum {
SMS_SUBMIT = 0x11,

SMS_MAX_7BIT_TEXT_LENGTH = 160,
SMS_MAX_UCS2_TEXT_LENGTH = 70,
};

// Swap decimal digits of a number (e.g. 12 -> 21).
Expand Down Expand Up @@ -208,7 +209,7 @@ static const int latin1_to_gsm7bits[256] = {
};

static int
AsciiToG7bit(const char* buffer, int buffer_length, unsigned char* output_buffer)
AsciiToG7bit(const char* buffer, int buffer_length, char* output_buffer)
{
int i, j, val;

Expand Down Expand Up @@ -247,7 +248,7 @@ EncodePhoneNumber(const char* phone_number, unsigned char* output_buffer, int bu
int i = 0;
for (; i < phone_number_length; ++i) {

if (phone_number[i] < '0' && phone_number[i] > '9')
if (phone_number[i] < '0' || phone_number[i] > '9')
return -1;

if (i % 2 == 0) {
Expand All @@ -262,6 +263,67 @@ EncodePhoneNumber(const char* phone_number, unsigned char* output_buffer, int bu
return output_buffer_length;
}

/*
* Convert a valid UTF-8 string to UCS-2 (UTF-16BE without surrogate pairs).
* SMS UCS-2 user data is limited to 140 octets for a single message.
*/
static int
Utf8ToUcs2(const char* input, unsigned char* output_buffer, int buffer_size)
{
const unsigned char* p = (const unsigned char*)input;
int output_buffer_length = 0;

while (*p) {
unsigned int codepoint;

if (*p < 0x80) {
codepoint = *p++;
} else if (*p >= 0xC2 && *p <= 0xDF) {
if ((p[1] & 0xC0) != 0x80)
return -1;
codepoint = ((*p & 0x1F) << 6) | (p[1] & 0x3F);
p += 2;
} else if (*p >= 0xE0 && *p <= 0xEF) {
if ((p[1] & 0xC0) != 0x80 || (p[2] & 0xC0) != 0x80)
return -1;
if ((*p == 0xE0 && p[1] < 0xA0) ||
(*p == 0xED && p[1] >= 0xA0))
return -1;
codepoint = ((*p & 0x0F) << 12) |
((p[1] & 0x3F) << 6) | (p[2] & 0x3F);
p += 3;
} else {
/* UCS-2 cannot represent four-byte UTF-8 code points. */
return -1;
}

if (output_buffer_length + 2 > buffer_size)
return -1;
output_buffer[output_buffer_length++] = codepoint >> 8;
output_buffer[output_buffer_length++] = codepoint & 0xFF;
}

return output_buffer_length;
}

static int
NormalizePhoneNumber(const char* phone_number, const char** digits, int* international)
{
if (!phone_number || !*phone_number)
return -1;

*international = phone_number[0] == '+';
*digits = phone_number + *international;
if (!**digits)
return -1;

const size_t length = strlen(*digits);
if (length > 255)
return -1;

return length;
}

// Decode a digit based phone number for SMS based format.
static int
DecodePhoneNumber(const unsigned char* buffer, int phone_number_length, char* output_phone_number)
Expand All @@ -282,18 +344,23 @@ int
pdu_encode(const char* service_center_number, const char* phone_number, const char* sms_text,
unsigned char* output_buffer, int buffer_size)
{
if (buffer_size < 2)
if (!phone_number || !sms_text || !output_buffer || buffer_size < 2)
return -1;

int output_buffer_length = 0;

// 1. Set SMS center number.
int length = 0;
if (service_center_number && strlen(service_center_number) > 0) {
const char* service_center_digits;
int service_center_international;
if (NormalizePhoneNumber(service_center_number, &service_center_digits,
&service_center_international) < 0)
return -1;
output_buffer[1] = TYPE_OF_ADDRESS_INTERNATIONAL_PHONE;
length = EncodePhoneNumber(service_center_number,
length = EncodePhoneNumber(service_center_digits,
output_buffer + 2, buffer_size - 2);
if (length < 0 && length >= 254)
if (length < 0 || length >= 254)
return -1;
length++; // Add type of address.
}
Expand All @@ -307,29 +374,60 @@ pdu_encode(const char* service_center_number, const char* phone_number, const ch
output_buffer[output_buffer_length++] = 0x00; // Message reference.

// 3. Set phone number.
output_buffer[output_buffer_length] = strlen(phone_number);
const char* phone_number_digits;
int phone_number_international;
const int phone_number_length = NormalizePhoneNumber(phone_number,
&phone_number_digits,
&phone_number_international);
if (phone_number_length < 0)
return -1;
output_buffer[output_buffer_length] = phone_number_length;

if (strlen(phone_number) < 6) {
if (!phone_number_international && phone_number_length < 6) {
output_buffer[output_buffer_length + 1] = TYPE_OF_ADDRESS_UNKNOWN;
} else {
output_buffer[output_buffer_length + 1] = TYPE_OF_ADDRESS_INTERNATIONAL_PHONE;
}

length = EncodePhoneNumber(phone_number,
length = EncodePhoneNumber(phone_number_digits,
output_buffer + output_buffer_length + 2,
buffer_size - output_buffer_length - 2);
if (length < 0)
return -1;
output_buffer_length += length + 2;
if (output_buffer_length + 4 > buffer_size)
return -1; // Check if it has space for four more bytes.


/* Use UCS-2 whenever the UTF-8 input is not pure ASCII. */
int use_ucs2 = 0;
for (const unsigned char* p = (const unsigned char*)sms_text; *p; ++p) {
if (*p & 0x80) {
use_ucs2 = 1;
break;
}
}

// 4. Protocol identifiers.
output_buffer[output_buffer_length++] = 0x00; // TP-PID: Protocol identifier.
output_buffer[output_buffer_length++] = 0x00; // TP-DCS: Data coding scheme.
output_buffer[output_buffer_length++] = use_ucs2 ? 0x08 : 0x00;
output_buffer[output_buffer_length++] = 0xB0; // TP-VP: Validity: 10 days

// 5. SMS message.
int sms_text_length = strlen(sms_text);
if (use_ucs2) {
unsigned char sms_text_ucs2[2 * SMS_MAX_UCS2_TEXT_LENGTH];
sms_text_length = Utf8ToUcs2(sms_text, sms_text_ucs2,
sizeof(sms_text_ucs2));
if (sms_text_length < 0 ||
output_buffer_length + 1 + sms_text_length > buffer_size)
return -1;
output_buffer[output_buffer_length++] = sms_text_length;
memcpy(output_buffer + output_buffer_length, sms_text_ucs2,
sms_text_length);
return output_buffer_length + sms_text_length;
}

char sms_text_7bit[2*SMS_MAX_7BIT_TEXT_LENGTH];
sms_text_length = AsciiToG7bit(sms_text, sms_text_length, sms_text_7bit);
if (sms_text_length > SMS_MAX_7BIT_TEXT_LENGTH)
Expand Down Expand Up @@ -445,4 +543,3 @@ int pdu_decode(const unsigned char* buffer, int buffer_length,

return output_sms_text_length;
}

64 changes: 64 additions & 0 deletions pdu_lib/pdu_test.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#include "pdu.h"

#include <stdio.h>
#include <string.h>

static int expect_pdu(const char* name, const char* number, const char* text,
const unsigned char* expected, size_t expected_length)
{
unsigned char actual[SMS_MAX_PDU_LENGTH];
int actual_length = pdu_encode("", number, text, actual, sizeof(actual));

if (actual_length != (int)expected_length ||
memcmp(actual, expected, expected_length) != 0) {
fprintf(stderr, "%s: encoded PDU does not match\n", name);
return 1;
}
return 0;
}

int main(void)
{
static const unsigned char ascii_pdu[] = {
0x00, 0x11, 0x00, 0x0A, 0x91, 0x21, 0x43, 0x65, 0x87, 0x09,
0x00, 0x00, 0xB0, 0x05, 0xE8, 0x32, 0x9B, 0xFD, 0x06,
};
static const unsigned char ucs2_pdu[] = {
0x00, 0x11, 0x00, 0x0B, 0x91, 0x97, 0x50, 0x25, 0x00, 0x53,
0xF3, 0x00, 0x08, 0xB0, 0x2C, 0x04, 0x14, 0x04, 0x38, 0x04,
0x3C, 0x04, 0x30, 0x00, 0x2C, 0x00, 0x20, 0x04, 0x3F, 0x04,
0x38, 0x04, 0x48, 0x04, 0x35, 0x04, 0x42, 0x00, 0x20, 0x04,
0x42, 0x04, 0x35, 0x04, 0x31, 0x04, 0x35, 0x00, 0x20, 0x00,
0x4E, 0x00, 0x4C, 0x00, 0x36, 0x00, 0x37, 0x00, 0x38,
};
unsigned char pdu[SMS_MAX_PDU_LENGTH];
char long_ucs2[71 * 2 + 1];
int failed = 0;

failed |= expect_pdu("ASCII", "+1234567890", "hello",
ascii_pdu, sizeof(ascii_pdu));
failed |= expect_pdu("UCS-2", "+79055200353",
"Дима, пишет тебе NL678",
ucs2_pdu, sizeof(ucs2_pdu));

if (pdu_encode("", "+12x34", "test", pdu, sizeof(pdu)) >= 0) {
fprintf(stderr, "invalid destination was accepted\n");
failed = 1;
}
if (pdu_encode("", "+1234", "\xF0\x9F\x98\x80", pdu,
sizeof(pdu)) >= 0) {
fprintf(stderr, "non-UCS-2 code point was accepted\n");
failed = 1;
}
for (int i = 0; i < 71; ++i) {
long_ucs2[2 * i] = (char)0xC2;
long_ucs2[2 * i + 1] = (char)0xA3;
}
long_ucs2[sizeof(long_ucs2) - 1] = '\0';
if (pdu_encode("", "+1234", long_ucs2, pdu, sizeof(pdu)) >= 0) {
fprintf(stderr, "overlong UCS-2 message was accepted\n");
failed = 1;
}

return failed;
}
62 changes: 42 additions & 20 deletions sms_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -194,8 +194,6 @@ int main(int argc, char* argv[])
{
if(argc < 3)
usage();
if(strlen(argv[2]) > 160)
fprintf(stderr,"sms message too long: '%s'\n", argv[2]);
}else if (!strcmp("delete",argv[0]))
{
if(argc < 2)
Expand All @@ -222,20 +220,26 @@ int main(int argc, char* argv[])
// open the port

port = open(dev, O_RDWR|O_NONBLOCK|O_NOCTTY);
if (port < 0)
fprintf(stderr,"open(%s)\n", dev);
if (port < 0) {
fprintf(stderr, "open(%s): %s\n", dev, strerror(errno));
return 1;
}
setserial(baudrate);
atexit(resetserial);

close(port);
port = open(dev, O_RDWR|O_NOCTTY);
if (port < 0)
fprintf(stderr,"reopen(%s)\n", dev);
if (port < 0) {
fprintf(stderr, "reopen(%s): %s\n", dev, strerror(errno));
return 1;
}

FILE* pf = fdopen(port, "w");
FILE* pfi = fdopen(port, "r");
if (!pf || ! pfi)
fprintf(stderr,"open port failed\n");
if (!pf || !pfi) {
fprintf(stderr, "fdopen(%s): %s\n", dev, strerror(errno));
return 1;
}
if(setvbuf(pf, NULL, _IOLBF, 0))
{
fprintf(stderr, "failed to make serial port linebuffered\n");
Expand All @@ -245,8 +249,11 @@ int main(int argc, char* argv[])
if (!strcmp("send", argv[0]))
{
int pdu_len = pdu_encode("", argv[1], argv[2], pdu, sizeof(pdu));
if (pdu_len < 0)
fprintf(stderr,"error encoding to PDU: %s \"%s\n", argv[1], argv[2]);
if (pdu_len < 0) {
fprintf(stderr, "error encoding to PDU: %s \"%s\"\n",
argv[1], argv[2]);
return 1;
}

const int pdu_len_except_smsc = pdu_len - 1 - pdu[0];
snprintf(cmdstr, sizeof(cmdstr), "AT+CMGS=%d\r\n", pdu_len_except_smsc);
Expand All @@ -256,36 +263,51 @@ int main(int argc, char* argv[])
sprintf(pdustr+2*i, "%02X", pdu[i]);
sprintf(pdustr+2*i, "%c\r\n", 0x1A); // End PDU mode with Ctrl-Z.

fputs("AT+CMGF=0\r\n", pf);
alarm(30);
if (fputs("AT+CMGF=0\r\n", pf) == EOF)
return 1;
int pdu_mode_ready = 0;
while(fgets(buf, sizeof(buf), pfi)) {
if(starts_with("OK", buf))
if(starts_with("OK", buf)) {
pdu_mode_ready = 1;
break;
}
if(starts_with("ERROR", buf) || starts_with("+CMS ERROR:", buf)) {
fprintf(stderr, "failed to enable PDU mode: %s", buf);
return 1;
}
}
fputs(cmdstr, pf);
if (!pdu_mode_ready) {
fprintf(stderr, "no response while enabling PDU mode\n");
return 1;
}
if (fputs(cmdstr, pf) == EOF)
return 1;
sleep(1);
fputs(pdustr, pf);
if (fputs(pdustr, pf) == EOF)
return 1;

alarm(5);
errno = 0;

while(fgets(buf, sizeof(buf), pfi))
{
if(starts_with("+CMGS:", buf))
{
printf("sms sent sucessfully: %s", buf + 7);
alarm(0);
printf("sms sent successfully: %s", buf + 7);
return 0;
} else if(starts_with("+CMS ERROR:", buf))
{
fprintf(stderr,"sms not sent, code: %s\n", buf + 11);
return 1;
} else if(starts_with("ERROR", buf))
{
fprintf(stderr,"sms not sent, command error\n");
} else if(starts_with("OK", buf))
{
return 0;
return 1;
}
}
fprintf(stderr,"reading port\n");
fprintf(stderr, "reading port: %s\n", strerror(errno));
return 1;
}

if (!strcmp("recv", argv[0]))
Expand Down