Blob Blame History Raw
/*
 *
 *  BlueZ - Bluetooth protocol stack for Linux
 *
 *  Copyright (C) 2001-2002  Ricky Yuen <ryuen@qualcomm.com>
 *  Copyright (C) 2003-2011  Marcel Holtmann <marcel@holtmann.org>
 *
 *
 *  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

#define _GNU_SOURCE
#include <stdio.h>
#include <errno.h>
#include <ctype.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

#include "parser.h"
#include "sdp.h"

#define SDP_ERROR_RSP                                  0x01
#define SDP_SERVICE_SEARCH_REQ                         0x02
#define SDP_SERVICE_SEARCH_RSP                         0x03
#define SDP_SERVICE_ATTR_REQ                           0x04
#define SDP_SERVICE_ATTR_RSP                           0x05
#define SDP_SERVICE_SEARCH_ATTR_REQ                    0x06
#define SDP_SERVICE_SEARCH_ATTR_RSP                    0x07

typedef struct {
	uint8_t  pid;
	uint16_t tid;
	uint16_t len;
} __attribute__ ((packed)) sdp_pdu_hdr;
#define SDP_PDU_HDR_SIZE 5

/* Data element type descriptor */
#define SDP_DE_NULL   0
#define SDP_DE_UINT   1
#define SDP_DE_INT    2
#define SDP_DE_UUID   3
#define SDP_DE_STRING 4
#define SDP_DE_BOOL   5
#define SDP_DE_SEQ    6
#define SDP_DE_ALT    7
#define SDP_DE_URL    8

/* Data element size index lookup table */
typedef struct {
	int addl_bits;
	int num_bytes;
} sdp_siz_idx_lookup_table_t;

static sdp_siz_idx_lookup_table_t sdp_siz_idx_lookup_table[] = {
	{ 0, 1  }, /* Size index = 0 */
	{ 0, 2  }, /*              1 */
	{ 0, 4  }, /*              2 */
	{ 0, 8  }, /*              3 */
	{ 0, 16 }, /*              4 */
	{ 1, 1  }, /*              5 */
	{ 1, 2  }, /*              6 */
	{ 1, 4  }, /*              7 */
};

/* UUID name lookup table */
typedef struct {
	int   uuid;
	char* name;
} sdp_uuid_nam_lookup_table_t;

