<commit_before>/*
 * cMapDrawer.cpp
 *
 *  Created on: 10-aug-2010
 *      Author: Stefan
 */

#include "../d2tmh.h"

cMapDrawer::cMapDrawer(cMap * theMap, cPlayer * thePlayer, cMapCamera * theCamera) {
	assert(theMap);
	assert(thePlayer);
	assert(theCamera);
	map = theMap;
	player = thePlayer;
	camera = theCamera;
}

cMapDrawer::~cMapDrawer() {
	map = NULL;
	player = NULL;
	camera = NULL;
}

// draw function, called by the drawManager
void cMapDrawer::draw() {
	drawTerrain();

	map->draw();

	// Only draw units/structures, etc, when we do NOT press D
	if (!key[KEY_D] || !key[KEY_TAB])
	{
		map->draw_structures(0);
	}

	// draw layer 1 (beneath units, on top of terrain
	map->draw_particles(1);

	map->draw_units();

	map->draw_bullets();

	map->draw_structures(2); // draw layer 2
	map->draw_structures_health();
	map->draw_units_2nd();

	map->draw_particles(0);

	map->draw_shroud();

	map->draw_minimap();
}

void cMapDrawer::drawTerrain() {
	// draw only what is visible

	// Scrolling:
	// - is X,Y position of map, not smoothed!
	// - all we need is the end of the 'to be drawn area' , which is:
	//   WIDTH OF SCREEN / 32

	cCellCalculator cellCalculator;

	int iDrawX=0;
	int iDrawY=42;

	int iCell=-1;

	int iPl = player->getId();

	char msg[255];
	sprintf(msg, "Drawing from %d, %d to %d, %d", camera->getX(), camera->getY(), camera->getEndX(), camera->getEndY());
	logbook(msg);

	// draw vertical rows..
	for (int iStartX = camera->getX(); iStartX < camera->getEndX(); iStartX++) {
		iDrawY=42;

		// new row
		for (int iStartY=camera->getY(); iStartY < camera->getEndY(); iStartY++) {
			iCell = iCellMake(iStartX, iStartY);

			if (map->iVisible[iCell][iPl] == false) {
				iDrawY+=TILESIZE_HEIGHT_PIXELS;
				continue; // do not draw this one
			}

			blit((BITMAP *)gfxdata[map->cell[iCell].type].dat, bmp_screen, map->cell[iCell].tile * 32, 0, iDrawX, iDrawY, 32, 32);

			// draw smudge if nescesary
			if (map->cell[iCell].smudgetype > -1 && map->cell[iCell].smudgetile > -1) {
				masked_blit((BITMAP *)gfxdata[SMUDGE].dat, bmp_screen,
						map->cell[iCell].smudgetile * 32, map->cell[iCell].smudgetype * 32, iDrawX, iDrawY, 32, 32);

			}

			if (DEBUGGING)
			{
				if (map->mouse_cell() > -1)
				{
					int mc = map->mouse_cell();
					if (cellCalculator.getX(mc) == iStartX && cellCalculator.getY(mc) == iStartY)
						rectfill(bmp_screen, iDrawX, iDrawY, iDrawX+32, iDrawY+32, makecol(64,64,64));

				}

				rect(bmp_screen, iDrawX, iDrawY, iDrawX+32, iDrawY+32, makecol(128,128,128));
			}

			if (game.selected_structure > -1)
			{
				// show draw a target on this cell so we know this is the rally point.
				if (structure[game.selected_structure]->getRallyPoint() > -1)
					if (structure[game.selected_structure]->getRallyPoint() == iCell)
					{
						// draw this thing ...
						set_trans_blender(0,0,0,128);
						draw_trans_sprite(bmp_screen, (BITMAP *)gfxdata[MOUSE_MOVE].dat, iDrawX, iDrawY);
					}
			}

			if (key[KEY_D] && key[KEY_TAB])
			{
				int iClr=makecol(255,0,0);

				bool bDraw=false;

				if (map->cell[iCell].passable == false)
					bDraw=true;

				if (map->cell[iCell].id[MAPID_STRUCTURES] > -1)
				{
					iClr=makecol(0,255,0);
					bDraw=true;
				}

				if (map->cell[iCell].id[MAPID_UNITS] > -1)
				{
					iClr=makecol(0,0,255);
					bDraw=true;
				}

				if (bDraw)
				{
					rectfill(bmp_screen, iDrawX, iDrawY, iDrawX+32, iDrawY+32, iClr);
				}

			}

			iDrawY+=TILESIZE_HEIGHT_PIXELS;
		}
		iDrawX+=TILESIZE_WIDTH_PIXELS;
	}
}
<commit_msg>remove logging statement<commit_after>/*
 * cMapDrawer.cpp
 *
 *  Created on: 10-aug-2010
 *      Author: Stefan
 */

#include "../d2tmh.h"

cMapDrawer::cMapDrawer(cMap * theMap, cPlayer * thePlayer, cMapCamera * theCamera) {
	assert(theMap);
	assert(thePlayer);
	assert(theCamera);
	map = theMap;
	player = thePlayer;
	camera = theCamera;
}

cMapDrawer::~cMapDrawer() {
	map = NULL;
	player = NULL;
	camera = NULL;
}

// draw function, called by the drawManager
void cMapDrawer::draw() {
	drawTerrain();

	map->draw();

	// Only draw units/structures, etc, when we do NOT press D
	if (!key[KEY_D] || !key[KEY_TAB])
	{
		map->draw_structures(0);
	}

	// draw layer 1 (beneath units, on top of terrain
	map->draw_particles(1);

	map->draw_units();

	map->draw_bullets();

	map->draw_structures(2); // draw layer 2
	map->draw_structures_health();
	map->draw_units_2nd();

	map->draw_particles(0);

	map->draw_shroud();

	map->draw_minimap();
}

void cMapDrawer::drawTerrain() {
	// draw only what is visible

	// Scrolling:
	// - is X,Y position of map, not smoothed!
	// - all we need is the end of the 'to be drawn area' , which is:
	//   WIDTH OF SCREEN / 32

	cCellCalculator cellCalculator;

	int iDrawX=0;
	int iDrawY=42;

	int iCell=-1;

	int iPl = player->getId();

//	char msg[255];
//	sprintf(msg, "Drawing from %d, %d to %d, %d", camera->getX(), camera->getY(), camera->getEndX(), camera->getEndY());
//	logbook(msg);

	// draw vertical rows..
	for (int iStartX = camera->getX(); iStartX < camera->getEndX(); iStartX++) {
		iDrawY=42;

		// new row
		for (int iStartY=camera->getY(); iStartY < camera->getEndY(); iStartY++) {
			iCell = iCellMake(iStartX, iStartY);

			if (map->iVisible[iCell][iPl] == false) {
				iDrawY+=TILESIZE_HEIGHT_PIXELS;
				continue; // do not draw this one
			}

			blit((BITMAP *)gfxdata[map->cell[iCell].type].dat, bmp_screen, map->cell[iCell].tile * 32, 0, iDrawX, iDrawY, 32, 32);

			// draw smudge if nescesary
			if (map->cell[iCell].smudgetype > -1 && map->cell[iCell].smudgetile > -1) {
				masked_blit((BITMAP *)gfxdata[SMUDGE].dat, bmp_screen,
						map->cell[iCell].smudgetile * 32, map->cell[iCell].smudgetype * 32, iDrawX, iDrawY, 32, 32);

			}

			if (DEBUGGING)
			{
				if (map->mouse_cell() > -1)
				{
					int mc = map->mouse_cell();
					if (cellCalculator.getX(mc) == iStartX && cellCalculator.getY(mc) == iStartY)
						rectfill(bmp_screen, iDrawX, iDrawY, iDrawX+32, iDrawY+32, makecol(64,64,64));

				}

				rect(bmp_screen, iDrawX, iDrawY, iDrawX+32, iDrawY+32, makecol(128,128,128));
			}

			if (game.selected_structure > -1)
			{
				// show draw a target on this cell so we know this is the rally point.
				if (structure[game.selected_structure]->getRallyPoint() > -1)
					if (structure[game.selected_structure]->getRallyPoint() == iCell)
					{
						// draw this thing ...
						set_trans_blender(0,0,0,128);
						draw_trans_sprite(bmp_screen, (BITMAP *)gfxdata[MOUSE_MOVE].dat, iDrawX, iDrawY);
					}
			}

			if (key[KEY_D] && key[KEY_TAB])
			{
				int iClr=makecol(255,0,0);

				bool bDraw=false;

				if (map->cell[iCell].passable == false)
					bDraw=true;

				if (map->cell[iCell].id[MAPID_STRUCTURES] > -1)
				{
					iClr=makecol(0,255,0);
					bDraw=true;
				}

				if (map->cell[iCell].id[MAPID_UNITS] > -1)
				{
					iClr=makecol(0,0,255);
					bDraw=true;
				}

				if (bDraw)
				{
					rectfill(bmp_screen, iDrawX, iDrawY, iDrawX+32, iDrawY+32, iClr);
				}

			}

			iDrawY+=TILESIZE_HEIGHT_PIXELS;
		}
		iDrawX+=TILESIZE_WIDTH_PIXELS;
	}
}
<|endoftext|>


<commit_before>#include "Energia.h"

#include "Ethernet.h"
#include "EthernetClient.h"
#include "EthernetServer.h"

EthernetClient::EthernetClient(){
	_connected = false;
	cs = &client_state;
	_read = &cs->read;
	cs->mode = true;
}

EthernetClient::EthernetClient(struct client *c) {
	if(c == NULL) {
		cpcb = NULL;
		return;
	}

	_connected = true;
	_read = &c->read;
	_p = c->p;
	cpcb = c->cpcb;
	cs = c;
	cs->mode = false;
}

err_t EthernetClient::do_poll(void *arg, struct tcp_pcb *cpcb)
{
	EthernetClient *client = static_cast<EthernetClient*>(arg);

	if(client->_connected) {
		if(cpcb->keep_cnt_sent++ > 4) {
			cpcb->keep_cnt_sent = 0;
			/* Stop polling */
			tcp_poll(cpcb, NULL, 0);
			tcp_abort(cpcb);
			return ERR_OK;
		}
		/* Send tcp keep alive probe */
		tcp_keepalive(cpcb);
		return ERR_OK;
	}

	/* We only end up here if the connection failed to close
	 * in an earlier call to tcp_close */
	err_t err = tcp_close(cpcb);

	if (err != ERR_OK) {
		/* error closing, try again later in poll (every 2 sec) */
		tcp_poll(cpcb, do_poll, 4);
		return err;
	}

	return err;
}

void EthernetClient::do_err(void * arg, err_t err)
{
	EthernetClient *client = static_cast<EthernetClient*>(arg);

	if(client->_connected) {
		client->_connected = false;
		return;
	}

	/*
	 * Set connected to true to finish connecting.
	 * ::connect wil take care of figuring out if we are truly connected
	 * by looking at the socket state
	 */
	client->_connected = true;
}

err_t EthernetClient::do_connected(void * arg, struct tcp_pcb * tpcb, err_t err)
{
	EthernetClient *client = static_cast<EthernetClient*>(arg);

	client->_connected = true;

	return err;
}

err_t EthernetClient::do_recv(void *arg, struct tcp_pcb *cpcb, struct pbuf *p, err_t err)
{
	/*
	 * Get the client object from the argument
	 * to get access to variables and functions
	 */
	EthernetClient *client = static_cast<EthernetClient*>(arg);

	if(p == 0) {
		client->_connected = false;
		return ERR_OK;
	}

	if(client->cs->p != 0)
		pbuf_cat(client->cs->p, p);
	else
		client->cs->p = p;

	return ERR_OK;
}

void EthernetClient::do_dns(const char *name, struct ip_addr *ipaddr, void *arg)
{
	ip_addr_t *result = (ip_addr_t *)arg;

	/* BEWARE: lwip stack has been modified to set ipaddr
	 * to IPADDR_NONE if the lookup failed */
	result->addr = ipaddr->addr;
}

int EthernetClient::connect(const char* host, uint16_t port)
{
	ip_addr_t ip;
	ip.addr = 0;

	dns_gethostbyname(host, &ip, do_dns, &ip);

	while(!ip.addr) {
		delay(10);
	}

	if(ip.addr == IPADDR_NONE) return false;

	return(connect(IPAddress(ip.addr), port));
}

int EthernetClient::connect(IPAddress ip, uint16_t port)
{
	ip_addr_t dest;
	dest.addr = ip;

	cpcb = tcp_new();

	if(cpcb == NULL) {
		return false;
	}

	tcp_arg(cpcb, this);
	tcp_recv(cpcb, do_recv);
	tcp_err(cpcb, do_err);

	uint8_t val = tcp_connect(cpcb, &dest, port, do_connected);

	if(val != ERR_OK) {
		return false;
	}

	/* Wait for the connection.
	 * Abort if the connection does not succeed within 10 sec */
	unsigned long then = millis();

	while(!_connected) {
		unsigned long now = millis();
		delay(10);
		if(now - then > CONNECTION_TIMEOUT) {
			tcp_close(cpcb);
			cpcb = NULL;
			return false;
		}
	}

	if(cpcb->state != ESTABLISHED) {
		_connected = false;
	}

	/* Poll to determine if the peer is still alive */
	tcp_poll(cpcb, do_poll, 10);
	return _connected;
}

size_t EthernetClient::write(uint8_t b) {
	return write(&b, 1);
}

size_t EthernetClient::write(const uint8_t *buf, size_t size) {
	uint32_t i = 0, inc = 0;
	boolean stuffed_buffer = false;

	// Attempt to write in 1024-byte increments.
	while (i < size) {
		inc = (size-i) < 1024 ? size-i : 1024;
		err_t err = tcp_write(cpcb, buf+i, inc, TCP_WRITE_FLAG_COPY);
		if (err != ERR_MEM) {
			// Keep enqueueing the lwIP buffer until it's full...
			i += inc;
			stuffed_buffer = false;
		} else {
			if (!stuffed_buffer) {
				// Buffer full; force output
				if (cs->mode) tcp_output(cpcb);
				stuffed_buffer = true;
			} else {
				delay(1);  // else wait a little bit for lwIP to flush its buffers
			}
		}
	}
	// flush any remaining queue contents
	if (!stuffed_buffer) {
		if (cs->mode) tcp_output(cpcb);
	}
}

