func (c *Client) PrepareWrite(handle uint16, offset uint16, value []byte) (uint16, uint16, []byte, error) {
	if len(value) > c.l2c.TxMTU()-5 {
		return 0, 0, nil, ErrInvalidArgument
	}
	txBuf := <-c.chTxBuf
	defer func() { c.chTxBuf <- txBuf }()
	req := PrepareWriteRequest(txBuf[:5+len(value)])
	req.SetAttributeOpcode()
	req.SetAttributeHandle(handle)
	req.SetValueOffset(offset)
	b, err := c.sendReq(req)
	if err != nil {
		return 0, 0, nil, err
	}
	rsp := PrepareWriteResponse(b)
	switch {
	case rsp[0] == ErrorResponseCode && len(rsp) == 5:
		return 0, 0, nil, ble.ATTError(rsp[4])
	case rsp[0] == ErrorResponseCode && len(rsp) != 5:
		fallthrough
	case rsp[0] != rsp.AttributeOpcode():
		fallthrough
	case len(rsp) < 5:
		return 0, 0, nil, ErrInvalidResponse
	}
	return rsp.AttributeHandle(), rsp.ValueOffset(), rsp.PartAttributeValue(), nil
}
PrepareWrite requests the server to prepare to write the value of an attribute.
The server will respond to this request with a Prepare Write Response, so that
the Client can verify that the value was received correctly.
[Vol 3, Part F, 3.4.6.1 & 3.4.6.2]
package att
import (
	"encoding/binary"
	"fmt"
	"time"
	"github.com/pkg/errors"
	"github.com/rigado/ble"
)
type NotificationHandler interface {
	HandleNotification(req []byte)
}
type Client struct {
	l2c  ble.Conn
	rspc chan []byte
	inc  chan []byte
	rxBuf      []byte
	chTxBuf    chan []byte
	chErr      chan error
	handler    NotificationHandler
	done       chan bool
	connClosed chan struct{}
	server *Server
	ble.Logger
}
func NewClient(l2c ble.Conn, h NotificationHandler, done chan bool, l ble.Logger) *Client {
	c := &Client{
		l2c:        l2c,
		rspc:       make(chan []byte),
		inc:        make(chan []byte, 10),
		chTxBuf:    make(chan []byte, 1),
		rxBuf:      make([]byte, ble.MaxMTU),
		chErr:      make(chan error, 1),
		handler:    h,
		done:       done,
		connClosed: make(chan struct{}),
		Logger:     l,
	}
	c.chTxBuf <- make([]byte, l2c.TxMTU())
	go func() {
		<-l2c.Disconnected()
		close(c.connClosed)
	}()
	return c
}
func (c *Client) WithServer(db *DB) *Client {
	var err error
	c.server, err = NewServer(db, c.l2c, c.Logger)
	if err != nil {
		c.Errorf("client: failed to create server")
	}
	return c
}
func (c *Client) ExchangeMTU(clientRxMTU int) (serverRxMTU int, err error) {
	if clientRxMTU < ble.DefaultMTU || clientRxMTU > ble.MaxMTU {
		return 0, ErrInvalidArgument
	}
	txBuf := <-c.chTxBuf
	defer func() { c.chTxBuf <- txBuf }()
	c.l2c.SetRxMTU(clientRxMTU)
	req := ExchangeMTURequest(txBuf[:3])
	req.SetAttributeOpcode()
	req.SetClientRxMTU(uint16(clientRxMTU))
	b, err := c.sendReq(req)
	if err != nil {
		return 0, err
	}
	rsp := ExchangeMTUResponse(b)
	switch {
	case rsp[0] == ErrorResponseCode && len(rsp) == 5:
		return 0, ble.ATTError(rsp[4])
	case rsp[0] == ErrorResponseCode && len(rsp) != 5:
		fallthrough
	case rsp[0] != rsp.AttributeOpcode():
		fallthrough
	case len(rsp) != 3:
		return 0, ErrInvalidResponse
	}
	txMTU := int(rsp.ServerRxMTU())
	if len(txBuf) != txMTU {
		c.l2c.SetTxMTU(txMTU)
		txBuf = make([]byte, txMTU)
	}
	return txMTU, nil
}
func (c *Client) FindInformation(starth, endh uint16) (fmt int, data []byte, err error) {
	if starth == 0 || starth > endh {
		return 0x00, nil, ErrInvalidArgument
	}
	txBuf := <-c.chTxBuf
	defer func() { c.chTxBuf <- txBuf }()
	req := FindInformationRequest(txBuf[:5])
	req.SetAttributeOpcode()
	req.SetStartingHandle(starth)
	req.SetEndingHandle(endh)
	b, err := c.sendReq(req)
	if err != nil {
		return 0x00, nil, err
	}
	rsp := FindInformationResponse(b)
	switch {
	case rsp[0] == ErrorResponseCode && len(rsp) == 5:
		return 0x00, nil, ble.ATTError(rsp[4])
	case rsp[0] == ErrorResponseCode && len(rsp) != 5:
		fallthrough
	case rsp[0] != rsp.AttributeOpcode():
		fallthrough
	case len(rsp) < 6:
		fallthrough
	case rsp.Format() == 0x01 && ((len(rsp)-2)%4) != 0:
		fallthrough
	case rsp.Format() == 0x02 && ((len(rsp)-2)%18) != 0:
		return 0x00, nil, ErrInvalidResponse
	}
	return int(rsp.Format()), rsp.InformationData(), nil
}
func (c *Client) ReadByType(starth, endh uint16, uuid ble.UUID) (int, []byte, error) {
	if starth > endh || (len(uuid) != 2 && len(uuid) != 16) {
		return 0, nil, ErrInvalidArgument
	}
	txBuf := <-c.chTxBuf
	defer func() { c.chTxBuf <- txBuf }()
	req := ReadByTypeRequest(txBuf[:5+len(uuid)])
	req.SetAttributeOpcode()
	req.SetStartingHandle(starth)
	req.SetEndingHandle(endh)
	req.SetAttributeType(uuid)
	b, err := c.sendReq(req)
	if err != nil {
		return 0, nil, err
	}
	rsp := ReadByTypeResponse(b)
	switch {
	case rsp[0] == ErrorResponseCode && len(rsp) == 5:
		return 0, nil, ble.ATTError(rsp[4])
	case rsp[0] == ErrorResponseCode && len(rsp) != 5:
		fallthrough
	case rsp[0] != rsp.AttributeOpcode():
		fallthrough
	case len(rsp) < 4 || len(rsp.AttributeDataList())%int(rsp.Length()) != 0:
		return 0, nil, ErrInvalidResponse
	}
	return int(rsp.Length()), rsp.AttributeDataList(), nil
}
func (c *Client) Read(handle uint16) ([]byte, error) {
	txBuf := <-c.chTxBuf
	defer func() { c.chTxBuf <- txBuf }()
	req := ReadRequest(txBuf[:3])
	req.SetAttributeOpcode()
	req.SetAttributeHandle(handle)
	b, err := c.sendReq(req)
	if err != nil {
		return nil, err
	}
	rsp := ReadResponse(b)
	switch {
	case rsp[0] == ErrorResponseCode && len(rsp) == 5:
		return nil, ble.ATTError(rsp[4])
	case rsp[0] == ErrorResponseCode && len(rsp) != 5:
		fallthrough
	case rsp[0] != rsp.AttributeOpcode():
		fallthrough
	case len(rsp) < 1:
		return nil, ErrInvalidResponse
	}
	return rsp.AttributeValue(), nil
}
func (c *Client) ReadBlob(handle, offset uint16) ([]byte, error) {
	txBuf := <-c.chTxBuf
	defer func() { c.chTxBuf <- txBuf }()
	req := ReadBlobRequest(txBuf[:5])
	req.SetAttributeOpcode()
	req.SetAttributeHandle(handle)
	req.SetValueOffset(offset)
	b, err := c.sendReq(req)
	if err != nil {
		return nil, err
	}
	rsp := ReadBlobResponse(b)
	switch {
	case rsp[0] == ErrorResponseCode && len(rsp) == 5:
		return nil, ble.ATTError(rsp[4])
	case rsp[0] == ErrorResponseCode && len(rsp) != 5:
		fallthrough
	case rsp[0] != rsp.AttributeOpcode():
		fallthrough
	case len(rsp) < 1:
		return nil, ErrInvalidResponse
	}
	return rsp.PartAttributeValue(), nil
}
func (c *Client) ReadMultiple(handles []uint16) ([]byte, error) {
	if len(handles) < 2 || len(handles)*2 > c.l2c.TxMTU()-1 {
		return nil, ErrInvalidArgument
	}
	txBuf := <-c.chTxBuf
	defer func() { c.chTxBuf <- txBuf }()
	req := ReadMultipleRequest(txBuf[:1+len(handles)*2])
	req.SetAttributeOpcode()
	p := req.SetOfHandles()
	for _, h := range handles {
		binary.LittleEndian.PutUint16(p, h)
		p = p[2:]
	}
	b, err := c.sendReq(req)
	if err != nil {
		return nil, err
	}
	rsp := ReadMultipleResponse(b)
	switch {
	case rsp[0] == ErrorResponseCode && len(rsp) == 5:
		return nil, ble.ATTError(rsp[4])
	case rsp[0] == ErrorResponseCode && len(rsp) != 5:
		fallthrough
	case rsp[0] != rsp.AttributeOpcode():
		fallthrough
	case len(rsp) < 1:
		return nil, ErrInvalidResponse
	}
	return rsp.SetOfValues(), nil
}
func (c *Client) ReadByGroupType(starth, endh uint16, uuid ble.UUID) (int, []byte, error) {
	if starth > endh || (len(uuid) != 2 && len(uuid) != 16) {
		return 0, nil, ErrInvalidArgument
	}
	txBuf := <-c.chTxBuf
	defer func() { c.chTxBuf <- txBuf }()
	req := ReadByGroupTypeRequest(txBuf[:5+len(uuid)])
	req.SetAttributeOpcode()
	req.SetStartingHandle(starth)
	req.SetEndingHandle(endh)
	req.SetAttributeGroupType(uuid)
	b, err := c.sendReq(req)
	if err != nil {
		return 0, nil, err
	}
	rsp := ReadByGroupTypeResponse(b)
	switch {
	case rsp[0] == ErrorResponseCode && len(rsp) == 5:
		return 0, nil, ble.ATTError(rsp[4])
	case rsp[0] == ErrorResponseCode && len(rsp) != 5:
		fallthrough
	case rsp[0] != rsp.AttributeOpcode():
		fallthrough
	case len(rsp) < 4:
		fallthrough
	case len(rsp.AttributeDataList())%int(rsp.Length()) != 0:
		return 0, nil, ErrInvalidResponse
	}
	return int(rsp.Length()), rsp.AttributeDataList(), nil
}
func (c *Client) Write(handle uint16, value []byte) error {
	if len(value) > c.l2c.TxMTU()-3 {
		return ErrInvalidArgument
	}
	txBuf := <-c.chTxBuf
	defer func() { c.chTxBuf <- txBuf }()
	req := WriteRequest(txBuf[:3+len(value)])
	req.SetAttributeOpcode()
	req.SetAttributeHandle(handle)
	req.SetAttributeValue(value)
	b, err := c.sendReq(req)
	if err != nil {
		return err
	}
	rsp := WriteResponse(b)
	switch {
	case rsp[0] == ErrorResponseCode && len(rsp) == 5:
		return ble.ATTError(rsp[4])
	case rsp[0] == ErrorResponseCode && len(rsp) != 5:
		fallthrough
	case rsp[0] != rsp.AttributeOpcode():
		return ErrInvalidResponse
	}
	return nil
}
func (c *Client) WriteCommand(handle uint16, value []byte) error {
	if len(value) > c.l2c.TxMTU()-3 {
		return ErrInvalidArgument
	}
	txBuf := <-c.chTxBuf
	defer func() { c.chTxBuf <- txBuf }()
	req := WriteCommand(txBuf[:3+len(value)])
	req.SetAttributeOpcode()
	req.SetAttributeHandle(handle)
	req.SetAttributeValue(value)
	return c.sendCmd(req)
}
func (c *Client) SignedWrite(handle uint16, value []byte, signature [12]byte) error {
	if len(value) > c.l2c.TxMTU()-15 {
		return ErrInvalidArgument
	}
	txBuf := <-c.chTxBuf
	defer func() { c.chTxBuf <- txBuf }()
	req := SignedWriteCommand(txBuf[:15+len(value)])
	req.SetAttributeOpcode()
	req.SetAttributeHandle(handle)
	req.SetAttributeValue(value)
	req.SetAuthenticationSignature(signature)
	return c.sendCmd(req)
}