{
  lib,
  stdenv,
  version,
  hash,
  patches ? [ ],
  fetchFromGitHub,
  buildPackages,

  cmake,
  ninja,
  perl, # Project uses Perl for scripting and testing
  python3,
  python3Packages,

  enableThreading ? true, # Threading can be disabled to increase security https://tls.mbed.org/kb/development/thread-safety-and-multi-threading
}:

stdenv.mkDerivation rec {
  pname = "mbedtls";
  inherit version;
  __structuredAttrs = true;

  src = fetchFromGitHub {
    owner = "Mbed-TLS";
    repo = "mbedtls";
    rev = "${pname}-${version}";
    inherit hash;
    # mbedtls >= 3.6.0 uses git submodules
    fetchSubmodules = true;
  };

  inherit patches;

  nativeBuildInputs = [
    cmake
    ninja
    perl
    python3
  ]
  ++ lib.optionals (lib.versionAtLeast version "4.0") [
    python3Packages.jinja2
    python3Packages.jsonschema
  ];

  strictDeps = true;

  # trivialautovarinit on clang causes test failures
  hardeningDisable = lib.optional stdenv.cc.isClang "trivialautovarinit";

  postConfigure =
    lib.optionalString (enableThreading && lib.versionOlder version "4.0") ''
      perl scripts/config.pl set MBEDTLS_THREADING_C    # Threading abstraction layer
      perl scripts/config.pl set MBEDTLS_THREADING_PTHREAD    # POSIX thread wrapper layer for the threading layer.
    ''
    + lib.optionalString (enableThreading && lib.versionAtLeast version "4.0") ''
      python scripts/config.py set MBEDTLS_THREADING_C    # Threading abstraction layer
      python scripts/config.py set MBEDTLS_THREADING_PTHREAD    # POSIX thread wrapper layer for the threading layer.
    '';

  cmakeFlags = [
    "-DUSE_SHARED_MBEDTLS_LIBRARY=${if stdenv.hostPlatform.isStatic then "off" else "on"}"
  ]
  ++ lib.optionals (lib.versionOlder version "4.0") [
    # Avoid a dependency on jsonschema and jinja2 by not generating source code
    # using python. In releases, these generated files are already present in
    # the repository and do not need to be regenerated. See:
    # https://github.com/Mbed-TLS/mbedtls/releases/tag/v3.3.0 below "Requirement changes".

    # This does not work out of the box on 4.1.0 and creates a significant amount of complexity to reproduce.
    # Possible related issue: https://github.com/Mbed-TLS/mbedtls/issues/10678
    "-DGEN_FILES=off"
  ]
  ++ lib.optionals stdenv.cc.isGNU [
    # mbedtls widely uses a pattern of starting unions with an
    # unsigned int dummy member, and then initializing those unions to
    # { 0 }.  The problem with this is that it only initializes that
    # first union member, so in the common case where the non-dummy
    # members are larger than the dummy member, they will only be
    # partially initialized since GCC 15[1].  Upstream has added
    # ad-hoc memset calls to mitigate this issue, but initializers are
    # also still widely used.  To avoid the risk of using
    # uninitialized memory, force the compiler to zero all bits of
    # unions, not just the first element, until upstream has a
    # systemic fix in place[2].
    #
    # [1]: https://gcc.gnu.org/gcc-15/changes.html
    # [2]: https://github.com/Mbed-TLS/mbedtls/issues/9885
    "-DCMAKE_C_FLAGS=-fzero-init-padding-bits=unions"
  ];

  env = lib.optionalAttrs (stdenv.buildPlatform != stdenv.hostPlatform) {
    HOSTCC = lib.getExe buildPackages.stdenv.cc;
  };

  doCheck = true;

  # Parallel checking causes test failures
  # https://github.com/Mbed-TLS/mbedtls/issues/4980
  enableParallelChecking = false;

  meta = {
    homepage = "https://www.trustedfirmware.org/projects/mbed-tls/";
    changelog = "https://github.com/Mbed-TLS/mbedtls/blob/${pname}-${version}/ChangeLog";
    description = "Portable cryptographic and TLS library, formerly known as PolarSSL";
    license = [
      lib.licenses.asl20 # or
      lib.licenses.gpl2Plus
    ];
    platforms = lib.platforms.all;
    maintainers = with lib.maintainers; [ raphaelr ];
  };
}