int EthernetClient::available() {

	if(!cs->p) return 0;

	return cs->p->tot_len - *_read;
}

int EthernetClient::port()
{
	return cs->port;
}

int EthernetClient::read() {
	if(!available()) return -1;

	uint8_t *buf = (uint8_t *)cs->p->payload;
	uint8_t b = buf[*_read];
	*_read = *_read + 1;

	tcp_recved(cpcb, *_read);

	if((*_read == cs->p->len) && cs->p->next) {
		*_read = 0;
		pbuf *p;
		p = cs->p->next;
		/* Increase ref count on p->next
		 * 1->2->1->etc */
		pbuf_ref(cs->p->next);
		/* Free p which decreases ref count of the chain
		 * and frees up to p->next in this case
		 * ...->1->1->etc */
		pbuf_free(cs->p);
		cs->p = NULL;
		cs->p = p;
	} else if(*_read == cs->p->len) {
		*_read = 0;
		pbuf_free(cs->p);
		cs->p = NULL;
	}

	return b;
}

int EthernetClient::read(uint8_t *buf, size_t size)
{
	uint16_t avail = available();
	uint16_t i;
	int b;

	if(!avail)
		return -1;

	/* TODO: Replace lazy method with optimized on */
	for(i = 0; i < size; i++) {
		b = read();
		if(b == -1)
			break;
		buf[i] = b;
	}

	return i;
}

int EthernetClient::peek()
{
	uint8_t b;

	if (!available())
		return -1;

	uint8_t *buf = (uint8_t *)cs->p->payload;
	b = buf[*_read];

	return b;
}

void EthernetClient::flush()
{
	if(available()) {
		*_read = _p->tot_len;
		tcp_recved(cpcb, _p->tot_len);
	}
}

void EthernetClient::stop()
{

	_connected = false;
	/* Stop frees any resources including any unread buffers */
	err_t err;

	if(cpcb) {
		tcp_err(cpcb, NULL);

		if(_p) {
			tcp_recved(cpcb, _p->tot_len);
			pbuf_free(_p);
			_p = NULL;
		}

		err = tcp_close(cpcb);
	}

	if (err != ERR_OK) {
		/* Error closing, try again later in poll (every 2 sec) */
		tcp_poll(cpcb, do_poll, 4);
	} else {
		cpcb = NULL;
	}
}

uint8_t EthernetClient::connected()
{
	return (available() || _connected);
}

uint8_t EthernetClient::status()
{
	if(cpcb == NULL) {
		return CLOSED;
	}

	return cpcb->state;
}

EthernetClient::operator bool()
{
	if(cpcb == NULL) return false;
	if(cpcb->state != ESTABLISHED) return false;

	return true;
}
<commit_msg>Fix no return value in EthernetClient::write()<commit_after>#include "Energia.h"

#include "Ethernet.h"
#include "EthernetClient.h"
#include "EthernetServer.h"

EthernetClient::EthernetClient(){
	_connected = false;
	cs = &client_state;
	_read = &cs->read;
	cs->mode = true;
}

EthernetClient::EthernetClient(struct client *c) {
	if(c == NULL) {
		cpcb = NULL;
		return;
	}

	_connected = true;
	_read = &c->read;
	_p = c->p;
	cpcb = c->cpcb;
	cs = c;
	cs->mode = false;
}

err_t EthernetClient::do_poll(void *arg, struct tcp_pcb *cpcb)
{
	EthernetClient *client = static_cast<EthernetClient*>(arg);

	if(client->_connected) {
		if(cpcb->keep_cnt_sent++ > 4) {
			cpcb->keep_cnt_sent = 0;
			/* Stop polling */
			tcp_poll(cpcb, NULL, 0);
			tcp_abort(cpcb);
			return ERR_OK;
		}
		/* Send tcp keep alive probe */
		tcp_keepalive(cpcb);
		return ERR_OK;
	}

	/* We only end up here if the connection failed to close
	 * in an earlier call to tcp_close */
	err_t err = tcp_close(cpcb);

	if (err != ERR_OK) {
		/* error closing, try again later in poll (every 2 sec) */
		tcp_poll(cpcb, do_poll, 4);
		return err;
	}

	return err;
}

void EthernetClient::do_err(void * arg, err_t err)
{
	EthernetClient *client = static_cast<EthernetClient*>(arg);

	if(client->_connected) {
		client->_connected = false;
		return;
	}

	/*
	 * Set connected to true to finish connecting.
	 * ::connect wil take care of figuring out if we are truly connected
	 * by looking at the socket state
	 */
	client->_connected = true;
}

err_t EthernetClient::do_connected(void * arg, struct tcp_pcb * tpcb, err_t err)
{
	EthernetClient *client = static_cast<EthernetClient*>(arg);

	client->_connected = true;

	return err;
}

err_t EthernetClient::do_recv(void *arg, struct tcp_pcb *cpcb, struct pbuf *p, err_t err)
{
	/*
	 * Get the client object from the argument
	 * to get access to variables and functions
	 */
	EthernetClient *client = static_cast<EthernetClient*>(arg);

	if(p == 0) {
		client->_connected = false;
		return ERR_OK;
	}

	if(client->cs->p != 0)
		pbuf_cat(client->cs->p, p);
	else
		client->cs->p = p;

	return ERR_OK;
}

void EthernetClient::do_dns(const char *name, struct ip_addr *ipaddr, void *arg)
{
	ip_addr_t *result = (ip_addr_t *)arg;

	/* BEWARE: lwip stack has been modified to set ipaddr
	 * to IPADDR_NONE if the lookup failed */
	result->addr = ipaddr->addr;
}

int EthernetClient::connect(const char* host, uint16_t port)
{
	ip_addr_t ip;
	ip.addr = 0;

	dns_gethostbyname(host, &ip, do_dns, &ip);

	while(!ip.addr) {
		delay(10);
	}

	if(ip.addr == IPADDR_NONE) return false;

	return(connect(IPAddress(ip.addr), port));
}

int EthernetClient::connect(IPAddress ip, uint16_t port)
{
	ip_addr_t dest;
	dest.addr = ip;

	cpcb = tcp_new();

	if(cpcb == NULL) {
		return false;
	}

	tcp_arg(cpcb, this);
	tcp_recv(cpcb, do_recv);
	tcp_err(cpcb, do_err);

	uint8_t val = tcp_connect(cpcb, &dest, port, do_connected);

	if(val != ERR_OK) {
		return false;
	}

	/* Wait for the connection.
	 * Abort if the connection does not succeed within 10 sec */
	unsigned long then = millis();

	while(!_connected) {
		unsigned long now = millis();
		delay(10);
		if(now - then > CONNECTION_TIMEOUT) {
			tcp_close(cpcb);
			cpcb = NULL;
			return false;
		}
	}

	if(cpcb->state != ESTABLISHED) {
		_connected = false;
	}

	/* Poll to determine if the peer is still alive */
	tcp_poll(cpcb, do_poll, 10);
	return _connected;
}

size_t EthernetClient::write(uint8_t b) {
	return write(&b, 1);
}

size_t EthernetClient::write(const uint8_t *buf, size_t size) {
	uint32_t i = 0, inc = 0;
	boolean stuffed_buffer = false;

	// Attempt to write in 1024-byte increments.
	while (i < size) {
		inc = (size-i) < 1024 ? size-i : 1024;
		err_t err = tcp_write(cpcb, buf+i, inc, TCP_WRITE_FLAG_COPY);
		if (err != ERR_MEM) {
			// Keep enqueueing the lwIP buffer until it's full...
			i += inc;
			stuffed_buffer = false;
		} else {
			if (!stuffed_buffer) {
				// Buffer full; force output
				if (cs->mode) tcp_output(cpcb);
				stuffed_buffer = true;
			} else {
				delay(1);  // else wait a little bit for lwIP to flush its buffers
			}
		}
	}
	// flush any remaining queue contents
	if (!stuffed_buffer) {
		if (cs->mode) tcp_output(cpcb);
	}

	return i;
}

int EthernetClient::available() {

	if(!cs->p) return 0;

	return cs->p->tot_len - *_read;
}

int EthernetClient::port()
{
	return cs->port;
}

int EthernetClient::read() {
	if(!available()) return -1;

	uint8_t *buf = (uint8_t *)cs->p->payload;
	uint8_t b = buf[*_read];
	*_read = *_read + 1;

	tcp_recved(cpcb, *_read);

	if((*_read == cs->p->len) && cs->p->next) {
		*_read = 0;
		pbuf *p;
		p = cs->p->next;
		/* Increase ref count on p->next
		 * 1->2->1->etc */
		pbuf_ref(cs->p->next);
		/* Free p which decreases ref count of the chain
		 * and frees up to p->next in this case
		 * ...->1->1->etc */
		pbuf_free(cs->p);
		cs->p = NULL;
		cs->p = p;
	} else if(*_read == cs->p->len) {
		*_read = 0;
		pbuf_free(cs->p);
		cs->p = NULL;
	}

	return b;
}

int EthernetClient::read(uint8_t *buf, size_t size)
{
	uint16_t avail = available();
	uint16_t i;
	int b;

	if(!avail)
		return -1;

	/* TODO: Replace lazy method with optimized on */
	for(i = 0; i < size; i++) {
		b = read();
		if(b == -1)
			break;
		buf[i] = b;
	}

	return i;
}

int EthernetClient::peek()
{
	uint8_t b;

	if (!available())
		return -1;

	uint8_t *buf = (uint8_t *)cs->p->payload;
	b = buf[*_read];

	return b;
}

void EthernetClient::flush()
{
	if(available()) {
		*_read = _p->tot_len;
		tcp_recved(cpcb, _p->tot_len);
	}
}

void EthernetClient::stop()
{

	_connected = false;
	/* Stop frees any resources including any unread buffers */
	err_t err;

	if(cpcb) {
		tcp_err(cpcb, NULL);

		if(_p) {
			tcp_recved(cpcb, _p->tot_len);
			pbuf_free(_p);
			_p = NULL;
		}

		err = tcp_close(cpcb);
	}

	if (err != ERR_OK) {
		/* Error closing, try again later in poll (every 2 sec) */
		tcp_poll(cpcb, do_poll, 4);
	} else {
		cpcb = NULL;
	}
}

uint8_t EthernetClient::connected()
{
	return (available() || _connected);
}

uint8_t EthernetClient::status()
{
	if(cpcb == NULL) {
		return CLOSED;
	}

	return cpcb->state;
}

EthernetClient::operator bool()
{
	if(cpcb == NULL) return false;
	if(cpcb->state != ESTABLISHED) return false;

	return true;
}
<|endoftext|>


<commit_before>//
// Copyright (C) 2004-2006 SIPfoundry Inc.
// Licensed by SIPfoundry under the LGPL license.
//
// Copyright (C) 2004-2006 Pingtel Corp.  All rights reserved.
// Licensed to SIPfoundry under a Contributor Agreement.
//
// $$
///////////////////////////////////////////////////////////////////////////////

// SYSTEM INCLUDES
#include <assert.h>

// APPLICATION INCLUDES
#include "os/OsLock.h"
#include "os/shared/OsMsgQShared.h"
#include "os/OsDateTime.h"
#include "os/OsSysLog.h"

// EXTERNAL FUNCTIONS
// EXTERNAL VARIABLES
// CONSTANTS
// STATIC VARIABLE INITIALIZATIONS

// Message Queue implementation for OS's which do not have native message queues
//
// Two kinds of concurrent tasks, called "senders" and "receivers",
// communicate using a message queue. When the queue is empty, receivers are
// blocked until there are messages to receive. When the queue is full,
// senders are blocked until some of the queued messages are received --
// freeing up space in the queue for more messages.
//
// This implementation is based on the description from the book "Operating
// Systems Principles" by Per Brinch Hansen, 1973.  This solution uses:
//   - a counting semaphore (mEmpty) to control the delay of the sender in
//     the following way:
//       initially:      the "empty" semaphore count is set to maxMsgs
//       before send:    acquire(empty)
//       after receive:  release(empty)
//   - a counting semaphore (mFull) to control the delay of the receiver in
//     the following way:
//       initially:      the "full" semaphore count is set to 0
//       before receive: acquire(full)
//       after send:     release(full)
//   - a binary semaphore (mGuard) to ensure against concurrent access to
//     internal object data

/* //////////////////////////// PUBLIC //////////////////////////////////// */

/* ============================ CREATORS ================================== */

// Constructor
// If the name is specified but is already in use, throw an exception
OsMsgQShared::OsMsgQShared(const int maxMsgs, const int maxMsgLen,
                     const int options, const UtlString& name)
   :
   OsMsgQBase(name),
   mGuard(OsMutex::Q_PRIORITY + OsMutex::INVERSION_SAFE +
          OsMutex::DELETE_SAFE),
   mEmpty(OsCSem::Q_PRIORITY, maxMsgs, maxMsgs),
   mFull(OsCSem::Q_PRIORITY, maxMsgs, 0),
   mDlist(),
   mOptions(options),
   mHighCnt(0)
{
   mMaxMsgs = maxMsgs;

#ifdef OS_MSGQ_REPORTING
   mIncrementLevel = mMaxMsgs / 20;
   if (mIncrementLevel < 1)
      mIncrementLevel = 1;
   mIncreaseLevel = mIncrementLevel;
   mDecreaseLevel = 0;
#endif

#ifdef MSGQ_IS_VALID_CHECK /* [ */
   OsStatus ret = mGuard.acquire();         // start critical section
   assert(ret == OS_SUCCESS);

   mNumInsertEntry = 0;
   mNumInsertExitOk = 0;
   mNumInsertExitFail = 0;

   mNumRemoveEntry = 0;
   mNumRemoveExitOk = 0;
   mNumRemoveExitFail = 0;

   mLastSuccessTest = 0;

   ret = mGuard.release();         // exit critical section
   assert(ret == OS_SUCCESS);
#endif /* MSGQ_IS_VALID_CHECK ] */
}

// Destructor
OsMsgQShared::~OsMsgQShared()
{
    if (numMsgs())
        flush();    // get rid of any messages in the queue
}

/* ============================ MANIPULATORS ============================== */

// Insert a message at the tail of the queue
// Wait until there is either room on the queue or the timeout expires.
OsStatus OsMsgQShared::send(const OsMsg& rMsg,
                         const OsTime& rTimeout)
{
   return doSend(rMsg, rTimeout, FALSE, FALSE);
}