static sdp_uuid_nam_lookup_table_t sdp_uuid_nam_lookup_table[] = {
	{ SDP_UUID_SDP,                      "SDP"          },
	{ SDP_UUID_UDP,                      "UDP"          },
	{ SDP_UUID_RFCOMM,                   "RFCOMM"       },
	{ SDP_UUID_TCP,                      "TCP"          },
	{ SDP_UUID_TCS_BIN,                  "TCS-BIN"      },
	{ SDP_UUID_TCS_AT,                   "TCS-AT"       },
	{ SDP_UUID_OBEX,                     "OBEX"         },
	{ SDP_UUID_IP,                       "IP"           },
	{ SDP_UUID_FTP,                      "FTP"          },
	{ SDP_UUID_HTTP,                     "HTTP"         },
	{ SDP_UUID_WSP,                      "WSP"          },
	{ SDP_UUID_L2CAP,                    "L2CAP"        },
	{ SDP_UUID_BNEP,                     "BNEP"         }, /* PAN */
	{ SDP_UUID_HIDP,                     "HIDP"         }, /* HID */
	{ SDP_UUID_AVCTP,                    "AVCTP"        }, /* AVCTP */
	{ SDP_UUID_AVDTP,                    "AVDTP"        }, /* AVDTP */
	{ SDP_UUID_CMTP,                     "CMTP"         }, /* CIP */
	{ SDP_UUID_UDI_C_PLANE,              "UDI_C-Plane"  }, /* UDI */
	{ SDP_UUID_SERVICE_DISCOVERY_SERVER, "SDServer"     },
	{ SDP_UUID_BROWSE_GROUP_DESCRIPTOR,  "BrwsGrpDesc"  },
	{ SDP_UUID_PUBLIC_BROWSE_GROUP,      "PubBrwsGrp"   },
	{ SDP_UUID_SERIAL_PORT,              "SP"           },
	{ SDP_UUID_LAN_ACCESS_PPP,           "LAN"          },
	{ SDP_UUID_DIALUP_NETWORKING,        "DUN"          },
	{ SDP_UUID_IR_MC_SYNC,               "IRMCSync"     },
	{ SDP_UUID_OBEX_OBJECT_PUSH,         "OBEXObjPush"  },
	{ SDP_UUID_OBEX_FILE_TRANSFER,       "OBEXObjTrnsf" },
	{ SDP_UUID_IR_MC_SYNC_COMMAND,       "IRMCSyncCmd"  },
	{ SDP_UUID_HEADSET,                  "Headset"      },
	{ SDP_UUID_CORDLESS_TELEPHONY,       "CordlessTel"  },
	{ SDP_UUID_AUDIO_SOURCE,             "AudioSource"  }, /* A2DP */
	{ SDP_UUID_AUDIO_SINK,               "AudioSink"    }, /* A2DP */
	{ SDP_UUID_AV_REMOTE_TARGET,         "AVRemTarget"  }, /* AVRCP */
	{ SDP_UUID_ADVANCED_AUDIO,           "AdvAudio"     }, /* A2DP */
	{ SDP_UUID_AV_REMOTE,                "AVRemote"     }, /* AVRCP */
	{ SDP_UUID_AV_REMOTE_CONTROLLER,     "AVRemCt"      }, /* AVRCP */
	{ SDP_UUID_INTERCOM,                 "Intercom"     },
	{ SDP_UUID_FAX,                      "Fax"          },
	{ SDP_UUID_HEADSET_AUDIO_GATEWAY,    "Headset AG"   },
	{ SDP_UUID_WAP,                      "WAP"          },
	{ SDP_UUID_WAP_CLIENT,               "WAP Client"   },
	{ SDP_UUID_PANU,                     "PANU"         }, /* PAN */
	{ SDP_UUID_NAP,                      "NAP"          }, /* PAN */
	{ SDP_UUID_GN,                       "GN"           }, /* PAN */
	{ SDP_UUID_DIRECT_PRINTING,          "DirectPrint"  }, /* BPP */
	{ SDP_UUID_REFERENCE_PRINTING,       "RefPrint"     }, /* BPP */
	{ SDP_UUID_IMAGING,                  "Imaging"      }, /* BIP */
	{ SDP_UUID_IMAGING_RESPONDER,        "ImagingResp"  }, /* BIP */
	{ SDP_UUID_HANDSFREE,                "Handsfree"    },
	{ SDP_UUID_HANDSFREE_AUDIO_GATEWAY,  "Handsfree AG" },
	{ SDP_UUID_DIRECT_PRINTING_REF_OBJS, "RefObjsPrint" }, /* BPP */
	{ SDP_UUID_REFLECTED_UI,             "ReflectedUI"  }, /* BPP */
	{ SDP_UUID_BASIC_PRINTING,           "BasicPrint"   }, /* BPP */
	{ SDP_UUID_PRINTING_STATUS,          "PrintStatus"  }, /* BPP */
	{ SDP_UUID_HUMAN_INTERFACE_DEVICE,   "HID"          }, /* HID */
	{ SDP_UUID_HARDCOPY_CABLE_REPLACE,   "HCRP"         }, /* HCRP */
	{ SDP_UUID_HCR_PRINT,                "HCRPrint"     }, /* HCRP */
	{ SDP_UUID_HCR_SCAN,                 "HCRScan"      }, /* HCRP */
	{ SDP_UUID_COMMON_ISDN_ACCESS,       "CIP"          }, /* CIP */
	{ SDP_UUID_UDI_MT,                   "UDI MT"       }, /* UDI */
	{ SDP_UUID_UDI_TA,                   "UDI TA"       }, /* UDI */
	{ SDP_UUID_AUDIO_VIDEO,              "AudioVideo"   }, /* VCP */
	{ SDP_UUID_SIM_ACCESS,               "SAP"          }, /* SAP */
	{ SDP_UUID_PHONEBOOK_ACCESS_PCE,     "PBAP PCE"     }, /* PBAP */
	{ SDP_UUID_PHONEBOOK_ACCESS_PSE,     "PBAP PSE"     }, /* PBAP */
	{ SDP_UUID_PHONEBOOK_ACCESS,         "PBAP"         }, /* PBAP */
	{ SDP_UUID_PNP_INFORMATION,          "PNPInfo"      },
	{ SDP_UUID_GENERIC_NETWORKING,       "Networking"   },
	{ SDP_UUID_GENERIC_FILE_TRANSFER,    "FileTrnsf"    },
	{ SDP_UUID_GENERIC_AUDIO,            "Audio"        },
	{ SDP_UUID_GENERIC_TELEPHONY,        "Telephony"    },
	{ SDP_UUID_UPNP_SERVICE,             "UPNP"         }, /* ESDP */
	{ SDP_UUID_UPNP_IP_SERVICE,          "UPNP IP"      }, /* ESDP */
	{ SDP_UUID_ESDP_UPNP_IP_PAN,         "UPNP PAN"     }, /* ESDP */
	{ SDP_UUID_ESDP_UPNP_IP_LAP,         "UPNP LAP"     }, /* ESDP */
	{ SDP_UUID_ESDP_UPNP_L2CAP,          "UPNP L2CAP"   }, /* ESDP */
	{ SDP_UUID_VIDEO_SOURCE,             "VideoSource"  }, /* VDP */
	{ SDP_UUID_VIDEO_SINK,               "VideoSink"    }, /* VDP */
	{ SDP_UUID_VIDEO_DISTRIBUTION,       "VideoDist"    }, /* VDP */
	{ SDP_UUID_APPLE_AGENT,              "AppleAgent"   },
};

