#include <sys/cdefs.h>
__RCSID("$NetBSD: service.c,v 1.5 2024/02/05 21:46:07 andvar Exp $");
#include <bluetooth.h>
#include <sdp.h>
#include "sdpd.h"
typedef struct {
uint8_t *start;
uint8_t *next;
uint8_t *end;
} sdpd_data_t;
static bool sdpd_valid_ssp(sdp_data_t *);
static bool sdpd_valid_ail(sdp_data_t *);
static bool sdpd_match_ail(record_t *, sdp_data_t, sdpd_data_t *);
static void sdpd_put_byte(sdpd_data_t *, uint8_t);
static void sdpd_put_attr(sdpd_data_t *, uint16_t, sdp_data_t *);
static void sdpd_open_seq(sdpd_data_t *);
static void sdpd_close_seq(sdpd_data_t *, uint8_t *);
uint16_t
service_search_request(server_t *srv, int fd)
{
record_t *r;
sdp_data_t d, s;
int max, total, count;
log_debug("ServiceSearchRequest by client on fd#%d", fd);
d.next = srv->ibuf;
d.end = srv->ibuf + srv->pdu.len;
if (!sdp_get_seq(&d, &s)
|| !sdpd_valid_ssp(&s))
return SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX;
if (d.next + sizeof(uint16_t) > d.end)
return SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX;
max = be16dec(d.next);
d.next += sizeof(uint16_t);
if (max < 0x0001)
return SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX;
if (d.next + 1 > d.end
|| d.next[0] > 16
|| d.next + 1 + d.next[0] != d.end)
return SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX;
if (d.next[0] == 0) {
srv->fdidx[fd].offset = 0;
db_unselect(srv, fd);
db_select_ssp(srv, fd, &s);
} else if (srv->fdidx[fd].offset == 0
|| d.next[0] != sizeof(uint16_t)
|| be16dec(d.next + 1) != srv->fdidx[fd].offset)
return SDP_ERROR_CODE_INVALID_CONTINUATION_STATE;
d.next = srv->obuf + sizeof(uint16_t) + sizeof(uint16_t);
d.end = srv->obuf + srv->fdidx[fd].omtu - 1 - sizeof(uint16_t);
count = total = 0;
if (d.next + sizeof(uint32_t) > d.end)
return SDP_ERROR_CODE_INSUFFICIENT_RESOURCES;
r = NULL;
while (db_next(srv, fd, &r) && total < max) {
if (total >= srv->fdidx[fd].offset
&& d.next + sizeof(uint32_t) <= d.end) {
be32enc(d.next, r->handle);
d.next += sizeof(uint32_t);
count++;
}
total++;
}
be16enc(srv->obuf, total);
be16enc(srv->obuf + sizeof(uint16_t), count);
if (r == NULL || total == max) {
srv->fdidx[fd].offset = 0;
db_unselect(srv, fd);
d.next[0] = 0;
d.next += 1;
} else {
srv->fdidx[fd].offset += count;
d.next[0] = sizeof(uint16_t);
be16enc(d.next + 1, srv->fdidx[fd].offset);
d.next += 1 + sizeof(uint16_t);
}
srv->pdu.pid = SDP_PDU_SERVICE_SEARCH_RESPONSE;
srv->pdu.len = d.next - srv->obuf;
return 0;
}
uint16_t
service_attribute_request(server_t *srv, int fd)
{
record_t *r;
sdp_data_t a, d;
sdpd_data_t b;
uint8_t *tmp;
uint32_t handle;
int max;
log_debug("ServiceAttributeRequest by client on fd#%d", fd);
d.next = srv->ibuf;
d.end = srv->ibuf + srv->pdu.len;
if (d.next + sizeof(uint32_t) > d.end)
return SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX;
handle = be32dec(d.next);
d.next += sizeof(uint32_t);
if (d.next + sizeof(uint16_t) > d.end)
return SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX;
max = be16dec(d.next);
d.next += sizeof(uint16_t);
if (max < 0x0007)
return SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX;
if (!sdp_get_seq(&d, &a)
|| !sdpd_valid_ail(&a))
return SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX;
if (d.next + 1 > d.end
|| d.next[0] > 16
|| d.next + 1 + d.next[0] != d.end)
return SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX;
if (d.next[0] == 0) {
srv->fdidx[fd].offset = 0;
db_unselect(srv, fd);
db_select_handle(srv, fd, handle);
} else if (srv->fdidx[fd].offset == 0
|| d.next[0] != sizeof(uint16_t)
|| be16dec(d.next + 1) != srv->fdidx[fd].offset)
return SDP_ERROR_CODE_INVALID_CONTINUATION_STATE;
b.start = srv->obuf + sizeof(uint16_t);
b.next = b.start - srv->fdidx[fd].offset;
b.end = srv->obuf + srv->fdidx[fd].omtu - 1;
if (b.start + max < b.end)
b.end = b.start + max;
r = NULL;
db_next(srv, fd, &r);
if (r == NULL)
return SDP_ERROR_CODE_INVALID_SERVICE_RECORD_HANDLE;
sdpd_match_ail(r, a, &b);
if (b.next > b.end) {
tmp = b.next;
b.next = srv->obuf + srv->fdidx[fd].omtu - 1 - sizeof(uint16_t);
if (b.next > b.end)
b.next = b.end;
if (tmp - b.next < 0x0002)
b.next = tmp - 0x0002;
be16enc(srv->obuf, (b.next - b.start));
srv->fdidx[fd].offset += (b.next - b.start);
b.next[0] = sizeof(uint16_t);
be16enc(b.next + 1, srv->fdidx[fd].offset);
b.next += 1 + sizeof(uint16_t);
} else {
be16enc(srv->obuf, (b.next - b.start));
srv->fdidx[fd].offset = 0;
db_unselect(srv, fd);
b.next[0] = 0;
b.next += 1;
}
srv->pdu.pid = SDP_PDU_SERVICE_ATTRIBUTE_RESPONSE;
srv->pdu.len = b.next - srv->obuf;
return 0;
}
uint16_t
service_search_attribute_request(server_t *srv, int fd)
{
record_t *r;
sdpd_data_t b;
sdp_data_t a, d, s;
uint8_t *tmp;
int max;
log_debug("ServiceSearchAttributeRequest by client on fd#%d", fd);
d.next = srv->ibuf;
d.end = srv->ibuf + srv->pdu.len;
if (!sdp_get_seq(&d, &s)
|| !sdpd_valid_ssp(&s))
return SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX;
if (d.next + sizeof(uint16_t) > d.end)
return SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX;
max = be16dec(d.next);
d.next += sizeof(uint16_t);
if (max < 0x0007)
return SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX;
if (!sdp_get_seq(&d, &a)
|| !sdpd_valid_ail(&a))
return SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX;
if (d.next + 1 > d.end
|| d.next[0] > 16
|| d.next + 1 + d.next[0] != d.end)
return SDP_ERROR_CODE_INVALID_REQUEST_SYNTAX;
if (d.next[0] == 0) {
srv->fdidx[fd].offset = 0;
db_unselect(srv, fd);
db_select_ssp(srv, fd, &s);
} else if (srv->fdidx[fd].offset == 0
|| d.next[0] != sizeof(uint16_t)
|| be16dec(d.next + 1) != srv->fdidx[fd].offset)
return SDP_ERROR_CODE_INVALID_CONTINUATION_STATE;
b.start = srv->obuf + sizeof(uint16_t);
b.end = srv->obuf + srv->fdidx[fd].omtu - 1;
b.next = b.start - srv->fdidx[fd].offset;
if (b.start + max < b.end)
b.end = b.start + max;
sdpd_open_seq(&b);
r = NULL;
while (db_next(srv, fd, &r)) {
tmp = b.next;
if (!sdpd_match_ail(r, a, &b))
b.next = tmp;
}
sdpd_close_seq(&b, b.start - srv->fdidx[fd].offset);
if (b.next > b.end) {
tmp = b.next;
b.next = srv->obuf + srv->fdidx[fd].omtu - 1 - sizeof(uint16_t);
if (b.next > b.end)
b.next = b.end;
if (tmp - b.next < 0x0002)
b.next = tmp - 0x0002;
be16enc(srv->obuf, (b.next - b.start));
srv->fdidx[fd].offset += (b.next - b.start);
b.next[0] = sizeof(uint16_t);
be16enc(b.next + 1, srv->fdidx[fd].offset);
b.next += 1 + sizeof(uint16_t);
} else {
be16enc(srv->obuf, (b.next - b.start));
srv->fdidx[fd].offset = 0;
db_unselect(srv, fd);
b.next[0] = 0;
b.next += 1;
}
srv->pdu.pid = SDP_PDU_SERVICE_SEARCH_ATTRIBUTE_RESPONSE;
srv->pdu.len = b.next - srv->obuf;
return 0;
}
static bool
sdpd_valid_ssp(sdp_data_t *ssp)
{
sdp_data_t s = *ssp;
uuid_t u;
int n;
if (!sdp_data_valid(&s))
return false;
n = 0;
while (sdp_get_uuid(&s, &u))
n++;
if (n < 1 || n > 12 || s.next != s.end)
return false;
return true;
}
static bool
sdpd_valid_ail(sdp_data_t *ail)
{
sdp_data_t a = *ail;
sdp_data_t d;
if (!sdp_data_valid(&a))
return false;
while (sdp_get_data(&a, &d)) {
if (sdp_data_type(&d) != SDP_DATA_UINT16
&& sdp_data_type(&d) != SDP_DATA_UINT32)
return false;
}
return true;
}
static bool
sdpd_match_ail(record_t *rec, sdp_data_t ail, sdpd_data_t *buf)
{
sdp_data_t r, v;
uint16_t a;
uintmax_t ui;
uint8_t *f;
int lo, hi;
bool rv;
r = rec->data;
f = buf->next;
lo = hi = -1;
rv = false;
sdpd_open_seq(buf);
while (sdp_get_attr(&r, &a, &v)) {
while (a > hi) {
if (ail.next == ail.end)
goto done;
if (sdp_data_type(&ail) == SDP_DATA_UINT16) {
sdp_get_uint(&ail, &ui);
lo = hi = ui;
} else {
sdp_get_uint(&ail, &ui);
lo = (uint16_t)(ui >> 16);
hi = (uint16_t)(ui);
}
}
if (a < lo)
continue;
sdpd_put_attr(buf, a, &v);
rv = true;
}
done:
sdpd_close_seq(buf, f);
return rv;
}
static void
sdpd_put_byte(sdpd_data_t *buf, uint8_t byte)
{
if (buf->next >= buf->start && buf->next < buf->end)
buf->next[0] = byte;
buf->next++;
}
static void
sdpd_put_attr(sdpd_data_t *buf, uint16_t attr, sdp_data_t *data)
{
uint8_t *p;
sdpd_put_byte(buf, SDP_DATA_UINT16);
sdpd_put_byte(buf, (uint8_t)(attr >> 8));
sdpd_put_byte(buf, (uint8_t)(attr));
for (p = data->next; p < data->end; p++)
sdpd_put_byte(buf, *p);
}
static void
sdpd_open_seq(sdpd_data_t *buf)
{
buf->next += 3;
}
static void
sdpd_close_seq(sdpd_data_t *buf, uint8_t *first)
{
uint8_t *next;
size_t len;
next = buf->next;
buf->next = first;
len = next - first - 3;
sdpd_put_byte(buf, SDP_DATA_SEQ16);
sdpd_put_byte(buf, 0xff & (len >> 8));
sdpd_put_byte(buf, 0xff & (len >> 0));
buf->next = next;
}