<|start_filename|>src/bindings.cpp<|end_filename|>
// -*- mode: c++; coding: utf-8 -*-

// Copyright (C) 2014,2020 <NAME> <<EMAIL>>
// This software is under the terms of Apache License v2 or later.

#include <boost/python.hpp>
#include <boost/python/suite/indexing/map_indexing_suite.hpp>
#include <boost/python/overloads.hpp>
#include <boost/python/raw_function.hpp>
#include <boost/python/to_python_converter.hpp>

#include "gattlib.h"
#include "gattservices.h"
#include "beacon.h"

using namespace boost::python;

boost::python::object pyGATTResponse;
boost::python::object pyBaseException;
PyObject* pyBaseExceptionPtr = NULL;
boost::python::object pyBTIOException;
PyObject* pyBTIOExceptionPtr = NULL;
boost::python::object pyGATTException;
PyObject* pyGATTExceptionPtr = NULL;

/** to-python convert for std::vector<char> */
struct bytes_vector_to_python_bytes
{
    static PyObject* convert(std::vector<char> const& s)
    {
        return PyBytes_FromStringAndSize(s.data(), s.size());
    }
};

class GATTResponseCb : public GATTResponse {
public:
    GATTResponseCb(PyObject* p) : GATTResponse(p) {
    }

    // to be called from c++ side
    void on_response(boost::python::object data) {
        try {
            call_method<void>(self, "on_response", data);
        } catch(error_already_set const&) {
            PyErr_Print();
        }
    }

    // to be called from python side
    static void default_on_response(GATTResponse& self_, boost::python::object data) {
        self_.GATTResponse::on_response(data);
    }

    // to be called from c++ side
    void on_response_complete() {
        try {
            call_method<void>(self, "on_response_complete");
        } catch(error_already_set const&) {
            PyErr_Print();
        }
    }

    // to be called from python side
    static void default_on_response_complete(GATTResponse& self_) {
        self_.GATTResponse::on_response_complete();
    }

    // to be called from c++ side
    void on_response_failed(int status) {
        try {
            call_method<void>(self, "on_response_failed", status);
        } catch(error_already_set const&) {
            PyErr_Print();
        }
    }

    // to be called from python side
    static void default_on_response_failed(GATTResponse& self_, int status) {
        self_.GATTResponse::on_response_failed(status);
    }
};

class GATTRequesterCb : public GATTRequester {
public:
    GATTRequesterCb(PyObject* p, std::string address,
            bool do_connect=true, std::string device="hci0") :
        GATTRequester(p, address, do_connect, device) {
    }

    // to be called from c++ side
    void on_connect(int mtu) {
        try {
          call_method<void>(self, "on_connect", mtu);
        } catch(error_already_set const&) {
            PyErr_Print();
        }
    }

    // to be called from python side
    static void default_on_connect(GATTRequester& self_, int mtu)
    {
        self_.GATTRequester::on_connect(mtu);
    }

    // to be called from c++ side
    void on_connect_failed(int code) {
        try {
          call_method<void>(self, "on_connect_failed", code);
        } catch(error_already_set const&) {
            PyErr_Print();
        }
    }

    // to be called from python side
    static void default_on_connect_failed(GATTRequester& self_, int code)
    {
        self_.GATTRequester::on_connect_failed(code);
    }

    // to be called from c++ side
    void on_disconnect() {
        try {
            call_method<void>(self, "on_disconnect");
        } catch(error_already_set const&) {
            PyErr_Print();
        }
    }

    // to be called from python side
    static void default_on_disconnect(GATTRequester& self_)
    {
        self_.GATTRequester::on_disconnect();
    }

    // to be called from c++ side
    void on_notification(const uint16_t handle, const std::string data) {
        try {
            const std::vector<char> vecdata(data.begin(), data.end());
            call_method<void>(self, "on_notification", handle, vecdata);
        } catch(error_already_set const&) {
            PyErr_Print();
        }
    }

    // to be called from python side
    static void default_on_notification(GATTRequester& self_,
                                        const uint16_t handle,
                                        const std::string data) {
        self_.GATTRequester::on_notification(handle, data);
    }

    // to be called from c++ side
    void on_indication(const uint16_t handle, const std::string data) {
        try {
            const std::vector<char> vecdata(data.begin(), data.end());
            call_method<void>(self, "on_indication", handle, vecdata);
        } catch(error_already_set const&) {
            PyErr_Print();
        }
    }

