diff --git a/README.md b/README.md index a2ea468..fd884be 100644 --- a/README.md +++ b/README.md @@ -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: ---------------- diff --git a/pdu_lib/Makefile b/pdu_lib/Makefile index 9564987..d04df60 100644 --- a/pdu_lib/Makefile +++ b/pdu_lib/Makefile @@ -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 diff --git a/pdu_lib/pdu.c b/pdu_lib/pdu.c index d1d282b..fa369a8 100644 --- a/pdu_lib/pdu.c +++ b/pdu_lib/pdu.c @@ -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). @@ -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; @@ -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) { @@ -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) @@ -282,7 +344,7 @@ 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; @@ -290,10 +352,15 @@ pdu_encode(const char* service_center_number, const char* phone_number, const ch // 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. } @@ -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) @@ -445,4 +543,3 @@ int pdu_decode(const unsigned char* buffer, int buffer_length, return output_sms_text_length; } - diff --git a/pdu_lib/pdu_test.c b/pdu_lib/pdu_test.c new file mode 100644 index 0000000..6078999 --- /dev/null +++ b/pdu_lib/pdu_test.c @@ -0,0 +1,64 @@ +#include "pdu.h" + +#include +#include + +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; +} diff --git a/sms_main.c b/sms_main.c index 0fdd7c6..d8155a5 100644 --- a/sms_main.c +++ b/sms_main.c @@ -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) @@ -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"); @@ -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); @@ -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]))