module MbedTLS

using Random, Sockets, MbedTLS_jll
import Sockets: TCPSocket

export
# Message digests
    MD_NONE,
    MD_MD2,
    MD_MD4,
    MD_MD5,
    MD_SHA1,
    MD_SHA224,
    MD_SHA256,
    MD_SHA384,
    MD_SHA,
    digest,
    digest!,

# Symmetric encryption,
    encrypt,
    decrypt,
    Cipher,
    CIPHER_AES,
    CIPHER_DES,
    CIPHER_3DES,
    CIPHER_CAMELLIA,
    CIPHER_BLOWFISH,
    CIPHER_ARC4,

# Types
    CtrDrbg,
    RSA,
    SSLConfig,
    Entropy,
    CRT

import Base: show

const MBED_SUCCESS = Cint(0)

include("constants.jl")
include("error.jl")
include("md.jl")
include("cipher.jl")
include("rsa.jl")
include("entropy.jl")
include("ctr_drbg.jl")
include("pk.jl")
include("x509_crt.jl")
include("ssl.jl")

function __init__()
    __ctr_drbg__init__()
    __sslinit__()
    __entropyinit__()
end

tls_dbg(level, filename, number, msg) = @warn "MbedTLS emitted debug info: $msg in $filename:$number"

secrets_log = ""
secrets_log_file = ""
secrets_log_state = :incomplete

function tls_dbg_log_secrets(level, filename, number, msg)
    global secrets_log
    global secrets_log_file
    global secrets_log_state
    if secrets_log_state == :complete
        return
    elseif msg == "dumping 'client hello, random bytes' (32 bytes)\n"
        secrets_log = "CLIENT_RANDOM "
        secrets_log_state = :client_random
    elseif msg == "dumping 'master secret' (48 bytes)\n"
        secrets_log_state = :master_secret
        secrets_log *= " "
    elseif secrets_log_state in (:client_random, :master_secret)
        if occursin(r"^00[0-2]0:", msg)
            secrets_log *= join(split(msg)[2:17])
        else
            if secrets_log_state == :master_secret
                secrets_log *= "\n"
                open(secrets_log_file, append=true) do io
                    write(io, secrets_log)
                end
                secrets_log_state = :incomplete
                secrets_log = ""
            else
                secrets_log_state = :incomplete
            end
        end
    end
end

"""
    SSLConfig(cert_file, key_file)

Initialise an SSLConfig from a certificate and key file on disk.
This is probably the method you want if you are the server in an HTTPS connection or similar.

# Example
```
julia> MbedTLS.SSLConfig("self-signed-certificate.pem", "keyfile.pem")
MbedTLS.SSLConfig()
```
"""
# already defined SSLConfig and SSLContext types in ssl.jl
function SSLConfig(cert_file, key_file)
    ssl_cert = MbedTLS.crt_parse_file(cert_file)
    key = MbedTLS.parse_keyfile(key_file)
    conf = MbedTLS.SSLConfig()
    entropy = MbedTLS.Entropy()
    rng = MbedTLS.CtrDrbg()
    MbedTLS.config_defaults!(conf, endpoint=MbedTLS.MBEDTLS_SSL_IS_SERVER)
    MbedTLS.seed!(rng, entropy)
    MbedTLS.rng!(conf, rng)
    MbedTLS.own_cert!(conf, ssl_cert, key)
    MbedTLS.dbg!(conf, tls_dbg)
    return conf
end

"""
    SSLConfig(verify::Bool; log_secrets=nothing)

Initialise a client SSLConfig for connecting to a server.

If `verify` is false, do not check that certificates are valid.

If `log_secrets` is a string, save connection secrets to a file with that name.
This is useful for decrypting traffic captured with Wireshark when debugging.
"""
function SSLConfig(verify::Bool; log_secrets=nothing)
    conf = MbedTLS.SSLConfig()
    MbedTLS.config_defaults!(conf)

    entropy = MbedTLS.Entropy()
    rng = MbedTLS.CtrDrbg()
    MbedTLS.seed!(rng, entropy)
    MbedTLS.rng!(conf, rng)

    MbedTLS.authmode!(conf,
      verify ? MbedTLS.MBEDTLS_SSL_VERIFY_REQUIRED : MbedTLS.MBEDTLS_SSL_VERIFY_NONE)
    if log_secrets !== nothing
        global secrets_log_file = log_secrets
        set_dbg_level(4)
        MbedTLS.dbg!(conf, tls_dbg_log_secrets)
    else
        MbedTLS.dbg!(conf, tls_dbg)
    end
    MbedTLS.ca_chain!(conf)
    conf
end

# already defined setup! in ssl.jl
associate!(tls::SSLContext, tcp::TCPSocket) = set_bio!(tls, tcp)
handshake!(tls::SSLContext) = handshake(tls)

end # module
