/*
* csclient.cc
* Douglas S. J. De Couto
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include <stdio.h>
#include <ctype.h>
#include <algorithm>
#include "csclient.hh"
#define INCLUDE_TEST_CODE 1
#if INCLUDE_TEST_CODE
#include <iostream>
#endif
#define check_init() do { if (!_init) return init_err; } while (false);
ControlSocketClient::err_t
ControlSocketClient::configure(unsigned int host_ip, unsigned short port)
{
if (_init)
return reinit_err;
_host = host_ip;
_port = port;
_fd = socket(PF_INET, SOCK_STREAM, 0);
if (_fd < 0)
return sys_err;
/*
* connect to remote ControlSocket
*/
struct sockaddr_in sa;
memset(&sa, 0, sizeof(sa));
sa.sin_family = AF_INET;
sa.sin_addr.s_addr = _host;
sa.sin_port = htons(port);
char namebuf[32];
snprintf(namebuf, 32, "%u.%u.%u.%u:%hu",
(_host & 0xff) >> 0,
(_host & 0xff00) >> 8,
(_host & 0xff0000) >> 16,
(_host & 0xff000000) >> 24,
port);
_name = namebuf;
int res = connect(_fd, (struct sockaddr *) &sa, sizeof(sa));
if (res < 0) {
int save_errno = errno;
::close(_fd);
errno = save_errno;
return sys_err;
}
int major, minor;
unsigned int slash, dot;
/*
* check that we get the expected banner
*/
string buf;
err_t err = readline(buf);
if (err != no_err) {
int save_errno = errno;
::close(_fd);
errno = save_errno;
return err;
}
slash = buf.find('/');
dot = (slash != string::npos ? buf.find('.', slash + 1) : string::npos);
if (slash == string::npos || dot == string::npos) {
::close(_fd);
return click_err; /* bad format */
}
/*
* check ControlSocket protocol version
*/
major = atoi(buf.substr(slash + 1, dot - slash - 1).c_str());
minor = atoi(buf.substr(dot + 1, buf.size() - dot - 1).c_str());
if (major != PROTOCOL_MAJOR_VERSION ||
minor < PROTOCOL_MINOR_VERSION) {
::close(_fd);
return click_err; /* wrong version */
}
_init = true;
return no_err;
}
ControlSocketClient::err_t
ControlSocketClient::close()
{
check_init();
_init = false;
int res = ::close(_fd);
if (res < 1)
return sys_err;
else
return no_err;
}
ControlSocketClient::err_t
ControlSocketClient::readline(string &buf)
{
assert(_fd);
#define MAX_LINE_SZ 1024 /* arbitrary... to prevent weirdness */
/*
* keep calling read() to get one character at a time, until we get
* a line. not very ``efficient'', but who cares?
*/
char c = 0;
buf.resize(0);
do {
int res = ::read(_fd, (void *) &c, 1);
if (res < 0)
return sys_err;
if (res != 1)
return sys_err;
buf += c;
if (buf.size() > MAX_LINE_SZ)
return click_err;
}
while (c != '\n');
return no_err;
}
int
ControlSocketClient::get_resp_code(string line)
{
if (line.size() < 3)
return -1;
return atoi(line.substr(0, 3).c_str());
}
int
ControlSocketClient::get_data_len(string line)
{
unsigned int i;
for (i = 0; i < line.size() && !isdigit(line[i]); i++)
; // scan string
if (i == line.size())
return -1;
return atoi(line.substr(i, line.size() - i).c_str());
}
ControlSocketClient::err_t
ControlSocketClient::read(string el, string handler, string &response)
{
check_init();
if (el.size() > 0)
handler = el + "." + handler;
string cmd = "READ " + handler + "\n";
int res = ::write(_fd, cmd.c_str(), cmd.size());
if (res < 0)
return sys_err;
if ((size_t) res != cmd.size())
return sys_err;
string cmd_resp;
string line;
do {
err_t err = readline(line);
if (err != no_err)
return err;
if (line.size() < 4)
return click_err;
cmd_resp += line;
}
while (line[3] == '-');
int code = get_resp_code(line);
if (code != CODE_OK && code != CODE_OK_WARN)
return handle_err_code(code);
res = readline(line);
if (res < 0)
return click_err;
int num = get_data_len(line);
if (num < 0)
return click_err;
response.resize(0);
if (num == 0)
return no_err;
char *buf = new char[num];
int num_read = 0;
while (num_read < num) {
res = ::read(_fd, buf + num_read, num - num_read);
if (res < 0) {
delete[] buf;
return sys_err;
}
num_read += res;
}
response.append(buf, num);
delete[] buf;
return no_err;
}
ControlSocketClient::err_t
ControlSocketClient::read(string el, string handler, char *buf, int &bufsz)
{
string resp;
err_t err = read(el, handler, resp);
if (err != no_err)
return err;
bufsz = min((size_t) bufsz, resp.size());
memcpy(buf, resp.c_str(), bufsz);
if (resp.size() > (size_t) bufsz)
return too_short;
else
return no_err;
}
ControlSocketClient::err_t
ControlSocketClient::write(string el, string handler, const char *buf, int bufsz)
{
check_init();
if (el.size() > 0)
handler = el + "." + handler;
char cbuf[10];
snprintf(cbuf, sizeof(cbuf), "%d", bufsz);
string cmd = "WRITEDATA " + handler + " " + cbuf + "\n";
int res = ::write(_fd, cmd.c_str(), cmd.size());
if (res < 0)
return sys_err;
if ((size_t) res != cmd.size())
return sys_err;
res = ::write(_fd, buf, bufsz);
if (res < 0)
return sys_err;
if (res != bufsz)
return sys_err;
string cmd_resp;
string line;
do {
err_t err = readline(line);
if (err != no_err)
return err;
if (line.size() < 4)
return click_err;
cmd_resp += line;
}
while (line[3] == '-');
int code = get_resp_code(line);
if (code != CODE_OK && code != CODE_OK_WARN)
{
cout << "CCCC " << code << endl;
return handle_err_code(code);
}
return no_err;
}
ControlSocketClient::err_t
ControlSocketClient::write(string el, string handler, string data)
{
return write(el, handler, data.c_str(), data.size());
}
ControlSocketClient::err_t
ControlSocketClient::handle_err_code(int code)
{
switch (code) {
case CODE_SYNTAX_ERR: return click_err; break;
case CODE_UNIMPLEMENTED: return click_err; break;
case CODE_NO_ELEMENT: return no_element; break;
case CODE_NO_HANDLER: return no_handler; break;
case CODE_HANDLER_ERR: return handler_err; break;
case CODE_PERMISSION: return handler_no_perm; break;
default: return click_err; break;
}
return click_err;
}
vector<string>
ControlSocketClient::split(string s, size_t offset, char terminator)
{
vector<string> v;
size_t pos = offset;
size_t len = s.size();
while (pos < len) {
size_t start = pos;
while (pos < len && s[pos] != terminator)
pos++;
if (start < pos || pos < len)
v.push_back(s.substr(start, pos - start));
pos++;
}
return v;
}
ControlSocketClient::err_t
ControlSocketClient::get_config_el_names(vector<string> &els)
{
string resp;
err_t err = read("", "list", resp);
if (err != no_err)
return err;
/* parse how many els */
int i = resp.find('\n');
int num = atoi(resp.substr(0, i).c_str());
els = split(resp, i + 1, '\n');
if (els.size() != (size_t) num)
return handler_bad_format;
return no_err;
}
ControlSocketClient::err_t
ControlSocketClient::get_string_vec(string el, string h, vector<string> &v)
{
string resp;
err_t err = read(el, h, resp);
if (err != no_err)
return err;
v = split(resp, 0, '\n');
return no_err;
}
ControlSocketClient::err_t
ControlSocketClient::get_el_handlers(string el, vector<handler_info_t> &handlers)
{
vector<handler_info_t> v;
vector<string> vh;
string buf;
err_t err = read(el, "handlers", buf);
if (err != no_err)
return err;
vh = split(buf, 0, '\n');
for (vector<string>::iterator i = vh.begin(); i != vh.end(); i++) {
string &s = *i;
size_t j;
for (j = 0; j < s.size() && !isspace(s[j]); j++)
; /* find record split -- don't use s.find because could be any whitespace */
if (j == s.size())
return click_err;
handler_info_t hi;
hi.element_name = el;
hi.handler_name = trim(s.substr(0, j));
while (j < s.size() && isspace(s[j]))
j++;
for ( ; j < s.size(); j++) {
if (tolower(s[j] == 'r'))
hi.can_read = true;
else if (tolower(s[j] == 'w'))
hi.can_write = true;
else if (isspace(s[j]))
break;
}
v.push_back(hi);
}
handlers = v;
return no_err;
}
ControlSocketClient::err_t
ControlSocketClient::check_handler(string el, string h, bool is_write, bool &exists)
{
check_init();
if (el.size() > 0)
h = el + "." + h;
string cmd = (is_write ? "CHECKWRITE " : "CHECKREAD ") + h + "\n";
int res = ::write(_fd, cmd.c_str(), cmd.size());
if (res < 0)
return sys_err;
if ((size_t) res != cmd.size())
return sys_err;
string cmd_resp;
string line;
do {
err_t err = readline(line);
if (err != no_err)
return err;
if (line.size() < 4)
return click_err;
cmd_resp += line;
}
while (line[3] == '-');
int code = get_resp_code(line);
switch (code) {
case CODE_OK:
case CODE_OK_WARN:
exists = true;
return no_err;;
case CODE_NO_ELEMENT:
case CODE_NO_HANDLER:
case CODE_HANDLER_ERR:
case CODE_PERMISSION:
exists = false;
return no_err;
case CODE_UNIMPLEMENTED:
if (el.size() == 0)
return handle_err_code(code); /* no workaround for top-level router handlers */
else
return check_handler_workaround(el, h, is_write, exists);
default:
return handle_err_code(code);
}
}
ControlSocketClient::err_t
ControlSocketClient::check_handler_workaround(string el, string h, bool is_write, bool &exists)
{
/*
* If talking to an old ControlSocket, try the "handlers" handler
* instead.
*/
vector<handler_info_t> v;
err_t err = get_el_handlers(el, v);
if (err != no_err)
return err;
for (vector<handler_info_t>::iterator i = v.begin(); i != v.end(); i++) {
if (i->handler_name == h) {
if (is_write && i->can_write ||
!is_write && i->can_read)
exists = true;
else
exists = false;
return no_err;
}
}
exists = false;
return no_err;
}
string
ControlSocketClient::trim(string s)
{
size_t start, end;
for (start = 0; start < s.size() && isspace(s[start]); start++)
; /* */
for (end = s.size(); end > 0 && isspace(s[end - 1]); end--)
; /* */
if (start >= end)
return "";
return s.substr(start, end - start);
}
#if INCLUDE_TEST_CODE
#define assert_eq(e, v) do { if ((e) != (v)) { cerr << "got " << (e) << endl; } assert((e) == (v)); } while (false)
#define ok(err) assert_eq(err, ControlSocketClient::no_err)
#define test(x, e) do { err_t err = (x); if (err != (e)) { cerr << "wanted " << assert(0) } while (false)
int
main(int argc, char **argv)
{
unsigned short port = 7777;
unsigned long ip;
if (argc > 1)
ip = inet_addr(argv[1]);
else
ip = inet_addr("127.0.0.1");
if (argc > 2)
port = (unsigned short) atoi(argv[2]);
typedef ControlSocketClient csc_t;
csc_t cs;
typedef csc_t::err_t err_t;
err_t err = cs.configure(ip, port);
ok(err);
cout << "Router name: ``" << cs.name() << "''" << endl;
string s;
err = cs.get_router_version(s);
ok(err);
cout << "Router version: ``" << s << "''" << endl;
vector<string> vs;
err = cs.get_config_el_names(vs);
ok(err);
cout << "Elements:" << endl;
for (size_t i = 0; i < vs.size(); i++)
cout << i << " ``" << vs[i] << "''" << endl;
err = cs.get_router_classes(vs);
ok(err);
cout << "Classes:" << endl;
for (size_t i = 0; i < vs.size(); i++)
cout << i << " ``" << vs[i] << "''" << endl;
err = cs.get_router_packages(vs);
ok(err);
cout << "Packages:" << endl;
for (size_t i = 0; i < vs.size(); i++)
cout << i << " ``" << vs[i] << "''" << endl;
err = cs.get_config_reqs(vs);
ok(err);
cout << "Configuration requirements:" << endl;
for (size_t i = 0; i < vs.size(); i++)
cout << i << " ``" << vs[i] << "''" << endl;
err = cs.get_router_config(s);
ok(err);
cout << endl;
cout << "*************** Begin Router Configuration *******************" << endl;
cout << s;
cout << "*************** End Router Configuration *******************" << endl;
err = cs.get_router_flat_config(s);
ok(err);
cout << endl;
cout << "*************** Begin Flattened Router Configuration *******************" << endl;
cout << s;
cout << "*************** End Flattened Router Configuration *******************" << endl;
cout << endl;
cout << "Handler info test: " << endl;
vector<csc_t::handler_info_t> vhi;
/* NB: this test assumes that there are some interesting elements in the configuration */
err = cs.get_config_el_names(vs);
ok(err);
if (vs.size() == 0) {
cout << "No elements to test handler info on, exiting tests early" << endl;
return 0;
}
string el = vs[0];
err = cs.get_el_handlers(el, vhi);
for (size_t i = 0; i < vhi.size(); i++)
cout << vhi[i].element_name << "." << vhi[i].handler_name << "\t"
<< (vhi[i].can_read ? "r" : "")
<< (vhi[i].can_write ? "w" : "")
<< endl;
cout << endl;
cout << "Check Handler test: " << endl;
for (size_t i = 0; i < vhi.size(); i++) {
cout << vhi[i].element_name << "." << vhi[i].handler_name << '\t';
bool res;
/* is writeable? */
err = cs.check_handler(vhi[i].element_name, vhi[i].handler_name,
true, res);
ok(err);
cout << "is_write:" << ((res == vhi[i].can_write) ? "pass" : "FAIL") << '\t';
/* is readable? */
err = cs.check_handler(vhi[i].element_name, vhi[i].handler_name,
false, res);
ok(err);
cout << "is_read:" << ((res == vhi[i].can_read) ? "pass" : "FAIL") << endl;
}
cout << endl;
cout << "Read/Write handler test: ";
string data = "1234567891abcdefghij";
/*
* NB: to place spaces in this handler's data requires that the
* string be quoted; however, the handler's read function won't
* return the quotes around the string so the read value doesn't
* exactly match the write value. to avoid, we don't use spaces....
*/
err = cs.write("InfiniteSource@1", "data", data);
ok(err);
string data2;
err = cs.read("InfiniteSource@1", "data", data2);
ok(err);
if (data2 != data)
cout << "FAIL (wanted ``" << data << "'', but got ``" << data2 << "'')";
else
cout << "pass";
cout << endl;
cout << endl
<< endl
<< "********** Tests complete **********" << endl;
return 0;
}
#endif;
syntax highlighted by Code2HTML, v. 0.9.1