| // SPDX-License-Identifier: GPL-2.0-or-later |
| /* |
| * |
| * OBEX library with GLib integration |
| * |
| * Copyright (C) 2011 Intel Corporation. All rights reserved. |
| * |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #include <string.h> |
| |
| #include "gobex-header.h" |
| #include "gobex-debug.h" |
| #include "src/shared/util.h" |
| |
| /* Header types */ |
| #define G_OBEX_HDR_ENC_UNICODE (0 << 6) |
| #define G_OBEX_HDR_ENC_BYTES (1 << 6) |
| #define G_OBEX_HDR_ENC_UINT8 (2 << 6) |
| #define G_OBEX_HDR_ENC_UINT32 (3 << 6) |
| |
| #define G_OBEX_HDR_ENC(id) ((id) & 0xc0) |
| |
| struct _GObexHeader { |
| guint8 id; |
| gboolean extdata; |
| gsize vlen; /* Length of value */ |
| gsize hlen; /* Length of full encoded header */ |
| union { |
| char *string; /* UTF-8 converted from UTF-16 */ |
| guint8 *data; /* Own buffer */ |
| const guint8 *extdata; /* Reference to external buffer */ |
| guint8 u8; |
| guint32 u32; |
| } v; |
| }; |
| |
| static glong utf8_to_utf16(gunichar2 **utf16, const char *utf8) { |
| glong utf16_len; |
| int i; |
| |
| if (*utf8 == '\0') { |
| *utf16 = NULL; |
| return 0; |
| } |
| |
| *utf16 = g_utf8_to_utf16(utf8, -1, NULL, &utf16_len, NULL); |
| if (*utf16 == NULL) |
| return -1; |
| |
| /* g_utf8_to_utf16 produces host-byteorder UTF-16, |
| * but OBEX requires network byteorder (big endian) */ |
| for (i = 0; i < utf16_len; i++) |
| (*utf16)[i] = g_htons((*utf16)[i]); |
| |
| utf16_len = (utf16_len + 1) << 1; |
| |
| return utf16_len; |
| } |
| |
| static guint8 *put_bytes(guint8 *to, const void *from, gsize count) |
| { |
| memcpy(to, from, count); |
| return (to + count); |
| } |
| |
| static const guint8 *get_bytes(void *to, const guint8 *from, gsize count) |
| { |
| memcpy(to, from, count); |
| return (from + count); |
| } |
| |
| gssize g_obex_header_encode(GObexHeader *header, void *buf, gsize buf_len) |
| { |
| guint8 *ptr = buf; |
| guint16 u16; |
| guint32 u32; |
| gunichar2 *utf16; |
| glong utf16_len; |
| |
| g_obex_debug(G_OBEX_DEBUG_HEADER, "header 0x%02x", |
| G_OBEX_HDR_ENC(header->id)); |
| |
| if (buf_len < header->hlen) |
| return -1; |
| |
| ptr = put_bytes(ptr, &header->id, sizeof(header->id)); |
| |
| switch (G_OBEX_HDR_ENC(header->id)) { |
| case G_OBEX_HDR_ENC_UNICODE: |
| utf16_len = utf8_to_utf16(&utf16, header->v.string); |
| if (utf16_len < 0 || (guint16) utf16_len > buf_len) |
| return -1; |
| g_assert_cmpuint(utf16_len + 3, ==, header->hlen); |
| u16 = g_htons(utf16_len + 3); |
| ptr = put_bytes(ptr, &u16, sizeof(u16)); |
| put_bytes(ptr, utf16, utf16_len); |
| g_free(utf16); |
| break; |
| case G_OBEX_HDR_ENC_BYTES: |
| u16 = g_htons(header->hlen); |
| ptr = put_bytes(ptr, &u16, sizeof(u16)); |
| if (header->extdata) |
| put_bytes(ptr, header->v.extdata, header->vlen); |
| else |
| put_bytes(ptr, header->v.data, header->vlen); |
| break; |
| case G_OBEX_HDR_ENC_UINT8: |
| *ptr = header->v.u8; |
| break; |
| case G_OBEX_HDR_ENC_UINT32: |
| u32 = g_htonl(header->v.u32); |
| put_bytes(ptr, &u32, sizeof(u32)); |
| break; |
| default: |
| g_assert_not_reached(); |
| } |
| |
| return header->hlen; |
| } |
| |
| GObexHeader *g_obex_header_decode(const void *data, gsize len, |
| GObexDataPolicy data_policy, gsize *parsed, |
| GError **err) |
| { |
| GObexHeader *header; |
| const guint8 *ptr = data; |
| guint16 hdr_len; |
| gsize str_len; |
| GError *conv_err = NULL; |
| |
| if (len < 2) { |
| if (!err) |
| return NULL; |
| g_set_error(err, G_OBEX_ERROR, G_OBEX_ERROR_PARSE_ERROR, |
| "Too short header in packet"); |
| g_obex_debug(G_OBEX_DEBUG_ERROR, "%s", (*err)->message); |
| return NULL; |
| } |
| |
| header = g_new0(GObexHeader, 1); |
| |
| ptr = get_bytes(&header->id, ptr, sizeof(header->id)); |
| |
| g_obex_debug(G_OBEX_DEBUG_HEADER, "header 0x%02x", |
| G_OBEX_HDR_ENC(header->id)); |
| |
| switch (G_OBEX_HDR_ENC(header->id)) { |
| case G_OBEX_HDR_ENC_UNICODE: |
| if (len < 3) { |
| g_set_error(err, G_OBEX_ERROR, |
| G_OBEX_ERROR_PARSE_ERROR, |
| "Not enough data for unicode header (0x%02x)", |
| header->id); |
| goto failed; |
| } |
| ptr = get_bytes(&hdr_len, ptr, sizeof(hdr_len)); |
| hdr_len = g_ntohs(hdr_len); |
| |
| if (hdr_len == 3) { |
| header->v.string = g_strdup(""); |
| header->vlen = 0; |
| header->hlen = hdr_len; |
| *parsed = hdr_len; |
| break; |
| } |
| |
| if (hdr_len > len || hdr_len < 5) { |
| g_set_error(err, G_OBEX_ERROR, |
| G_OBEX_ERROR_PARSE_ERROR, |
| "Invalid unicode header (0x%02x) length (%u)", |
| header->id, hdr_len); |
| goto failed; |
| } |
| |
| header->v.string = g_convert((const char *) ptr, hdr_len - 5, |
| "UTF-8", "UTF-16BE", |
| NULL, &str_len, &conv_err); |
| if (header->v.string == NULL) { |
| g_set_error(err, G_OBEX_ERROR, |
| G_OBEX_ERROR_PARSE_ERROR, |
| "Unicode conversion failed: %s", |
| conv_err->message); |
| g_error_free(conv_err); |
| goto failed; |
| } |
| |
| header->vlen = (gsize) str_len; |
| header->hlen = hdr_len; |
| |
| *parsed = hdr_len; |
| |
| break; |
| case G_OBEX_HDR_ENC_BYTES: |
| if (len < 3) { |
| g_set_error(err, G_OBEX_ERROR, |
| G_OBEX_ERROR_PARSE_ERROR, |
| "Too short byte array header"); |
| goto failed; |
| } |
| ptr = get_bytes(&hdr_len, ptr, sizeof(hdr_len)); |
| hdr_len = g_ntohs(hdr_len); |
| if (hdr_len > len) { |
| g_set_error(err, G_OBEX_ERROR, |
| G_OBEX_ERROR_PARSE_ERROR, |
| "Too long byte array header"); |
| goto failed; |
| } |
| |
| if (hdr_len < 3) { |
| g_set_error(err, G_OBEX_ERROR, |
| G_OBEX_ERROR_PARSE_ERROR, |
| "Too small byte array length"); |
| goto failed; |
| } |
| |
| header->vlen = hdr_len - 3; |
| header->hlen = hdr_len; |
| |
| switch (data_policy) { |
| case G_OBEX_DATA_COPY: |
| header->v.data = util_memdup(ptr, header->vlen); |
| break; |
| case G_OBEX_DATA_REF: |
| header->extdata = TRUE; |
| header->v.extdata = ptr; |
| break; |
| case G_OBEX_DATA_INHERIT: |
| default: |
| g_set_error(err, G_OBEX_ERROR, |
| G_OBEX_ERROR_INVALID_ARGS, |
| "Invalid data policy"); |
| goto failed; |
| } |
| |
| *parsed = hdr_len; |
| |
| break; |
| case G_OBEX_HDR_ENC_UINT8: |
| header->vlen = 1; |
| header->hlen = 2; |
| header->v.u8 = *ptr; |
| *parsed = 2; |
| break; |
| case G_OBEX_HDR_ENC_UINT32: |
| if (len < 5) { |
| g_set_error(err, G_OBEX_ERROR, |
| G_OBEX_ERROR_PARSE_ERROR, |
| "Too short uint32 header"); |
| goto failed; |
| } |
| header->vlen = 4; |
| header->hlen = 5; |
| get_bytes(&header->v.u32, ptr, sizeof(header->v.u32)); |
| header->v.u32 = g_ntohl(header->v.u32); |
| *parsed = 5; |
| break; |
| default: |
| g_assert_not_reached(); |
| } |
| |
| return header; |
| |
| failed: |
| if (*err) |
| g_obex_debug(G_OBEX_DEBUG_ERROR, "%s", (*err)->message); |
| g_obex_header_free(header); |
| return NULL; |
| } |
| |
| void g_obex_header_free(GObexHeader *header) |
| { |
| g_obex_debug(G_OBEX_DEBUG_HEADER, "header 0x%02x", |
| G_OBEX_HDR_ENC(header->id)); |
| |
| switch (G_OBEX_HDR_ENC(header->id)) { |
| case G_OBEX_HDR_ENC_UNICODE: |
| g_free(header->v.string); |
| break; |
| case G_OBEX_HDR_ENC_BYTES: |
| if (!header->extdata) |
| free(header->v.data); |
| break; |
| case G_OBEX_HDR_ENC_UINT8: |
| case G_OBEX_HDR_ENC_UINT32: |
| break; |
| default: |
| g_assert_not_reached(); |
| } |
| |
| g_free(header); |
| } |
| |
| gboolean g_obex_header_get_unicode(GObexHeader *header, const char **str) |
| { |
| g_obex_debug(G_OBEX_DEBUG_HEADER, "header 0x%02x", |
| G_OBEX_HDR_ENC(header->id)); |
| |
| if (G_OBEX_HDR_ENC(header->id) != G_OBEX_HDR_ENC_UNICODE) |
| return FALSE; |
| |
| *str = header->v.string; |
| |
| g_obex_debug(G_OBEX_DEBUG_HEADER, "%s", *str); |
| |
| return TRUE; |
| } |
| |
| gboolean g_obex_header_get_bytes(GObexHeader *header, const guint8 **val, |
| gsize *len) |
| { |
| g_obex_debug(G_OBEX_DEBUG_HEADER, "header 0x%02x", |
| G_OBEX_HDR_ENC(header->id)); |
| |
| if (G_OBEX_HDR_ENC(header->id) != G_OBEX_HDR_ENC_BYTES) |
| return FALSE; |
| |
| *len = header->vlen; |
| |
| if (header->extdata) |
| *val = header->v.extdata; |
| else |
| *val = header->v.data; |
| |
| return TRUE; |
| } |
| |
| GObexApparam *g_obex_header_get_apparam(GObexHeader *header) |
| { |
| gboolean ret; |
| const guint8 *val; |
| gsize len; |
| |
| ret = g_obex_header_get_bytes(header, &val, &len); |
| if (!ret) |
| return NULL; |
| |
| return g_obex_apparam_decode(val, len); |
| } |
| |
| gboolean g_obex_header_get_uint8(GObexHeader *header, guint8 *val) |
| { |
| g_obex_debug(G_OBEX_DEBUG_HEADER, "header 0x%02x", |
| G_OBEX_HDR_ENC(header->id)); |
| |
| if (G_OBEX_HDR_ENC(header->id) != G_OBEX_HDR_ENC_UINT8) |
| return FALSE; |
| |
| *val = header->v.u8; |
| |
| g_obex_debug(G_OBEX_DEBUG_HEADER, "%u", *val); |
| |
| return TRUE; |
| } |
| |
| gboolean g_obex_header_get_uint32(GObexHeader *header, guint32 *val) |
| { |
| g_obex_debug(G_OBEX_DEBUG_HEADER, "header 0x%02x", |
| G_OBEX_HDR_ENC(header->id)); |
| |
| if (G_OBEX_HDR_ENC(header->id) != G_OBEX_HDR_ENC_UINT32) |
| return FALSE; |
| |
| *val = header->v.u32; |
| |
| g_obex_debug(G_OBEX_DEBUG_HEADER, "%u", *val); |
| |
| return TRUE; |
| } |
| |
| GObexHeader *g_obex_header_new_unicode(guint8 id, const char *str) |
| { |
| GObexHeader *header; |
| gsize len; |
| |
| g_obex_debug(G_OBEX_DEBUG_HEADER, "header 0x%02x", G_OBEX_HDR_ENC(id)); |
| |
| if (G_OBEX_HDR_ENC(id) != G_OBEX_HDR_ENC_UNICODE) |
| return NULL; |
| |
| header = g_new0(GObexHeader, 1); |
| |
| header->id = id; |
| |
| len = g_utf8_strlen(str, -1); |
| |
| header->vlen = len; |
| header->hlen = len == 0 ? 3 : 3 + ((len + 1) * 2); |
| header->v.string = g_strdup(str); |
| |
| g_obex_debug(G_OBEX_DEBUG_HEADER, "%s", header->v.string); |
| |
| return header; |
| } |
| |
| GObexHeader *g_obex_header_new_bytes(guint8 id, const void *data, gsize len) |
| { |
| GObexHeader *header; |
| |
| g_obex_debug(G_OBEX_DEBUG_HEADER, "header 0x%02x", G_OBEX_HDR_ENC(id)); |
| |
| if (G_OBEX_HDR_ENC(id) != G_OBEX_HDR_ENC_BYTES) |
| return NULL; |
| |
| header = g_new0(GObexHeader, 1); |
| |
| header->id = id; |
| header->vlen = len; |
| header->hlen = len + 3; |
| header->v.data = util_memdup(data, len); |
| |
| return header; |
| } |
| |
| GObexHeader *g_obex_header_new_tag(guint8 id, GObexApparam *apparam) |
| { |
| guint8 buf[1024]; |
| gssize len; |
| |
| len = g_obex_apparam_encode(apparam, buf, sizeof(buf)); |
| if (len < 0) |
| return NULL; |
| |
| return g_obex_header_new_bytes(id, buf, len); |
| } |
| |
| GObexHeader *g_obex_header_new_apparam(GObexApparam *apparam) |
| { |
| return g_obex_header_new_tag(G_OBEX_HDR_APPARAM, apparam); |
| } |
| |
| GObexHeader *g_obex_header_new_uint8(guint8 id, guint8 val) |
| { |
| GObexHeader *header; |
| |
| g_obex_debug(G_OBEX_DEBUG_HEADER, "header 0x%02x", G_OBEX_HDR_ENC(id)); |
| |
| if (G_OBEX_HDR_ENC(id) != G_OBEX_HDR_ENC_UINT8) |
| return NULL; |
| |
| header = g_new0(GObexHeader, 1); |
| |
| header->id = id; |
| header->vlen = 1; |
| header->hlen = 2; |
| header->v.u8 = val; |
| |
| g_obex_debug(G_OBEX_DEBUG_HEADER, "%u", header->v.u8); |
| |
| return header; |
| } |
| |
| GObexHeader *g_obex_header_new_uint32(guint8 id, guint32 val) |
| { |
| GObexHeader *header; |
| |
| g_obex_debug(G_OBEX_DEBUG_HEADER, "header 0x%02x", G_OBEX_HDR_ENC(id)); |
| |
| if (G_OBEX_HDR_ENC(id) != G_OBEX_HDR_ENC_UINT32) |
| return NULL; |
| |
| header = g_new0(GObexHeader, 1); |
| |
| header->id = id; |
| header->vlen = 4; |
| header->hlen = 5; |
| header->v.u32 = val; |
| |
| g_obex_debug(G_OBEX_DEBUG_HEADER, "%u", header->v.u32); |
| |
| return header; |
| } |
| |
| guint8 g_obex_header_get_id(GObexHeader *header) |
| { |
| g_obex_debug(G_OBEX_DEBUG_HEADER, "header 0x%02x id 0x%02x", |
| G_OBEX_HDR_ENC(header->id), header->id); |
| |
| return header->id; |
| } |
| |
| guint16 g_obex_header_get_length(GObexHeader *header) |
| { |
| g_obex_debug(G_OBEX_DEBUG_HEADER, "header 0x%02x length %zu", |
| G_OBEX_HDR_ENC(header->id), header->hlen); |
| |
| return header->hlen; |
| } |
| |
| GSList *g_obex_header_create_list(guint8 first_hdr_id, va_list args, |
| gsize *total_len) |
| { |
| unsigned int id = first_hdr_id; |
| GSList *l = NULL; |
| |
| g_obex_debug(G_OBEX_DEBUG_HEADER, ""); |
| |
| *total_len = 0; |
| |
| while (id != G_OBEX_HDR_INVALID) { |
| GObexHeader *hdr; |
| const char *str; |
| const void *bytes; |
| unsigned int val; |
| gsize len; |
| |
| switch (G_OBEX_HDR_ENC(id)) { |
| case G_OBEX_HDR_ENC_UNICODE: |
| str = va_arg(args, const char *); |
| hdr = g_obex_header_new_unicode(id, str); |
| break; |
| case G_OBEX_HDR_ENC_BYTES: |
| bytes = va_arg(args, void *); |
| len = va_arg(args, gsize); |
| hdr = g_obex_header_new_bytes(id, bytes, len); |
| break; |
| case G_OBEX_HDR_ENC_UINT8: |
| val = va_arg(args, unsigned int); |
| hdr = g_obex_header_new_uint8(id, val); |
| break; |
| case G_OBEX_HDR_ENC_UINT32: |
| val = va_arg(args, unsigned int); |
| hdr = g_obex_header_new_uint32(id, val); |
| break; |
| default: |
| g_assert_not_reached(); |
| } |
| |
| l = g_slist_append(l, hdr); |
| *total_len += hdr->hlen; |
| id = va_arg(args, int); |
| } |
| |
| return l; |
| } |