From 291e61346bb55d0462c07bf078cab3bb7eec2241 Mon Sep 17 00:00:00 2001 From: Dennis Wehrle Date: Sun, 24 Jul 2011 20:14:13 +0200 Subject: sms: SMS where cropped (from VTY), concatenation of SMS where not possible Additionally it wasn't possible to send concatenated sms from the vty. To send multiple sms, it is necessary to use padding bits and add a user_data_header. Therefore the gsm_7bit_encode function was splitted to gsm_7bit_encode and gsm_septets2octets. gsm_septets2octets: this is the old gsm_7bit_encode function + additional padding parameter Additionally the gsm_7bit_decode function was modified to take account for the user_data_header. With the new gsm_get_octet_len function you can get the octet length for a given septet length. I also added several sms tests. --- include/osmocom/gsm/gsm_utils.h | 5 + src/gsm/gsm_utils.c | 118 +++++++++++++++------ tests/sms/sms_test.c | 226 +++++++++++++++++++++++++++++++++++++--- 3 files changed, 306 insertions(+), 43 deletions(-) diff --git a/include/osmocom/gsm/gsm_utils.h b/include/osmocom/gsm/gsm_utils.h index 707047ae..c9a31016 100644 --- a/include/osmocom/gsm/gsm_utils.h +++ b/include/osmocom/gsm/gsm_utils.h @@ -57,8 +57,13 @@ const char *gsm_band_name(enum gsm_band band); enum gsm_band gsm_band_parse(const char *mhz); int gsm_7bit_decode(char *decoded, const uint8_t *user_data, uint8_t length); +int gsm_7bit_decode_hdr(char *decoded, const uint8_t *user_data, uint8_t length, uint8_t ud_hdr_ind); int gsm_7bit_encode(uint8_t *result, const char *data); +int gsm_septets2octets(uint8_t *result, uint8_t *rdata, uint8_t septet_len, uint8_t padding); +int gsm_septet_encode(uint8_t *result, const char *data); +uint8_t gsm_get_octet_len(const uint8_t sept_len); + unsigned int ms_class_gmsk_dbm(enum gsm_band band, int ms_class); int ms_pwr_ctl_lvl(enum gsm_band band, unsigned int dbm); diff --git a/src/gsm/gsm_utils.c b/src/gsm/gsm_utils.c index 5da713c2..6b9cadc4 100644 --- a/src/gsm/gsm_utils.c +++ b/src/gsm/gsm_utils.c @@ -73,28 +73,44 @@ static int gsm_septet_lookup(uint8_t ch) return -1; } +/* Compute the number of octets from the number of septets, for instance: 47 septets needs 41,125 = 42 octets */ +uint8_t gsm_get_octet_len(const uint8_t sept_len){ + int octet_len = (sept_len * 7) / 8; + if ((sept_len * 7) % 8 != 0) + octet_len++; + + return octet_len; +} + /* GSM 03.38 6.2.1 Character unpacking */ -int gsm_7bit_decode(char *text, const uint8_t *user_data, uint8_t length) +int gsm_7bit_decode_hdr(char *text, const uint8_t *user_data, uint8_t septet_l, uint8_t ud_hdr_ind) { int i = 0; - int l = 0; - int septet_l = (length * 8) / 7; + int shift = 0; + uint8_t *rtext = calloc(septet_l, sizeof(uint8_t)); uint8_t tmp; - /* FIXME: We need to account for user data headers here */ - i += l; - for (; i < septet_l; i++){ + /* skip the user data header */ + if (ud_hdr_ind) { + /* get user data header length + 1 (for the 'user data header length'-field) */ + shift = ((user_data[0] + 1) * 8) / 7; + if ((((user_data[0] + 1) * 8) % 7) != 0) + shift++; + septet_l = septet_l - shift; + } + + for (i = 0; i < septet_l; i++) { rtext[i] = - ((user_data[(i * 7 + 7) >> 3] << - (7 - ((i * 7 + 7) & 7))) | - (user_data[(i * 7) >> 3] >> - ((i * 7) & 7))) & 0x7f; + ((user_data[((i + shift) * 7 + 7) >> 3] << + (7 - (((i + shift) * 7 + 7) & 7))) | + (user_data[((i + shift) * 7) >> 3] >> + (((i + shift) * 7) & 7))) & 0x7f; } for(i = 0; i < septet_l; i++){ /* this is an extension character */ - if(rtext[i] == 0x1b && i + 1 < length){ + if(rtext[i] == 0x1b && i + 1 < septet_l){ tmp = rtext[i+1]; *(text++) = gsm_7bit_alphabet[0x7f + tmp]; i++; @@ -104,14 +120,21 @@ int gsm_7bit_decode(char *text, const uint8_t *user_data, uint8_t length) *(text++) = gsm_septet_lookup(rtext[i]); } + if (ud_hdr_ind) + i += shift; *text = '\0'; free(rtext); return i; } +int gsm_7bit_decode(char *text, const uint8_t *user_data, uint8_t septet_l) +{ + return gsm_7bit_decode_hdr(text, user_data, septet_l, 0); +} + /* GSM 03.38 6.2.1 Prepare character packing */ -static int gsm_septet_encode(uint8_t *result, const char *data) +int gsm_septet_encode(uint8_t *result, const char *data) { int i, y = 0; uint8_t ch; @@ -139,38 +162,75 @@ static int gsm_septet_encode(uint8_t *result, const char *data) return y; } -/* GSM 03.38 6.2.1 Character packing */ -int gsm_7bit_encode(uint8_t *result, const char *data) -{ - int i,y,z = 0; - /* prepare for the worst case, every character expanding to two bytes */ - uint8_t *rdata = calloc(strlen(data) * 2, sizeof(uint8_t)); +/* 7bit to octet packing */ +int gsm_septets2octets(uint8_t *result, uint8_t *rdata, uint8_t septet_len, uint8_t padding){ + int i = 0, z = 0; uint8_t cb, nb; int shift = 0; + uint8_t *data = calloc(septet_len + 1, sizeof(uint8_t)); - y = gsm_septet_encode(rdata, data); - - for(i = 0; i < y; i++) { - if(shift == 7 && i + 1 < y){ - shift = 0; - continue; + if (padding) { + shift = 7 - padding; + /* the first zero is needed for padding */ + memcpy(data + 1, rdata, septet_len); + septet_len++; + } else + memcpy(data, rdata, septet_len); + + for(i = 0; i < septet_len; i++) { + if (shift == 7) { + /* + * special end case with the. This is necessary if the + * last septet fits into the previous octet. E.g. 48 + * non-extension characters: + * ....ag ( a = 1100001, g = 1100111) + * result[40] = 100001 XX, result[41] = 1100111 1 */ + if (i + 1 < septet_len) { + shift = 0; + continue; + } else if (i + 1 == septet_len) + break; } - cb = (rdata[i] & 0x7f) >> shift; - if(i + 1 < y){ - nb = (rdata[i + 1] & 0x7f) << (7 - shift); + cb = (data[i] & 0x7f) >> shift; + if (i + 1 < septet_len) { + nb = (data[i + 1] & 0x7f) << (7 - shift); cb = cb | nb; } result[z++] = cb; - shift++; } - free(rdata); + free(data); + return z; } +/* GSM 03.38 6.2.1 Character packing */ +int gsm_7bit_encode(uint8_t *result, const char *data) +{ + int y = 0, z = 0; + /* prepare for the worst case, every character expanding to two bytes */ + uint8_t *rdata = calloc(strlen(data) * 2, sizeof(uint8_t)); + y = gsm_septet_encode(rdata, data); + z = gsm_septets2octets(result, rdata, y, 0); + + free(rdata); + + /* + * We don't care about the number of octets (z), because they are not + * unique. E.g.: + * 1.) 46 non-extension characters + 1 extension character + * => (46 * 7 bit + (1 * (2 * 7 bit))) / 8 bit = 42 octets + * 2.) 47 non-extension characters + * => (47 * 7 bit) / 8 bit = 41,125 = 42 octets + * 3.) 48 non-extension characters + * => (48 * 7 bit) / 8 bit = 42 octects + */ + return y; +} + /* convert power class to dBm according to GSM TS 05.05 */ unsigned int ms_class_gmsk_dbm(enum gsm_band band, int class) { diff --git a/tests/sms/sms_test.c b/tests/sms/sms_test.c index b4ed631f..6df4b623 100644 --- a/tests/sms/sms_test.c +++ b/tests/sms/sms_test.c @@ -31,15 +31,19 @@ struct test_case { const uint16_t input_length; const uint8_t *expected; - const uint16_t expected_length; + const uint16_t expected_octet_length; + const uint16_t expected_septet_length; + const uint8_t ud_hdr_ind; }; static const char simple_text[] = "test text"; +#define simple_septet_length 9 static const uint8_t simple_enc[] = { 0xf4, 0xf2, 0x9c, 0x0e, 0xa2, 0x97, 0xf1, 0x74 }; -static const char escape_text[] = "!$ a more#^- complicated test@@?_\%! case"; +static const char escape_text[] = "!$ a more#^- complicated test@@?_%! case"; +#define escape_septet_length 41 /* note: the ^ counts as two, because it is a extension character */ static const uint8_t escape_enc[] = { 0x21, 0x01, 0x28, 0x0c, 0x6a, 0xbf, 0xe5, 0xe5, 0xd1, 0x86, 0xd2, 0x02, 0x8d, 0xdf, 0x6d, 0x38, 0x3b, 0x3d, @@ -47,17 +51,115 @@ static const uint8_t escape_enc[] = { 0x00, 0xbf, 0x48, 0x29, 0x04, 0x1a, 0x87, 0xe7, 0x65, }; +static const char enhanced_text[] = "enhanced ^ {][} test |+~ ^ test"; +#define enhanced_septet_length 39 /* note: the characters { } [ ] ^ | ~ count as two (each of them), because they are extension characters */ +static const uint8_t enhanced_enc[] = { + 0x65, 0x37, 0x3A, 0xEC, 0x1E, 0x97, 0xC9, 0xA0, 0x0D, + 0x05, 0xB4, 0x41, 0x6D, 0x7C, 0x1B, 0xDE, 0x26, 0x05, + 0xA2, 0x97, 0xE7, 0x74, 0xD0, 0x06, 0xB8, 0xDA, 0xF4, + 0x40, 0x1B, 0x0A, 0x88, 0x5E, 0x9E, 0xD3, 0x01, +}; + +static const char enhancedV2_text[] = "enhanced ^ {][} test |+~ ^ tests"; +#define enhancedV2_septet_length 40 /* note: number of octets are equal to the enhanced_text! */ +static const uint8_t enhancedV2_enc[] = { + 0x65, 0x37, 0x3A, 0xEC, 0x1E, 0x97, 0xC9, 0xA0, 0x0D, + 0x05, 0xB4, 0x41, 0x6D, 0x7C, 0x1B, 0xDE, 0x26, 0x05, + 0xA2, 0x97, 0xE7, 0x74, 0xD0, 0x06, 0xB8, 0xDA, 0xF4, + 0x40, 0x1B, 0x0A, 0x88, 0x5E, 0x9E, 0xD3, 0xE7, +}; + + + +static const char concatenated_text[] = + "this is a testmessage. this is a testmessage. this is a testmessage. this is a testmessage. " + "this is a testmessage. this is a testmessage. cut here .....: this is a second testmessage. end here."; + +static const char splitted_text_part1[] = + "this is a testmessage. this is a testmessage. this is a testmessage. this is a testmessage. " + "this is a testmessage. this is a testmessage. cut here .....:"; +#define concatenated_part1_septet_length_with_header 160 +#define concatenated_part1_septet_length 153 +static const uint8_t concatenated_part1_enc[] = { + 0x05, 0x00, 0x03, 0x6f, 0x02, 0x01, + 0xe8, 0xe8, 0xf4, 0x1c, 0x94, 0x9e, 0x83, 0xc2, + 0x20, 0x7a, 0x79, 0x4e, 0x6f, 0x97, 0xe7, 0xf3, + 0xf0, 0xb9, 0xec, 0x02, 0xd1, 0xd1, 0xe9, 0x39, + 0x28, 0x3d, 0x07, 0x85, 0x41, 0xf4, 0xf2, 0x9c, + 0xde, 0x2e, 0xcf, 0xe7, 0xe1, 0x73, 0xd9, 0x05, + 0xa2, 0xa3, 0xd3, 0x73, 0x50, 0x7a, 0x0e, 0x0a, + 0x83, 0xe8, 0xe5, 0x39, 0xbd, 0x5d, 0x9e, 0xcf, + 0xc3, 0xe7, 0xb2, 0x0b, 0x44, 0x47, 0xa7, 0xe7, + 0xa0, 0xf4, 0x1c, 0x14, 0x06, 0xd1, 0xcb, 0x73, + 0x7a, 0xbb, 0x3c, 0x9f, 0x87, 0xcf, 0x65, 0x17, + 0x88, 0x8e, 0x4e, 0xcf, 0x41, 0xe9, 0x39, 0x28, + 0x0c, 0xa2, 0x97, 0xe7, 0xf4, 0x76, 0x79, 0x3e, + 0x0f, 0x9f, 0xcb, 0x2e, 0x10, 0x1d, 0x9d, 0x9e, + 0x83, 0xd2, 0x73, 0x50, 0x18, 0x44, 0x2f, 0xcf, + 0xe9, 0xed, 0xf2, 0x7c, 0x1e, 0x3e, 0x97, 0x5d, + 0xa0, 0x71, 0x9d, 0x0e, 0x42, 0x97, 0xe5, 0x65, + 0x90, 0xcb, 0xe5, 0x72, 0xb9, 0x74, +}; + +static const char splitted_text_part2[] = " this is a second testmessage. end here."; +#define concatenated_part2_septet_length_with_header 47 +#define concatenated_part2_septet_length 40 +static const uint8_t concatenated_part2_enc[] = { + 0x05, 0x00, 0x03, 0x6f, 0x02, 0x02, + 0x40, 0x74, 0x74, 0x7a, 0x0e, 0x4a, 0xcf, 0x41, + 0x61, 0xd0, 0xbc, 0x3c, 0x7e, 0xbb, 0xc9, 0x20, + 0x7a, 0x79, 0x4e, 0x6f, 0x97, 0xe7, 0xf3, 0xf0, + 0xb9, 0xec, 0x02, 0x95, 0xdd, 0x64, 0x10, 0xba, + 0x2c, 0x2f, 0xbb, 0x00, +}; + +static const struct test_case test_multiple_encode[] = +{ + { + .input = concatenated_text, + .expected = concatenated_part1_enc, + .expected_octet_length = sizeof(concatenated_part1_enc), + .expected_septet_length = concatenated_part1_septet_length, + .ud_hdr_ind = 1, + }, + { + .input = concatenated_text, + .expected = concatenated_part2_enc, + .expected_octet_length = sizeof(concatenated_part2_enc), + .expected_septet_length = concatenated_part2_septet_length, + .ud_hdr_ind = 1, + }, +}; + static const struct test_case test_encode[] = { { .input = simple_text, .expected = simple_enc, - .expected_length = sizeof(simple_enc), + .expected_octet_length = sizeof(simple_enc), + .expected_septet_length = simple_septet_length, + .ud_hdr_ind = 0, }, { .input = escape_text, .expected = escape_enc, - .expected_length = sizeof(escape_enc), + .expected_octet_length = sizeof(escape_enc), + .expected_septet_length = escape_septet_length, + .ud_hdr_ind = 0, + }, + { + .input = enhanced_text, + .expected = enhanced_enc, + .expected_octet_length = sizeof(enhanced_enc), + .expected_septet_length = enhanced_septet_length, + .ud_hdr_ind = 0, + }, + { + .input = enhancedV2_text, + .expected = enhancedV2_enc, + .expected_octet_length = sizeof(enhancedV2_enc), + .expected_septet_length = enhancedV2_septet_length, + .ud_hdr_ind = 0, }, }; @@ -67,11 +169,43 @@ static const struct test_case test_decode[] = .input = simple_enc, .input_length = sizeof(simple_enc), .expected = simple_text, + .expected_septet_length = simple_septet_length, + .ud_hdr_ind = 0, }, { .input = escape_enc, .input_length = sizeof(escape_enc), .expected = escape_text, + .expected_septet_length = escape_septet_length, + .ud_hdr_ind = 0, + }, + { + .input = enhanced_enc, + .input_length = sizeof(enhanced_enc), + .expected = enhanced_text, + .expected_septet_length = enhanced_septet_length, + .ud_hdr_ind = 0, + }, + { + .input = enhancedV2_enc, + .input_length = sizeof(enhancedV2_enc), + .expected = enhancedV2_text, + .expected_septet_length = enhancedV2_septet_length, + .ud_hdr_ind = 0, + }, + { + .input = concatenated_part1_enc, + .input_length = sizeof(concatenated_part1_enc), + .expected = splitted_text_part1, + .expected_septet_length = concatenated_part1_septet_length_with_header, + .ud_hdr_ind = 1, + }, + { + .input = concatenated_part2_enc, + .input_length = sizeof(concatenated_part2_enc), + .expected = splitted_text_part2, + .expected_septet_length = concatenated_part2_septet_length_with_header, + .ud_hdr_ind = 1, }, }; @@ -79,41 +213,105 @@ int main(int argc, char** argv) { printf("SMS testing\n"); struct msgb *msg; - uint8_t *sms; uint8_t i; - uint8_t length; + uint8_t octet_length; + uint8_t septet_length; + uint8_t gsm_septet_length; uint8_t coded[256]; + uint8_t tmp[160]; + uint8_t septet_data[256]; + uint8_t ud_header[6]; char result[256]; /* test 7-bit encoding */ for (i = 0; i < ARRAY_SIZE(test_encode); ++i) { memset(coded, 0x42, sizeof(coded)); - length = gsm_7bit_encode(coded, test_encode[i].input); + septet_length = gsm_7bit_encode(coded, test_encode[i].input); + octet_length = gsm_get_octet_len(septet_length); + if (octet_length != test_encode[i].expected_octet_length) { + fprintf(stderr, "Encode case %d: Octet length failure. Got %d, expected %d\n", + i, octet_length, test_encode[i].expected_octet_length); + return -1; + } - if (length != test_encode[i].expected_length) { - fprintf(stderr, "Failed to encode case %d. Got %d, expected %d\n", - i, length, test_encode[i].expected_length); + if (septet_length != test_encode[i].expected_septet_length){ + fprintf(stderr, "Encode case %d: Septet length failure. Got %d, expected %d\n", + i, septet_length, test_encode[i].expected_septet_length); return -1; } - if (memcmp(coded, test_encode[i].expected, length) != 0) { - fprintf(stderr, "Encoded content does not match for %d\n", + if (memcmp(coded, test_encode[i].expected, octet_length) != 0) { + fprintf(stderr, "Encoded content does not match for case %d\n", i); return -1; } } + + /* Test: encode multiple SMS */ + int number_of_septets = gsm_septet_encode(septet_data, test_multiple_encode[0].input); + + /* SMS part 1 */ + memset(tmp, 0x42, sizeof(tmp)); + memset(coded, 0x42, sizeof(coded)); + memcpy(tmp, septet_data, concatenated_part1_septet_length); + + /* In our case: test_multiple_decode[0].ud_hdr_ind equals number of padding bits*/ + octet_length = gsm_septets2octets(coded, tmp, concatenated_part1_septet_length, test_multiple_encode[0].ud_hdr_ind); + + /* copy header */ + memset(tmp, 0x42, sizeof(tmp)); + int udh_length = test_multiple_encode[0].expected[0] + 1; + memcpy(tmp, test_multiple_encode[0].expected, udh_length); + memcpy(tmp + udh_length, coded, octet_length); + memset(coded, 0x42, sizeof(coded)); + memcpy(coded, tmp, octet_length + 6); + + if (memcmp(coded, test_multiple_encode[0].expected, octet_length) != 0) { + fprintf(stderr, "Multiple-SMS encoded content does not match for part 1\n"); + return -1; + } + + + /* SMS part 2 */ + memset(tmp, 0x42, sizeof(tmp)); + memset(coded, 0x42, sizeof(coded)); + memcpy(tmp, septet_data + concatenated_part1_septet_length, concatenated_part2_septet_length); + + /* In our case: test_multiple_decode[1].ud_hdr_ind equals number of padding bits*/ + octet_length = gsm_septets2octets(coded, tmp, concatenated_part2_septet_length, test_multiple_encode[1].ud_hdr_ind); + + /* copy header */ + memset(tmp, 0x42, sizeof(tmp)); + udh_length = test_multiple_encode[1].expected[0] + 1; + memcpy(tmp, test_multiple_encode[1].expected, udh_length); + memcpy(tmp + udh_length, coded, octet_length); + memset(coded, 0x42, sizeof(coded)); + memcpy(coded, tmp, octet_length + 6); + + if (memcmp(coded, test_multiple_encode[1].expected, octet_length) != 0) { + fprintf(stderr, "Multiple-SMS encoded content does not match for part 2\n"); + return -1; + } + + + /* test 7-bit decoding */ for (i = 0; i < ARRAY_SIZE(test_decode); ++i) { memset(result, 0x42, sizeof(coded)); - gsm_7bit_decode(result, test_decode[i].input, - test_decode[i].input_length); + septet_length = gsm_7bit_decode_hdr(result, test_decode[i].input, + test_decode[i].expected_septet_length, test_decode[i].ud_hdr_ind); if (strcmp(result, test_decode[i].expected) != 0) { fprintf(stderr, "Test case %d failed to decode.\n", i); return -1; } + if (septet_length != test_decode[i].expected_septet_length) { + fprintf(stderr, "Decode case %d: Septet length failure. Got %d, expected %d\n", + i, septet_length, test_decode[i].expected_septet_length); + return -1; + } } printf("OK\n"); -- cgit v1.2.3