| /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
| |
| /* |
| * Copyright (c) 2012 The Chromium OS Authors. All rights reserved. |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file of the libqmi library. |
| */ |
| |
| /* |
| * libqmi-glib -- GLib/GIO based library to control QMI devices |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, write to the |
| * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| * Boston, MA 02110-1301 USA. |
| * |
| * Copyright (C) 2012-2019 Aleksander Morgado <aleksander@aleksander.es> |
| * Copyright (c) 2022 Qualcomm Innovation Center, Inc. |
| */ |
| |
| #include <glib.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <endian.h> |
| |
| #include "qmi-common.h" |
| #include "qmi-message.h" |
| #include "qmi-helpers.h" |
| #include "qmi-enums-private.h" |
| #include "qmi-enum-types-private.h" |
| #include "qmi-flag-types-private.h" |
| #include "qmi-enum-types.h" |
| #include "qmi-flag-types.h" |
| #include "qmi-error-types.h" |
| |
| #include "qmi-ctl.h" |
| #include "qmi-dms.h" |
| #include "qmi-wds.h" |
| #include "qmi-nas.h" |
| #include "qmi-wms.h" |
| #include "qmi-pdc.h" |
| #include "qmi-pds.h" |
| #include "qmi-pbm.h" |
| #include "qmi-uim.h" |
| #include "qmi-oma.h" |
| #include "qmi-wda.h" |
| #include "qmi-voice.h" |
| #include "qmi-loc.h" |
| #include "qmi-qos.h" |
| #include "qmi-gas.h" |
| #include "qmi-gms.h" |
| #include "qmi-dsd.h" |
| #include "qmi-sar.h" |
| #include "qmi-dpm.h" |
| #include "qmi-fox.h" |
| #include "qmi-atr.h" |
| #include "qmi-ssc.h" |
| |
| #define PACKED __attribute__((packed)) |
| |
| struct qmux_header { |
| guint16 length; |
| guint8 flags; |
| guint8 service; |
| guint8 client; |
| } PACKED; |
| |
| /* This is not a real header in QRTR messages, it is exclusively used within |
| * libqmi to flag messages that have 16-bit service id and would otherwise not |
| * fit in a standard QMUX message. */ |
| struct qrtr_header { |
| guint16 length; |
| guint16 service; |
| guint8 client; |
| } PACKED; |
| |
| struct control_header { |
| guint8 flags; |
| guint8 transaction; |
| guint16 message; |
| guint16 tlv_length; |
| } PACKED; |
| |
| struct service_header { |
| guint8 flags; |
| guint16 transaction; |
| guint16 message; |
| guint16 tlv_length; |
| } PACKED; |
| |
| struct tlv { |
| guint8 type; |
| guint16 length; |
| guint8 value[]; |
| } PACKED; |
| |
| struct control_message { |
| struct control_header header; |
| struct tlv tlv[]; |
| } PACKED; |
| |
| struct service_message { |
| struct service_header header; |
| struct tlv tlv[]; |
| } PACKED; |
| |
| struct full_message { |
| guint8 marker; |
| union { |
| struct qmux_header qmux; |
| struct qrtr_header qrtr; |
| } header; |
| union { |
| struct control_message control; |
| struct service_message service; |
| } qmi; |
| } PACKED; |
| |
| /* qmux_header and qrtr_header are of the same size, so that the overall |
| * QMI header struct is also always of the same size. This is convenient |
| * when building new QMI messages or ensuring the full header is available, |
| * but it is not a strict requirement. */ |
| G_STATIC_ASSERT (sizeof (struct qmux_header) == sizeof (struct qrtr_header)); |
| |
| #define MESSAGE_IS_QMUX(self) (((struct full_message *)(self->data))->marker == QMI_MESSAGE_QMUX_MARKER) |
| #define MESSAGE_IS_QRTR(self) (((struct full_message *)(self->data))->marker == QMI_MESSAGE_QRTR_MARKER) |
| |
| static inline gboolean |
| message_is_control (QmiMessage *self) |
| { |
| return MESSAGE_IS_QMUX (self) ? |
| ((struct full_message *)(self->data))->header.qmux.service == QMI_SERVICE_CTL : |
| ((struct full_message *)(self->data))->header.qrtr.service == QMI_SERVICE_CTL; |
| } |
| |
| static inline guint16 |
| get_message_length (QmiMessage *self) |
| { |
| return MESSAGE_IS_QMUX (self) ? |
| GUINT16_FROM_LE (((struct full_message *)(self->data))->header.qmux.length) : |
| GUINT16_FROM_LE (((struct full_message *)(self->data))->header.qrtr.length); |
| } |
| |
| static inline void |
| set_message_length (QmiMessage *self, |
| guint16 length) |
| { |
| if (MESSAGE_IS_QMUX (self)) |
| ((struct full_message *)(self->data))->header.qmux.length = GUINT16_TO_LE (length); |
| else |
| ((struct full_message *)(self->data))->header.qrtr.length = GUINT16_TO_LE (length); |
| } |
| |
| static inline guint8 |
| get_qmux_flags (QmiMessage *self) |
| { |
| /* QMI_MESSAGE_QRTR_MARKER does not support flags */ |
| g_assert (MESSAGE_IS_QMUX (self)); |
| return ((struct full_message *)(self->data))->header.qmux.flags; |
| } |
| |
| static inline guint8 |
| get_qmi_flags (QmiMessage *self) |
| { |
| if (message_is_control (self)) |
| return ((struct full_message *)(self->data))->qmi.control.header.flags; |
| |
| return ((struct full_message *)(self->data))->qmi.service.header.flags; |
| } |
| |
| gboolean |
| qmi_message_is_request (QmiMessage *self) |
| { |
| g_return_val_if_fail (self != NULL, FALSE); |
| |
| return (!qmi_message_is_response (self) && !qmi_message_is_indication (self)); |
| } |
| |
| gboolean |
| qmi_message_is_response (QmiMessage *self) |
| { |
| g_return_val_if_fail (self != NULL, FALSE); |
| |
| if (message_is_control (self)) { |
| if (((struct full_message *)(self->data))->qmi.control.header.flags & QMI_CTL_FLAG_RESPONSE) |
| return TRUE; |
| } else { |
| if (((struct full_message *)(self->data))->qmi.service.header.flags & QMI_SERVICE_FLAG_RESPONSE) |
| return TRUE; |
| } |
| |
| return FALSE; |
| } |
| |
| gboolean |
| qmi_message_is_indication (QmiMessage *self) |
| { |
| g_return_val_if_fail (self != NULL, FALSE); |
| |
| if (message_is_control (self)) { |
| if (((struct full_message *)(self->data))->qmi.control.header.flags & QMI_CTL_FLAG_INDICATION) |
| return TRUE; |
| } else { |
| if (((struct full_message *)(self->data))->qmi.service.header.flags & QMI_SERVICE_FLAG_INDICATION) |
| return TRUE; |
| } |
| |
| return FALSE; |
| } |
| |
| QmiService |
| qmi_message_get_service (QmiMessage *self) |
| { |
| g_return_val_if_fail (self != NULL, QMI_SERVICE_UNKNOWN); |
| |
| if (MESSAGE_IS_QMUX (self)) |
| return (QmiService)((struct full_message *)(self->data))->header.qmux.service; |
| |
| return (QmiService)GUINT16_FROM_LE (((struct full_message *)(self->data))->header.qrtr.service); |
| } |
| |
| guint8 |
| qmi_message_get_client_id (QmiMessage *self) |
| { |
| g_return_val_if_fail (self != NULL, 0); |
| |
| if (MESSAGE_IS_QMUX (self)) |
| return ((struct full_message *)(self->data))->header.qmux.client; |
| |
| return ((struct full_message *)(self->data))->header.qrtr.client; |
| } |
| |
| guint16 |
| qmi_message_get_transaction_id (QmiMessage *self) |
| { |
| g_return_val_if_fail (self != NULL, 0); |
| |
| if (message_is_control (self)) |
| /* note: only 1 byte for transaction in CTL message */ |
| return (guint16)((struct full_message *)(self->data))->qmi.control.header.transaction; |
| |
| return GUINT16_FROM_LE (((struct full_message *)(self->data))->qmi.service.header.transaction); |
| } |
| |
| void |
| qmi_message_set_transaction_id (QmiMessage *self, |
| guint16 transaction_id) |
| { |
| g_return_if_fail (self != NULL); |
| |
| if (message_is_control (self)) |
| ((struct full_message *)self->data)->qmi.control.header.transaction = (guint8)transaction_id; |
| else |
| ((struct full_message *)self->data)->qmi.service.header.transaction = GUINT16_TO_LE (transaction_id); |
| } |
| |
| guint16 |
| qmi_message_get_message_id (QmiMessage *self) |
| { |
| g_return_val_if_fail (self != NULL, 0); |
| |
| if (message_is_control (self)) |
| return GUINT16_FROM_LE (((struct full_message *)(self->data))->qmi.control.header.message); |
| |
| return GUINT16_FROM_LE (((struct full_message *)(self->data))->qmi.service.header.message); |
| } |
| |
| gsize |
| qmi_message_get_length (QmiMessage *self) |
| { |
| g_return_val_if_fail (self != NULL, 0); |
| |
| return self->len; |
| } |
| |
| static inline guint16 |
| get_all_tlvs_length (QmiMessage *self) |
| { |
| if (message_is_control (self)) |
| return GUINT16_FROM_LE (((struct full_message *)(self->data))->qmi.control.header.tlv_length); |
| |
| return GUINT16_FROM_LE (((struct full_message *)(self->data))->qmi.service.header.tlv_length); |
| } |
| |
| static inline void |
| set_all_tlvs_length (QmiMessage *self, |
| guint16 length) |
| { |
| if (message_is_control (self)) |
| ((struct full_message *)(self->data))->qmi.control.header.tlv_length = GUINT16_TO_LE (length); |
| else |
| ((struct full_message *)(self->data))->qmi.service.header.tlv_length = GUINT16_TO_LE (length); |
| } |
| |
| static inline struct tlv * |
| qmi_tlv (QmiMessage *self) |
| { |
| if (message_is_control (self)) |
| return ((struct full_message *)(self->data))->qmi.control.tlv; |
| |
| return ((struct full_message *)(self->data))->qmi.service.tlv; |
| } |
| |
| static inline guint8 * |
| qmi_end (QmiMessage *self) |
| { |
| return (guint8 *) self->data + self->len; |
| } |
| |
| static inline struct tlv * |
| tlv_next (struct tlv *tlv) |
| { |
| return (struct tlv *)((guint8 *)tlv + sizeof(struct tlv) + GUINT16_FROM_LE (tlv->length)); |
| } |
| |
| static inline struct tlv * |
| qmi_tlv_first (QmiMessage *self) |
| { |
| if (get_all_tlvs_length (self)) |
| return qmi_tlv (self); |
| |
| return NULL; |
| } |
| |
| static inline struct tlv * |
| qmi_tlv_next (QmiMessage *self, |
| struct tlv *tlv) |
| { |
| struct tlv *end; |
| struct tlv *next; |
| |
| end = (struct tlv *) qmi_end (self); |
| next = tlv_next (tlv); |
| |
| return (next < end ? next : NULL); |
| } |
| |
| /* |
| * Checks the validity of a QMI message. |
| * |
| * In particular, checks: |
| * 1. The message has space for all required headers. |
| * 2. The length of the buffer, the qmux length field, and the QMI tlv_length |
| * field are all consistent. |
| * 3. The TLVs in the message fit exactly in the payload size. |
| * |
| * Returns: %TRUE if the message is valid, %FALSE otherwise. |
| */ |
| static gboolean |
| message_check (QmiMessage *self, |
| GError **error) |
| { |
| gsize header_length; |
| gsize message_length; |
| guint8 *end; |
| struct tlv *tlv; |
| |
| if (self->len < (1 + sizeof (struct qmux_header))) { |
| g_set_error (error, |
| QMI_CORE_ERROR, |
| QMI_CORE_ERROR_INVALID_MESSAGE, |
| "Buffer length too short for QMUX header (%u < %" G_GSIZE_FORMAT ")", |
| self->len, 1 + sizeof (struct qmux_header)); |
| return FALSE; |
| } |
| |
| if (!MESSAGE_IS_QMUX (self) && !MESSAGE_IS_QRTR (self)) { |
| g_set_error (error, |
| QMI_CORE_ERROR, |
| QMI_CORE_ERROR_INVALID_MESSAGE, |
| "Unexpected marker (0x%02x)", |
| ((struct full_message *)(self->data))->marker); |
| return FALSE; |
| } |
| |
| /* |
| * message length is one byte shorter than buffer length because it |
| * does not include the qmux frame marker. |
| */ |
| message_length = get_message_length (self); |
| if (message_length != self->len - 1) { |
| g_set_error (error, |
| QMI_CORE_ERROR, |
| QMI_CORE_ERROR_INVALID_MESSAGE, |
| "Message length and buffer length don't match (%u != %u)", |
| get_message_length (self), self->len - 1); |
| return FALSE; |
| } |
| |
| header_length = sizeof (struct qmux_header) + (message_is_control (self) ? |
| sizeof (struct control_header) : |
| sizeof (struct service_header)); |
| |
| if (message_length < header_length) { |
| g_set_error (error, |
| QMI_CORE_ERROR, |
| QMI_CORE_ERROR_INVALID_MESSAGE, |
| "Message length too short for QMI header (%u < %" G_GSIZE_FORMAT ")", |
| get_message_length (self), header_length); |
| return FALSE; |
| } |
| |
| if (message_length - header_length != get_all_tlvs_length (self)) { |
| g_set_error (error, |
| QMI_CORE_ERROR, |
| QMI_CORE_ERROR_INVALID_MESSAGE, |
| "QMUX length and QMI TLV lengths don't match (%u - %" G_GSIZE_FORMAT " != %u)", |
| get_message_length (self), header_length, get_all_tlvs_length (self)); |
| return FALSE; |
| } |
| |
| end = qmi_end (self); |
| for (tlv = qmi_tlv (self); tlv < (struct tlv *)end; tlv = tlv_next (tlv)) { |
| if (tlv->value > end) { |
| g_set_error (error, |
| QMI_CORE_ERROR, |
| QMI_CORE_ERROR_INVALID_MESSAGE, |
| "TLV header runs over buffer (%p > %p)", |
| tlv->value, end); |
| return FALSE; |
| } |
| if (tlv->value + GUINT16_FROM_LE (tlv->length) > end) { |
| g_set_error (error, |
| QMI_CORE_ERROR, |
| QMI_CORE_ERROR_INVALID_MESSAGE, |
| "TLV value runs over buffer (%p + %u > %p)", |
| tlv->value, GUINT16_FROM_LE (tlv->length), end); |
| return FALSE; |
| } |
| } |
| |
| /* |
| * If this assert triggers, one of the if statements in the loop is wrong. |
| * (It shouldn't be reached on malformed QMI messages.) |
| */ |
| g_assert (tlv == (struct tlv *)end); |
| |
| return TRUE; |
| } |
| |
| QmiMessage * |
| qmi_message_new (QmiService service, |
| guint8 client_id, |
| guint16 transaction_id, |
| guint16 message_id) |
| { |
| GByteArray *self; |
| struct full_message *buffer; |
| gsize buffer_len; |
| |
| /* Transaction ID in the control service is 8bit only */ |
| g_return_val_if_fail ((service != QMI_SERVICE_CTL || transaction_id <= G_MAXUINT8), NULL); |
| |
| /* Service must fit in 16 bits */ |
| g_return_val_if_fail (service <= G_MAXUINT16, NULL); |
| |
| /* Create array with enough size for the QMUX marker, the QMUX header and |
| * the QMI header. Use the qmux_header size for both QMUX and QRTR messages |
| * as they are the same size. */ |
| buffer_len = (1 + |
| sizeof (struct qmux_header) + |
| (service == QMI_SERVICE_CTL ? sizeof (struct control_header) : sizeof (struct service_header))); |
| |
| /* Create the GByteArray with buffer_len bytes preallocated */ |
| self = g_byte_array_sized_new (buffer_len); |
| /* Actually flag as all the buffer_len bytes being used. */ |
| g_byte_array_set_size (self, buffer_len); |
| |
| buffer = (struct full_message *)(self->data); |
| /* QMI messages of services up to 255 are QMUX compatible */ |
| if (service <= G_MAXUINT8) { |
| buffer->marker = QMI_MESSAGE_QMUX_MARKER; |
| buffer->header.qmux.flags = 0; |
| buffer->header.qmux.service = (guint8) service; |
| buffer->header.qmux.client = client_id; |
| } else if (service <= G_MAXUINT16) { |
| buffer->marker = QMI_MESSAGE_QRTR_MARKER; |
| buffer->header.qrtr.service = (guint16) service; |
| buffer->header.qrtr.client = client_id; |
| } else |
| g_assert_not_reached (); |
| |
| if (service == QMI_SERVICE_CTL) { |
| buffer->qmi.control.header.flags = 0; |
| buffer->qmi.control.header.transaction = (guint8)transaction_id; |
| buffer->qmi.control.header.message = GUINT16_TO_LE (message_id); |
| } else { |
| buffer->qmi.service.header.flags = 0; |
| buffer->qmi.service.header.transaction = GUINT16_TO_LE (transaction_id); |
| buffer->qmi.service.header.message = GUINT16_TO_LE (message_id); |
| } |
| |
| /* Update length fields. */ |
| set_message_length (self, buffer_len - 1); /* marker not included in length */ |
| set_all_tlvs_length (self, 0); |
| |
| /* We shouldn't create invalid empty messages */ |
| g_assert (message_check (self, NULL)); |
| |
| return (QmiMessage *)self; |
| } |
| |
| QmiMessage * |
| qmi_message_new_from_data (QmiService service, |
| guint8 client_id, |
| GByteArray *qmi_data, |
| GError **error) |
| { |
| g_autoptr(GByteArray) self = NULL; |
| struct full_message *buffer; |
| gsize buffer_len; |
| gsize message_len; |
| |
| /* Service must fit in 16 bits */ |
| g_return_val_if_fail (service <= G_MAXUINT16, NULL); |
| |
| /* Create array with enough size for the QMUX marker and QMUX header, and |
| * with enough room to copy the rest of the message into */ |
| if (service == QMI_SERVICE_CTL) { |
| message_len = sizeof (struct control_header) + |
| ((struct control_message *)(qmi_data->data))->header.tlv_length; |
| } else { |
| message_len = sizeof (struct service_header) + |
| ((struct service_message *)(qmi_data->data))->header.tlv_length; |
| } |
| |
| /* Use the size of qmux_header for both QMUX and QRTR as they are the same */ |
| buffer_len = (1 + sizeof (struct qmux_header) + message_len); |
| |
| /* Create the GByteArray with buffer_len bytes preallocated */ |
| self = g_byte_array_sized_new (buffer_len); |
| g_byte_array_set_size (self, buffer_len); |
| |
| /* Set up fake QMUX header */ |
| buffer = (struct full_message *)(self->data); |
| if (service <= G_MAXUINT8) { |
| buffer->marker = QMI_MESSAGE_QMUX_MARKER; |
| buffer->header.qmux.length = GUINT16_TO_LE (buffer_len - 1); |
| buffer->header.qmux.flags = 0; |
| buffer->header.qmux.service = (guint8) service; |
| buffer->header.qmux.client = client_id; |
| } else if (service <= G_MAXUINT16) { |
| buffer->marker = QMI_MESSAGE_QRTR_MARKER; |
| buffer->header.qrtr.length = GUINT16_TO_LE (buffer_len - 1); |
| buffer->header.qrtr.service = (guint16) service; |
| buffer->header.qrtr.client = client_id; |
| } else |
| g_assert_not_reached (); |
| |
| /* Move bytes from the qmi_data array to the newly created message */ |
| memcpy (&buffer->qmi, qmi_data->data, message_len); |
| g_byte_array_remove_range (qmi_data, 0, message_len); |
| |
| /* Check input message validity as soon as we create the QmiMessage */ |
| if (!message_check (self, error)) |
| return NULL; |
| |
| return (QmiMessage *) g_steal_pointer (&self); |
| } |
| |
| QmiMessage * |
| qmi_message_response_new (QmiMessage *request, |
| QmiProtocolError error) |
| { |
| QmiMessage *response; |
| gsize tlv_offset; |
| |
| response = qmi_message_new (qmi_message_get_service (request), |
| qmi_message_get_client_id (request), |
| qmi_message_get_transaction_id (request), |
| qmi_message_get_message_id (request)); |
| |
| /* Set sender type flag */ |
| if (qmi_message_get_service (request) <= G_MAXUINT8) |
| ((struct full_message *)(((GByteArray *)response)->data))->header.qmux.flags = 0x80; |
| |
| /* Set the response flag */ |
| if (message_is_control (request)) |
| ((struct full_message *)(((GByteArray *)response)->data))->qmi.control.header.flags |= QMI_CTL_FLAG_RESPONSE; |
| else |
| ((struct full_message *)(((GByteArray *)response)->data))->qmi.service.header.flags |= QMI_SERVICE_FLAG_RESPONSE; |
| |
| /* Add result TLV, should never fail */ |
| tlv_offset = qmi_message_tlv_write_init (response, 0x02, NULL); |
| qmi_message_tlv_write_guint16 (response, QMI_ENDIAN_LITTLE, (error != QMI_PROTOCOL_ERROR_NONE), NULL); |
| qmi_message_tlv_write_guint16 (response, QMI_ENDIAN_LITTLE, error, NULL); |
| qmi_message_tlv_write_complete (response, tlv_offset, NULL); |
| |
| /* We shouldn't create invalid response messages */ |
| g_assert (message_check (response, NULL)); |
| |
| return response; |
| } |
| |
| QmiMessage * |
| qmi_message_ref (QmiMessage *self) |
| { |
| g_return_val_if_fail (self != NULL, NULL); |
| |
| return (QmiMessage *)g_byte_array_ref (self); |
| } |
| |
| void |
| qmi_message_unref (QmiMessage *self) |
| { |
| g_return_if_fail (self != NULL); |
| |
| g_byte_array_unref (self); |
| } |
| |
| guint8 |
| qmi_message_get_marker (QmiMessage *self) |
| { |
| g_return_val_if_fail (self != NULL, 0x00); |
| |
| return ((struct full_message *)(self->data))->marker; |
| } |
| |
| const guint8 * |
| qmi_message_get_raw (QmiMessage *self, |
| gsize *length, |
| GError **error) |
| { |
| g_return_val_if_fail (self != NULL, NULL); |
| g_return_val_if_fail (length != NULL, NULL); |
| |
| *length = self->len; |
| return self->data; |
| } |
| |
| const guint8 * |
| qmi_message_get_data (QmiMessage *self, |
| gsize *length, |
| GError **error) |
| { |
| g_return_val_if_fail (self != NULL, NULL); |
| g_return_val_if_fail (length != NULL, NULL); |
| |
| if (message_is_control (self)) |
| *length = sizeof (struct control_header); |
| else |
| *length = sizeof (struct service_header); |
| *length += get_all_tlvs_length (self); |
| return (guint8 *)(&((struct full_message *)(self->data))->qmi); |
| } |
| |
| /*****************************************************************************/ |
| /* TLV builder & writer */ |
| |
| static gboolean |
| tlv_error_if_write_overflow (QmiMessage *self, |
| gsize len, |
| GError **error) |
| { |
| /* Check for overflow of message size. */ |
| if (self->len + len > G_MAXUINT16) { |
| g_set_error (error, QMI_CORE_ERROR, QMI_CORE_ERROR_TLV_TOO_LONG, |
| "Writing TLV would overflow"); |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| static struct tlv * |
| tlv_get_header (QmiMessage *self, |
| gsize init_offset) |
| { |
| g_assert (init_offset <= self->len); |
| return (struct tlv *)(&self->data[init_offset]); |
| } |
| |
| gsize |
| qmi_message_tlv_write_init (QmiMessage *self, |
| guint8 type, |
| GError **error) |
| { |
| gsize init_offset; |
| struct tlv *tlv; |
| |
| g_return_val_if_fail (self != NULL, 0); |
| g_return_val_if_fail (self->len > 0, 0); |
| |
| /* Check for overflow of message size. Note that a valid TLV will at least |
| * have 1 byte of value. */ |
| if (!tlv_error_if_write_overflow (self, sizeof (struct tlv) + 1, error)) |
| return 0; |
| |
| /* Store where exactly we started adding the TLV */ |
| init_offset = self->len; |
| |
| /* Resize buffer to fit the TLV header */ |
| g_byte_array_set_size (self, self->len + sizeof (struct tlv)); |
| |
| /* Write the TLV header */ |
| tlv = tlv_get_header (self, init_offset); |
| tlv->type = type; |
| tlv->length = 0; /* Correct value will be set in complete() */ |
| |
| return init_offset; |
| } |
| |
| void |
| qmi_message_tlv_write_reset (QmiMessage *self, |
| gsize tlv_offset) |
| { |
| g_return_if_fail (self != NULL); |
| |
| g_byte_array_set_size (self, tlv_offset); |
| } |
| |
| gboolean |
| qmi_message_tlv_write_complete (QmiMessage *self, |
| gsize tlv_offset, |
| GError **error) |
| { |
| gsize tlv_length; |
| struct tlv *tlv; |
| |
| g_return_val_if_fail (self != NULL, FALSE); |
| g_return_val_if_fail (self->len >= (tlv_offset + sizeof (struct tlv)), FALSE); |
| |
| /* A TLV without content is actually not an error, e.g. TLV strings with no |
| * data are totally valid. */ |
| tlv_length = self->len - tlv_offset; |
| |
| /* Update length fields. */ |
| tlv = tlv_get_header (self, tlv_offset); |
| tlv->length = GUINT16_TO_LE (tlv_length - sizeof (struct tlv)); |
| set_message_length (self, (guint16)(get_message_length (self) + tlv_length)); |
| set_all_tlvs_length (self, (guint16)(get_all_tlvs_length (self) + tlv_length)); |
| |
| /* Make sure we didn't break anything. */ |
| g_assert (message_check (self, NULL)); |
| |
| return TRUE; |
| } |
| |
| gboolean |
| qmi_message_tlv_write_guint8 (QmiMessage *self, |
| guint8 in, |
| GError **error) |
| { |
| g_return_val_if_fail (self != NULL, FALSE); |
| |
| /* Check for overflow of message size */ |
| if (!tlv_error_if_write_overflow (self, sizeof (in), error)) |
| return FALSE; |
| |
| g_byte_array_append (self, &in, sizeof (in)); |
| return TRUE; |
| } |
| |
| gboolean |
| qmi_message_tlv_write_gint8 (QmiMessage *self, |
| gint8 in, |
| GError **error) |
| { |
| g_return_val_if_fail (self != NULL, FALSE); |
| |
| /* Check for overflow of message size */ |
| if (!tlv_error_if_write_overflow (self, sizeof (in), error)) |
| return FALSE; |
| |
| g_byte_array_append (self, (guint8 *)&in, sizeof (in)); |
| return TRUE; |
| } |
| |
| gboolean |
| qmi_message_tlv_write_guint16 (QmiMessage *self, |
| QmiEndian endian, |
| guint16 in, |
| GError **error) |
| { |
| guint16 tmp; |
| |
| g_return_val_if_fail (self != NULL, FALSE); |
| |
| /* Check for overflow of message size */ |
| if (!tlv_error_if_write_overflow (self, sizeof (in), error)) |
| return FALSE; |
| |
| tmp = (endian == QMI_ENDIAN_BIG ? GUINT16_TO_BE (in) : GUINT16_TO_LE (in)); |
| g_byte_array_append (self, (guint8 *)&tmp, sizeof (tmp)); |
| return TRUE; |
| } |
| |
| gboolean |
| qmi_message_tlv_write_gint16 (QmiMessage *self, |
| QmiEndian endian, |
| gint16 in, |
| GError **error) |
| { |
| gint16 tmp; |
| |
| g_return_val_if_fail (self != NULL, FALSE); |
| |
| /* Check for overflow of message size */ |
| if (!tlv_error_if_write_overflow (self, sizeof (in), error)) |
| return FALSE; |
| |
| tmp = (endian == QMI_ENDIAN_BIG ? GINT16_TO_BE (in) : GINT16_TO_LE (in)); |
| g_byte_array_append (self, (guint8 *)&tmp, sizeof (tmp)); |
| return TRUE; |
| } |
| |
| gboolean |
| qmi_message_tlv_write_guint32 (QmiMessage *self, |
| QmiEndian endian, |
| guint32 in, |
| GError **error) |
| { |
| guint32 tmp; |
| |
| g_return_val_if_fail (self != NULL, FALSE); |
| |
| /* Check for overflow of message size */ |
| if (!tlv_error_if_write_overflow (self, sizeof (in), error)) |
| return FALSE; |
| |
| tmp = (endian == QMI_ENDIAN_BIG ? GUINT32_TO_BE (in) : GUINT32_TO_LE (in)); |
| g_byte_array_append (self, (guint8 *)&tmp, sizeof (tmp)); |
| return TRUE; |
| } |
| |
| gboolean |
| qmi_message_tlv_write_gint32 (QmiMessage *self, |
| QmiEndian endian, |
| gint32 in, |
| GError **error) |
| { |
| gint32 tmp; |
| |
| g_return_val_if_fail (self != NULL, FALSE); |
| |
| /* Check for overflow of message size */ |
| if (!tlv_error_if_write_overflow (self, sizeof (in), error)) |
| return FALSE; |
| |
| tmp = (endian == QMI_ENDIAN_BIG ? GINT32_TO_BE (in) : GINT32_TO_LE (in)); |
| g_byte_array_append (self, (guint8 *)&tmp, sizeof (tmp)); |
| return TRUE; |
| } |
| |
| gboolean |
| qmi_message_tlv_write_guint64 (QmiMessage *self, |
| QmiEndian endian, |
| guint64 in, |
| GError **error) |
| { |
| guint64 tmp; |
| |
| g_return_val_if_fail (self != NULL, FALSE); |
| |
| /* Check for overflow of message size */ |
| if (!tlv_error_if_write_overflow (self, sizeof (in), error)) |
| return FALSE; |
| |
| tmp = (endian == QMI_ENDIAN_BIG ? GUINT64_TO_BE (in) : GUINT64_TO_LE (in)); |
| g_byte_array_append (self, (guint8 *)&tmp, sizeof (tmp)); |
| return TRUE; |
| } |
| |
| gboolean |
| qmi_message_tlv_write_gint64 (QmiMessage *self, |
| QmiEndian endian, |
| gint64 in, |
| GError **error) |
| { |
| gint64 tmp; |
| |
| g_return_val_if_fail (self != NULL, FALSE); |
| |
| /* Check for overflow of message size */ |
| if (!tlv_error_if_write_overflow (self, sizeof (in), error)) |
| return FALSE; |
| |
| tmp = (endian == QMI_ENDIAN_BIG ? GINT64_TO_BE (in) : GINT64_TO_LE (in)); |
| g_byte_array_append (self, (guint8 *)&tmp, sizeof (tmp)); |
| return TRUE; |
| } |
| |
| gboolean |
| qmi_message_tlv_write_sized_guint (QmiMessage *self, |
| guint n_bytes, |
| QmiEndian endian, |
| guint64 in, |
| GError **error) |
| { |
| guint64 tmp; |
| goffset offset; |
| |
| g_return_val_if_fail (self != NULL, FALSE); |
| g_return_val_if_fail (n_bytes <= 8, FALSE); |
| |
| /* Check for overflow of message size */ |
| if (!tlv_error_if_write_overflow (self, n_bytes, error)) |
| return FALSE; |
| |
| tmp = (endian == QMI_ENDIAN_BIG ? GUINT64_TO_BE (in) : GUINT64_TO_LE (in)); |
| |
| /* Update buffer size */ |
| offset = self->len; |
| g_byte_array_set_size (self, self->len + n_bytes); |
| |
| /* In Little Endian, we read the bytes from the beginning of the buffer */ |
| if (endian == QMI_ENDIAN_LITTLE) { |
| memcpy (&self->data[offset], &tmp, n_bytes); |
| } |
| /* In Big Endian, we read the bytes from the end of the buffer */ |
| else { |
| guint8 tmp_buffer[8]; |
| |
| memcpy (&tmp_buffer[0], &tmp, 8); |
| memcpy (&self->data[offset], &tmp_buffer[8 - n_bytes], n_bytes); |
| } |
| |
| return TRUE; |
| } |
| |
| gboolean |
| qmi_message_tlv_write_string (QmiMessage *self, |
| guint8 n_size_prefix_bytes, |
| const gchar *in, |
| gssize in_length, |
| GError **error) |
| { |
| gsize len; |
| |
| g_return_val_if_fail (self != NULL, FALSE); |
| g_return_val_if_fail (in != NULL, FALSE); |
| g_return_val_if_fail (n_size_prefix_bytes <= 2, FALSE); |
| |
| len = (in_length < 0 ? strlen (in) : (gsize) in_length); |
| |
| /* Write size prefix first */ |
| switch (n_size_prefix_bytes) { |
| case 0: |
| break; |
| case 1: |
| if (len > G_MAXUINT8) { |
| g_set_error (error, |
| QMI_CORE_ERROR, |
| QMI_CORE_ERROR_INVALID_ARGS, |
| "String too long for a 1 byte size prefix: %" G_GSIZE_FORMAT, len); |
| return FALSE; |
| } |
| if (!qmi_message_tlv_write_guint8 (self, (guint8) len, error)) { |
| g_prefix_error (error, "Cannot append string 1 byte size prefix"); |
| return FALSE; |
| } |
| break; |
| case 2: |
| if (len > G_MAXUINT16) { |
| g_set_error (error, |
| QMI_CORE_ERROR, |
| QMI_CORE_ERROR_INVALID_ARGS, |
| "String too long for a 2 byte size prefix: %" G_GSIZE_FORMAT, len); |
| return FALSE; |
| } |
| if (!qmi_message_tlv_write_guint16 (self, QMI_ENDIAN_LITTLE, (guint16) len, error)) { |
| g_prefix_error (error, "Cannot append string 2 byte size prefix"); |
| return FALSE; |
| } |
| break; |
| default: |
| g_assert_not_reached (); |
| } |
| |
| /* Check for overflow of string size */ |
| if (!tlv_error_if_write_overflow (self, len, error)) |
| return FALSE; |
| |
| g_byte_array_append (self, (guint8 *)in, len); |
| return TRUE; |
| } |
| |
| /*****************************************************************************/ |
| /* TLV reader */ |
| |
| gsize |
| qmi_message_tlv_read_init (QmiMessage *self, |
| guint8 type, |
| guint16 *out_tlv_length, |
| GError **error) |
| { |
| struct tlv *tlv; |
| guint16 tlv_length; |
| |
| g_return_val_if_fail (self != NULL, 0); |
| g_return_val_if_fail (self->len > 0, 0); |
| |
| for (tlv = qmi_tlv_first (self); tlv; tlv = qmi_tlv_next (self, tlv)) { |
| if (tlv->type == type) |
| break; |
| } |
| |
| if (!tlv) { |
| g_set_error (error, QMI_CORE_ERROR, QMI_CORE_ERROR_TLV_NOT_FOUND, |
| "TLV 0x%02X not found", type); |
| return 0; |
| } |
| |
| tlv_length = GUINT16_FROM_LE (tlv->length); |
| |
| if (((guint8 *) tlv_next (tlv)) > ((guint8 *) qmi_end (self))) { |
| g_set_error (error, QMI_CORE_ERROR, QMI_CORE_ERROR_TLV_TOO_LONG, |
| "Invalid length for TLV 0x%02X: %" G_GUINT16_FORMAT, type, tlv_length); |
| return 0; |
| } |
| |
| if (out_tlv_length) |
| *out_tlv_length = tlv_length; |
| |
| return (((guint8 *)tlv) - self->data); |
| } |
| |
| static const guint8 * |
| tlv_error_if_read_overflow (QmiMessage *self, |
| gsize tlv_offset, |
| gsize offset, |
| gsize len, |
| GError **error) |
| { |
| const guint8 *ptr; |
| struct tlv *tlv; |
| |
| tlv = (struct tlv *) &(self->data[tlv_offset]); |
| ptr = ((guint8 *)tlv) + sizeof (struct tlv) + offset; |
| |
| if (((guint8 *)(ptr + len) > (guint8 *)tlv_next (tlv)) || |
| ((guint8 *)(ptr + len) > (guint8 *)qmi_end (self))) { |
| g_set_error (error, |
| QMI_CORE_ERROR, |
| QMI_CORE_ERROR_TLV_TOO_LONG, |
| "Reading TLV would overflow"); |
| return NULL; |
| } |
| |
| return ptr; |
| } |
| |
| gboolean |
| qmi_message_tlv_read_guint8 (QmiMessage *self, |
| gsize tlv_offset, |
| gsize *offset, |
| guint8 *out, |
| GError **error) |
| { |
| const guint8 *ptr; |
| |
| g_return_val_if_fail (self != NULL, FALSE); |
| g_return_val_if_fail (offset != NULL, FALSE); |
| g_return_val_if_fail (out != NULL, FALSE); |
| |
| if (!(ptr = tlv_error_if_read_overflow (self, tlv_offset, *offset, sizeof (*out), error))) |
| return FALSE; |
| |
| *offset = *offset + 1; |
| *out = *ptr; |
| return TRUE; |
| } |
| |
| gboolean |
| qmi_message_tlv_read_gint8 (QmiMessage *self, |
| gsize tlv_offset, |
| gsize *offset, |
| gint8 *out, |
| GError **error) |
| { |
| const guint8 *ptr; |
| |
| g_return_val_if_fail (self != NULL, FALSE); |
| g_return_val_if_fail (offset != NULL, FALSE); |
| g_return_val_if_fail (out != NULL, FALSE); |
| |
| if (!(ptr = tlv_error_if_read_overflow (self, tlv_offset, *offset, sizeof (*out), error))) |
| return FALSE; |
| |
| *out = (gint8)(*ptr); |
| *offset = *offset + 1; |
| return TRUE; |
| } |
| |
| gboolean |
| qmi_message_tlv_read_guint16 (QmiMessage *self, |
| gsize tlv_offset, |
| gsize *offset, |
| QmiEndian endian, |
| guint16 *out, |
| GError **error) |
| { |
| const guint8 *ptr; |
| |
| g_return_val_if_fail (self != NULL, FALSE); |
| g_return_val_if_fail (offset != NULL, FALSE); |
| g_return_val_if_fail (out != NULL, FALSE); |
| |
| if (!(ptr = tlv_error_if_read_overflow (self, tlv_offset, *offset, sizeof (*out), error))) |
| return FALSE; |
| |
| memcpy (out, ptr, 2); |
| if (endian == QMI_ENDIAN_BIG) |
| *out = GUINT16_FROM_BE (*out); |
| else |
| *out = GUINT16_FROM_LE (*out); |
| *offset = *offset + 2; |
| return TRUE; |
| } |
| |
| gboolean |
| qmi_message_tlv_read_gint16 (QmiMessage *self, |
| gsize tlv_offset, |
| gsize *offset, |
| QmiEndian endian, |
| gint16 *out, |
| GError **error) |
| { |
| const guint8 *ptr; |
| |
| g_return_val_if_fail (self != NULL, FALSE); |
| g_return_val_if_fail (offset != NULL, FALSE); |
| g_return_val_if_fail (out != NULL, FALSE); |
| |
| if (!(ptr = tlv_error_if_read_overflow (self, tlv_offset, *offset, sizeof (*out), error))) |
| return FALSE; |
| |
| memcpy (out, ptr, 2); |
| if (endian == QMI_ENDIAN_BIG) |
| *out = GUINT16_FROM_BE (*out); |
| else |
| *out = GUINT16_FROM_LE (*out); |
| *offset = *offset + 2; |
| return TRUE; |
| } |
| |
| gboolean |
| qmi_message_tlv_read_guint32 (QmiMessage *self, |
| gsize tlv_offset, |
| gsize *offset, |
| QmiEndian endian, |
| guint32 *out, |
| GError **error) |
| { |
| const guint8 *ptr; |
| |
| g_return_val_if_fail (self != NULL, FALSE); |
| g_return_val_if_fail (offset != NULL, FALSE); |
| g_return_val_if_fail (out != NULL, FALSE); |
| |
| if (!(ptr = tlv_error_if_read_overflow (self, tlv_offset, *offset, sizeof (*out), error))) |
| return FALSE; |
| |
| memcpy (out, ptr, 4); |
| if (endian == QMI_ENDIAN_BIG) |
| *out = GUINT32_FROM_BE (*out); |
| else |
| *out = GUINT32_FROM_LE (*out); |
| *offset = *offset + 4; |
| return TRUE; |
| } |
| |
| gboolean |
| qmi_message_tlv_read_gint32 (QmiMessage *self, |
| gsize tlv_offset, |
| gsize *offset, |
| QmiEndian endian, |
| gint32 *out, |
| GError **error) |
| { |
| const guint8 *ptr; |
| |
| g_return_val_if_fail (self != NULL, FALSE); |
| g_return_val_if_fail (offset != NULL, FALSE); |
| g_return_val_if_fail (out != NULL, FALSE); |
| |
| if (!(ptr = tlv_error_if_read_overflow (self, tlv_offset, *offset, sizeof (*out), error))) |
| return FALSE; |
| |
| memcpy (out, ptr, 4); |
| if (endian == QMI_ENDIAN_BIG) |
| *out = GINT32_FROM_BE (*out); |
| else |
| *out = GINT32_FROM_LE (*out); |
| *offset = *offset + 4; |
| return TRUE; |
| } |
| |
| gboolean |
| qmi_message_tlv_read_guint64 (QmiMessage *self, |
| gsize tlv_offset, |
| gsize *offset, |
| QmiEndian endian, |
| guint64 *out, |
| GError **error) |
| { |
| const guint8 *ptr; |
| |
| g_return_val_if_fail (self != NULL, FALSE); |
| g_return_val_if_fail (offset != NULL, FALSE); |
| g_return_val_if_fail (out != NULL, FALSE); |
| |
| if (!(ptr = tlv_error_if_read_overflow (self, tlv_offset, *offset, sizeof (*out), error))) |
| return FALSE; |
| |
| memcpy (out, ptr, 8); |
| if (endian == QMI_ENDIAN_BIG) |
| *out = GUINT64_FROM_BE (*out); |
| else |
| *out = GUINT64_FROM_LE (*out); |
| *offset = *offset + 8; |
| return TRUE; |
| } |
| |
| gboolean |
| qmi_message_tlv_read_gint64 (QmiMessage *self, |
| gsize tlv_offset, |
| gsize *offset, |
| QmiEndian endian, |
| gint64 *out, |
| GError **error) |
| { |
| const guint8 *ptr; |
| |
| g_return_val_if_fail (self != NULL, FALSE); |
| g_return_val_if_fail (offset != NULL, FALSE); |
| g_return_val_if_fail (out != NULL, FALSE); |
| |
| if (!(ptr = tlv_error_if_read_overflow (self, tlv_offset, *offset, sizeof (*out), error))) |
| return FALSE; |
| |
| memcpy (out, ptr, 8); |
| if (endian == QMI_ENDIAN_BIG) |
| *out = GINT64_FROM_BE (*out); |
| else |
| *out = GINT64_FROM_LE (*out); |
| *offset = *offset + 8; |
| return TRUE; |
| } |
| |
| gboolean |
| qmi_message_tlv_read_sized_guint (QmiMessage *self, |
| gsize tlv_offset, |
| gsize *offset, |
| guint n_bytes, |
| QmiEndian endian, |
| guint64 *out, |
| GError **error) |
| { |
| const guint8 *ptr; |
| |
| g_return_val_if_fail (self != NULL, FALSE); |
| g_return_val_if_fail (offset != NULL, FALSE); |
| g_return_val_if_fail (out != NULL, FALSE); |
| g_return_val_if_fail (n_bytes <= 8, FALSE); |
| |
| if (!(ptr = tlv_error_if_read_overflow (self, tlv_offset, *offset, n_bytes, error))) |
| return FALSE; |
| |
| *out = 0; |
| |
| /* In Little Endian, we copy the bytes to the beginning of the output |
| * buffer. */ |
| if (endian == QMI_ENDIAN_LITTLE) { |
| memcpy (out, ptr, n_bytes); |
| *out = GUINT64_FROM_LE (*out); |
| } |
| /* In Big Endian, we copy the bytes to the end of the output buffer */ |
| else { |
| guint8 tmp[8] = { 0 }; |
| |
| memcpy (&tmp[8 - n_bytes], ptr, n_bytes); |
| memcpy (out, &tmp[0], 8); |
| *out = GUINT64_FROM_BE (*out); |
| } |
| |
| *offset = *offset + n_bytes; |
| return TRUE; |
| } |
| |
| gboolean |
| qmi_message_tlv_read_gfloat_endian (QmiMessage *self, |
| gsize tlv_offset, |
| gsize *offset, |
| QmiEndian endian, |
| gfloat *out, |
| GError **error) |
| { |
| const guint8 *ptr; |
| |
| g_return_val_if_fail (self != NULL, FALSE); |
| g_return_val_if_fail (offset != NULL, FALSE); |
| g_return_val_if_fail (out != NULL, FALSE); |
| |
| if (!(ptr = tlv_error_if_read_overflow (self, tlv_offset, *offset, 4, error))) |
| return FALSE; |
| |
| memcpy (out, ptr, 4); |
| if (endian == QMI_ENDIAN_BIG) |
| *out = QMI_GFLOAT_FROM_BE (*out); |
| else |
| *out = QMI_GFLOAT_FROM_LE (*out); |
| *offset = *offset + 4; |
| return TRUE; |
| } |
| |
| gboolean |
| qmi_message_tlv_read_gdouble (QmiMessage *self, |
| gsize tlv_offset, |
| gsize *offset, |
| QmiEndian endian, |
| gdouble *out, |
| GError **error) |
| { |
| const guint8 *ptr; |
| |
| g_return_val_if_fail (self != NULL, FALSE); |
| g_return_val_if_fail (offset != NULL, FALSE); |
| g_return_val_if_fail (out != NULL, FALSE); |
| |
| if (!(ptr = tlv_error_if_read_overflow (self, tlv_offset, *offset, 8, error))) |
| return FALSE; |
| |
| /* Yeah, do this for now */ |
| memcpy (out, ptr, 8); |
| if (endian == QMI_ENDIAN_BIG) |
| *out = QMI_GDOUBLE_FROM_BE (*out); |
| else |
| *out = QMI_GDOUBLE_FROM_LE (*out); |
| *offset = *offset + 8; |
| return TRUE; |
| } |
| |
| gboolean |
| qmi_message_tlv_read_string (QmiMessage *self, |
| gsize tlv_offset, |
| gsize *offset, |
| guint8 n_size_prefix_bytes, |
| guint16 max_size, |
| gchar **out, |
| GError **error) |
| { |
| const guint8 *ptr; |
| guint16 string_length; |
| guint16 valid_string_length; |
| |
| g_return_val_if_fail (self != NULL, FALSE); |
| g_return_val_if_fail (offset != NULL, FALSE); |
| g_return_val_if_fail (out != NULL, FALSE); |
| g_return_val_if_fail (n_size_prefix_bytes <= 2, FALSE); |
| |
| switch (n_size_prefix_bytes) { |
| case 0: { |
| struct tlv *tlv; |
| |
| if (!tlv_error_if_read_overflow (self, tlv_offset, *offset, 0, error)) |
| return FALSE; |
| |
| /* If no length prefix given, read the remaining TLV buffer into a string */ |
| tlv = (struct tlv *) &(self->data[tlv_offset]); |
| string_length = (GUINT16_FROM_LE (tlv->length) - *offset); |
| break; |
| } |
| case 1: { |
| guint8 string_length_8; |
| |
| if (!qmi_message_tlv_read_guint8 (self, tlv_offset, offset, &string_length_8, error)) |
| return FALSE; |
| string_length = (guint16) string_length_8; |
| break; |
| } |
| case 2: |
| if (!qmi_message_tlv_read_guint16 (self, tlv_offset, offset, QMI_ENDIAN_LITTLE, &string_length, error)) |
| return FALSE; |
| break; |
| default: |
| g_assert_not_reached (); |
| } |
| |
| if (string_length == 0) { |
| *out = g_strdup (""); |
| return TRUE; |
| } |
| |
| if (max_size > 0 && string_length > max_size) |
| valid_string_length = max_size; |
| else |
| valid_string_length = string_length; |
| |
| if (!(ptr = tlv_error_if_read_overflow (self, tlv_offset, *offset, valid_string_length, error))) |
| return FALSE; |
| |
| /* Perform a quick UTF-8 validation check first. This check isn't perfect, |
| * because there may be GSM-7 encoded strings that are valid UTF-8 as well, |
| * but hey, the strings read using this method should all really be ASCII-7 |
| * and we're trying to do our best to overcome modem firmware problems... |
| */ |
| if (qmi_helpers_string_utf8_validate_printable (ptr, valid_string_length)) { |
| *out = g_malloc (valid_string_length + 1); |
| memcpy (*out, ptr, valid_string_length); |
| (*out)[valid_string_length] = '\0'; |
| } else { |
| /* Otherwise, attempt GSM-7 */ |
| *out =qmi_helpers_string_utf8_from_gsm7 (ptr, valid_string_length); |
| if (*out == NULL) { |
| /* Otherwise, attempt UCS-2 */ |
| *out = qmi_helpers_string_utf8_from_ucs2le (ptr, valid_string_length); |
| if (*out == NULL) { |
| /* Otherwise, error */ |
| g_set_error (error, QMI_CORE_ERROR, QMI_CORE_ERROR_INVALID_DATA, "invalid string"); |
| return FALSE; |
| } |
| } |
| } |
| |
| *offset = (*offset + string_length); |
| return TRUE; |
| } |
| |
| gboolean |
| qmi_message_tlv_read_fixed_size_string (QmiMessage *self, |
| gsize tlv_offset, |
| gsize *offset, |
| guint16 string_length, |
| gchar *out, |
| GError **error) |
| { |
| const guint8 *ptr; |
| const gchar *end = NULL; |
| |
| g_return_val_if_fail (self != NULL, FALSE); |
| g_return_val_if_fail (offset != NULL, FALSE); |
| g_return_val_if_fail (out != NULL, FALSE); |
| |
| if (string_length == 0) |
| return TRUE; |
| |
| if (!(ptr = tlv_error_if_read_overflow (self, tlv_offset, *offset, string_length, error))) |
| return FALSE; |
| |
| /* full string valid? */ |
| if (g_utf8_validate ((const gchar *)ptr, string_length, &end)) { |
| memcpy (out, ptr, string_length); |
| *offset = (*offset + string_length); |
| return TRUE; |
| } |
| |
| /* partial string valid? */ |
| if (end && end > (const gchar *)ptr) { |
| /* copy only the valid bytes */ |
| memcpy (out, ptr, end - (const gchar *)ptr); |
| /* but update offset with the full expected length */ |
| *offset = (*offset + string_length); |
| return TRUE; |
| } |
| |
| g_set_error (error, QMI_CORE_ERROR, QMI_CORE_ERROR_INVALID_DATA, "invalid string"); |
| return FALSE; |
| } |
| |
| guint16 |
| qmi_message_tlv_read_remaining_size (QmiMessage *self, |
| gsize tlv_offset, |
| gsize offset) |
| { |
| struct tlv *tlv; |
| |
| g_return_val_if_fail (self != NULL, FALSE); |
| |
| tlv = (struct tlv *) &(self->data[tlv_offset]); |
| |
| g_warn_if_fail (GUINT16_FROM_LE (tlv->length) >= offset); |
| return (GUINT16_FROM_LE (tlv->length) >= offset ? (GUINT16_FROM_LE (tlv->length) - offset) : 0); |
| } |
| |
| /*****************************************************************************/ |
| |
| const guint8 * |
| qmi_message_get_raw_tlv (QmiMessage *self, |
| guint8 type, |
| guint16 *length) |
| { |
| struct tlv *tlv; |
| |
| g_return_val_if_fail (self != NULL, NULL); |
| g_return_val_if_fail (length != NULL, NULL); |
| |
| for (tlv = qmi_tlv_first (self); tlv; tlv = qmi_tlv_next (self, tlv)) { |
| if (tlv->type == type) { |
| *length = GUINT16_FROM_LE (tlv->length); |
| return (guint8 *)&(tlv->value[0]); |
| } |
| } |
| |
| return NULL; |
| } |
| |
| void |
| qmi_message_foreach_raw_tlv (QmiMessage *self, |
| QmiMessageForeachRawTlvFn func, |
| gpointer user_data) |
| { |
| struct tlv *tlv; |
| |
| g_return_if_fail (self != NULL); |
| g_return_if_fail (func != NULL); |
| |
| for (tlv = qmi_tlv_first (self); tlv; tlv = qmi_tlv_next (self, tlv)) { |
| func (tlv->type, |
| (const guint8 *)tlv->value, |
| (gsize)(GUINT16_FROM_LE (tlv->length)), |
| user_data); |
| } |
| } |
| |
| gboolean |
| qmi_message_add_raw_tlv (QmiMessage *self, |
| guint8 type, |
| const guint8 *raw, |
| gsize length, |
| GError **error) |
| { |
| size_t tlv_len; |
| struct tlv *tlv; |
| |
| g_return_val_if_fail (self != NULL, FALSE); |
| g_return_val_if_fail (raw != NULL, FALSE); |
| g_return_val_if_fail (length > 0, FALSE); |
| |
| /* Find length of new TLV */ |
| tlv_len = sizeof (struct tlv) + length; |
| |
| /* Check for overflow of message size. */ |
| if (get_message_length (self) + tlv_len > G_MAXUINT16) { |
| g_set_error (error, |
| QMI_CORE_ERROR, |
| QMI_CORE_ERROR_TLV_TOO_LONG, |
| "TLV to add is too long"); |
| return FALSE; |
| } |
| |
| /* Resize buffer. */ |
| g_byte_array_set_size (self, self->len + tlv_len); |
| |
| /* Fill in new TLV. */ |
| tlv = (struct tlv *)(qmi_end (self) - tlv_len); |
| tlv->type = type; |
| tlv->length = GUINT16_TO_LE (length); |
| memcpy (tlv->value, raw, length); |
| |
| /* Update length fields. */ |
| set_message_length (self, (guint16)(get_message_length (self) + tlv_len)); |
| set_all_tlvs_length (self, (guint16)(get_all_tlvs_length (self) + tlv_len)); |
| |
| /* Make sure we didn't break anything. */ |
| g_assert (message_check (self, NULL)); |
| |
| return TRUE; |
| } |
| |
| QmiMessage * |
| qmi_message_new_from_raw (GByteArray *raw, |
| GError **error) |
| { |
| GByteArray *self; |
| gsize message_len; |
| |
| g_return_val_if_fail (raw != NULL, NULL); |
| |
| if (MESSAGE_IS_QMUX (raw)) |
| message_len = GUINT16_FROM_LE (((struct full_message *)raw->data)->header.qmux.length); |
| else |
| message_len = GUINT16_FROM_LE (((struct full_message *)raw->data)->header.qrtr.length); |
| |
| /* If we didn't even read the QMUX header (comes after the 1-byte marker), |
| * leave */ |
| if (raw->len < (sizeof (struct qrtr_header) + 1)) |
| return NULL; |
| |
| /* We need to have read the length reported by the QMUX header (plus the |
| * initial 1-byte marker) */ |
| if (raw->len < (message_len + 1)) |
| return NULL; |
| |
| /* Ok, so we should have all the data available already */ |
| self = g_byte_array_sized_new (message_len + 1); |
| g_byte_array_prepend (self, raw->data, message_len + 1); |
| |
| /* We got a complete QMI message, remove from input buffer */ |
| g_byte_array_remove_range (raw, 0, self->len); |
| |
| /* Check input message validity as soon as we create the QmiMessage */ |
| if (!message_check (self, error)) { |
| /* Yes, we lose the whole message here */ |
| qmi_message_unref (self); |
| return NULL; |
| } |
| |
| return (QmiMessage *)self; |
| } |
| |
| gchar * |
| qmi_message_get_tlv_printable (QmiMessage *self, |
| const gchar *line_prefix, |
| guint8 type, |
| const guint8 *raw, |
| gsize raw_length) |
| { |
| gchar *printable; |
| gchar *value_hex; |
| |
| g_return_val_if_fail (self != NULL, NULL); |
| g_return_val_if_fail (line_prefix != NULL, NULL); |
| g_return_val_if_fail (raw != NULL, NULL); |
| |
| value_hex = qmi_common_str_hex (raw, raw_length, ':'); |
| printable = g_strdup_printf ("%sTLV:\n" |
| "%s type = 0x%02x\n" |
| "%s length = %" G_GSIZE_FORMAT "\n" |
| "%s value = %s\n", |
| line_prefix, |
| line_prefix, type, |
| line_prefix, raw_length, |
| line_prefix, value_hex); |
| g_free (value_hex); |
| return printable; |
| } |
| |
| static gchar * |
| get_generic_printable (QmiMessage *self, |
| const gchar *line_prefix) |
| { |
| GString *printable; |
| struct tlv *tlv; |
| |
| printable = g_string_new (""); |
| |
| g_string_append_printf (printable, |
| "%s message = (0x%04x)\n", |
| line_prefix, qmi_message_get_message_id (self)); |
| |
| for (tlv = qmi_tlv_first (self); tlv; tlv = qmi_tlv_next (self, tlv)) { |
| gchar *printable_tlv; |
| |
| printable_tlv = qmi_message_get_tlv_printable (self, |
| line_prefix, |
| tlv->type, |
| tlv->value, |
| GUINT16_FROM_LE (tlv->length)); |
| g_string_append (printable, printable_tlv); |
| g_free (printable_tlv); |
| } |
| |
| return g_string_free (printable, FALSE); |
| } |
| |
| gchar * |
| qmi_message_get_printable_full (QmiMessage *self, |
| QmiMessageContext *context, |
| const gchar *line_prefix) |
| { |
| GString *printable; |
| gchar *qmi_flags_str; |
| gchar *contents; |
| |
| g_return_val_if_fail (self != NULL, NULL); |
| |
| if (!line_prefix) |
| line_prefix = ""; |
| |
| printable = g_string_new (""); |
| if (MESSAGE_IS_QMUX (self)) { |
| g_string_append_printf (printable, |
| "%sQMUX:\n" |
| "%s length = %u\n" |
| "%s flags = 0x%02x\n" |
| "%s service = \"%s\"\n" |
| "%s client = %u\n", |
| line_prefix, |
| line_prefix, get_message_length (self), |
| line_prefix, get_qmux_flags (self), |
| line_prefix, qmi_service_get_string (qmi_message_get_service (self)), |
| line_prefix, qmi_message_get_client_id (self)); |
| } else if (MESSAGE_IS_QRTR (self)) { |
| g_string_append_printf (printable, |
| "%sQRTR:\n" |
| "%s length = %u\n" |
| "%s service = \"%s\"\n" |
| "%s client = %u\n", |
| line_prefix, |
| line_prefix, get_message_length (self), |
| line_prefix, qmi_service_get_string (qmi_message_get_service (self)), |
| line_prefix, qmi_message_get_client_id (self)); |
| } else { |
| g_warn_if_reached (); |
| return g_string_free (printable, FALSE); |
| } |
| |
| if (qmi_message_get_service (self) == QMI_SERVICE_CTL) |
| qmi_flags_str = qmi_ctl_flag_build_string_from_mask (get_qmi_flags (self)); |
| else |
| qmi_flags_str = qmi_service_flag_build_string_from_mask (get_qmi_flags (self)); |
| |
| g_string_append_printf (printable, |
| "%sQMI:\n" |
| "%s flags = \"%s\"\n" |
| "%s transaction = %u\n" |
| "%s tlv_length = %u\n", |
| line_prefix, |
| line_prefix, qmi_flags_str, |
| line_prefix, qmi_message_get_transaction_id (self), |
| line_prefix, get_all_tlvs_length (self)); |
| g_free (qmi_flags_str); |
| |
| contents = NULL; |
| switch (qmi_message_get_service (self)) { |
| case QMI_SERVICE_CTL: |
| #if defined HAVE_QMI_SERVICE_CTL |
| contents = __qmi_message_ctl_get_printable (self, context, line_prefix); |
| #endif |
| break; |
| case QMI_SERVICE_DMS: |
| #if defined HAVE_QMI_SERVICE_DMS |
| contents = __qmi_message_dms_get_printable (self, context, line_prefix); |
| #endif |
| break; |
| case QMI_SERVICE_WDS: |
| #if defined HAVE_QMI_SERVICE_WDS |
| contents = __qmi_message_wds_get_printable (self, context, line_prefix); |
| #endif |
| break; |
| case QMI_SERVICE_NAS: |
| #if defined HAVE_QMI_SERVICE_NAS |
| contents = __qmi_message_nas_get_printable (self, context, line_prefix); |
| #endif |
| break; |
| case QMI_SERVICE_WMS: |
| #if defined HAVE_QMI_SERVICE_WMS |
| contents = __qmi_message_wms_get_printable (self, context, line_prefix); |
| #endif |
| break; |
| case QMI_SERVICE_PDC: |
| #if defined HAVE_QMI_SERVICE_PDC |
| contents = __qmi_message_pdc_get_printable (self, context, line_prefix); |
| #endif |
| break; |
| case QMI_SERVICE_PDS: |
| #if defined HAVE_QMI_SERVICE_PDS |
| contents = __qmi_message_pds_get_printable (self, context, line_prefix); |
| #endif |
| break; |
| case QMI_SERVICE_PBM: |
| #if defined HAVE_QMI_SERVICE_PBM |
| contents = __qmi_message_pbm_get_printable (self, context, line_prefix); |
| #endif |
| break; |
| case QMI_SERVICE_UIM: |
| #if defined HAVE_QMI_SERVICE_UIM |
| contents = __qmi_message_uim_get_printable (self, context, line_prefix); |
| #endif |
| break; |
| case QMI_SERVICE_OMA: |
| #if defined HAVE_QMI_SERVICE_OMA |
| contents = __qmi_message_oma_get_printable (self, context, line_prefix); |
| #endif |
| break; |
| case QMI_SERVICE_GAS: |
| #if defined HAVE_QMI_SERVICE_GAS |
| contents = __qmi_message_gas_get_printable (self, context, line_prefix); |
| #endif |
| break; |
| case QMI_SERVICE_GMS: |
| #if defined HAVE_QMI_SERVICE_GMS |
| contents = __qmi_message_gms_get_printable (self, context, line_prefix); |
| #endif |
| break; |
| case QMI_SERVICE_WDA: |
| #if defined HAVE_QMI_SERVICE_WDA |
| contents = __qmi_message_wda_get_printable (self, context, line_prefix); |
| #endif |
| break; |
| case QMI_SERVICE_VOICE: |
| #if defined HAVE_QMI_SERVICE_VOICE |
| contents = __qmi_message_voice_get_printable (self, context, line_prefix); |
| #endif |
| break; |
| case QMI_SERVICE_LOC: |
| #if defined HAVE_QMI_SERVICE_LOC |
| contents = __qmi_message_loc_get_printable (self, context, line_prefix); |
| #endif |
| break; |
| case QMI_SERVICE_QOS: |
| #if defined HAVE_QMI_SERVICE_QOS |
| contents = __qmi_message_qos_get_printable (self, context, line_prefix); |
| #endif |
| break; |
| case QMI_SERVICE_DSD: |
| #if defined HAVE_QMI_SERVICE_DSD |
| contents = __qmi_message_dsd_get_printable (self, context, line_prefix); |
| #endif |
| break; |
| case QMI_SERVICE_DPM: |
| #if defined HAVE_QMI_SERVICE_DPM |
| contents = __qmi_message_dpm_get_printable (self, context, line_prefix); |
| #endif |
| break; |
| case QMI_SERVICE_FOX: |
| #if defined HAVE_QMI_SERVICE_FOX |
| contents = __qmi_message_fox_get_printable (self, context, line_prefix); |
| #endif |
| break; |
| case QMI_SERVICE_ATR: |
| #if defined HAVE_QMI_SERVICE_ATR |
| contents = __qmi_message_atr_get_printable (self, context, line_prefix); |
| #endif |
| case QMI_SERVICE_IMSP: |
| #if defined HAVE_QMI_SERVICE_IMSP |
| contents = __qmi_message_imsp_get_printable (self, context, line_prefix); |
| #endif |
| break; |
| case QMI_SERVICE_IMSA: |
| #if defined HAVE_QMI_SERVICE_IMSA |
| contents = __qmi_message_imsa_get_printable (self, context, line_prefix); |
| #endif |
| break; |
| case QMI_SERVICE_IMS: |
| #if defined HAVE_QMI_SERVICE_IMS |
| contents = __qmi_message_ims_get_printable (self, context, line_prefix); |
| #endif |
| break; |
| case QMI_SERVICE_SSC: |
| #if defined HAVE_QMI_SERVICE_SSC |
| contents = __qmi_message_ssc_get_printable (self, context, line_prefix); |
| #endif |
| break; |
| |
| case QMI_SERVICE_UNKNOWN: |
| g_assert_not_reached (); |
| |
| case QMI_SERVICE_AUTH: |
| case QMI_SERVICE_AT: |
| case QMI_SERVICE_CAT2: |
| case QMI_SERVICE_QCHAT: |
| case QMI_SERVICE_RMTFS: |
| case QMI_SERVICE_TEST: |
| case QMI_SERVICE_SAR: |
| case QMI_SERVICE_ADC: |
| case QMI_SERVICE_CSD: |
| case QMI_SERVICE_MFS: |
| case QMI_SERVICE_TIME: |
| case QMI_SERVICE_TS: |
| case QMI_SERVICE_TMD: |
| case QMI_SERVICE_SAP: |
| case QMI_SERVICE_TSYNC: |
| case QMI_SERVICE_RFSA: |
| case QMI_SERVICE_CSVT: |
| case QMI_SERVICE_QCMAP: |
| case QMI_SERVICE_IMSVT: |
| case QMI_SERVICE_COEX: |
| case QMI_SERVICE_STX: |
| case QMI_SERVICE_BIT: |
| case QMI_SERVICE_IMSRTP: |
| case QMI_SERVICE_RFRPE: |
| case QMI_SERVICE_SSCTL: |
| case QMI_SERVICE_CAT: |
| case QMI_SERVICE_RMS: |
| case QMI_SERVICE_FOTA: |
| default: |
| break; |
| } |
| |
| if (!contents) |
| contents = get_generic_printable (self, line_prefix); |
| g_string_append (printable, contents); |
| g_free (contents); |
| |
| return g_string_free (printable, FALSE); |
| } |
| |
| gboolean |
| __qmi_message_is_abortable (QmiMessage *self, |
| QmiMessageContext *context) |
| { |
| switch (qmi_message_get_service (self)) { |
| case QMI_SERVICE_WDS: |
| #if defined HAVE_QMI_SERVICE_WDS |
| return __qmi_message_wds_is_abortable (self, context); |
| #else |
| return FALSE; |
| #endif |
| case QMI_SERVICE_NAS: |
| #if defined HAVE_QMI_SERVICE_NAS |
| return __qmi_message_nas_is_abortable (self, context); |
| #else |
| return FALSE; |
| #endif |
| |
| case QMI_SERVICE_UNKNOWN: |
| g_assert_not_reached (); |
| |
| case QMI_SERVICE_CTL: |
| case QMI_SERVICE_DMS: |
| case QMI_SERVICE_WMS: |
| case QMI_SERVICE_PDS: |
| case QMI_SERVICE_PBM: |
| case QMI_SERVICE_UIM: |
| case QMI_SERVICE_OMA: |
| case QMI_SERVICE_GAS: |
| case QMI_SERVICE_WDA: |
| case QMI_SERVICE_LOC: |
| case QMI_SERVICE_AUTH: |
| case QMI_SERVICE_AT: |
| case QMI_SERVICE_CAT2: |
| case QMI_SERVICE_QCHAT: |
| case QMI_SERVICE_RMTFS: |
| case QMI_SERVICE_TEST: |
| case QMI_SERVICE_SAR: |
| case QMI_SERVICE_IMS: |
| case QMI_SERVICE_ADC: |
| case QMI_SERVICE_CSD: |
| case QMI_SERVICE_MFS: |
| case QMI_SERVICE_TIME: |
| case QMI_SERVICE_TS: |
| case QMI_SERVICE_TMD: |
| case QMI_SERVICE_SAP: |
| case QMI_SERVICE_TSYNC: |
| case QMI_SERVICE_RFSA: |
| case QMI_SERVICE_CSVT: |
| case QMI_SERVICE_QCMAP: |
| case QMI_SERVICE_IMSP: |
| case QMI_SERVICE_IMSVT: |
| case QMI_SERVICE_IMSA: |
| case QMI_SERVICE_COEX: |
| case QMI_SERVICE_STX: |
| case QMI_SERVICE_BIT: |
| case QMI_SERVICE_IMSRTP: |
| case QMI_SERVICE_RFRPE: |
| case QMI_SERVICE_SSCTL: |
| case QMI_SERVICE_DPM: |
| case QMI_SERVICE_CAT: |
| case QMI_SERVICE_RMS: |
| case QMI_SERVICE_FOTA: |
| case QMI_SERVICE_GMS: |
| case QMI_SERVICE_VOICE: |
| case QMI_SERVICE_PDC: |
| case QMI_SERVICE_DSD: |
| case QMI_SERVICE_QOS: |
| case QMI_SERVICE_FOX: |
| case QMI_SERVICE_ATR: |
| case QMI_SERVICE_SSC: |
| default: |
| return FALSE; |
| } |
| } |