// bug6230761.cpp : DLL アプリケーション用にエクスポートされる関数を定義します。
//

#include "stdafx.h"

#include <stdlib.h>
#include <stdio.h>

#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdarg.h>
#include <time.h>

#include "jp_himacs_avoidbugs_ipv6_bug6230761_WinsockIPv6.h"

#ifdef _MANAGED
#pragma managed(push, off)
#endif

#define OP_ACCEPT 16 
#define OP_CONNECT 8 
#define OP_READ 1 
#define OP_WRITE 4 
/*
BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
					 )
{
	switch (ul_reason_for_call)
	{
	case DLL_PROCESS_ATTACH:
	case DLL_THREAD_ATTACH:
	case DLL_THREAD_DETACH:
	case DLL_PROCESS_DETACH:
		break;
	}
    return TRUE;
}*/

#ifdef _MANAGED
#pragma managed(pop)
#endif

static volatile int seqno = 0;

int getSeqno() {
#pragma omp parallel num_threads(20)
  {
      #pragma omp atomic
      seqno++;
	  return seqno;
   }
}

char *getFilename(char *head, char *filename) {
	char *dir = NULL;
	if ((dir=getenv("BUG6230761_DUMPDIR"))==NULL) {
		return NULL;
	}
	time_t timeval = time(0);
	struct tm tmval;
	memset(&tmval,0,sizeof(tmval));
	localtime_s(&tmval, &timeval);
	sprintf(filename, "%s\\%s_%04d%02d%02d_%02d%02d%02d_%d_%d.dmp",
		dir, head, tmval.tm_year+1900, tmval.tm_mon+1, tmval.tm_mday,
		tmval.tm_hour, tmval.tm_min, tmval.tm_sec, clock(), getSeqno());

	return filename;
}

void send_dump(const char *buff, int len) {
	char *dir = NULL;
	if ((dir=getenv("BUG6230761_DUMPDIR"))==NULL) {
		return;
	}
	char filename[256];
	FILE *fp = fopen(getFilename("send", filename),"w");
	if (fp==NULL) return;
	fwrite(buff, len,1, fp);
	fclose(fp);
}

void recv_dump(const char *buff, int len) {
	char *dir = NULL;
	if ((dir=getenv("BUG6230761_DUMPDIR"))==NULL) {
		return;
	}
	char filename[256];
	FILE *fp = fopen(getFilename("recv", filename),"w");
	if (fp==NULL) return;
	fwrite(buff, len, 1, fp);
	fclose(fp);
}

/**
 * errLogger
 */
void errLogger(const char *fmt, ...) {
	va_list ap;
	va_start(ap, fmt);
	char filename[256];
	char datebuf[256];
	time_t timeval;
	sprintf(filename, "BUG6230761_Error_%d.log",GetCurrentProcessId());
	time(&timeval);
	strcpy(datebuf, ctime(&timeval));
	char *p = strchr(datebuf, '\n');
	if (p) *p = 0;

	char buffer[1024];
	vsprintf(buffer,fmt,ap);
	FILE *fp = fopen(filename,"a");
	if (fp) {
		fprintf(fp,"%s:(%d):%s\n", datebuf, GetCurrentThreadId(),buffer);
		fclose(fp);
	}
	va_end(ap);
}
/**
 * address dump.
 */
char* errToString(sockaddr_in6 *addr, char *buffer) {
	char tmp[256];
	static const char *hex = "0123456789abcdef";
	int i = 0;

	memset(tmp, 0,sizeof(tmp));
	for(i = 0; i < sizeof(addr->sin6_addr.u.Byte); i++) {
		unsigned char c = addr->sin6_addr.u.Byte[i];
		tmp[2*i] = hex[(c/16)&0xf];
		tmp[2*i+1] = hex[c%16];
	}
	sprintf(buffer, "%s%%%d:%d",tmp,addr->sin6_scope_id,ntohs(addr->sin6_port));

	return buffer;
}


/**
 * logger
 */
