001/*
002 * Copyright 2015-2024 the original author or authors
003 *
004 * This software is licensed under the Apache License, Version 2.0,
005 * the GNU Lesser General Public License version 2 or later ("LGPL")
006 * and the WTFPL.
007 * You may choose either license to govern your use of this software only
008 * upon the condition that you accept all of the terms of either
009 * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
010 */
011package org.minidns.cache;
012
013import java.util.LinkedHashMap;
014import java.util.Map.Entry;
015
016import org.minidns.DnsCache;
017import org.minidns.dnsmessage.DnsMessage;
018import org.minidns.dnsname.DnsName;
019import org.minidns.dnsqueryresult.CachedDnsQueryResult;
020import org.minidns.dnsqueryresult.DirectCachedDnsQueryResult;
021import org.minidns.dnsqueryresult.DnsQueryResult;
022
023/**
024 * LRU based DNSCache backed by a LinkedHashMap.
025 */
026public class LruCache extends DnsCache {
027
028    /**
029     * Internal miss count.
030     */
031    protected long missCount = 0L;
032
033    /**
034     * Internal expire count (subset of misses that was caused by expire).
035     */
036    protected long expireCount = 0L;
037
038    /**
039     * Internal hit count.
040     */
041    protected long hitCount = 0L;
042
043    /**
044     * The internal capacity of the backend cache.
045     */
046    protected int capacity;
047
048    /**
049     * The upper bound of the ttl. All longer TTLs will be capped by this ttl.
050     */
051    protected long maxTTL;
052
053    /**
054     * The backend cache.
055     */
056    protected LinkedHashMap<DnsMessage, CachedDnsQueryResult> backend;
057
058    /**
059     * Create a new LRUCache with given capacity and upper bound ttl.
060     * @param capacity The internal capacity.
061     * @param maxTTL The upper bound for any ttl.
062     */
063    @SuppressWarnings("serial")
064    public LruCache(final int capacity, final long maxTTL) {
065        this.capacity = capacity;
066        this.maxTTL = maxTTL;
067        backend = new LinkedHashMap<DnsMessage, CachedDnsQueryResult>(
068                Math.min(capacity + (capacity + 3) / 4 + 2, 11), 0.75f, true) {
069                @Override
070                protected boolean removeEldestEntry(
071                        Entry<DnsMessage, CachedDnsQueryResult> eldest) {
072                    return size() > capacity;
073                }
074            };
075    }
076
077    /**
078     * Create a new LRUCache with given capacity.
079     * @param capacity The capacity of this cache.
080     */
081    public LruCache(final int capacity) {
082        this(capacity, Long.MAX_VALUE);
083    }
084
085    public LruCache() {
086        this(DEFAULT_CACHE_SIZE);
087    }
088
089    @Override
090    protected synchronized void putNormalized(DnsMessage q, DnsQueryResult result) {
091        if (result.response.receiveTimestamp <= 0L) {
092            return;
093        }
094        backend.put(q, new DirectCachedDnsQueryResult(q, result));
095    }
096
097    @Override
098    protected synchronized CachedDnsQueryResult getNormalized(DnsMessage q) {
099        CachedDnsQueryResult result = backend.get(q);
100        if (result == null) {
101            missCount++;
102            return null;
103        }
104
105        DnsMessage message = result.response;
106
107        // RFC 2181 ยง 5.2 says that all TTLs in a RRSet should be equal, if this isn't the case, then we assume the
108        // shortest TTL to be the effective one.
109        final long answersMinTtl = message.getAnswersMinTtl();
110        final long ttl = Math.min(answersMinTtl, maxTTL);
111
112        final long expiryDate = message.receiveTimestamp + (ttl * 1000);
113        final long now = System.currentTimeMillis();
114        if (expiryDate < now) {
115            missCount++;
116            expireCount++;
117            backend.remove(q);
118            return null;
119        } else {
120            hitCount++;
121            return result;
122        }
123    }
124
125    /**
126     * Clear all entries in this cache.
127     */
128    public synchronized void clear() {
129        backend.clear();
130        missCount = 0L;
131        hitCount = 0L;
132        expireCount = 0L;
133    }
134
135    /**
136     * Get the miss count of this cache which is the number of fruitless
137     * get calls since this cache was last resetted.
138     * @return The number of cache misses.
139     */
140    public long getMissCount() {
141        return missCount;
142    }
143
144    /**
145     * The number of expires (cache hits that have had a ttl to low to be
146     * retrieved).
147     * @return The expire count.
148     */
149    public long getExpireCount() {
150        return expireCount;
151    }
152
153    /**
154     * The cache hit count (all successful calls to get).
155     * @return The hit count.
156     */
157    public long getHitCount() {
158        return hitCount;
159    }
160
161    @Override
162    public String toString() {
163        return "LRUCache{usage=" + backend.size() + "/" + capacity + ", hits=" + hitCount + ", misses=" + missCount + ", expires=" + expireCount + "}";
164    }
165
166    @Override
167    public void offer(DnsMessage query, DnsQueryResult result, DnsName knownAuthoritativeZone) {
168    }
169}