#define SDP_UUID_NAM_LOOKUP_TABLE_SIZE \
	(sizeof(sdp_uuid_nam_lookup_table)/sizeof(sdp_uuid_nam_lookup_table_t))

/* AttrID name lookup table */
typedef struct {
	int   attr_id;
	char* name;
} sdp_attr_id_nam_lookup_table_t;

static sdp_attr_id_nam_lookup_table_t sdp_attr_id_nam_lookup_table[] = {
	{ SDP_ATTR_ID_SERVICE_RECORD_HANDLE,             "SrvRecHndl"         },
	{ SDP_ATTR_ID_SERVICE_CLASS_ID_LIST,             "SrvClassIDList"     },
	{ SDP_ATTR_ID_SERVICE_RECORD_STATE,              "SrvRecState"        },
	{ SDP_ATTR_ID_SERVICE_SERVICE_ID,                "SrvID"              },
	{ SDP_ATTR_ID_PROTOCOL_DESCRIPTOR_LIST,          "ProtocolDescList"   },
	{ SDP_ATTR_ID_BROWSE_GROUP_LIST,                 "BrwGrpList"         },
	{ SDP_ATTR_ID_LANGUAGE_BASE_ATTRIBUTE_ID_LIST,   "LangBaseAttrIDList" },
	{ SDP_ATTR_ID_SERVICE_INFO_TIME_TO_LIVE,         "SrvInfoTimeToLive"  },
	{ SDP_ATTR_ID_SERVICE_AVAILABILITY,              "SrvAvail"           },
	{ SDP_ATTR_ID_BLUETOOTH_PROFILE_DESCRIPTOR_LIST, "BTProfileDescList"  },
	{ SDP_ATTR_ID_DOCUMENTATION_URL,                 "DocURL"             },
	{ SDP_ATTR_ID_CLIENT_EXECUTABLE_URL,             "ClientExeURL"       },
	{ SDP_ATTR_ID_ICON_URL,                          "IconURL"            },
	{ SDP_ATTR_ID_ADDITIONAL_PROTOCOL_DESC_LISTS,    "AdditionalProtocolDescLists" },
	{ SDP_ATTR_ID_SERVICE_NAME,                      "SrvName"            },
	{ SDP_ATTR_ID_SERVICE_DESCRIPTION,               "SrvDesc"            },
	{ SDP_ATTR_ID_PROVIDER_NAME,                     "ProviderName"       },
	{ SDP_ATTR_ID_VERSION_NUMBER_LIST,               "VersionNumList"     },
	{ SDP_ATTR_ID_GROUP_ID,                          "GrpID"              },
	{ SDP_ATTR_ID_SERVICE_DATABASE_STATE,            "SrvDBState"         },
	{ SDP_ATTR_ID_SERVICE_VERSION,                   "SrvVersion"         },
	{ SDP_ATTR_ID_SECURITY_DESCRIPTION,              "SecurityDescription"}, /* PAN */
	{ SDP_ATTR_ID_SUPPORTED_DATA_STORES_LIST,        "SuppDataStoresList" }, /* Synchronization */
	{ SDP_ATTR_ID_SUPPORTED_FORMATS_LIST,            "SuppFormatsList"    }, /* OBEX Object Push */
	{ SDP_ATTR_ID_NET_ACCESS_TYPE,                   "NetAccessType"      }, /* PAN */
	{ SDP_ATTR_ID_MAX_NET_ACCESS_RATE,               "MaxNetAccessRate"   }, /* PAN */
	{ SDP_ATTR_ID_IPV4_SUBNET,                       "IPv4Subnet"         }, /* PAN */
	{ SDP_ATTR_ID_IPV6_SUBNET,                       "IPv6Subnet"         }, /* PAN */
	{ SDP_ATTR_ID_SUPPORTED_CAPABILITIES,            "SuppCapabilities"   }, /* Imaging */
	{ SDP_ATTR_ID_SUPPORTED_FEATURES,                "SuppFeatures"       }, /* Imaging and Hansfree */
	{ SDP_ATTR_ID_SUPPORTED_FUNCTIONS,               "SuppFunctions"      }, /* Imaging */
	{ SDP_ATTR_ID_TOTAL_IMAGING_DATA_CAPACITY,       "SuppTotalCapacity"  }, /* Imaging */
	{ SDP_ATTR_ID_SUPPORTED_REPOSITORIES,            "SuppRepositories"   }, /* PBAP */
};

