func (cln *Client) DiscoverDescriptors(ds []ble.UUID, c *ble.Characteristic) ([]*ble.Descriptor, error) {
	rsp, err := cln.conn.sendReq(cmdDiscoverDescriptors, xpc.Dict{
		"kCBMsgArgDeviceUUID":                cln.id,
		"kCBMsgArgCharacteristicHandle":      c.Handle,
		"kCBMsgArgCharacteristicValueHandle": c.ValueHandle,
		"kCBMsgArgUUIDs":                     uuidSlice(ds),
	})
	if err != nil {
		return nil, err
	}
	if err := rsp.err(); err != nil {
		return nil, err
	}
	for _, xds := range rsp.descriptors() {
		xd := msg(xds.(xpc.Dict))
		c.Descriptors = append(c.Descriptors, &ble.Descriptor{
			UUID:   ble.MustParse(xd.uuid()),
			Handle: uint16(xd.descriptorHandle()),
		})
	}
	return c.Descriptors, nil
}
DiscoverDescriptors finds all the descriptors within a characteristic. [Vol 3, Part G, 4.7.1]
If filter is specified, only filtered descriptors are returned.
package darwin
import (
	"fmt"
	"github.com/rigado/ble"
	"github.com/raff/goble/xpc"
)
type Client struct {
	profile *ble.Profile
	name    string
	id   xpc.UUID
	conn *conn
}
func NewClient(c ble.Conn) (*Client, error) {
	return &Client{
		conn: c.(*conn),
		id:   xpc.MakeUUID(c.RemoteAddr().String()),
	}, nil
}
func (cln *Client) Addr() ble.Addr {
	return cln.conn.RemoteAddr()
}
func (cln *Client) Name() string {
	return cln.name
}
func (cln *Client) Profile() *ble.Profile {
	return cln.profile
}
func (cln *Client) DiscoverProfile(force bool) (*ble.Profile, error) {
	if cln.profile != nil && !force {
		return cln.profile, nil
	}
	ss, err := cln.DiscoverServices(nil)
	if err != nil {
		return nil, fmt.Errorf("can't discover services: %s", err)
	}
	for _, s := range ss {
		cs, err := cln.DiscoverCharacteristics(nil, s)
		if err != nil {
			return nil, fmt.Errorf("can't discover characteristics: %s", err)
		}
		for _, c := range cs {
			_, err := cln.DiscoverDescriptors(nil, c)
			if err != nil {
				return nil, fmt.Errorf("can't discover descriptors: %s", err)
			}
		}
	}
	cln.profile = &ble.Profile{Services: ss}
	return cln.profile, nil
}
func (cln *Client) DiscoverServices(ss []ble.UUID) ([]*ble.Service, error) {
	rsp, err := cln.conn.sendReq(cmdDiscoverServices, xpc.Dict{
		"kCBMsgArgDeviceUUID": cln.id,
		"kCBMsgArgUUIDs":      uuidSlice(ss),
	})
	if err != nil {
		return nil, err
	}
	if err := rsp.err(); err != nil {
		return nil, err
	}
	svcs := []*ble.Service{}
	for _, xss := range rsp.services() {
		xs := msg(xss.(xpc.Dict))
		svcs = append(svcs, &ble.Service{
			UUID:      ble.MustParse(xs.uuid()),
			Handle:    uint16(xs.serviceStartHandle()),
			EndHandle: uint16(xs.serviceEndHandle()),
		})
	}
	if cln.profile == nil {
		cln.profile = &ble.Profile{Services: svcs}
	}
	return svcs, nil
}
func (cln *Client) DiscoverIncludedServices(ss []ble.UUID, s *ble.Service) ([]*ble.Service, error) {
	rsp, err := cln.conn.sendReq(cmdDiscoverIncludedServices, xpc.Dict{
		"kCBMsgArgDeviceUUID":         cln.id,
		"kCBMsgArgServiceStartHandle": s.Handle,
		"kCBMsgArgServiceEndHandle":   s.EndHandle,
		"kCBMsgArgUUIDs":              uuidSlice(ss),
	})
	if err != nil {
		return nil, err
	}
	if err := rsp.err(); err != nil {
		return nil, err
	}
	return nil, ble.ErrNotImplemented
}
func (cln *Client) DiscoverCharacteristics(cs []ble.UUID, s *ble.Service) ([]*ble.Characteristic, error) {
	rsp, err := cln.conn.sendReq(cmdDiscoverCharacteristics, xpc.Dict{
		"kCBMsgArgDeviceUUID":         cln.id,
		"kCBMsgArgServiceStartHandle": s.Handle,
		"kCBMsgArgServiceEndHandle":   s.EndHandle,
		"kCBMsgArgUUIDs":              uuidSlice(cs),
	})
	if err != nil {
		return nil, err
	}
	if err := rsp.err(); err != nil {
		return nil, err
	}
	for _, xcs := range rsp.characteristics() {
		xc := msg(xcs.(xpc.Dict))
		s.Characteristics = append(s.Characteristics, &ble.Characteristic{
			UUID:        ble.MustParse(xc.uuid()),
			Property:    ble.Property(xc.characteristicProperties()),
			Handle:      uint16(xc.characteristicHandle()),
			ValueHandle: uint16(xc.characteristicValueHandle()),
		})
	}
	return s.Characteristics, nil
}