download original
Bluetooth + Linux implementation
physical layer:
- wireless connection technology, 2.4GHz (ISM band)
- several devices share a common radio channel, forming a "piconet"
- each device has a unique Bluetooth address (a sequence of 6 bytes,
dubbed the "BD_ADDR"). The sequence is constant and unique per
device (assigned by some registration authority). The bit sequence
is subdivided into LAP (bits 0-23), UAP (24-31), and NAP
(32-47). Some sequences are reserved for special purposes, e.g. for
"inquiries".
- one of the devices in a piconet serves as the "master", the others
are the "slaves"
- frequency hopping patterns to avoid interference with other senders
- hopping over 79 channels, each ca. 1MHz wide
- hopping sequence determined by master's address and clock
- 1600 timeslots/sec
- raw bitrate 914560/s (TODO: verify). Shared among all devices, and
communication links, protocol headers etc.
- max. effective bitrate 723200/s ("DH5", 2 devices, 5 timeslots per
packet in the "fast" direction, 1 in the slow one)
- "physical links" only between master and a slave, "logical links"
between any devices use "physical links" as their transport
- types of logical links: unicast, multicast, synchronous,
asynchronous, isochronous (??)
- "link manager protocol (LMP)": control protocol for the physical
layers (TODO: elaborate), transported over a dedicated asynchronous,
connection-oriented logical link, the ACL. Each dev in the piconet
automatically connects to that transport upon joining the piconet
- devices may create additional transports if desired
- L2CAP: higher-level protocol providing connection-oriented or -less
communication over distinct "channels" (somewhat like TCP on the
internet)
- SDP (service discovery protocol): used for discovering he "services"
provided by a given device.
- TODO: more...
- Linux implementation:
- kernel: core, device drivers, HCI, L2CAP, RFCOMM
- interfacing to userspace: Socket API (new socket types), ioctls
for non-stream-orientied things
- userspace wrapper libraries (bluez-libs)
- userspace: SDP, OBEX, everything in higher layers
////bluez-libs/bluetooth/bluetooth.h
/* BD Address */
typedef struct {
uint8_t b[6];
} __attribute__((packed)) bdaddr_t;
//so a bluetooth endpoint is a 6-byte number ("Bluetooth device address")
So bluetooth/bluetooth.h only defines some basic utility
functions. The hard work happens in the kernel.
HCI
========================
////bluez-libs/bluetooth/hci.h/hci_lib.h/hci.c
//the functions here are mostly frontends to correspondings
//ioctls on socket created via socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_HCI)
//(name is misleading, should be "hci_find_dev")
//find first (w.r.t. ioctl HCIGETDEVLIST) connected device dev_id for which
//func(<socket created via socket(AF_BLUETOOTH,SOCK_RAW,BTPROTO_HCI)>,
// dev_id, args)
//returns non-0. Returns that device's dev_id. Returns -1 if no such
//device was found.
//If func is NULL, return first connected device.
//QU: What is a dev_id? Has a corresponding bdaddr_t when connected
// (ioctl HCIGETDEVINFO can find the bdaddr_t of a given dev_id)
int hci_for_each_dev(int flag, int (*func)(int s, int dev_id, long arg), long arg);
//return dev_id of first conn. device having an address other than *bdaddr
int hci_get_route(bdaddr_t *bdaddr);
//return dev_id of conn. device identified by str
//str may either be "hci<dev_id>" (dev_id = the integer number 2b
// returned) or a string representation of teh device's bdaddr
// (looks like "xx:xx:xx:xx:xx:xx", i.e. the six bytes comprising the
// bdaddr in hex representation)
int hci_devid(const char *str);
//gets complete hci_dev_info of device identified by dev_id
int hci_devinfo(int dev_id, /*out*/struct hci_dev_info *di);
struct hci_dev_info {
uint16_t dev_id;
char name[8];
bdaddr_t bdaddr;
uint32_t flags;
uint8_t type;
uint8_t features[8];
uint32_t pkt_type;
uint32_t link_policy;
uint32_t link_mode;
uint16_t acl_mtu;
uint16_t acl_pkts;
uint16_t sco_mtu;
uint16_t sco_pkts;
struct hci_dev_stats stat;
};
//gets bdaddr of device dev_id
int hci_devba(int dev_id, /*out*/bdaddr_t *ba)
// (Wrapper around ioctl: HCIINQUIRY)
//
//inputs:
//
//dev_id - "starting device" of the inquiry? If NULL, start with
// first (HCIGETDEVLIST) found device.
//
//nrsp - max. number of result devices
//
//len - ?? ( => hci_inquiry_req.length)
//
//flags - ?? ( => hci_inquiry_req.flags)
//
//return: -1 on error, else:
// number of result inquiry_info structures; *ii will be
// malloc()ed and filled with these inquiry_info structures
int hci_inquiry(int dev_id, int len, int nrsp, const uint8_t *lap, inquiry_info
**ii, long flags);
L2CAP
========================
// create L2CAP connection
int sock = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
struct sockaddr_l2 sa;
sa.l2_family = AF_BLUETOOTH;
sa.l2_psm = 0;
sa.l2_bdaddr = <source, i.e. our local address?>;
bind(sock, (struct sockaddr *)&sa, sizeof(sa));
sa.l2_psm = htobs(SDP_PSM);
sa.l2_bdaddr = <destination device's address>;
connect(sock, (struct sockaddr *)&sa, sizeof(sa));
SDP (Service Discovery Protocol)
================================
- used to discover "services" provided by connected devices
- protocol on top of L2CAP
- devices provide "services", services have "attributes"
- specific services and attributes have unique identifiers (UUIDs?)
registered with the Bluetooth SIG (special interest group)
- SDP capabilities:
- discover services and attributes offered by a given device
- discover all devices offering a given service
- discover all devices offering services with given attributes
- ...
- services may be dynamically activated/deactivated on a device
- but there's no event notification mechanism (so polling may be
necessary)
- Linux implementation:
- in userspace
-
const bdaddr_t *src = ...; const bdaddr_t *dst = ...;
struct sockaddr_l2 sa;
int sock = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
sa.l2_family = AF_BLUETOOTH;
sa.l2_psm = 0;
if (bacmp(src, BDADDR_ANY) != 0) {
sa.l2_bdaddr = *src;
if (0 > bind(session->sock, (struct sockaddr *)&sa, sizeof(sa)))
goto fail;
}
sa.l2_psm = htobs(SDP_PSM);
sa.l2_bdaddr = *dst;
connect(session->sock, (struct sockaddr *)&sa, sizeof(sa)) == 0)
...then write/read SDP PDUs to/from the socket.
- sdp.h/sdp.c (from bluez-libs) provides wrapper functions in userspace
//////sdp.h/sdp.c userspace library:
struct sdp_list_t { //utility type: linked list of void*s
sdp_list_t *next;
void *data;
};
struct sdp_record_t {
uint32_t handle;
//Spec: A service record handle is a 32-bit number that uniquely
//identifies each service record within an SDP server. It is
//important to note that, in general, each handle is
//unique only within each SDP server.
sdp_list_t *pattern; //purpose?
sdp_list_t *attrlist; //list of sdp_data_t, i.e. list of lists of SDP attributes
};
struct sdp_data_t { //obviously a list of SDP attributes
uint16_t attrId;
SDP_BOOL|SDP_UINT8|SDP_UINT16|...|SDP_UUID_..|..|SDP_TEXT_STR8|SDP_TEXT_STR16|...|SDP_SEQ_.. dtd; //type
//SDP_TEXT_STR8 -- string length <256 chars; SDP_TEXT_STR16 -- string length <65536 chars
int8_t int8 | int16_t int16 | .. | uuid_t uuid | char *str | sdp_data_t *dataseq val; //value
sdp_data_t *next;
int unitSize; //?
};
//predefined attributes (attrId, dtd):
// ( * See SDP Spec, section "Service Attribute Definitions" for more details.)
// SDP_ATTR_SVCNAME_PRIMARY, STR8
// SDP_ATTR_SVCDESC_PRIMARY, STR8
// SDP_ATTR_PROVNAME_PRIMARY, STR8
// SDP_ATTR_PROTO_DESC_LIST, ?
// SDP_ATTR_LANG_BASE_ATTR_ID_LIST, ?
// SDP_ATTR_PFILE_DESC_LIST, SDP_SEQ_? (list of (uint8_t uuid, uint16_t Vnum) => sdp_profile_desc_t. Use
// int sdp_get_profile_descs(const sdp_record_t *rec, sdp_list_t **profDescSeq);
// to extract. *profDescSeq will be returned as a list of sdp_profile_desc_t's.
// SDP_ATTR_VERSION_NUM_LIST, SDP_SEQ_? (list of uint16_t Vnum). Use
// int sdp_get_server_ver(const sdp_record_t *rec, sdp_list_t **u16); to extract.
// SDP_ATTR_SERVICE_ID, SDP_UUID_?. Use int sdp_get_service_id(const sdp_record_t *rec, uuid_t *uuid) to extract
// SDP_ATTR_GROUP_ID, SDP_UUID_?. Use int sdp_get_group_id(const sdp_record_t *rec, uuid_t *uuid) to extract
// SDP_ATTR_RECORD_STATE, SDP_UINT32
// ...
//return first attribute (sdp_data_t) in rec->attrlist whose attrId equals attrId
sdp_data_t *sdp_data_get(const sdp_record_t *rec, uint16_t attrId);
//if first attribute (sdp_data_t) in rec->attrlist whose attrId equals attrId
// is an int, return it in *value.
int sdp_get_int_attr(const sdp_record_t *rec, uint16_t attrid, int *value);
//same for string attributes (length must be < valuelen)
int sdp_get_string_attr(const sdp_record_t *rec, uint16_t attrid, char *value, int valuelen);
//allocate new attribute of type dtd and value *value
sdp_data_t *sdp_data_alloc(uint8_t dtd, const void *value)
////all functions dexcribed so far work "off-line", i.e. on sdp_record_t's present
//// in the local machine's memory
////"on-line" functions
sdp_session_t *sdp_connect(const bdaddr_t *src, const bdaddr_t *dst, uint32_t flags);
int sdp_close(sdp_session_t *session);
static inline int sdp_get_socket(const sdp_session_t *s);
/*
* a service search request.
*
* INPUT :
*
* sdp_list_t *search_list
* list containing elements of the search
* pattern. Each entry in the list is a UUID
* of the service to be searched
*
* uint16_t max_rec_num
* An integer specifying the maximum number of
* entries that the client can handle in the response.
*
* OUTPUT :
*
* int return value
* 0
* The request completed successfully. This does not
* mean the requested services were found
* -1
* The request completed unsuccessfully
*
* sdp_list_t *rsp_list
* This variable is set on a successful return if there are
* non-zero service handles. It is a singly linked list of
* service records (sdp_record_t)
*/
int sdp_service_search_req(sdp_session_t *session, const sdp_list_t *search_list, uint16_t max_rec_num, sdp_list_t **rsp_list);
/////BUGS in bluez-libs
setting dtd in sdp_data_t *sdp_data_alloc(uint8_t dtd, const void *value) (in sdp.c) has no effect.
========================
========================
========================
data structures (userspace):
typedef struct {
bdaddr_t bdaddr;
uint8_t pscan_rep_mode;
uint8_t pscan_period_mode;
uint8_t pscan_mode;
uint8_t dev_class[3];
uint16_t clock_offset;
} __attribute__ ((packed)) inquiry_info;
#define INQUIRY_INFO_SIZE 14
struct hci_inquiry_req {
uint16_t dev_id;
uint16_t flags;
uint8_t lap[3];
uint8_t length;
uint8_t num_rsp;
};
data structures (kernel):
struct hci_inquiry_req {
__u16 dev_id;
__u16 flags;
__u8 lap[3];
__u8 length;
__u8 num_rsp;
};
typedef struct {
__u8 lap[3];
__u8 length;
__u8 num_rsp;
} __attribute__ ((packed)) inquiry_cp;
#define INQUIRY_CP_SIZE 5
struct hci_dev {
struct list_head list;
spinlock_t lock;
atomic_t refcnt;
char name[8];
unsigned long flags;
__u16 id;
__u8 type;
bdaddr_t bdaddr;
__u8 features[8];
__u16 pkt_type;
__u16 link_policy;
__u16 link_mode;
atomic_t cmd_cnt;
unsigned int acl_cnt;
unsigned int sco_cnt;
unsigned int acl_mtu;
unsigned int sco_mtu;
unsigned int acl_pkts;
unsigned int sco_pkts;
unsigned long cmd_last_tx;
unsigned long acl_last_tx;
unsigned long sco_last_tx;
struct tasklet_struct cmd_task;
struct tasklet_struct rx_task;
struct tasklet_struct tx_task;
struct sk_buff_head rx_q;
struct sk_buff_head raw_q;
struct sk_buff_head cmd_q;
struct sk_buff *sent_cmd;
struct semaphore req_lock;
wait_queue_head_t req_wait_q;
__u32 req_status;
__u32 req_result;
struct inquiry_cache inq_cache;
struct conn_hash conn_hash;
struct hci_dev_stats stat;
void *driver_data;
void *core_data;
atomic_t promisc;
int (*open)(struct hci_dev *hdev);
int (*close)(struct hci_dev *hdev);
int (*flush)(struct hci_dev *hdev);
int (*send)(struct sk_buff *skb);
void (*destruct)(struct hci_dev *hdev);
int (*ioctl)(struct hci_dev *hdev, unsigned int cmd, unsigned long arg);
};
back to bluetooth
(C) 1998-2017 Olaf Klischat <olaf.klischat@gmail.com>