monotone

monotone Mtn Source Tree

Root/proto_machine.cc

1// copyright (C) 2002, 2003 graydon hoare <graydon@pobox.com>
2// all rights reserved.
3// licensed to the public under the terms of the GNU GPL (>= 2)
4// see the file COPYING for details
5
6#include <string>
7#include <iostream>
8#include <vector>
9#include <sstream>
10#include <algorithm>
11#include <iterator>
12
13#include <boost/lexical_cast.hpp>
14#include <boost/tokenizer.hpp>
15
16#include "proto_machine.hh"
17#include "sanity.hh"
18
19// this file describes the interface to a network stream
20// in terms of NNTP / SMTP protocol state machines.
21
22using namespace std;
23
24// lowest level is a bunch of string-encoding functions
25
26string const linesep("\r\n");
27
28struct dot_escape {
29 typedef string result_type;
30 string operator()(string const & x) const {
31 if (x.size() > 0 && x.at(0) == '.')
32 return '.' + x;
33 return x;
34 }
35};
36
37struct dot_unescape {
38 typedef string result_type;
39 string operator()(string const & x) const {
40 if (x.size() > 0 && x.at(0) == '.')
41 return x.substr(1);
42 return x;
43 }
44};
45
46template <typename T>
47struct builder {
48 typedef T result_type;
49 T operator()(string const & x) const {
50 return T(x);
51 }
52};
53
54typedef vector<string>::const_iterator lines_iter;
55typedef boost::transform_iterator_generator <dot_escape, lines_iter>::type dot_escaper;
56typedef boost::transform_iterator_generator <dot_unescape, lines_iter>::type dot_unescaper;
57
58template <typename XFM, typename IN, typename OUT_ITER>
59void transform(IN const & in, OUT_ITER out)
60{
61 copy(XFM(in.begin()), XFM(in.end()), out);
62}
63
64template<typename T, typename SEQ, typename OUT_ITER>
65void interleave(SEQ const & in,
66T const & sep,
67OUT_ITER out)
68{
69 typedef typename SEQ::const_iterator iter;
70 if (in.size() < 1)
71 return;
72 iter i = in.begin();
73 assert(i != in.end());
74 *out = *i; ++out;
75 ++i;
76 for(;i != in.end(); ++i) {
77 *out = sep; ++out;
78 *out = *i; ++out;
79 }
80}
81
82// next layer is concerned with composing and receiving protocol messages
83// of the most basic "command-and-args", "code-and-response", and
84// "line-set-with-dot" forms
85
86void write_command(ostream & out,
87 string const & cmd,
88 vector<string> const & args)
89{
90 out << cmd;
91 if (args.size() > 0)
92 {
93 out << ' ';
94 interleave(args, string(", "), ostream_iterator<string>(out));
95 }
96 out << linesep;
97 out.flush();
98}
99
100void write_lines(ostream & out,
101 vector<string> const & lines)
102{
103 transform<dot_escaper>(lines, ostream_iterator<string>(out, linesep.c_str()));
104 out << '.' << linesep;
105 out.flush();
106}
107
108void read_line(istream & in, string & result)
109{
110 ostringstream oss;
111 in.get(*oss.rdbuf(), '\n');
112
113 if (in.good())
114 in.ignore(1);
115
116 result = oss.str();
117 if (result.size() > 0 &&
118 result.at(result.size() - 1) == '\r')
119 result.resize(result.size() - 1);
120}
121
122void read_lines(istream & in,
123vector<string> & result)
124{
125 string tmp("");
126 vector<string> tvec;
127 while(in)
128 {
129 read_line(in,tmp);
130 if (tmp == ".")
131break;
132 tvec.push_back(tmp);
133 }
134 if (tmp != ".")
135 throw oops("stream closed before '.' terminating body response. last line was '" + tmp + "'");
136 result.reserve(tvec.size());
137 transform<dot_unescaper>(tvec, back_inserter(result));
138}
139
140
141void read_status_response(istream & in,
142 int & code,
143 string & result)
144{
145 string tmp;
146 read_line(in, tmp);
147 L(F("RECV <- %s\n") % tmp);
148
149 stringstream ss(tmp);
150
151 if (ss >> code)
152 result = tmp.substr(ss.gcount());
153 else
154 throw oops(string("non-numeric beginning of command response line: '") + tmp + "'");
155}
156
157
158// next layer are protocol-state objects you can wire together into state machines
159
160proto_edge::proto_edge(proto_state * t, int c, string const & m,
161 vector<string> const & l) :
162 targ(t), code(c), msg(m), lines(l)
163{}
164
165
166proto_edge proto_state::handle_response(istream & net)
167{
168
169 string res;
170 read_status_response(net, res_code, res);
171
172 vector<string> res_lines;
173
174 // we might end...
175 if (codes.find(res_code) == codes.end())
176 {
177 return proto_edge(NULL, res_code, res, res_lines);
178 }
179
180 // or we might want a message...
181 if (codes[res_code].first)
182 {
183 read_lines(net, res_lines);
184 L(F("RECV <- %d lines\n") % res_lines.size());
185 }
186
187 // and, in any event, we're at an edge!
188 return proto_edge(codes[res_code].second, res_code, res, res_lines);
189}
190
191
192proto_edge proto_state::step_lines(iostream & net,
193 vector<string> const & send_lines)
194{
195 if (send_lines.size() > 0)
196 {
197 write_lines(net, send_lines);
198 L(F("SEND -> %d lines\n") % send_lines.size());
199 }
200 return handle_response(net);
201}
202
203proto_edge proto_state::step_cmd(iostream & net,
204 string const & cmd,
205 vector<string> const & args)
206{
207 write_command(net, string(cmd), args);
208 stringstream ss;
209 write_command(ss, string(cmd), args);
210 L(F("SEND -> %s") % ss.str());
211 net.flush();
212 return handle_response(net);
213}
214
215void proto_state::add_edge(int code, proto_state * targ, bool read_lines)
216{
217 codes[code] = pair<bool,proto_state *>(read_lines, targ);
218}
219
220proto_state::~proto_state() {}
221
222
223cmd_state::cmd_state(string const & c) :
224 cmd(c)
225{}
226
227cmd_state::cmd_state(string const & c,
228 string const & arg1) :
229 cmd(c)
230{
231 args.push_back(arg1);
232}
233
234cmd_state::cmd_state(string const & c,
235 string const & arg1,
236 string const & arg2)
237{
238 args.push_back(arg1);
239 args.push_back(arg2);
240}
241
242proto_edge cmd_state::drive(iostream & net,
243 proto_edge const & e)
244{
245 return step_cmd(net, cmd, args);
246}
247
248cmd_state::~cmd_state() {}
249
250void run_proto_state_machine(proto_state * machine,
251 iostream & link)
252{
253
254 if (!machine)
255 throw oops("null protocol state machine given");
256
257 string res;
258 int res_code;
259 vector<string> no_lines;
260
261 // NNTP / SMTP sessions start with a greet from their end
262 read_status_response(link, res_code, res);
263
264 proto_edge edge(machine, res_code, res, no_lines);
265 while(edge.targ != NULL)
266 edge = edge.targ->drive(link, edge);
267
268}
269

Archive Download this file

Branches

Tags

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