code: 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
  	}
  
  	// Acquire and reuse the txBuf, and release it after usage.
  	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
  	}
  
  	// Convert and validate the response.
  	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
  }
docs: // ReadByGroupType obtains the values of attributes where the attribute type is known,
  // the type of a grouping attribute as defined by a higher layer specification, but
  // the handle is not known. [Vol 3, Part F, 3.4.4.9 & 3.4.4.10]