//
// CRAM: CREATE Real-time Applications Manager
// See the copyright and acknowledgment of authors in the file COPYRIGHT
//
//// NodeTest.cpp -- basic node tester ////
//
//	Sends ping and then get_name messages to the specified node manager port.
//	If that works, try creating a service!
//
//	Included as an illustratory example of talking the CRAM protocol from C++.
//
//	Usage:
//		testnode host_name port
//

#include "CRAM.h"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>

// Private careful send/receive functions

int tsafe_recv(int ssoc, char * buf, unsigned ssiz, struct sockaddr * scln, int * sadr, int stimeo, int srtry);
int tsafe_send(int ssoc, char * buf, unsigned ssiz, struct sockaddr * scln);


#define PRINT_BUF(msg)					\
	strcat(&(buffer[offset]), msg);		\
	offset += (strlen(msg) + 1);

////// MAIN //////

int main(int argc, char * argv[]) {
	char buffer[CRAM_MEDIUM_MSG];		// buffer the message header and 64 byte payload
	CRAMPacketHeader header;
	
	int sock, offset, tmp;				// socket io data
	unsigned short port;
	struct sockaddr_in sname;
	struct hostent * hostInfo; 	
	ssize_t xfer;
	int size = sizeof(sname);
										// check cmd-line options
	if (argc != 3) {
		printf("Usage: %s host_name port\n", argv[0]);
		exit(0);
	}
	port = atoi(argv[2]);
	hostInfo = gethostbyname(argv[1]);	// get hostname
	if (hostInfo == NULL) {
		printf("%s - Unknown host %s\n", argv[0], argv[1]);
		exit(EXIT_FAILURE);
	}									// socket address
	sname.sin_addr = * (struct in_addr *) hostInfo->h_addr;
	sname.sin_family = AF_INET;			// protocol family
	sname.sin_port = htons(port);		// socket port
	sock = socket(PF_INET, SOCK_DGRAM, 0);
	if (sock < 0) {
		printf("%s - Can't create socket %s\n", argv[0], argv[2]);
		exit(EXIT_FAILURE);
	}
	
// Set up the ping message header
	header.magic = CRAM_PACKET_MAGIC | CRAM_PING;
//	header.magic = CRAM_PACKET_MAGIC | CRAM_EMER_PING;
	header.size = CRAM_HEADER_SIZE;
	header.sequence = 1;
							// Send ping
//	printf("\t%s: sending ping packet\n", argv[0]);
	xfer = tsafe_send(sock, (char *) & header, CRAM_HEADER_SIZE, (struct sockaddr *) & sname);
	if (xfer != CRAM_HEADER_SIZE) {
		perror("Error writing to socket");
		close(sock);
		exit(0);
	}						// Receive
//	printf("\t%s: receiving reply\n", argv[0]);
	xfer = tsafe_recv(sock, buffer, CRAM_HEADER_SIZE, (struct sockaddr *) & sname, & size, 10, 1);
	if (xfer < (int) CRAM_HEADER_SIZE) {
		perror("Error reading from socket");
		close(sock);
		exit(0);
	}						// Copy and process the ping response packet
	memcpy((void *) & header, (void *) buffer, CRAM_HEADER_SIZE);
	printf("\tReceived ping response: %x %d %d %d\n", 
				header.magic, header.size, header.sequence, header.value);
	if (header.value >= CRAM_HEADER_ERROR) {				// error returned
		switch (header.size) {
			case CRAM_HEADER_ERROR:			printf("\tError response: header error\n"); 		break;
			case CRAM_ARGUMENT_ERROR:		printf("\tError response: argument error\n"); 		break;
			case CRAM_COMMAND_ERROR:		printf("\tError response: command ID error\n"); 	break;
			case CRAM_PROCESSING_ERROR:		printf("\tError response: processing error\n"); 	break;
			case CRAM_IO_ERROR:				printf("\tError response: IO error\n"); 			break;
			case CRAM_NO_SERVICE_ERROR:		printf("\tError response: no such service\n");		break;
			case CRAM_OTHER_ERROR:			printf("\tError response: unknown error\n"); 		break;
		}
	}
	
// Set up the get_name message header
	header.magic = CRAM_PACKET_MAGIC | CRAM_GET_NAME;
	header.size = CRAM_HEADER_SIZE;
	header.sequence = 2;
							// Send get_name
	//	printf("\t%s: sending get_name packet\n", argv[0]);
	xfer = tsafe_send(sock, (char *) & header, CRAM_HEADER_SIZE, (struct sockaddr *) & sname);
	if (xfer != CRAM_HEADER_SIZE) {
		perror("Error writing to socket");
		close(sock);
		exit(0);
	}						// Receive
	xfer = tsafe_recv(sock, buffer, CRAM_HEADER_SIZE, (struct sockaddr *) & sname, & size, 10, 1);
	if (xfer <= (int) CRAM_HEADER_SIZE) {
		perror("Error reading from socket");
		close(sock);
		exit(0);
	}
	//	printf("\t%s: received %d bytes\n", argv[0], xfer);
	memcpy((void *) & header, (void *) buffer, CRAM_HEADER_SIZE);
	printf("\tReceived get_name response: %x %d %d\n", header.magic, header.size, header.sequence);
	if (header.size > (CRAM_HEADER_SIZE + 6)) {		// if there's a name response
		char answer[32];
		strcpy(answer, (char *) & buffer[CRAM_HEADER_SIZE]);
		printf("\tResponse name: \"%s\"\n", answer);
	} else {
		printf("\tEmpty name response\n");
	}
	
#ifdef NOT_DEF_TODAY

// Set up the create_service message header (%s%s%s), 
//		answer = scan_packet(pkt, "sss", name, cmd, args);

	header.magic = CRAM_PACKET_MAGIC | CRAM_CREATE_SVC;
	header.sequence = 3;
	offset = CRAM_HEADER_SIZE;
//	char arg[64];
	PRINT_BUF("clock4")		// print name, cmd, args onto packet buffer
	PRINT_BUF("/Users/stp/CRAM/MacOSX/build/ClockService")
	PRINT_BUF("10")
	tmp = CRAM_PACKET_MAGIC;		// write trailing magic number onto packet
	memcpy((void *) & (buffer[offset]), & tmp, 4);
	header.size = offset + 4;
	
							//// Send create_service
	//	printf("\t%s: sending create_service packet\n", argv[0]);
	xfer = tsafe_send(sock, (char *) & header, header.size, (struct sockaddr *) & sname);
	if (xfer != CRAM_HEADER_SIZE) {
		perror("Error writing to socket");
		close(sock);
		exit(0);
	}
	sleep(3);				// wait for service to start
							// Receive response from create service
	xfer = tsafe_recv(sock, buffer, CRAM_HEADER_SIZE, (struct sockaddr *) & sname, & size, 10, 1);
	if (xfer <= (int) CRAM_HEADER_SIZE) {
		perror("Error reading from socket");
		close(sock);
		exit(0);
	}
	//	printf("\t%s: received %d bytes\n", argv[0], xfer);
	memcpy((void *) & header, (void *) buffer, CRAM_HEADER_SIZE);
	printf("\tReceived get_name response: %x %d %d\n", header.magic, header.size, header.sequence);
	if (header.size > (CRAM_HEADER_SIZE + 6)) {		// if there's a name response
		char answer[32];
		strcpy(answer, (char *) & buffer[CRAM_HEADER_SIZE]);
		printf("\tResponse name: \"%s\"\n", answer);
	} else {
		printf("\tEmpty name response\n");
	}

#endif

// Exit
	close(sock);
}

