| /* | 
 |  * | 
 |  *  OBEX library with GLib integration | 
 |  * | 
 |  *  Copyright (C) 2011  Intel Corporation. All rights reserved. | 
 |  * | 
 |  *  This program is free software; you can redistribute it and/or modify | 
 |  *  it under the terms of the GNU General Public License as published by | 
 |  *  the Free Software Foundation; either version 2 of the License, or | 
 |  *  (at your option) any later version. | 
 |  * | 
 |  *  This program 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 General Public License for more details. | 
 |  * | 
 |  *  You should have received a copy of the GNU General Public License | 
 |  *  along with this program; if not, write to the Free Software | 
 |  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA | 
 |  * | 
 |  */ | 
 |  | 
 | #ifdef HAVE_CONFIG_H | 
 | #include <config.h> | 
 | #endif | 
 |  | 
 | #include <string.h> | 
 | #include <errno.h> | 
 |  | 
 | #include "gobex-defs.h" | 
 | #include "gobex-packet.h" | 
 | #include "gobex-debug.h" | 
 |  | 
 | #define FINAL_BIT 0x80 | 
 |  | 
 | struct _GObexPacket { | 
 | 	guint8 opcode; | 
 | 	gboolean final; | 
 |  | 
 | 	GObexDataPolicy data_policy; | 
 |  | 
 | 	union { | 
 | 		void *buf;		/* Non-header data */ | 
 | 		const void *buf_ref;	/* Reference to non-header data */ | 
 | 	} data; | 
 | 	gsize data_len; | 
 |  | 
 | 	gsize hlen;		/* Length of all encoded headers */ | 
 | 	GSList *headers; | 
 |  | 
 | 	GObexDataProducer get_body; | 
 | 	gpointer get_body_data; | 
 | }; | 
 |  | 
 | GObexHeader *g_obex_packet_get_header(GObexPacket *pkt, guint8 id) | 
 | { | 
 | 	GSList *l; | 
 |  | 
 | 	g_obex_debug(G_OBEX_DEBUG_PACKET, "opcode 0x%02x", pkt->opcode); | 
 |  | 
 | 	for (l = pkt->headers; l != NULL; l = g_slist_next(l)) { | 
 | 		GObexHeader *hdr = l->data; | 
 |  | 
 | 		if (g_obex_header_get_id(hdr) == id) | 
 | 			return hdr; | 
 | 	} | 
 |  | 
 | 	return NULL; | 
 | } | 
 |  | 
 | GObexHeader *g_obex_packet_get_body(GObexPacket *pkt) | 
 | { | 
 | 	GObexHeader *body; | 
 |  | 
 | 	g_obex_debug(G_OBEX_DEBUG_PACKET, "opcode 0x%02x", pkt->opcode); | 
 |  | 
 | 	body = g_obex_packet_get_header(pkt, G_OBEX_HDR_BODY); | 
 | 	if (body != NULL) | 
 | 		return body; | 
 |  | 
 | 	return g_obex_packet_get_header(pkt, G_OBEX_HDR_BODY_END); | 
 | } | 
 |  | 
 | guint8 g_obex_packet_get_operation(GObexPacket *pkt, gboolean *final) | 
 | { | 
 | 	g_obex_debug(G_OBEX_DEBUG_PACKET, "opcode 0x%02x", pkt->opcode); | 
 |  | 
 | 	if (final) | 
 | 		*final = pkt->final; | 
 |  | 
 | 	return pkt->opcode; | 
 | } | 
 |  | 
 | gboolean g_obex_packet_prepend_header(GObexPacket *pkt, GObexHeader *header) | 
 | { | 
 | 	g_obex_debug(G_OBEX_DEBUG_PACKET, "opcode 0x%02x", pkt->opcode); | 
 |  | 
 | 	pkt->headers = g_slist_prepend(pkt->headers, header); | 
 | 	pkt->hlen += g_obex_header_get_length(header); | 
 |  | 
 | 	return TRUE; | 
 | } | 
 |  | 
 | gboolean g_obex_packet_add_header(GObexPacket *pkt, GObexHeader *header) | 
 | { | 
 | 	g_obex_debug(G_OBEX_DEBUG_PACKET, "opcode 0x%02x", pkt->opcode); | 
 |  | 
 | 	pkt->headers = g_slist_append(pkt->headers, header); | 
 | 	pkt->hlen += g_obex_header_get_length(header); | 
 |  | 
 | 	return TRUE; | 
 | } | 
 |  | 
 | gboolean g_obex_packet_add_body(GObexPacket *pkt, GObexDataProducer func, | 
 | 							gpointer user_data) | 
 | { | 
 | 	g_obex_debug(G_OBEX_DEBUG_PACKET, "opcode 0x%02x", pkt->opcode); | 
 |  | 
 | 	if (pkt->get_body != NULL) | 
 | 		return FALSE; | 
 |  | 
 | 	pkt->get_body = func; | 
 | 	pkt->get_body_data = user_data; | 
 |  | 
 | 	return TRUE; | 
 | } | 
 |  | 
 | gboolean g_obex_packet_add_unicode(GObexPacket *pkt, guint8 id, | 
 | 							const char *str) | 
 | { | 
 | 	GObexHeader *hdr; | 
 |  | 
 | 	g_obex_debug(G_OBEX_DEBUG_PACKET, "opcode 0x%02x", pkt->opcode); | 
 |  | 
 | 	hdr = g_obex_header_new_unicode(id, str); | 
 | 	if (hdr == NULL) | 
 | 		return FALSE; | 
 |  | 
 | 	return g_obex_packet_add_header(pkt, hdr); | 
 | } | 
 |  | 
 | gboolean g_obex_packet_add_bytes(GObexPacket *pkt, guint8 id, | 
 | 						const void *data, gsize len) | 
 | { | 
 | 	GObexHeader *hdr; | 
 |  | 
 | 	g_obex_debug(G_OBEX_DEBUG_PACKET, "opcode 0x%02x", pkt->opcode); | 
 |  | 
 | 	hdr = g_obex_header_new_bytes(id, data, len); | 
 | 	if (hdr == NULL) | 
 | 		return FALSE; | 
 |  | 
 | 	return g_obex_packet_add_header(pkt, hdr); | 
 | } | 
 |  | 
 | gboolean g_obex_packet_add_uint8(GObexPacket *pkt, guint8 id, guint8 val) | 
 | { | 
 | 	GObexHeader *hdr; | 
 |  | 
 | 	g_obex_debug(G_OBEX_DEBUG_PACKET, "opcode 0x%02x", pkt->opcode); | 
 |  | 
 | 	hdr = g_obex_header_new_uint8(id, val); | 
 | 	if (hdr == NULL) | 
 | 		return FALSE; | 
 |  | 
 | 	return g_obex_packet_add_header(pkt, hdr); | 
 | } | 
 |  | 
 | gboolean g_obex_packet_add_uint32(GObexPacket *pkt, guint8 id, guint32 val) | 
 | { | 
 | 	GObexHeader *hdr; | 
 |  | 
 | 	g_obex_debug(G_OBEX_DEBUG_PACKET, "opcode 0x%02x", pkt->opcode); | 
 |  | 
 | 	hdr = g_obex_header_new_uint32(id, val); | 
 | 	if (hdr == NULL) | 
 | 		return FALSE; | 
 |  | 
 | 	return g_obex_packet_add_header(pkt, hdr); | 
 | } | 
 |  | 
 | const void *g_obex_packet_get_data(GObexPacket *pkt, gsize *len) | 
 | { | 
 | 	g_obex_debug(G_OBEX_DEBUG_PACKET, "opcode 0x%02x", pkt->opcode); | 
 |  | 
 | 	if (pkt->data_len == 0) { | 
 | 		*len = 0; | 
 | 		return NULL; | 
 | 	} | 
 |  | 
 | 	*len = pkt->data_len; | 
 |  | 
 | 	switch (pkt->data_policy) { | 
 | 	case G_OBEX_DATA_INHERIT: | 
 | 	case G_OBEX_DATA_COPY: | 
 | 		return pkt->data.buf; | 
 | 	case G_OBEX_DATA_REF: | 
 | 		return pkt->data.buf_ref; | 
 | 	} | 
 |  | 
 | 	g_assert_not_reached(); | 
 | } | 
 |  | 
 | gboolean g_obex_packet_set_data(GObexPacket *pkt, const void *data, gsize len, | 
 | 						GObexDataPolicy data_policy) | 
 | { | 
 | 	g_obex_debug(G_OBEX_DEBUG_PACKET, "opcode 0x%02x", pkt->opcode); | 
 |  | 
 | 	if (pkt->data.buf || pkt->data.buf_ref) | 
 | 		return FALSE; | 
 |  | 
 | 	pkt->data_policy = data_policy; | 
 | 	pkt->data_len = len; | 
 |  | 
 | 	switch (data_policy) { | 
 | 	case G_OBEX_DATA_COPY: | 
 | 		pkt->data.buf = g_memdup(data, len); | 
 | 		break; | 
 | 	case G_OBEX_DATA_REF: | 
 | 		pkt->data.buf_ref = data; | 
 | 		break; | 
 | 	case G_OBEX_DATA_INHERIT: | 
 | 		pkt->data.buf = (void *) data; | 
 | 		break; | 
 | 	} | 
 |  | 
 | 	return TRUE; | 
 | } | 
 |  | 
 | GObexPacket *g_obex_packet_new_valist(guint8 opcode, gboolean final, | 
 | 					guint first_hdr_id, va_list args) | 
 | { | 
 | 	GObexPacket *pkt; | 
 |  | 
 | 	g_obex_debug(G_OBEX_DEBUG_PACKET, "opcode 0x%02x", opcode); | 
 |  | 
 | 	pkt = g_new0(GObexPacket, 1); | 
 |  | 
 | 	pkt->opcode = opcode; | 
 | 	pkt->final = final; | 
 | 	pkt->headers = g_obex_header_create_list(first_hdr_id, args, | 
 | 								&pkt->hlen); | 
 | 	pkt->data_policy = G_OBEX_DATA_COPY; | 
 |  | 
 | 	return pkt; | 
 | } | 
 |  | 
 | GObexPacket *g_obex_packet_new(guint8 opcode, gboolean final, | 
 | 						guint first_hdr_id, ...) | 
 | { | 
 | 	GObexPacket *pkt; | 
 | 	va_list args; | 
 |  | 
 | 	g_obex_debug(G_OBEX_DEBUG_PACKET, "opcode 0x%02x", opcode); | 
 |  | 
 | 	va_start(args, first_hdr_id); | 
 | 	pkt = g_obex_packet_new_valist(opcode, final, first_hdr_id, args); | 
 | 	va_end(args); | 
 |  | 
 | 	return pkt; | 
 | } | 
 |  | 
 | void g_obex_packet_free(GObexPacket *pkt) | 
 | { | 
 | 	g_obex_debug(G_OBEX_DEBUG_PACKET, "opcode 0x%02x", pkt->opcode); | 
 |  | 
 | 	switch (pkt->data_policy) { | 
 | 	case G_OBEX_DATA_INHERIT: | 
 | 	case G_OBEX_DATA_COPY: | 
 | 		g_free(pkt->data.buf); | 
 | 		break; | 
 | 	case G_OBEX_DATA_REF: | 
 | 		break; | 
 | 	} | 
 |  | 
 | 	g_slist_foreach(pkt->headers, (GFunc) g_obex_header_free, NULL); | 
 | 	g_slist_free(pkt->headers); | 
 | 	g_free(pkt); | 
 | } | 
 |  | 
 | static gboolean parse_headers(GObexPacket *pkt, const void *data, gsize len, | 
 | 						GObexDataPolicy data_policy, | 
 | 						GError **err) | 
 | { | 
 | 	const guint8 *buf = data; | 
 |  | 
 | 	g_obex_debug(G_OBEX_DEBUG_PACKET, "opcode 0x%02x", pkt->opcode); | 
 |  | 
 | 	while (len > 0) { | 
 | 		GObexHeader *header; | 
 | 		gsize parsed; | 
 |  | 
 | 		header = g_obex_header_decode(buf, len, data_policy, &parsed, | 
 | 									err); | 
 | 		if (header == NULL) | 
 | 			return FALSE; | 
 |  | 
 | 		pkt->headers = g_slist_append(pkt->headers, header); | 
 | 		pkt->hlen += parsed; | 
 |  | 
 | 		len -= parsed; | 
 | 		buf += parsed; | 
 | 	} | 
 |  | 
 | 	return TRUE; | 
 | } | 
 |  | 
 | static const guint8 *get_bytes(void *to, const guint8 *from, gsize count) | 
 | { | 
 | 	memcpy(to, from, count); | 
 | 	return (from + count); | 
 | } | 
 |  | 
 | GObexPacket *g_obex_packet_decode(const void *data, gsize len, | 
 | 						gsize header_offset, | 
 | 						GObexDataPolicy data_policy, | 
 | 						GError **err) | 
 | { | 
 | 	const guint8 *buf = data; | 
 | 	guint16 packet_len; | 
 | 	guint8 opcode; | 
 | 	GObexPacket *pkt; | 
 | 	gboolean final; | 
 |  | 
 | 	g_obex_debug(G_OBEX_DEBUG_PACKET, ""); | 
 |  | 
 | 	if (data_policy == G_OBEX_DATA_INHERIT) { | 
 | 		if (!err) | 
 | 			return NULL; | 
 | 		g_set_error(err, G_OBEX_ERROR, G_OBEX_ERROR_INVALID_ARGS, | 
 | 							"Invalid data policy"); | 
 | 		g_obex_debug(G_OBEX_DEBUG_ERROR, "%s", (*err)->message); | 
 | 		return NULL; | 
 | 	} | 
 |  | 
 | 	if (len < 3 + header_offset) { | 
 | 		if (!err) | 
 | 			return NULL; | 
 | 		g_set_error(err, G_OBEX_ERROR, G_OBEX_ERROR_PARSE_ERROR, | 
 | 					"Not enough data to decode packet"); | 
 | 		g_obex_debug(G_OBEX_DEBUG_ERROR, "%s", (*err)->message); | 
 | 		return NULL; | 
 | 	} | 
 |  | 
 | 	buf = get_bytes(&opcode, buf, sizeof(opcode)); | 
 | 	buf = get_bytes(&packet_len, buf, sizeof(packet_len)); | 
 |  | 
 | 	packet_len = g_ntohs(packet_len); | 
 | 	if (packet_len != len) { | 
 | 		if (!err) | 
 | 			return NULL; | 
 | 		g_set_error(err, G_OBEX_ERROR, G_OBEX_ERROR_PARSE_ERROR, | 
 | 				"Incorrect packet length (%u != %zu)", | 
 | 				packet_len, len); | 
 | 		g_obex_debug(G_OBEX_DEBUG_ERROR, "%s", (*err)->message); | 
 | 		return NULL; | 
 | 	} | 
 |  | 
 | 	final = (opcode & FINAL_BIT) ? TRUE : FALSE; | 
 | 	opcode &= ~FINAL_BIT; | 
 |  | 
 | 	pkt = g_obex_packet_new(opcode, final, G_OBEX_HDR_INVALID); | 
 |  | 
 | 	if (header_offset == 0) | 
 | 		goto headers; | 
 |  | 
 | 	g_obex_packet_set_data(pkt, buf, header_offset, data_policy); | 
 | 	buf += header_offset; | 
 |  | 
 | headers: | 
 | 	if (!parse_headers(pkt, buf, len - (3 + header_offset), | 
 | 							data_policy, err)) | 
 | 		goto failed; | 
 |  | 
 | 	return pkt; | 
 |  | 
 | failed: | 
 | 	g_obex_packet_free(pkt); | 
 | 	return NULL; | 
 | } | 
 |  | 
 | static gssize get_body(GObexPacket *pkt, guint8 *buf, gsize len) | 
 | { | 
 | 	guint16 u16; | 
 | 	gssize ret; | 
 |  | 
 | 	g_obex_debug(G_OBEX_DEBUG_PACKET, "opcode 0x%02x", pkt->opcode); | 
 |  | 
 | 	if (len < 3) | 
 | 		return -ENOBUFS; | 
 |  | 
 | 	ret = pkt->get_body(buf + 3, len - 3, pkt->get_body_data); | 
 | 	if (ret < 0) | 
 | 		return ret; | 
 |  | 
 | 	if (ret > 0) | 
 | 		buf[0] = G_OBEX_HDR_BODY; | 
 | 	else | 
 | 		buf[0] = G_OBEX_HDR_BODY_END; | 
 |  | 
 | 	u16 = g_htons(ret + 3); | 
 | 	memcpy(&buf[1], &u16, sizeof(u16)); | 
 |  | 
 | 	return ret; | 
 | } | 
 |  | 
 | gssize g_obex_packet_encode(GObexPacket *pkt, guint8 *buf, gsize len) | 
 | { | 
 | 	gssize ret; | 
 | 	gsize count; | 
 | 	guint16 u16; | 
 | 	GSList *l; | 
 |  | 
 | 	g_obex_debug(G_OBEX_DEBUG_PACKET, "opcode 0x%02x", pkt->opcode); | 
 |  | 
 | 	if (3 + pkt->data_len + pkt->hlen > len) | 
 | 		return -ENOBUFS; | 
 |  | 
 | 	buf[0] = pkt->opcode; | 
 | 	if (pkt->final) | 
 | 		buf[0] |= FINAL_BIT; | 
 |  | 
 | 	if (pkt->data_len > 0) { | 
 | 		if (pkt->data_policy == G_OBEX_DATA_REF) | 
 | 			memcpy(&buf[3], pkt->data.buf_ref, pkt->data_len); | 
 | 		else | 
 | 			memcpy(&buf[3], pkt->data.buf, pkt->data_len); | 
 | 	} | 
 |  | 
 | 	count = 3 + pkt->data_len; | 
 |  | 
 | 	for (l = pkt->headers; l != NULL; l = g_slist_next(l)) { | 
 | 		GObexHeader *hdr = l->data; | 
 |  | 
 | 		if (count >= len) | 
 | 			return -ENOBUFS; | 
 |  | 
 | 		ret = g_obex_header_encode(hdr, buf + count, len - count); | 
 | 		if (ret < 0) | 
 | 			return ret; | 
 |  | 
 | 		count += ret; | 
 | 	} | 
 |  | 
 | 	if (pkt->get_body) { | 
 | 		ret = get_body(pkt, buf + count, len - count); | 
 | 		if (ret < 0) | 
 | 			return ret; | 
 | 		if (ret == 0) { | 
 | 			if (pkt->opcode == G_OBEX_RSP_CONTINUE) | 
 | 				buf[0] = G_OBEX_RSP_SUCCESS; | 
 | 			buf[0] |= FINAL_BIT; | 
 | 		} | 
 |  | 
 | 		count += ret + 3; | 
 | 	} | 
 |  | 
 | 	u16 = g_htons(count); | 
 | 	memcpy(&buf[1], &u16, sizeof(u16)); | 
 |  | 
 | 	return count; | 
 | } |