void logger(const char *fmt, ...) {
	va_list ap;
	va_start(ap, fmt);
	char filename[256];
	char datebuf[256];
	time_t timeval;
#ifdef _DEBUG
	sprintf(filename, "c:\\temp\\BUG6230761_%d.log",GetCurrentProcessId());
	time(&timeval);
	strcpy(datebuf, ctime(&timeval));
	char *p = strchr(datebuf, '\n');
	if (p) *p = 0;

	char buffer[1024];
	vsprintf(buffer,fmt,ap);
	FILE *fp = fopen(filename,"a");
	fprintf(fp,"%s:(%d):%s\n", datebuf, GetCurrentThreadId(),buffer);
	fclose(fp);
	va_end(ap);
#endif
}

/**
 * address dump.
 */
char* toString(sockaddr_in6 *addr, char *buffer) {
	char tmp[256];
	static const char *hex = "0123456789abcdef";
	int i = 0;

#ifdef _DEBUG
	memset(tmp, 0,sizeof(tmp));
	for(i = 0; i < sizeof(addr->sin6_addr.u.Byte); i++) {
		unsigned char c = addr->sin6_addr.u.Byte[i];
		tmp[2*i] = hex[(c/16)&0xf];
		tmp[2*i+1] = hex[c%16];
	}
	sprintf(buffer, "%s%%%d:%d",tmp,addr->sin6_scope_id,ntohs(addr->sin6_port));
#endif

	return buffer;
}

/**
 * Winsock2 Initialize.
 */
__declspec(dllexport) void Init() {
	static BOOL isInited = FALSE;

	if (isInited) {
		return;
	}
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;
	 
	wVersionRequested = MAKEWORD( 2, 2 );
	 
	err = WSAStartup( wVersionRequested, &wsaData );
	if ( err != 0 ) {
		/* Tell the user that we could not find a usable */
		/* WinSock DLL.                                  */
		return;
	}
	 
	/* Confirm that the WinSock DLL supports 2.2.*/
	/* Note that if the DLL supports versions greater    */
	/* than 2.2 in addition to 2.2, it will still return */
	/* 2.2 in wVersion since that is the version we      */
	/* requested.                                        */
	 
	if ( LOBYTE( wsaData.wVersion ) != 2 ||
			HIBYTE( wsaData.wVersion ) != 2 ) {
		/* Tell the user that we could not find a usable */
		/* WinSock DLL.                                  */
		WSACleanup( );
		return; 
	}
	 
	/* The WinSock DLL is acceptable. Proceed. */
	isInited = TRUE;
}

/**
 * Winsock2 Terminate.
 */
__declspec(dllexport) void Term() {
	WSACleanup( );
}
/**
 * return new socket.
 */
__declspec(dllexport) SOCKET CreateSocket(int type, int protocol)
{
	Init();
	SOCKET sock = socket(AF_INET6, type, protocol);
	logger("socket:fd=[%d],err=[%d]", sock, WSAGetLastError());
	return sock;	
}
/*
 */
JNIEXPORT jint JNICALL Java_jp_himacs_avoidbugs_ipv6_bug6230761_WinsockIPv6_createSocket
(JNIEnv * env, jclass obj, jint type, jint protocol) {
	return (jint)CreateSocket(type,protocol);
}
/*
 * Class:     jp_himacs_avoidbugs_ipv6_bug6230761_WinsockIPv6
 * Method:    connect
 * Signature: (II[B)I
 */