    // to be called from python side
    static void default_on_indication(GATTRequester& self_,
                                      const uint16_t handle,
                                      const std::string data) {
        self_.GATTRequester::on_indication(handle, data);
    }
};

BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(
        start_advertising, BeaconService::start_advertising, 0, 5)

BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(
        GATTRequester_discover_characteristics_overloads,
        GATTRequester::discover_characteristics, 0, 3)

BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(
        GATTRequester_discover_characteristics_async_overloads,
        GATTRequester::discover_characteristics_async, 1, 4)

BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(
        GATTRequester_discover_descriptors_overloads,
        GATTRequester::discover_descriptors, 0, 3)

BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(
        GATTRequester_discover_descriptors_async_overloads,
        GATTRequester::discover_descriptors_async, 1, 4)

BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(
        GATTResponse_wait_overloads,
        GATTResponse::wait_locked, 0, 1)

static PyObject*
createExceptionClass(const char* name, PyObject* baseType, boost::python::object &pyrv, const char *docstr=NULL)
{
    boost::python::object scope = boost::python::scope();
    std::string scopename = boost::python::extract<std::string>(scope.attr("__name__"));
    std::string qname = scopename + "." + name;
    PyObject* rv = PyErr_NewExceptionWithDoc(qname.c_str(), docstr, baseType, 0);
    boost::python::object o(boost::python::handle<>(boost::python::borrowed(rv)));
    pyrv = o;
    scope.attr(name) = pyrv;
    return rv;
}

void
translate_BTIOException(const BTIOException& e)
{
    boost::python::object pye(pyBTIOException(e.what()));
    pye.attr("code") = e.status;
    PyErr_SetObject(pyBTIOExceptionPtr, pye.ptr());
}

void
translate_GATTException(const GATTException& e)
{
    boost::python::object pye(pyGATTException(e.what()));
    pye.attr("status") = e.status;
    PyErr_SetObject(pyGATTExceptionPtr, pye.ptr());
}

#define BOOST_PYTHON_CONSTANT(x) boost::python::scope().attr(#x) = x

