diff options
author | lloyd <[email protected]> | 2012-04-16 19:00:49 +0000 |
---|---|---|
committer | lloyd <[email protected]> | 2012-04-16 19:00:49 +0000 |
commit | b224e899c8846f17a36dc41c53dd94ba037ada79 (patch) | |
tree | 81e2b0391b436b7620ffbeaa15252c75ae3c9039 | |
parent | c09b208d5d3ead81ef7ad662f71f55f1e00f61bc (diff) |
Add support for TLS heartbeats (RFC 6520). Heartbeat initiations from
the peer are automatically responded to. TLS::Channel::heartbeat can
initiate a new heartbeat if the peer allows it. Heartbeat replies are
passed back to the application processing function with an Alert value
of HEARTBEAT_PAYLOAD (a 'fake' value, 256, which is out of range of
the valid TLS alert space), along with the sent payload.
The RFC requires us to have no more than one heartbeat 'in flight' at
a time, ie without getting a response (or a timeout in the case of
DTLS). Currently we do not prevent an application from requesting
more.
-rw-r--r-- | src/tls/c_hello.cpp | 31 | ||||
-rw-r--r-- | src/tls/info.txt | 2 | ||||
-rw-r--r-- | src/tls/rec_read.cpp | 3 | ||||
-rw-r--r-- | src/tls/s_hello.cpp | 14 | ||||
-rw-r--r-- | src/tls/tls_alert.cpp | 5 | ||||
-rw-r--r-- | src/tls/tls_alert.h | 4 | ||||
-rw-r--r-- | src/tls/tls_channel.cpp | 48 | ||||
-rw-r--r-- | src/tls/tls_channel.h | 14 | ||||
-rw-r--r-- | src/tls/tls_client.cpp | 3 | ||||
-rw-r--r-- | src/tls/tls_extensions.cpp | 3 | ||||
-rw-r--r-- | src/tls/tls_extensions.h | 27 | ||||
-rw-r--r-- | src/tls/tls_heartbeats.cpp | 78 | ||||
-rw-r--r-- | src/tls/tls_heartbeats.h | 40 | ||||
-rw-r--r-- | src/tls/tls_magic.h | 3 | ||||
-rw-r--r-- | src/tls/tls_messages.h | 37 | ||||
-rw-r--r-- | src/tls/tls_record.h | 3 | ||||
-rw-r--r-- | src/tls/tls_server.cpp | 5 |
17 files changed, 282 insertions, 38 deletions
diff --git a/src/tls/c_hello.cpp b/src/tls/c_hello.cpp index d3fff8b00..e52d34bc6 100644 --- a/src/tls/c_hello.cpp +++ b/src/tls/c_hello.cpp @@ -77,7 +77,9 @@ Client_Hello::Client_Hello(Record_Writer& writer, m_secure_renegotiation(true), m_renegotiation_info(reneg_info), m_supported_curves(policy.allowed_ecc_curves()), - m_supports_session_ticket(true) + m_supports_session_ticket(true), + m_supports_heartbeats(true), + m_peer_can_send_heartbeats(true) { std::vector<std::string> hashes = policy.allowed_hashes(); std::vector<std::string> sigs = policy.allowed_signature_methods(); @@ -110,7 +112,9 @@ Client_Hello::Client_Hello(Record_Writer& writer, m_secure_renegotiation(session.secure_renegotiation()), m_supported_curves(policy.allowed_ecc_curves()), m_supports_session_ticket(true), - m_session_ticket(session.session_ticket()) + m_session_ticket(session.session_ticket()), + m_supports_heartbeats(true), + m_peer_can_send_heartbeats(true) { if(!value_exists(m_suites, session.ciphersuite_code())) m_suites.push_back(session.ciphersuite_code()); @@ -136,6 +140,8 @@ Client_Hello::Client_Hello(const MemoryRegion<byte>& buf, Handshake_Type type) m_next_protocol = false; m_secure_renegotiation = false; m_supports_session_ticket = false; + m_supports_heartbeats = false; + m_peer_can_send_heartbeats = false; m_fragment_size = 0; if(type == CLIENT_HELLO) @@ -168,29 +174,24 @@ MemoryVector<byte> Client_Hello::serialize() const Extensions extensions; - // Initial handshake + // Extensions only used on an initial handshake if(m_renegotiation_info.empty()) { - extensions.add(new Renegotation_Extension(m_renegotiation_info)); extensions.add(new Server_Name_Indicator(m_hostname)); extensions.add(new SRP_Identifier(m_srp_identifier)); extensions.add(new Supported_Elliptic_Curves(m_supported_curves)); + extensions.add(new Heartbeat_Support_Indicator(true)); if(m_version >= Protocol_Version::TLS_V12) extensions.add(new Signature_Algorithms(m_supported_algos)); if(m_next_protocol) extensions.add(new Next_Protocol_Notification()); - - extensions.add(new Session_Ticket(m_session_ticket)); - } - else - { - // renegotiation - extensions.add(new Renegotation_Extension(m_renegotiation_info)); - extensions.add(new Session_Ticket(m_session_ticket)); } + extensions.add(new Renegotation_Extension(m_renegotiation_info)); + extensions.add(new Session_Ticket(m_session_ticket)); + buf += extensions.serialize(); return buf; @@ -338,6 +339,12 @@ void Client_Hello::deserialize(const MemoryRegion<byte>& buf) m_session_ticket = ticket->contents(); } + if(Heartbeat_Support_Indicator* hb = extensions.get<Heartbeat_Support_Indicator>()) + { + m_supports_heartbeats = true; + m_peer_can_send_heartbeats = hb->peer_allowed_to_send(); + } + if(Renegotation_Extension* reneg = extensions.get<Renegotation_Extension>()) { // checked by TLS_Client / TLS_Server as they know the handshake state diff --git a/src/tls/info.txt b/src/tls/info.txt index 229cf658f..77ffb4585 100644 --- a/src/tls/info.txt +++ b/src/tls/info.txt @@ -29,6 +29,7 @@ tls_extensions.h tls_handshake_hash.h tls_handshake_reader.h tls_handshake_state.h +tls_heartbeats.h tls_messages.h tls_reader.h tls_session_key.h @@ -55,6 +56,7 @@ tls_extensions.cpp tls_handshake_hash.cpp tls_handshake_reader.cpp tls_handshake_state.cpp +tls_heartbeats.cpp tls_policy.cpp tls_server.cpp tls_session.cpp diff --git a/src/tls/rec_read.cpp b/src/tls/rec_read.cpp index d1fab4692..5d46ec1fa 100644 --- a/src/tls/rec_read.cpp +++ b/src/tls/rec_read.cpp @@ -216,7 +216,8 @@ size_t Record_Reader::add_input(const byte input_array[], size_t input_sz, if(m_readbuf[0] != CHANGE_CIPHER_SPEC && m_readbuf[0] != ALERT && m_readbuf[0] != HANDSHAKE && - m_readbuf[0] != APPLICATION_DATA) + m_readbuf[0] != APPLICATION_DATA && + m_readbuf[0] != HEARTBEAT) { throw Unexpected_Message( "Unknown record type " + to_string(m_readbuf[0]) + " from counterparty"); diff --git a/src/tls/s_hello.cpp b/src/tls/s_hello.cpp index bb93108d9..1244dd2d8 100644 --- a/src/tls/s_hello.cpp +++ b/src/tls/s_hello.cpp @@ -31,6 +31,7 @@ Server_Hello::Server_Hello(Record_Writer& writer, bool offer_session_ticket, bool client_has_npn, const std::vector<std::string>& next_protocols, + bool client_has_heartbeat, RandomNumberGenerator& rng) : m_version(ver), m_session_id(session_id), @@ -42,7 +43,9 @@ Server_Hello::Server_Hello(Record_Writer& writer, m_renegotiation_info(reneg_info), m_next_protocol(client_has_npn), m_next_protocols(next_protocols), - m_supports_session_ticket(offer_session_ticket) + m_supports_session_ticket(offer_session_ticket), + m_supports_heartbeats(client_has_heartbeat), + m_peer_can_send_heartbeats(true) { hash.update(writer.send(*this)); } @@ -104,6 +107,12 @@ Server_Hello::Server_Hello(const MemoryRegion<byte>& buf) throw Decoding_Error("TLS server sent non-empty session ticket extension"); m_supports_session_ticket = true; } + + if(Heartbeat_Support_Indicator* hb = extensions.get<Heartbeat_Support_Indicator>()) + { + m_supports_heartbeats = true; + m_peer_can_send_heartbeats = hb->peer_allowed_to_send(); + } } /* @@ -126,6 +135,9 @@ MemoryVector<byte> Server_Hello::serialize() const Extensions extensions; + if(m_supports_heartbeats) + extensions.add(new Heartbeat_Support_Indicator(m_peer_can_send_heartbeats)); + if(m_secure_renegotiation) extensions.add(new Renegotation_Extension(m_renegotiation_info)); diff --git a/src/tls/tls_alert.cpp b/src/tls/tls_alert.cpp index 2461854e1..83205bb74 100644 --- a/src/tls/tls_alert.cpp +++ b/src/tls/tls_alert.cpp @@ -102,7 +102,10 @@ std::string Alert::type_string() const return "unknown_psk_identity"; case NULL_ALERT: - return ""; + return "none"; + + case HEARTBEAT_PAYLOAD: + return "heartbeat_payload"; } /* diff --git a/src/tls/tls_alert.h b/src/tls/tls_alert.h index 8b4b963f0..3dfff3d29 100644 --- a/src/tls/tls_alert.h +++ b/src/tls/tls_alert.h @@ -53,7 +53,9 @@ class BOTAN_DLL Alert BAD_CERTIFICATE_HASH_VALUE = 114, UNKNOWN_PSK_IDENTITY = 115, - NULL_ALERT = 255 + NULL_ALERT = 255, + + HEARTBEAT_PAYLOAD = 256 }; /** diff --git a/src/tls/tls_channel.cpp b/src/tls/tls_channel.cpp index f45ce4bda..2464d339a 100644 --- a/src/tls/tls_channel.cpp +++ b/src/tls/tls_channel.cpp @@ -8,6 +8,7 @@ #include <botan/tls_channel.h> #include <botan/internal/tls_handshake_state.h> #include <botan/internal/tls_messages.h> +#include <botan/internal/tls_heartbeats.h> #include <botan/internal/assert.h> #include <botan/loadstor.h> @@ -23,7 +24,9 @@ Channel::Channel(std::tr1::function<void (const byte[], size_t)> socket_output_f writer(socket_output_fn), state(0), handshake_completed(false), - connection_closed(false) + connection_closed(false), + m_peer_supports_heartbeats(false), + m_heartbeat_sending_allowed(false) { } @@ -56,7 +59,30 @@ size_t Channel::received_data(const byte buf[], size_t buf_size) if(buf_size == 0 && needed != 0) return needed; // need more data to complete record - if(rec_type == APPLICATION_DATA) + if(rec_type == HANDSHAKE || rec_type == CHANGE_CIPHER_SPEC) + { + read_handshake(rec_type, record); + } + else if(rec_type == HEARTBEAT && m_peer_supports_heartbeats) + { + Heartbeat_Message heartbeat(record); + + const MemoryRegion<byte>& payload = heartbeat.payload(); + + if(heartbeat.is_request() && !state) + { + Heartbeat_Message response(Heartbeat_Message::RESPONSE, + payload, payload.size()); + + writer.send(HEARTBEAT, response.contents()); + } + else + { + // pass up to the application + proc_fn(&payload[0], payload.size(), Alert(Alert::HEARTBEAT_PAYLOAD)); + } + } + else if(rec_type == APPLICATION_DATA) { if(handshake_completed) { @@ -73,10 +99,6 @@ size_t Channel::received_data(const byte buf[], size_t buf_size) throw Unexpected_Message("Application data before handshake done"); } } - else if(rec_type == HANDSHAKE || rec_type == CHANGE_CIPHER_SPEC) - { - read_handshake(rec_type, record); - } else if(rec_type == ALERT) { Alert alert_msg(record); @@ -178,6 +200,20 @@ void Channel::read_handshake(byte rec_type, } } +void Channel::heartbeat(const byte payload[], size_t payload_size) + { + if(!is_active()) + throw std::runtime_error("Heartbeat cannot be sent on inactive TLS connection"); + + if(m_heartbeat_sending_allowed) + { + Heartbeat_Message heartbeat(Heartbeat_Message::REQUEST, + payload, payload_size); + + writer.send(HEARTBEAT, heartbeat.contents()); + } + } + void Channel::send(const byte buf[], size_t buf_size) { if(!is_active()) diff --git a/src/tls/tls_channel.h b/src/tls/tls_channel.h index 53af0bdfc..05fb7e82f 100644 --- a/src/tls/tls_channel.h +++ b/src/tls/tls_channel.h @@ -58,6 +58,18 @@ class BOTAN_DLL Channel virtual void renegotiate() = 0; /** + * Attempt to send a heartbeat message (if negotiated with counterparty) + * @param payload will be echoed back + * @param countents_size size of payload in bytes + */ + void heartbeat(const byte payload[], size_t payload_size); + + /** + * Attempt to send a heartbeat message (if negotiated with counterparty) + */ + void heartbeat() { heartbeat(0, 0); } + + /** * @return certificate chain of the peer (may be empty) */ std::vector<X509_Certificate> peer_cert_chain() const { return peer_certs; } @@ -130,6 +142,8 @@ class BOTAN_DLL Channel bool handshake_completed; bool connection_closed; + bool m_peer_supports_heartbeats; + bool m_heartbeat_sending_allowed; }; } diff --git a/src/tls/tls_client.cpp b/src/tls/tls_client.cpp index 850d053a2..f6266ee6a 100644 --- a/src/tls/tls_client.cpp +++ b/src/tls/tls_client.cpp @@ -188,6 +188,9 @@ void Client::process_handshake_msg(Handshake_Type type, secure_renegotiation.update(state->server_hello); + m_peer_supports_heartbeats = state->server_hello->supports_heartbeats(); + m_heartbeat_sending_allowed = state->server_hello->peer_can_send_heartbeats(); + state->suite = Ciphersuite::by_id(state->server_hello->ciphersuite()); const bool server_returned_same_session_id = diff --git a/src/tls/tls_extensions.cpp b/src/tls/tls_extensions.cpp index 59ab64374..f1361bbb9 100644 --- a/src/tls/tls_extensions.cpp +++ b/src/tls/tls_extensions.cpp @@ -42,6 +42,9 @@ Extension* make_extension(TLS_Data_Reader& reader, case TLSEXT_NEXT_PROTOCOL: return new Next_Protocol_Notification(reader, size); + case TLSEXT_HEARTBEAT_SUPPORT: + return new Heartbeat_Support_Indicator(reader, size); + case TLSEXT_SESSION_TICKET: return new Session_Ticket(reader, size); diff --git a/src/tls/tls_extensions.h b/src/tls/tls_extensions.h index 6a97d2560..3fe3f7399 100644 --- a/src/tls/tls_extensions.h +++ b/src/tls/tls_extensions.h @@ -32,6 +32,7 @@ enum Handshake_Extension_Type { TLSEXT_EC_POINT_FORMATS = 11, TLSEXT_SRP_IDENTIFIER = 12, TLSEXT_SIGNATURE_ALGORITHMS = 13, + TLSEXT_HEARTBEAT_SUPPORT = 15, TLSEXT_SESSION_TICKET = 35, @@ -309,6 +310,32 @@ class Signature_Algorithms : public Extension }; /** +* Heartbeat Extension (RFC 6520) +*/ +class Heartbeat_Support_Indicator : public Extension + { + public: + static Handshake_Extension_Type static_type() + { return TLSEXT_HEARTBEAT_SUPPORT; } + + Handshake_Extension_Type type() const { return static_type(); } + + bool peer_allowed_to_send() const { return m_peer_allowed_to_send; } + + MemoryVector<byte> serialize() const; + + bool empty() const { return false; } + + Heartbeat_Support_Indicator(bool peer_allowed_to_send) : + m_peer_allowed_to_send(peer_allowed_to_send) {} + + Heartbeat_Support_Indicator(TLS_Data_Reader& reader, u16bit extension_size); + + private: + bool m_peer_allowed_to_send; + }; + +/** * Represents a block of extensions in a hello message */ class Extensions diff --git a/src/tls/tls_heartbeats.cpp b/src/tls/tls_heartbeats.cpp new file mode 100644 index 000000000..a77d23534 --- /dev/null +++ b/src/tls/tls_heartbeats.cpp @@ -0,0 +1,78 @@ +/* +* TLS Heartbeats +* (C) 2012 Jack Lloyd +* +* Released under the terms of the Botan license +*/ + +#include <botan/internal/tls_heartbeats.h> +#include <botan/internal/tls_extensions.h> +#include <botan/internal/tls_reader.h> +#include <botan/tls_exceptn.h> + +namespace Botan { + +namespace TLS { + +Heartbeat_Message::Heartbeat_Message(const MemoryRegion<byte>& buf) + { + TLS_Data_Reader reader(buf); + + const byte type = reader.get_byte(); + + if(type != 1 && type != 2) + throw TLS_Exception(Alert::ILLEGAL_PARAMETER, + "Unknown heartbeat message type"); + + m_type = static_cast<Type>(type); + + m_payload = reader.get_range<byte>(2, 0, 16*1024); + + // padding follows and is ignored + } + +Heartbeat_Message::Heartbeat_Message(Type type, + const byte payload[], + size_t payload_len) : + m_type(type), + m_payload(payload, payload_len) + { + } + +MemoryVector<byte> Heartbeat_Message::contents() const + { + MemoryVector<byte> send_buf(3 + m_payload.size() + 16); + send_buf[0] = m_type; + send_buf[1] = get_byte<u16bit>(0, m_payload.size()); + send_buf[2] = get_byte<u16bit>(1, m_payload.size()); + copy_mem(&send_buf[3], &m_payload[0], m_payload.size()); + // leave padding as all zeros + + return send_buf; + } + +MemoryVector<byte> Heartbeat_Support_Indicator::serialize() const + { + MemoryVector<byte> heartbeat(1); + heartbeat[0] = (m_peer_allowed_to_send ? 1 : 2); + return heartbeat; + } + +Heartbeat_Support_Indicator::Heartbeat_Support_Indicator(TLS_Data_Reader& reader, + u16bit extension_size) + { + if(extension_size != 1) + throw Decoding_Error("Strange size for heartbeat extension"); + + const byte code = reader.get_byte(); + + if(code != 1 && code != 2) + throw TLS_Exception(Alert::ILLEGAL_PARAMETER, + "Unknown heartbeat code " + to_string(code)); + + m_peer_allowed_to_send = (code == 1); + } + +} + +} diff --git a/src/tls/tls_heartbeats.h b/src/tls/tls_heartbeats.h new file mode 100644 index 000000000..4fa49501b --- /dev/null +++ b/src/tls/tls_heartbeats.h @@ -0,0 +1,40 @@ +/* +* TLS Heartbeats +* (C) 2012 Jack Lloyd +* +* Released under the terms of the Botan license +*/ + +#ifndef BOTAN_TLS_HEARTBEATS_H__ +#define BOTAN_TLS_HEARTBEATS_H__ + +#include <botan/secmem.h> + +namespace Botan { + +namespace TLS { + +class Heartbeat_Message + { + public: + enum Type { REQUEST = 1, RESPONSE = 2 }; + + MemoryVector<byte> contents() const; + + const MemoryRegion<byte>& payload() const { return m_payload; } + + bool is_request() const { return m_type == REQUEST; } + + Heartbeat_Message(const MemoryRegion<byte>& buf); + + Heartbeat_Message(Type type, const byte payload[], size_t payload_len); + private: + Type m_type; + MemoryVector<byte> m_payload; + }; + +} + +} + +#endif diff --git a/src/tls/tls_magic.h b/src/tls/tls_magic.h index 6dd50ead2..2972321c9 100644 --- a/src/tls/tls_magic.h +++ b/src/tls/tls_magic.h @@ -32,7 +32,8 @@ enum Record_Type { CHANGE_CIPHER_SPEC = 20, ALERT = 21, HANDSHAKE = 22, - APPLICATION_DATA = 23 + APPLICATION_DATA = 23, + HEARTBEAT = 24, }; enum Handshake_Type { diff --git a/src/tls/tls_messages.h b/src/tls/tls_messages.h index c8a9382d6..4c2b1b797 100644 --- a/src/tls/tls_messages.h +++ b/src/tls/tls_messages.h @@ -73,15 +73,10 @@ class Client_Hello : public Handshake_Message { public: Handshake_Type type() const { return CLIENT_HELLO; } + Protocol_Version version() const { return m_version; } - const MemoryVector<byte>& session_id() const { return m_session_id; } - std::vector<byte> session_id_vector() const - { - std::vector<byte> v; - v.insert(v.begin(), &m_session_id[0], &m_session_id[m_session_id.size()]); - return v; - } + const MemoryVector<byte>& session_id() const { return m_session_id; } const std::vector<std::pair<std::string, std::string> >& supported_algos() const { return m_supported_algos; } @@ -114,6 +109,10 @@ class Client_Hello : public Handshake_Message const MemoryRegion<byte>& session_ticket() const { return m_session_ticket; } + bool supports_heartbeats() const { return m_supports_heartbeats; } + + bool peer_can_send_heartbeats() const { return m_peer_can_send_heartbeats; } + Client_Hello(Record_Writer& writer, Handshake_Hash& hash, const Policy& policy, @@ -155,6 +154,9 @@ class Client_Hello : public Handshake_Message bool m_supports_session_ticket; MemoryVector<byte> m_session_ticket; + + bool m_supports_heartbeats; + bool m_peer_can_send_heartbeats; }; /** @@ -164,17 +166,16 @@ class Server_Hello : public Handshake_Message { public: Handshake_Type type() const { return SERVER_HELLO; } + Protocol_Version version() { return m_version; } + + const MemoryVector<byte>& random() const { return m_random; } + const MemoryVector<byte>& session_id() const { return m_session_id; } + u16bit ciphersuite() const { return m_ciphersuite; } - byte compression_method() const { return m_comp_method; } - std::vector<byte> session_id_vector() const - { - std::vector<byte> v; - v.insert(v.begin(), &m_session_id[0], &m_session_id[m_session_id.size()]); - return v; - } + byte compression_method() const { return m_comp_method; } bool secure_renegotiation() const { return m_secure_renegotiation; } @@ -190,7 +191,9 @@ class Server_Hello : public Handshake_Message const MemoryVector<byte>& renegotiation_info() { return m_renegotiation_info; } - const MemoryVector<byte>& random() const { return m_random; } + bool supports_heartbeats() const { return m_supports_heartbeats; } + + bool peer_can_send_heartbeats() const { return m_peer_can_send_heartbeats; } Server_Hello(Record_Writer& writer, Handshake_Hash& hash, @@ -204,6 +207,7 @@ class Server_Hello : public Handshake_Message bool offer_session_ticket, bool client_has_npn, const std::vector<std::string>& next_protocols, + bool client_has_heartbeat, RandomNumberGenerator& rng); Server_Hello(const MemoryRegion<byte>& buf); @@ -222,6 +226,9 @@ class Server_Hello : public Handshake_Message bool m_next_protocol; std::vector<std::string> m_next_protocols; bool m_supports_session_ticket; + + bool m_supports_heartbeats; + bool m_peer_can_send_heartbeats; }; /** diff --git a/src/tls/tls_record.h b/src/tls/tls_record.h index fb27db5e2..38eae7823 100644 --- a/src/tls/tls_record.h +++ b/src/tls/tls_record.h @@ -46,6 +46,9 @@ class BOTAN_DLL Record_Writer void send(byte type, const byte input[], size_t length); void send(byte type, byte val) { send(type, &val, 1); } + void send(byte type, const MemoryRegion<byte>& input) + { send(type, &input[0], input.size()); } + MemoryVector<byte> send(class Handshake_Message& msg); void send_alert(const Alert& alert); diff --git a/src/tls/tls_server.cpp b/src/tls/tls_server.cpp index 10872a825..c591a45c5 100644 --- a/src/tls/tls_server.cpp +++ b/src/tls/tls_server.cpp @@ -286,6 +286,9 @@ void Server::process_handshake_msg(Handshake_Type type, secure_renegotiation.update(state->client_hello); + m_peer_supports_heartbeats = state->client_hello->supports_heartbeats(); + m_heartbeat_sending_allowed = state->client_hello->peer_can_send_heartbeats(); + writer.set_version(state->version()); reader.set_version(state->version()); @@ -324,6 +327,7 @@ void Server::process_handshake_msg(Handshake_Type type, have_session_ticket_key), state->client_hello->next_protocol_notification(), m_possible_protocols, + state->client_hello->supports_heartbeats(), rng); if(session_info.fragment_size()) @@ -395,6 +399,7 @@ void Server::process_handshake_msg(Handshake_Type type, state->client_hello->supports_session_ticket() && have_session_ticket_key, state->client_hello->next_protocol_notification(), m_possible_protocols, + state->client_hello->supports_heartbeats(), rng); if(state->client_hello->fragment_size()) |