// Insert a message at the head of the queue
// Wait until there is either room on the queue or the timeout expires.
OsStatus OsMsgQShared::sendUrgent(const OsMsg& rMsg,
                               const OsTime& rTimeout)
{
   return doSend(rMsg, rTimeout, TRUE, FALSE);
}

// Insert a message at the tail of the queue.
// Sending from an ISR has a couple of implications.  Since we can't
// allocate memory within an ISR, we don't create a copy of the message
// before sending it and the sender and receiver need to agree on a
// protocol (outside this class) for when the message can be freed.
// The sentFromISR flag in the OsMsg object will be TRUE for messages
// sent using this method.
OsStatus OsMsgQShared::sendFromISR(const OsMsg& rMsg)
{
   return doSend(rMsg, OsTime::NO_WAIT_TIME, FALSE, TRUE);
}

// Remove a message from the head of the queue
// Wait until either a message arrives or the timeout expires.
// The receiver is responsible for freeing the received message.
// Other than for messages sent from an ISR, the receiver is responsible
// for freeing the received message.
OsStatus OsMsgQShared::receive(OsMsg*& rpMsg, const OsTime& rTimeout)
{
   return doReceive(rpMsg, rTimeout);
}

/* ============================ ACCESSORS ================================= */

// Return the number of messages in the queue
int OsMsgQShared::numMsgs(void)
{
   OsLock lock(mGuard);

   return(mDlist.entries());
}

// Print information on the message queue to the console
// Output enabled via a compile-time #ifdef
#ifdef MSGQ_IS_VALID_CHECK
void OsMsgQShared::show(void)
{
   osPrintf("* OsMsgQShared: OsMsgQ=0x%p, options=%d, limitMsgs=%d, maxMsgs=%d, numMsgs=%d\n",
            (void *) this, mOptions, mMaxMsgs, mHighCnt, numMsgs());

   osPrintf("* OsMsgQShared: mEmpty counting semaphore information\n");
   mEmpty.OsCSemShow();

   osPrintf("* OsMsgQShared: mFull counting semaphore information\n");
   mFull.OsCSemShow();
}
#endif

/* ============================ INQUIRY =================================== */

/* //////////////////////////// PROTECTED ///////////////////////////////// */

/* //////////////////////////// PRIVATE /////////////////////////////////// */

OsStatus OsMsgQShared::doSend(const OsMsg& rMsg, const OsTime& rTimeout,
                           const UtlBoolean isUrgent,
                           const UtlBoolean sendFromISR)
{
   OsStatus ret;
   OsMsg*   pMsg;
   const void*    insResult;

#ifdef MSGQ_IS_VALID_CHECK /* [ */
   int      msgCnt;

   ret = mGuard.acquire();         // start critical section
   assert(ret == OS_SUCCESS);

   testMessageQ();

   mNumInsertEntry++;

   ret = mGuard.release();         // exit critical section
   assert(ret == OS_SUCCESS);
#endif /* MSGQ_IS_VALID_CHECK ] */

   if (mSendHookFunc != NULL)
   {
      if (mSendHookFunc(rMsg))
      {
         // by returning TRUE, the mSendHookFunc indicates that it has handled
         // the message and there is no need to queue the message.
#ifdef MSGQ_IS_VALID_CHECK /* [ */
         OsStatus rc = mGuard.acquire();         // start critical section
         assert(rc == OS_SUCCESS);

         mNumInsertExitOk++;
         testMessageQ();

         rc = mGuard.release();         // exit critical section
         assert(rc == OS_SUCCESS);
#endif /* MSGQ_IS_VALID_CHECK ] */
         return OS_SUCCESS;
      }
   }

   ret = mEmpty.acquire(rTimeout);   // wait for there to be room in the queue
   if (ret != OS_SUCCESS)
   {
      // The caller should be responsible for logging this information
      /*OsSysLog::add(FAC_KERNEL, PRI_CRIT,
                    "OsMsgQShared::doSend message send failed - no room, ret = %d",
                    ret);*/
      if (ret == OS_BUSY || ret == OS_WAIT_TIMEOUT)
         ret =  OS_WAIT_TIMEOUT;     // send timed out
   }
   else
   {
      if (sendFromISR || rMsg.isMsgReusable())
      {
         pMsg = (OsMsg*) &rMsg;
      }
      else
      {
         pMsg = rMsg.createCopy();      // we place a copy of the message on the
                                        //  queue so that the caller is free to
                                        //  destroy the original
      }

      pMsg->setSentFromISR(sendFromISR);// set flag in the msg to indicate
                                        //  whether sent from an ISR

      ret = mGuard.acquire();           // start critical section
      assert(ret == OS_SUCCESS);

      if (isUrgent)
         insResult = mDlist.insertAt(0, pMsg); // insert msg at queue head
      else
         insResult = mDlist.insert(pMsg);      // insert msg at queue tail

#ifdef MSGQ_IS_VALID_CHECK
      msgCnt = mDlist.entries();
      if (msgCnt > mHighCnt)
         mHighCnt = msgCnt;
#endif

      if (insResult == NULL)
      {                                 // queue insert failed
         OsSysLog::add(FAC_KERNEL, PRI_CRIT,
                       "OsMsgQShared::doSend message send failed - insert failed");
         if (!(sendFromISR || rMsg.isMsgReusable()))
            delete pMsg;                // destroy the msg copy we made earlier
         assert(FALSE);

         ret = OS_UNSPECIFIED;
      }
      else
      {
         ret = mFull.release();            // signal rcvrs that a msg is available
         assert(ret == OS_SUCCESS);
      }

#ifdef OS_MSGQ_REPORTING
      int curCount;
      UtlBoolean increasedLevel = FALSE;
      UtlBoolean decreasedLevel = FALSE;

      curCount = mDlist.entries();
      if (curCount >= mIncreaseLevel)
      {
          increasedLevel = TRUE;
          while (curCount >= mIncreaseLevel)
          {
              mIncreaseLevel += mIncrementLevel;
          }
          mDecreaseLevel = mIncreaseLevel - (2 * mIncrementLevel);
      }

      if (curCount <= mDecreaseLevel)
      {
          decreasedLevel = TRUE;
          while (curCount <= mDecreaseLevel)
          {
              mDecreaseLevel = mDecreaseLevel - mIncrementLevel;
          }
          mIncreaseLevel = mDecreaseLevel + (2 * mIncrementLevel);
      }
#endif

      OsStatus guardRet = mGuard.release();           // exit critical section
      assert(guardRet == OS_SUCCESS);

#ifdef OS_MSGQ_REPORTING
      if (increasedLevel)
      {
          OsSysLogPriority pri = PRI_INFO;
          if (curCount == mMaxMsgs)
                pri = PRI_WARNING;

          OsSysLog::add(FAC_KERNEL, pri,
                    "Message queue 0x%x increased to %d msgs (max=%d)\n",
                    (int) this, curCount, mMaxMsgs);
      }
      else if (decreasedLevel)
      {
          OsSysLog::add(FAC_KERNEL, PRI_INFO,
                        "Message queue 0x%x decreased to %d msgs (max=%d)\n",
                        (int) this, curCount, mMaxMsgs);
      }
#endif
   }

#ifdef MSGQ_IS_VALID_CHECK /* [ */
   OsStatus rc = mGuard.acquire();         // start critical section
   assert(rc == OS_SUCCESS);

   if (ret == OS_SUCCESS)
      mNumInsertExitOk++;
   else
      mNumInsertExitFail++;

   testMessageQ();

   rc = mGuard.release();         // exit critical section
   assert(rc == OS_SUCCESS);
#endif /* MSGQ_IS_VALID_CHECK ] */

   return ret;
}

// Helper function for removing a message from the head of the queue
OsStatus OsMsgQShared::doReceive(OsMsg*& rpMsg, const OsTime& rTimeout)
{
   OsStatus ret;

#ifdef MSGQ_IS_VALID_CHECK /* [ */
   ret = mGuard.acquire();         // start critical section
   assert(ret == OS_SUCCESS);

   testMessageQ();
   mNumRemoveEntry++;

   ret = mGuard.release();         // exit critical section
   assert(ret == OS_SUCCESS);
#endif /* MSGQ_IS_VALID_CHECK ] */

   ret = mFull.acquire(rTimeout);  // wait for a message to be available
   if (ret != OS_SUCCESS)
   {
      if (ret == OS_BUSY || ret == OS_WAIT_TIMEOUT)
         ret = OS_WAIT_TIMEOUT;   // receive timed out
      else
      {
         assert(FALSE);
         ret = OS_UNSPECIFIED;
      }
   }
   else
   {
      ret = mGuard.acquire();         // start critical section
      assert(ret == OS_SUCCESS);

      assert(numMsgs() > 0);
      rpMsg = (OsMsg*) mDlist.get();  // get the first message

      if (rpMsg == NULL)              // was there a message?
      {
         assert(FALSE);
         ret = OS_UNSPECIFIED;
      }
      else
      {
         ret = mEmpty.release();         // the remove operation succeeded, signal
         assert(ret == OS_SUCCESS);      //  senders that there is an available
                                         //  message slot.
      }

      (void)mGuard.release();         // exit critical section
   }

#ifdef MSGQ_IS_VALID_CHECK /* [ */
   OsStatus rc = mGuard.acquire();         // start critical section
   assert(rc == OS_SUCCESS);

   if (ret == OS_SUCCESS)
      mNumRemoveExitOk++;
   else
      mNumRemoveExitFail++;

   testMessageQ();

   rc = mGuard.release();         // exit critical section
   assert(rc == OS_SUCCESS);
#endif /* MSGQ_IS_VALID_CHECK ] */

   return ret;
}

#if defined(MSGQ_IS_VALID_CHECK) && defined(OS_CSEM_DEBUG) /* [ */
// Test for message queue integrity
void OsMsgQShared::testMessageQ()
{

   if ( (mNumInsertEntry - mNumInsertExitOk - mNumInsertExitFail == 0) &&
        (mNumRemoveEntry - mNumRemoveExitOk - mNumRemoveExitFail == 0))
   {
      unsigned int numMsgs = mDlist.entries();
      assert(numMsgs == mNumInsertExitOk - mNumRemoveExitOk);
      assert(mEmpty.getValue() == mMaxMsgs - numMsgs);
      assert(mFull.getValue() == numMsgs);
      mLastSuccessTest = 0;
   }
   else
   {
      mLastSuccessTest++;
   }
}
#endif /* defined(MSGQ_IS_VALID_CHECK) && defined(OS_CSEM_DEBUG) ] */

/* ============================ FUNCTIONS ================================= */
<commit_msg>Set returned message pointer to NULL if something gone bad in OsMsgQ::receive().<commit_after>//
// Copyright (C) 2004-2006 SIPfoundry Inc.
// Licensed by SIPfoundry under the LGPL license.
//
// Copyright (C) 2004-2006 Pingtel Corp.  All rights reserved.
// Licensed to SIPfoundry under a Contributor Agreement.
//
// $$
///////////////////////////////////////////////////////////////////////////////

// SYSTEM INCLUDES
#include <assert.h>

// APPLICATION INCLUDES
#include "os/OsLock.h"
#include "os/shared/OsMsgQShared.h"
#include "os/OsDateTime.h"
#include "os/OsSysLog.h"

// EXTERNAL FUNCTIONS
// EXTERNAL VARIABLES
// CONSTANTS
// STATIC VARIABLE INITIALIZATIONS

// Message Queue implementation for OS's which do not have native message queues
//
// Two kinds of concurrent tasks, called "senders" and "receivers",
// communicate using a message queue. When the queue is empty, receivers are
// blocked until there are messages to receive. When the queue is full,
// senders are blocked until some of the queued messages are received --
// freeing up space in the queue for more messages.
//
// This implementation is based on the description from the book "Operating
// Systems Principles" by Per Brinch Hansen, 1973.  This solution uses:
//   - a counting semaphore (mEmpty) to control the delay of the sender in
//     the following way:
//       initially:      the "empty" semaphore count is set to maxMsgs
//       before send:    acquire(empty)
//       after receive:  release(empty)
//   - a counting semaphore (mFull) to control the delay of the receiver in
//     the following way:
//       initially:      the "full" semaphore count is set to 0
//       before receive: acquire(full)
//       after send:     release(full)
//   - a binary semaphore (mGuard) to ensure against concurrent access to
//     internal object data

/* //////////////////////////// PUBLIC //////////////////////////////////// */

/* ============================ CREATORS ================================== */

// Constructor
// If the name is specified but is already in use, throw an exception
OsMsgQShared::OsMsgQShared(const int maxMsgs, const int maxMsgLen,
                     const int options, const UtlString& name)
   :
   OsMsgQBase(name),
   mGuard(OsMutex::Q_PRIORITY + OsMutex::INVERSION_SAFE +
          OsMutex::DELETE_SAFE),
   mEmpty(OsCSem::Q_PRIORITY, maxMsgs, maxMsgs),
   mFull(OsCSem::Q_PRIORITY, maxMsgs, 0),
   mDlist(),
   mOptions(options),
   mHighCnt(0)
{
   mMaxMsgs = maxMsgs;

#ifdef OS_MSGQ_REPORTING
   mIncrementLevel = mMaxMsgs / 20;
   if (mIncrementLevel < 1)
      mIncrementLevel = 1;
   mIncreaseLevel = mIncrementLevel;
   mDecreaseLevel = 0;
#endif

#ifdef MSGQ_IS_VALID_CHECK /* [ */
   OsStatus ret = mGuard.acquire();         // start critical section
   assert(ret == OS_SUCCESS);

   mNumInsertEntry = 0;
   mNumInsertExitOk = 0;
   mNumInsertExitFail = 0;

   mNumRemoveEntry = 0;
   mNumRemoveExitOk = 0;
   mNumRemoveExitFail = 0;

   mLastSuccessTest = 0;

   ret = mGuard.release();         // exit critical section
   assert(ret == OS_SUCCESS);
#endif /* MSGQ_IS_VALID_CHECK ] */
}

// Destructor
OsMsgQShared::~OsMsgQShared()
{
    if (numMsgs())
        flush();    // get rid of any messages in the queue
}

/* ============================ MANIPULATORS ============================== */

