monotone

monotone Mtn Source Tree

Root/packet.cc

1// Copyright (C) 2002 Graydon Hoare <graydon@pobox.com>
2//
3// This program is made available under the GNU GPL version 2.0 or
4// greater. See the accompanying file COPYING for details.
5//
6// This program is distributed WITHOUT ANY WARRANTY; without even the
7// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
8// PURPOSE.
9
10#include <iostream>
11#include <string>
12
13#include <boost/regex.hpp>
14#include <boost/lexical_cast.hpp>
15
16#include "app_state.hh"
17#include "cset.hh"
18#include "constants.hh"
19#include "packet.hh"
20#include "revision.hh"
21#include "sanity.hh"
22#include "transforms.hh"
23#include "simplestring_xform.hh"
24#include "keys.hh"
25#include "cert.hh"
26
27using std::endl;
28using std::istream;
29using std::make_pair;
30using std::map;
31using std::ostream;
32using std::pair;
33using std::string;
34
35using boost::lexical_cast;
36using boost::match_default;
37using boost::match_results;
38using boost::regex;
39using boost::shared_ptr;
40
41void
42packet_consumer::set_on_revision_written(boost::function1<void,
43 revision_id> const & x)
44{
45 on_revision_written=x;
46}
47
48void
49packet_consumer::set_on_cert_written(boost::function1<void,
50 cert const &> const & x)
51{
52 on_cert_written=x;
53}
54
55void
56packet_consumer::set_on_pubkey_written(boost::function1<void, rsa_keypair_id>
57 const & x)
58{
59 on_pubkey_written=x;
60}
61
62void
63packet_consumer::set_on_keypair_written(boost::function1<void, rsa_keypair_id>
64 const & x)
65{
66 on_keypair_written=x;
67}
68
69
70packet_db_writer::packet_db_writer(app_state & app)
71 : app(app)
72{}
73
74packet_db_writer::~packet_db_writer()
75{}
76
77void
78packet_db_writer::consume_file_data(file_id const & ident,
79 file_data const & dat)
80{
81 if (app.db.file_version_exists(ident))
82 {
83 L(FL("file version '%s' already exists in db") % ident);
84 return;
85 }
86
87 transaction_guard guard(app.db);
88 app.db.put_file(ident, dat);
89 guard.commit();
90}
91
92void
93packet_db_writer::consume_file_delta(file_id const & old_id,
94 file_id const & new_id,
95 file_delta const & del)
96{
97 transaction_guard guard(app.db);
98
99 if (app.db.file_version_exists(new_id))
100 {
101 L(FL("file version '%s' already exists in db") % new_id);
102 return;
103 }
104
105 if (!app.db.file_version_exists(old_id))
106 {
107 W(F("file preimage '%s' missing in db") % old_id);
108 W(F("dropping delta '%s' -> '%s'") % old_id % new_id);
109 return;
110 }
111
112 app.db.put_file_version(old_id, new_id, del);
113
114 guard.commit();
115}
116
117void
118packet_db_writer::consume_revision_data(revision_id const & ident,
119 revision_data const & dat)
120{
121 MM(ident);
122 transaction_guard guard(app.db);
123 if (app.db.revision_exists(ident))
124 {
125 L(FL("revision '%s' already exists in db") % ident);
126 return;
127 }
128
129 revision_t rev;
130 MM(rev);
131 read_revision(dat, rev);
132
133 for (edge_map::const_iterator i = rev.edges.begin();
134 i != rev.edges.end(); ++i)
135 {
136 if (!edge_old_revision(i).inner()().empty()
137 && !app.db.revision_exists(edge_old_revision(i)))
138 {
139 W(F("missing prerequisite revision '%s'") % edge_old_revision(i));
140 W(F("dropping revision '%s'") % ident);
141 return;
142 }
143
144 for (map<split_path, file_id>::const_iterator a
145 = edge_changes(i).files_added.begin();
146 a != edge_changes(i).files_added.end(); ++a)
147 {
148 if (! app.db.file_version_exists(a->second))
149 {
150 W(F("missing prerequisite file '%s'") % a->second);
151 W(F("dropping revision '%s'") % ident);
152 return;
153 }
154 }
155
156 for (map<split_path, pair<file_id, file_id> >::const_iterator d
157 = edge_changes(i).deltas_applied.begin();
158 d != edge_changes(i).deltas_applied.end(); ++d)
159 {
160 I(!delta_entry_src(d).inner()().empty());
161 I(!delta_entry_dst(d).inner()().empty());
162
163 if (! app.db.file_version_exists(delta_entry_src(d)))
164 {
165 W(F("missing prerequisite file pre-delta '%s'")
166 % delta_entry_src(d));
167 W(F("dropping revision '%s'") % ident);
168 return;
169 }
170
171 if (! app.db.file_version_exists(delta_entry_dst(d)))
172 {
173 W(F("missing prerequisite file post-delta '%s'")
174 % delta_entry_dst(d));
175 W(F("dropping revision '%s'") % ident);
176 return;
177 }
178 }
179 }
180
181 app.db.put_revision(ident, dat);
182 if (on_revision_written)
183 on_revision_written(ident);
184 guard.commit();
185}
186
187void
188packet_db_writer::consume_revision_cert(revision<cert> const & t)
189{
190 transaction_guard guard(app.db);
191
192 if (app.db.revision_cert_exists(t))
193 {
194 L(FL("revision cert on '%s' already exists in db")
195 % t.inner().ident);
196 return;
197 }
198
199 if (!app.db.revision_exists(revision_id(t.inner().ident)))
200 {
201 W(F("cert revision '%s' does not exist in db")
202 % t.inner().ident);
203 W(F("dropping cert"));
204 return;
205 }
206
207 app.db.put_revision_cert(t);
208 if (on_cert_written)
209 on_cert_written(t.inner());
210
211 guard.commit();
212}
213
214
215void
216packet_db_writer::consume_public_key(rsa_keypair_id const & ident,
217 base64< rsa_pub_key > const & k)
218{
219 transaction_guard guard(app.db);
220
221 if (app.db.public_key_exists(ident))
222 {
223 base64<rsa_pub_key> tmp;
224 app.db.get_key(ident, tmp);
225 if (!keys_match(ident, tmp, ident, k))
226 W(F("key '%s' is not equal to key '%s' in database") % ident % ident);
227 L(FL("skipping existing public key %s") % ident);
228 return;
229 }
230
231 L(FL("putting public key %s") % ident);
232 app.db.put_key(ident, k);
233 if (on_pubkey_written)
234 on_pubkey_written(ident);
235
236 guard.commit();
237}
238
239void
240packet_db_writer::consume_key_pair(rsa_keypair_id const & ident,
241 keypair const & kp)
242{
243 transaction_guard guard(app.db);
244
245 if (app.keys.key_pair_exists(ident))
246 {
247 L(FL("skipping existing key pair %s") % ident);
248 return;
249 }
250
251 app.keys.put_key_pair(ident, kp);
252 if (on_keypair_written)
253 on_keypair_written(ident);
254
255 guard.commit();
256}
257
258// --- packet writer ---
259
260packet_writer::packet_writer(ostream & o) : ost(o) {}
261
262void
263packet_writer::consume_file_data(file_id const & ident,
264 file_data const & dat)
265{
266 base64<gzip<data> > packed;
267 pack(dat.inner(), packed);
268 ost << "[fdata " << ident.inner()() << "]" << endl
269 << trim_ws(packed()) << endl
270 << "[end]" << endl;
271}
272
273void
274packet_writer::consume_file_delta(file_id const & old_id,
275 file_id const & new_id,
276 file_delta const & del)
277{
278 base64<gzip<delta> > packed;
279 pack(del.inner(), packed);
280 ost << "[fdelta " << old_id.inner()() << endl
281 << " " << new_id.inner()() << "]" << endl
282 << trim_ws(packed()) << endl
283 << "[end]" << endl;
284}
285
286void
287packet_writer::consume_revision_data(revision_id const & ident,
288 revision_data const & dat)
289{
290 base64<gzip<data> > packed;
291 pack(dat.inner(), packed);
292 ost << "[rdata " << ident.inner()() << "]" << endl
293 << trim_ws(packed()) << endl
294 << "[end]" << endl;
295}
296
297void
298packet_writer::consume_revision_cert(revision<cert> const & t)
299{
300 ost << "[rcert " << t.inner().ident() << endl
301 << " " << t.inner().name() << endl
302 << " " << t.inner().key() << endl
303 << " " << trim_ws(t.inner().value()) << "]" << endl
304 << trim_ws(t.inner().sig()) << endl
305 << "[end]" << endl;
306}
307
308void
309packet_writer::consume_public_key(rsa_keypair_id const & ident,
310 base64< rsa_pub_key > const & k)
311{
312 ost << "[pubkey " << ident() << "]" << endl
313 << trim_ws(k()) << endl
314 << "[end]" << endl;
315}
316
317void
318packet_writer::consume_key_pair(rsa_keypair_id const & ident,
319 keypair const & kp)
320{
321 ost << "[keypair " << ident() << "]" << endl
322 << trim_ws(kp.pub()) <<"#\n" <<trim_ws(kp.priv()) << endl
323 << "[end]" << endl;
324}
325
326
327// -- remainder just deals with the regexes for reading packets off streams
328
329struct
330feed_packet_consumer
331{
332 app_state & app;
333 size_t & count;
334 packet_consumer & cons;
335 string ident;
336 string key;
337 string certname;
338 string base;
339 string sp;
340 feed_packet_consumer(size_t & count, packet_consumer & c, app_state & app_)
341 : app(app_), count(count), cons(c),
342 ident(constants::regex_legal_id_bytes),
343 key(constants::regex_legal_key_name_bytes),
344 certname(constants::regex_legal_cert_name_bytes),
345 base(constants::regex_legal_packet_bytes),
346 sp("[[:space:]]+")
347 {}
348 void require(bool x) const
349 {
350 E(x, F("malformed packet"));
351 }
352 bool operator()(match_results<string::const_iterator> const & res) const
353 {
354 if (res.size() != 4)
355 throw oops("matched impossible packet with "
356 + lexical_cast<string>(res.size()) + " matching parts: " +
357 string(res[0].first, res[0].second));
358 I(res[1].matched);
359 I(res[2].matched);
360 I(res[3].matched);
361 string type(res[1].first, res[1].second);
362 string args(res[2].first, res[2].second);
363 string body(res[3].first, res[3].second);
364 if (regex_match(type, regex("[fr]data")))
365 {
366 L(FL("read data packet"));
367 require(regex_match(args, regex(ident)));
368 require(regex_match(body, regex(base)));
369 base64<gzip<data> > body_packed(trim_ws(body));
370 data contents;
371 unpack(body_packed, contents);
372 if (type == "rdata")
373 cons.consume_revision_data(revision_id(hexenc<id>(args)),
374 revision_data(contents));
375 else if (type == "fdata")
376 cons.consume_file_data(file_id(hexenc<id>(args)),
377 file_data(contents));
378 else
379 throw oops("matched impossible data packet with head '" + type + "'");
380 }
381 else if (type == "fdelta")
382 {
383 L(FL("read delta packet"));
384 match_results<string::const_iterator> matches;
385 require(regex_match(args, matches, regex(ident + sp + ident)));
386 string src_id(matches[1].first, matches[1].second);
387 string dst_id(matches[2].first, matches[2].second);
388 require(regex_match(body, regex(base)));
389 base64<gzip<delta> > body_packed(trim_ws(body));
390 delta contents;
391 unpack(body_packed, contents);
392 cons.consume_file_delta(file_id(hexenc<id>(src_id)),
393 file_id(hexenc<id>(dst_id)),
394 file_delta(contents));
395 }
396 else if (type == "rcert")
397 {
398 L(FL("read cert packet"));
399 match_results<string::const_iterator> matches;
400 require(regex_match(args, matches, regex(ident + sp + certname
401 + sp + key + sp + base)));
402 string certid(matches[1].first, matches[1].second);
403 string name(matches[2].first, matches[2].second);
404 string keyid(matches[3].first, matches[3].second);
405 string val(matches[4].first, matches[4].second);
406 string contents(trim_ws(body));
407
408 // canonicalize the base64 encodings to permit searches
409 cert t = cert(hexenc<id>(certid),
410 cert_name(name),
411 base64<cert_value>(canonical_base64(val)),
412 rsa_keypair_id(keyid),
413 base64<rsa_sha1_signature>(canonical_base64(contents)));
414 cons.consume_revision_cert(revision<cert>(t));
415 }
416 else if (type == "pubkey")
417 {
418 L(FL("read pubkey data packet"));
419 require(regex_match(args, regex(key)));
420 require(regex_match(body, regex(base)));
421 string contents(trim_ws(body));
422 cons.consume_public_key(rsa_keypair_id(args),
423 base64<rsa_pub_key>(contents));
424 }
425 else if (type == "keypair")
426 {
427 L(FL("read keypair data packet"));
428 require(regex_match(args, regex(key)));
429 match_results<string::const_iterator> matches;
430 require(regex_match(body, matches, regex(base + "#" + base)));
431 string pub_dat(trim_ws(string(matches[1].first, matches[1].second)));
432 string priv_dat(trim_ws(string(matches[2].first, matches[2].second)));
433 cons.consume_key_pair(rsa_keypair_id(args), keypair(pub_dat, priv_dat));
434 }
435 else if (type == "privkey")
436 {
437 L(FL("read pubkey data packet"));
438 require(regex_match(args, regex(key)));
439 require(regex_match(body, regex(base)));
440 string contents(trim_ws(body));
441 keypair kp;
442 migrate_private_key(app,
443 rsa_keypair_id(args),
444 base64<arc4<rsa_priv_key> >(contents),
445 kp);
446 cons.consume_key_pair(rsa_keypair_id(args), kp);
447 }
448 else
449 {
450 W(F("unknown packet type: '%s'") % type);
451 return true;
452 }
453 ++count;
454 return true;
455 }
456};
457
458static size_t
459extract_packets(string const & s, packet_consumer & cons, app_state & app)
460{
461 string const head("\\[([a-z]+)[[:space:]]+([^\\[\\]]+)\\]");
462 string const body("([^\\[\\]]+)");
463 string const tail("\\[end\\]");
464 string const whole = head + body + tail;
465 regex expr(whole);
466 size_t count = 0;
467 regex_grep(feed_packet_consumer(count, cons, app), s, expr, match_default);
468 return count;
469}
470
471
472size_t
473read_packets(istream & in, packet_consumer & cons, app_state & app)
474{
475 string accum, tmp;
476 size_t count = 0;
477 size_t const bufsz = 0xff;
478 char buf[bufsz];
479 string const end("[end]");
480 while(in)
481 {
482 in.read(buf, bufsz);
483 accum.append(buf, in.gcount());
484 string::size_type endpos = string::npos;
485 endpos = accum.rfind(end);
486 if (endpos != string::npos)
487 {
488 endpos += end.size();
489 string tmp = accum.substr(0, endpos);
490 count += extract_packets(tmp, cons, app);
491 if (endpos < accum.size() - 1)
492 accum = accum.substr(endpos+1);
493 else
494 accum.clear();
495 }
496 }
497 return count;
498}
499
500
501#ifdef BUILD_UNIT_TESTS
502#include "unit_tests.hh"
503#include "transforms.hh"
504
505using std::istringstream;
506using std::ostringstream;
507
508static void
509packet_roundabout_test()
510{
511 string tmp;
512
513 {
514 ostringstream oss;
515 packet_writer pw(oss);
516
517 // an fdata packet
518 file_data fdata(data("this is some file data"));
519 file_id fid;
520 calculate_ident(fdata, fid);
521 pw.consume_file_data(fid, fdata);
522
523 // an fdelta packet
524 file_data fdata2(data("this is some file data which is not the same as the first one"));
525 file_id fid2;
526 calculate_ident(fdata2, fid2);
527 delta del;
528 diff(fdata.inner(), fdata2.inner(), del);
529 pw.consume_file_delta(fid, fid2, file_delta(del));
530
531 // a rdata packet
532 revision_t rev;
533 rev.new_manifest = manifest_id(string("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"));
534 split_path sp;
535 file_path_internal("").split(sp);
536 shared_ptr<cset> cs(new cset);
537 cs->dirs_added.insert(sp);
538 rev.edges.insert(make_pair(revision_id(string("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")),
539 cs));
540 revision_data rdat;
541 write_revision(rev, rdat);
542 revision_id rid;
543 calculate_ident(rdat, rid);
544 pw.consume_revision_data(rid, rdat);
545
546 // a cert packet
547 base64<cert_value> val;
548 encode_base64(cert_value("peaches"), val);
549 base64<rsa_sha1_signature> sig;
550 encode_base64(rsa_sha1_signature("blah blah there is no way this is a valid signature"), sig);
551 // should be a type violation to use a file id here instead of a revision
552 // id, but no-one checks...
553 cert c(fid.inner(), cert_name("smell"), val,
554 rsa_keypair_id("fun@moonman.com"), sig);
555 pw.consume_revision_cert(revision<cert>(c));
556
557 keypair kp;
558 // a public key packet
559 encode_base64(rsa_pub_key("this is not a real rsa key"), kp.pub);
560 pw.consume_public_key(rsa_keypair_id("test@lala.com"), kp.pub);
561
562 // a keypair packet
563 encode_base64(rsa_priv_key("this is not a real rsa key either!"), kp.priv);
564
565 pw.consume_key_pair(rsa_keypair_id("test@lala.com"), kp);
566
567 tmp = oss.str();
568 }
569
570 // read_packets needs this to convert privkeys to keypairs.
571 // This doesn't test privkey packets (theres a tests/ test for that),
572 // so we don't actually use the app_state for anything. So a default one
573 // is ok.
574 app_state aaa;
575 for (int i = 0; i < 10; ++i)
576 {
577 // now spin around sending and receiving this a few times
578 ostringstream oss;
579 packet_writer pw(oss);
580 istringstream iss(tmp);
581 read_packets(iss, pw, aaa);
582 BOOST_CHECK(oss.str() == tmp);
583 tmp = oss.str();
584 }
585}
586
587void
588add_packet_tests(test_suite * suite)
589{
590 I(suite);
591 suite->add(BOOST_TEST_CASE(&packet_roundabout_test));
592}
593
594#endif // BUILD_UNIT_TESTS
595
596// Local Variables:
597// mode: C++
598// fill-column: 76
599// c-file-style: "gnu"
600// indent-tabs-mode: nil
601// End:
602// vim: et:sw=2:sts=2:ts=2:cino=>2s,{s,\:s,+s,t0,g0,^-2,e-2,n-2,p2s,(0,=s:

Archive Download this file

Branches

Tags

Quick Links:     www.monotone.ca    -     Downloads    -     Documentation    -     Wiki    -     Code Forge    -     Build Status