uqmi: print radio interfaces in serving system command
[project/uqmi.git] / commands-wms.c
1 /*
2 * uqmi -- tiny QMI support implementation
3 *
4 * Copyright (C) 2014-2015 Felix Fietkau <nbd@openwrt.org>
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the
18 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 * Boston, MA 02110-1301 USA.
20 */
21
22 #include "qmi-message.h"
23
24 #define MIN(a,b) (((a)<(b))?(a):(b))
25 #define CEILDIV(x,y) (((x) + (y) - 1) / (y))
26
27 static void cmd_wms_list_messages_cb(struct qmi_dev *qmi, struct qmi_request *req, struct qmi_msg *msg)
28 {
29 struct qmi_wms_list_messages_response res;
30 void *c;
31 int i;
32
33 qmi_parse_wms_list_messages_response(msg, &res);
34 c = blobmsg_open_array(&status, NULL);
35 for (i = 0; i < res.data.message_list_n; i++)
36 blobmsg_add_u32(&status, NULL, res.data.message_list[i].memory_index);
37
38 blobmsg_close_array(&status, c);
39 }
40
41 static enum qmi_cmd_result
42 cmd_wms_list_messages_prepare(struct qmi_dev *qmi, struct qmi_request *req, struct qmi_msg *msg, char *arg)
43 {
44 static struct qmi_wms_list_messages_request mreq = {
45 QMI_INIT(storage_type, QMI_WMS_STORAGE_TYPE_UIM),
46 QMI_INIT(message_tag, QMI_WMS_MESSAGE_TAG_TYPE_MT_NOT_READ),
47 };
48
49 qmi_set_wms_list_messages_request(msg, &mreq);
50
51 return QMI_CMD_REQUEST;
52 }
53
54 static int
55 put_unicode_char(char *dest, uint16_t c)
56 {
57 if (c < 0x80) {
58 *dest = c;
59 return 1;
60 } else if (c < 0x800) {
61 *(dest++) = 0xc0 | ((c >> 6) & 0x1f);
62 *dest = 0x80 | (c & 0x3f);
63 return 2;
64 } else {
65 *(dest++) = 0xe0 | ((c >> 12) & 0xf);
66 *(dest++) = 0x80 | ((c >> 6) & 0x3f);
67 *dest = 0x80 | (c & 0x3f);
68 return 3;
69 }
70 }
71
72
73 static int
74 pdu_decode_7bit_char(char *dest, int len, unsigned char c, bool *escape)
75 {
76 uint16_t conv_0x20[] = {
77 0x0040, 0x00A3, 0x0024, 0x00A5, 0x00E8, 0x00E9, 0x00F9, 0x00EC,
78 0x00F2, 0x00E7, 0x000A, 0x00D8, 0x00F8, 0x000D, 0x00C5, 0x00E5,
79 0x0394, 0x005F, 0x03A6, 0x0393, 0x039B, 0x03A9, 0x03A0, 0x03A8,
80 0x03A3, 0x0398, 0x039E, 0x00A0, 0x00C6, 0x00E6, 0x00DF, 0x00C9,
81 };
82 uint16_t conv_0x5b[] = {
83 0x00C4, 0x00D6, 0x00D1, 0x00DC, 0x00A7, 0x00BF,
84 };
85 uint16_t conv_0x7b[] = {
86 0x00E4, 0x00F6, 0x00F1, 0x00FC, 0x00E0
87 };
88 int cur_len = 0;
89 uint16_t outc;
90
91 fprintf(stderr, " %02x", c);
92 dest += len;
93 if (*escape) {
94 *escape = false;
95 switch(c) {
96 case 0x0A:
97 *dest = 0x0C;
98 return 1;
99 case 0x14:
100 *dest = 0x5E;
101 return 1;
102 case 0x28:
103 *dest = 0x7B;
104 return 1;
105 case 0x29:
106 *dest = 0x7D;
107 return 1;
108 case 0x2F:
109 *dest = 0x5C;
110 return 1;
111 case 0x3C:
112 *dest = 0x5B;
113 return 1;
114 case 0x3D:
115 *dest = 0x7E;
116 return 1;
117 case 0x3E:
118 *dest = 0x5D;
119 return 1;
120 case 0x40:
121 *dest = 0x7C;
122 return 1;
123 case 0x65:
124 outc = 0x20AC;
125 goto out;
126 case 0x1B:
127 goto normal;
128 default:
129 /* invalid */
130 *(dest++) = conv_0x20[0x1B];
131 cur_len++;
132 goto normal;
133 }
134 }
135
136 if (c == 0x1b) {
137 *escape = true;
138 return 0;
139 }
140
141 normal:
142 if (c < 0x20)
143 outc = conv_0x20[(int) c];
144 else if (c == 0x40)
145 outc = 0x00A1;
146 else if (c >= 0x5b && c <= 0x60)
147 outc = conv_0x5b[c - 0x5b];
148 else if (c >= 0x7b && c <= 0x7f)
149 outc = conv_0x7b[c - 0x7b];
150 else
151 outc = c;
152
153 out:
154 return cur_len + put_unicode_char(dest, outc);
155 }
156
157 static int
158 pdu_decode_7bit_str(char *dest, const unsigned char *data, int data_len, int bit_offset)
159 {
160 bool escape = false;
161 int len = 0;
162 int i;
163
164 fprintf(stderr, "Raw text:");
165 for (i = 0; i < data_len; i++) {
166 int pos = (i + bit_offset) % 7;
167
168 if (pos == 0) {
169 len += pdu_decode_7bit_char(dest, len, data[i] & 0x7f, &escape);
170 } else {
171 if (i)
172 len += pdu_decode_7bit_char(dest, len,
173 (data[i - 1] >> (7 + 1 - pos)) |
174 ((data[i] << pos) & 0x7f), &escape);
175
176 if (pos == 6)
177 len += pdu_decode_7bit_char(dest, len, (data[i] >> 1) & 0x7f,
178 &escape);
179 }
180 }
181 dest[len] = 0;
182 fprintf(stderr, "\n");
183 return len;
184 }
185
186 static int decode_udh(const unsigned char *data)
187 {
188 const unsigned char *end;
189 unsigned int type, len, udh_len;
190
191 udh_len = *(data++);
192 end = data + udh_len;
193 while (data < end) {
194 const unsigned char *val;
195
196 type = data[0];
197 len = data[1];
198 val = &data[2];
199 data += 2 + len;
200 if (data > end)
201 break;
202
203 switch (type) {
204 case 0x00:
205 blobmsg_add_u32(&status, "concat_ref", (uint32_t) val[0]);
206 blobmsg_add_u32(&status, "concat_part", (uint32_t) val[2]);
207 blobmsg_add_u32(&status, "concat_parts", (uint32_t) val[1]);
208 break;
209 case 0x08:
210 blobmsg_add_u32(&status, "concat_ref", (uint32_t) (val[0] << 8 | val[1]));
211 blobmsg_add_u32(&status, "concat_part", (uint32_t) val[3]);
212 blobmsg_add_u32(&status, "concat_parts", (uint32_t) val[2]);
213 break;
214 default:
215 break;
216 }
217 }
218
219 return udh_len + 1;
220 }
221
222 static void decode_7bit_field(char *name, const unsigned char *data, int data_len, int bit_offset)
223 {
224 char *dest = blobmsg_alloc_string_buffer(&status, name, 3 * data_len + 2);
225 pdu_decode_7bit_str(dest, data, CEILDIV(data_len * 7, 8), bit_offset);
226 dest[data_len] = 0;
227 blobmsg_add_string_buffer(&status);
228 }
229
230 static char *pdu_add_semioctet(char *str, char val)
231 {
232 *str = '0' + (val & 0xf);
233 if (*str <= '9')
234 str++;
235
236 *str = '0' + ((val >> 4) & 0xf);
237 if (*str <= '9')
238 str++;
239
240 return str;
241 }
242
243 static void
244 pdu_decode_address(char *str, unsigned char *data, int len)
245 {
246 unsigned char toa;
247
248 toa = *(data++);
249 switch (toa & 0x70) {
250 case 0x50:
251 pdu_decode_7bit_str(str, data, len, 0);
252 return;
253 case 0x10:
254 *(str++) = '+';
255 /* fall through */
256 default:
257 while (len--) {
258 str = pdu_add_semioctet(str, *data);
259 data++;
260 }
261 }
262
263 *str = 0;
264 }
265
266 static void wms_decode_address(char *name, unsigned char *data, int len)
267 {
268 char *str = blobmsg_alloc_string_buffer(&status, name, len * 2 + 2);
269 pdu_decode_address(str, data, len);
270 blobmsg_add_string_buffer(&status);
271 }
272
273 static void blobmsg_add_hex(struct blob_buf *buf, const char *name, unsigned const char *data, int len)
274 {
275 char* str = blobmsg_alloc_string_buffer(buf, name, len * 2 + 1);
276 for (int i = 0; i < len; i++) {
277 str += sprintf(str, "%02x", data[i]);
278 }
279 blobmsg_add_string_buffer(buf);
280 }
281
282 #define cmd_wms_delete_message_cb no_cb
283 static enum qmi_cmd_result
284 cmd_wms_delete_message_prepare(struct qmi_dev *qmi, struct qmi_request *req, struct qmi_msg *msg, char *arg)
285 {
286 char *err;
287 int id;
288
289 id = strtoul(arg, &err, 10);
290 if (err && *err) {
291 uqmi_add_error("Invalid message ID");
292 return QMI_CMD_EXIT;
293 }
294
295 static struct qmi_wms_delete_request mreq = {
296 QMI_INIT(memory_storage, QMI_WMS_STORAGE_TYPE_UIM),
297 QMI_INIT(message_mode, QMI_WMS_MESSAGE_MODE_GSM_WCDMA),
298 };
299
300 mreq.set.memory_index = 1;
301 mreq.data.memory_index = id;
302
303 qmi_set_wms_delete_request(msg, &mreq);
304
305 return QMI_CMD_REQUEST;
306 }
307
308
309 static void cmd_wms_get_message_cb(struct qmi_dev *qmi, struct qmi_request *req, struct qmi_msg *msg)
310 {
311 struct qmi_wms_raw_read_response res;
312 unsigned char *data, *end;
313 char *str;
314 int cur_len;
315 bool sent;
316 unsigned char first, dcs;
317 void *c;
318
319 qmi_parse_wms_raw_read_response(msg, &res);
320 c = blobmsg_open_table(&status, NULL);
321 data = (unsigned char *) res.data.raw_message_data.raw_data;
322 end = data + res.data.raw_message_data.raw_data_n;
323
324 cur_len = *(data++);
325 if (data + cur_len >= end)
326 goto error;
327
328 if (cur_len) {
329 wms_decode_address("smsc", data, cur_len - 1);
330 data += cur_len;
331 }
332
333 if (data + 3 >= end)
334 goto error;
335
336 first = *(data++);
337 sent = (first & 0x3) == 1;
338 if (sent)
339 data++;
340
341 cur_len = *(data++);
342 if (data + cur_len >= end)
343 goto error;
344
345 if (cur_len) {
346 cur_len = (cur_len + 1) / 2;
347 wms_decode_address(sent ? "receiver" : "sender", data, cur_len);
348 data += cur_len + 1;
349 }
350
351 if (data + 3 >= end)
352 goto error;
353
354 /* Protocol ID */
355 if (*(data++) != 0)
356 goto error;
357
358 /* Data Encoding */
359 dcs = *(data++);
360
361 if (dcs & 0x10)
362 blobmsg_add_u32(&status, "class", (dcs & 3));
363
364 if (sent) {
365 /* Message validity */
366 data++;
367 } else {
368 if (data + 6 >= end)
369 goto error;
370
371 str = blobmsg_alloc_string_buffer(&status, "timestamp", 32);
372
373 /* year */
374 *(str++) = '2';
375 *(str++) = '0';
376 str = pdu_add_semioctet(str, data[0]);
377 /* month */
378 *(str++) = '-';
379 str = pdu_add_semioctet(str, data[1]);
380 /* day */
381 *(str++) = '-';
382 str = pdu_add_semioctet(str, data[2]);
383
384 /* hour */
385 *(str++) = ' ';
386 str = pdu_add_semioctet(str, data[3]);
387 /* minute */
388 *(str++) = ':';
389 str = pdu_add_semioctet(str, data[4]);
390 /* second */
391 *(str++) = ':';
392 str = pdu_add_semioctet(str, data[5]);
393 *str = 0;
394
395 blobmsg_add_string_buffer(&status);
396
397 data += 7;
398 }
399
400 int message_len = *(data++);
401 int udh_len = 0;
402 int bit_offset = 0;
403
404 /* User Data Header */
405 if (first & 0x40) {
406 udh_len = decode_udh(data);
407 data += udh_len;
408 bit_offset = udh_len % 7;
409 }
410
411 if (data >= end)
412 goto error;
413
414 switch(dcs & 0x0c) {
415 case 0x00:
416 /* 7 bit GSM alphabet */
417 message_len = message_len - CEILDIV(udh_len * 8, 7);
418 message_len = MIN(message_len, CEILDIV((end - data) * 8, 7));
419 decode_7bit_field("text", data, message_len, bit_offset);
420 break;
421 case 0x04:
422 /* 8 bit data */
423 message_len = MIN(message_len - udh_len, end - data);
424 blobmsg_add_hex(&status, "data", data, message_len);
425 break;
426 case 0x08:
427 /* 16 bit UCS-2 string */
428 message_len = MIN(message_len - udh_len, end - data);
429 blobmsg_add_hex(&status, "ucs-2", data, message_len);
430 break;
431 default:
432 goto error;
433 }
434
435 blobmsg_close_table(&status, c);
436 return;
437
438 error:
439 blobmsg_close_table(&status, c);
440 fprintf(stderr, "There was an error reading message.\n");
441 }
442
443 static enum qmi_cmd_result
444 cmd_wms_get_message_prepare(struct qmi_dev *qmi, struct qmi_request *req, struct qmi_msg *msg, char *arg)
445 {
446 static struct qmi_wms_raw_read_request mreq = {
447 QMI_INIT_SEQUENCE(message_memory_storage_id,
448 .storage_type = QMI_WMS_STORAGE_TYPE_UIM,
449 ),
450 QMI_INIT(message_mode, QMI_WMS_MESSAGE_MODE_GSM_WCDMA),
451 };
452 char *err;
453 int id;
454
455 id = strtoul(arg, &err, 10);
456 if (err && *err) {
457 uqmi_add_error("Invalid message ID");
458 return QMI_CMD_EXIT;
459 }
460
461 mreq.data.message_memory_storage_id.memory_index = id;
462 qmi_set_wms_raw_read_request(msg, &mreq);
463
464 return QMI_CMD_REQUEST;
465 }
466
467
468 static void cmd_wms_get_raw_message_cb(struct qmi_dev *qmi, struct qmi_request *req, struct qmi_msg *msg)
469 {
470 struct qmi_wms_raw_read_response res;
471 unsigned char *data;
472 char *str;
473 int i;
474
475 qmi_parse_wms_raw_read_response(msg, &res);
476 data = (unsigned char *) res.data.raw_message_data.raw_data;
477 str = blobmsg_alloc_string_buffer(&status, NULL, res.data.raw_message_data.raw_data_n * 3);
478 for (i = 0; i < res.data.raw_message_data.raw_data_n; i++) {
479 str += sprintf(str, &" %02x"[i ? 0 : 1], data[i]);
480 }
481 blobmsg_add_string_buffer(&status);
482 }
483
484 #define cmd_wms_get_raw_message_prepare cmd_wms_get_message_prepare
485
486
487 static struct {
488 const char *smsc;
489 const char *target;
490 bool flash;
491 } _send;
492
493
494 #define cmd_wms_send_message_smsc_cb no_cb
495 static enum qmi_cmd_result
496 cmd_wms_send_message_smsc_prepare(struct qmi_dev *qmi, struct qmi_request *req, struct qmi_msg *msg, char *arg)
497 {
498 _send.smsc = arg;
499 return QMI_CMD_DONE;
500 }
501
502 #define cmd_wms_send_message_target_cb no_cb
503 static enum qmi_cmd_result
504 cmd_wms_send_message_target_prepare(struct qmi_dev *qmi, struct qmi_request *req, struct qmi_msg *msg, char *arg)
505 {
506 _send.target = arg;
507 return QMI_CMD_DONE;
508 }
509
510 #define cmd_wms_send_message_flash_cb no_cb
511 static enum qmi_cmd_result
512 cmd_wms_send_message_flash_prepare(struct qmi_dev *qmi, struct qmi_request *req, struct qmi_msg *msg, char *arg)
513 {
514 _send.flash = true;
515 return QMI_CMD_DONE;
516 }
517
518 static int
519 pdu_encode_semioctet(unsigned char *dest, const char *str)
520 {
521 int len = 0;
522 bool lower = true;
523
524 while (*str) {
525 char digit = *str - '0';
526
527 if (lower)
528 dest[len] = 0xf0 | digit;
529 else
530 dest[len++] &= (digit << 4) | 0xf;
531
532 lower = !lower;
533 str++;
534 }
535
536 return lower ? len : (len + 1);
537 }
538
539 static int
540 pdu_encode_7bit_str(unsigned char *data, const char *str)
541 {
542 unsigned char c;
543 int len = 0;
544 int ofs = 0;
545
546 while(1) {
547 c = *(str++) & 0x7f;
548 if (!c)
549 break;
550
551 switch(ofs) {
552 case 0:
553 data[len] = c;
554 break;
555 default:
556 data[len++] |= c << (8 - ofs);
557 data[len] = c >> ofs;
558 break;
559 }
560
561 ofs = (ofs + 1) % 8;
562 }
563
564 return len + 1;
565 }
566
567 static int
568 pdu_encode_number(unsigned char *dest, const char *str, bool smsc)
569 {
570 unsigned char format;
571 bool ascii = false;
572 int len = 0;
573 int i;
574
575 dest[len++] = 0;
576 if (*str == '+') {
577 str++;
578 format = 0x91;
579 } else {
580 format = 0x81;
581 }
582
583 for (i = 0; str[i]; i++) {
584 if (str[i] >= '0' && str[i] <= '9')
585 continue;
586
587 ascii = true;
588 break;
589 }
590
591 if (ascii)
592 format |= 0x40;
593
594 dest[len++] = format;
595 if (!ascii)
596 len += pdu_encode_semioctet(&dest[len], str);
597 else
598 len += pdu_encode_7bit_str(&dest[len], str);
599
600 if (smsc)
601 dest[0] = len - 1;
602 else
603 dest[0] = strlen(str);
604
605 return len;
606 }
607
608 static int
609 pdu_encode_data(unsigned char *dest, const char *str)
610 {
611 int len = 0;
612
613 dest[len++] = 0;
614 len += pdu_encode_7bit_str(&dest[len], str);
615 dest[0] = strlen(str);
616
617 return len;
618 }
619
620 #define cmd_wms_send_message_cb no_cb
621 static enum qmi_cmd_result
622 cmd_wms_send_message_prepare(struct qmi_dev *qmi, struct qmi_request *req, struct qmi_msg *msg, char *arg)
623 {
624 static unsigned char buf[512];
625 static struct qmi_wms_raw_send_request mreq = {
626 QMI_INIT_SEQUENCE(raw_message_data,
627 .format = QMI_WMS_MESSAGE_FORMAT_GSM_WCDMA_POINT_TO_POINT,
628 .raw_data = buf,
629 ),
630 };
631 unsigned char *cur = buf;
632 unsigned char first_octet = 0x11;
633 unsigned char protocol_id = 0x00;
634 unsigned char dcs = 0x00;
635
636 if (!_send.target || !*_send.target) {
637 uqmi_add_error("Missing argument");
638 return QMI_CMD_EXIT;
639 }
640
641 if ((_send.smsc && strlen(_send.smsc) > 16) || strlen(_send.target) > 16 || strlen(arg) > 160) {
642 uqmi_add_error("Argument too long");
643 return QMI_CMD_EXIT;
644 }
645
646 if (_send.flash)
647 dcs |= 0x10;
648
649 if (!_send.smsc || !*_send.smsc)
650 *(cur++) = 0;
651 else
652 cur += pdu_encode_number(cur, _send.smsc, true);
653
654 *(cur++) = first_octet;
655 *(cur++) = 0; /* reference */
656
657 cur += pdu_encode_number(cur, _send.target, false);
658 *(cur++) = protocol_id;
659 *(cur++) = dcs;
660
661 *(cur++) = 0xff; /* validity */
662 cur += pdu_encode_data(cur, arg);
663
664 mreq.data.raw_message_data.raw_data_n = cur - buf;
665 qmi_set_wms_raw_send_request(msg, &mreq);
666
667 return QMI_CMD_REQUEST;
668 }