JNIEXPORT jint JNICALL Java_jp_himacs_avoidbugs_ipv6_bug6230761_WinsockIPv6_connect
(JNIEnv *env, jclass obj, jint fd, jint port, jbyteArray addr) {
	char buffer[256];
	jboolean b;

	jbyte* addrArr = (*env).GetByteArrayElements(addr, &b);

	struct sockaddr_in6 sin6;
	memset(&sin6, 0, sizeof(sin6));

	sin6.sin6_family = AF_INET6;
	sin6.sin6_port = htons((u_short)port);
	memcpy(sin6.sin6_addr.u.Byte, addrArr, sizeof(sin6.sin6_addr.u.Byte));
	int result = connect((SOCKET)fd, (const struct sockaddr*)&sin6, sizeof(sin6));
	(*env).ReleaseByteArrayElements(addr,addrArr,0);
	logger("connect:fd=[%d],err=[%d],addr=[%s]", fd, WSAGetLastError(),toString(&sin6,buffer));
	return result;
}

/*
 * Class:     jp_himacs_avoidbugs_ipv6_bug6230761_WinsockIPv6
 * Method:    sendTo
 * Signature: (II[B[B)I
 */
JNIEXPORT jint JNICALL Java_jp_himacs_avoidbugs_ipv6_bug6230761_WinsockIPv6_sendto
(JNIEnv *env, jclass obj, jint fd, jint port, jbyteArray addr, jint scope_id, jbyteArray data) {
	char buffer[256];
	jboolean b;

	jbyte* addrArr = (*env).GetByteArrayElements(addr, &b);

/*
	fd_set wfds;

	FD_ZERO(&wfds);
	FD_SET(fd, &wfds);

	int num = select(1024,NULL,&wfds,NULL,NULL);
*/
	struct sockaddr_in6 sin6;
	memset(&sin6, 0, sizeof(sin6));
	sin6.sin6_family = AF_INET6;
	sin6.sin6_port = (u_short)htons(port);
	memcpy(sin6.sin6_addr.u.Byte, addrArr, sizeof(sin6.sin6_addr.u.Byte));
	sin6.sin6_scope_id = scope_id;
	jbyte* dataArr = (*env).GetByteArrayElements(data, &b);
	int dataLen = (*env).GetArrayLength(data);

	int result = sendto((SOCKET)fd, (const char*)dataArr, dataLen, 0, (struct sockaddr*)&sin6,sizeof(sin6));
	
	if (result > 0) {
		send_dump((const char*)dataArr, result);
	} else {
		errLogger("BUG6230761.dll:sendto failed(%d) error(%d) remote(%s)", result, WSAGetLastError(),errToString(&sin6,buffer));
	}
	(*env).ReleaseByteArrayElements(addr,addrArr,0);
	(*env).ReleaseByteArrayElements(data,dataArr,0);

	logger("sendto:fd=[%d],err=[%d],addr=[%s]", fd, WSAGetLastError(), toString(&sin6,buffer));

	return result;
}

/*
 * Class:     jp_himacs_avoidbugs_ipv6_bug6230761_WinsockIPv6
 * Method:    recvFrom
 * Signature: (II[B[B)I
 */