// Insert a message at the tail of the queue
// Wait until there is either room on the queue or the timeout expires.
OsStatus OsMsgQShared::send(const OsMsg& rMsg,
                         const OsTime& rTimeout)
{
   return doSend(rMsg, rTimeout, FALSE, FALSE);
}

// Insert a message at the head of the queue
// Wait until there is either room on the queue or the timeout expires.
OsStatus OsMsgQShared::sendUrgent(const OsMsg& rMsg,
                               const OsTime& rTimeout)
{
   return doSend(rMsg, rTimeout, TRUE, FALSE);
}

// Insert a message at the tail of the queue.
// Sending from an ISR has a couple of implications.  Since we can't
// allocate memory within an ISR, we don't create a copy of the message
// before sending it and the sender and receiver need to agree on a
// protocol (outside this class) for when the message can be freed.
// The sentFromISR flag in the OsMsg object will be TRUE for messages
// sent using this method.
OsStatus OsMsgQShared::sendFromISR(const OsMsg& rMsg)
{
   return doSend(rMsg, OsTime::NO_WAIT_TIME, FALSE, TRUE);
}

// Remove a message from the head of the queue
// Wait until either a message arrives or the timeout expires.
// The receiver is responsible for freeing the received message.
// Other than for messages sent from an ISR, the receiver is responsible
// for freeing the received message.
OsStatus OsMsgQShared::receive(OsMsg*& rpMsg, const OsTime& rTimeout)
{
   return doReceive(rpMsg, rTimeout);
}

/* ============================ ACCESSORS ================================= */

// Return the number of messages in the queue
int OsMsgQShared::numMsgs(void)
{
   OsLock lock(mGuard);

   return(mDlist.entries());
}

// Print information on the message queue to the console
// Output enabled via a compile-time #ifdef
#ifdef MSGQ_IS_VALID_CHECK
void OsMsgQShared::show(void)
{
   osPrintf("* OsMsgQShared: OsMsgQ=0x%p, options=%d, limitMsgs=%d, maxMsgs=%d, numMsgs=%d\n",
            (void *) this, mOptions, mMaxMsgs, mHighCnt, numMsgs());

   osPrintf("* OsMsgQShared: mEmpty counting semaphore information\n");
   mEmpty.OsCSemShow();

   osPrintf("* OsMsgQShared: mFull counting semaphore information\n");
   mFull.OsCSemShow();
}
#endif

/* ============================ INQUIRY =================================== */

/* //////////////////////////// PROTECTED ///////////////////////////////// */

/* //////////////////////////// PRIVATE /////////////////////////////////// */

OsStatus OsMsgQShared::doSend(const OsMsg& rMsg, const OsTime& rTimeout,
                           const UtlBoolean isUrgent,
                           const UtlBoolean sendFromISR)
{
   OsStatus ret;
   OsMsg*   pMsg;
   const void*    insResult;

#ifdef MSGQ_IS_VALID_CHECK /* [ */
   int      msgCnt;

   ret = mGuard.acquire();         // start critical section
   assert(ret == OS_SUCCESS);

   testMessageQ();

   mNumInsertEntry++;

   ret = mGuard.release();         // exit critical section
   assert(ret == OS_SUCCESS);
#endif /* MSGQ_IS_VALID_CHECK ] */

   if (mSendHookFunc != NULL)
   {
      if (mSendHookFunc(rMsg))
      {
         // by returning TRUE, the mSendHookFunc indicates that it has handled
         // the message and there is no need to queue the message.
#ifdef MSGQ_IS_VALID_CHECK /* [ */
         OsStatus rc = mGuard.acquire();         // start critical section
         assert(rc == OS_SUCCESS);

         mNumInsertExitOk++;
         testMessageQ();

         rc = mGuard.release();         // exit critical section
         assert(rc == OS_SUCCESS);
#endif /* MSGQ_IS_VALID_CHECK ] */
         return OS_SUCCESS;
      }
   }

   ret = mEmpty.acquire(rTimeout);   // wait for there to be room in the queue
   if (ret != OS_SUCCESS)
   {
      // The caller should be responsible for logging this information
      /*OsSysLog::add(FAC_KERNEL, PRI_CRIT,
                    "OsMsgQShared::doSend message send failed - no room, ret = %d",
                    ret);*/
      if (ret == OS_BUSY || ret == OS_WAIT_TIMEOUT)
         ret =  OS_WAIT_TIMEOUT;     // send timed out
   }
   else
   {
      if (sendFromISR || rMsg.isMsgReusable())
      {
         pMsg = (OsMsg*) &rMsg;
      }
      else
      {
         pMsg = rMsg.createCopy();      // we place a copy of the message on the
                                        //  queue so that the caller is free to
                                        //  destroy the original
      }

      pMsg->setSentFromISR(sendFromISR);// set flag in the msg to indicate
                                        //  whether sent from an ISR

      ret = mGuard.acquire();           // start critical section
      assert(ret == OS_SUCCESS);

      if (isUrgent)
         insResult = mDlist.insertAt(0, pMsg); // insert msg at queue head
      else
         insResult = mDlist.insert(pMsg);      // insert msg at queue tail

#ifdef MSGQ_IS_VALID_CHECK
      msgCnt = mDlist.entries();
      if (msgCnt > mHighCnt)
         mHighCnt = msgCnt;
#endif

      if (insResult == NULL)
      {                                 // queue insert failed
         OsSysLog::add(FAC_KERNEL, PRI_CRIT,
                       "OsMsgQShared::doSend message send failed - insert failed");
         if (!(sendFromISR || rMsg.isMsgReusable()))
            delete pMsg;                // destroy the msg copy we made earlier
         assert(FALSE);

         ret = OS_UNSPECIFIED;
      }
      else
      {
         ret = mFull.release();            // signal rcvrs that a msg is available
         assert(ret == OS_SUCCESS);
      }

#ifdef OS_MSGQ_REPORTING
      int curCount;
      UtlBoolean increasedLevel = FALSE;
      UtlBoolean decreasedLevel = FALSE;

      curCount = mDlist.entries();
      if (curCount >= mIncreaseLevel)
      {
          increasedLevel = TRUE;
          while (curCount >= mIncreaseLevel)
          {
              mIncreaseLevel += mIncrementLevel;
          }
          mDecreaseLevel = mIncreaseLevel - (2 * mIncrementLevel);
      }

      if (curCount <= mDecreaseLevel)
      {
          decreasedLevel = TRUE;
          while (curCount <= mDecreaseLevel)
          {
              mDecreaseLevel = mDecreaseLevel - mIncrementLevel;
          }
          mIncreaseLevel = mDecreaseLevel + (2 * mIncrementLevel);
      }
#endif

      OsStatus guardRet = mGuard.release();           // exit critical section
      assert(guardRet == OS_SUCCESS);

#ifdef OS_MSGQ_REPORTING
      if (increasedLevel)
      {
          OsSysLogPriority pri = PRI_INFO;
          if (curCount == mMaxMsgs)
                pri = PRI_WARNING;

          OsSysLog::add(FAC_KERNEL, pri,
                    "Message queue 0x%x increased to %d msgs (max=%d)\n",
                    (int) this, curCount, mMaxMsgs);
      }
      else if (decreasedLevel)
      {
          OsSysLog::add(FAC_KERNEL, PRI_INFO,
                        "Message queue 0x%x decreased to %d msgs (max=%d)\n",
                        (int) this, curCount, mMaxMsgs);
      }
#endif
   }

#ifdef MSGQ_IS_VALID_CHECK /* [ */
   OsStatus rc = mGuard.acquire();         // start critical section
   assert(rc == OS_SUCCESS);

   if (ret == OS_SUCCESS)
      mNumInsertExitOk++;
   else
      mNumInsertExitFail++;

   testMessageQ();

   rc = mGuard.release();         // exit critical section
   assert(rc == OS_SUCCESS);
#endif /* MSGQ_IS_VALID_CHECK ] */

   return ret;
}

// Helper function for removing a message from the head of the queue
OsStatus OsMsgQShared::doReceive(OsMsg*& rpMsg, const OsTime& rTimeout)
{
   OsStatus ret;

   rpMsg = NULL;

#ifdef MSGQ_IS_VALID_CHECK /* [ */
   ret = mGuard.acquire();         // start critical section
   assert(ret == OS_SUCCESS);

   testMessageQ();
   mNumRemoveEntry++;

   ret = mGuard.release();         // exit critical section
   assert(ret == OS_SUCCESS);
#endif /* MSGQ_IS_VALID_CHECK ] */

   ret = mFull.acquire(rTimeout);  // wait for a message to be available
   if (ret != OS_SUCCESS)
   {
      if (ret == OS_BUSY || ret == OS_WAIT_TIMEOUT)
         ret = OS_WAIT_TIMEOUT;   // receive timed out
      else
      {
         assert(FALSE);
         ret = OS_UNSPECIFIED;
      }
   }
   else
   {
      ret = mGuard.acquire();         // start critical section
      assert(ret == OS_SUCCESS);

      assert(numMsgs() > 0);
      rpMsg = (OsMsg*) mDlist.get();  // get the first message

      if (rpMsg == NULL)              // was there a message?
      {
         assert(FALSE);
         ret = OS_UNSPECIFIED;
      }
      else
      {
         ret = mEmpty.release();         // the remove operation succeeded, signal
         assert(ret == OS_SUCCESS);      //  senders that there is an available
                                         //  message slot.
      }

      (void)mGuard.release();         // exit critical section
   }

#ifdef MSGQ_IS_VALID_CHECK /* [ */
   OsStatus rc = mGuard.acquire();         // start critical section
   assert(rc == OS_SUCCESS);

   if (ret == OS_SUCCESS)
      mNumRemoveExitOk++;
   else
      mNumRemoveExitFail++;

   testMessageQ();

   rc = mGuard.release();         // exit critical section
   assert(rc == OS_SUCCESS);
#endif /* MSGQ_IS_VALID_CHECK ] */

   return ret;
}

#if defined(MSGQ_IS_VALID_CHECK) && defined(OS_CSEM_DEBUG) /* [ */
// Test for message queue integrity
void OsMsgQShared::testMessageQ()
{

   if ( (mNumInsertEntry - mNumInsertExitOk - mNumInsertExitFail == 0) &&
        (mNumRemoveEntry - mNumRemoveExitOk - mNumRemoveExitFail == 0))
   {
      unsigned int numMsgs = mDlist.entries();
      assert(numMsgs == mNumInsertExitOk - mNumRemoveExitOk);
      assert(mEmpty.getValue() == mMaxMsgs - numMsgs);
      assert(mFull.getValue() == numMsgs);
      mLastSuccessTest = 0;
   }
   else
   {
      mLastSuccessTest++;
   }
}
#endif /* defined(MSGQ_IS_VALID_CHECK) && defined(OS_CSEM_DEBUG) ] */

/* ============================ FUNCTIONS ================================= */
<|endoftext|>


