code: func (c *udpAnnounce) request(action Action, args interface{}, options []byte) (*bytes.Buffer, error) {
  	tid := newTransactionId()
  	if err := errors.Wrap(
  		c.write(
  			&RequestHeader{
  				ConnectionId:  c.connectionId,
  				Action:        action,
  				TransactionId: tid,
  			}, args, options),
  		"writing request",
  	); err != nil {
  		return nil, err
  	}
  	c.socket.SetReadDeadline(time.Now().Add(timeout(c.contiguousTimeouts)))
  	b := make([]byte, 0x800) // 2KiB
  	for {
  		var (
  			n        int
  			readErr  error
  			readDone = make(chan struct{})
  		)
  		go func() {
  			defer close(readDone)
  			n, readErr = c.socket.Read(b)
  		}()
  		ctx := c.a.Context
  		if ctx == nil {
  			ctx = context.Background()
  		}
  		select {
  		case <-ctx.Done():
  			return nil, ctx.Err()
  		case <-readDone:
  		}
  		if opE, ok := readErr.(*net.OpError); ok && opE.Timeout() {
  			c.contiguousTimeouts++
  		}
  		if readErr != nil {
  			return nil, errors.Wrap(readErr, "reading from socket")
  		}
  		buf := bytes.NewBuffer(b[:n])
  		var h ResponseHeader
  		err := binary.Read(buf, binary.BigEndian, &h)
  		switch err {
  		default:
  			panic(err)
  		case io.ErrUnexpectedEOF, io.EOF:
  			continue
  		case nil:
  		}
  		if h.TransactionId != tid {
  			continue
  		}
  		c.contiguousTimeouts = 0
  		if h.Action == ActionError {
  			err = errors.New(buf.String())
  		}
  		return buf, err
  	}
  }
docs: // args is the binary serializable request body. trailer is optional data
  // following it, such as for BEP 41.