JNIEXPORT jint JNICALL Java_jp_himacs_avoidbugs_ipv6_bug6230761_WinsockIPv6_recvfrom
(JNIEnv *env, jclass cls, jint fd, jobject obj, jbyteArray data) {
	jboolean b;
	char buffer[256];
	int retry_count = 0;

	jbyte* dataArr = (*env).GetByteArrayElements(data, &b);
	jclass clazz = (*env).GetObjectClass(obj);
	jmethodID setPort =(*env).GetMethodID(clazz,"setPort","(I)V");
	jmethodID setHost =(*env).GetMethodID(clazz,"setHost","([B)V");
	jmethodID getHost =(*env).GetMethodID(clazz,"getHost","()[B");
	jmethodID setScopeId =(*env).GetMethodID(clazz,"setScopeId","(I)V");
	struct sockaddr_in6 sin6;
	memset(&sin6, 0, sizeof(sin6));
	int fromLen = sizeof(sin6);
	int dataLen = (*env).GetArrayLength(data);

	int result = recvfrom((SOCKET)fd, (char*)dataArr,dataLen,0,(struct sockaddr*)&sin6, &fromLen); 
	if (result < 0) {
		errLogger("BUG6230761.dll:recvfrom failed(%d) error(%d) remote(%s)", result, WSAGetLastError(),errToString(&sin6,buffer));
	} else {
		recv_dump((const char*)dataArr, result);
	}
	
	(*env).CallShortMethod(obj,setPort,ntohs(sin6.sin6_port));
	jbyteArray host = (*env).NewByteArray(16); //IPv6 IP address len
	jbyte* pHost = (*env).GetByteArrayElements(host, &b);
	memcpy(pHost, sin6.sin6_addr.u.Byte, sizeof(sin6.sin6_addr.u.Byte));
	(*env).CallVoidMethod(obj,setPort,ntohs(sin6.sin6_port));
	(*env).CallVoidMethod(obj,setScopeId,sin6.sin6_scope_id);
	(*env).SetByteArrayRegion(host,0,16,pHost);
	//(*env).ReleaseByteArrayElements(host,pHost, 0);
	(*env).CallVoidMethod(obj,setHost,(jvalue*)host);
	if (result>0) {
		(*env).SetByteArrayRegion(data,0,result,dataArr);
		(*env).ReleaseByteArrayElements(data,dataArr,0);
	}
	logger("recvfrom:fd=[%d],err=[%d],addr=[%s]", fd, WSAGetLastError(), toString(&sin6,buffer));

	return result;
}
/*
 * Class:     jp_himacs_avoidbugs_ipv6_bug6230761_WinsockIPv6
 * Method:    bind
 * Signature: (II[BI)I
 */
JNIEXPORT jint JNICALL Java_jp_himacs_avoidbugs_ipv6_bug6230761_WinsockIPv6_bind__II_3BI
(JNIEnv *env, jclass cls, jint fd, jint port, jbyteArray addr, jint scopeId) {
	char buffer[256];
	jboolean b;

	jbyte* addrArr = (*env).GetByteArrayElements(addr, &b);

	struct sockaddr_in6 sin6;
	memset(&sin6, 0, sizeof(sin6));
	sin6.sin6_family = AF_INET6;
	sin6.sin6_port = (u_short)htons(port);
	memcpy(sin6.sin6_addr.u.Byte, addrArr, sizeof(sin6.sin6_addr.u.Byte));
	sin6.sin6_scope_id = scopeId;
	int result = bind((SOCKET)fd, (struct sockaddr*)&sin6, sizeof(sin6));
	if (result < 0) {
		errLogger("BUG6230761.dll:bind failed(%d) error(%d) addr(%s)", result, WSAGetLastError(),errToString(&sin6,buffer));
	}
	(*env).ReleaseByteArrayElements(addr,addrArr,0);
	logger("bind:fd=[%d],err=[%d],addr=[%s]", fd, WSAGetLastError(), toString(&sin6,buffer));
	return result;
}
/*
 * Class:     jp_himacs_avoidbugs_ipv6_bug6230761_WinsockIPv6
 * Method:    select
 * Signature: ([I[II)I
 */