#define SDP_ATTR_ID_NAM_LOOKUP_TABLE_SIZE \
	(sizeof(sdp_attr_id_nam_lookup_table)/sizeof(sdp_attr_id_nam_lookup_table_t))

char* get_uuid_name(int uuid)
{
	unsigned int i;

	for (i = 0; i < SDP_UUID_NAM_LOOKUP_TABLE_SIZE; i++) {
		if (sdp_uuid_nam_lookup_table[i].uuid == uuid)
			return sdp_uuid_nam_lookup_table[i].name;
	}

	return 0;
}

static inline char* get_attr_id_name(int attr_id)
{
	unsigned int i;

	for (i = 0; i < SDP_ATTR_ID_NAM_LOOKUP_TABLE_SIZE; i++)
		if (sdp_attr_id_nam_lookup_table[i].attr_id == attr_id)
			return sdp_attr_id_nam_lookup_table[i].name;
	return 0;
}

static inline uint8_t parse_de_hdr(struct frame *frm, int *n)
{
	uint8_t de_hdr = p_get_u8(frm);
	uint8_t de_type = de_hdr >> 3;
	uint8_t siz_idx = de_hdr & 0x07;

	/* Get the number of bytes */
	if (sdp_siz_idx_lookup_table[siz_idx].addl_bits) {
		switch(sdp_siz_idx_lookup_table[siz_idx].num_bytes) {
		case 1:
			*n = p_get_u8(frm); break;
		case 2:
			*n = p_get_u16(frm); break;
		case 4:
			*n = p_get_u32(frm); break;
		case 8:
			*n = p_get_u64(frm); break;
		}
	} else
		*n = sdp_siz_idx_lookup_table[siz_idx].num_bytes;

	return de_type;
}