<commit_before>/*
  Q Light Controller
  rgbscript_test.cpp

  Copyright (C) Heikki Junnila

  This program is free software; you can redistribute it and/or
  modify it under the terms of the GNU General Public License
  Version 2 as published by the Free Software Foundation.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details. The license is
  in the file "COPYING".

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/

#include <QtTest>

#define private public
#include "rgbscript_test.h"
#include "rgbscript.h"
#undef private

#define INTERNAL_SCRIPTDIR "../../../rgbscripts"

void RGBScript_Test::initial()
{
    RGBScript script;
    QVERIFY(script.s_engine == NULL);
    QCOMPARE(script.m_apiVersion, 0);
    QCOMPARE(script.m_fileName, QString());
    QCOMPARE(script.m_contents, QString());
}

void RGBScript_Test::directories()
{
    QDir dir = RGBScript::systemScriptDirectory();
    QCOMPARE(dir.filter(), QDir::Files);
    QCOMPARE(dir.nameFilters(), QStringList() << QString("*.js"));
#ifdef __APPLE__
    QString path("%1/../%2");
    QCOMPARE(dir.path(), path.arg(QCoreApplication::applicationDirPath())
                             .arg("Resources/RGBScripts"));
#elif WIN32
    QVERIFY(dir.path().endsWith("RGBScripts"));
#else
    QVERIFY(dir.path().endsWith("qlc/rgbscripts"));
#endif

    dir = RGBScript::userScriptDirectory();
    QCOMPARE(dir.filter(), QDir::Files);
    QCOMPARE(dir.nameFilters(), QStringList() << QString("*.js"));
#ifdef __APPLE__
    QVERIFY(dir.path().endsWith("Library/Application Support/QLC/RGBScripts"));
#elif WIN32
    QVERIFY(dir.path().endsWith("RGBScripts"));
#else
    QVERIFY(dir.path().endsWith(".qlc/rgbscripts"));
#endif

    dir = RGBScript::customScriptDirectory();
    QCOMPARE(dir.filter(), QDir::Files);
    QCOMPARE(dir.nameFilters(), QStringList() << QString("*.js"));
    QCOMPARE(dir.dirName(), QString("."));

    RGBScript::setCustomScriptDirectory(INTERNAL_SCRIPTDIR);
    dir = RGBScript::customScriptDirectory();
    QCOMPARE(dir.filter(), QDir::Files);
    QCOMPARE(dir.nameFilters(), QStringList() << QString("*.js"));
    QVERIFY(dir.path().endsWith(INTERNAL_SCRIPTDIR));
}

void RGBScript_Test::scripts()
{
    QDir dir(INTERNAL_SCRIPTDIR);
    dir.setFilter(QDir::Files);
    dir.setNameFilters(QStringList() << QString("*.js"));
    QVERIFY(dir.entryList().size() > 0);

    RGBScript::setCustomScriptDirectory(INTERNAL_SCRIPTDIR);
    QList <RGBScript> list = RGBScript::scripts();
    QVERIFY(list.size() >= 0);
}

void RGBScript_Test::script()
{
    RGBScript::setCustomScriptDirectory(INTERNAL_SCRIPTDIR);

    RGBScript s = RGBScript::script("A script that should not exist");
    QCOMPARE(s.fileName(), QString());
    QCOMPARE(s.m_contents, QString());
    QCOMPARE(s.apiVersion(), 0);
    QCOMPARE(s.author(), QString());
    QCOMPARE(s.name(), QString());
    QVERIFY(s.m_script.isValid() == false);
    QVERIFY(s.m_rgbMap.isValid() == false);
    QVERIFY(s.m_rgbMapStepCount.isValid() == false);

    s = RGBScript::script("Full Rows");
    QCOMPARE(s.fileName(), QString("fullrows.js"));
    QVERIFY(s.m_contents.isEmpty() == false);
    QVERIFY(s.apiVersion() > 0);
    QCOMPARE(s.author(), QString("Heikki Junnila"));
    QCOMPARE(s.name(), QString("Full Rows"));
    QVERIFY(s.m_script.isValid() == true);
    QVERIFY(s.m_rgbMap.isValid() == true);
    QVERIFY(s.m_rgbMapStepCount.isValid() == true);
}

void RGBScript_Test::evaluateException()
{
    // Should be    function()
    QString code("( function { return 5; } )()");
    RGBScript s;
    s.m_contents = code;
    QCOMPARE(s.evaluate(), false);
}

void RGBScript_Test::evaluateNoRgbMapFunction()
{
    // No rgbMap() function present
    QString code("( function() { return 5; } )()");
    RGBScript s;
    s.m_contents = code;
    QCOMPARE(s.evaluate(), false);
    QCOMPARE(s.rgbMap(QSize(5, 5), 1, 0), RGBMap());
}

void RGBScript_Test::evaluateNoRgbMapStepCountFunction()
{
    // No rgbMapStepCount() function present
    QString code("( function() { var foo = new Object; foo.rgbMap = function() { return 0; }; return foo; } )()");
    RGBScript s;
    s.m_contents = code;
    QCOMPARE(s.evaluate(), false);
    QCOMPARE(s.rgbMapStepCount(QSize(5, 5)), -1);
}

void RGBScript_Test::evaluateInvalidApiVersion()
{
    // No apiVersion property
    QString code("( function() { var foo = new Object; foo.rgbMap = function() { return 0; }; foo.rgbMapStepCount = function(width, height) { return 0; }; return foo; } )()");
    RGBScript s;
    s.m_contents = code;
    QCOMPARE(s.evaluate(), false);
}

void RGBScript_Test::rgbMapStepCount()
{
    RGBScript s = RGBScript::script("Full Rows");
    QCOMPARE(s.rgbMapStepCount(QSize(10, 15)), 15);
}

void RGBScript_Test::rgbMap()
{
    RGBScript s = RGBScript::script("Full Rows");
    QVERIFY(s.rgbMap(QSize(3, 4), 0, 0).isEmpty() == false);

    for (int z = 0; z < 5; z++)
    {
        RGBMap map = s.rgbMap(QSize(5, 5), QColor(Qt::red).rgb(), z);
        for (int y = 0; y < 5; y++)
        {
            for (int x = 0; x < 5; x++)
            {
                if (y == z)
                    QCOMPARE(map[y][x], QColor(Qt::red).rgb());
                else
                    QCOMPARE(map[y][x], uint(0));
            }
        }
    }
}

QTEST_MAIN(RGBScript_Test)
<commit_msg>Fixed RGB script test after QLC+ rename<commit_after>/*
  Q Light Controller
  rgbscript_test.cpp

  Copyright (C) Heikki Junnila

  This program is free software; you can redistribute it and/or
  modify it under the terms of the GNU General Public License
  Version 2 as published by the Free Software Foundation.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details. The license is
  in the file "COPYING".

  You should have received a copy of the GNU General Public License
  along with this program; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/

#include <QtTest>

#define private public
#include "rgbscript_test.h"
#include "rgbscript.h"
#undef private

#define INTERNAL_SCRIPTDIR "../../../rgbscripts"

void RGBScript_Test::initial()
{
    RGBScript script;
    QVERIFY(script.s_engine == NULL);
    QCOMPARE(script.m_apiVersion, 0);
    QCOMPARE(script.m_fileName, QString());
    QCOMPARE(script.m_contents, QString());
}

void RGBScript_Test::directories()
{
    QDir dir = RGBScript::systemScriptDirectory();
    QCOMPARE(dir.filter(), QDir::Files);
    QCOMPARE(dir.nameFilters(), QStringList() << QString("*.js"));
#ifdef __APPLE__
    QString path("%1/../%2");
    QCOMPARE(dir.path(), path.arg(QCoreApplication::applicationDirPath())
                             .arg("Resources/RGBScripts"));
#elif WIN32
    QVERIFY(dir.path().endsWith("RGBScripts"));
#else
    QVERIFY(dir.path().endsWith("qlcplus/rgbscripts"));
#endif

    dir = RGBScript::userScriptDirectory();
    QCOMPARE(dir.filter(), QDir::Files);
    QCOMPARE(dir.nameFilters(), QStringList() << QString("*.js"));
#ifdef __APPLE__
    QVERIFY(dir.path().endsWith("Library/Application Support/QLC+/RGBScripts"));
#elif WIN32
    QVERIFY(dir.path().endsWith("RGBScripts"));
#else
    QVERIFY(dir.path().endsWith(".qlcplus/rgbscripts"));
#endif

    dir = RGBScript::customScriptDirectory();
    QCOMPARE(dir.filter(), QDir::Files);
    QCOMPARE(dir.nameFilters(), QStringList() << QString("*.js"));
    QCOMPARE(dir.dirName(), QString("."));

    RGBScript::setCustomScriptDirectory(INTERNAL_SCRIPTDIR);
    dir = RGBScript::customScriptDirectory();
    QCOMPARE(dir.filter(), QDir::Files);
    QCOMPARE(dir.nameFilters(), QStringList() << QString("*.js"));
    QVERIFY(dir.path().endsWith(INTERNAL_SCRIPTDIR));
}

void RGBScript_Test::scripts()
{
    QDir dir(INTERNAL_SCRIPTDIR);
    dir.setFilter(QDir::Files);
    dir.setNameFilters(QStringList() << QString("*.js"));
    QVERIFY(dir.entryList().size() > 0);

    RGBScript::setCustomScriptDirectory(INTERNAL_SCRIPTDIR);
    QList <RGBScript> list = RGBScript::scripts();
    QVERIFY(list.size() >= 0);
}

void RGBScript_Test::script()
{
    RGBScript::setCustomScriptDirectory(INTERNAL_SCRIPTDIR);

    RGBScript s = RGBScript::script("A script that should not exist");
    QCOMPARE(s.fileName(), QString());
    QCOMPARE(s.m_contents, QString());
    QCOMPARE(s.apiVersion(), 0);
    QCOMPARE(s.author(), QString());
    QCOMPARE(s.name(), QString());
    QVERIFY(s.m_script.isValid() == false);
    QVERIFY(s.m_rgbMap.isValid() == false);
    QVERIFY(s.m_rgbMapStepCount.isValid() == false);

    s = RGBScript::script("Full Rows");
    QCOMPARE(s.fileName(), QString("fullrows.js"));
    QVERIFY(s.m_contents.isEmpty() == false);
    QVERIFY(s.apiVersion() > 0);
    QCOMPARE(s.author(), QString("Heikki Junnila"));
    QCOMPARE(s.name(), QString("Full Rows"));
    QVERIFY(s.m_script.isValid() == true);
    QVERIFY(s.m_rgbMap.isValid() == true);
    QVERIFY(s.m_rgbMapStepCount.isValid() == true);
}

void RGBScript_Test::evaluateException()
{
    // Should be    function()
    QString code("( function { return 5; } )()");
    RGBScript s;
    s.m_contents = code;
    QCOMPARE(s.evaluate(), false);
}

void RGBScript_Test::evaluateNoRgbMapFunction()
{
    // No rgbMap() function present
    QString code("( function() { return 5; } )()");
    RGBScript s;
    s.m_contents = code;
    QCOMPARE(s.evaluate(), false);
    QCOMPARE(s.rgbMap(QSize(5, 5), 1, 0), RGBMap());
}

void RGBScript_Test::evaluateNoRgbMapStepCountFunction()
{
    // No rgbMapStepCount() function present
    QString code("( function() { var foo = new Object; foo.rgbMap = function() { return 0; }; return foo; } )()");
    RGBScript s;
    s.m_contents = code;
    QCOMPARE(s.evaluate(), false);
    QCOMPARE(s.rgbMapStepCount(QSize(5, 5)), -1);
}

void RGBScript_Test::evaluateInvalidApiVersion()
{
    // No apiVersion property
    QString code("( function() { var foo = new Object; foo.rgbMap = function() { return 0; }; foo.rgbMapStepCount = function(width, height) { return 0; }; return foo; } )()");
    RGBScript s;
    s.m_contents = code;
    QCOMPARE(s.evaluate(), false);
}

void RGBScript_Test::rgbMapStepCount()
{
    RGBScript s = RGBScript::script("Full Rows");
    QCOMPARE(s.rgbMapStepCount(QSize(10, 15)), 15);
}

void RGBScript_Test::rgbMap()
{
    RGBScript s = RGBScript::script("Full Rows");
    QVERIFY(s.rgbMap(QSize(3, 4), 0, 0).isEmpty() == false);

    for (int z = 0; z < 5; z++)
    {
        RGBMap map = s.rgbMap(QSize(5, 5), QColor(Qt::red).rgb(), z);
        for (int y = 0; y < 5; y++)
        {
            for (int x = 0; x < 5; x++)
            {
                if (y == z)
                    QCOMPARE(map[y][x], QColor(Qt::red).rgb());
                else
                    QCOMPARE(map[y][x], uint(0));
            }
        }
    }
}

QTEST_MAIN(RGBScript_Test)
<|endoftext|>


<commit_before>#include "uinput_device.h"

#include "ros/ros.h"
#include <linux/input.h>
#include <linux/uinput.h>
#include <linux/types.h>
#include <algorithm>
#include <fcntl.h>
#include <stdio.h>
#include <sstream>
#include <string>

#include "util.h"
#include "lg_mirror/EvdevEvent.h"
#include "lg_mirror/EvdevEvents.h"
#include "lg_mirror/EvdevDeviceInfo.h"

// emulate a typical ELO touchscreen
const __s32 MIN_TRACKING_ID = 0;
const __s32 MAX_TRACKING_ID = 65535;
const __s32 MIN_SLOT = 0;
const __s32 MAX_SLOT = 7;

UinputDeviceInitError::UinputDeviceInitError(const char* msg): msg_(msg) {}
UinputDeviceInitError::UinputDeviceInitError(const std::string& msg): msg_(msg) {}
UinputDeviceInitError::~UinputDeviceInitError() throw() {}
const char* UinputDeviceInitError::what() const throw() {
  return msg_.c_str();
}

/**
 * \brief Constructor
 * \param device_name Human-readable name for the virtual device.
 */
UinputDevice::UinputDevice(const std::string& device_name, bool translate_to_multitouch):
  fd_(-1),
  device_name_(device_name),
  translate_to_multitouch_(translate_to_multitouch),
  offscreen_x_(0),
  offscreen_y_(0)
{}

/**
 * \brief Creates the uinput device.
 * \param info Information about the source device.
 * \return true if successful.
 */
bool UinputDevice::Init(const lg_mirror::EvdevDeviceInfoResponse& info) {
  int fd;

  // open the special uinput device
  fd = open(UinputDeviceConstants::UINPUT_PATH, O_WRONLY | O_NONBLOCK);
  if (fd < 0) {
    perror("opening the uinput node");
    return false;
  }

  try {
    InitDevice_(fd, info);
  } catch (UinputDeviceInitError& e) {
    ROS_ERROR_STREAM(e.what());
    close(fd);
    return false;
  }

  // fd is now a handle for the user device
  fd_ = fd;
  return true;
}

/**
 * \brief Internal helper for creating the uinput device.
 *
 * Throws UinputDeviceInitError on failure.
 *
 * \param fd File descriptor to operate on.
 * \param info Information about the source device.
 */
void UinputDevice::InitDevice_(int fd, const lg_mirror::EvdevDeviceInfoResponse& info) {
  struct uinput_user_dev uidev;
  int status;

  // initialize the user device struct
  memset(&uidev, 0, sizeof(uidev));

  // set allowed device types and codes
  std::size_t i;
  std::size_t sz;
  sz = info.types.size();
  for (i = 0; i < sz; i++) {
    EnableCode_(fd, UI_SET_EVBIT, info.types[i]);
  }

  sz = info.key_codes.size();
  for (i = 0; i < sz; i++) {
    __u16 code = info.key_codes[i];

    if (translate_to_multitouch_ && code == BTN_LEFT) {
      code = BTN_TOUCH;
    }

    EnableCode_(fd, UI_SET_KEYBIT, code);
  }

  sz = info.rel_codes.size();
  for (i = 0; i < sz; i++) {
    EnableCode_(fd, UI_SET_RELBIT, info.rel_codes[i]);
  }

  sz = info.abs_codes.size();
  for (i = 0; i < sz; i++) {
    __u16 code = info.abs_codes[i];

    if (i > info.abs_min.size() || i > info.abs_max.size()) {
      std::ostringstream error_msg;
      error_msg << "Device info missing abs_min or abs_max for abs code " << code << " at index " << i;
      throw UinputDeviceInitError(error_msg.str());
    }
    EnableCode_(fd, UI_SET_ABSBIT, code);
    uidev.absmax[code] = info.abs_max[i];
    uidev.absmin[code] = info.abs_min[i];

    // set offscreen coords to maximums
    if (code == ABS_X) {
      offscreen_x_ = info.abs_max[i];
    } else if (code == ABS_Y) {
      offscreen_y_ = info.abs_max[i];
    }

    if (translate_to_multitouch_) {
      if (code == ABS_X) {
        EnableCode_(fd, UI_SET_ABSBIT, ABS_MT_POSITION_X);
        uidev.absmax[ABS_MT_POSITION_X] = info.abs_max[i];
        uidev.absmin[ABS_MT_POSITION_X] = info.abs_min[i];
      } else if (code == ABS_Y) {
        EnableCode_(fd, UI_SET_ABSBIT, ABS_MT_POSITION_Y);
        uidev.absmax[ABS_MT_POSITION_Y] = info.abs_max[i];
        uidev.absmin[ABS_MT_POSITION_Y] = info.abs_min[i];
      }
    }
  }

  if (translate_to_multitouch_) {
    EnableCode_(fd, UI_SET_ABSBIT, ABS_MT_TRACKING_ID);
    uidev.absmax[ABS_MT_TRACKING_ID] = MAX_TRACKING_ID;
    uidev.absmin[ABS_MT_TRACKING_ID] = MIN_TRACKING_ID;
    EnableCode_(fd, UI_SET_ABSBIT, ABS_MT_SLOT);
    uidev.absmax[ABS_MT_TRACKING_ID] = MAX_SLOT;
    uidev.absmin[ABS_MT_TRACKING_ID] = MIN_SLOT;
  }

  // set the device name
  strncpy(uidev.name, device_name_.c_str(), UINPUT_MAX_NAME_SIZE);

  // set more device attributes
  uidev.id.bustype = info.bustype;
  uidev.id.vendor = info.vendor;
  uidev.id.product = info.product;
  uidev.id.version = info.version;

  // write the device information
  status = write(fd, &uidev, sizeof(uidev));
  if (status != sizeof(uidev)) {
    throw UinputDeviceInitError("Error writing to uinput device");
  }

  // create the device
  status = ioctl(fd, UI_DEV_CREATE);
  if (status != 0) {
    throw UinputDeviceInitError("Error on ioctl UI_DEV_CREATE");
  }

  ROS_DEBUG(
    "Created device: %s with vendor: %d product: %d version: %d",
    uidev.name, uidev.id.vendor, uidev.id.product, uidev.id.version
  );
}