JNIEXPORT jint JNICALL Java_jp_himacs_avoidbugs_ipv6_bug6230761_WinsockIPv6_select
(JNIEnv *env, jclass cls, jintArray fd, jintArray opt, jint timeout) {
	fd_set rfds;
	fd_set wfds;
	fd_set efds;
	timeval tm, *ptm = NULL;
	jboolean b;

	jint* fdArr = (*env).GetIntArrayElements(fd,&b);
	jsize fdlen = (*env).GetArrayLength(fd);
	jint* optArr = (*env).GetIntArrayElements(opt,&b);
	jsize optlen = (*env).GetArrayLength(opt);

	logger("select:start timeout=[%d]", timeout);

	if (fdlen != optlen) {
		// parameter errot
		logger("native select parameter error"); 
		return -1;
	}

	if (timeout!=-1) {
		memset(&tm, 0, sizeof(tm));
		tm.tv_usec = (timeout * 1000)%1000000;
		tm.tv_sec =  timeout / 1000;
		ptm = &tm;
	}

	FD_ZERO(&rfds);
	FD_ZERO(&wfds);
	FD_ZERO(&efds);

	int i = 0;
	for(i = 0; i < fdlen; i++) {
		int opts = optArr[i];
		int fd = fdArr[i];

		if (opts&OP_READ || opts&OP_ACCEPT) {
			FD_SET(fd, &rfds);
		}
		if (opts&OP_WRITE || opts&OP_CONNECT) {
			FD_SET(fd, &wfds);
		}
	}

	int result = select(1024,&rfds,&wfds,&efds,ptm);

	if (result>0) {
		for(i = 0; i < fdlen; i++) {
			int opts = 0;
			int fd = fdArr[i];
			if (FD_ISSET(fd, &rfds)) {
				opts += (OP_READ|OP_ACCEPT);
			}
			if (FD_ISSET(fd, &wfds)) {
				opts += (OP_WRITE|OP_CONNECT);
			}
			optArr[i] = opts;
		}
		(*env).SetIntArrayRegion(opt,0,optlen,optArr);
	} else {
		for(i = 0; i < fdlen; i++) {
			int opts = 0;
			optArr[i] = opts;
		}
		(*env).SetIntArrayRegion(opt,0,optlen,optArr);
		//fprintf(stderr,"native select error : [%d]\n", WSAGetLastError()); 
	}
	logger("select:end result=[%d],err=[%d]", result, WSAGetLastError());
	return result;
}

/*
 * Class:     jp_himacs_avoidbugs_ipv6_bug6230761_WinsockIPv6
 * Method:    closesocket
 * Signature: (I)I
 */
JNIEXPORT jint JNICALL Java_jp_himacs_avoidbugs_ipv6_bug6230761_WinsockIPv6_closesocket
(JNIEnv *env, jclass cls, jint fd) {
	shutdown(fd, 2);
	logger("close:fd=[%d],err=[%d]", fd, WSAGetLastError());
	return closesocket(fd);
}

/*
 * Class:     jp_himacs_avoidbugs_ipv6_bug6230761_WinsockIPv6
 * Method:    setsockopt
 * Signature: (IIII)I
 */
JNIEXPORT jint JNICALL Java_jp_himacs_avoidbugs_ipv6_bug6230761_WinsockIPv6_setsockopt
(JNIEnv *env, jclass cls, jint fd, jint level, jint opt, jint val) {
	logger("setsockopt:fd=[%d],level=[%d],opt=[%d],err=[%d]", fd, level, opt, WSAGetLastError());
	return setsockopt(fd,level,opt,(const char*)&val,sizeof(val)); 
}

/*
 * Class:     jp_himacs_avoidbugs_ipv6_bug6230761_WinsockIPv6
 * Method:    getsockopt
 * Signature: (III)I
 */
JNIEXPORT jint JNICALL Java_jp_himacs_avoidbugs_ipv6_bug6230761_WinsockIPv6_getsockopt
(JNIEnv *env, jclass cls, jint fd, jint level, jint opt) {
	int val = 0;
	socklen_t len = sizeof(val); 
	int result = getsockopt(fd,level,opt,(char*)&val,&len);
	if (result >= 0) {
		result = val;
	}
	logger("getsockopt:fd=[%d],level=[%d],opt=[%d],result=[%d],err=[%d]", fd, level, opt, result,WSAGetLastError());
	return result;
}

/*
 * Class:     jp_himacs_avoidbugs_ipv6_bug6230761_WinsockIPv6
 * Method:    blocking
 * Signature: (II)I
 */
JNIEXPORT jint JNICALL Java_jp_himacs_avoidbugs_ipv6_bug6230761_WinsockIPv6_blocking
(JNIEnv *env, jclass cls, jint fd, jint blocking) {
	logger("ioctlsocket:fd=[%d],FIONBIO,val=[%d],err=[%d]", fd, blocking,WSAGetLastError());
	return ioctlsocket(fd, FIONBIO, (u_long*)&blocking);
}


