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