tanjent@gmail.com | ad4b363 | 2010-11-05 01:20:58 +0000 | [diff] [blame] | 1 | #include "KeysetTest.h"
|
| 2 |
|
tanjent@gmail.com | 603c878 | 2011-04-01 08:50:06 +0000 | [diff] [blame] | 3 | #include "Platform.h"
|
tanjent@gmail.com | ad4b363 | 2010-11-05 01:20:58 +0000 | [diff] [blame] | 4 | #include "Random.h"
|
| 5 |
|
tanjent@gmail.com | 603c878 | 2011-04-01 08:50:06 +0000 | [diff] [blame] | 6 | #include <map>
|
| 7 | #include <set>
|
| 8 |
|
tanjent@gmail.com | ad4b363 | 2010-11-05 01:20:58 +0000 | [diff] [blame] | 9 | //-----------------------------------------------------------------------------
|
tanjent@gmail.com | 6ffe010 | 2011-03-19 21:28:26 +0000 | [diff] [blame] | 10 | // This should hopefully be a thorough and uambiguous test of whether a hash
|
| 11 | // is correctly implemented on a given platform
|
tanjent@gmail.com | ad4b363 | 2010-11-05 01:20:58 +0000 | [diff] [blame] | 12 |
|
tanjent@gmail.com | 6ffe010 | 2011-03-19 21:28:26 +0000 | [diff] [blame] | 13 | bool VerificationTest ( pfHash hash, const int hashbits, uint32_t expected, bool verbose )
|
tanjent@gmail.com | ad4b363 | 2010-11-05 01:20:58 +0000 | [diff] [blame] | 14 | {
|
tanjent@gmail.com | 6ffe010 | 2011-03-19 21:28:26 +0000 | [diff] [blame] | 15 | const int hashbytes = hashbits / 8;
|
tanjent@gmail.com | ad4b363 | 2010-11-05 01:20:58 +0000 | [diff] [blame] | 16 |
|
tanjent@gmail.com | 6ffe010 | 2011-03-19 21:28:26 +0000 | [diff] [blame] | 17 | uint8_t * key = new uint8_t[256];
|
| 18 | uint8_t * hashes = new uint8_t[hashbytes * 256];
|
| 19 | uint8_t * final = new uint8_t[hashbytes];
|
tanjent@gmail.com | ad4b363 | 2010-11-05 01:20:58 +0000 | [diff] [blame] | 20 |
|
tanjent@gmail.com | 6ffe010 | 2011-03-19 21:28:26 +0000 | [diff] [blame] | 21 | memset(key,0,256);
|
| 22 | memset(hashes,0,hashbytes*256);
|
| 23 | memset(final,0,hashbytes);
|
tanjent@gmail.com | ad4b363 | 2010-11-05 01:20:58 +0000 | [diff] [blame] | 24 |
|
tanjent@gmail.com | 623590d | 2011-03-28 18:19:31 +0000 | [diff] [blame] | 25 | // Hash keys of the form {0}, {0,1}, {0,1,2}... up to N=255,using 256-N as
|
| 26 | // the seed
|
| 27 |
|
tanjent@gmail.com | 6ffe010 | 2011-03-19 21:28:26 +0000 | [diff] [blame] | 28 | for(int i = 0; i < 256; i++)
|
| 29 | {
|
| 30 | key[i] = (uint8_t)i;
|
tanjent@gmail.com | 623590d | 2011-03-28 18:19:31 +0000 | [diff] [blame] | 31 |
|
| 32 | hash(key,i,256-i,&hashes[i*hashbytes]);
|
tanjent@gmail.com | 6ffe010 | 2011-03-19 21:28:26 +0000 | [diff] [blame] | 33 | }
|
tanjent@gmail.com | ad4b363 | 2010-11-05 01:20:58 +0000 | [diff] [blame] | 34 |
|
tanjent@gmail.com | 623590d | 2011-03-28 18:19:31 +0000 | [diff] [blame] | 35 | // Then hash the result array
|
tanjent@gmail.com | ad4b363 | 2010-11-05 01:20:58 +0000 | [diff] [blame] | 36 |
|
tanjent@gmail.com | 6ffe010 | 2011-03-19 21:28:26 +0000 | [diff] [blame] | 37 | hash(hashes,hashbytes*256,0,final);
|
tanjent@gmail.com | ad4b363 | 2010-11-05 01:20:58 +0000 | [diff] [blame] | 38 |
|
tanjent@gmail.com | 623590d | 2011-03-28 18:19:31 +0000 | [diff] [blame] | 39 | // The first four bytes of that hash, interpreted as a little-endian integer, is our
|
| 40 | // verification value
|
| 41 |
|
tanjent@gmail.com | 6ffe010 | 2011-03-19 21:28:26 +0000 | [diff] [blame] | 42 | uint32_t verification = (final[0] << 0) | (final[1] << 8) | (final[2] << 16) | (final[3] << 24);
|
| 43 |
|
| 44 | delete [] key;
|
| 45 | delete [] hashes;
|
| 46 | delete [] final;
|
| 47 |
|
| 48 | //----------
|
| 49 |
|
| 50 | if(expected != verification)
|
| 51 | {
|
| 52 | if(verbose) printf("Verification value 0x%08X : Failed! (Expected 0x%08x)\n",verification,expected);
|
| 53 | return false;
|
| 54 | }
|
| 55 | else
|
| 56 | {
|
| 57 | if(verbose) printf("Verification value 0x%08X : Passed!\n",verification);
|
| 58 | return true;
|
| 59 | }
|
tanjent@gmail.com | ad4b363 | 2010-11-05 01:20:58 +0000 | [diff] [blame] | 60 | }
|
| 61 |
|
| 62 | //----------------------------------------------------------------------------
|
tanjent@gmail.com | babb553 | 2011-02-28 06:03:12 +0000 | [diff] [blame] | 63 | // Basic sanity checks -
|
tanjent@gmail.com | ad4b363 | 2010-11-05 01:20:58 +0000 | [diff] [blame] | 64 |
|
tanjent@gmail.com | babb553 | 2011-02-28 06:03:12 +0000 | [diff] [blame] | 65 | // A hash function should not be reading outside the bounds of the key.
|
tanjent@gmail.com | ad4b363 | 2010-11-05 01:20:58 +0000 | [diff] [blame] | 66 |
|
tanjent@gmail.com | babb553 | 2011-02-28 06:03:12 +0000 | [diff] [blame] | 67 | // Flipping a bit of a key should, with overwhelmingly high probability,
|
| 68 | // result in a different hash.
|
tanjent@gmail.com | ad4b363 | 2010-11-05 01:20:58 +0000 | [diff] [blame] | 69 |
|
tanjent@gmail.com | babb553 | 2011-02-28 06:03:12 +0000 | [diff] [blame] | 70 | // Hashing the same key twice should always produce the same result.
|
tanjent@gmail.com | ad4b363 | 2010-11-05 01:20:58 +0000 | [diff] [blame] | 71 |
|
tanjent@gmail.com | babb553 | 2011-02-28 06:03:12 +0000 | [diff] [blame] | 72 | // The memory alignment of the key should not affect the hash result.
|
tanjent@gmail.com | ad4b363 | 2010-11-05 01:20:58 +0000 | [diff] [blame] | 73 |
|
tanjent@gmail.com | babb553 | 2011-02-28 06:03:12 +0000 | [diff] [blame] | 74 | bool SanityTest ( pfHash hash, const int hashbits )
|
tanjent@gmail.com | 6ffe010 | 2011-03-19 21:28:26 +0000 | [diff] [blame] | 75 | {
|
| 76 | printf("Running sanity check 1");
|
aappleby@google.com | 7f20a31 | 2011-03-21 20:55:06 +0000 | [diff] [blame] | 77 |
|
| 78 | Rand r(883741);
|
tanjent@gmail.com | ad4b363 | 2010-11-05 01:20:58 +0000 | [diff] [blame] | 79 |
|
tanjent@gmail.com | 6ffe010 | 2011-03-19 21:28:26 +0000 | [diff] [blame] | 80 | bool result = true;
|
tanjent@gmail.com | 9d17d0b | 2010-11-17 04:07:51 +0000 | [diff] [blame] | 81 |
|
tanjent@gmail.com | 6ffe010 | 2011-03-19 21:28:26 +0000 | [diff] [blame] | 82 | const int hashbytes = hashbits/8;
|
| 83 | const int reps = 10;
|
| 84 | const int keymax = 128;
|
| 85 | const int pad = 16;
|
| 86 | const int buflen = keymax + pad*3;
|
| 87 |
|
| 88 | uint8_t * buffer1 = new uint8_t[buflen];
|
| 89 | uint8_t * buffer2 = new uint8_t[buflen];
|
tanjent@gmail.com | ad4b363 | 2010-11-05 01:20:58 +0000 | [diff] [blame] | 90 |
|
tanjent@gmail.com | 6ffe010 | 2011-03-19 21:28:26 +0000 | [diff] [blame] | 91 | uint8_t * hash1 = new uint8_t[hashbytes];
|
| 92 | uint8_t * hash2 = new uint8_t[hashbytes];
|
tanjent@gmail.com | babb553 | 2011-02-28 06:03:12 +0000 | [diff] [blame] | 93 |
|
tanjent@gmail.com | 6ffe010 | 2011-03-19 21:28:26 +0000 | [diff] [blame] | 94 | //----------
|
| 95 |
|
| 96 | for(int irep = 0; irep < reps; irep++)
|
| 97 | {
|
| 98 | if(irep % (reps/10) == 0) printf(".");
|
tanjent@gmail.com | ad4b363 | 2010-11-05 01:20:58 +0000 | [diff] [blame] | 99 |
|
tanjent@gmail.com | 6ffe010 | 2011-03-19 21:28:26 +0000 | [diff] [blame] | 100 | for(int len = 4; len <= keymax; len++)
|
| 101 | {
|
| 102 | for(int offset = pad; offset < pad*2; offset++)
|
| 103 | {
|
| 104 | uint8_t * key1 = &buffer1[pad];
|
| 105 | uint8_t * key2 = &buffer2[pad+offset];
|
tanjent@gmail.com | babb553 | 2011-02-28 06:03:12 +0000 | [diff] [blame] | 106 |
|
aappleby@google.com | 7f20a31 | 2011-03-21 20:55:06 +0000 | [diff] [blame] | 107 | r.rand_p(buffer1,buflen);
|
| 108 | r.rand_p(buffer2,buflen);
|
tanjent@gmail.com | babb553 | 2011-02-28 06:03:12 +0000 | [diff] [blame] | 109 |
|
tanjent@gmail.com | 6ffe010 | 2011-03-19 21:28:26 +0000 | [diff] [blame] | 110 | memcpy(key2,key1,len);
|
tanjent@gmail.com | babb553 | 2011-02-28 06:03:12 +0000 | [diff] [blame] | 111 |
|
tanjent@gmail.com | 6ffe010 | 2011-03-19 21:28:26 +0000 | [diff] [blame] | 112 | hash(key1,len,0,hash1);
|
tanjent@gmail.com | babb553 | 2011-02-28 06:03:12 +0000 | [diff] [blame] | 113 |
|
tanjent@gmail.com | 6ffe010 | 2011-03-19 21:28:26 +0000 | [diff] [blame] | 114 | for(int bit = 0; bit < (len * 8); bit++)
|
| 115 | {
|
| 116 | // Flip a bit, hash the key -> we should get a different result.
|
tanjent@gmail.com | babb553 | 2011-02-28 06:03:12 +0000 | [diff] [blame] | 117 |
|
tanjent@gmail.com | 6ffe010 | 2011-03-19 21:28:26 +0000 | [diff] [blame] | 118 | flipbit(key2,len,bit);
|
| 119 | hash(key2,len,0,hash2);
|
tanjent@gmail.com | babb553 | 2011-02-28 06:03:12 +0000 | [diff] [blame] | 120 |
|
tanjent@gmail.com | 6ffe010 | 2011-03-19 21:28:26 +0000 | [diff] [blame] | 121 | if(memcmp(hash1,hash2,hashbytes) == 0)
|
| 122 | {
|
| 123 | result = false;
|
| 124 | }
|
tanjent@gmail.com | babb553 | 2011-02-28 06:03:12 +0000 | [diff] [blame] | 125 |
|
tanjent@gmail.com | 6ffe010 | 2011-03-19 21:28:26 +0000 | [diff] [blame] | 126 | // Flip it back, hash again -> we should get the original result.
|
tanjent@gmail.com | babb553 | 2011-02-28 06:03:12 +0000 | [diff] [blame] | 127 |
|
tanjent@gmail.com | 6ffe010 | 2011-03-19 21:28:26 +0000 | [diff] [blame] | 128 | flipbit(key2,len,bit);
|
| 129 | hash(key2,len,0,hash2);
|
tanjent@gmail.com | babb553 | 2011-02-28 06:03:12 +0000 | [diff] [blame] | 130 |
|
tanjent@gmail.com | 6ffe010 | 2011-03-19 21:28:26 +0000 | [diff] [blame] | 131 | if(memcmp(hash1,hash2,hashbytes) != 0)
|
| 132 | {
|
| 133 | result = false;
|
| 134 | }
|
| 135 | }
|
| 136 | }
|
| 137 | }
|
| 138 | }
|
tanjent@gmail.com | ad4b363 | 2010-11-05 01:20:58 +0000 | [diff] [blame] | 139 |
|
tanjent@gmail.com | 6ffe010 | 2011-03-19 21:28:26 +0000 | [diff] [blame] | 140 | if(result == false)
|
| 141 | {
|
| 142 | printf("*********FAIL*********\n");
|
| 143 | }
|
| 144 | else
|
| 145 | {
|
| 146 | printf("PASS\n");
|
| 147 | }
|
tanjent@gmail.com | 9d17d0b | 2010-11-17 04:07:51 +0000 | [diff] [blame] | 148 |
|
tanjent@gmail.com | 6ffe010 | 2011-03-19 21:28:26 +0000 | [diff] [blame] | 149 | delete [] hash1;
|
| 150 | delete [] hash2;
|
tanjent@gmail.com | babb553 | 2011-02-28 06:03:12 +0000 | [diff] [blame] | 151 |
|
tanjent@gmail.com | 6ffe010 | 2011-03-19 21:28:26 +0000 | [diff] [blame] | 152 | return result;
|
tanjent@gmail.com | ad4b363 | 2010-11-05 01:20:58 +0000 | [diff] [blame] | 153 | }
|
| 154 |
|
| 155 | //----------------------------------------------------------------------------
|
| 156 | // Appending zero bytes to a key should always cause it to produce a different
|
| 157 | // hash value
|
| 158 |
|
| 159 | void AppendedZeroesTest ( pfHash hash, const int hashbits )
|
| 160 | {
|
tanjent@gmail.com | 6ffe010 | 2011-03-19 21:28:26 +0000 | [diff] [blame] | 161 | printf("Running sanity check 2");
|
aappleby@google.com | 7f20a31 | 2011-03-21 20:55:06 +0000 | [diff] [blame] | 162 |
|
| 163 | Rand r(173994);
|
tanjent@gmail.com | ad4b363 | 2010-11-05 01:20:58 +0000 | [diff] [blame] | 164 |
|
tanjent@gmail.com | 6ffe010 | 2011-03-19 21:28:26 +0000 | [diff] [blame] | 165 | const int hashbytes = hashbits/8;
|
tanjent@gmail.com | ad4b363 | 2010-11-05 01:20:58 +0000 | [diff] [blame] | 166 |
|
tanjent@gmail.com | 6ffe010 | 2011-03-19 21:28:26 +0000 | [diff] [blame] | 167 | for(int rep = 0; rep < 100; rep++)
|
| 168 | {
|
| 169 | if(rep % 10 == 0) printf(".");
|
tanjent@gmail.com | ad4b363 | 2010-11-05 01:20:58 +0000 | [diff] [blame] | 170 |
|
tanjent@gmail.com | 6ffe010 | 2011-03-19 21:28:26 +0000 | [diff] [blame] | 171 | unsigned char key[256];
|
tanjent@gmail.com | ad4b363 | 2010-11-05 01:20:58 +0000 | [diff] [blame] | 172 |
|
tanjent@gmail.com | 6ffe010 | 2011-03-19 21:28:26 +0000 | [diff] [blame] | 173 | memset(key,0,sizeof(key));
|
tanjent@gmail.com | ad4b363 | 2010-11-05 01:20:58 +0000 | [diff] [blame] | 174 |
|
aappleby@google.com | 7f20a31 | 2011-03-21 20:55:06 +0000 | [diff] [blame] | 175 | r.rand_p(key,32);
|
tanjent@gmail.com | ad4b363 | 2010-11-05 01:20:58 +0000 | [diff] [blame] | 176 |
|
tanjent@gmail.com | 6ffe010 | 2011-03-19 21:28:26 +0000 | [diff] [blame] | 177 | uint32_t h1[16];
|
| 178 | uint32_t h2[16];
|
tanjent@gmail.com | ad4b363 | 2010-11-05 01:20:58 +0000 | [diff] [blame] | 179 |
|
tanjent@gmail.com | 6ffe010 | 2011-03-19 21:28:26 +0000 | [diff] [blame] | 180 | memset(h1,0,hashbytes);
|
| 181 | memset(h2,0,hashbytes);
|
tanjent@gmail.com | ad4b363 | 2010-11-05 01:20:58 +0000 | [diff] [blame] | 182 |
|
tanjent@gmail.com | 6ffe010 | 2011-03-19 21:28:26 +0000 | [diff] [blame] | 183 | for(int i = 0; i < 32; i++)
|
| 184 | {
|
| 185 | hash(key,32+i,0,h1);
|
tanjent@gmail.com | ad4b363 | 2010-11-05 01:20:58 +0000 | [diff] [blame] | 186 |
|
tanjent@gmail.com | 6ffe010 | 2011-03-19 21:28:26 +0000 | [diff] [blame] | 187 | if(memcmp(h1,h2,hashbytes) == 0)
|
| 188 | {
|
| 189 | printf("\n*********FAIL*********\n");
|
| 190 | return;
|
| 191 | }
|
tanjent@gmail.com | ad4b363 | 2010-11-05 01:20:58 +0000 | [diff] [blame] | 192 |
|
tanjent@gmail.com | 6ffe010 | 2011-03-19 21:28:26 +0000 | [diff] [blame] | 193 | memcpy(h2,h1,hashbytes);
|
| 194 | }
|
| 195 | }
|
tanjent@gmail.com | ad4b363 | 2010-11-05 01:20:58 +0000 | [diff] [blame] | 196 |
|
tanjent@gmail.com | 6ffe010 | 2011-03-19 21:28:26 +0000 | [diff] [blame] | 197 | printf("PASS\n");
|
tanjent@gmail.com | ad4b363 | 2010-11-05 01:20:58 +0000 | [diff] [blame] | 198 | }
|
| 199 |
|
tanjent@gmail.com | ad4b363 | 2010-11-05 01:20:58 +0000 | [diff] [blame] | 200 | //-----------------------------------------------------------------------------
|
tanjent@gmail.com | 603c878 | 2011-04-01 08:50:06 +0000 | [diff] [blame] | 201 | // Generate all keys of up to N bytes containing two non-zero bytes
|
| 202 |
|
| 203 | void TwoBytesKeygen ( int maxlen, KeyCallback & c )
|
| 204 | {
|
| 205 | //----------
|
| 206 | // Compute # of keys
|
| 207 |
|
| 208 | int keycount = 0;
|
| 209 |
|
| 210 | for(int i = 2; i <= maxlen; i++) keycount += (int)chooseK(i,2);
|
| 211 |
|
| 212 | keycount *= 255*255;
|
| 213 |
|
| 214 | for(int i = 2; i <= maxlen; i++) keycount += i*255;
|
| 215 |
|
| 216 | printf("Keyset 'TwoBytes' - up-to-%d-byte keys, %d total keys\n",maxlen, keycount);
|
| 217 |
|
| 218 | c.reserve(keycount);
|
| 219 |
|
| 220 | //----------
|
| 221 | // Add all keys with one non-zero byte
|
| 222 |
|
| 223 | uint8_t key[256];
|
| 224 |
|
| 225 | memset(key,0,256);
|
| 226 |
|
| 227 | for(int keylen = 2; keylen <= maxlen; keylen++)
|
| 228 | for(int byteA = 0; byteA < keylen; byteA++)
|
| 229 | {
|
| 230 | for(int valA = 1; valA <= 255; valA++)
|
| 231 | {
|
| 232 | key[byteA] = (uint8_t)valA;
|
| 233 |
|
| 234 | c(key,keylen);
|
| 235 | }
|
| 236 |
|
| 237 | key[byteA] = 0;
|
| 238 | }
|
| 239 |
|
| 240 | //----------
|
| 241 | // Add all keys with two non-zero bytes
|
| 242 |
|
| 243 | for(int keylen = 2; keylen <= maxlen; keylen++)
|
| 244 | for(int byteA = 0; byteA < keylen-1; byteA++)
|
| 245 | for(int byteB = byteA+1; byteB < keylen; byteB++)
|
| 246 | {
|
| 247 | for(int valA = 1; valA <= 255; valA++)
|
| 248 | {
|
| 249 | key[byteA] = (uint8_t)valA;
|
| 250 |
|
| 251 | for(int valB = 1; valB <= 255; valB++)
|
| 252 | {
|
| 253 | key[byteB] = (uint8_t)valB;
|
| 254 | c(key,keylen);
|
| 255 | }
|
| 256 |
|
| 257 | key[byteB] = 0;
|
| 258 | }
|
| 259 |
|
| 260 | key[byteA] = 0;
|
| 261 | }
|
| 262 | }
|
| 263 |
|
| 264 | //-----------------------------------------------------------------------------
|
tanjent@gmail.com | 0f37bbd | 2011-04-04 23:05:26 +0000 | [diff] [blame] | 265 |
|
| 266 | template< typename hashtype >
|
| 267 | void DumpCollisionMap ( CollisionMap<hashtype,ByteVec> & cmap )
|
| 268 | {
|
| 269 | typedef CollisionMap<hashtype,ByteVec> cmap_t;
|
| 270 |
|
aappleby@google.com | 4be8e18 | 2011-04-04 23:07:18 +0000 | [diff] [blame] | 271 | for(typename cmap_t::iterator it = cmap.begin(); it != cmap.end(); ++it)
|
tanjent@gmail.com | 0f37bbd | 2011-04-04 23:05:26 +0000 | [diff] [blame] | 272 | {
|
| 273 | const hashtype & hash = (*it).first;
|
| 274 |
|
| 275 | printf("Hash - ");
|
| 276 | printbytes(&hash,sizeof(hashtype));
|
| 277 | printf("\n");
|
| 278 |
|
| 279 | std::vector<ByteVec> & keys = (*it).second;
|
| 280 |
|
| 281 | for(int i = 0; i < (int)keys.size(); i++)
|
| 282 | {
|
| 283 | ByteVec & key = keys[i];
|
| 284 |
|
| 285 | printf("Key - ");
|
| 286 | printbytes(&key[0],(int)key.size());
|
| 287 | printf("\n");
|
| 288 | }
|
| 289 | printf("\n");
|
| 290 | }
|
| 291 |
|
| 292 | }
|
| 293 |
|
| 294 | // test code
|
| 295 |
|
| 296 | void ReportCollisions ( pfHash hash )
|
| 297 | {
|
| 298 | printf("Hashing keyset\n");
|
| 299 |
|
| 300 | std::vector<uint128_t> hashes;
|
| 301 |
|
| 302 | HashCallback<uint128_t> c(hash,hashes);
|
| 303 |
|
| 304 | TwoBytesKeygen(20,c);
|
| 305 |
|
aappleby@google.com | 4be8e18 | 2011-04-04 23:07:18 +0000 | [diff] [blame] | 306 | printf("%d hashes\n",(int)hashes.size());
|
tanjent@gmail.com | 0f37bbd | 2011-04-04 23:05:26 +0000 | [diff] [blame] | 307 |
|
| 308 | printf("Finding collisions\n");
|
| 309 |
|
| 310 | HashSet<uint128_t> collisions;
|
| 311 |
|
| 312 | FindCollisions(hashes,collisions,1000);
|
| 313 |
|
aappleby@google.com | 4be8e18 | 2011-04-04 23:07:18 +0000 | [diff] [blame] | 314 | printf("%d collisions\n",(int)collisions.size());
|
tanjent@gmail.com | 0f37bbd | 2011-04-04 23:05:26 +0000 | [diff] [blame] | 315 |
|
| 316 | printf("Mapping collisions\n");
|
| 317 |
|
| 318 | CollisionMap<uint128_t,ByteVec> cmap;
|
| 319 |
|
| 320 | CollisionCallback<uint128_t> c2(hash,collisions,cmap);
|
| 321 |
|
| 322 | TwoBytesKeygen(20,c2);
|
| 323 |
|
| 324 | printf("Dumping collisions\n");
|
| 325 |
|
| 326 | DumpCollisionMap(cmap);
|
aappleby@google.com | 4be8e18 | 2011-04-04 23:07:18 +0000 | [diff] [blame] | 327 | }
|