The heartbeat timeout is calculated as the minimum nonzero value (using the badly-named negotiatedMaxValue) of the heartbeat field in the the server's and client's tune frames.
Heartbeats are sent by a heartbeat sender. The heartbeat sender's activity timeout is reset on every write. The sender sends a heartbeat packet after no activity for more longer than the negotiated timeout value. It checks for activity at twice the negotiated rate, so at most it will send a heartbeat packet 1.5x the timeout value after the last activity.
While reading, any data from the server resets _missedHeartbeats to 0. The socket timeout is set to one quarter of the heartbeat timeout, and when this timeout expires, handleSocketTimeout will raise an exception and sever the connection if 8 consecutive timeouts have occurred, meaning two times the negotiated timeout value.