// sendSshKeepAlive is a helper which sends a [email protected] request
// on the specified SSH connections and returns true of the request succeeds
// within a specified timeout. If the request fails, the associated conn is
// closed, which will terminate the associated tunnel.
func sendSshKeepAlive(
	sshClient *ssh.Client, conn net.Conn, timeout time.Duration) error {

	errChannel := make(chan error, 2)
	if timeout > 0 {
		time.AfterFunc(timeout, func() {
			errChannel <- TimeoutError{}
		})
	}

	go func() {
		// Random padding to frustrate fingerprinting
		randomPadding, err := common.MakeSecureRandomPadding(0, TUNNEL_SSH_KEEP_ALIVE_PAYLOAD_MAX_BYTES)
		if err != nil {
			NoticeAlert("MakeSecureRandomPadding failed: %s", err)
			// Proceed without random padding
			randomPadding = make([]byte, 0)
		}
		// Note: reading a reply is important for last-received-time tunnel
		// duration calculation.
		_, _, err = sshClient.SendRequest("*****@*****.**", true, randomPadding)
		errChannel <- err
	}()

	err := <-errChannel
	if err != nil {
		sshClient.Close()
		conn.Close()
	}

	return common.ContextError(err)
}
func (serverContext *ServerContext) getStatusParams(isTunneled bool) requestJSONObject {

	params := serverContext.getBaseParams()

	// Add a random amount of padding to help prevent stats updates from being
	// a predictable size (which often happens when the connection is quiet).
	// TODO: base64 encoding of padding means the padding size is not exactly
	// [0, PADDING_MAX_BYTES].

	randomPadding, err := common.MakeSecureRandomPadding(0, PSIPHON_API_STATUS_REQUEST_PADDING_MAX_BYTES)
	if err != nil {
		NoticeAlert("MakeSecureRandomPadding failed: %s", err)
		// Proceed without random padding
		randomPadding = make([]byte, 0)
	}
	params["padding"] = base64.StdEncoding.EncodeToString(randomPadding)

	// Legacy clients set "connected" to "0" when disconnecting, and this value
	// is used to calculate session duration estimates. This is now superseded
	// by explicit tunnel stats duration reporting.
	// The legacy method of reconstructing session durations is not compatible
	// with this client's connected request retries and asynchronous final
	// status request attempts. So we simply set this "connected" flag to reflect
	// whether the request is sent tunneled or not.

	connected := "1"
	if !isTunneled {
		connected = "0"
	}
	params["connected"] = connected

	return params
}