Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,7 @@ jobs:

- name: vet
run: go vet ./...

# The tests need no Bluetooth controller.
- name: test
run: go test ./...
10 changes: 10 additions & 0 deletions att.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,24 +48,32 @@ type ATTRequest struct {

// Central: https://developer.apple.com/documentation/corebluetooth/cbattrequest/1518995-central
func (r ATTRequest) Central() Central {
defer popPool(pushPool())

ptr := C.cb_atr_central(r.ptr)
return Central{unsafe.Pointer(ptr)}
}

// Characteristic: https://developer.apple.com/documentation/corebluetooth/cbattrequest/1518716-characteristic
func (r ATTRequest) Characteristic() Characteristic {
defer popPool(pushPool())

ptr := C.cb_atr_characteristic(r.ptr)
return Characteristic{unsafe.Pointer(ptr)}
}

// Value: https://developer.apple.com/documentation/corebluetooth/cbattrequest/1518795-value
func (r ATTRequest) Value() []byte {
defer popPool(pushPool())

ba := C.cb_atr_value(r.ptr)
return byteArrToByteSlice(&ba)
}

// SetValue: https://developer.apple.com/documentation/corebluetooth/cbattrequest/1518795-value
func (r ATTRequest) SetValue(v []byte) {
defer popPool(pushPool())

ba := byteSliceToByteArr(v)
defer C.free(unsafe.Pointer(ba.data))

Expand All @@ -74,5 +82,7 @@ func (r ATTRequest) SetValue(v []byte) {

// Offset: https://developer.apple.com/documentation/corebluetooth/cbattrequest/1518857-offset
func (r ATTRequest) Offset() int {
defer popPool(pushPool())

return int(C.cb_atr_offset(r.ptr))
}
2 changes: 2 additions & 0 deletions bt.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ struct pmgr_restore_opts {
bool bt_start();
void bt_stop();
void bt_init();
void *bt_pool_push();
void bt_pool_pop(void *pool);

// util.m
struct byte_arr nsdata_to_byte_arr(const NSData *nsdata);
Expand Down
17 changes: 17 additions & 0 deletions bt.m
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,20 @@
bt_dlg = [[BTDlg alloc] init];
[bt_dlg retain];
}

/**
* Pushes an autorelease pool on the calling thread. Go threads have none, so
* an object autoreleased by a call from Go stays allocated until bt_pool_pop
* drains the pool this returns.
*/
void *
bt_pool_push()
{
return [[NSAutoreleasePool alloc] init];
}

void
bt_pool_pop(void *pool)
{
[(NSAutoreleasePool *)pool drain];
}
4 changes: 4 additions & 0 deletions central.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,15 @@ type Central struct {

// Identifier: https://developer.apple.com/documentation/corebluetooth/cbpeer/1620687-identifier
func (c Central) Identifier() UUID {
defer popPool(pushPool())

cstr := C.cb_peer_identifier(c.ptr)
return MustParseUUID(C.GoString(cstr))
}

// MaximumUpdateValueLength: https://developer.apple.com/documentation/corebluetooth/cbcentral/1408800-maximumupdatevaluelength
func (c Central) MaximumUpdateValueLength() int {
defer popPool(pushPool())

return int(C.cb_cent_maximum_update_len(c.ptr))
}
20 changes: 20 additions & 0 deletions centralmanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ func findCentralManagerDlg(ptr unsafe.Pointer) CentralManagerDelegate {
// NewCentralManager creates a central manager. Specify a nil `opts` value for
// defaults. Don't forget to call `SetDelegate()` afterwards!
func NewCentralManager(opts *ManagerOpts) CentralManager {
defer popPool(pushPool())

if opts == nil {
opts = &DfltManagerOpts
}
Expand All @@ -84,6 +86,8 @@ func NewCentralManager(opts *ManagerOpts) CentralManager {
// SetDelegate configures a receiver for a central manager's asynchronous
// callbacks.
func (cm CentralManager) SetDelegate(d CentralManagerDelegate) {
defer popPool(pushPool())

if d != nil {
cmgrPtrMap.add(cm.ptr, d)
}
Expand All @@ -92,11 +96,15 @@ func (cm CentralManager) SetDelegate(d CentralManagerDelegate) {

// State: https://developer.apple.com/documentation/corebluetooth/cbmanager/1648600-state
func (cm CentralManager) State() ManagerState {
defer popPool(pushPool())

return ManagerState(C.cb_cmgr_state(cm.ptr))
}

// Connect: https://developer.apple.com/documentation/corebluetooth/cbcentralmanager/1518766-connectperipheral
func (cm CentralManager) Connect(prph Peripheral, opts *CentralManagerConnectOpts) {
defer popPool(pushPool())

if opts == nil {
opts = &DfltCentralManagerConnectOpts
}
Expand All @@ -115,11 +123,15 @@ func (cm CentralManager) Connect(prph Peripheral, opts *CentralManagerConnectOpt

// CancelConnect: https://developer.apple.com/documentation/corebluetooth/cbcentralmanager/1518952-cancelperipheralconnection
func (cm CentralManager) CancelConnect(prph Peripheral) {
defer popPool(pushPool())

C.cb_cmgr_cancel_connect(cm.ptr, prph.ptr)
}

// RetrieveConnectedPeripheralsWithServices: https://developer.apple.com/documentation/corebluetooth/cbcentralmanager/1518924-retrieveconnectedperipheralswith
func (cm CentralManager) RetrieveConnectedPeripheralsWithServices(uuids []UUID) []Peripheral {
defer popPool(pushPool())

strs := uuidsToStrArr(uuids)
defer freeStrArr(&strs)

Expand All @@ -138,6 +150,8 @@ func (cm CentralManager) RetrieveConnectedPeripheralsWithServices(uuids []UUID)

// RetrievePeripheralsWithIdentifiers: https://developer.apple.com/documentation/corebluetooth/cbcentralmanager/1519127-retrieveperipheralswithidentifie
func (cm CentralManager) RetrievePeripheralsWithIdentifiers(uuids []UUID) []Peripheral {
defer popPool(pushPool())

strs := uuidsToStrArr(uuids)
defer freeStrArr(&strs)

Expand All @@ -156,6 +170,8 @@ func (cm CentralManager) RetrievePeripheralsWithIdentifiers(uuids []UUID) []Peri

// Scan: https://developer.apple.com/documentation/corebluetooth/cbcentralmanager/1518986-scanforperipheralswithservices
func (cm CentralManager) Scan(serviceUUIDs []UUID, opts *CentralManagerScanOpts) {
defer popPool(pushPool())

arrSvcUUIDs := uuidsToStrArr(serviceUUIDs)
defer freeStrArr(&arrSvcUUIDs)

Expand All @@ -176,10 +192,14 @@ func (cm CentralManager) Scan(serviceUUIDs []UUID, opts *CentralManagerScanOpts)

// StopScan: https://developer.apple.com/documentation/corebluetooth/cbcentralmanager/1518984-stopscan
func (cm CentralManager) StopScan() {
defer popPool(pushPool())

C.cb_cmgr_stop_scan(cm.ptr)
}

// IsScanning: https://developer.apple.com/documentation/corebluetooth/cbcentralmanager/1620640-isscanning
func (cm CentralManager) IsScanning() bool {
defer popPool(pushPool())

return bool(C.cb_cmgr_is_scanning(cm.ptr))
}
12 changes: 12 additions & 0 deletions characteristic.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,24 +39,32 @@ type Characteristic struct {

// UUID: https://developer.apple.com/documentation/corebluetooth/cbattribute/1620638-uuid
func (c Characteristic) UUID() UUID {
defer popPool(pushPool())

cstr := C.cb_chr_uuid(c.ptr)
return MustParseUUID(C.GoString(cstr))
}

// Service: https://developer.apple.com/documentation/corebluetooth/cbcharacteristic/1518728-service
func (c Characteristic) Service() Service {
defer popPool(pushPool())

ptr := C.cb_chr_service(c.ptr)
return Service{ptr}
}

// Value: https://developer.apple.com/documentation/corebluetooth/cbcharacteristic/1518878-value
func (c Characteristic) Value() []byte {
defer popPool(pushPool())

ba := C.cb_chr_value(c.ptr)
return byteArrToByteSlice(&ba)
}

// Descriptors: https://developer.apple.com/documentation/corebluetooth/cbcharacteristic/1518957-descriptors
func (c Characteristic) Descriptors() []Descriptor {
defer popPool(pushPool())

oa := C.cb_chr_descriptors(c.ptr)
defer C.free(unsafe.Pointer(oa.objs))

Expand All @@ -71,10 +79,14 @@ func (c Characteristic) Descriptors() []Descriptor {

// Properties: https://developer.apple.com/documentation/corebluetooth/cbcharacteristic/1519010-properties
func (c Characteristic) Properties() CharacteristicProperties {
defer popPool(pushPool())

return CharacteristicProperties(C.cb_chr_properties(c.ptr))
}

// IsNotifying: https://developer.apple.com/documentation/corebluetooth/cbcharacteristic/1519057-isnotifying
func (c Characteristic) IsNotifying() bool {
defer popPool(pushPool())

return bool(C.cb_chr_is_notifying(c.ptr))
}
10 changes: 6 additions & 4 deletions cmgr.m
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@
const struct scan_opts *opts)
{
NSArray *arr_svc_uuids = strs_to_cbuuids(svc_uuids);
[arr_svc_uuids autorelease];

NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
[dict autorelease];
Expand All @@ -69,7 +68,6 @@

if (opts->sol_svc_uuids.count > 0) {
NSArray *arr_sol_svc_uuids = strs_to_cbuuids(&opts->sol_svc_uuids);
[arr_sol_svc_uuids autorelease];

[dict setObject:arr_sol_svc_uuids
forKey:CBCentralManagerScanOptionSolicitedServiceUUIDsKey];
Expand Down Expand Up @@ -150,8 +148,10 @@
void **objs = malloc([prphs count] * sizeof *objs);
assert(objs != NULL);

// Go keeps these after the pool releases the array, so each one is
// retained like a peripheral the delegate hands to Go.
for (int i = 0; i < [prphs count]; i++) {
objs[i] = [prphs objectAtIndex:i];
objs[i] = [[prphs objectAtIndex:i] retain];
}

return (struct obj_arr) {
Expand All @@ -175,8 +175,10 @@
void **objs = malloc([prphs count] * sizeof *objs);
assert(objs != NULL);

// Go keeps these after the pool releases the array, so each one is
// retained like a peripheral the delegate hands to Go.
for (int i = 0; i < [prphs count]; i++) {
objs[i] = [prphs objectAtIndex:i];
objs[i] = [[prphs objectAtIndex:i] retain];
}

return (struct obj_arr) {
Expand Down
20 changes: 20 additions & 0 deletions cutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,29 @@ package cbgo
import "C"

import (
"runtime"
"unsafe"
)

// pushPool pushes an autorelease pool for a call into Objective-C, and locks
// the goroutine to its thread until popPool drains it: a pool belongs to the
// thread that pushed it. Every function that calls into Objective-C starts
// with
//
// defer popPool(pushPool())
//
// so that what the call autoreleases is released when it returns, after the
// function has copied any C string or bytes that point into those objects.
func pushPool() unsafe.Pointer {
runtime.LockOSThread()
return C.bt_pool_push()
}

func popPool(pool unsafe.Pointer) {
C.bt_pool_pop(pool)
runtime.UnlockOSThread()
}

func mallocArr(numElems int, elemSize uintptr) unsafe.Pointer {
return C.malloc(C.size_t(numElems) * C.size_t(elemSize))
}
Expand Down
6 changes: 6 additions & 0 deletions descriptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,24 @@ type Descriptor struct {

// UUID: https://developer.apple.com/documentation/corebluetooth/cbattribute/1620638-uuid
func (d Descriptor) UUID() UUID {
defer popPool(pushPool())

cstr := C.cb_dsc_uuid(d.ptr)
return MustParseUUID(C.GoString(cstr))
}

// Characteristic: https://developer.apple.com/documentation/corebluetooth/cbdescriptor/1519035-characteristic
func (d Descriptor) Characteristic() Characteristic {
defer popPool(pushPool())

ptr := C.cb_dsc_characteristic(d.ptr)
return Characteristic{ptr}
}

// Value: https://developer.apple.com/documentation/corebluetooth/cbdescriptor/1518778-value
func (d Descriptor) Value() []byte {
defer popPool(pushPool())

ba := C.cb_dsc_value(d.ptr)
return byteArrToByteSlice(&ba)
}
21 changes: 21 additions & 0 deletions internal/heap/heap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Package heap measures the C heap, for tests that check what a call into
// Objective-C leaves allocated.
package heap

/*
#include <malloc/malloc.h>

static size_t
heap_in_use(void)
{
malloc_statistics_t stats;
malloc_zone_statistics(NULL, &stats);
return stats.size_in_use;
}
*/
import "C"

// InUse returns the bytes allocated in every malloc zone of the process.
func InUse() int {
return int(C.heap_in_use())
}
Loading
Loading