static inline void print_int(uint8_t de_type, int level, int n, struct frame *frm, uint16_t *psm, uint8_t *channel)
{
	uint64_t val, val2;

	switch(de_type) {
	case SDP_DE_UINT:
		printf(" uint");
		break;
	case SDP_DE_INT:
		printf(" int");
		break;
	case SDP_DE_BOOL:
		printf(" bool");
		break;
	}

	switch(n) {
	case 1: /* 8-bit */
		val = p_get_u8(frm);
		if (channel && de_type == SDP_DE_UINT)
			if (*channel == 0)
				*channel = val;
		break;
	case 2: /* 16-bit */
		val = p_get_u16(frm);
		if (psm && de_type == SDP_DE_UINT)
			if (*psm == 0)
				*psm = val;
		break;
	case 4: /* 32-bit */
		val = p_get_u32(frm);
		break;
	case 8: /* 64-bit */
		val = p_get_u64(frm);
		break;
	case 16:/* 128-bit */
		p_get_u128(frm, &val, &val2);
		printf(" 0x%jx", val2);
		if (val < 0x1000000000000000LL)
			printf("0");
		printf("%jx", val);
		return;
	default: /* syntax error */
		printf(" err");
		frm->ptr += n;
		frm->len -= n;
		return;
	}

	printf(" 0x%jx", val);
}

static inline void print_uuid(int n, struct frame *frm, uint16_t *psm, uint8_t *channel)
{
	uint32_t uuid = 0;
	char* s;
	int i;

	switch(n) {
	case 2: /* 16-bit UUID */
		uuid = p_get_u16(frm);
		s = "uuid-16";
		break;
	case 4: /* 32_bit UUID */
		uuid = p_get_u32(frm);
		s = "uuid-32";
		break;
	case 16: /* 128-bit UUID */
		printf(" uuid-128 ");
		for (i = 0; i < 16; i++) {
			printf("%02x", ((unsigned char *) frm->ptr)[i]);
			if (i == 3 || i == 5 || i == 7 || i == 9)
				printf("-");
		}
		frm->ptr += 16;
		frm->len -= 16;
		return;
	default: /* syntax error */
		printf(" *err*");
		frm->ptr += n;
		frm->len -= n;
		return;
	}

	if (psm && *psm > 0 && *psm != 0xffff) {
		set_proto(frm->handle, *psm, 0, uuid);
		*psm = 0xffff;
	}

	if (channel && *channel > 0 && *channel != 0xff) {
		set_proto(frm->handle, *psm, *channel, uuid);
		*channel = 0xff;
	}

	printf(" %s 0x%04x", s, uuid);
	if ((s = get_uuid_name(uuid)))
		printf(" (%s)", s);
}

static inline void print_string(int n, struct frame *frm, const char *name)
{
	int i, hex = 0;

	for (i = 0; i < n; i++) {
		if (i == (n - 1) && ((char *) frm->ptr)[i] == '\0')
			break;

		if (!isprint(((char *) frm->ptr)[i])) {
			hex = 1;
			break;
		}
	}

	printf(" %s", name);
	if (hex) {
		for (i = 0; i < n; i++)
			printf(" %02x", ((unsigned char *) frm->ptr)[i]);
	} else {
		printf(" \"");
		for (i = 0; i < n; i++)
			printf("%c", ((char *) frm->ptr)[i]);
		printf("\"");
	}

	frm->ptr += n;
	frm->len -= n;
}

static inline void print_de(int, struct frame *frm, int *split, uint16_t *psm, uint8_t *channel);

static inline void print_des(uint8_t de_type, int level, int n, struct frame *frm, int *split, uint16_t *psm, uint8_t *channel)
{
	int len = frm->len;
	while (len - (int) frm->len < n && (int) frm->len > 0)
		print_de(level, frm, split, psm, channel);
}

