From 6a933540f24e76c05cc652acbf2aa92b9ab76d58 Mon Sep 17 00:00:00 2001 From: Ingela Anderton Andin Date: Wed, 15 Sep 2010 14:34:58 +0200 Subject: Corrected and added dialyzer specs --- lib/public_key/src/pubkey_cert.erl | 4 +- lib/public_key/src/public_key.erl | 1 - lib/ssl/src/ssl.erl | 182 +++++++++++++++++++------------------ lib/ssl/src/ssl_certificate.erl | 7 +- lib/ssl/src/ssl_connection.erl | 2 +- lib/ssl/src/ssl_handshake.erl | 137 +++++++++++++++++----------- lib/ssl/src/ssl_manager.erl | 2 + lib/ssl/src/ssl_record.erl | 97 ++++++++++++-------- 8 files changed, 248 insertions(+), 184 deletions(-) (limited to 'lib') diff --git a/lib/public_key/src/pubkey_cert.erl b/lib/public_key/src/pubkey_cert.erl index e704c168f1..c1819e6ddc 100644 --- a/lib/public_key/src/pubkey_cert.erl +++ b/lib/public_key/src/pubkey_cert.erl @@ -290,7 +290,7 @@ is_fixed_dh_cert(#'OTPCertificate'{tbsCertificate = %%-------------------------------------------------------------------- --spec verify_fun(#'OTPTBSCertificate'{}, {bad_cert, atom()} | {extension, #'Extension'{}}| +-spec verify_fun(#'OTPCertificate'{}, {bad_cert, atom()} | {extension, #'Extension'{}}| valid, term(), fun()) -> term(). %% %% Description: Gives the user application the opportunity handle path @@ -313,7 +313,7 @@ verify_fun(Otpcert, Result, UserState0, VerifyFun) -> {extension, #'Extension'{critical = true}} -> throw({bad_cert, unknown_critical_extension}); _ -> - UserState + UserState end end. diff --git a/lib/public_key/src/public_key.erl b/lib/public_key/src/public_key.erl index 9c7817fa8e..6de5f388dc 100644 --- a/lib/public_key/src/public_key.erl +++ b/lib/public_key/src/public_key.erl @@ -528,7 +528,6 @@ path_validation([DerCert | _] = Path, {error, Reason} end. - validate(DerCert, #path_validation_state{working_issuer_name = Issuer, working_public_key = Key, working_public_key_parameters = diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl index 12dffb413c..314bdd1aab 100644 --- a/lib/ssl/src/ssl.erl +++ b/lib/ssl/src/ssl.erl @@ -190,7 +190,8 @@ transport_accept(#sslsocket{} = ListenSocket, Timeout) -> %%-------------------------------------------------------------------- -spec ssl_accept(#sslsocket{}) -> {ok, #sslsocket{}} | {error, reason()}. --spec ssl_accept(#sslsocket{}, timeout()) -> {ok, #sslsocket{}} | {error, reason()}. +-spec ssl_accept(#sslsocket{}, list() | timeout()) -> {ok, #sslsocket{}} | {error, reason()}. +-spec ssl_accept(port(), list(), timeout()) -> {ok, #sslsocket{}} | {error, reason()}. %% %% Description: Performs accept on a ssl listen socket. e.i. performs %% ssl handshake. @@ -463,11 +464,102 @@ versions() -> %%--------------------------------------------------------------- -spec renegotiate(#sslsocket{}) -> ok | {error, reason()}. %% -%% Description: +%% Description: Initiates a renegotiation. %%-------------------------------------------------------------------- renegotiate(#sslsocket{pid = Pid, fd = new_ssl}) -> ssl_connection:renegotiation(Pid). +%%--------------------------------------------------------------- +-spec format_error({error, term()}) -> list(). +%% +%% Description: Creates error string. +%%-------------------------------------------------------------------- +format_error({error, Reason}) -> + format_error(Reason); +format_error(Reason) when is_list(Reason) -> + Reason; +format_error(closed) -> + "The connection is closed"; +format_error(ecacertfile) -> + "Own CA certificate file is invalid."; +format_error(ecertfile) -> + "Own certificate file is invalid."; +format_error(ekeyfile) -> + "Own private key file is invalid."; +format_error(esslaccept) -> + "Server SSL handshake procedure between client and server failed."; +format_error(esslconnect) -> + "Client SSL handshake procedure between client and server failed."; +format_error({eoptions, Options}) -> + lists:flatten(io_lib:format("Error in options list: ~p~n", [Options])); + +%%%%%%%%%%%% START OLD SSL format_error %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +format_error(ebadsocket) -> + "Connection not found (internal error)."; +format_error(ebadstate) -> + "Connection not in connect state (internal error)."; +format_error(ebrokertype) -> + "Wrong broker type (internal error)."; +format_error(echaintoolong) -> + "The chain of certificates provided by peer is too long."; +format_error(ecipher) -> + "Own list of specified ciphers is invalid."; +format_error(ekeymismatch) -> + "Own private key does not match own certificate."; +format_error(enoissuercert) -> + "Cannot find certificate of issuer of certificate provided by peer."; +format_error(enoservercert) -> + "Attempt to do accept without having set own certificate."; +format_error(enotlistener) -> + "Attempt to accept on a non-listening socket."; +format_error(enoproxysocket) -> + "No proxy socket found (internal error or max number of file " + "descriptors exceeded)."; +format_error(enooptions) -> + "List of options is empty."; +format_error(enotstarted) -> + "The SSL application has not been started."; +format_error(eoptions) -> + "Invalid list of options."; +format_error(epeercert) -> + "Certificate provided by peer is in error."; +format_error(epeercertexpired) -> + "Certificate provided by peer has expired."; +format_error(epeercertinvalid) -> + "Certificate provided by peer is invalid."; +format_error(eselfsignedcert) -> + "Certificate provided by peer is self signed."; +format_error(esslerrssl) -> + "SSL protocol failure. Typically because of a fatal alert from peer."; +format_error(ewantconnect) -> + "Protocol wants to connect, which is not supported in this " + "version of the SSL application."; +format_error(ex509lookup) -> + "Protocol wants X.509 lookup, which is not supported in this " + "version of the SSL application."; +format_error({badcall, _Call}) -> + "Call not recognized for current mode (active or passive) and state " + "of socket."; +format_error({badcast, _Cast}) -> + "Call not recognized for current mode (active or passive) and state " + "of socket."; + +format_error({badinfo, _Info}) -> + "Call not recognized for current mode (active or passive) and state " + "of socket."; + +%%%%%%%%%%%%%%%%%% END OLD SSL format_error %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +format_error(Error) -> + case (catch inet:format_error(Error)) of + "unkknown POSIX" ++ _ -> + no_format(Error); + {'EXIT', _} -> + no_format(Error); + Other -> + Other + end. + %%%-------------------------------------------------------------- %%% Internal functions %%%-------------------------------------------------------------------- @@ -810,92 +902,6 @@ cipher_suites(Version, Ciphers0) -> Ciphers = [ssl_cipher:openssl_suite(C) || C <- string:tokens(Ciphers0, ":")], cipher_suites(Version, Ciphers). -format_error({error, Reason}) -> - format_error(Reason); -format_error(Reason) when is_list(Reason) -> - Reason; -format_error(closed) -> - "The connection is closed"; -format_error(ecacertfile) -> - "Own CA certificate file is invalid."; -format_error(ecertfile) -> - "Own certificate file is invalid."; -format_error(ekeyfile) -> - "Own private key file is invalid."; -format_error(esslaccept) -> - "Server SSL handshake procedure between client and server failed."; -format_error(esslconnect) -> - "Client SSL handshake procedure between client and server failed."; -format_error({eoptions, Options}) -> - lists:flatten(io_lib:format("Error in options list: ~p~n", [Options])); - -%%%%%%%%%%%% START OLD SSL format_error %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -format_error(ebadsocket) -> - "Connection not found (internal error)."; -format_error(ebadstate) -> - "Connection not in connect state (internal error)."; -format_error(ebrokertype) -> - "Wrong broker type (internal error)."; -format_error(echaintoolong) -> - "The chain of certificates provided by peer is too long."; -format_error(ecipher) -> - "Own list of specified ciphers is invalid."; -format_error(ekeymismatch) -> - "Own private key does not match own certificate."; -format_error(enoissuercert) -> - "Cannot find certificate of issuer of certificate provided by peer."; -format_error(enoservercert) -> - "Attempt to do accept without having set own certificate."; -format_error(enotlistener) -> - "Attempt to accept on a non-listening socket."; -format_error(enoproxysocket) -> - "No proxy socket found (internal error or max number of file " - "descriptors exceeded)."; -format_error(enooptions) -> - "List of options is empty."; -format_error(enotstarted) -> - "The SSL application has not been started."; -format_error(eoptions) -> - "Invalid list of options."; -format_error(epeercert) -> - "Certificate provided by peer is in error."; -format_error(epeercertexpired) -> - "Certificate provided by peer has expired."; -format_error(epeercertinvalid) -> - "Certificate provided by peer is invalid."; -format_error(eselfsignedcert) -> - "Certificate provided by peer is self signed."; -format_error(esslerrssl) -> - "SSL protocol failure. Typically because of a fatal alert from peer."; -format_error(ewantconnect) -> - "Protocol wants to connect, which is not supported in this " - "version of the SSL application."; -format_error(ex509lookup) -> - "Protocol wants X.509 lookup, which is not supported in this " - "version of the SSL application."; -format_error({badcall, _Call}) -> - "Call not recognized for current mode (active or passive) and state " - "of socket."; -format_error({badcast, _Cast}) -> - "Call not recognized for current mode (active or passive) and state " - "of socket."; - -format_error({badinfo, _Info}) -> - "Call not recognized for current mode (active or passive) and state " - "of socket."; - -%%%%%%%%%%%%%%%%%% END OLD SSL format_error %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -format_error(Error) -> - case (catch inet:format_error(Error)) of - "unkknown POSIX" ++ _ -> - no_format(Error); - {'EXIT', _} -> - no_format(Error); - Other -> - Other - end. - no_format(Error) -> lists:flatten(io_lib:format("No format string for error: \"~p\" available.", [Error])). diff --git a/lib/ssl/src/ssl_certificate.erl b/lib/ssl/src/ssl_certificate.erl index 206024315e..d2ab21657c 100644 --- a/lib/ssl/src/ssl_certificate.erl +++ b/lib/ssl/src/ssl_certificate.erl @@ -110,9 +110,10 @@ file_to_certificats(File) -> {ok, List} = ssl_manager:cache_pem_file(File), [Bin || {'Certificate', Bin, not_encrypted} <- List]. %%-------------------------------------------------------------------- --spec validate_extension(term(), #'Extension'{}, term()) -> {valid, term()} | - {fail, tuple()} | - {unknown, term()}. +-spec validate_extension(term(), #'Extension'{} | {bad_cert, atom()} | valid, + term()) -> {valid, term()} | + {fail, tuple()} | + {unknown, term()}. %% %% Description: Validates ssl/tls specific extensions %%-------------------------------------------------------------------- diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl index c94199c336..bd1ba6978a 100644 --- a/lib/ssl/src/ssl_connection.erl +++ b/lib/ssl/src/ssl_connection.erl @@ -75,7 +75,7 @@ session, % #session{} from ssl_handshake.hrl session_cache, % session_cache_cb, % - negotiated_version, % #protocol_version{} + negotiated_version, % tls_version() supported_protocol_versions, % [atom()] client_certificate_requested = false, key_algorithm, % atom as defined by cipher_suite diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl index 99bc47f04b..3f01be101c 100644 --- a/lib/ssl/src/ssl_handshake.erl +++ b/lib/ssl/src/ssl_handshake.erl @@ -276,9 +276,9 @@ client_certificate_verify(OwnCert, MasterSecret, Version, Algorithm, end. %%-------------------------------------------------------------------- -%% -spec certificate_verify(binary(), public_key_info(), tls_version(), -%% binary(), key_algo(), -%% {_, {binary(), binary()}}) -> valid | #alert{}. +-spec certificate_verify(binary(), public_key_info(), tls_version(), + binary(), key_algo(), + {_, {binary(), binary()}}) -> valid | #alert{}. %% %% Description: Checks that the certificate_verify message is valid. %%-------------------------------------------------------------------- @@ -473,6 +473,88 @@ get_tls_handshake(Data, Buffer) -> decode_client_key(ClientKey, Type, Version) -> dec_client_key(ClientKey, key_exchange_alg(Type), Version). +%%-------------------------------------------------------------------- +-spec init_hashes() ->{{binary(), binary()}, {binary(), binary()}}. + +%% +%% Description: Calls crypto hash (md5 and sha) init functions to +%% initalize the hash context. +%%-------------------------------------------------------------------- +init_hashes() -> + T = {crypto:md5_init(), crypto:sha_init()}, + {T, T}. + +%%-------------------------------------------------------------------- +-spec update_hashes({{binary(), binary()}, {binary(), binary()}}, Data ::term()) -> + {{binary(), binary()}, {binary(), binary()}}. +%% +%% Description: Calls crypto hash (md5 and sha) update functions to +%% update the hash context with Data. +%%-------------------------------------------------------------------- +update_hashes(Hashes, % special-case SSL2 client hello + <>) -> + update_hashes(Hashes, + <>); +update_hashes({{MD50, SHA0}, _Prev}, Data) -> + ?DBG_HEX(Data), + {MD51, SHA1} = {crypto:md5_update(MD50, Data), + crypto:sha_update(SHA0, Data)}, + ?DBG_HEX(crypto:md5_final(MD51)), + ?DBG_HEX(crypto:sha_final(SHA1)), + {{MD51, SHA1}, {MD50, SHA0}}. + +%%-------------------------------------------------------------------- +-spec decrypt_premaster_secret(binary(), #'RSAPrivateKey'{}) -> binary(). + +%% +%% Description: Public key decryption using the private key. +%%-------------------------------------------------------------------- +decrypt_premaster_secret(Secret, RSAPrivateKey) -> + try public_key:decrypt_private(Secret, RSAPrivateKey, + [{rsa_pad, rsa_pkcs1_padding}]) + catch + _:_ -> + throw(?ALERT_REC(?FATAL, ?DECRYPTION_FAILED)) + end. + +%%-------------------------------------------------------------------- +-spec server_key_exchange_hash(rsa | dhe_rsa| dhe_dss, binary()) -> binary(). + +%% +%% Description: Calculate server key exchange hash +%%-------------------------------------------------------------------- +server_key_exchange_hash(Algorithm, Value) when Algorithm == rsa; + Algorithm == dhe_rsa -> + MD5 = crypto:md5(Value), + SHA = crypto:sha(Value), + <>; + +server_key_exchange_hash(dhe_dss, Value) -> + crypto:sha(Value). + +%%-------------------------------------------------------------------- +-spec sig_alg(atom()) -> integer(). + +%% +%% Description: Translate atom representation to enum representation. +%%-------------------------------------------------------------------- +sig_alg(dh_anon) -> + ?SIGNATURE_ANONYMOUS; +sig_alg(Alg) when Alg == dhe_rsa; Alg == rsa -> + ?SIGNATURE_RSA; +sig_alg(dhe_dss) -> + ?SIGNATURE_DSA; +sig_alg(_) -> + ?NULL. + %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- @@ -859,14 +941,6 @@ encrypted_premaster_secret(Secret, RSAPublicKey) -> throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE)) end. -decrypt_premaster_secret(Secret, RSAPrivateKey) -> - try public_key:decrypt_private(Secret, RSAPrivateKey, - [{rsa_pad, rsa_pkcs1_padding}]) - catch - _:_ -> - throw(?ALERT_REC(?FATAL, ?DECRYPTION_FAILED)) - end. - %% encode/decode stream of certificate data to/from list of certificate data certs_to_list(ASN1Certs) -> certs_to_list(ASN1Certs, []). @@ -985,29 +1059,6 @@ enc_hello_extensions([#renegotiation_info{renegotiated_connection = Info} | Rest Len = InfoLen +1, enc_hello_extensions(Rest, <>). -init_hashes() -> - T = {crypto:md5_init(), crypto:sha_init()}, - {T, T}. - -update_hashes(Hashes, % special-case SSL2 client hello - <>) -> - update_hashes(Hashes, - <>); -update_hashes({{MD50, SHA0}, _Prev}, Data) -> - ?DBG_HEX(Data), - {MD51, SHA1} = {crypto:md5_update(MD50, Data), - crypto:sha_update(SHA0, Data)}, - ?DBG_HEX(crypto:md5_final(MD51)), - ?DBG_HEX(crypto:sha_final(SHA1)), - {{MD51, SHA1}, {MD50, SHA0}}. from_3bytes(Bin3) -> from_3bytes(Bin3, []). @@ -1096,24 +1147,6 @@ calc_certificate_verify({3, N}, _, Algorithm, Hashes) when N == 1; N == 2 -> ssl_tls1:certificate_verify(Algorithm, Hashes). -server_key_exchange_hash(Algorithm, Value) when Algorithm == rsa; - Algorithm == dhe_rsa -> - MD5 = crypto:md5(Value), - SHA = crypto:sha(Value), - <>; - -server_key_exchange_hash(dhe_dss, Value) -> - crypto:sha(Value). - -sig_alg(dh_anon) -> - ?SIGNATURE_ANONYMOUS; -sig_alg(Alg) when Alg == dhe_rsa; Alg == rsa -> - ?SIGNATURE_RSA; -sig_alg(dhe_dss) -> - ?SIGNATURE_DSA; -sig_alg(_) -> - ?NULL. - key_exchange_alg(rsa) -> ?KEY_EXCHANGE_RSA; key_exchange_alg(Alg) when Alg == dhe_rsa; Alg == dhe_dss; diff --git a/lib/ssl/src/ssl_manager.erl b/lib/ssl/src/ssl_manager.erl index 0116466677..3b02d96562 100644 --- a/lib/ssl/src/ssl_manager.erl +++ b/lib/ssl/src/ssl_manager.erl @@ -122,6 +122,7 @@ server_session_id(Port, SuggestedSessionId, SslOpts) -> call({server_session_id, Port, SuggestedSessionId, SslOpts}). %%-------------------------------------------------------------------- +-spec register_session(port_num(), #session{}) -> ok. -spec register_session(host(), port_num(), #session{}) -> ok. %% %% Description: Make the session available for reuse. @@ -132,6 +133,7 @@ register_session(Host, Port, Session) -> register_session(Port, Session) -> cast({register_session, Port, Session}). %%-------------------------------------------------------------------- +-spec invalidate_session(port_num(), #session{}) -> ok. -spec invalidate_session(host(), port_num(), #session{}) -> ok. %% %% Description: Make the session unavilable for reuse. diff --git a/lib/ssl/src/ssl_record.erl b/lib/ssl/src/ssl_record.erl index acd0d49c19..803baeb09c 100644 --- a/lib/ssl/src/ssl_record.erl +++ b/lib/ssl/src/ssl_record.erl @@ -497,6 +497,66 @@ decode_cipher_text(CipherText, ConnnectionStates0) -> #alert{} = Alert -> Alert end. +%%-------------------------------------------------------------------- +-spec encode_data(iolist(), tls_version(), #connection_states{}, integer()) -> + {iolist(), iolist(), #connection_states{}}. +%% +%% Description: Encodes data to send on the ssl-socket. +%%-------------------------------------------------------------------- +encode_data(Frag, Version, ConnectionStates, RenegotiateAt) + when byte_size(Frag) < (?MAX_PLAIN_TEXT_LENGTH - 2048) -> + case encode_plain_text(?APPLICATION_DATA,Version,Frag,ConnectionStates, RenegotiateAt) of + {renegotiate, Data} -> + {[], Data, ConnectionStates}; + {Msg, CS} -> + {Msg, [], CS} + end; + +encode_data(Frag, Version, ConnectionStates, RenegotiateAt) when is_binary(Frag) -> + Data = split_bin(Frag, ?MAX_PLAIN_TEXT_LENGTH - 2048), + encode_data(Data, Version, ConnectionStates, RenegotiateAt); + +encode_data(Data, Version, ConnectionStates0, RenegotiateAt) when is_list(Data) -> + {ConnectionStates, EncodedMsg, NotEncdedData} = + lists:foldl(fun(B, {CS0, Encoded, Rest}) -> + case encode_plain_text(?APPLICATION_DATA, + Version, B, CS0, RenegotiateAt) of + {renegotiate, NotEnc} -> + {CS0, Encoded, [NotEnc | Rest]}; + {Enc, CS1} -> + {CS1, [Enc | Encoded], Rest} + end + end, {ConnectionStates0, [], []}, Data), + {lists:reverse(EncodedMsg), lists:reverse(NotEncdedData), ConnectionStates}. + +%%-------------------------------------------------------------------- +-spec encode_handshake(iolist(), tls_version(), #connection_states{}) -> + {iolist(), #connection_states{}}. +%% +%% Description: Encodes a handshake message to send on the ssl-socket. +%%-------------------------------------------------------------------- +encode_handshake(Frag, Version, ConnectionStates) -> + encode_plain_text(?HANDSHAKE, Version, Frag, ConnectionStates). + +%%-------------------------------------------------------------------- +-spec encode_alert_record(#alert{}, tls_version(), #connection_states{}) -> + {iolist(), #connection_states{}}. +%% +%% Description: Encodes an alert message to send on the ssl-socket. +%%-------------------------------------------------------------------- +encode_alert_record(#alert{level = Level, description = Description}, + Version, ConnectionStates) -> + encode_plain_text(?ALERT, Version, <>, + ConnectionStates). + +%%-------------------------------------------------------------------- +-spec encode_change_cipher_spec(tls_version(), #connection_states{}) -> + {iolist(), #connection_states{}}. +%% +%% Description: Encodes a change_cipher_spec-message to send on the ssl socket. +%%-------------------------------------------------------------------- +encode_change_cipher_spec(Version, ConnectionStates) -> + encode_plain_text(?CHANGE_CIPHER_SPEC, Version, <<1:8>>, ConnectionStates). %%-------------------------------------------------------------------- %%% Internal functions @@ -550,43 +610,6 @@ split_bin(Bin, ChunkSize, Acc) -> lists:reverse(Acc, [Bin]) end. -encode_data(Frag, Version, ConnectionStates, RenegotiateAt) - when byte_size(Frag) < (?MAX_PLAIN_TEXT_LENGTH - 2048) -> - case encode_plain_text(?APPLICATION_DATA,Version,Frag,ConnectionStates, RenegotiateAt) of - {renegotiate, Data} -> - {[], Data, ConnectionStates}; - {Msg, CS} -> - {Msg, [], CS} - end; - -encode_data(Frag, Version, ConnectionStates, RenegotiateAt) when is_binary(Frag) -> - Data = split_bin(Frag, ?MAX_PLAIN_TEXT_LENGTH - 2048), - encode_data(Data, Version, ConnectionStates, RenegotiateAt); - -encode_data(Data, Version, ConnectionStates0, RenegotiateAt) when is_list(Data) -> - {ConnectionStates, EncodedMsg, NotEncdedData} = - lists:foldl(fun(B, {CS0, Encoded, Rest}) -> - case encode_plain_text(?APPLICATION_DATA, - Version, B, CS0, RenegotiateAt) of - {renegotiate, NotEnc} -> - {CS0, Encoded, [NotEnc | Rest]}; - {Enc, CS1} -> - {CS1, [Enc | Encoded], Rest} - end - end, {ConnectionStates0, [], []}, Data), - {lists:reverse(EncodedMsg), lists:reverse(NotEncdedData), ConnectionStates}. - -encode_handshake(Frag, Version, ConnectionStates) -> - encode_plain_text(?HANDSHAKE, Version, Frag, ConnectionStates). - -encode_alert_record(#alert{level = Level, description = Description}, - Version, ConnectionStates) -> - encode_plain_text(?ALERT, Version, <>, - ConnectionStates). - -encode_change_cipher_spec(Version, ConnectionStates) -> - encode_plain_text(?CHANGE_CIPHER_SPEC, Version, <<1:8>>, ConnectionStates). - encode_plain_text(Type, Version, Data, ConnectionStates, RenegotiateAt) -> #connection_states{current_write = #connection_state{sequence_number = Num}} = ConnectionStates, -- cgit v1.2.3