blob: 605e9db05b460506178718f4bb1d2262c64acead [file] [log] [blame]
David Reissfaebedd2007-09-17 23:20:38 +00001/*
David Reissea2cba82009-03-30 21:35:00 +00002 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19
David Reiss3cc9dab2010-10-06 17:10:21 +000020#define __STDC_LIMIT_MACROS
21#define __STDC_FORMAT_MACROS
22
David Reiss9a961e72010-10-06 17:10:23 +000023#ifndef _GNU_SOURCE
24#define _GNU_SOURCE // needed for getopt_long
25#endif
26
David Reiss3cc9dab2010-10-06 17:10:21 +000027#include <stdint.h>
28#include <inttypes.h>
David Reiss9a961e72010-10-06 17:10:23 +000029#include <getopt.h>
David Reissfaebedd2007-09-17 23:20:38 +000030#include <cstddef>
David Reissfaebedd2007-09-17 23:20:38 +000031#include <fstream>
32#include <iostream>
David Reiss9a961e72010-10-06 17:10:23 +000033#include <tr1/functional>
David Reiss3cc9dab2010-10-06 17:10:21 +000034
35#include <boost/random.hpp>
David Reiss9a961e72010-10-06 17:10:23 +000036#include <boost/shared_array.hpp>
37#include <boost/test/unit_test.hpp>
David Reiss3cc9dab2010-10-06 17:10:21 +000038
David Reiss28f298d2008-05-01 06:17:36 +000039#include <transport/TBufferTransports.h>
David Reissfaebedd2007-09-17 23:20:38 +000040#include <transport/TZlibTransport.h>
41
David Reiss9a961e72010-10-06 17:10:23 +000042using namespace std;
43using namespace boost;
44using namespace apache::thrift::transport;
45
David Reissf2abcf92010-10-06 17:10:24 +000046boost::mt19937 rng;
David Reissfaebedd2007-09-17 23:20:38 +000047
David Reissf2abcf92010-10-06 17:10:24 +000048/*
49 * Utility code
50 */
David Reissfaebedd2007-09-17 23:20:38 +000051
David Reissf2abcf92010-10-06 17:10:24 +000052class SizeGenerator {
53 public:
54 virtual ~SizeGenerator() {}
55 virtual unsigned int getSize() = 0;
David Reissfaebedd2007-09-17 23:20:38 +000056};
57
David Reissf2abcf92010-10-06 17:10:24 +000058class ConstantSizeGenerator : public SizeGenerator {
59 public:
60 ConstantSizeGenerator(unsigned int value) : value_(value) {}
61 virtual unsigned int getSize() {
62 return value_;
63 }
64
65 private:
66 unsigned int value_;
67};
68
69class LogNormalSizeGenerator : public SizeGenerator {
70 public:
71 LogNormalSizeGenerator(double mean, double std_dev) :
72 gen_(rng, lognormal_distribution<double>(mean, std_dev)) {}
73
74 virtual unsigned int getSize() {
75 // Loop until we get a size of 1 or more
76 while (true) {
77 unsigned int value = static_cast<unsigned int>(gen_());
78 if (value >= 1) {
79 return value;
80 }
81 }
82 }
83
84 private:
85 variate_generator< mt19937, lognormal_distribution<double> > gen_;
86};
David Reiss3cc9dab2010-10-06 17:10:21 +000087
88uint8_t* gen_uniform_buffer(uint32_t buf_len, uint8_t c) {
89 uint8_t* buf = new uint8_t[buf_len];
90 memset(buf, c, buf_len);
91 return buf;
92}
93
94uint8_t* gen_compressible_buffer(uint32_t buf_len) {
95 uint8_t* buf = new uint8_t[buf_len];
96
97 // Generate small runs of alternately increasing and decreasing bytes
98 boost::uniform_smallint<uint32_t> run_length_distribution(1, 64);
99 boost::uniform_smallint<uint8_t> byte_distribution(0, UINT8_MAX);
100 boost::variate_generator< boost::mt19937, boost::uniform_smallint<uint8_t> >
101 byte_generator(rng, byte_distribution);
102 boost::variate_generator< boost::mt19937, boost::uniform_smallint<uint32_t> >
103 run_len_generator(rng, run_length_distribution);
104
105 uint32_t idx = 0;
106 int8_t step = 1;
107 while (idx < buf_len) {
108 uint32_t run_length = run_len_generator();
109 if (idx + run_length > buf_len) {
110 run_length = buf_len - idx;
111 }
112
113 uint8_t byte = byte_generator();
114 for (uint32_t n = 0; n < run_length; ++n) {
115 buf[idx] = byte;
116 ++idx;
117 byte += step;
118 }
119
120 step *= -1;
121 }
122
123 return buf;
124}
125
126uint8_t* gen_random_buffer(uint32_t buf_len) {
127 uint8_t* buf = new uint8_t[buf_len];
128
129 boost::uniform_smallint<uint8_t> distribution(0, UINT8_MAX);
130 boost::variate_generator< boost::mt19937, boost::uniform_smallint<uint8_t> >
131 generator(rng, distribution);
132
133 for (uint32_t n = 0; n < buf_len; ++n) {
134 buf[n] = generator();
135 }
136
137 return buf;
138}
David Reissfaebedd2007-09-17 23:20:38 +0000139
David Reissf2abcf92010-10-06 17:10:24 +0000140/*
141 * Test functions
142 */
143
David Reiss9a961e72010-10-06 17:10:23 +0000144void test_write_then_read(const uint8_t* buf, uint32_t buf_len) {
Roger Meier611f90c2011-12-11 22:08:51 +0000145 boost::shared_ptr<TMemoryBuffer> membuf(new TMemoryBuffer());
146 boost::shared_ptr<TZlibTransport> zlib_trans(new TZlibTransport(membuf));
David Reiss9a961e72010-10-06 17:10:23 +0000147 zlib_trans->write(buf, buf_len);
David Reisse94fa332010-10-06 17:10:26 +0000148 zlib_trans->finish();
David Reissfaebedd2007-09-17 23:20:38 +0000149
David Reiss9a961e72010-10-06 17:10:23 +0000150 boost::shared_array<uint8_t> mirror(new uint8_t[buf_len]);
David Reiss0a2d81e2010-10-06 17:10:40 +0000151 uint32_t got = zlib_trans->readAll(mirror.get(), buf_len);
David Reiss9a961e72010-10-06 17:10:23 +0000152 BOOST_REQUIRE_EQUAL(got, buf_len);
153 BOOST_CHECK_EQUAL(memcmp(mirror.get(), buf, buf_len), 0);
154 zlib_trans->verifyChecksum();
155}
David Reissfaebedd2007-09-17 23:20:38 +0000156
David Reiss9a961e72010-10-06 17:10:23 +0000157void test_separate_checksum(const uint8_t* buf, uint32_t buf_len) {
158 // This one is tricky. I separate the last byte of the stream out
159 // into a separate crbuf_. The last byte is part of the checksum,
160 // so the entire read goes fine, but when I go to verify the checksum
161 // it isn't there. The original implementation complained that
162 // the stream was not complete. I'm about to go fix that.
163 // It worked. Awesome.
Roger Meier611f90c2011-12-11 22:08:51 +0000164 boost::shared_ptr<TMemoryBuffer> membuf(new TMemoryBuffer());
165 boost::shared_ptr<TZlibTransport> zlib_trans(new TZlibTransport(membuf));
David Reiss9a961e72010-10-06 17:10:23 +0000166 zlib_trans->write(buf, buf_len);
David Reisse94fa332010-10-06 17:10:26 +0000167 zlib_trans->finish();
David Reiss9a961e72010-10-06 17:10:23 +0000168 string tmp_buf;
169 membuf->appendBufferToString(tmp_buf);
David Reissa0e11592010-10-06 17:10:27 +0000170 zlib_trans.reset(new TZlibTransport(membuf,
David Reiss9a961e72010-10-06 17:10:23 +0000171 TZlibTransport::DEFAULT_URBUF_SIZE,
172 tmp_buf.length()-1));
David Reissfaebedd2007-09-17 23:20:38 +0000173
David Reiss9a961e72010-10-06 17:10:23 +0000174 boost::shared_array<uint8_t> mirror(new uint8_t[buf_len]);
David Reiss0a2d81e2010-10-06 17:10:40 +0000175 uint32_t got = zlib_trans->readAll(mirror.get(), buf_len);
David Reiss9a961e72010-10-06 17:10:23 +0000176 BOOST_REQUIRE_EQUAL(got, buf_len);
177 BOOST_CHECK_EQUAL(memcmp(mirror.get(), buf, buf_len), 0);
178 zlib_trans->verifyChecksum();
179}
David Reissfaebedd2007-09-17 23:20:38 +0000180
David Reiss9a961e72010-10-06 17:10:23 +0000181void test_incomplete_checksum(const uint8_t* buf, uint32_t buf_len) {
182 // Make sure we still get that "not complete" error if
183 // it really isn't complete.
Roger Meier611f90c2011-12-11 22:08:51 +0000184 boost::shared_ptr<TMemoryBuffer> membuf(new TMemoryBuffer());
185 boost::shared_ptr<TZlibTransport> zlib_trans(new TZlibTransport(membuf));
David Reiss9a961e72010-10-06 17:10:23 +0000186 zlib_trans->write(buf, buf_len);
David Reisse94fa332010-10-06 17:10:26 +0000187 zlib_trans->finish();
David Reiss9a961e72010-10-06 17:10:23 +0000188 string tmp_buf;
189 membuf->appendBufferToString(tmp_buf);
190 tmp_buf.erase(tmp_buf.length() - 1);
191 membuf->resetBuffer(const_cast<uint8_t*>(
192 reinterpret_cast<const uint8_t*>(tmp_buf.data())),
193 tmp_buf.length());
194
195 boost::shared_array<uint8_t> mirror(new uint8_t[buf_len]);
David Reiss0a2d81e2010-10-06 17:10:40 +0000196 uint32_t got = zlib_trans->readAll(mirror.get(), buf_len);
David Reiss9a961e72010-10-06 17:10:23 +0000197 BOOST_REQUIRE_EQUAL(got, buf_len);
198 BOOST_CHECK_EQUAL(memcmp(mirror.get(), buf, buf_len), 0);
199 try {
200 zlib_trans->verifyChecksum();
201 BOOST_ERROR("verifyChecksum() did not report an error");
202 } catch (TTransportException& ex) {
203 BOOST_CHECK_EQUAL(ex.getType(), TTransportException::CORRUPTED_DATA);
204 }
205}
206
207void test_read_write_mix(const uint8_t* buf, uint32_t buf_len,
Roger Meier611f90c2011-12-11 22:08:51 +0000208 const boost::shared_ptr<SizeGenerator>& write_gen,
209 const boost::shared_ptr<SizeGenerator>& read_gen) {
David Reiss9a961e72010-10-06 17:10:23 +0000210 // Try it with a mix of read/write sizes.
Roger Meier611f90c2011-12-11 22:08:51 +0000211 boost::shared_ptr<TMemoryBuffer> membuf(new TMemoryBuffer());
212 boost::shared_ptr<TZlibTransport> zlib_trans(new TZlibTransport(membuf));
David Reiss9a961e72010-10-06 17:10:23 +0000213 unsigned int tot;
214
David Reiss9a961e72010-10-06 17:10:23 +0000215 tot = 0;
216 while (tot < buf_len) {
David Reissf2abcf92010-10-06 17:10:24 +0000217 uint32_t write_len = write_gen->getSize();
David Reiss9a961e72010-10-06 17:10:23 +0000218 if (tot + write_len > buf_len) {
219 write_len = buf_len - tot;
David Reissfaebedd2007-09-17 23:20:38 +0000220 }
David Reiss9a961e72010-10-06 17:10:23 +0000221 zlib_trans->write(buf + tot, write_len);
222 tot += write_len;
David Reiss9a961e72010-10-06 17:10:23 +0000223 }
David Reissfaebedd2007-09-17 23:20:38 +0000224
David Reisse94fa332010-10-06 17:10:26 +0000225 zlib_trans->finish();
David Reiss9a961e72010-10-06 17:10:23 +0000226
David Reiss9a961e72010-10-06 17:10:23 +0000227 tot = 0;
228 boost::shared_array<uint8_t> mirror(new uint8_t[buf_len]);
229 while (tot < buf_len) {
David Reissf2abcf92010-10-06 17:10:24 +0000230 uint32_t read_len = read_gen->getSize();
David Reiss9a961e72010-10-06 17:10:23 +0000231 uint32_t expected_read_len = read_len;
232 if (tot + read_len > buf_len) {
233 expected_read_len = buf_len - tot;
David Reissfaebedd2007-09-17 23:20:38 +0000234 }
David Reiss9a961e72010-10-06 17:10:23 +0000235 uint32_t got = zlib_trans->read(mirror.get() + tot, read_len);
David Reiss0a2d81e2010-10-06 17:10:40 +0000236 BOOST_REQUIRE_LE(got, expected_read_len);
Christian Lavoie01c5ceb2010-11-04 20:35:15 +0000237 BOOST_REQUIRE_NE(got, (uint32_t) 0);
David Reiss9a961e72010-10-06 17:10:23 +0000238 tot += got;
David Reiss9a961e72010-10-06 17:10:23 +0000239 }
David Reissfaebedd2007-09-17 23:20:38 +0000240
David Reiss9a961e72010-10-06 17:10:23 +0000241 BOOST_CHECK_EQUAL(memcmp(mirror.get(), buf, buf_len), 0);
242 zlib_trans->verifyChecksum();
243}
David Reissfaebedd2007-09-17 23:20:38 +0000244
David Reiss9a961e72010-10-06 17:10:23 +0000245void test_invalid_checksum(const uint8_t* buf, uint32_t buf_len) {
246 // Verify checksum checking.
Roger Meier611f90c2011-12-11 22:08:51 +0000247 boost::shared_ptr<TMemoryBuffer> membuf(new TMemoryBuffer());
248 boost::shared_ptr<TZlibTransport> zlib_trans(new TZlibTransport(membuf));
David Reiss9a961e72010-10-06 17:10:23 +0000249 zlib_trans->write(buf, buf_len);
David Reisse94fa332010-10-06 17:10:26 +0000250 zlib_trans->finish();
David Reiss9a961e72010-10-06 17:10:23 +0000251 string tmp_buf;
252 membuf->appendBufferToString(tmp_buf);
253 // Modify a byte at the end of the buffer (part of the checksum).
254 // On rare occasions, modifying a byte in the middle of the buffer
255 // isn't caught by the checksum.
256 //
257 // (This happens especially often for the uniform buffer. The
258 // re-inflated data is correct, however. I suspect in this case that
259 // we're more likely to modify bytes that are part of zlib metadata
260 // instead of the actual compressed data.)
261 //
262 // I've also seen some failure scenarios where a checksum failure isn't
263 // reported, but zlib keeps trying to decode past the end of the data.
264 // (When this occurs, verifyChecksum() throws an exception indicating
265 // that the end of the data hasn't been reached.) I haven't seen this
266 // error when only modifying checksum bytes.
267 int index = tmp_buf.size() - 1;
268 tmp_buf[index]++;
269 membuf->resetBuffer(const_cast<uint8_t*>(
270 reinterpret_cast<const uint8_t*>(tmp_buf.data())),
271 tmp_buf.length());
David Reissfaebedd2007-09-17 23:20:38 +0000272
David Reiss9a961e72010-10-06 17:10:23 +0000273 boost::shared_array<uint8_t> mirror(new uint8_t[buf_len]);
274 try {
David Reiss0a2d81e2010-10-06 17:10:40 +0000275 zlib_trans->readAll(mirror.get(), buf_len);
David Reiss9a961e72010-10-06 17:10:23 +0000276 zlib_trans->verifyChecksum();
277 BOOST_ERROR("verifyChecksum() did not report an error");
278 } catch (TZlibTransportException& ex) {
279 BOOST_CHECK_EQUAL(ex.getType(), TTransportException::INTERNAL_ERROR);
280 }
281}
David Reissfaebedd2007-09-17 23:20:38 +0000282
David Reisse94fa332010-10-06 17:10:26 +0000283void test_write_after_flush(const uint8_t* buf, uint32_t buf_len) {
284 // write some data
Roger Meier611f90c2011-12-11 22:08:51 +0000285 boost::shared_ptr<TMemoryBuffer> membuf(new TMemoryBuffer());
286 boost::shared_ptr<TZlibTransport> zlib_trans(new TZlibTransport(membuf));
David Reisse94fa332010-10-06 17:10:26 +0000287 zlib_trans->write(buf, buf_len);
288
289 // call finish()
290 zlib_trans->finish();
291
292 // make sure write() throws an error
293 try {
294 uint8_t write_buf[] = "a";
295 zlib_trans->write(write_buf, 1);
296 BOOST_ERROR("write() after finish() did not raise an exception");
297 } catch (TTransportException& ex) {
298 BOOST_CHECK_EQUAL(ex.getType(), TTransportException::BAD_ARGS);
299 }
300
301 // make sure flush() throws an error
302 try {
303 zlib_trans->flush();
304 BOOST_ERROR("flush() after finish() did not raise an exception");
305 } catch (TTransportException& ex) {
306 BOOST_CHECK_EQUAL(ex.getType(), TTransportException::BAD_ARGS);
307 }
308
309 // make sure finish() throws an error
310 try {
311 zlib_trans->finish();
312 BOOST_ERROR("finish() after finish() did not raise an exception");
313 } catch (TTransportException& ex) {
314 BOOST_CHECK_EQUAL(ex.getType(), TTransportException::BAD_ARGS);
315 }
316}
317
318void test_no_write() {
319 // Verify that no data is written to the underlying transport if we
320 // never write data to the TZlibTransport.
Roger Meier611f90c2011-12-11 22:08:51 +0000321 boost::shared_ptr<TMemoryBuffer> membuf(new TMemoryBuffer());
David Reisse94fa332010-10-06 17:10:26 +0000322 {
323 // Create a TZlibTransport object, and immediately destroy it
324 // when it goes out of scope.
David Reissa0e11592010-10-06 17:10:27 +0000325 TZlibTransport w_zlib_trans(membuf);
David Reisse94fa332010-10-06 17:10:26 +0000326 }
327
Christian Lavoie01c5ceb2010-11-04 20:35:15 +0000328 BOOST_CHECK_EQUAL(membuf->available_read(), (uint32_t) 0);
David Reisse94fa332010-10-06 17:10:26 +0000329}
330
David Reissf2abcf92010-10-06 17:10:24 +0000331/*
332 * Initialization
333 */
334
David Reiss9a961e72010-10-06 17:10:23 +0000335#define ADD_TEST_CASE(suite, name, function, ...) \
336 do { \
337 ::std::ostringstream name_ss; \
338 name_ss << name << "-" << BOOST_STRINGIZE(function); \
339 ::boost::unit_test::test_case* tc = ::boost::unit_test::make_test_case( \
340 ::std::tr1::bind(function, ## __VA_ARGS__), \
341 name_ss.str()); \
342 (suite)->add(tc); \
343 } while (0)
David Reissfaebedd2007-09-17 23:20:38 +0000344
David Reiss9a961e72010-10-06 17:10:23 +0000345void add_tests(unit_test::test_suite* suite,
346 const uint8_t* buf,
347 uint32_t buf_len,
348 const char* name) {
349 ADD_TEST_CASE(suite, name, test_write_then_read, buf, buf_len);
350 ADD_TEST_CASE(suite, name, test_separate_checksum, buf, buf_len);
351 ADD_TEST_CASE(suite, name, test_incomplete_checksum, buf, buf_len);
David Reiss9a961e72010-10-06 17:10:23 +0000352 ADD_TEST_CASE(suite, name, test_invalid_checksum, buf, buf_len);
David Reisse94fa332010-10-06 17:10:26 +0000353 ADD_TEST_CASE(suite, name, test_write_after_flush, buf, buf_len);
David Reissf2abcf92010-10-06 17:10:24 +0000354
Roger Meier611f90c2011-12-11 22:08:51 +0000355 boost::shared_ptr<SizeGenerator> size_32k(new ConstantSizeGenerator(1<<15));
356 boost::shared_ptr<SizeGenerator> size_lognormal(new LogNormalSizeGenerator(20, 30));
David Reissf2abcf92010-10-06 17:10:24 +0000357 ADD_TEST_CASE(suite, name << "-constant",
358 test_read_write_mix, buf, buf_len,
359 size_32k, size_32k);
360 ADD_TEST_CASE(suite, name << "-lognormal-write",
361 test_read_write_mix, buf, buf_len,
362 size_lognormal, size_32k);
363 ADD_TEST_CASE(suite, name << "-lognormal-read",
364 test_read_write_mix, buf, buf_len,
365 size_32k, size_lognormal);
366 ADD_TEST_CASE(suite, name << "-lognormal-both",
367 test_read_write_mix, buf, buf_len,
368 size_lognormal, size_lognormal);
369
370 // Test with a random size distribution,
371 // but use the exact same distribution for reading as for writing.
372 //
373 // Because the SizeGenerator makes a copy of the random number generator,
374 // both SizeGenerators should return the exact same set of values, since they
375 // both start with random number generators in the same state.
Roger Meier611f90c2011-12-11 22:08:51 +0000376 boost::shared_ptr<SizeGenerator> write_size_gen(new LogNormalSizeGenerator(20, 30));
377 boost::shared_ptr<SizeGenerator> read_size_gen(new LogNormalSizeGenerator(20, 30));
David Reissf2abcf92010-10-06 17:10:24 +0000378 ADD_TEST_CASE(suite, name << "-lognormal-same-distribution",
379 test_read_write_mix, buf, buf_len,
380 write_size_gen, read_size_gen);
David Reiss9a961e72010-10-06 17:10:23 +0000381}
382
383void print_usage(FILE* f, const char* argv0) {
384 fprintf(f, "Usage: %s [boost_options] [options]\n", argv0);
385 fprintf(f, "Options:\n");
386 fprintf(f, " --seed=<N>, -s <N>\n");
387 fprintf(f, " --help\n");
388}
389
390void parse_args(int argc, char* argv[]) {
391 uint32_t seed = 0;
392 bool has_seed = false;
393
394 struct option long_opts[] = {
395 { "help", false, NULL, 'h' },
396 { "seed", true, NULL, 's' },
397 { NULL, 0, NULL, 0 }
398 };
399
400 while (true) {
401 optopt = 1;
402 int optchar = getopt_long(argc, argv, "hs:", long_opts, NULL);
403 if (optchar == -1) {
404 break;
405 }
406
407 switch (optchar) {
408 case 's': {
409 char *endptr;
410 seed = strtol(optarg, &endptr, 0);
411 if (endptr == optarg || *endptr != '\0') {
412 fprintf(stderr, "invalid seed value \"%s\": must be a positive "
413 "integer\n", optarg);
414 exit(1);
415 }
416 has_seed = true;
417 break;
418 }
419 case 'h':
420 print_usage(stdout, argv[0]);
421 exit(0);
422 case '?':
423 exit(1);
424 default:
425 // Only happens if someone adds another option to the optarg string,
426 // but doesn't update the switch statement to handle it.
427 fprintf(stderr, "unknown option \"-%c\"\n", optchar);
428 exit(1);
429 }
430 }
431
432 if (!has_seed) {
433 seed = time(NULL);
434 }
435
436 printf("seed: %" PRIu32 "\n", seed);
437 rng.seed(seed);
438}
439
440unit_test::test_suite* init_unit_test_suite(int argc, char* argv[]) {
441 parse_args(argc, argv);
442
David Reiss109693c2010-10-06 17:10:42 +0000443 unit_test::test_suite* suite =
444 &boost::unit_test::framework::master_test_suite();
445 suite->p_name.value = "ZlibTest";
David Reiss9a961e72010-10-06 17:10:23 +0000446
447 uint32_t buf_len = 1024*32;
448 add_tests(suite, gen_uniform_buffer(buf_len, 'a'), buf_len, "uniform");
449 add_tests(suite, gen_compressible_buffer(buf_len), buf_len, "compressible");
450 add_tests(suite, gen_random_buffer(buf_len), buf_len, "random");
451
David Reisse94fa332010-10-06 17:10:26 +0000452 suite->add(BOOST_TEST_CASE(test_no_write));
453
David Reiss109693c2010-10-06 17:10:42 +0000454 return NULL;
David Reissfaebedd2007-09-17 23:20:38 +0000455}