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
}
FindInformation obtains the mapping of attribute handles with their associated types.
This allows a Client to discover the list of attributes and their types on a server.
[Vol 3, Part F, 3.4.3.1 & 3.4.3.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
}