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 "base.hh"
11#include <sstream>
12
13#include "app_state.hh"
14#include "cset.hh"
15#include "constants.hh"
16#include "packet.hh"
17#include "revision.hh"
18#include "sanity.hh"
19#include "transforms.hh"
20#include "simplestring_xform.hh"
21#include "keys.hh"
22#include "cert.hh"
23
24using std::istream;
25using std::istringstream;
26using std::make_pair;
27using std::map;
28using std::ostream;
29using std::pair;
30using std::string;
31
32using boost::shared_ptr;
33
34// --- packet writer ---
35
36packet_writer::packet_writer(ostream & o) : ost(o) {}
37
38void
39packet_writer::consume_file_data(file_id const & ident,
40 file_data const & dat)
41{
42 base64<gzip<data> > packed;
43 pack(dat.inner(), packed);
44 ost << "[fdata " << ident.inner()() << "]\n"
45 << trim_ws(packed()) << '\n'
46 << "[end]\n";
47}
48
49void
50packet_writer::consume_file_delta(file_id const & old_id,
51 file_id const & new_id,
52 file_delta const & del)
53{
54 base64<gzip<delta> > packed;
55 pack(del.inner(), packed);
56 ost << "[fdelta " << old_id.inner()() << '\n'
57 << " " << new_id.inner()() << "]\n"
58 << trim_ws(packed()) << '\n'
59 << "[end]\n";
60}
61
62void
63packet_writer::consume_revision_data(revision_id const & ident,
64 revision_data const & dat)
65{
66 base64<gzip<data> > packed;
67 pack(dat.inner(), packed);
68 ost << "[rdata " << ident.inner()() << "]\n"
69 << trim_ws(packed()) << '\n'
70 << "[end]\n";
71}
72
73void
74packet_writer::consume_revision_cert(revision<cert> const & t)
75{
76 ost << "[rcert " << t.inner().ident() << '\n'
77 << " " << t.inner().name() << '\n'
78 << " " << t.inner().key() << '\n'
79 << " " << trim_ws(t.inner().value()) << "]\n"
80 << trim_ws(t.inner().sig()) << '\n'
81 << "[end]\n";
82}
83
84void
85packet_writer::consume_public_key(rsa_keypair_id const & ident,
86 base64< rsa_pub_key > const & k)
87{
88 ost << "[pubkey " << ident() << "]\n"
89 << trim_ws(k()) << '\n'
90 << "[end]\n";
91}
92
93void
94packet_writer::consume_key_pair(rsa_keypair_id const & ident,
95 keypair const & kp)
96{
97 ost << "[keypair " << ident() << "]\n"
98 << trim_ws(kp.pub()) <<"#\n" <<trim_ws(kp.priv()) << '\n'
99 << "[end]\n";
100}
101
102
103// -- remainder just deals with the regexes for reading packets off streams
104//
105// Note: If these change, the character sets in constants.cc may need to
106// change too.
107
108struct
109feed_packet_consumer
110{
111 app_state & app;
112 size_t & count;
113 packet_consumer & cons;
114 feed_packet_consumer(size_t & count, packet_consumer & c, app_state & app_)
115 : app(app_), count(count), cons(c)
116 {}
117 void validate_id(string const & id) const
118 {
119 E(id.size() == constants::idlen
120 && id.find_first_not_of(constants::legal_id_bytes) == string::npos,
121 F("malformed packet: invalid identifier"));
122 }
123 void validate_base64(string const & s) const
124 {
125 E(s.size() > 0
126 && s.find_first_not_of(constants::legal_base64_bytes) == string::npos,
127 F("malformed packet: invalid base64 block"));
128 }
129 void validate_key(string const & k) const
130 {
131 E(k.size() > 0
132 && k.find_first_not_of(constants::legal_key_name_bytes) == string::npos,
133 F("malformed packet: invalid key name"));
134 }
135 void validate_certname(string const & cn) const
136 {
137 E(cn.size() > 0
138 && cn.find_first_not_of(constants::legal_cert_name_bytes) == string::npos,
139 F("malformed packet: invalid cert name"));
140 }
141 void validate_no_more_args(istringstream & iss) const
142 {
143 string next;
144 iss >> next;
145 E(next.size() == 0,
146 F("malformed packet: too many arguments in header"));
147 }
148
149 void data_packet(string const & args, string const & body,
150 bool is_revision) const
151 {
152 L(FL("read %s data packet") % (is_revision ? "revision" : "file"));
153 validate_id(args);
154 validate_base64(body);
155
156 data contents;
157 unpack(base64<gzip<data> >(body), contents);
158 if (is_revision)
159 cons.consume_revision_data(revision_id(hexenc<id>(args)),
160 revision_data(contents));
161 else
162 cons.consume_file_data(file_id(hexenc<id>(args)),
163 file_data(contents));
164 }
165
166 void fdelta_packet(string const & args, string const & body) const
167 {
168 L(FL("read delta packet"));
169 istringstream iss(args);
170 string src_id; iss >> src_id; validate_id(src_id);
171 string dst_id; iss >> dst_id; validate_id(dst_id);
172 validate_no_more_args(iss);
173 validate_base64(body);
174
175 delta contents;
176 unpack(base64<gzip<delta> >(body), contents);
177 cons.consume_file_delta(file_id(hexenc<id>(src_id)),
178 file_id(hexenc<id>(dst_id)),
179 file_delta(contents));
180 }
181
182 void rcert_packet(string const & args, string const & body) const
183 {
184 L(FL("read cert packet"));
185 istringstream iss(args);
186 string certid; iss >> certid; validate_id(certid);
187 string name; iss >> name; validate_certname(name);
188 string keyid; iss >> keyid; validate_key(keyid);
189 string val; iss >> val; validate_base64(val);
190 validate_no_more_args(iss);
191 validate_base64(body);
192 // canonicalize the base64 encodings to permit searches
193 cert t = cert(hexenc<id>(certid),
194 cert_name(name),
195 base64<cert_value>(canonical_base64(val)),
196 rsa_keypair_id(keyid),
197 base64<rsa_sha1_signature>(canonical_base64(body)));
198 cons.consume_revision_cert(revision<cert>(t));
199 }
200
201 void pubkey_packet(string const & args, string const & body) const
202 {
203 L(FL("read pubkey packet"));
204 validate_key(args);
205 validate_base64(body);
206
207 cons.consume_public_key(rsa_keypair_id(args),
208 base64<rsa_pub_key>(body));
209 }
210
211 void keypair_packet(string const & args, string const & body) const
212 {
213 L(FL("read keypair packet"));
214 string::size_type hashpos = body.find('#');
215 string pub(body, 0, hashpos);
216 string priv(body, hashpos+1);
217
218 validate_key(args);
219 validate_base64(pub);
220 validate_base64(priv);
221 cons.consume_key_pair(rsa_keypair_id(args),
222 keypair(base64<rsa_pub_key>(pub),
223 base64<rsa_priv_key>(priv)));
224 }
225
226 void privkey_packet(string const & args, string const & body) const
227 {
228 L(FL("read privkey packet"));
229 validate_key(args);
230 validate_base64(body);
231 keypair kp;
232 migrate_private_key(app,
233 rsa_keypair_id(args),
234 base64<arc4<rsa_priv_key> >(body),
235 kp);
236 cons.consume_key_pair(rsa_keypair_id(args), kp);
237 }
238
239 void operator()(string const & type,
240 string const & args,
241 string const & body) const
242 {
243 if (type == "rdata")
244 data_packet(args, body, true);
245 if (type == "fdata")
246 data_packet(args, body, false);
247 else if (type == "fdelta")
248 fdelta_packet(args, body);
249 else if (type == "rcert")
250 rcert_packet(args, body);
251 else if (type == "pubkey")
252 pubkey_packet(args, body);
253 else if (type == "keypair")
254 keypair_packet(args, body);
255 else if (type == "privkey")
256 privkey_packet(args, body);
257 else
258 {
259 W(F("unknown packet type: '%s'") % type);
260 return;
261 }
262 ++count;
263 }
264};
265
266static size_t
267extract_packets(string const & s, packet_consumer & cons, app_state & app)
268{
269 size_t count = 0;
270 feed_packet_consumer feeder(count, cons, app);
271
272 string::const_iterator p, tbeg, tend, abeg, aend, bbeg, bend;
273
274 enum extract_state {
275 skipping, open_bracket, scanning_type, found_type,
276 scanning_args, found_args, scanning_body,
277 end_1, end_2, end_3, end_4, end_5
278 } state = skipping;
279
280 for (p = s.begin(); p != s.end(); p++)
281 switch (state)
282 {
283 case skipping: if (*p == '[') state = open_bracket; break;
284 case open_bracket:
285 if (is_alpha (*p))
286 state = scanning_type;
287 else
288 state = skipping;
289 tbeg = p;
290 break;
291 case scanning_type:
292 if (!is_alpha (*p))
293 {
294 state = is_space(*p) ? found_type : skipping;
295 tend = p;
296 }
297 break;
298 case found_type:
299 if (!is_space (*p))
300 {
301 state = (*p != ']') ? scanning_args : skipping;
302 abeg = p;
303 }
304 break;
305 case scanning_args:
306 if (*p == ']')
307 {
308 state = found_args;
309 aend = p;
310 }
311 break;
312 case found_args:
313 state = (*p != '[' && *p != ']') ? scanning_body : skipping;
314 bbeg = p;
315 break;
316 case scanning_body:
317 if (*p == '[')
318 {
319 state = end_1;
320 bend = p;
321 }
322 else if (*p == ']')
323 state = skipping;
324 break;
325
326 case end_1: state = (*p == 'e') ? end_2 : skipping; break;
327 case end_2: state = (*p == 'n') ? end_3 : skipping; break;
328 case end_3: state = (*p == 'd') ? end_4 : skipping; break;
329 case end_4:
330 if (*p == ']')
331 feeder(string(tbeg, tend), string(abeg, aend), string(bbeg, bend));
332 state = skipping;
333 break;
334 default:
335 I(false);
336 }
337 return count;
338}
339
340size_t
341read_packets(istream & in, packet_consumer & cons, app_state & app)
342{
343 string accum, tmp;
344 size_t count = 0;
345 size_t const bufsz = 0xff;
346 char buf[bufsz];
347 static string const end("[end]");
348 while(in)
349 {
350 in.read(buf, bufsz);
351 accum.append(buf, in.gcount());
352 string::size_type endpos = string::npos;
353 endpos = accum.rfind(end);
354 if (endpos != string::npos)
355 {
356 endpos += end.size();
357 string tmp = accum.substr(0, endpos);
358 count += extract_packets(tmp, cons, app);
359 if (endpos < accum.size() - 1)
360 accum = accum.substr(endpos+1);
361 else
362 accum.clear();
363 }
364 }
365 return count;
366}
367
368
369#ifdef BUILD_UNIT_TESTS
370#include "unit_tests.hh"
371#include "transforms.hh"
372
373using std::ostringstream;
374
375UNIT_TEST(packet, validators)
376{
377 ostringstream oss;
378 packet_writer pw(oss);
379 app_state app;
380 size_t count;
381 feed_packet_consumer f(count, pw, app);
382
383#define N_THROW(expr) UNIT_TEST_CHECK_NOT_THROW(expr, informative_failure)
384#define Y_THROW(expr) UNIT_TEST_CHECK_THROW(expr, informative_failure)
385
386 // validate_id
387 N_THROW(f.validate_id("5d7005fadff386039a8d066684d22d369c1e6c94"));
388 Y_THROW(f.validate_id(""));
389 Y_THROW(f.validate_id("5d7005fadff386039a8d066684d22d369c1e6c9"));
390 for (int i = 1; i < std::numeric_limits<unsigned char>::max(); i++)
391 if (!((i >= '0' && i <= '9')
392 || (i >= 'a' && i <= 'f')))
393 Y_THROW(f.validate_id(string("5d7005fadff386039a8d066684d22d369c1e6c9")
394 + char(i)));
395
396 // validate_base64
397 N_THROW(f.validate_base64("YmwK"));
398 N_THROW(f.validate_base64(" Y m x h a A o = "));
399 N_THROW(f.validate_base64("ABCD EFGH IJKL MNOP QRST UVWX YZ"
400 "abcd efgh ijkl mnop qrst uvwx yz"
401 "0123 4567 89/+ z\t=\r=\n="));
402
403 Y_THROW(f.validate_base64(""));
404 Y_THROW(f.validate_base64("!@#$"));
405
406 // validate_key
407 N_THROW(f.validate_key("graydon@venge.net"));
408 N_THROW(f.validate_key("dscherger+mtn"));
409 N_THROW(f.validate_key("ABCDEFGHIJKLMNOPQRSTUVWXYZ"
410 "abcdefghijklmnopqrstuvwxyz"
411 "0123456789-.@+_"));
412 Y_THROW(f.validate_key(""));
413 Y_THROW(f.validate_key("graydon at venge dot net"));
414
415 // validate_certname
416 N_THROW(f.validate_certname("graydon-at-venge-dot-net"));
417 N_THROW(f.validate_certname("ABCDEFGHIJKLMNOPQRSTUVWXYZ"
418 "abcdefghijklmnopqrstuvwxyz"
419 "0123456789-"));
420
421 Y_THROW(f.validate_certname(""));
422 Y_THROW(f.validate_certname("graydon@venge.net"));
423 Y_THROW(f.validate_certname("graydon at venge dot net"));
424
425 // validate_no_more_args
426 {
427 istringstream iss("a b");
428 string a; iss >> a; UNIT_TEST_CHECK(a == "a");
429 string b; iss >> b; UNIT_TEST_CHECK(b == "b");
430 N_THROW(f.validate_no_more_args(iss));
431 }
432 {
433 istringstream iss("a ");
434 string a; iss >> a; UNIT_TEST_CHECK(a == "a");
435 N_THROW(f.validate_no_more_args(iss));
436 }
437 {
438 istringstream iss("a b");
439 string a; iss >> a; UNIT_TEST_CHECK(a == "a");
440 Y_THROW(f.validate_no_more_args(iss));
441 }
442}
443
444UNIT_TEST(packet, roundabout)
445{
446 string tmp;
447
448 {
449 ostringstream oss;
450 packet_writer pw(oss);
451
452 // an fdata packet
453 file_data fdata(data("this is some file data"));
454 file_id fid;
455 calculate_ident(fdata, fid);
456 pw.consume_file_data(fid, fdata);
457
458 // an fdelta packet
459 file_data fdata2(data("this is some file data which is not the same as the first one"));
460 file_id fid2;
461 calculate_ident(fdata2, fid2);
462 delta del;
463 diff(fdata.inner(), fdata2.inner(), del);
464 pw.consume_file_delta(fid, fid2, file_delta(del));
465
466 // a rdata packet
467 revision_t rev;
468 rev.new_manifest = manifest_id(string("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"));
469 shared_ptr<cset> cs(new cset);
470 cs->dirs_added.insert(file_path_internal(""));
471 rev.edges.insert(make_pair(revision_id(string("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")),
472 cs));
473 revision_data rdat;
474 write_revision(rev, rdat);
475 revision_id rid;
476 calculate_ident(rdat, rid);
477 pw.consume_revision_data(rid, rdat);
478
479 // a cert packet
480 base64<cert_value> val;
481 encode_base64(cert_value("peaches"), val);
482 base64<rsa_sha1_signature> sig;
483 encode_base64(rsa_sha1_signature("blah blah there is no way this is a valid signature"), sig);
484 // should be a type violation to use a file id here instead of a revision
485 // id, but no-one checks...
486 cert c(fid.inner(), cert_name("smell"), val,
487 rsa_keypair_id("fun@moonman.com"), sig);
488 pw.consume_revision_cert(revision<cert>(c));
489
490 keypair kp;
491 // a public key packet
492 encode_base64(rsa_pub_key("this is not a real rsa key"), kp.pub);
493 pw.consume_public_key(rsa_keypair_id("test@lala.com"), kp.pub);
494
495 // a keypair packet
496 encode_base64(rsa_priv_key("this is not a real rsa key either!"), kp.priv);
497
498 pw.consume_key_pair(rsa_keypair_id("test@lala.com"), kp);
499
500 tmp = oss.str();
501 }
502
503 // read_packets needs this to convert privkeys to keypairs.
504 // This doesn't test privkey packets (theres a tests/ test for that),
505 // so we don't actually use the app_state for anything. So a default one
506 // is ok.
507 app_state aaa;
508 for (int i = 0; i < 10; ++i)
509 {
510 // now spin around sending and receiving this a few times
511 ostringstream oss;
512 packet_writer pw(oss);
513 istringstream iss(tmp);
514 read_packets(iss, pw, aaa);
515 UNIT_TEST_CHECK(oss.str() == tmp);
516 tmp = oss.str();
517 }
518}
519
520#endif // BUILD_UNIT_TESTS
521
522// Local Variables:
523// mode: C++
524// fill-column: 76
525// c-file-style: "gnu"
526// indent-tabs-mode: nil
527// End:
528// 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