BOOST_PYTHON_MODULE(gattlib) {

    BOOST_PYTHON_CONSTANT(ATT_ECODE_INVALID_HANDLE);
    BOOST_PYTHON_CONSTANT(ATT_ECODE_READ_NOT_PERM);
    BOOST_PYTHON_CONSTANT(ATT_ECODE_WRITE_NOT_PERM);
    BOOST_PYTHON_CONSTANT(ATT_ECODE_INVALID_PDU);
    BOOST_PYTHON_CONSTANT(ATT_ECODE_AUTHENTICATION);
    BOOST_PYTHON_CONSTANT(ATT_ECODE_REQ_NOT_SUPP);
    BOOST_PYTHON_CONSTANT(ATT_ECODE_INVALID_OFFSET);
    BOOST_PYTHON_CONSTANT(ATT_ECODE_AUTHORIZATION);
    BOOST_PYTHON_CONSTANT(ATT_ECODE_PREP_QUEUE_FULL);
    BOOST_PYTHON_CONSTANT(ATT_ECODE_ATTR_NOT_FOUND);
    BOOST_PYTHON_CONSTANT(ATT_ECODE_ATTR_NOT_LONG);
    BOOST_PYTHON_CONSTANT(ATT_ECODE_INSUFF_ENCR_KEY_SIZE);
    BOOST_PYTHON_CONSTANT(ATT_ECODE_INVAL_ATTR_VALUE_LEN);
    BOOST_PYTHON_CONSTANT(ATT_ECODE_UNLIKELY);
    BOOST_PYTHON_CONSTANT(ATT_ECODE_INSUFF_ENC);
    BOOST_PYTHON_CONSTANT(ATT_ECODE_UNSUPP_GRP_TYPE);
    BOOST_PYTHON_CONSTANT(ATT_ECODE_INSUFF_RESOURCES);
    BOOST_PYTHON_CONSTANT(ATT_ECODE_IO);
    BOOST_PYTHON_CONSTANT(ATT_ECODE_TIMEOUT);
    BOOST_PYTHON_CONSTANT(ATT_ECODE_ABORTED);

    BOOST_PYTHON_CONSTANT(GATT_CHR_PROP_BROADCAST);
    BOOST_PYTHON_CONSTANT(GATT_CHR_PROP_READ);
    BOOST_PYTHON_CONSTANT(GATT_CHR_PROP_WRITE_WITHOUT_RESP);
    BOOST_PYTHON_CONSTANT(GATT_CHR_PROP_WRITE);
    BOOST_PYTHON_CONSTANT(GATT_CHR_PROP_NOTIFY);
    BOOST_PYTHON_CONSTANT(GATT_CHR_PROP_INDICATE);
    BOOST_PYTHON_CONSTANT(GATT_CHR_PROP_AUTH);
    BOOST_PYTHON_CONSTANT(GATT_CHR_PROP_EXT_PROP);

    BOOST_PYTHON_CONSTANT(GATT_CLIENT_CHARAC_CFG_NOTIF_BIT);
    BOOST_PYTHON_CONSTANT(GATT_CLIENT_CHARAC_CFG_IND_BIT);

    to_python_converter<std::vector<char>, bytes_vector_to_python_bytes>();

    pyBaseExceptionPtr = createExceptionClass("BTBaseException", PyExc_RuntimeError, pyBaseException,
        "Base for custom gattlib exceptions.");

    pyBTIOExceptionPtr = createExceptionClass("BTIOException", pyBaseExceptionPtr, pyBTIOException,
        "Parameter, state and BT protocol level errors. code is an errno value.");
    register_exception_translator<BTIOException>(&translate_BTIOException);

    pyGATTExceptionPtr = createExceptionClass("GATTException", pyBaseExceptionPtr, pyGATTException,
        "GATT level errors. status is an ATT_ECODE_* value.");
    register_exception_translator<GATTException>(&translate_GATTException);

    register_ptr_to_python<GATTRequester*>();

    class_<GATTRequester, boost::noncopyable, GATTRequesterCb> ("GATTRequester",
            init<std::string, optional<bool, std::string> >())

        .def("connect", boost::python::raw_function(GATTRequester::connect_kwarg,1))
        .def("is_connected", &GATTRequester::is_connected)
        .def("on_connect", &GATTRequesterCb::default_on_connect)
        .def("on_connect_failed", &GATTRequesterCb::default_on_connect_failed)
        .def("disconnect", &GATTRequester::disconnect)
        .def("on_disconnect", &GATTRequesterCb::default_on_disconnect)
        .def("update_connection_parameters", boost::python::raw_function(GATTRequester::update_connection_parameters_kwarg,1))
        .def("exchange_mtu", &GATTRequester::exchange_mtu)
        .def("exchange_mtu_async", &GATTRequester::exchange_mtu_async)
        .def("set_mtu", &GATTRequester::set_mtu)
        .def("read_by_handle", &GATTRequester::read_by_handle)
        .def("read_by_handle_async", &GATTRequester::read_by_handle_async)
        .def("read_by_uuid", &GATTRequester::read_by_uuid)
        .def("read_by_uuid_async", &GATTRequester::read_by_uuid_async)
        .def("write_by_handle", &GATTRequester::write_by_handle)
        .def("write_by_handle_async", &GATTRequester::write_by_handle_async)
        .def("write_cmd", &GATTRequester::write_cmd)
        .def("enable_notifications", &GATTRequester::enable_notifications)
        .def("enable_notifications_async", &GATTRequester::enable_notifications_async)
        .def("on_notification", &GATTRequesterCb::default_on_notification)
        .def("on_indication", &GATTRequesterCb::default_on_indication)
        .def("discover_primary", &GATTRequester::discover_primary,
                "returns a list with of primary services,"
                " with their handles and UUIDs.")
        .def("discover_primary_async", &GATTRequester::discover_primary_async)
        .def("find_included", &GATTRequester::find_included)
        .def("find_included_async", &GATTRequester::find_included_async)
        .def("discover_characteristics",
                &GATTRequester::discover_characteristics,
                GATTRequester_discover_characteristics_overloads())
        .def("discover_characteristics_async",
                &GATTRequester::discover_characteristics_async,
                GATTRequester_discover_characteristics_async_overloads())
        .def("discover_descriptors",
                &GATTRequester::discover_descriptors,
                GATTRequester_discover_descriptors_overloads())
        .def("discover_descriptors_async",
                &GATTRequester::discover_descriptors_async,
                GATTRequester_discover_descriptors_async_overloads());

    register_ptr_to_python<GATTResponse*>();

    pyGATTResponse = class_<GATTResponse, boost::noncopyable, GATTResponseCb>("GATTResponse")
            .def("complete", &GATTResponse::complete)
            .def("status", &GATTResponse::status)
            .def("wait", &GATTResponse::wait_locked, GATTResponse_wait_overloads())
            .def("received", &GATTResponse::received)
            .def("on_response", &GATTResponseCb::default_on_response)
            .def("on_response_complete", &GATTResponseCb::default_on_response_complete)
            .def("on_response_failed", &GATTResponseCb::default_on_response_failed);

    class_<DiscoveryService>("DiscoveryService", init<optional<std::string> >())
            .def("discover", &DiscoveryService::discover);

    class_<BeaconService>("BeaconService", init<optional<std::string> >())
            .def("scan", &BeaconService::scan)
            .def("start_advertising", &BeaconService::start_advertising,
                    start_advertising(
                        args("uuid", "major", "minor", "txpower", "interval"),
                        "starts advertising beacon packets"))
            .def("stop_advertising", &BeaconService::stop_advertising);
}