/**
 * \brief Shortcut for enabling an event type or code during virtual device setup.
 *
 * Throws UinputDeviceInitError on failure.
 *
 * \param fd File descriptor to write to.
 * \param codeBits Bitmask describing which type of event code to enable.
 * \param code Event code to be enabled.
 */
void UinputDevice::EnableCode_(int fd, int codeBits, int code) {
  int status = ioctl(fd, codeBits, code);
  if (status != 0) {
    std::ostringstream error_msg;
    error_msg << "Failed to enable code " << codeBits << ":" << code;
    throw UinputDeviceInitError(error_msg.str());
  }
}

/**
 * \brief Waits for the virtual device to appear in the xinput list.
 *
 * Breaks if ROS is shutdown.
 *
 * \return true if the device is available.
 */
bool UinputDevice::WaitForXinput() {
  using util::exec;

  const unsigned int MAX_INTERVAL = 1000000;// usec
  unsigned int interval = 10000; // usec

  std::ostringstream cmd;
  cmd << "xinput query-state '" << device_name_ << "'";

  while (true) {
    usleep(interval);

    if (!ros::ok()) {
      return false;
    }

    int status = system(cmd.str().c_str());
    if (status == 0) {
      return true;
    }

    interval = std::max(interval * 2, MAX_INTERVAL);
  }
}

/**
 * \brief Floats the xinput device pointer.
 * \return true if successful.
 */
bool UinputDevice::FloatPointer() const {
  std::ostringstream cmd;
  cmd << "/usr/bin/xinput float '" << device_name_ << "'";

  int status = system(cmd.str().c_str());
  return status == 0;
}

/**
 * \brief Handles a ROS message containing a vector of events.
 *
 * Implicitly writes a SYN event after the incoming events.
 *
 * Shuts down ROS upon failure.
 *
 * \param msg A message describing one or more evdev events.
 */
void UinputDevice::HandleEventMessage(const lg_mirror::EvdevEvents::Ptr& msg) {
  if (fd_ < 0) {
    ROS_ERROR("Tried to handle an event message, but UinputDevice was not initialized");
    ros::shutdown();
    return;
  }

  std::size_t num_events = msg->events.size();

  if (translate_to_multitouch_) {
    for (int i = 0; i < num_events; i++) {
      lg_mirror::EvdevEvent ev = msg->events[i];

      __u16 type = ev.type;
      __u16 code = ev.code;
      __s32 value = ev.value;

      if (type == EV_KEY) {
        if (code == BTN_LEFT) {
          if (value == 1) {
            if (!WriteEvent_(EV_ABS, ABS_MT_TRACKING_ID, 1)) {
              ROS_ERROR("Error while writing an event to the device");
              ros::shutdown();
              return;
            }
          } else if (value == 0) {
            if (!WriteEvent_(EV_ABS, ABS_MT_TRACKING_ID, -1)) {
              ROS_ERROR("Error while writing an event to the device");
              ros::shutdown();
              return;
            }
          }
        }
      }
      else if (type == EV_ABS) {
        if (code == ABS_X) {
          if (!WriteEvent_(EV_ABS, ABS_MT_POSITION_X, value)) {
            ROS_ERROR("Error while writing an event to the device");
            ros::shutdown();
            return;
          }
        }
        else if (code == ABS_Y) {
          if (!WriteEvent_(EV_ABS, ABS_MT_POSITION_Y, value)) {
            ROS_ERROR("Error while writing an event to the device");
            ros::shutdown();
            return;
          }
        }
      }
    }
  }

  for (int i = 0; i < num_events; i++) {
    lg_mirror::EvdevEvent ev = msg->events[i];

    __u16 type = ev.type;
    __u16 code = ev.code;
    __s32 value = ev.value;

    if (translate_to_multitouch_) {
      if (type == EV_KEY && code == BTN_LEFT) {
        code = BTN_TOUCH;
      }
    }

    if (!WriteEvent_(type, code, value)) {
      ROS_ERROR("Error while writing an event to the device");
      ros::shutdown();
      return;
    }

  }

  // force an EV_SYN after each message
  if (!WriteEvent_(EV_SYN, SYN_REPORT, 0)) {
    ROS_ERROR("Error while writing a SYN event");
    ros::shutdown();
    return;
  }
}

/**
 * \brief Zeroes the ABS position to clear the cursor.
 */
void UinputDevice::Zero() {
  WriteEvent_(EV_ABS, ABS_X, offscreen_x_);
  WriteEvent_(EV_ABS, ABS_Y, offscreen_y_);
  WriteEvent_(EV_SYN, SYN_REPORT, 0);
}

/*
 * \brief Writes an event to the virtual device.
 * \param type Event type.
 * \param code Event code.
 * \param value Event value.
 * \return true if successful.
 */
bool UinputDevice::WriteEvent_(__u16 type, __u16 code, __s32 value) {
  struct input_event ev;

  memset(&ev, 0, sizeof(ev));

  ev.type = type;
  ev.code = code;
  ev.value = value;

  int num_wrote = write(fd_, &ev, sizeof(ev));

  if (num_wrote != sizeof(ev)) {
    return false;
  }

  ROS_DEBUG(
    "Wrote type: %d code: %d value: %d",
    ev.type, ev.code, ev.value
  );

  return true;
}
<commit_msg>killing self when unable to find xinput device<commit_after>#include "uinput_device.h"

#include "ros/ros.h"
#include <linux/input.h>
#include <linux/uinput.h>
#include <linux/types.h>
#include <algorithm>
#include <fcntl.h>
#include <stdio.h>
#include <sstream>
#include <string>

#include "util.h"
#include "lg_mirror/EvdevEvent.h"
#include "lg_mirror/EvdevEvents.h"
#include "lg_mirror/EvdevDeviceInfo.h"

// emulate a typical ELO touchscreen
const __s32 MIN_TRACKING_ID = 0;
const __s32 MAX_TRACKING_ID = 65535;
const __s32 MIN_SLOT = 0;
const __s32 MAX_SLOT = 7;

UinputDeviceInitError::UinputDeviceInitError(const char* msg): msg_(msg) {}
UinputDeviceInitError::UinputDeviceInitError(const std::string& msg): msg_(msg) {}
UinputDeviceInitError::~UinputDeviceInitError() throw() {}
const char* UinputDeviceInitError::what() const throw() {
  return msg_.c_str();
}

/**
 * \brief Constructor
 * \param device_name Human-readable name for the virtual device.
 */
UinputDevice::UinputDevice(const std::string& device_name, bool translate_to_multitouch):
  fd_(-1),
  device_name_(device_name),
  translate_to_multitouch_(translate_to_multitouch),
  offscreen_x_(0),
  offscreen_y_(0)
{}

/**
 * \brief Creates the uinput device.
 * \param info Information about the source device.
 * \return true if successful.
 */
bool UinputDevice::Init(const lg_mirror::EvdevDeviceInfoResponse& info) {
  int fd;

  // open the special uinput device
  fd = open(UinputDeviceConstants::UINPUT_PATH, O_WRONLY | O_NONBLOCK);
  if (fd < 0) {
    perror("opening the uinput node");
    return false;
  }

  try {
    InitDevice_(fd, info);
  } catch (UinputDeviceInitError& e) {
    ROS_ERROR_STREAM(e.what());
    close(fd);
    return false;
  }

  // fd is now a handle for the user device
  fd_ = fd;
  return true;
}

/**
 * \brief Internal helper for creating the uinput device.
 *
 * Throws UinputDeviceInitError on failure.
 *
 * \param fd File descriptor to operate on.
 * \param info Information about the source device.
 */
void UinputDevice::InitDevice_(int fd, const lg_mirror::EvdevDeviceInfoResponse& info) {
  struct uinput_user_dev uidev;
  int status;

  // initialize the user device struct
  memset(&uidev, 0, sizeof(uidev));

  // set allowed device types and codes
  std::size_t i;
  std::size_t sz;
  sz = info.types.size();
  for (i = 0; i < sz; i++) {
    EnableCode_(fd, UI_SET_EVBIT, info.types[i]);
  }

  sz = info.key_codes.size();
  for (i = 0; i < sz; i++) {
    __u16 code = info.key_codes[i];

    if (translate_to_multitouch_ && code == BTN_LEFT) {
      code = BTN_TOUCH;
    }

    EnableCode_(fd, UI_SET_KEYBIT, code);
  }

  sz = info.rel_codes.size();
  for (i = 0; i < sz; i++) {
    EnableCode_(fd, UI_SET_RELBIT, info.rel_codes[i]);
  }

  sz = info.abs_codes.size();
  for (i = 0; i < sz; i++) {
    __u16 code = info.abs_codes[i];

    if (i > info.abs_min.size() || i > info.abs_max.size()) {
      std::ostringstream error_msg;
      error_msg << "Device info missing abs_min or abs_max for abs code " << code << " at index " << i;
      throw UinputDeviceInitError(error_msg.str());
    }
    EnableCode_(fd, UI_SET_ABSBIT, code);
    uidev.absmax[code] = info.abs_max[i];
    uidev.absmin[code] = info.abs_min[i];

    // set offscreen coords to maximums
    if (code == ABS_X) {
      offscreen_x_ = info.abs_max[i];
    } else if (code == ABS_Y) {
      offscreen_y_ = info.abs_max[i];
    }

    if (translate_to_multitouch_) {
      if (code == ABS_X) {
        EnableCode_(fd, UI_SET_ABSBIT, ABS_MT_POSITION_X);
        uidev.absmax[ABS_MT_POSITION_X] = info.abs_max[i];
        uidev.absmin[ABS_MT_POSITION_X] = info.abs_min[i];
      } else if (code == ABS_Y) {
        EnableCode_(fd, UI_SET_ABSBIT, ABS_MT_POSITION_Y);
        uidev.absmax[ABS_MT_POSITION_Y] = info.abs_max[i];
        uidev.absmin[ABS_MT_POSITION_Y] = info.abs_min[i];
      }
    }
  }

  if (translate_to_multitouch_) {
    EnableCode_(fd, UI_SET_ABSBIT, ABS_MT_TRACKING_ID);
    uidev.absmax[ABS_MT_TRACKING_ID] = MAX_TRACKING_ID;
    uidev.absmin[ABS_MT_TRACKING_ID] = MIN_TRACKING_ID;
    EnableCode_(fd, UI_SET_ABSBIT, ABS_MT_SLOT);
    uidev.absmax[ABS_MT_TRACKING_ID] = MAX_SLOT;
    uidev.absmin[ABS_MT_TRACKING_ID] = MIN_SLOT;
  }

  // set the device name
  strncpy(uidev.name, device_name_.c_str(), UINPUT_MAX_NAME_SIZE);

  // set more device attributes
  uidev.id.bustype = info.bustype;
  uidev.id.vendor = info.vendor;
  uidev.id.product = info.product;
  uidev.id.version = info.version;

  // write the device information
  status = write(fd, &uidev, sizeof(uidev));
  if (status != sizeof(uidev)) {
    throw UinputDeviceInitError("Error writing to uinput device");
  }

  // create the device
  status = ioctl(fd, UI_DEV_CREATE);
  if (status != 0) {
    throw UinputDeviceInitError("Error on ioctl UI_DEV_CREATE");
  }

  ROS_DEBUG(
    "Created device: %s with vendor: %d product: %d version: %d",
    uidev.name, uidev.id.vendor, uidev.id.product, uidev.id.version
  );
}

/**
 * \brief Shortcut for enabling an event type or code during virtual device setup.
 *
 * Throws UinputDeviceInitError on failure.
 *
 * \param fd File descriptor to write to.
 * \param codeBits Bitmask describing which type of event code to enable.
 * \param code Event code to be enabled.
 */
void UinputDevice::EnableCode_(int fd, int codeBits, int code) {
  int status = ioctl(fd, codeBits, code);
  if (status != 0) {
    std::ostringstream error_msg;
    error_msg << "Failed to enable code " << codeBits << ":" << code;
    throw UinputDeviceInitError(error_msg.str());
  }
}

/**
 * \brief Waits for the virtual device to appear in the xinput list.
 *
 * Breaks if ROS is shutdown.
 *
 * \return true if the device is available.
 */
bool UinputDevice::WaitForXinput() {
  using util::exec;

  const unsigned int MAX_INTERVAL = 999999;// usec
  unsigned int interval = 10000; // usec

  std::ostringstream cmd;
  cmd << "xinput query-state '" << device_name_ << "'";

  while (true) {
    usleep(std::min(interval, MAX_INTERVAL));

    if (!ros::ok()) {
      return false;
    }

    int status = system(cmd.str().c_str());
    if (status == 0) {
      return true;
    }

    interval *= 2;
    if (interval > (MAX_INTERVAL*5)) {
      ROS_ERROR("Couldn't find device after many attempts... shutting down");
      ros::shutdown();
    }
  }
}

/**
 * \brief Floats the xinput device pointer.
 * \return true if successful.
 */
bool UinputDevice::FloatPointer() const {
  std::ostringstream cmd;
  cmd << "/usr/bin/xinput float '" << device_name_ << "'";

  int status = system(cmd.str().c_str());
  return status == 0;
}

/**
 * \brief Handles a ROS message containing a vector of events.
 *
 * Implicitly writes a SYN event after the incoming events.
 *
 * Shuts down ROS upon failure.
 *
 * \param msg A message describing one or more evdev events.
 */
