File: cache.go

package info (click to toggle)
golang-mongodb-mongo-driver 1.8.4%2Bds1-3
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, bookworm-backports
  • size: 18,520 kB
  • sloc: perl: 533; ansic: 491; python: 432; makefile: 187; sh: 72
file content (121 lines) | stat: -rw-r--r-- 3,513 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
// Copyright (C) MongoDB, Inc. 2017-present.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0

package ocsp

import (
	"crypto"
	"sync"
	"time"

	"golang.org/x/crypto/ocsp"
)

type cacheKey struct {
	HashAlgorithm  crypto.Hash
	IssuerNameHash string
	IssuerKeyHash  string
	SerialNumber   string
}

// Cache represents an OCSP cache.
type Cache interface {
	Update(*ocsp.Request, *ResponseDetails) *ResponseDetails
	Get(request *ocsp.Request) *ResponseDetails
}

// ConcurrentCache is an implementation of ocsp.Cache that's safe for concurrent use.
type ConcurrentCache struct {
	cache map[cacheKey]*ResponseDetails
	sync.Mutex
}

var _ Cache = (*ConcurrentCache)(nil)

// NewCache creates an empty OCSP cache.
func NewCache() *ConcurrentCache {
	return &ConcurrentCache{
		cache: make(map[cacheKey]*ResponseDetails),
	}
}

// Update updates the cache entry for the provided request. The provided response will only be cached if it has a
// status that is not ocsp.Unknown and has a non-zero NextUpdate time. If there is an existing cache entry for request,
// it will be overwritten by response if response.NextUpdate is further ahead in the future than the existing entry's
// NextUpdate.
//
// This function returns the most up-to-date response corresponding to the request.
func (c *ConcurrentCache) Update(request *ocsp.Request, response *ResponseDetails) *ResponseDetails {
	unknown := response.Status == ocsp.Unknown
	hasUpdateTime := !response.NextUpdate.IsZero()
	canBeCached := !unknown && hasUpdateTime
	key := createCacheKey(request)

	c.Lock()
	defer c.Unlock()

	current, ok := c.cache[key]
	if !ok {
		if canBeCached {
			c.cache[key] = response
		}

		// Return the provided response even though it might not have been cached because it's the most up-to-date
		// response available.
		return response
	}

	// If the new response is Unknown, we can't cache it. Return the existing cached response.
	if unknown {
		return current
	}

	// If a response has no nextUpdate set, the responder is telling us that newer information is always available.
	// In this case, remove the existing cache entry because it is stale and return the new response because it is
	// more up-to-date.
	if !hasUpdateTime {
		delete(c.cache, key)
		return response
	}

	// If we get here, the new response is conclusive and has a non-empty nextUpdate so it can be cached. Overwrite
	// the existing cache entry if the new one will be valid for longer.
	newest := current
	if response.NextUpdate.After(current.NextUpdate) {
		c.cache[key] = response
		newest = response
	}
	return newest
}

// Get returns the cached response for the request, or nil if there is no cached response. If the cached response has
// expired, it will be removed from the cache and nil will be returned.
func (c *ConcurrentCache) Get(request *ocsp.Request) *ResponseDetails {
	key := createCacheKey(request)

	c.Lock()
	defer c.Unlock()

	response, ok := c.cache[key]
	if !ok {
		return nil
	}

	if time.Now().UTC().Before(response.NextUpdate) {
		return response
	}
	delete(c.cache, key)
	return nil
}

func createCacheKey(request *ocsp.Request) cacheKey {
	return cacheKey{
		HashAlgorithm:  request.HashAlgorithm,
		IssuerNameHash: string(request.IssuerNameHash),
		IssuerKeyHash:  string(request.IssuerKeyHash),
		SerialNumber:   request.SerialNumber.String(),
	}
}