static inline void print_de(int level, struct frame *frm, int *split, uint16_t *psm, uint8_t *channel)
{
	int n = 0;
	uint8_t de_type = parse_de_hdr(frm, &n);

	switch (de_type) {
	case SDP_DE_NULL:
		printf(" null");
		break;
	case SDP_DE_UINT:
	case SDP_DE_INT:
	case SDP_DE_BOOL:
		print_int(de_type, level, n, frm, psm, channel);
		break;
	case SDP_DE_UUID:
		if (split) {
			/* Split output by uuids.
			 * Used for printing Protocol Desc List */
			if (*split) {
				printf("\n");
				p_indent(level, NULL);
			}
			++*split;
		}
		print_uuid(n, frm, psm, channel);
		break;
	case SDP_DE_URL:
	case SDP_DE_STRING:
		print_string(n, frm, de_type == SDP_DE_URL? "url": "str");
		break;
	case SDP_DE_SEQ:
		printf(" <");
		print_des(de_type, level, n, frm, split, psm, channel);
		printf(" >");
		break;
	case SDP_DE_ALT:
		printf(" [");
		print_des(de_type, level, n, frm, split, psm, channel);
		printf(" ]");
		break;
	}
}

static inline void print_srv_srch_pat(int level, struct frame *frm)
{
	int len, n1 = 0, n2 = 0;

	p_indent(level, frm);
	printf("pat");

	if (parse_de_hdr(frm, &n1) == SDP_DE_SEQ) {
		len = frm->len;
		while (len - (int) frm->len < n1 && (int) frm->len > 0) {
			if (parse_de_hdr(frm, &n2) == SDP_DE_UUID) {
				print_uuid(n2, frm, NULL, NULL);
			} else {
				printf("\nERROR: Unexpected syntax (UUID)\n");
				raw_dump(level, frm);
			}
		}
		printf("\n");
	} else {
		printf("\nERROR: Unexpected syntax (SEQ)\n");
		raw_dump(level, frm);
	}
}

static inline void print_attr_id_list(int level, struct frame *frm)
{
	uint16_t attr_id;
	uint32_t attr_id_range;
	int len, n1 = 0, n2 = 0;

	p_indent(level, frm);
	printf("aid(s)");

	if (parse_de_hdr(frm, &n1) == SDP_DE_SEQ) {
		len = frm->len;
		while (len - (int) frm->len < n1 && (int) frm->len > 0) {
			/* Print AttributeID */
			if (parse_de_hdr(frm, &n2) == SDP_DE_UINT) {
				char *name;
				switch(n2) {
				case 2:
					attr_id = p_get_u16(frm);
					name = get_attr_id_name(attr_id);
					if (!name)
						name = "unknown";
					printf(" 0x%04x (%s)", attr_id, name);
					break;
				case 4:
					attr_id_range = p_get_u32(frm);
					printf(" 0x%04x - 0x%04x",
							(attr_id_range >> 16),
							(attr_id_range & 0xFFFF));
					break;
				}
			} else {
				printf("\nERROR: Unexpected syntax\n");
				raw_dump(level, frm);
			}
		}
		printf("\n");
	} else {
		printf("\nERROR: Unexpected syntax\n");
		raw_dump(level, frm);
	}
}

static inline void print_attr_list(int level, struct frame *frm)
{
	uint16_t attr_id, psm;
	uint8_t channel;
	int len, split, n1 = 0, n2 = 0;

	if (parse_de_hdr(frm, &n1) == SDP_DE_SEQ) {
		len = frm->len;
		while (len - (int) frm->len < n1 && (int) frm->len > 0) {
			/* Print AttributeID */
			if (parse_de_hdr(frm, &n2) == SDP_DE_UINT && n2 == sizeof(attr_id)) {
				char *name;
				attr_id = p_get_u16(frm);
				p_indent(level, 0);
				name = get_attr_id_name(attr_id);
				if (!name)
					name = "unknown";
				printf("aid 0x%04x (%s)\n", attr_id, name);
				split = (attr_id != SDP_ATTR_ID_PROTOCOL_DESCRIPTOR_LIST);
				psm = 0;
				channel = 0;

				/* Print AttributeValue */
				p_indent(level + 1, 0);
				print_de(level + 1, frm, split ? NULL: &split,
					attr_id == SDP_ATTR_ID_PROTOCOL_DESCRIPTOR_LIST ? &psm : NULL,
					attr_id == SDP_ATTR_ID_PROTOCOL_DESCRIPTOR_LIST ? &channel : NULL);
				printf("\n");
			} else {
				printf("\nERROR: Unexpected syntax\n");
				raw_dump(level, frm);
				break;
			}
		}
	} else {
		printf("\nERROR: Unexpected syntax\n");
		raw_dump(level, frm);
	}
}