void UinputDevice::HandleEventMessage(const lg_mirror::EvdevEvents::Ptr& msg) {
  if (fd_ < 0) {
    ROS_ERROR("Tried to handle an event message, but UinputDevice was not initialized");
    ros::shutdown();
    return;
  }

  std::size_t num_events = msg->events.size();

  if (translate_to_multitouch_) {
    for (int i = 0; i < num_events; i++) {
      lg_mirror::EvdevEvent ev = msg->events[i];

      __u16 type = ev.type;
      __u16 code = ev.code;
      __s32 value = ev.value;

      if (type == EV_KEY) {
        if (code == BTN_LEFT) {
          if (value == 1) {
            if (!WriteEvent_(EV_ABS, ABS_MT_TRACKING_ID, 1)) {
              ROS_ERROR("Error while writing an event to the device");
              ros::shutdown();
              return;
            }
          } else if (value == 0) {
            if (!WriteEvent_(EV_ABS, ABS_MT_TRACKING_ID, -1)) {
              ROS_ERROR("Error while writing an event to the device");
              ros::shutdown();
              return;
            }
          }
        }
      }
      else if (type == EV_ABS) {
        if (code == ABS_X) {
          if (!WriteEvent_(EV_ABS, ABS_MT_POSITION_X, value)) {
            ROS_ERROR("Error while writing an event to the device");
            ros::shutdown();
            return;
          }
        }
        else if (code == ABS_Y) {
          if (!WriteEvent_(EV_ABS, ABS_MT_POSITION_Y, value)) {
            ROS_ERROR("Error while writing an event to the device");
            ros::shutdown();
            return;
          }
        }
      }
    }
  }

  for (int i = 0; i < num_events; i++) {
    lg_mirror::EvdevEvent ev = msg->events[i];

    __u16 type = ev.type;
    __u16 code = ev.code;
    __s32 value = ev.value;

    if (translate_to_multitouch_) {
      if (type == EV_KEY && code == BTN_LEFT) {
        code = BTN_TOUCH;
      }
    }

    if (!WriteEvent_(type, code, value)) {
      ROS_ERROR("Error while writing an event to the device");
      ros::shutdown();
      return;
    }

  }

  // force an EV_SYN after each message
  if (!WriteEvent_(EV_SYN, SYN_REPORT, 0)) {
    ROS_ERROR("Error while writing a SYN event");
    ros::shutdown();
    return;
  }
}

/**
 * \brief Zeroes the ABS position to clear the cursor.
 */
void UinputDevice::Zero() {
  WriteEvent_(EV_ABS, ABS_X, offscreen_x_);
  WriteEvent_(EV_ABS, ABS_Y, offscreen_y_);
  WriteEvent_(EV_SYN, SYN_REPORT, 0);
}

/*
 * \brief Writes an event to the virtual device.
 * \param type Event type.
 * \param code Event code.
 * \param value Event value.
 * \return true if successful.
 */
bool UinputDevice::WriteEvent_(__u16 type, __u16 code, __s32 value) {
  struct input_event ev;

  memset(&ev, 0, sizeof(ev));

  ev.type = type;
  ev.code = code;
  ev.value = value;

  int num_wrote = write(fd_, &ev, sizeof(ev));

  if (num_wrote != sizeof(ev)) {
    return false;
  }

  ROS_DEBUG(
    "Wrote type: %d code: %d value: %d",
    ev.type, ev.code, ev.value
  );

  return true;
}
<|endoftext|>


<commit_before>/*
 * rpcServer.cpp
 *
 *  Created on: 31. 10. 2017
 *      Author: ondra
 */

#include "rpcServer.h"
#include <imtjson/serializer.h>
#include <imtjson/parser.h>
#include "../simpleServer/urlencode.h"
#include "../simpleServer/websockets_stream.h"
#include "../simpleServer/asyncProvider.h"
#include "../simpleServer/http_hostmapping.h"
#include "../simpleServer/websockets_stream.h"
#include "../simpleServer/query_parser.h"

#include "../simpleServer/logOutput.h"

#include "resources.h"

namespace simpleServer {

using ondra_shared::LogLevel;
using ondra_shared::SharedLogObject;
using ondra_shared::AbstractLogProvider;

class WSStreamWithContext: public _details::WebSocketStreamImpl {
public:

	typedef _details::WebSocketStreamImpl Super;
	using Super::WebSocketStreamImpl;

	RefCntPtr<HttpRpcConnContext> ctx;
};

class WebSocketHandlerWithContext: public WebSocketHandler{
public:
	using WebSocketHandler::WebSocketHandler ;

	virtual WebSocketStream createStream(Stream sx) const {
		return new WSStreamWithContext(sx);
	}
	virtual ~WebSocketHandlerWithContext() {}
};

RpcHandler::RpcHandler(RpcServer& rpcserver)
	:rpcserver(rpcserver) {
}

void RpcHandler::operator ()(simpleServer::HTTPRequest req) const {

	operator()(req, req.getPath());
}

class RpcServerEnum: public RpcServer {
public:

	template<typename Fn>
	void forEach(const Fn &fn) {
		for(auto &x : mapReg) {
			fn(x.second->name);
		}
	}
};

static void handleLogging(const LogObject &logObj, const Value &v, const RpcRequest &req) noexcept {
	if (logObj.isLogLevelEnabled(LogLevel::progress)) {
		try {
			Value diagData = req.getDiagData();
			//do not log notifications
			if ((v.type() != json::object || v["method"].defined()) && !diagData.defined()) return;
			Value args = req.getArgs();
			Value method = req.getMethodName();
			Value context = req.getContext();
			if (!diagData.defined() && req.isErrorSent()) {
				diagData = v["error"];
			}
			if (!diagData.defined()) diagData = nullptr;
			if (!context.defined()) context = nullptr;
			Value output = {method,args,context,diagData};
			logObj.progress("$1", output.toString());
		} catch (...) {

		}
	}
}

Value HttpRpcConnContext::retrieve(StrViewA key) const {
	Value sup = RpcConnContext::retrieve(key);
	if (sup.defined()) return sup;
	if (!cookies.defined())  {
			Object datain;
			std::string buff;
			std::string vkey;
			auto buffput = [&](char c) {buff.push_back(c);};
			HeaderValue cookie = req["Cookie"];
			if (cookie.defined())  {
				auto parts = cookie.split("; ");
				while (!!parts) {
					UrlDecode<decltype(buffput) &> dec(buffput);

					auto subparts = StrViewA(parts()).split("=",2);
					StrViewA key = subparts();
					StrViewA value = subparts();

					buff.clear();
					for (auto &&c:key) dec(c);
					std::swap(vkey,buff);

					buff.clear();
					for (auto &&c:value) dec(c);

					try {
						datain.set(vkey, Value::fromString(buff));
					} catch (...) {
						datain.set(vkey, buff);
					}
				}
			}
			{
				auto headers = datain.object("_headers");
				for(auto &&c: req) {
					headers.set(c.first, c.second);
				}
			}
			cookies = datain;
	}
	return cookies[key];
}

void HttpRpcConnContext::exportToHeader(SendHeaders &hdrs) {
	if (this->data.size()) {
		std::ostringstream buff;
		bool sep = false;
		auto buffput = [&](char c){buff.put(c);};
		for (Value v : this->data) {
			if (sep) {buffput(',');buffput(' ');}
			StrViewA key = v.getKey();
			String value = v.stringify();
			UrlEncode<decltype(buffput) &> enc(buffput);
			for (auto &&c: key) enc(c);
			buffput('=');
			for (auto &&c: value) enc(c);
		}
		hdrs("Set-Cookie", buff.str());
	}
}

bool RpcHandler::operator ()(simpleServer::HTTPRequest req, const StrViewA &vpath) const {

	StrViewA method = req.getMethod();
	if (method == "POST") {
		RpcServer &srv(rpcserver);
		req.readBodyAsync(maxReqSize, [&srv](HTTPRequest httpreq){
			auto x = httpreq.getUserBuffer();
			if (x.empty()) {

				RpcServerEnum &enm = static_cast<RpcServerEnum &>(srv);
				Array methods;
				enm.forEach([&methods](Value v){methods.push_back(v);});
				Stream out = httpreq.sendResponse("application/json");
				Value(methods).serialize(out);
				out.flush();

			} else {
				Value rdata = Value::fromString(StrViewA(BinaryView(x)));
				Stream out;
				RefCntPtr<HttpRpcConnContext> ctx = new HttpRpcConnContext(httpreq,false);
				RpcRequest rrq = RpcRequest::create(rdata,[httpreq,logObj = LogObject(httpreq->log,"RPC"),out,ctx](
							const Value &v, const RpcRequest &req) mutable {

					if (out == nullptr) {
						HTTPResponse hdrs(200);
						ctx->exportToHeader(hdrs);
						hdrs.contentType("application/json");
						out = httpreq.sendResponse(hdrs);
					}
					handleLogging(logObj,v,req);

					try {

						if (v.defined()) {
							v.serialize(out);
						}
						out << "\r\n";
						return out.flush();

					} catch (...) {
						return false;
					}
				}, RpcFlags::preResponseNotify, PRpcConnContext::staticCast(ctx));
				srv(rrq);
			}
		});
	} else if (vpath.empty()) {
		req.redirectToFolderRoot();
	} else {
		const Resource *selRes = nullptr;
		StrViewA fname;
		QueryParser qp(vpath);
		auto splt = qp.getPath().split("/");
		while (splt) fname = splt();
		if (fname.empty()) fname="index.html";

		if (fname == "index.html") selRes = consoleEnabled?&client_index_html:nullptr;
		else if (fname == "styles.css") selRes = consoleEnabled?&client_styles_css:nullptr;
		else if (fname == "rpc.js") selRes = &client_rpc_js;
		else if (fname == "methods") {
			StrViewA cb = qp["callback"];
			Value methods;
			{
				RpcServer &srv(rpcserver);
				RpcServerEnum &enm = static_cast<RpcServerEnum &>(srv);
				Array methodsArr;
				enm.forEach([&methodsArr](Value v){methodsArr.push_back(v);});
				methods = methodsArr;
			}
			if (cb.empty()) {
				auto stream = req.sendResponse("application/json");
				methods.serialize(stream);
			} else {
				auto stream = req.sendResponse("text/javascript");
				stream << cb << "(";
				methods.serialize(stream);
				stream << ");\r\n";
			}
			return true;
		}

		if (selRes == nullptr) {
			req.sendErrorPage(404);
		} else {
			req.sendResponse(selRes->contentType, selRes->data);
		}

	}

	return true;
}

void RpcHttpServer::addRPCPath(String path) {
	Config cfg;
	addRPCPath(path,cfg);
}
void RpcHttpServer::addRPCPath(String path, const Config &cfg) {
	RpcHandler h(*this);
	if (cfg.maxReqSize) h.setMaxReqSize(cfg.maxReqSize);
	enableDirect = cfg.enableDirect;
	h.enableConsole(cfg.enableConsole);
	if (cfg.enableWS) {
		WebSocketHandlerWithContext ws(h);
		auto h2 = [=](HTTPRequest req, StrViewA vpath) mutable {
			if (!ws(req,vpath)) return h(req,vpath);
			else return true;
		};
		mapRecords.push_back(Item(path,HTTPMappedHandler(h2)));
	} else {
		mapRecords.push_back(Item(path,HTTPMappedHandler(h)));
	}

}

void RpcHttpServer::addPath(String path, simpleServer::HTTPMappedHandler hndl) {
	mapRecords.push_back(std::make_pair(path, hndl));
}

void RpcHttpServer::setHostMapping(const String &mapping) {
	hostMapping = mapping;
}

RpcHttpServer::DirectRpcConnContext::DirectRpcConnContext()
	:logObj(AbstractLogProvider::create(),Ident("TCP")) {}

/*
template<typename WrFn>
void logPrintValue(WrFn &wr, const HttpIdent &ident) {
	wr("http:");
	unsignedToString(ident.instanceId,wr,36,4);
}
*/

void RpcHttpServer::directRpcAsync(Stream s) {
	directRpcAsync2(s,new DirectRpcConnContext);
}
void RpcHttpServer::directRpcAsync2(simpleServer::Stream s, RefCntPtr<DirectRpcConnContext> ctx) {

	auto sendFn =[=](Value v, RpcRequest req) {
		try {
			if (v.defined()) v.serialize(s);
			s << "\n";
			s.flush();
			handleLogging(ctx->logObj,v,req);
			return true;
		} catch (...) {
			return false;
		}

	};

	try {
		BinaryView b = s.read(true);
		while (!b.empty() && isspace(b[0])) b = b.substr(1);
		if (!b.empty()) {
			s.putBack(b);
			Value jsonReq = Value::parse(s);
			RpcRequest req = RpcRequest::create(jsonReq,sendFn, RpcFlags::notify, PRpcConnContext::staticCast(ctx));
			ctx->store("__last_jsonrpc_ver",req.getVersionField());
			this->operator ()(req);
		}

		s.readAsync([=](simpleServer::AsyncState st, const ondra_shared::BinaryView &b) {
			if (st == asyncTimeout) {
				Value ver = ctx->retrieve("__last_jsonrpc_ver");
				RpcRequest req = RpcRequest::create({Value(),Value(),Value(),Value(),ver},sendFn,RpcFlags::notify);
				req.sendNotify("ping",Value());
				s.readAsync([=](simpleServer::AsyncState st, const ondra_shared::BinaryView &b) {
					if (st == asyncOK) {
						s.putBack(b);
						directRpcAsync2(s,ctx);
					}
				});
			} else if (st == asyncOK) {
				s.putBack(b);
				directRpcAsync2(s,ctx);
			}
		});

	} catch (...) {

	}

}

void RpcHttpServer::start() {
	std::vector<HttpStaticPathMapper::MapRecord> reglist;
	reglist.reserve(mapRecords.size());
	for (auto &&x: mapRecords) {
		HttpStaticPathMapper::MapRecord k;
		k.path = x.first;
		k.handler = x.second;
		reglist.push_back(k);
	}
	HttpStaticPathMapper hndl(std::move(reglist));
	if (enableDirect) {
		this->preHandler = [=](Stream s) {
			try {
				int b = s.peek();
				if (b == '{') {//starting with RPC protocol

					directRpcAsync(s);
					return true;
				} else {
					return false;
				}
			} catch (...) {
				return true;
			}
		};
	}
	HostMappingHandler hostMap;
	hostMap.setMapping(hostMapping);
	HttpStaticPathMapperHandler stHandler(hndl);
	(*this)>>(hostMap>>HTTPMappedHandler(stHandler));
}

void RpcHandler::operator ()(simpleServer::HTTPRequest httpreq, WebSocketStream wsstream) const {

	if (wsstream.getFrameType() == WSFrameType::text) {
		Value jreq;
		try {
			 jreq = Value::fromString(wsstream.getText());
		} catch (std::exception &e) {
			Value genError = Object("error",rpcserver.formatError(-32700,"Parse error",Value()));
			wsstream.postText(genError.stringify());
			return;
		}

		WSStreamWithContext::Super *wsx = wsstream;
		WSStreamWithContext *wswc = static_cast<WSStreamWithContext *>(wsx);
		PRpcConnContext connctx;
		if (wswc->ctx == nullptr) wswc->ctx = new HttpRpcConnContext(httpreq, true);
		connctx = PRpcConnContext::staticCast(wswc->ctx);

		RpcRequest rrq = RpcRequest::create(jreq,[wsstream,logObj = LogObject(httpreq->log, "RPC")](const Value &v, const RpcRequest &req){
			WebSocketStream ws(wsstream);
			if (!v.defined()) {
				return !(ws.isClosed());
			}

			handleLogging(logObj,v,req);
			try {
				ws.postText(v.stringify());
				return true;
			} catch (...) {
				return false;
			}
		},RpcFlags::notify, connctx);
		rpcserver(rrq);
	}
}

} /* namespace hflib */

