Add parsing cache to `mbedtls_x509_crt`

This commit replaces the dummy implementation of the CRT acquire/release
framework by a cache-based implementation which remembers frame and PK
associated to a CRT across multiple `acquire/release` pairs.
diff --git a/include/mbedtls/x509_crt.h b/include/mbedtls/x509_crt.h
index 25e8077..3f8350a 100644
--- a/include/mbedtls/x509_crt.h
+++ b/include/mbedtls/x509_crt.h
@@ -32,6 +32,7 @@
 
 #include "x509.h"
 #include "x509_crl.h"
+#include "threading.h"
 
 /**
  * \addtogroup x509_module
@@ -99,6 +100,22 @@
 
 } mbedtls_x509_crt_frame;
 
+/* This is an internal structure used for caching parsed data from an X.509 CRT.
+ *
+ * This structure may change at any time, and it is discouraged
+ * to access it directly.
+ */
+typedef struct mbedtls_x509_crt_cache
+{
+#if defined(MBEDTLS_THREADING_C)
+    mbedtls_threading_mutex_t frame_mutex;
+    mbedtls_threading_mutex_t pk_mutex;
+#endif
+    mbedtls_x509_buf_raw pk_raw;
+    mbedtls_x509_crt_frame *frame;
+    mbedtls_pk_context *pk;
+} mbedtls_x509_crt_cache;
+
 /**
  * Container for an X.509 certificate. The certificate may be chained.
  */
@@ -150,6 +167,8 @@
     mbedtls_pk_type_t sig_pk;           /**< Internal representation of the Public Key algorithm of the signature algorithm, e.g. MBEDTLS_PK_RSA */
     void *sig_opts;             /**< Signature options to be passed to mbedtls_pk_verify_ext(), e.g. for RSASSA-PSS */
 
+    mbedtls_x509_crt_cache *cache;     /**< Internal parsing cache. */
+
     struct mbedtls_x509_crt *next;     /**< Next certificate in the CA-chain. */
 }
 mbedtls_x509_crt;
@@ -643,6 +662,127 @@
  */
 void mbedtls_x509_crt_restart_free( mbedtls_x509_crt_restart_ctx *ctx );
 #endif /* MBEDTLS_ECDSA_C && MBEDTLS_ECP_RESTARTABLE */
+
+
+/**
+ * \brief           Flush internal X.509 CRT parsing cache, if present.
+ *
+ * \param crt       The CRT structure whose cache to flush.
+ *
+ * \note            Calling this function frequently reduces RAM usage
+ *                  at the cost of performance.
+ *
+ * \return          \c 0 on success.
+ * \return          A negative error code on failure.
+ */
+int mbedtls_x509_crt_flush_cache( mbedtls_x509_crt const *crt );
+
+/* Internal X.509 CRT cache handling functions.
+ * They are not part of the public API and may change
+ * at any time. */
+
+int mbedtls_x509_crt_cache_provide_frame( mbedtls_x509_crt const *crt );
+int mbedtls_x509_crt_cache_provide_pk( mbedtls_x509_crt const *crt );
+
+static inline int mbedtls_x509_crt_cache_frame_set(
+    mbedtls_x509_crt_cache *cache )
+{
+    return( cache->frame != NULL );
+}
+
+static inline mbedtls_x509_crt_frame* mbedtls_x509_crt_cache_get_frame(
+    mbedtls_x509_crt_cache *cache )
+{
+    return( cache->frame );
+}
+
+static inline int mbedtls_x509_crt_cache_pk_set(
+    mbedtls_x509_crt_cache *cache )
+{
+    return( cache->pk != NULL );
+}
+
+static inline mbedtls_pk_context* mbedtls_x509_crt_cache_get_pk(
+    mbedtls_x509_crt_cache *cache )
+{
+    return( cache->pk );
+}
+
+static inline int mbedtls_x509_crt_frame_acquire( mbedtls_x509_crt const *crt,
+                                         mbedtls_x509_crt_frame **frame_ptr )
+{
+#if defined(MBEDTLS_THREADING_C)
+    if( mbedtls_mutex_lock( &crt->cache->frame_mutex ) != 0 )
+        return( MBEDTLS_ERR_THREADING_MUTEX_ERROR );
+#endif
+
+    if( !mbedtls_x509_crt_cache_frame_set( crt->cache ) )
+    {
+        int ret;
+        ret = mbedtls_x509_crt_cache_provide_frame( crt );
+        if( ret != 0 )
+        {
+#if defined(MBEDTLS_THREADING_C)
+            if( mbedtls_mutex_unlock( &crt->cache->frame_mutex ) != 0 )
+                return( MBEDTLS_ERR_THREADING_MUTEX_ERROR );
+#endif
+            return( ret );
+        }
+    }
+
+    *frame_ptr = mbedtls_x509_crt_cache_get_frame( crt->cache );
+    return( 0 );
+}
+
+static inline void mbedtls_x509_crt_frame_release(
+    mbedtls_x509_crt const *crt,
+    mbedtls_x509_crt_frame *frame )
+{
+    ((void) frame);
+    ((void) crt);
+
+#if defined(MBEDTLS_THREADING_C)
+    mbedtls_mutex_unlock( &crt->cache->frame_mutex );
+#endif
+}
+
+static inline int mbedtls_x509_crt_pk_acquire( mbedtls_x509_crt *crt,
+                                               mbedtls_pk_context **pk_ptr )
+{
+#if defined(MBEDTLS_THREADING_C)
+    if( mbedtls_mutex_lock( &crt->cache->pk_mutex ) != 0 )
+        return( MBEDTLS_ERR_THREADING_MUTEX_ERROR );
+#endif
+
+    if( !mbedtls_x509_crt_cache_pk_set( crt->cache ) )
+    {
+        int ret;
+        ret = mbedtls_x509_crt_cache_provide_pk( crt );
+        if( ret != 0 )
+        {
+#if defined(MBEDTLS_THREADING_C)
+            if( mbedtls_mutex_unlock( &crt->cache->pk_mutex ) != 0 )
+                return( MBEDTLS_ERR_THREADING_MUTEX_ERROR );
+#endif
+            return( ret );
+        }
+    }
+
+    *pk_ptr = mbedtls_x509_crt_cache_get_pk( crt->cache );
+    return( 0 );
+}
+
+static inline void mbedtls_x509_crt_pk_release( mbedtls_x509_crt *crt,
+                                                mbedtls_pk_context *pk )
+{
+    ((void) pk);
+    ((void) crt);
+
+#if defined(MBEDTLS_THREADING_C)
+    mbedtls_mutex_unlock( &crt->cache->pk_mutex );
+#endif
+}
+
 #endif /* MBEDTLS_X509_CRT_PARSE_C */
 
 /* \} name */