<|start_filename|>Makefile<|end_filename|>
# -*- mode: makefile-gmake; coding: utf-8 -*-

all:
	$(MAKE) -C src $@

pypi-test-upload:
	python3 setup.py sdist
	twine upload --repository testpypi dist/*
	@echo "pip install -i https://test.pypi.org/simple/ gattlib --force-reinstall"

pypi-upload:
	python3 setup.py sdist
	twine upload dist/*

%:
	$(MAKE) -C src $@

.PHONY: clean
clean:
	$(MAKE) -C src $@
	$(RM) -fr build dist MANIFEST gattlib.egg-info


<|start_filename|>src/Makefile<|end_filename|>
# -*- mode: makefile-gmake; coding: utf-8 -*-

# Copyright (C) 2014,2020 <NAME> <<EMAIL>>
# This software is under the terms of Apache License v2 or later.

PLATFORM := $(shell uname -m)
TARGETS   = gattlib.so
OBJECTS   = att.o crypto.o uuid.o gatt.o gattrib.o btio.o log.o utils.o \
            gattservices.o gattlib.o bindings.o beacon.o

export PKG_CONFIG_PATH := $(PKG_CONFIG_PATH):/usr/lib/$(PLATFORM)-linux-gnu/pkgconfig/

PYTHON_CONFIG = python3-config
BOOST_PYTHON  = $(shell python3 ../setup.py --get-boost-lib)

CFLAGS  += -ggdb -fPIC -Wall $$($(PYTHON_CONFIG) --includes) \
           -Ibluez $$(pkg-config --cflags glib-2.0)
CFLAGS  += -DDEBUG

CFLAGS  += -DVERSION='"5.25"'
CXXFLAGS = $(CFLAGS)
LDFLAGS  = -l$(BOOST_PYTHON) -lboost_thread -lbluetooth \
           $$(pkg-config --libs glib-2.0)

vpath %.c bluez/attrib
vpath %.c bluez/src
vpath %.c bluez/src/shared
vpath %.c bluez/lib
vpath %.c bluez/btio

all: $(TARGETS)

gattlib.so: $(OBJECTS)
	$(CXX) $(CXXFLAGS) -shared -o $@ $^ $(LDFLAGS)

print-boost-ver:
	@echo $(BOOST_PYTHON)

.PHONY: clean
clean:
	rm -f *.o *.so* *~


<|start_filename|>src/gattservices.cpp<|end_filename|>
// -*- mode: c++; coding: utf-8; tab-width: 4 -*-

// Copyright (C) 2014, <NAME> <<EMAIL>>
// This software is under the terms of Apache License v2 or later.

#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>

#include <exception>

#include "gattlib.h"
#include "gattservices.h"

DiscoveryService::DiscoveryService(const std::string device) :
	_device(device),
	_device_desc(-1) {

	int dev_id = hci_devid(device.c_str());
	if (dev_id < 0)
		throw std::runtime_error("Invalid device!");

	_device_desc = hci_open_dev(dev_id);
	if (_device_desc < 0)
		throw std::runtime_error("Could not open device!");
	}

DiscoveryService::~DiscoveryService() {
	if (_device_desc != -1)
		hci_close_dev(_device_desc);
}

void
DiscoveryService::enable_scan_mode() {
	int result;
	uint8_t scan_type = 0x01;
	uint16_t interval = htobs(0x0010);
	uint16_t window = htobs(0x0010);
	uint8_t own_type = 0x00;
	uint8_t filter_policy = 0x00;

	result = hci_le_set_scan_parameters
		(_device_desc, scan_type, interval, window,
		 own_type, filter_policy, 10000);

	if (result < 0)
		throw std::runtime_error
			("Set scan parameters failed (are you root?)");

	result = hci_le_set_scan_enable(_device_desc, 0x01, 1, 10000);
	if (result < 0)
		throw std::runtime_error("Enable scan failed");
}

void
DiscoveryService::get_advertisements(int timeout, boost::python::dict & ret) {
	struct hci_filter old_options;
	socklen_t slen = sizeof(old_options);
	if (getsockopt(_device_desc, SOL_HCI, HCI_FILTER,
				   &old_options, &slen) < 0)
		throw std::runtime_error("Could not get socket options");

	struct hci_filter new_options;
	hci_filter_clear(&new_options);
	hci_filter_set_ptype(HCI_EVENT_PKT, &new_options);
	hci_filter_set_event(EVT_LE_META_EVENT, &new_options);

	if (setsockopt(_device_desc, SOL_HCI, HCI_FILTER,
				   &new_options, sizeof(new_options)) < 0)
		throw std::runtime_error("Could not set socket options\n");

	int len;
	unsigned char buffer[HCI_MAX_EVENT_SIZE];
	struct timeval wait = (struct timeval){0};
	fd_set read_set;
	wait.tv_sec = timeout;
	int ts = time(NULL);

	while(1) {
		FD_ZERO(&read_set);
		FD_SET(_device_desc, &read_set);

		int err = select(FD_SETSIZE, &read_set, NULL, NULL, &wait);
		if (err <= 0)
			break;

		len = read(_device_desc, buffer, sizeof(buffer));
		process_input(buffer, len, ret);

		int elapsed = time(NULL) - ts;
		if (elapsed >= timeout)
			break;

		wait.tv_sec = timeout - elapsed;
	}

	setsockopt(_device_desc, SOL_HCI, HCI_FILTER,
			   &old_options, sizeof(old_options));
}

void
DiscoveryService::process_input(unsigned char* buffer, int size,
		boost::python::dict & ret) {
	unsigned char* ptr = buffer + HCI_EVENT_HDR_SIZE + 1;
	evt_le_meta_event* meta = (evt_le_meta_event*) ptr;

	if (meta->subevent != 0x02 || (uint8_t)buffer[BLE_EVENT_TYPE] != BLE_SCAN_RESPONSE)
		return;

	le_advertising_info* info;
	info = (le_advertising_info*) (meta->data + 1);

	char addr[18];
	ba2str(&info->bdaddr, addr);

	std::string name = parse_name(info->data, info->length);
	ret[addr] = name;
}

std::string
DiscoveryService::parse_name(uint8_t* data, size_t size) {
	size_t offset = 0;
	std::string unknown = "";

	while (offset < size) {
		uint8_t field_len = data[0];
		size_t name_len;

		if (field_len == 0 || offset + field_len > size)
			return unknown;

		switch (data[1]) {
		case EIR_NAME_SHORT:
		case EIR_NAME_COMPLETE:
			name_len = field_len - 1;
			if (name_len > size)
				return unknown;

			return std::string((const char*)(data + 2), name_len);
		}

		offset += field_len + 1;
		data += field_len + 1;
	}

	return unknown;
}

void
DiscoveryService::disable_scan_mode() {
	if (_device_desc == -1)
		throw std::runtime_error("Could not disable scan, not enabled yet");

	int result = hci_le_set_scan_enable(_device_desc, 0x00, 1, 10000);
	if (result < 0)
		throw std::runtime_error("Disable scan failed");
}

boost::python::dict
DiscoveryService::discover(int timeout) {
	boost::python::dict retval;
	enable_scan_mode();
	get_advertisements(timeout, retval);
	disable_scan_mode();

	return retval;
}