static inline void print_attr_lists(int level, struct frame *frm)
{
	int n = 0, cnt = 0;
	int count = frm->len;

	if (parse_de_hdr(frm, &n) == SDP_DE_SEQ) {
		while (count - (int) frm->len < n && (int) frm->len > 0) {
			p_indent(level, 0);
			printf("record #%d\n", cnt++);
			print_attr_list(level + 2, frm);
		}
	} else {
		printf("\nERROR: Unexpected syntax\n");
		raw_dump(level, frm);
	}
}

static inline void print_cont_state(int level, unsigned char *buf)
{
	uint8_t cont = buf[0];
	int i;

	p_indent(level, 0);
	printf("cont");
	for (i = 0; i < cont + 1; i++)
		printf(" %2.2X", buf[i]);
	printf("\n");
}

static char *pid2str(uint8_t pid)
{
	switch (pid) {
	case SDP_ERROR_RSP:
		return "Error Rsp";
	case SDP_SERVICE_SEARCH_REQ:
		return "SS Req";
	case SDP_SERVICE_SEARCH_RSP:
		return "SS Rsp";
	case SDP_SERVICE_ATTR_REQ:
		return "SA Req";
	case SDP_SERVICE_ATTR_RSP:
		return "SA Rsp";
	case SDP_SERVICE_SEARCH_ATTR_REQ:
		return "SSA Req";
	case SDP_SERVICE_SEARCH_ATTR_RSP:
		return "SSA Rsp";
	default:
		return "Unknown";
	}
}

#define FRAME_TABLE_SIZE 10

static struct frame frame_table[FRAME_TABLE_SIZE];

static int frame_add(struct frame *frm, int count)
{
	register struct frame *fr;
	register unsigned char *data;
	register int i, len = 0, pos = -1;

	for (i = 0; i < FRAME_TABLE_SIZE; i++) {
		if (frame_table[i].handle == frm->handle &&
				frame_table[i].cid == frm->cid) {
			pos = i;
			len = frame_table[i].data_len;
			break;
		}
		if (pos < 0 && !frame_table[i].handle)
			pos = i;
	}

	if (pos < 0 || count <= 0)
		return -EIO;

	data = malloc(len + count);
	if (!data)
		return -ENOMEM;

	fr = &frame_table[pos];

	if (len > 0) {
		memcpy(data, fr->data, len);
		memcpy(data + len, frm->ptr, count);
	} else
		memcpy(data, frm->ptr, count);

	if (fr->data)
		free(fr->data);

	fr->data       = data;
	fr->data_len   = len + count;
	fr->len        = fr->data_len;
	fr->ptr        = fr->data;
	fr->dev_id     = frm->dev_id;
	fr->in         = frm->in;
	fr->ts         = frm->ts;
	fr->handle     = frm->handle;
	fr->cid        = frm->cid;
	fr->num        = frm->num;
	fr->channel    = frm->channel;
	fr->pppdump_fd = frm->pppdump_fd;
	fr->audio_fd   = frm->audio_fd;

	return pos;
}

static struct frame *frame_get(struct frame *frm, int count)
{
	register int pos;

	pos = frame_add(frm, count);
	if (pos < 0)
		return frm;

	frame_table[pos].handle = 0;

	return &frame_table[pos];
}

