aboutsummaryrefslogtreecommitdiff
path: root/src/or/replaycache.c
blob: 90f87c12d55b5ec08f99d4c5cb03abb8133ecdd4 (plain)
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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
 /* Copyright (c) 2012-2013, The Tor Project, Inc. */
 /* See LICENSE for licensing information */

/*
 * \file replaycache.c
 *
 * \brief Self-scrubbing replay cache for rendservice.c
 */

#define REPLAYCACHE_PRIVATE

#include "or.h"
#include "replaycache.h"

/** Free the replaycache r and all of its entries.
 */

void
replaycache_free(replaycache_t *r)
{
  if (!r) {
    log_info(LD_BUG, "replaycache_free() called on NULL");
    return;
  }

  if (r->digests_seen) digestmap_free(r->digests_seen, tor_free_);

  tor_free(r);
}

/** Allocate a new, empty replay detection cache, where horizon is the time
 * for entries to age out and interval is the time after which the cache
 * should be scrubbed for old entries.
 */

replaycache_t *
replaycache_new(time_t horizon, time_t interval)
{
  replaycache_t *r = NULL;

  if (horizon < 0) {
    log_info(LD_BUG, "replaycache_new() called with negative"
        " horizon parameter");
    goto err;
  }

  if (interval < 0) {
    log_info(LD_BUG, "replaycache_new() called with negative interval"
        " parameter");
    interval = 0;
  }

  r = tor_malloc(sizeof(*r));
  r->scrub_interval = interval;
  r->scrubbed = 0;
  r->horizon = horizon;
  r->digests_seen = digestmap_new();

 err:
  return r;
}

/** See documentation for replaycache_add_and_test()
 */

STATIC int
replaycache_add_and_test_internal(
    time_t present, replaycache_t *r, const void *data, size_t len,
    time_t *elapsed)
{
  int rv = 0;
  char digest[DIGEST_LEN];
  time_t *access_time;

  /* sanity check */
  if (present <= 0 || !r || !data || len == 0) {
    log_info(LD_BUG, "replaycache_add_and_test_internal() called with stupid"
        " parameters; please fix this.");
    goto done;
  }

  /* compute digest */
  crypto_digest(digest, (const char *)data, len);

  /* check map */
  access_time = digestmap_get(r->digests_seen, digest);

  /* seen before? */
  if (access_time != NULL) {
    /*
     * If it's far enough in the past, no hit.  If the horizon is zero, we
     * never expire.
     */
    if (*access_time >= present - r->horizon || r->horizon == 0) {
      /* replay cache hit, return 1 */
      rv = 1;
      /* If we want to output an elapsed time, do so */
      if (elapsed) {
        if (present >= *access_time) {
          *elapsed = present - *access_time;
        } else {
          /* We shouldn't really be seeing hits from the future, but... */
          *elapsed = 0;
        }
      }
    }
    /*
     * If it's ahead of the cached time, update
     */
    if (*access_time < present) {
      *access_time = present;
    }
  } else {
    /* No, so no hit and update the digest map with the current time */
    access_time = tor_malloc(sizeof(*access_time));
    *access_time = present;
    digestmap_set(r->digests_seen, digest, access_time);
  }

  /* now scrub the cache if it's time */
  replaycache_scrub_if_needed_internal(present, r);

 done:
  return rv;
}

/** See documentation for replaycache_scrub_if_needed()
 */

STATIC void
replaycache_scrub_if_needed_internal(time_t present, replaycache_t *r)
{
  digestmap_iter_t *itr = NULL;
  const char *digest;
  void *valp;
  time_t *access_time;

  /* sanity check */
  if (!r || !(r->digests_seen)) {
    log_info(LD_BUG, "replaycache_scrub_if_needed_internal() called with"
        " stupid parameters; please fix this.");
    return;
  }

  /* scrub time yet? (scrubbed == 0 indicates never scrubbed before) */
  if (present - r->scrubbed < r->scrub_interval && r->scrubbed > 0) return;

  /* if we're never expiring, don't bother scrubbing */
  if (r->horizon == 0) return;

  /* okay, scrub time */
  itr = digestmap_iter_init(r->digests_seen);
  while (!digestmap_iter_done(itr)) {
    digestmap_iter_get(itr, &digest, &valp);
    access_time = (time_t *)valp;
    /* aged out yet? */
    if (*access_time < present - r->horizon) {
      /* Advance the iterator and remove this one */
      itr = digestmap_iter_next_rmv(r->digests_seen, itr);
      /* Free the value removed */
      tor_free(access_time);
    } else {
      /* Just advance the iterator */
      itr = digestmap_iter_next(r->digests_seen, itr);
    }
  }

  /* update scrubbed timestamp */
  if (present > r->scrubbed) r->scrubbed = present;
}

/** Test the buffer of length len point to by data against the replay cache r;
 * the digest of the buffer will be added to the cache at the current time,
 * and the function will return 1 if it was already seen within the cache's
 * horizon, or 0 otherwise.
 */

int
replaycache_add_and_test(replaycache_t *r, const void *data, size_t len)
{
  return replaycache_add_and_test_internal(time(NULL), r, data, len, NULL);
}

/** Like replaycache_add_and_test(), but if it's a hit also return the time
 * elapsed since this digest was last seen.
 */

int
replaycache_add_test_and_elapsed(
    replaycache_t *r, const void *data, size_t len, time_t *elapsed)
{
  return replaycache_add_and_test_internal(time(NULL), r, data, len, elapsed);
}

/** Scrub aged entries out of r if sufficiently long has elapsed since r was
 * last scrubbed.
 */

void
replaycache_scrub_if_needed(replaycache_t *r)
{
  replaycache_scrub_if_needed_internal(time(NULL), r);
}