func (s *Session) call(rpcID renterhost.Specifier, req, resp renterhost.ProtocolObject) error {
	if err := s.sess.WriteRequest(rpcID, req); err != nil {
		return err
	}
	err := s.sess.ReadResponse(resp, 4096)
	return wrapResponseErr(err, fmt.Sprintf("couldn't read %v response", rpcID), fmt.Sprintf("host rejected %v request", rpcID))
}
call is a helper method that writes a request and then reads a response.
package proto
import (
	"bufio"
	"crypto/ed25519"
	"encoding/binary"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"math"
	"math/bits"
	"net"
	"sort"
	"time"
	"go.sia.tech/siad/crypto"
	"go.sia.tech/siad/modules"
	"go.sia.tech/siad/types"
	"lukechampine.com/us/ed25519hash"
	"lukechampine.com/us/hostdb"
	"lukechampine.com/us/merkle"
	"lukechampine.com/us/renterhost"
)
var (
	ErrInsufficientFunds = errors.New("insufficient funds")
	ErrInvalidMerkleProof = errors.New("host supplied invalid Merkle proof")
	ErrContractLocked = errors.New("contract is locked by another party")
	ErrNoContractLocked = errors.New("no contract locked")
	ErrContractFinalized = errors.New("contract cannot be revised further")
)
func wrapResponseErr(err error, readCtx, rejectCtx string) error {
	if errors.As(err, new(*renterhost.RPCError)) {
		return fmt.Errorf("%s: %w", rejectCtx, err)
	}
	if err != nil {
		return fmt.Errorf("%s: %w", readCtx, err)
	}
	return nil
}
type statsConn struct {
	net.Conn
	r, w uint64
}
func (sc *statsConn) Read(p []byte) (int, error) {
	n, err := sc.Conn.Read(p)
	sc.r += uint64(n)
	return n, err
}
func (sc *statsConn) Write(p []byte) (int, error) {
	n, err := sc.Conn.Write(p)
	sc.w += uint64(n)
	return n, err
}
type Session struct {
	sess        *renterhost.Session
	conn        *statsConn
	appendRoots []crypto.Hash
	latency       time.Duration
	readDeadline  time.Duration
	writeDeadline time.Duration
	stats         RPCStatsRecorder
	host   hostdb.ScannedHost
	height types.BlockHeight
	rev    ContractRevision
	key    ed25519.PrivateKey
}
func (s *Session) HostKey() hostdb.HostPublicKey { return s.host.PublicKey }
func (s *Session) Revision() ContractRevision { return s.rev }
func (s *Session) IsClosed() bool { return s.sess.IsClosed() }
func (s *Session) SetLatency(d time.Duration) { s.latency = d }
func (s *Session) SetReadDeadline(d time.Duration) { s.readDeadline = d }
func (s *Session) SetWriteDeadline(d time.Duration) { s.writeDeadline = d }
func (s *Session) extendDeadline(d time.Duration) {
	_ = s.conn.SetDeadline(time.Now().Add(s.latency + d))
}
func (s *Session) extendBandwidthDeadline(up, down uint64) {
	if up < renterhost.MinMessageSize {
		up = renterhost.MinMessageSize
	}
	if down < renterhost.MinMessageSize {
		down = renterhost.MinMessageSize
	}
	s.extendDeadline(s.writeDeadline*time.Duration(up) + s.readDeadline*time.Duration(down))
}
func (s *Session) SetRPCStatsRecorder(stats RPCStatsRecorder) { s.stats = stats }
func (s *Session) collectStats(id renterhost.Specifier, err *error) (record func()) {
	if s.stats == nil {
		return func() {}
	}
	stats := RPCStats{
		Host:      s.host.PublicKey,
		Contract:  s.rev.Revision.ID(),
		RPC:       id,
		Timestamp: time.Now(),
	}
	var startFunds types.Currency
	if s.rev.IsValid() {
		startFunds = s.rev.RenterFunds()
	}
	oldW, oldR := s.conn.w, s.conn.r
	return func() {
		stats.Err = *err
		stats.Elapsed = time.Since(stats.Timestamp)
		stats.Uploaded = s.conn.w - oldW
		stats.Downloaded = s.conn.r - oldR
		if s.rev.IsValid() && startFunds.Cmp(s.rev.RenterFunds()) > 0 {
			stats.Cost = startFunds.Sub(s.rev.RenterFunds())
		}
		s.stats.RecordRPCStats(stats)
	}
}