void sdp_dump(int level, struct frame *frm)
{
	sdp_pdu_hdr *hdr = frm->ptr;
	uint16_t tid = ntohs(hdr->tid);
	uint16_t len = ntohs(hdr->len);
	uint16_t total, count;
	uint8_t cont;

	frm->ptr += SDP_PDU_HDR_SIZE;
	frm->len -= SDP_PDU_HDR_SIZE;

	p_indent(level, frm);
	printf("SDP %s: tid 0x%x len 0x%x\n", pid2str(hdr->pid), tid, len);

	switch (hdr->pid) {
	case SDP_ERROR_RSP:
		p_indent(level + 1, frm);
		printf("code 0x%x info ", p_get_u16(frm));
		if (frm->len > 0)
			hex_dump(0, frm, frm->len);
		else
			printf("none\n");
		break;

	case SDP_SERVICE_SEARCH_REQ:
		/* Parse ServiceSearchPattern */
		print_srv_srch_pat(level + 1, frm);

		/* Parse MaximumServiceRecordCount */
		p_indent(level + 1, frm);
		printf("max %d\n", p_get_u16(frm));

		/* Parse ContinuationState */
		print_cont_state(level + 1, frm->ptr);
		break;

	case SDP_SERVICE_SEARCH_RSP:
		/* Parse TotalServiceRecordCount */
		total = p_get_u16(frm);

		/* Parse CurrentServiceRecordCount */
		count = p_get_u16(frm);
		p_indent(level + 1, frm);
		if (count < total)
			printf("count %d of %d\n", count, total);
		else
			printf("count %d\n", count);

		/* Parse service record handle(s) */
		if (count > 0) {
			int i;
			p_indent(level + 1, frm);
			printf("handle%s", count > 1 ? "s" : "");
			for (i = 0; i < count; i++)
				printf(" 0x%x", p_get_u32(frm));
			printf("\n");
		}

		/* Parse ContinuationState */
		print_cont_state(level + 1, frm->ptr);
		break;

	case SDP_SERVICE_ATTR_REQ:
		/* Parse ServiceRecordHandle */
		p_indent(level + 1, frm);
		printf("handle 0x%x\n", p_get_u32(frm));

		/* Parse MaximumAttributeByteCount */
		p_indent(level + 1, frm);
		printf("max %d\n", p_get_u16(frm));

		/* Parse ServiceSearchPattern */
		print_attr_id_list(level + 1, frm);

		/* Parse ContinuationState */
		print_cont_state(level + 1, frm->ptr);
		break;

	case SDP_SERVICE_ATTR_RSP:
		/* Parse AttributeByteCount */
		count = p_get_u16(frm);
		p_indent(level + 1, frm);
		printf("count %d\n", count);

		/* Parse ContinuationState */
		cont = *(unsigned char *)(frm->ptr + count);

		if (cont == 0) {
			/* Parse AttributeList */
			print_attr_list(level + 1, frame_get(frm, count));
		} else
			frame_add(frm, count);

		print_cont_state(level + 1, frm->ptr + count);
		break;

	case SDP_SERVICE_SEARCH_ATTR_REQ:
		/* Parse ServiceSearchPattern */
		print_srv_srch_pat(level + 1, frm);

		/* Parse MaximumAttributeByteCount */
		p_indent(level + 1, frm);
		printf("max %d\n", p_get_u16(frm));

		/* Parse AttributeList */
		print_attr_id_list(level + 1, frm);

		/* Parse ContinuationState */
		print_cont_state(level + 1, frm->ptr);
		break;

	case SDP_SERVICE_SEARCH_ATTR_RSP:
		/* Parse AttributeByteCount */
		count = p_get_u16(frm);
		p_indent(level + 1, frm);
		printf("count %d\n", count);

		/* Parse ContinuationState */
		cont = *(unsigned char *)(frm->ptr + count);

		if (cont == 0) {
			/* Parse AttributeLists */
			print_attr_lists(level + 1, frame_get(frm, count));
		} else
			frame_add(frm, count);

		print_cont_state(level + 1, frm->ptr + count);
		break;

	default:
		raw_dump(level + 1, frm);
		break;
	}
}