// Safe socket IO

// Safe_send retries multiple writes to a socket; returns bytes sent or error code
// It does no exception handling; call it with in try{}catch{}

int tsafe_send(int ssoc, char * buf, unsigned ssiz, struct sockaddr * scln) {
	int val;
	unsigned ssent = 0;	
	unsigned to_ssend = ssiz;	
	while (ssent < ssiz) {					// loop retrying partial sends
		val = sendto(ssoc, (& buf[ssent]), to_ssend, 0, scln, sizeof(struct sockaddr_in));
		if (val < 0) {	
			perror("Could not write to socket");
			return -1;
		} else {
			ssent += val;
			to_ssend -= val;
		}
	}
	return ssent;
}

// this version of safe_recv uses blocking receives rather than selects.

int tsafe_recv(int ssoc, char * buf, unsigned ssiz, struct sockaddr * scln, int * sadr, int stimeo, int srtry) {
	int sr_ret;		
	CRAMPacketHeader * pkt = (CRAMPacketHeader *) buf;
	
	sr_ret = recvfrom(ssoc, buf, CRAM_MEDIUM_MSG, 0, scln, sadr);	// try recv-from
	
	if (sr_ret < 0) {								// if it fails...	
		perror("Recv IO error");
		return -1;	
	} else {										// else check read size
		if (pkt->size < ssiz) {						// if we got less than the minimum, give up
			printf("Recv size error: %d", pkt->size);
			return -1;
		}	
	}		
	return sr_ret;	
}