<commit_msg>RpcServer rejects methods other then GET and POST<commit_after>/*
 * rpcServer.cpp
 *
 *  Created on: 31. 10. 2017
 *      Author: ondra
 */

#include "rpcServer.h"
#include <imtjson/serializer.h>
#include <imtjson/parser.h>
#include "../simpleServer/urlencode.h"
#include "../simpleServer/websockets_stream.h"
#include "../simpleServer/asyncProvider.h"
#include "../simpleServer/http_hostmapping.h"
#include "../simpleServer/websockets_stream.h"
#include "../simpleServer/query_parser.h"

#include "../simpleServer/logOutput.h"

#include "resources.h"

namespace simpleServer {

using ondra_shared::LogLevel;
using ondra_shared::SharedLogObject;
using ondra_shared::AbstractLogProvider;

class WSStreamWithContext: public _details::WebSocketStreamImpl {
public:

	typedef _details::WebSocketStreamImpl Super;
	using Super::WebSocketStreamImpl;

	RefCntPtr<HttpRpcConnContext> ctx;
};

class WebSocketHandlerWithContext: public WebSocketHandler{
public:
	using WebSocketHandler::WebSocketHandler ;

	virtual WebSocketStream createStream(Stream sx) const {
		return new WSStreamWithContext(sx);
	}
	virtual ~WebSocketHandlerWithContext() {}
};

RpcHandler::RpcHandler(RpcServer& rpcserver)
	:rpcserver(rpcserver) {
}

void RpcHandler::operator ()(simpleServer::HTTPRequest req) const {

	operator()(req, req.getPath());
}

class RpcServerEnum: public RpcServer {
public:

	template<typename Fn>
	void forEach(const Fn &fn) {
		for(auto &x : mapReg) {
			fn(x.second->name);
		}
	}
};

static void handleLogging(const LogObject &logObj, const Value &v, const RpcRequest &req) noexcept {
	if (logObj.isLogLevelEnabled(LogLevel::progress)) {
		try {
			Value diagData = req.getDiagData();
			//do not log notifications
			if ((v.type() != json::object || v["method"].defined()) && !diagData.defined()) return;
			Value args = req.getArgs();
			Value method = req.getMethodName();
			Value context = req.getContext();
			if (!diagData.defined() && req.isErrorSent()) {
				diagData = v["error"];
			}
			if (!diagData.defined()) diagData = nullptr;
			if (!context.defined()) context = nullptr;
			Value output = {method,args,context,diagData};
			logObj.progress("$1", output.toString());
		} catch (...) {

		}
	}
}

Value HttpRpcConnContext::retrieve(StrViewA key) const {
	Value sup = RpcConnContext::retrieve(key);
	if (sup.defined()) return sup;
	if (!cookies.defined())  {
			Object datain;
			std::string buff;
			std::string vkey;
			auto buffput = [&](char c) {buff.push_back(c);};
			HeaderValue cookie = req["Cookie"];
			if (cookie.defined())  {
				auto parts = cookie.split("; ");
				while (!!parts) {
					UrlDecode<decltype(buffput) &> dec(buffput);

					auto subparts = StrViewA(parts()).split("=",2);
					StrViewA key = subparts();
					StrViewA value = subparts();

					buff.clear();
					for (auto &&c:key) dec(c);
					std::swap(vkey,buff);

					buff.clear();
					for (auto &&c:value) dec(c);

					try {
						datain.set(vkey, Value::fromString(buff));
					} catch (...) {
						datain.set(vkey, buff);
					}
				}
			}
			{
				auto headers = datain.object("_headers");
				for(auto &&c: req) {
					headers.set(c.first, c.second);
				}
			}
			cookies = datain;
	}
	return cookies[key];
}

void HttpRpcConnContext::exportToHeader(SendHeaders &hdrs) {
	if (this->data.size()) {
		std::ostringstream buff;
		bool sep = false;
		auto buffput = [&](char c){buff.put(c);};
		for (Value v : this->data) {
			if (sep) {buffput(',');buffput(' ');}
			StrViewA key = v.getKey();
			String value = v.stringify();
			UrlEncode<decltype(buffput) &> enc(buffput);
			for (auto &&c: key) enc(c);
			buffput('=');
			for (auto &&c: value) enc(c);
		}
		hdrs("Set-Cookie", buff.str());
	}
}

bool RpcHandler::operator ()(simpleServer::HTTPRequest req, const StrViewA &vpath) const {

	if (!req.allowMethods({"GET","POST"})) return true;
	StrViewA method = req.getMethod();
	if (method == "POST") {
		RpcServer &srv(rpcserver);
		req.readBodyAsync(maxReqSize, [&srv](HTTPRequest httpreq){
			auto x = httpreq.getUserBuffer();
			if (x.empty()) {

				RpcServerEnum &enm = static_cast<RpcServerEnum &>(srv);
				Array methods;
				enm.forEach([&methods](Value v){methods.push_back(v);});
				Stream out = httpreq.sendResponse("application/json");
				Value(methods).serialize(out);
				out.flush();

			} else {
				Value rdata = Value::fromString(StrViewA(BinaryView(x)));
				Stream out;
				RefCntPtr<HttpRpcConnContext> ctx = new HttpRpcConnContext(httpreq,false);
				RpcRequest rrq = RpcRequest::create(rdata,[httpreq,logObj = LogObject(httpreq->log,"RPC"),out,ctx](
							const Value &v, const RpcRequest &req) mutable {

					if (out == nullptr) {
						HTTPResponse hdrs(200);
						ctx->exportToHeader(hdrs);
						hdrs.contentType("application/json");
						out = httpreq.sendResponse(hdrs);
					}
					handleLogging(logObj,v,req);

					try {

						if (v.defined()) {
							v.serialize(out);
						}
						out << "\r\n";
						return out.flush();

					} catch (...) {
						return false;
					}
				}, RpcFlags::preResponseNotify, PRpcConnContext::staticCast(ctx));
				srv(rrq);
			}
		});
	} else if (vpath.empty()) {
		req.redirectToFolderRoot();
	} else {
		const Resource *selRes = nullptr;
		StrViewA fname;
		QueryParser qp(vpath);
		auto splt = qp.getPath().split("/");
		while (splt) fname = splt();
		if (fname.empty()) fname="index.html";

		if (fname == "index.html") selRes = consoleEnabled?&client_index_html:nullptr;
		else if (fname == "styles.css") selRes = consoleEnabled?&client_styles_css:nullptr;
		else if (fname == "rpc.js") selRes = &client_rpc_js;
		else if (fname == "methods") {
			StrViewA cb = qp["callback"];
			Value methods;
			{
				RpcServer &srv(rpcserver);
				RpcServerEnum &enm = static_cast<RpcServerEnum &>(srv);
				Array methodsArr;
				enm.forEach([&methodsArr](Value v){methodsArr.push_back(v);});
				methods = methodsArr;
			}
			if (cb.empty()) {
				auto stream = req.sendResponse("application/json");
				methods.serialize(stream);
			} else {
				auto stream = req.sendResponse("text/javascript");
				stream << cb << "(";
				methods.serialize(stream);
				stream << ");\r\n";
			}
			return true;
		}

		if (selRes == nullptr) {
			req.sendErrorPage(404);
		} else {
			req.sendResponse(selRes->contentType, selRes->data);
		}

	}

	return true;
}

void RpcHttpServer::addRPCPath(String path) {
	Config cfg;
	addRPCPath(path,cfg);
}
void RpcHttpServer::addRPCPath(String path, const Config &cfg) {
	RpcHandler h(*this);
	if (cfg.maxReqSize) h.setMaxReqSize(cfg.maxReqSize);
	enableDirect = cfg.enableDirect;
	h.enableConsole(cfg.enableConsole);
	if (cfg.enableWS) {
		WebSocketHandlerWithContext ws(h);
		auto h2 = [=](HTTPRequest req, StrViewA vpath) mutable {
			if (!ws(req,vpath)) return h(req,vpath);
			else return true;
		};
		mapRecords.push_back(Item(path,HTTPMappedHandler(h2)));
	} else {
		mapRecords.push_back(Item(path,HTTPMappedHandler(h)));
	}

}

void RpcHttpServer::addPath(String path, simpleServer::HTTPMappedHandler hndl) {
	mapRecords.push_back(std::make_pair(path, hndl));
}

void RpcHttpServer::setHostMapping(const String &mapping) {
	hostMapping = mapping;
}

RpcHttpServer::DirectRpcConnContext::DirectRpcConnContext()
	:logObj(AbstractLogProvider::create(),Ident("TCP")) {}

/*
template<typename WrFn>
void logPrintValue(WrFn &wr, const HttpIdent &ident) {
	wr("http:");
	unsignedToString(ident.instanceId,wr,36,4);
}
*/

void RpcHttpServer::directRpcAsync(Stream s) {
	directRpcAsync2(s,new DirectRpcConnContext);
}
void RpcHttpServer::directRpcAsync2(simpleServer::Stream s, RefCntPtr<DirectRpcConnContext> ctx) {

	auto sendFn =[=](Value v, RpcRequest req) {
		try {
			if (v.defined()) v.serialize(s);
			s << "\n";
			s.flush();
			handleLogging(ctx->logObj,v,req);
			return true;
		} catch (...) {
			return false;
		}

	};

	try {
		BinaryView b = s.read(true);
		while (!b.empty() && isspace(b[0])) b = b.substr(1);
		if (!b.empty()) {
			s.putBack(b);
			Value jsonReq = Value::parse(s);
			RpcRequest req = RpcRequest::create(jsonReq,sendFn, RpcFlags::notify, PRpcConnContext::staticCast(ctx));
			ctx->store("__last_jsonrpc_ver",req.getVersionField());
			this->operator ()(req);
		}

		s.readAsync([=](simpleServer::AsyncState st, const ondra_shared::BinaryView &b) {
			if (st == asyncTimeout) {
				Value ver = ctx->retrieve("__last_jsonrpc_ver");
				RpcRequest req = RpcRequest::create({Value(),Value(),Value(),Value(),ver},sendFn,RpcFlags::notify);
				req.sendNotify("ping",Value());
				s.readAsync([=](simpleServer::AsyncState st, const ondra_shared::BinaryView &b) {
					if (st == asyncOK) {
						s.putBack(b);
						directRpcAsync2(s,ctx);
					}
				});
			} else if (st == asyncOK) {
				s.putBack(b);
				directRpcAsync2(s,ctx);
			}
		});

	} catch (...) {

	}

}

void RpcHttpServer::start() {
	std::vector<HttpStaticPathMapper::MapRecord> reglist;
	reglist.reserve(mapRecords.size());
	for (auto &&x: mapRecords) {
		HttpStaticPathMapper::MapRecord k;
		k.path = x.first;
		k.handler = x.second;
		reglist.push_back(k);
	}
	HttpStaticPathMapper hndl(std::move(reglist));
	if (enableDirect) {
		this->preHandler = [=](Stream s) {
			try {
				int b = s.peek();
				if (b == '{') {//starting with RPC protocol

					directRpcAsync(s);
					return true;
				} else {
					return false;
				}
			} catch (...) {
				return true;
			}
		};
	}
	HostMappingHandler hostMap;
	hostMap.setMapping(hostMapping);
	HttpStaticPathMapperHandler stHandler(hndl);
	(*this)>>(hostMap>>HTTPMappedHandler(stHandler));
}

void RpcHandler::operator ()(simpleServer::HTTPRequest httpreq, WebSocketStream wsstream) const {

	if (wsstream.getFrameType() == WSFrameType::text) {
		Value jreq;
		try {
			 jreq = Value::fromString(wsstream.getText());
		} catch (std::exception &e) {
			Value genError = Object("error",rpcserver.formatError(-32700,"Parse error",Value()));
			wsstream.postText(genError.stringify());
			return;
		}

		WSStreamWithContext::Super *wsx = wsstream;
		WSStreamWithContext *wswc = static_cast<WSStreamWithContext *>(wsx);
		PRpcConnContext connctx;
		if (wswc->ctx == nullptr) wswc->ctx = new HttpRpcConnContext(httpreq, true);
		connctx = PRpcConnContext::staticCast(wswc->ctx);

		RpcRequest rrq = RpcRequest::create(jreq,[wsstream,logObj = LogObject(httpreq->log, "RPC")](const Value &v, const RpcRequest &req){
			WebSocketStream ws(wsstream);
			if (!v.defined()) {
				return !(ws.isClosed());
			}

			handleLogging(logObj,v,req);
			try {
				ws.postText(v.stringify());
				return true;
			} catch (...) {
				return false;
			}
		},RpcFlags::notify, connctx);
		rpcserver(rrq);
	}
}

} /* namespace hflib */

<|endoftext|>