blob: fe69220d52957ab1c7e56104f88244fe2578c08d [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include <assert.h>
#include <string.h>
#include <stdint.h>
#include "os/mynewt.h"
#if (MYNEWT_VAL(OC_TRANSPORT_GATT) == 1)
#include <stats/stats.h>
#include "oic/oc_gatt.h"
#include "oic/oc_log.h"
#include "oic/oc_ri.h"
#include "oic/messaging/coap/coap.h"
#include "oic/port/oc_connectivity.h"
#include "oic/port/mynewt/ble.h"
#include "host/ble_hs.h"
#include "services/gap/ble_svc_gap.h"
#include "services/gatt/ble_svc_gatt.h"
static uint8_t oc_ep_gatt_size(const struct oc_endpoint *oe);
static void oc_send_buffer_gatt(struct os_mbuf *m);
static char *oc_log_ep_gatt(char *ptr, int maxlen, const struct oc_endpoint *);
enum oc_resource_properties
oc_get_trans_security_gatt(const struct oc_endpoint *oe_ble);
static int oc_connectivity_init_gatt(void);
void oc_connectivity_shutdown_gatt(void);
static const struct oc_transport oc_gatt_transport = {
.ot_flags = OC_TRANSPORT_USE_TCP,
.ot_ep_size = oc_ep_gatt_size,
.ot_tx_ucast = oc_send_buffer_gatt,
.ot_tx_mcast = oc_send_buffer_gatt,
.ot_get_trans_security = oc_get_trans_security_gatt,
.ot_ep_str = oc_log_ep_gatt,
.ot_init = oc_connectivity_init_gatt,
.ot_shutdown = oc_connectivity_shutdown_gatt
};
static uint8_t oc_gatt_transport_id;
/* OIC Transport Profile GATT */
/* unsecure service UUID */
/* ADE3D529-C784-4F63-A987-EB69F70EE816 */
static const ble_uuid128_t oc_gatt_unsec_svc_uuid =
BLE_UUID128_INIT(OC_GATT_UNSEC_SVC_UUID);
/* unsecure request characteristic UUID */
/* AD7B334F-4637-4B86-90B6-9D787F03D218 */
static const ble_uuid128_t oc_gatt_unsec_req_chr_uuid =
BLE_UUID128_INIT(OC_GATT_UNSEC_REQ_CHR_UUID);
/* response characteristic UUID */
/* E9241982-4580-42C4-8831-95048216B256 */
static const ble_uuid128_t oc_gatt_unsec_rsp_chr_uuid =
BLE_UUID128_INIT(OC_GATT_UNSEC_RSP_CHR_UUID);
/* secure service UUID. */
/* 0xfe18 */
static const ble_uuid16_t oc_gatt_sec_svc_uuid =
BLE_UUID16_INIT(OC_GATT_SEC_SVC_UUID);
/* secure request characteristic UUID. */
/* 0x1000 */
static const ble_uuid16_t oc_gatt_sec_req_chr_uuid =
BLE_UUID16_INIT(OC_GATT_SEC_REQ_CHR_UUID);
/* secure response characteristic UUID. */
/* 0x1001 */
static const ble_uuid16_t oc_gatt_sec_rsp_chr_uuid =
BLE_UUID16_INIT(OC_GATT_SEC_RSP_CHR_UUID);
STATS_SECT_START(oc_ble_stats)
STATS_SECT_ENTRY(iframe)
STATS_SECT_ENTRY(iseg)
STATS_SECT_ENTRY(ibytes)
STATS_SECT_ENTRY(ierr)
STATS_SECT_ENTRY(oframe)
STATS_SECT_ENTRY(oseg)
STATS_SECT_ENTRY(obytes)
STATS_SECT_ENTRY(oerr)
STATS_SECT_END
STATS_SECT_DECL(oc_ble_stats) oc_ble_stats;
STATS_NAME_START(oc_ble_stats)
STATS_NAME(oc_ble_stats, iframe)
STATS_NAME(oc_ble_stats, iseg)
STATS_NAME(oc_ble_stats, ibytes)
STATS_NAME(oc_ble_stats, ierr)
STATS_NAME(oc_ble_stats, oframe)
STATS_NAME(oc_ble_stats, oseg)
STATS_NAME(oc_ble_stats, obytes)
STATS_NAME(oc_ble_stats, oerr)
STATS_NAME_END(oc_ble_stats)
static STAILQ_HEAD(, os_mbuf_pkthdr) oc_ble_reass_q;
#if (MYNEWT_VAL(OC_SERVER) == 1)
/*
* BLE nmgr attribute handles for service
*/
#define OC_BLE_SRV_CNT 2
static struct {
uint16_t req;
uint16_t rsp;
} oc_ble_srv_handles[OC_BLE_SRV_CNT];
static int oc_gatt_chr_access(uint16_t conn_handle, uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt, void *arg);
static const struct ble_gatt_svc_def oc_gatt_svr_svcs[] = { {
/* Service: iotivity */
.type = BLE_GATT_SVC_TYPE_PRIMARY,
.uuid = &oc_gatt_unsec_svc_uuid.u,
.characteristics = (struct ble_gatt_chr_def[]) {
{
/* Characteristic: Request */
.uuid = &oc_gatt_unsec_req_chr_uuid.u,
.access_cb = oc_gatt_chr_access,
.flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_WRITE_NO_RSP,
.val_handle = &oc_ble_srv_handles[0].req,
},{
/* Characteristic: Response */
.uuid = &oc_gatt_unsec_rsp_chr_uuid.u,
.access_cb = oc_gatt_chr_access,
.flags = BLE_GATT_CHR_F_NOTIFY,
.val_handle = &oc_ble_srv_handles[0].rsp,
},{
0, /* No more characteristics in this service */
}
},
},{
/* Service: CoAP-over-BLE */
.type = BLE_GATT_SVC_TYPE_PRIMARY,
.uuid = &oc_gatt_sec_svc_uuid.u,
.characteristics = (struct ble_gatt_chr_def[]) {
{
/* Characteristic: Request */
.uuid = &oc_gatt_sec_req_chr_uuid.u,
.access_cb = oc_gatt_chr_access,
.flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_WRITE_NO_RSP,
.val_handle = &oc_ble_srv_handles[1].req,
},{
/* Characteristic: Response */
.uuid = &oc_gatt_sec_rsp_chr_uuid.u,
.access_cb = oc_gatt_chr_access,
.flags = BLE_GATT_CHR_F_NOTIFY,
.val_handle = &oc_ble_srv_handles[1].rsp,
},{
0, /* No more characteristics in this service */
}
},
},
{
0, /* No more services */
},
};
/*
* Look up service index based on characteristic handle from request.
*/
static int
oc_ble_req_attr_to_idx(uint16_t attr_handle)
{
int i;
for (i = 0; i < OC_BLE_SRV_CNT; i++) {
if (oc_ble_srv_handles[i].req == attr_handle) {
return i;
}
}
return -1;
}
static uint8_t
oc_ep_gatt_size(const struct oc_endpoint *oe)
{
return sizeof(struct oc_endpoint_ble);
}
static char *
oc_log_ep_gatt(char *ptr, int maxlen, const struct oc_endpoint *oe)
{
struct oc_endpoint_ble *oe_ble = (struct oc_endpoint_ble *)oe;
snprintf(ptr, maxlen, "ble %u", oe_ble->conn_handle);
return ptr;
}
int
oc_ble_reass(struct os_mbuf *om1, uint16_t conn_handle, uint8_t srv_idx)
{
struct os_mbuf_pkthdr *pkt1;
struct oc_endpoint_ble *oe_ble;
struct os_mbuf *om2;
struct os_mbuf_pkthdr *pkt2;
uint8_t hdr[6]; /* sizeof(coap_tcp_hdr32) */
pkt1 = OS_MBUF_PKTHDR(om1);
assert(pkt1);
STATS_INC(oc_ble_stats, iseg);
STATS_INCN(oc_ble_stats, ibytes, pkt1->omp_len);
OC_LOG(DEBUG, "oc_gatt rx seg %u-%x-%u\n", conn_handle,
(unsigned)pkt1, pkt1->omp_len);
STAILQ_FOREACH(pkt2, &oc_ble_reass_q, omp_next) {
om2 = OS_MBUF_PKTHDR_TO_MBUF(pkt2);
oe_ble = (struct oc_endpoint_ble *)OC_MBUF_ENDPOINT(om2);
if (conn_handle == oe_ble->conn_handle && srv_idx == oe_ble->srv_idx) {
/*
* Data from same connection. Append.
*/
os_mbuf_concat(om2, om1);
os_mbuf_copydata(om2, 0, sizeof(hdr), hdr);
if (coap_tcp_msg_size(hdr, sizeof(hdr)) <= pkt2->omp_len) {
STAILQ_REMOVE(&oc_ble_reass_q, pkt2, os_mbuf_pkthdr, omp_next);
STATS_INC(oc_ble_stats, iframe);
oc_recv_message(om2);
}
pkt1 = NULL;
break;
}
}
if (pkt1) {
/*
* New frame, need to add oc_endpoint_ble in the front.
* Check if there is enough space available. If not, allocate a
* new pkthdr.
*/
if (OS_MBUF_USRHDR_LEN(om1) < sizeof(struct oc_endpoint_ble)) {
om2 = os_msys_get_pkthdr(0, sizeof(struct oc_endpoint_ble));
if (!om2) {
OC_LOG(ERROR, "oc_gatt_rx: Could not allocate mbuf\n");
STATS_INC(oc_ble_stats, ierr);
return -1;
}
OS_MBUF_PKTHDR(om2)->omp_len = pkt1->omp_len;
SLIST_NEXT(om2, om_next) = om1;
} else {
om2 = om1;
}
oe_ble = (struct oc_endpoint_ble *)OC_MBUF_ENDPOINT(om2);
oe_ble->ep.oe_type = oc_gatt_transport_id;
oe_ble->ep.oe_flags = 0;
oe_ble->srv_idx = srv_idx;
oe_ble->conn_handle = conn_handle;
pkt2 = OS_MBUF_PKTHDR(om2);
os_mbuf_copydata(om2, 0, sizeof(hdr), hdr);
if (coap_tcp_msg_size(hdr, sizeof(hdr)) > pkt2->omp_len) {
STAILQ_INSERT_TAIL(&oc_ble_reass_q, pkt2, omp_next);
} else {
STATS_INC(oc_ble_stats, iframe);
oc_recv_message(om2);
}
}
return 0;
}
static int
oc_gatt_chr_access(uint16_t conn_handle, uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt, void *arg)
{
struct os_mbuf *m;
int rc;
int srv_idx;
switch (ctxt->op) {
case BLE_GATT_ACCESS_OP_WRITE_CHR:
m = ctxt->om;
srv_idx = oc_ble_req_attr_to_idx(attr_handle);
assert(srv_idx >= 0);
rc = oc_ble_reass(m, conn_handle, srv_idx);
if (rc) {
return BLE_ATT_ERR_INSUFFICIENT_RES;
}
/* tell nimble we are keeping the mbuf */
ctxt->om = NULL;
break;
default:
assert(0);
return BLE_ATT_ERR_UNLIKELY;
}
return 0;
}
#endif
int
oc_ble_coap_gatt_srv_init(void)
{
#if (MYNEWT_VAL(OC_SERVER) == 1)
int rc;
rc = ble_gatts_count_cfg(oc_gatt_svr_svcs);
assert(rc == 0);
rc = ble_gatts_add_svcs(oc_gatt_svr_svcs);
assert(rc == 0);
#endif
(void)stats_init_and_reg(STATS_HDR(oc_ble_stats),
STATS_SIZE_INIT_PARMS(oc_ble_stats, STATS_SIZE_32),
STATS_NAME_INIT_PARMS(oc_ble_stats), "oc_ble");
return 0;
}
void
oc_ble_coap_conn_new(uint16_t conn_handle)
{
OC_LOG(DEBUG, "oc_gatt newconn %x\n", conn_handle);
}
void
oc_ble_coap_conn_del(uint16_t conn_handle)
{
struct os_mbuf_pkthdr *pkt;
struct os_mbuf *m;
struct oc_endpoint_ble *oe_ble;
OC_LOG(DEBUG, "oc_gatt endconn %x\n", conn_handle);
STAILQ_FOREACH(pkt, &oc_ble_reass_q, omp_next) {
m = OS_MBUF_PKTHDR_TO_MBUF(pkt);
oe_ble = (struct oc_endpoint_ble *)OC_MBUF_ENDPOINT(m);
if (oe_ble->conn_handle == conn_handle) {
STAILQ_REMOVE(&oc_ble_reass_q, pkt, os_mbuf_pkthdr, omp_next);
os_mbuf_free_chain(m);
break;
}
}
}
int
oc_connectivity_init_gatt(void)
{
STAILQ_INIT(&oc_ble_reass_q);
return 0;
}
void
oc_connectivity_shutdown_gatt(void)
{
/* there is not unregister for BLE */
}
#if (MYNEWT_VAL(OC_SERVER) == 1)
static int
oc_ble_frag(struct os_mbuf *m, uint16_t mtu)
{
struct os_mbuf_pkthdr *pkt;
struct os_mbuf *n;
uint16_t off, blk;
pkt = OS_MBUF_PKTHDR(m);
if (pkt->omp_len <= mtu) {
STAILQ_NEXT(pkt, omp_next) = NULL;
return 0;
}
off = pkt->omp_len - (pkt->omp_len % mtu);
while (off >= mtu) {
n = os_msys_get_pkthdr(mtu, 0);
if (!n) {
goto err;
}
STAILQ_NEXT(OS_MBUF_PKTHDR(n), omp_next) = STAILQ_NEXT(pkt, omp_next);
STAILQ_NEXT(pkt, omp_next) = OS_MBUF_PKTHDR(n);
blk = pkt->omp_len - off;
if (os_mbuf_appendfrom(n, m, off, blk)) {
goto err;
}
off -= mtu;
os_mbuf_adj(m, -blk);
}
return 0;
err:
pkt = OS_MBUF_PKTHDR(m);
while (1) {
pkt = STAILQ_NEXT(pkt, omp_next);
os_mbuf_free_chain(m);
if (!pkt) {
break;
}
m = OS_MBUF_PKTHDR_TO_MBUF(pkt);
};
return -1;
}
#endif
void
oc_send_buffer_gatt(struct os_mbuf *m)
{
#if (MYNEWT_VAL(OC_SERVER) == 1)
struct oc_endpoint_ble *oe_ble;
struct os_mbuf_pkthdr *pkt;
uint16_t mtu;
uint16_t conn_handle;
uint16_t attr_handle;
#endif
#if (MYNEWT_VAL(OC_SERVER) == 1)
assert(OS_MBUF_USRHDR_LEN(m) >= sizeof(struct oc_endpoint_ble));
oe_ble = (struct oc_endpoint_ble *)OC_MBUF_ENDPOINT(m);
conn_handle = oe_ble->conn_handle;
STATS_INC(oc_ble_stats, oframe);
STATS_INCN(oc_ble_stats, obytes, OS_MBUF_PKTLEN(m));
if (oe_ble->srv_idx >= OC_BLE_SRV_CNT) {
goto err;
}
attr_handle = oc_ble_srv_handles[oe_ble->srv_idx].rsp;
mtu = ble_att_mtu(conn_handle);
if (mtu < 4) {
oc_ble_coap_conn_del(conn_handle);
goto err;
}
mtu -= 3; /* # of bytes for ATT notification base */
if (oc_ble_frag(m, mtu)) {
STATS_INC(oc_ble_stats, oerr);
return;
}
while (1) {
STATS_INC(oc_ble_stats, oseg);
pkt = STAILQ_NEXT(OS_MBUF_PKTHDR(m), omp_next);
ble_gattc_notify_custom(conn_handle, attr_handle, m);
if (pkt) {
m = OS_MBUF_PKTHDR_TO_MBUF(pkt);
} else {
break;
}
}
return;
err:
os_mbuf_free_chain(m);
STATS_INC(oc_ble_stats, oerr);
#endif
}
/**
* Retrieves the specified BLE endpoint's transport layer security properties.
*/
oc_resource_properties_t
oc_get_trans_security_gatt(const struct oc_endpoint *oe)
{
const struct oc_endpoint_ble *oe_ble;
oc_resource_properties_t props;
struct ble_gap_conn_desc desc;
int rc;
oe_ble = (const struct oc_endpoint_ble *)oe;
rc = ble_gap_conn_find(oe_ble->conn_handle, &desc);
if (rc != 0) {
return 0;
}
props = 0;
if (desc.sec_state.encrypted) {
props |= OC_TRANS_ENC;
}
if (desc.sec_state.authenticated) {
props |= OC_TRANS_AUTH;
}
return props;
}
#endif
void
oc_register_gatt(void)
{
#if (MYNEWT_VAL(OC_TRANSPORT_GATT) == 1)
oc_gatt_transport_id = oc_transport_register(&oc_gatt_transport);
#endif
}