diff --git a/build.xml b/build.xml index e82dac04109ffacf961f1b01212594afed152a22..251a3aa9a42283e68f4388484242548acc30d61a 100644 --- a/build.xml +++ b/build.xml @@ -3,9 +3,13 @@ <property file="local.properties" /> <property name="dest" value="classes" /> + <property name="dest.test" value="test-classes" /> <property name="dist" value="dist" /> + <property name="JUnit.home" value="lib/test"/> + <property name="junit.reports" value="junit-reports"/> <property name="libjitsi.jar" value="libjitsi.jar"/> <property name="src" value="src"/> + <property name="src.test" value="test"/> <property name="doc" value="doc"/> <property name="java.doc" value="${doc}/api"/> <property name="native.libs" value="lib/native"/> @@ -14,6 +18,13 @@ <path id="compile.class.path"> <fileset dir="lib" includes="*.jar" /> </path> + <path id="test.class.path"> + <path refid="compile.class.path" /> + <pathelement location="${dest}" /> + <pathelement location="${dest.test}" /> + <pathelement location="${JUnit.home}/junit-4.11.jar"/> + <pathelement location="${JUnit.home}/hamcrest-core-1.3.jar"/> + </path> <condition property="build.label" value="-${label}" @@ -30,8 +41,10 @@ <delete failonerror="false" includeemptydirs="true"> <fileset file="${libjitsi.jar}" /> <fileset dir="${dest}" /> + <fileset dir="${dest.test}" /> <fileset dir="${dist}" /> <fileset dir="${doc}" /> + <fileset dir="${junit.reports}"/> </delete> </target> @@ -49,6 +62,19 @@ <exclude name="org/jitsi/impl/neomedia/codec/audio/speex/Java*"/> </javac> </target> + <target name="compile-test" depends="compile"> + <mkdir dir="${dest.test}" /> + <javac + classpathref="test.class.path" + debug="true" + destdir="${dest.test}" + fork="true" + optimize="true" + source="1.6" + target="1.6"> + <src path="${src.test}"/> + </javac> + </target> <target name="compile-with-g729"> <replace @@ -192,5 +218,18 @@ </zipfileset> </zip> </target> + <!-- Run the tests--> + <target name="test" depends="compile-test"> + <mkdir dir="${junit.reports}"/> + <junit printsummary="yes" haltonfailure="true" fork="true" forkmode="once"> + <formatter type="xml" /> + <classpath refid="test.class.path"/> + <sysproperty + key="java.library.path" + path="lib/native/linux-64:lib/native/linux:lib/native/mac:lib/native/windows-64:lib/native/windows" /> + <test name="org.jitsi.sctp4j.SctpTestSuite" + todir="${junit.reports}"/> + </junit> + </target> </project> diff --git a/lib/test/hamcrest-core-1.3.jar b/lib/test/hamcrest-core-1.3.jar new file mode 100644 index 0000000000000000000000000000000000000000..9d5fe16e3dd37ebe79a36f61f5d0e1a69a653a8a Binary files /dev/null and b/lib/test/hamcrest-core-1.3.jar differ diff --git a/lib/test/junit-4.11.jar b/lib/test/junit-4.11.jar new file mode 100644 index 0000000000000000000000000000000000000000..aaf74448492932e95902b40a70c7a4da5bad4744 Binary files /dev/null and b/lib/test/junit-4.11.jar differ diff --git a/src/native/sctp/Makefile.nmake b/src/native/sctp/Makefile.nmake index 484a6c8e3d7e496bab0c7e1dd61fced889968b5c..29d3eb20f0d1eb702268e17fd228e9a1e1ad61c5 100644 --- a/src/native/sctp/Makefile.nmake +++ b/src/native/sctp/Makefile.nmake @@ -1,8 +1,12 @@ -CC = cl /O2 +CC = cl /O2 /Zi +#CC = cl /O2 + JNI_HEADERS = /I"%JAVA_HOME%\include" /I"%JAVA_HOME%\include\win32" SCTP_HEADERS = /I"sctp-refimpl-read-only\KERN\usrsctp\usrsctplib" -LIBS = /link /LIBPATH:"sctp-refimpl-read-only\KERN\usrsctp\usrsctplib" /LIBPATH:"%JAVA_HOME%\lib" + +LIBS = /link /DEBUG /LIBPATH:"sctp-refimpl-read-only\KERN\usrsctp\usrsctplib" /LIBPATH:"%JAVA_HOME%\lib" +#LIBS = /link /LIBPATH:"sctp-refimpl-read-only\KERN\usrsctp\usrsctplib" /LIBPATH:"%JAVA_HOME%\lib" #CFLAGS = $(JNI_HEADERS) $(SCTP_HEADERS) -DSCTP_DEBUG=1 -DINET6=1 CFLAGS = $(JNI_HEADERS) $(SCTP_HEADERS) -DINET6=1 diff --git a/src/native/sctp/org_jitsi_sctp4j_Sctp.c b/src/native/sctp/org_jitsi_sctp4j_Sctp.c index 2ad83f82f15478ef7f0c8b1a6cc2f338a01fe124..a4dd1a5cfc8ad7969674983f310edd6c8db1879f 100644 --- a/src/native/sctp/org_jitsi_sctp4j_Sctp.c +++ b/src/native/sctp/org_jitsi_sctp4j_Sctp.c @@ -5,7 +5,7 @@ #include <string.h> // errno returned after connect call on success -#define SCTP_EINPROGRESS 115 +#define SCTP_EINPROGRESS EINPROGRESS // Name of the class that contains callback methods. #define SCTP_CLASS "org/jitsi/sctp4j/Sctp" @@ -15,79 +15,81 @@ struct sctp_socket { // Socket object created by SCTP stack struct socket *sock; - + int localPort; }; // Java Virtual Machine instance JavaVM* jvm; -void callOnSctpOutboundPacket( void* socketPtr, void* data, +int callOnSctpOutboundPacket( void* socketPtr, void* data, size_t length, uint8_t tos, uint8_t set_df ) { JNIEnv* jniEnv; - + int hadToAttach = 0; - + int getEnvStat; - - jclass sctpClass; + + jclass sctpClass; jmethodID outboundCallback; - - jlong sctpPtr; - jbyteArray jBuff; - jint jtos; + + jlong sctpPtr; + jbyteArray jBuff; + jint jtos; jint jset_df; - - getEnvStat = (*jvm)->GetEnv(jvm, (void **)&jniEnv, JNI_VERSION_1_6); + + jint result; + + getEnvStat = (*jvm)->GetEnv(jvm, (void **)&jniEnv, JNI_VERSION_1_6); if (getEnvStat == JNI_EDETACHED) { hadToAttach = 1; - + if ((*jvm)->AttachCurrentThread(jvm, (void **) &jniEnv, NULL) != 0) { printf("Failed to attach new thread\n"); - return; + return -1; } } - else if (getEnvStat == JNI_EVERSION) + else if (getEnvStat == JNI_EVERSION) { printf("GetEnv: version not supported\n"); - return; + return -1; } - else if (getEnvStat == JNI_OK) + else if (getEnvStat == JNI_OK) { // OK } - + sctpClass = (*jniEnv)->FindClass(jniEnv, SCTP_CLASS); if(!sctpClass) { printf("Failed to get SCTP class\n"); - return; + return -1; } outboundCallback = (*jniEnv)->GetStaticMethodID( - jniEnv, sctpClass, "onSctpOutboundPacket", "(J[BII)V"); + jniEnv, sctpClass, "onSctpOutboundPacket", "(J[BII)I"); if(!outboundCallback) { printf("Failed to get onSctpOutboundPacket method\n"); - return; + return -1; } - + sctpPtr = (jlong)(long)socketPtr; - - jBuff = (*jniEnv)->NewByteArray(jniEnv, length); + + jBuff = (*jniEnv)->NewByteArray(jniEnv, length); (*jniEnv)->SetByteArrayRegion(jniEnv, jBuff, 0, length, (jbyte*) data); - + jtos = (jint)tos; - + jset_df = (jint)set_df; - - (*jniEnv)->CallStaticObjectMethod( + + result = (jint)(*jniEnv)->CallStaticIntMethod( jniEnv, sctpClass, outboundCallback, sctpPtr, jBuff, jtos, jset_df); // FIXME: not sure if jBuff should be released @@ -95,14 +97,16 @@ void callOnSctpOutboundPacket( void* socketPtr, void* data, //(*jniEnv)->ReleaseByteArrayElements(jniEnv, jBuff, packetDataPtr, // JNI_ABORT/*free the buffer without copying back the possible changes */); (*jniEnv)->DeleteLocalRef(jniEnv, jBuff); - + if(hadToAttach) - { - if ((*jvm)->DetachCurrentThread(jvm) != 0) + { + if ((*jvm)->DetachCurrentThread(jvm) != 0) { printf("Failed to deattach the thread\n"); - } + } } + + return (int)result; } void callOnSctpInboundPacket( void* socketPtr, void* data, @@ -114,31 +118,31 @@ void callOnSctpInboundPacket( void* socketPtr, void* data, JNIEnv* jniEnv; int hadToAttach = 0; - - int getEnvStat; - - jclass sctpClass; - jmethodID inboundCallback; + + int getEnvStat; + jclass sctpClass; + jmethodID inboundCallback; + jlong sctpPtr; - + jbyteArray jBuff; - + jint jsid; jint jssn; jint jtsn; jlong jppid; jint jcontext; - + getEnvStat = (*jvm)->GetEnv(jvm, (void **)&jniEnv, JNI_VERSION_1_6); - if (getEnvStat == JNI_EDETACHED) + if (getEnvStat == JNI_EDETACHED) { hadToAttach = 1; - + if ((*jvm)->AttachCurrentThread(jvm, (void **) &jniEnv, NULL) != 0) { - printf("Failed to attach new thread\n"); - return; + printf("Failed to attach new thread\n"); + return; } } else if (getEnvStat == JNI_EVERSION) @@ -150,14 +154,14 @@ void callOnSctpInboundPacket( void* socketPtr, void* data, { // OK } - + sctpClass = (*jniEnv)->FindClass(jniEnv, SCTP_CLASS); if(!sctpClass) { printf("Failed to get SCTP class\n"); return; } - + inboundCallback = (*jniEnv)->GetStaticMethodID( jniEnv, sctpClass, "onSctpInboundPacket", "(J[BIIIJII)V"); @@ -166,54 +170,58 @@ void callOnSctpInboundPacket( void* socketPtr, void* data, printf("Failed to get onSctpInboundPacket method\n"); return; } - + sctpPtr = (jlong)(long)socketPtr; - - jBuff = (*jniEnv)->NewByteArray(jniEnv, length); + + jBuff = (*jniEnv)->NewByteArray(jniEnv, length); (*jniEnv)->SetByteArrayRegion(jniEnv, jBuff, 0, length, (jbyte*) data); - + jsid = (jint)sid; jssn = (jint)ssn; jtsn = (jint)tsn; jppid = (jlong)ntohl(ppid); jcontext = (jint)context; - - (*jniEnv)->CallStaticObjectMethod( + + (*jniEnv)->CallStaticVoidMethod( jniEnv, sctpClass, inboundCallback, sctpPtr, jBuff, jsid, jssn, jtsn, jppid, jcontext, (jint)flags); - + // Release byte array - //(*jniEnv)->ReleaseByteArrayElements(jniEnv, jBuff, packetDataPtr, + //(*jniEnv)->ReleaseByteArrayElements(jniEnv, jBuff, packetDataPtr, // JNI_ABORT/*free the buffer without copying back the possible changes */); (*jniEnv)->DeleteLocalRef(jniEnv, jBuff); - + if(hadToAttach) - { - if ((*jvm)->DetachCurrentThread(jvm) != 0) + { + if ((*jvm)->DetachCurrentThread(jvm) != 0) { printf("Failed to deattach the thread\n"); - } - } + } + } } static int onSctpOutboundPacket(void* addr, void* data, size_t length, uint8_t tos, uint8_t set_df ) { - if(data && length) - { - callOnSctpOutboundPacket(addr, data, length, tos, set_df); - } + if(data && length) + { + if(callOnSctpOutboundPacket(addr, data, length, tos, set_df) == 0) + { + return 0; + } + } - return 0; + //FIXME: not sure about this value, but an error for now + return -1; } void debugSctpPrintf(const char *format, ...) { - va_list ap; + va_list ap; - va_start(ap, format); - vprintf(format, ap); - va_end(ap); + va_start(ap, format); + vprintf(format, ap); + va_end(ap); } /* @@ -257,9 +265,9 @@ JNIEXPORT void JNICALL Java_org_jitsi_sctp4j_Sctp_on_1network_1in (JNIEnv *env, jclass class, jlong ptr, jbyteArray jbytesPacket) { struct sctp_socket* sock; - jbyte* packetDataPtr; + jbyte* packetDataPtr; jsize packetLength; - + sock = (struct sctp_socket*)(long)ptr; packetDataPtr = (*env)->GetByteArrayElements( @@ -269,7 +277,7 @@ JNIEXPORT void JNICALL Java_org_jitsi_sctp4j_Sctp_on_1network_1in usrsctp_conninput(sock, (char*)packetDataPtr, packetLength, 0); - (*env)->ReleaseByteArrayElements(env, jbytesPacket, packetDataPtr, + (*env)->ReleaseByteArrayElements(env, jbytesPacket, packetDataPtr, JNI_ABORT/*free the buffer without copying back the possible changes */); } @@ -281,29 +289,22 @@ int onSctpInboundPacket(struct socket* sock, union sctp_sockstore addr, void* data, size_t length, struct sctp_rcvinfo rcv, int flags, void* ulp_info) { - //union sctp_notification* notification; - if(data) { - if (flags & MSG_NOTIFICATION) + if (flags & MSG_NOTIFICATION) { - //printf("SCTP NOTIFICATION F: %i L: %i\n", flags, (int)length); - //notification = (union sctp_notification*)data; - //printf("NOTIFI T: %u F: %u L: %u\n", - // notification->sn_header.sn_type, - // notification->sn_header.sn_flags, - // notification->sn_header.sn_length - //); + callOnSctpInboundPacket( + ulp_info, data, length, + 0, 0, 0, 0, 0, flags ); } else { // Pass packet data to Java - callOnSctpInboundPacket( - ulp_info, - data, length, - rcv.rcv_sid, rcv.rcv_ssn, rcv.rcv_tsn, - rcv.rcv_ppid, rcv.rcv_context, flags ); - + callOnSctpInboundPacket( + ulp_info, + data, length, + rcv.rcv_sid, rcv.rcv_ssn, rcv.rcv_tsn, + rcv.rcv_ppid, rcv.rcv_context, flags ); } free(data); } @@ -326,12 +327,12 @@ JNIEXPORT jint JNICALL Java_org_jitsi_sctp4j_Sctp_usrsctp_1send // data using SCTP. ssize_t send_res = 0; // result from usrsctp_sendv. //struct sctp_sendv_spa spa; - - jbyte* dataPtr; + + jbyte* dataPtr; jsize dataLength; - - sctpSocket = (struct sctp_socket*)((long)ptr); - + + sctpSocket = (struct sctp_socket*)((long)ptr); + //memset(&spa, 0, sizeof(struct sctp_sendv_spa)); //spa.sendv_flags |= SCTP_SEND_SNDINFO_VALID; //spa.sendv_sndinfo.snd_sid = sid;//params.ssrc; @@ -362,34 +363,34 @@ JNIEXPORT jint JNICALL Java_org_jitsi_sctp4j_Sctp_usrsctp_1send //send_res = usrsctp_sendv(sctpSocket->sock, dataPtr, dataLength, // NULL, 0, &spa, sizeof(spa), // SCTP_SENDV_SPA, 0); - + sndinfo.snd_sid = sid; - sndinfo.snd_flags = 0;//8; - sndinfo.snd_ppid = htonl(ppid); - sndinfo.snd_context = 0; - sndinfo.snd_assoc_id = 0; - if(ordered != JNI_TRUE) - { - sndinfo.snd_flags |= SCTP_UNORDERED; - } + sndinfo.snd_flags = 0;//8; + sndinfo.snd_ppid = htonl(ppid); + sndinfo.snd_context = 0; + sndinfo.snd_assoc_id = 0; + if(ordered != JNI_TRUE) + { + sndinfo.snd_flags |= SCTP_UNORDERED; + } send_res = usrsctp_sendv( sctpSocket->sock, dataPtr, dataLength, NULL, 0, (void *)&sndinfo, (socklen_t)sizeof(struct sctp_sndinfo), SCTP_SENDV_SNDINFO, 0); - + /*send_res = usrsctp_sendv( sctpSocket->sock, dataPtr, dataLength, NULL, 0, NULL, 0, SCTP_SENDV_NOINFO, 0);*/ - + (*env)->ReleaseByteArrayElements(env, jdata, dataPtr, JNI_ABORT/*free the buffer without copying back the possible changes */); - + if (send_res < 0) { perror("Sctp send error: "); } - + return (jint)send_res; } @@ -402,19 +403,19 @@ JNIEXPORT jlong JNICALL Java_org_jitsi_sctp4j_Sctp_usersctp_1socket (JNIEnv *env, jclass class, jint localPort) { struct sctp_socket* sctpSocket = malloc(sizeof(struct sctp_socket)); - struct socket *sock; - struct linger linger_opt; + struct socket *sock; + struct linger linger_opt; struct sctp_assoc_value stream_rst; uint32_t nodelay = 1; - size_t i; - + size_t i; + int event_types[] = {SCTP_ASSOC_CHANGE, SCTP_PEER_ADDR_CHANGE, SCTP_SEND_FAILED_EVENT, SCTP_SENDER_DRY_EVENT, SCTP_STREAM_RESET_EVENT}; struct sctp_event event; - + // Register this class as an address for usrsctp. This is used by SCTP to // direct the packets received (by the created socket) to this class. usrsctp_register_address((void*)sctpSocket); @@ -427,27 +428,27 @@ JNIEXPORT jlong JNICALL Java_org_jitsi_sctp4j_Sctp_usersctp_1socket free(sctpSocket); return 0; } - + // Make the socket non-blocking. Connect, close, shutdown etc will not block // the thread waiting for the socket operation to complete. if (usrsctp_set_non_blocking(sock, 1) < 0) { - perror("Failed to set SCTP to non blocking."); - free(sctpSocket); - return 0; + perror("Failed to set SCTP to non blocking."); + free(sctpSocket); + return 0; } // This ensures that the usrsctp close call deletes the association. This // prevents usrsctp from calling OnSctpOutboundPacket with references to - // this class as the address. + // this class as the address. linger_opt.l_onoff = 1; linger_opt.l_linger = 0; if (usrsctp_setsockopt(sock, SOL_SOCKET, SO_LINGER, &linger_opt, sizeof(linger_opt))) { - perror("Failed to set SO_LINGER."); - free(sctpSocket); - return 0; + perror("Failed to set SO_LINGER."); + free(sctpSocket); + return 0; } // Enable stream ID resets. @@ -456,18 +457,18 @@ JNIEXPORT jlong JNICALL Java_org_jitsi_sctp4j_Sctp_usersctp_1socket if (usrsctp_setsockopt(sock, IPPROTO_SCTP, SCTP_ENABLE_STREAM_RESET, &stream_rst, sizeof(stream_rst))) { - perror("Failed to set SCTP_ENABLE_STREAM_RESET."); - free(sctpSocket); - return 0; + perror("Failed to set SCTP_ENABLE_STREAM_RESET."); + free(sctpSocket); + return 0; } // Nagle. if (usrsctp_setsockopt(sock, IPPROTO_SCTP, SCTP_NODELAY, &nodelay, sizeof(nodelay))) { - perror("Failed to set SCTP_NODELAY."); - free(sctpSocket); - return 0; + perror("Failed to set SCTP_NODELAY."); + free(sctpSocket); + return 0; } // Subscribe to SCTP event notifications. @@ -488,7 +489,7 @@ JNIEXPORT jlong JNICALL Java_org_jitsi_sctp4j_Sctp_usersctp_1socket sctpSocket->sock = sock; sctpSocket->localPort = (int)localPort; - + return (jlong)((long)sctpSocket); } @@ -496,12 +497,12 @@ struct sockaddr_conn getSctpSockAddr(int port, void* adr) { struct sockaddr_conn sconn; memset(&sconn, 0, sizeof(struct sockaddr_conn)); - sconn.sconn_family = AF_CONN; + sconn.sconn_family = AF_CONN; #ifdef HAVE_SCONN_LEN - sconn.sconn_len = sizeof(struct sockaddr_conn); + sconn.sconn_len = sizeof(struct sockaddr_conn); #endif - sconn.sconn_port = htons(port); - sconn.sconn_addr = adr; + sconn.sconn_port = htons(port); + sconn.sconn_addr = adr; return sconn; } @@ -514,85 +515,78 @@ JNIEXPORT void JNICALL Java_org_jitsi_sctp4j_Sctp_usrsctp_1listen (JNIEnv *env, jclass class, jlong ptr) { struct sctp_socket* sctpSocket; - struct sockaddr_conn sconn; - + struct sockaddr_conn sconn; + sctpSocket = (struct sctp_socket*)((long)ptr); - + sconn = getSctpSockAddr(sctpSocket->localPort, (void*)sctpSocket); - + /* Bind server socket */ - if (usrsctp_bind( sctpSocket->sock, - (struct sockaddr *) &sconn, - sizeof(struct sockaddr_conn)) < 0) + if (usrsctp_bind( sctpSocket->sock, + (struct sockaddr *) &sconn, + sizeof(struct sockaddr_conn)) < 0) { - perror("usrsctp_bind"); - } - + perror("usrsctp_bind"); + } + /* Make server side passive... */ - if (usrsctp_listen(sctpSocket->sock, 1) < 0) - { - perror("usrsctp_listen"); - } + if (usrsctp_listen(sctpSocket->sock, 1) < 0) + { + perror("usrsctp_listen"); + } } /* * Class: org_jitsi_sctp4j_Sctp * Method: usrsctp_accept - * Signature: (J)V + * Signature: (J)Z */ -JNIEXPORT void JNICALL Java_org_jitsi_sctp4j_Sctp_usrsctp_1accept +JNIEXPORT jboolean JNICALL Java_org_jitsi_sctp4j_Sctp_usrsctp_1accept (JNIEnv *env, jclass class, jlong ptr) { struct sctp_socket* sctpSocket; struct socket* acceptedSocket; - - sctpSocket = (struct sctp_socket*)((long)ptr); - - while((acceptedSocket = usrsctp_accept(sctpSocket->sock, NULL, NULL)) - == NULL) - { - //perror("usrsctp_accept"); - } - usrsctp_close(sctpSocket->sock); - sctpSocket->sock = acceptedSocket; + + sctpSocket = (struct sctp_socket*)((long)ptr); + + if((acceptedSocket = usrsctp_accept(sctpSocket->sock, NULL, NULL)) + == NULL) + { + //perror("usrsctp_accept"); + return JNI_FALSE; + } + usrsctp_close(sctpSocket->sock); + sctpSocket->sock = acceptedSocket; + return JNI_TRUE; } int connectSctp(struct sctp_socket *sctp_socket, int remotePort) { struct socket* sock; - struct sockaddr_conn sconn; - int connect_result; + struct sockaddr_conn sconn; + int connect_result; - sock = sctp_socket->sock; + sock = sctp_socket->sock; sconn = getSctpSockAddr(sctp_socket->localPort, (void*)sctp_socket); - if (usrsctp_bind( sock, - (struct sockaddr *)&sconn, - sizeof(struct sockaddr_conn)) < 0) + if (usrsctp_bind( sock, + (struct sockaddr *)&sconn, + sizeof(struct sockaddr_conn)) < 0) { - perror("usrsctp_bind"); - return 0; - } + perror("usrsctp_bind"); + return 0; + } sconn = getSctpSockAddr(remotePort, (void*)sctp_socket); connect_result = usrsctp_connect( sock, (struct sockaddr *)&sconn, sizeof(struct sockaddr_conn)); -//#ifdef _WIN32 - // if (connect_result == SOCKET_ERROR) - //{ - // perror("usrsctp_connect"); - //return 0; -// } -//#else -// FIXME: on windows "Unknown error" is returned but the socket works anyway... if (connect_result < 0 && errno != SCTP_EINPROGRESS) { perror("usrsctp_connect"); return 0; } -//#endif return 1; } @@ -630,13 +624,13 @@ void closeSocket(struct sctp_socket* sctp) */ JNIEXPORT void JNICALL Java_org_jitsi_sctp4j_Sctp_usrsctp_1close (JNIEnv *env, jclass class, jlong ptr) -{ +{ struct sctp_socket* sctp; sctp = (struct sctp_socket*)((long)ptr); closeSocket(sctp); - free(sctp); + free(sctp); } /* @@ -649,10 +643,10 @@ JNIEXPORT jboolean JNICALL Java_org_jitsi_sctp4j_Sctp_usrsctp_1finish { if(usrsctp_finish() != 0) { - return JNI_TRUE; - } - else - { + return JNI_TRUE; + } + else + { return JNI_FALSE; } } diff --git a/src/native/sctp/org_jitsi_sctp4j_Sctp.h b/src/native/sctp/org_jitsi_sctp4j_Sctp.h index 3bec962c4fa64f292dc5f37f5dd9b01f8b589335..594ddaa6f660e154f60d7593f40d951f079f971a 100644 --- a/src/native/sctp/org_jitsi_sctp4j_Sctp.h +++ b/src/native/sctp/org_jitsi_sctp4j_Sctp.h @@ -9,82 +9,6 @@ extern "C" { #endif #undef org_jitsi_sctp4j_Sctp_MSG_NOTIFICATION #define org_jitsi_sctp4j_Sctp_MSG_NOTIFICATION 8192L -#undef org_jitsi_sctp4j_Sctp_SCTP_ASSOC_CHANGE -#define org_jitsi_sctp4j_Sctp_SCTP_ASSOC_CHANGE 1L -#undef org_jitsi_sctp4j_Sctp_SCTP_PEER_ADDR_CHANGE -#define org_jitsi_sctp4j_Sctp_SCTP_PEER_ADDR_CHANGE 2L -#undef org_jitsi_sctp4j_Sctp_SCTP_REMOTE_ERROR -#define org_jitsi_sctp4j_Sctp_SCTP_REMOTE_ERROR 3L -#undef org_jitsi_sctp4j_Sctp_SCTP_SEND_FAILED -#define org_jitsi_sctp4j_Sctp_SCTP_SEND_FAILED 4L -#undef org_jitsi_sctp4j_Sctp_SCTP_SHUTDOWN_EVENT -#define org_jitsi_sctp4j_Sctp_SCTP_SHUTDOWN_EVENT 5L -#undef org_jitsi_sctp4j_Sctp_SCTP_ADAPTATION_INDICATION -#define org_jitsi_sctp4j_Sctp_SCTP_ADAPTATION_INDICATION 6L -#undef org_jitsi_sctp4j_Sctp_SCTP_PARTIAL_DELIVERY_EVENT -#define org_jitsi_sctp4j_Sctp_SCTP_PARTIAL_DELIVERY_EVENT 7L -#undef org_jitsi_sctp4j_Sctp_SCTP_AUTHENTICATION_EVENT -#define org_jitsi_sctp4j_Sctp_SCTP_AUTHENTICATION_EVENT 8L -#undef org_jitsi_sctp4j_Sctp_SCTP_STREAM_RESET_EVENT -#define org_jitsi_sctp4j_Sctp_SCTP_STREAM_RESET_EVENT 9L -#undef org_jitsi_sctp4j_Sctp_SCTP_SENDER_DRY_EVENT -#define org_jitsi_sctp4j_Sctp_SCTP_SENDER_DRY_EVENT 10L -#undef org_jitsi_sctp4j_Sctp_SCTP_NOTIFICATIONS_STOPPED_EVENT -#define org_jitsi_sctp4j_Sctp_SCTP_NOTIFICATIONS_STOPPED_EVENT 11L -#undef org_jitsi_sctp4j_Sctp_SCTP_ASSOC_RESET_EVENT -#define org_jitsi_sctp4j_Sctp_SCTP_ASSOC_RESET_EVENT 12L -#undef org_jitsi_sctp4j_Sctp_SCTP_STREAM_CHANGE_EVENT -#define org_jitsi_sctp4j_Sctp_SCTP_STREAM_CHANGE_EVENT 13L -#undef org_jitsi_sctp4j_Sctp_SCTP_SEND_FAILED_EVENT -#define org_jitsi_sctp4j_Sctp_SCTP_SEND_FAILED_EVENT 14L -#undef org_jitsi_sctp4j_Sctp_SCTP_COMM_UP -#define org_jitsi_sctp4j_Sctp_SCTP_COMM_UP 1L -#undef org_jitsi_sctp4j_Sctp_SCTP_COMM_LOST -#define org_jitsi_sctp4j_Sctp_SCTP_COMM_LOST 2L -#undef org_jitsi_sctp4j_Sctp_SCTP_RESTART -#define org_jitsi_sctp4j_Sctp_SCTP_RESTART 3L -#undef org_jitsi_sctp4j_Sctp_SCTP_SHUTDOWN_COMP -#define org_jitsi_sctp4j_Sctp_SCTP_SHUTDOWN_COMP 4L -#undef org_jitsi_sctp4j_Sctp_SCTP_CANT_STR_ASSOC -#define org_jitsi_sctp4j_Sctp_SCTP_CANT_STR_ASSOC 5L -#undef org_jitsi_sctp4j_Sctp_SCTP_ASSOC_SUPPORTS_PR -#define org_jitsi_sctp4j_Sctp_SCTP_ASSOC_SUPPORTS_PR 1L -#undef org_jitsi_sctp4j_Sctp_SCTP_ASSOC_SUPPORTS_AUTH -#define org_jitsi_sctp4j_Sctp_SCTP_ASSOC_SUPPORTS_AUTH 2L -#undef org_jitsi_sctp4j_Sctp_SCTP_ASSOC_SUPPORTS_ASCONF -#define org_jitsi_sctp4j_Sctp_SCTP_ASSOC_SUPPORTS_ASCONF 3L -#undef org_jitsi_sctp4j_Sctp_SCTP_ASSOC_SUPPORTS_MULTIBUF -#define org_jitsi_sctp4j_Sctp_SCTP_ASSOC_SUPPORTS_MULTIBUF 4L -#undef org_jitsi_sctp4j_Sctp_SCTP_ASSOC_SUPPORTS_RE_CONFIG -#define org_jitsi_sctp4j_Sctp_SCTP_ASSOC_SUPPORTS_RE_CONFIG 5L -#undef org_jitsi_sctp4j_Sctp_SCTP_ASSOC_SUPPORTS_MAX -#define org_jitsi_sctp4j_Sctp_SCTP_ASSOC_SUPPORTS_MAX 5L -#undef org_jitsi_sctp4j_Sctp_SCTP_ADDR_AVAILABLE -#define org_jitsi_sctp4j_Sctp_SCTP_ADDR_AVAILABLE 1L -#undef org_jitsi_sctp4j_Sctp_SCTP_ADDR_UNREACHABLE -#define org_jitsi_sctp4j_Sctp_SCTP_ADDR_UNREACHABLE 2L -#undef org_jitsi_sctp4j_Sctp_SCTP_ADDR_REMOVED -#define org_jitsi_sctp4j_Sctp_SCTP_ADDR_REMOVED 3L -#undef org_jitsi_sctp4j_Sctp_SCTP_ADDR_ADDED -#define org_jitsi_sctp4j_Sctp_SCTP_ADDR_ADDED 4L -#undef org_jitsi_sctp4j_Sctp_SCTP_ADDR_MADE_PRIM -#define org_jitsi_sctp4j_Sctp_SCTP_ADDR_MADE_PRIM 5L -#undef org_jitsi_sctp4j_Sctp_SCTP_ADDR_CONFIRMED -#define org_jitsi_sctp4j_Sctp_SCTP_ADDR_CONFIRMED 6L -#undef org_jitsi_sctp4j_Sctp_SCTP_STREAM_RESET_INCOMING_SSN -#define org_jitsi_sctp4j_Sctp_SCTP_STREAM_RESET_INCOMING_SSN 1L -#undef org_jitsi_sctp4j_Sctp_SCTP_STREAM_RESET_OUTGOING_SSN -#define org_jitsi_sctp4j_Sctp_SCTP_STREAM_RESET_OUTGOING_SSN 2L -#undef org_jitsi_sctp4j_Sctp_SCTP_STREAM_RESET_DENIED -#define org_jitsi_sctp4j_Sctp_SCTP_STREAM_RESET_DENIED 4L -#undef org_jitsi_sctp4j_Sctp_SCTP_STREAM_RESET_FAILED -#define org_jitsi_sctp4j_Sctp_SCTP_STREAM_RESET_FAILED 8L -#undef org_jitsi_sctp4j_Sctp_SCTP_STREAM_CHANGED_DENIED -#define org_jitsi_sctp4j_Sctp_SCTP_STREAM_CHANGED_DENIED 16L -#undef org_jitsi_sctp4j_Sctp_SCTP_STREAM_RESET_INCOMING -#define org_jitsi_sctp4j_Sctp_SCTP_STREAM_RESET_INCOMING 1L -#undef org_jitsi_sctp4j_Sctp_SCTP_STREAM_RESET_OUTGOING -#define org_jitsi_sctp4j_Sctp_SCTP_STREAM_RESET_OUTGOING 2L /* * Class: org_jitsi_sctp4j_Sctp * Method: usrsctp_init @@ -120,9 +44,9 @@ JNIEXPORT void JNICALL Java_org_jitsi_sctp4j_Sctp_usrsctp_1listen /* * Class: org_jitsi_sctp4j_Sctp * Method: usrsctp_accept - * Signature: (J)V + * Signature: (J)Z */ -JNIEXPORT void JNICALL Java_org_jitsi_sctp4j_Sctp_usrsctp_1accept +JNIEXPORT jboolean JNICALL Java_org_jitsi_sctp4j_Sctp_usrsctp_1accept (JNIEnv *, jclass, jlong); /* diff --git a/src/org/jitsi/sctp4j/DirectLink.java b/src/org/jitsi/sctp4j/DirectLink.java index 5498b0e36d1026f5011c398d46bde22542b902bb..c9808070c55583a652668ce67f1d5d9705530422 100644 --- a/src/org/jitsi/sctp4j/DirectLink.java +++ b/src/org/jitsi/sctp4j/DirectLink.java @@ -6,6 +6,10 @@ */ package org.jitsi.sctp4j; +import org.jitsi.util.*; + +import java.io.*; + /** * A direct connection that passes packets between two <tt>SctpSocket</tt> * instances. @@ -15,6 +19,11 @@ public class DirectLink implements NetworkLink { + /** + * The logger used by this class instances. + */ + private static final Logger logger = Logger.getLogger(DirectLink.class); + /** * Instance "a" of this direct connection. */ @@ -35,13 +44,21 @@ public DirectLink(SctpSocket a, SctpSocket b) * {@inheritDoc} */ public void onConnOut(final SctpSocket s, final byte[] packet) + throws IOException { final SctpSocket dest = s == this.a ? this.b : this.a; new Thread(new Runnable() { public void run() { - dest.onConnIn(packet); + try + { + dest.onConnIn(packet); + } + catch (IOException e) + { + logger.error(e, e); + } } }).start(); } diff --git a/src/org/jitsi/sctp4j/NetworkLink.java b/src/org/jitsi/sctp4j/NetworkLink.java index 54a117c21f1aaca1c5d7c7608f2de4ab57e34f5a..dbd030fae60894a6e56145470a776d2f97c3b90f 100644 --- a/src/org/jitsi/sctp4j/NetworkLink.java +++ b/src/org/jitsi/sctp4j/NetworkLink.java @@ -6,6 +6,8 @@ */ package org.jitsi.sctp4j; +import java.io.*; + /** * Interface used by {@link SctpSocket} for sending network packets. * @@ -21,6 +23,9 @@ public interface NetworkLink * network packet. * @param s source <tt>SctpSocket</tt> instance. * @param packet network packet buffer. + * + * @throws java.io.IOException in case of transport error. */ - public void onConnOut(final SctpSocket s, final byte[] packet); + public void onConnOut(final SctpSocket s, final byte[] packet) + throws IOException; } diff --git a/src/org/jitsi/sctp4j/SampleClient.java b/src/org/jitsi/sctp4j/SampleClient.java index cbe4ce50b747a02b9a16351994a07317b54ee973..783e6ff34eb6ec99cd45f14123c729f7e4a7c1e4 100644 --- a/src/org/jitsi/sctp4j/SampleClient.java +++ b/src/org/jitsi/sctp4j/SampleClient.java @@ -30,7 +30,7 @@ public static void main(String[] args) throws Exception int remotePort = 48001; int remoteSctpPort = 5001; - Sctp.init(0); + Sctp.init(); final SctpSocket client = Sctp.createSocket(localSctpPort); diff --git a/src/org/jitsi/sctp4j/SampleLoop.java b/src/org/jitsi/sctp4j/SampleLoop.java index 1314d27ee33a481289877c73fa763334512bb69f..23d62bb36cb4c0b17f8a689d15b46966faa1a3d6 100644 --- a/src/org/jitsi/sctp4j/SampleLoop.java +++ b/src/org/jitsi/sctp4j/SampleLoop.java @@ -8,6 +8,8 @@ import org.jitsi.util.*; +import java.io.*; + /** * Sample that uses two <tt>SctpSocket</tt>s with {@link DirectLink}. * @@ -22,7 +24,7 @@ public class SampleLoop public static void main(String[] args) throws Exception { - Sctp.init(0); + Sctp.init(); final SctpSocket server = Sctp.createSocket(5001); final SctpSocket client = Sctp.createSocket(5002); @@ -40,19 +42,21 @@ public static void main(String[] args) throws Exception { public void run() { - if(!client.connect(server.getPort())) + try { - // FIXME: Unknown error returned on Windows, - // but it works after that - //return; - } + client.connect(server.getPort()); + logger.info("Client: connect"); + + try { Thread.sleep(1000); } catch(Exception e) { } + + int sent = client.send(new byte[200], false, 0, 0); + logger.info("Client sent: " + sent); - logger.info("Client: connect"); - - try { Thread.sleep(1000); } catch(Exception e) { } - - int sent = client.send(new byte[200], false, 0, 0); - logger.info("Client sent: " + sent); + } + catch (IOException e) + { + logger.error(e, e); + } } } ).start(); diff --git a/src/org/jitsi/sctp4j/SampleServer.java b/src/org/jitsi/sctp4j/SampleServer.java index d38ddc114e535a6e466ab210eadca0aadbffee3f..278871dadea6c98c40734b0998f6a6a450745e77 100644 --- a/src/org/jitsi/sctp4j/SampleServer.java +++ b/src/org/jitsi/sctp4j/SampleServer.java @@ -29,7 +29,7 @@ public static void main(String[] args) throws Exception String remoteAddr = "127.0.0.1"; int remotePort = 48002; - Sctp.init(0); + Sctp.init(); final SctpSocket sock1 = Sctp.createSocket(localSctpPort); @@ -41,7 +41,10 @@ public static void main(String[] args) throws Exception sock1.listen(); - sock1.accept(); + while (!sock1.accept()) + { + Thread.sleep(100); + } sock1.setDataCallback(new SctpDataCallback() { diff --git a/src/org/jitsi/sctp4j/Sctp.java b/src/org/jitsi/sctp4j/Sctp.java index 097f029939fcbd99d50b9655c9b83aaaa656cc7d..af491b75f21719c0b933275dc37b10c1ed934631 100644 --- a/src/org/jitsi/sctp4j/Sctp.java +++ b/src/org/jitsi/sctp4j/Sctp.java @@ -8,6 +8,7 @@ import org.jitsi.util.*; +import java.io.*; import java.util.*; /** @@ -43,101 +44,33 @@ public class Sctp */ public static final int MSG_NOTIFICATION = 0x2000; - /******** Notifications **************/ - - /* notification types */ - public static final int SCTP_ASSOC_CHANGE = 0x0001; - public static final int SCTP_PEER_ADDR_CHANGE = 0x0002; - public static final int SCTP_REMOTE_ERROR = 0x0003; - public static final int SCTP_SEND_FAILED = 0x0004; - public static final int SCTP_SHUTDOWN_EVENT = 0x0005; - public static final int SCTP_ADAPTATION_INDICATION = 0x0006; - public static final int SCTP_PARTIAL_DELIVERY_EVENT = 0x0007; - public static final int SCTP_AUTHENTICATION_EVENT = 0x0008; - public static final int SCTP_STREAM_RESET_EVENT = 0x0009; - public static final int SCTP_SENDER_DRY_EVENT = 0x000a; - public static final int SCTP_NOTIFICATIONS_STOPPED_EVENT = 0x000b; - public static final int SCTP_ASSOC_RESET_EVENT = 0x000c; - public static final int SCTP_STREAM_CHANGE_EVENT = 0x000d; - public static final int SCTP_SEND_FAILED_EVENT = 0x000e; - - /* notification event structures */ - - /* association change event */ - /*struct sctp_assoc_change { - uint16_t sac_type; - uint16_t sac_flags; - uint32_t sac_length; - uint16_t sac_state; - uint16_t sac_error; - uint16_t sac_outbound_streams; - uint16_t sac_inbound_streams; - sctp_assoc_t sac_assoc_id; - uint8_t sac_info[]; // not available yet - };*/ - - /* sac_state values */ - public static final int SCTP_COMM_UP = 0x0001; - public static final int SCTP_COMM_LOST = 0x0002; - public static final int SCTP_RESTART = 0x0003; - public static final int SCTP_SHUTDOWN_COMP = 0x0004; - public static final int SCTP_CANT_STR_ASSOC = 0x0005; - - /* sac_info values */ - public static final int SCTP_ASSOC_SUPPORTS_PR = 0x01; - public static final int SCTP_ASSOC_SUPPORTS_AUTH = 0x02; - public static final int SCTP_ASSOC_SUPPORTS_ASCONF = 0x03; - public static final int SCTP_ASSOC_SUPPORTS_MULTIBUF = 0x04; - public static final int SCTP_ASSOC_SUPPORTS_RE_CONFIG = 0x05; - public static final int SCTP_ASSOC_SUPPORTS_MAX = 0x05; - - /* Address event */ - /*struct sctp_paddr_change { - uint16_t spc_type; - uint16_t spc_flags; - uint32_t spc_length; - struct sockaddr_storage spc_aaddr; - uint32_t spc_state; - uint32_t spc_error; - sctp_assoc_t spc_assoc_id; - uint8_t spc_padding[4]; - };*/ - - /* paddr state values */ - public static final int SCTP_ADDR_AVAILABLE = 0x0001; - public static final int SCTP_ADDR_UNREACHABLE = 0x0002; - public static final int SCTP_ADDR_REMOVED = 0x0003; - public static final int SCTP_ADDR_ADDED = 0x0004; - public static final int SCTP_ADDR_MADE_PRIM = 0x0005; - public static final int SCTP_ADDR_CONFIRMED = 0x0006; - - /* flags in stream_reset_event (strreset_flags) */ - public static final int SCTP_STREAM_RESET_INCOMING_SSN = 0x0001; - public static final int SCTP_STREAM_RESET_OUTGOING_SSN = 0x0002; - public static final int SCTP_STREAM_RESET_DENIED = 0x0004; - public static final int SCTP_STREAM_RESET_FAILED = 0x0008; - public static final int SCTP_STREAM_CHANGED_DENIED = 0x0010; - - public static final int SCTP_STREAM_RESET_INCOMING = 0x00000001; - public static final int SCTP_STREAM_RESET_OUTGOING = 0x00000002; + /** + * Track the number of currently running SCTP engines. + * Each engine calls {@link #init()} on startup and {@link #finish()} + * on shutdown. We want {@link #init()} to be effectively called only when + * there are 0 engines currently running and {@link #finish()} when the last + * one is performing a shutdown. + */ + private static int sctpEngineCount; /** - * Track initialization state of native counterpart. + * FIXME: Remove once usrsctp_finish is fixed */ private static boolean initialized; /** * Initializes native SCTP counterpart. - * @param port UDP encapsulation port. */ - public static synchronized void init(int port) + public static synchronized void init() { - if(initialized) - return; - - usrsctp_init(port); - - initialized = true; + // Skip if we're not the first one + //if(sctpEngineCount++ > 0) + // return; + if(!initialized) + { + usrsctp_init(0); + initialized = true; + } } /** @@ -165,7 +98,7 @@ public static SctpSocket createSocket(int localPort) if(ptr != 0) { SctpSocket sock = new SctpSocket(ptr, localPort); - sockets.put(ptr, sock); + sockets.put(ptr, sock); return sock; } else @@ -203,7 +136,7 @@ native static int usrsctp_send(long socketPtr, byte[] data, * Waits for incoming connection. * @param socketPtr native socket pointer. */ - native static void usrsctp_accept(long socketPtr); + native static boolean usrsctp_accept(long socketPtr); /** * Connects SCTP socket to remote socket on given SCTP port. @@ -232,28 +165,50 @@ static void closeSocket(Long ptr) /** * Disposes of the resources held by native counterpart. + * + * @throws IOException if usrsctp stack has failed to shutdown. */ public static synchronized void finish() + throws IOException { - if(!initialized) - return; + // Skip if we're not the last one + //if(--sctpEngineCount > 0) + // return; - try - { + //try + //{ // FIXME: fix this loop ? // it comes from SCTP samples written in C - while(!usrsctp_finish()) - { - Thread.sleep(50); - } - initialized = false; - } - catch(InterruptedException e) - { - logger.error("Finish interrupted", e); - Thread.currentThread().interrupt(); - } + // Retry limited amount of times + /* + FIXME: usrsctp issue: + SCTP stack is now never deinitialized in order to prevent deadlock + in usrsctp_finish. + https://code.google.com/p/webrtc/issues/detail?id=2749 + + final int CLOSE_RETRY_COUNT = 20; + + for(int i=0; i < CLOSE_RETRY_COUNT; i++) + { + if(usrsctp_finish()) + return; + + Thread.sleep(50); + }*/ + + //FIXME: after throwing we might end up with other SCTP users broken + // (or stack not disposed) at this point because engine count will + // be out of sync for the purpose of calling init() and finish() + // methods. + // throw new IOException("Failed to shutdown usrsctp stack" + + // " after 20 retries"); + //} + //catch(InterruptedException e) + //{ + // logger.error("Finish interrupted", e); + // Thread.currentThread().interrupt(); + //} } /** @@ -269,8 +224,9 @@ public static synchronized void finish() * @param data buffer holding packet data * @param tos type of service??? * @param set_df use IP don't fragment option + * @return 0 if the packet has been successfully sent or -1 otherwise. */ - public static void onSctpOutboundPacket(long socketAddr, byte[] data, + public static int onSctpOutboundPacket(long socketAddr, byte[] data, int tos, int set_df) { // FIXME: handle tos and set_df @@ -278,11 +234,12 @@ public static void onSctpOutboundPacket(long socketAddr, byte[] data, SctpSocket socket = sockets.get(socketAddr); if(socket != null) { - socket.onSctpOut(data, tos, set_df); + return socket.onSctpOut(data, tos, set_df); } else { logger.error("No SctpSocket found for ptr: " + socketAddr); + return -1; } } @@ -298,15 +255,34 @@ public static void onSctpOutboundPacket(long socketAddr, byte[] data, * @param context * @param flags */ - public static void onSctpInboundPacket(long socketAddr, - byte[] data, - int sid, int ssn, int tsn, - long ppid, int context, int flags) + public static void onSctpInboundPacket( + long socketAddr, final byte[] data, final int sid, + final int ssn, final int tsn, final long ppid, + final int context, final int flags) { - SctpSocket socket = sockets.get(socketAddr); + final SctpSocket socket = sockets.get(socketAddr); if(socket != null) { - socket.onSctpIn(data, sid, ssn, tsn, ppid, context, flags); + // FIXME: fix threads + new Thread(new Runnable() + { + @Override + public void run() + { + if((flags & MSG_NOTIFICATION) > 0) + { + final SctpNotification notification + = SctpNotification.parse(data); + + socket.onNotification(notification); + } + else + { + socket.onSctpIn( + data, sid, ssn, tsn, ppid, context, flags); + } + } + }).start(); } else { diff --git a/src/org/jitsi/sctp4j/SctpNotification.java b/src/org/jitsi/sctp4j/SctpNotification.java new file mode 100644 index 0000000000000000000000000000000000000000..4f9120b37fe0c049823a42d4ccb3737601d4d5af --- /dev/null +++ b/src/org/jitsi/sctp4j/SctpNotification.java @@ -0,0 +1,438 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package org.jitsi.sctp4j; + +import java.nio.*; + +/** + * Partially implemented SCTP notifications for which the native wrapper + * currently registers for. + * + * @author Pawel Domas + */ +public class SctpNotification +{ + /******** Notifications **************/ + /* + union sctp_notification { + struct sctp_tlv { + uint16_t sn_type; + uint16_t sn_flags; + uint32_t sn_length; + } sn_header; + struct sctp_assoc_change sn_assoc_change; + struct sctp_paddr_change sn_paddr_change; + struct sctp_remote_error sn_remote_error; + struct sctp_shutdown_event sn_shutdown_event; + struct sctp_adaptation_event sn_adaptation_event; + struct sctp_pdapi_event sn_pdapi_event; + struct sctp_authkey_event sn_auth_event; + struct sctp_sender_dry_event sn_sender_dry_event; + struct sctp_send_failed_event sn_send_failed_event; + struct sctp_stream_reset_event sn_strreset_event; + struct sctp_assoc_reset_event sn_assocreset_event; + struct sctp_stream_change_event sn_strchange_event; + }; + */ + + /* notification types */ + + public static final int SCTP_ASSOC_CHANGE = 0x0001; + public static final int SCTP_PEER_ADDR_CHANGE = 0x0002; + public static final int SCTP_REMOTE_ERROR = 0x0003; + public static final int SCTP_SEND_FAILED = 0x0004; + public static final int SCTP_SHUTDOWN_EVENT = 0x0005; + public static final int SCTP_ADAPTATION_INDICATION = 0x0006; + public static final int SCTP_PARTIAL_DELIVERY_EVENT = 0x0007; + public static final int SCTP_AUTHENTICATION_EVENT = 0x0008; + public static final int SCTP_STREAM_RESET_EVENT = 0x0009; + /** + * When the SCTP implementation has no user data anymore to send or + * retransmit this notification is given to the user. + */ + public static final int SCTP_SENDER_DRY_EVENT = 0x000a; + public static final int SCTP_NOTIFICATIONS_STOPPED_EVENT = 0x000b; + public static final int SCTP_ASSOC_RESET_EVENT = 0x000c; + public static final int SCTP_STREAM_CHANGE_EVENT = 0x000d; + public static final int SCTP_SEND_FAILED_EVENT = 0x000e; + + public final int sn_type; + public final int sn_flags; + public final int sn_length; + + protected final ByteBuffer buffer; + + private SctpNotification(byte[] data) + { + // FIXME: unsigned types + this.buffer = ByteBuffer.wrap(data); + buffer.order(ByteOrder.LITTLE_ENDIAN); + + this.sn_type = buffer.getChar(); + this.sn_flags = buffer.getChar(); + this.sn_length = buffer.getInt(); + } + + @Override + public String toString() + { + switch (sn_type) + { + case SCTP_ASSOC_CHANGE: + return "SCTP_ASSOC_CHANGE"; + case SCTP_PEER_ADDR_CHANGE: + return "SCTP_PEER_ADDR_CHANGE"; + case SCTP_REMOTE_ERROR: + return "SCTP_REMOTE_ERROR"; + case SCTP_SEND_FAILED: + return "SCTP_SEND_FAILED"; + case SCTP_SHUTDOWN_EVENT: + return "SCTP_SHUTDOWN_EVENT"; + case SCTP_ADAPTATION_INDICATION: + return "SCTP_ADAPTATION_INDICATION"; + case SCTP_PARTIAL_DELIVERY_EVENT: + return "SCTP_PARTIAL_DELIVERY_EVENT"; + case SCTP_AUTHENTICATION_EVENT: + return "SCTP_AUTHENTICATION_EVENT"; + case SCTP_STREAM_RESET_EVENT: + return "SCTP_STREAM_RESET_EVENT"; + case SCTP_SENDER_DRY_EVENT: + return "SCTP_SENDER_DRY_EVENT"; + case SCTP_NOTIFICATIONS_STOPPED_EVENT: + return "SCTP_NOTIFICATIONS_STOPPED_EVENT"; + case SCTP_ASSOC_RESET_EVENT: + return "SCTP_ASSOC_RESET_EVENT"; + case SCTP_STREAM_CHANGE_EVENT: + return "SCTP_STREAM_CHANGE_EVENT"; + case SCTP_SEND_FAILED_EVENT: + return "SCTP_SEND_FAILED_EVENT"; + } + return "SCTP_NOTIFICATION_0x"+Integer.toHexString(sn_type); + } + + public static SctpNotification parse(byte[] data) + { + int type = data[0] | (data[1] << 8); + switch (type) + { + case SCTP_ASSOC_CHANGE: + return new AssociationChange(data); + case SCTP_PEER_ADDR_CHANGE: + return new PeerAddressChange(data); + case SCTP_SEND_FAILED: + return new SendFailed(data); + case SCTP_SENDER_DRY_EVENT: + return new SenderDry(data); + case SCTP_STREAM_RESET_EVENT: + return new StreamReset(data); + default: + return new SctpNotification(data); + } + } + + /** + * Association change event + * struct sctp_assoc_change { + * uint16_t sac_type; + * uint16_t sac_flags; + * uint32_t sac_length; + * uint16_t sac_state; + * uint16_t sac_error; + * uint16_t sac_outbound_streams; + * uint16_t sac_inbound_streams; + * sctp_assoc_t sac_assoc_id; //uint32_t + * uint8_t sac_info[]; // not available yet + * }; + */ + public static class AssociationChange + extends SctpNotification + { + /* sac_state values */ + public static final int SCTP_COMM_UP = 0x0001; + public static final int SCTP_COMM_LOST = 0x0002; + public static final int SCTP_RESTART = 0x0003; + public static final int SCTP_SHUTDOWN_COMP = 0x0004; + public static final int SCTP_CANT_STR_ASSOC = 0x0005; + + /* sac_info values */ + public static final int SCTP_ASSOC_SUPPORTS_PR = 0x01; + public static final int SCTP_ASSOC_SUPPORTS_AUTH = 0x02; + public static final int SCTP_ASSOC_SUPPORTS_ASCONF = 0x03; + public static final int SCTP_ASSOC_SUPPORTS_MULTIBUF = 0x04; + public static final int SCTP_ASSOC_SUPPORTS_RE_CONFIG = 0x05; + public static final int SCTP_ASSOC_SUPPORTS_MAX = 0x05; + + public final int state; + + public final int error; + + public final int outboundStreams; + + public final int inboundStreams; + + public final long assocId; + + private AssociationChange(byte[] data) + { + super(data); + // FIXME: UINT types + this.state = buffer.getChar(); + this.error = buffer.getChar(); + this.outboundStreams = buffer.getChar(); + this.inboundStreams = buffer.getChar(); + this.assocId = buffer.getInt(); + } + + @Override + public String toString() + { + String str = super.toString(); + + str += ":assocId:0x" + Long.toHexString(assocId); + + // type + switch (state) + { + case SCTP_COMM_UP: + str += ",COMM_UP"; + break; + case SCTP_COMM_LOST: + str += ",COMM_LOST"; + break; + case SCTP_RESTART: + str += ",RESTART"; + break; + case SCTP_SHUTDOWN_COMP: + str += ",SHUTDOWN_COMP"; + break; + case SCTP_CANT_STR_ASSOC: + str += ",CANT_STR_ASSOC"; + break; + default: + str += ",0x" + Integer.toHexString(state); + break; + } + // in/out streams supported + str += ",(in/out)(" + inboundStreams + + "/" + outboundStreams +")"; + // error + str += ",err0x" + Integer.toHexString(error); + + return str; + } + } + + /** + * Address event + * struct sctp_paddr_change { + * uint16_t spc_type; + * uint16_t spc_flags; + * uint32_t spc_length; + * struct sockaddr_storage spc_aaddr; + * uint32_t spc_state; + * uint32_t spc_error; + * sctp_assoc_t spc_assoc_id; //uint32_t + * uint8_t spc_padding[4]; + * }; + * + */ + public static class PeerAddressChange + extends SctpNotification + { + /* paddr state values */ + public static final int SCTP_ADDR_AVAILABLE = 0x0001; + public static final int SCTP_ADDR_UNREACHABLE = 0x0002; + public static final int SCTP_ADDR_REMOVED = 0x0003; + public static final int SCTP_ADDR_ADDED = 0x0004; + public static final int SCTP_ADDR_MADE_PRIM = 0x0005; + public static final int SCTP_ADDR_CONFIRMED = 0x0006; + + public final int state; + + public final long error; + + public final long assocId; + + private PeerAddressChange(byte[] data) + { + super(data); + + // Skip struct sockaddr_storage + int sockAddrStorageLen = data.length - 24; + buffer.position(buffer.position() + sockAddrStorageLen); + + this.state = buffer.getInt(); + this.error = buffer.getInt(); + this.assocId = buffer.getInt(); + } + + @Override + public String toString() + { + String base = super.toString(); + + base += ",assocId:0x" + Long.toHexString(assocId); + + switch (state) + { + case SCTP_ADDR_AVAILABLE: + base += ",ADDR_AVAILABLE"; + break; + case SCTP_ADDR_UNREACHABLE: + base += ",ADDR_UNREACHABLE"; + break; + case SCTP_ADDR_REMOVED: + base += ",ADDR_REMOVED"; + break; + case SCTP_ADDR_ADDED: + base += ",ADDR_ADDED"; + break; + case SCTP_ADDR_MADE_PRIM: + base += ",ADDR_MADE_PRIM"; + break; + case SCTP_ADDR_CONFIRMED: + base += ",ADDR_CONFIRMED"; + break; + default: + base += "," + Integer.toHexString(state); + break; + } + + // Error + base += ",err:" + Long.toHexString(error); + + return base; + } + } + + /** + * SCTP send failed event + * + * struct sctp_send_failed_event { + * uint16_t ssfe_type; + * uint16_t ssfe_flags; + * uint32_t ssfe_length; + * uint32_t ssfe_error; + * struct sctp_sndinfo ssfe_info; + * sctp_assoc_t ssfe_assoc_id; + * uint8_t ssfe_data[]; + * }; + * + * struct sctp_sndinfo { + * uint16_t snd_sid; + * uint16_t snd_flags; + * uint32_t snd_ppid; + * uint32_t snd_context; + * sctp_assoc_t snd_assoc_id; // uint32 + * }; + * + */ + public static class SendFailed + extends SctpNotification + { + /* flag that indicates state of data */ + + /** + * Inqueue never on wire. + */ + public static final int SCTP_DATA_UNSENT = 0x0001; + + /** + * On wire at failure. + */ + public static final int SCTP_DATA_SENT = 0x0002; + + public final long error; + + private SendFailed(byte[] data) + { + super(data); + + this.error = buffer.getInt(); + } + + @Override + public String toString() + { + String base = super.toString(); + + if((sn_flags & SCTP_DATA_SENT) > 0) + base += ",DATA_SENT"; + + if((sn_flags & SCTP_DATA_UNSENT) > 0) + base += ",DATA_UNSENT"; + + // error + base += ",err0x" + Long.toHexString(error); + + return base; + } + } + + /** + * SCTP sender dry event + * + * struct sctp_sender_dry_event { + * uint16_t sender_dry_type; + * uint16_t sender_dry_flags; + * uint32_t sender_dry_length; + * sctp_assoc_t sender_dry_assoc_id; + * }; + */ + public static class SenderDry + extends SctpNotification + { + private final long assocId; + + private SenderDry(byte[] data) + { + super(data); + + this.assocId = buffer.getInt(); + } + + @Override + public String toString() + { + String base = super.toString(); + + base += ",assocID:0x" + Long.toHexString(assocId); + + return base; + } + } + + /** + * Stream reset event + * + * struct sctp_stream_reset_event { + * uint16_t strreset_type; + * uint16_t strreset_flags; + * uint32_t strreset_length; + * sctp_assoc_t strreset_assoc_id; + * uint16_t strreset_stream_list[]; + * }; + */ + public static class StreamReset + extends SctpNotification + { + /* flags in stream_reset_event (strreset_flags) */ + public static final int SCTP_STREAM_RESET_INCOMING_SSN = 0x0001; + public static final int SCTP_STREAM_RESET_OUTGOING_SSN = 0x0002; + public static final int SCTP_STREAM_RESET_DENIED = 0x0004; + public static final int SCTP_STREAM_RESET_FAILED = 0x0008; + public static final int SCTP_STREAM_CHANGED_DENIED = 0x0010; + + public static final int SCTP_STREAM_RESET_INCOMING = 0x00000001; + public static final int SCTP_STREAM_RESET_OUTGOING = 0x00000002; + + private StreamReset(byte[] data) + { + super(data); + } + } +} diff --git a/src/org/jitsi/sctp4j/SctpSocket.java b/src/org/jitsi/sctp4j/SctpSocket.java index 0518562e7b3ea5004b61c3e59335697165b870a2..e04b417f9f137f217bd0ffc6809ae38246f225fd 100644 --- a/src/org/jitsi/sctp4j/SctpSocket.java +++ b/src/org/jitsi/sctp4j/SctpSocket.java @@ -8,6 +8,8 @@ import org.jitsi.util.*; +import java.io.*; + /** * SCTP socket implemented using "usrsctp" lib. * @@ -40,6 +42,20 @@ public class SctpSocket */ private SctpDataCallback dataCallback; + /** + * SCTP notification listener. + */ + private NotificationListener notificationListener + = new NotificationListener() + { + @Override + public void onSctpNotification(SctpSocket socket, + SctpNotification notification) + { + logger.trace("SctpSocket(0x" + socketPtr +")event: " + notification); + } + }; + /** * Creates new instance of <tt>SctpSocket</tt>. * @param socketPtr native socket pointer. @@ -47,6 +63,9 @@ public class SctpSocket */ SctpSocket(long socketPtr, int localPort) { + if(socketPtr == 0) + throw new NullPointerException(); + this.socketPtr = socketPtr; this.localPort = localPort; } @@ -73,27 +92,46 @@ public int getPort() /** * Makes SCTP socket passive. */ - public void listen() + public synchronized void listen() + throws IOException { + checkIsPointerValid(); + Sctp.usrsctp_listen(socketPtr); } /** * Accepts incoming SCTP connection. + * FIXME: + * Usrscp is currently configured to work in non blocking mode thus this + * method should be polled in intervals. + * + * @return <tt>true</tt> if we have accepted incoming connection + * successfully. */ - public void accept() + public synchronized boolean accept() + throws IOException { - Sctp.usrsctp_accept(socketPtr); + checkIsPointerValid(); + + return Sctp.usrsctp_accept(socketPtr); } /** * Initializes SCTP connection by sending INIT message. * @param remotePort remote SCTP port. - * @return <tt>true</tt> on success. + * @throws java.io.IOException if this socket is closed or an error occurs + * while trying to connect the socket. */ - public boolean connect(int remotePort) + public synchronized void connect(int remotePort) + throws IOException { - return Sctp.usrsctp_connect(socketPtr, remotePort); + checkIsPointerValid(); + + if(!Sctp.usrsctp_connect(socketPtr, remotePort)) + { + throw new IOException("Failed to connect SCTP"); + } } /** @@ -106,8 +144,13 @@ public boolean connect(int remotePort) * @param ppid payload protocol identifier * @return sent bytes count or <tt>-1</tt> in case of an error. */ - public int send(byte[] data, boolean ordered, int sid, int ppid) + public synchronized int send(byte[] data, boolean ordered, + int sid, int ppid) + throws IOException { + // Prevent JVM crash by throwing IOException + checkIsPointerValid(); + return Sctp.usrsctp_send(socketPtr, data, ordered, sid, ppid); } @@ -117,10 +160,25 @@ public int send(byte[] data, boolean ordered, int sid, int ppid) * @param packet network packet buffer. * @param tos type of service??? * @param set_df use IP don't fragment option + * @return 0 if the packet was successfully sent or -1 otherwise. */ - void onSctpOut(byte[] packet, int tos, int set_df) + int onSctpOut(byte[] packet, int tos, int set_df) { - this.link.onConnOut(this, packet); + if(link == null) + return -1; + + try + { + this.link.onConnOut(this, packet); + + return 0; + } + catch (IOException e) + { + logger.error( + "Error while sending packet trough the link: " + link, e); + } + return -1; } /** @@ -158,8 +216,15 @@ public void setDataCallback(SctpDataCallback callback) * Call this method to pass network packets received on the link. * @param packet network packet received. */ - public void onConnIn(byte[] packet) + public synchronized void onConnIn(byte[] packet) + throws IOException { + if(packet == null) + throw new NullPointerException("packet"); + + // Prevent JVM crash by throwing IOException + checkIsPointerValid(); + //debugSctpPacket(packet); Sctp.onConnIn(socketPtr, packet); @@ -169,7 +234,7 @@ public void onConnIn(byte[] packet) * Closes this socket. After call to this method this instance MUST NOT be * used. */ - public void close() + public synchronized void close() { if(socketPtr != 0) { @@ -178,6 +243,42 @@ public void close() socketPtr = 0; } } + + /** + * Checks if {@link #socketPtr} is not null. Otherwise throws + * <tt>IOException</tt>. + * + * @throws IOException in case this socket pointer is invalid. + */ + private void checkIsPointerValid() + throws IOException + { + if(socketPtr == 0) + { + throw new IOException("Socket is closed"); + } + } + + /** + * Sets the listener that will be notified about SCTP event. + * @param l the {@link NotificationListener} to set. + */ + public void setNotificationListener(NotificationListener l) + { + this.notificationListener = l; + } + + /** + * Fired when usrsctp stack sends notification. + * @param notification the <tt>SctpNotification</tt> triggered. + */ + void onNotification(SctpNotification notification) + { + if(notificationListener != null) + { + notificationListener.onSctpNotification(this, notification); + } + } public synchronized static void debugSctpPacket(byte[] packet, String id) { @@ -351,4 +452,18 @@ private static int bytes_to_short(byte[] buffer, int offset) | sByte)) & 0xFFFF; } + + /** + * Interface used to listen for SCTP notifications on specific socket. + */ + public interface NotificationListener + { + /** + * Fired when usrsctp stack sends notification. + * @param socket the {@link SctpSocket} notification source. + * @param notification the <tt>SctpNotification</tt> triggered. + */ + void onSctpNotification(SctpSocket socket, + SctpNotification notification); + } } diff --git a/src/org/jitsi/sctp4j/UdpLink.java b/src/org/jitsi/sctp4j/UdpLink.java index 337cf1bbd5349e3029baefc6d784c5970c785882..b26832f89288fcd134b903014119ef7da0cd98fa 100644 --- a/src/org/jitsi/sctp4j/UdpLink.java +++ b/src/org/jitsi/sctp4j/UdpLink.java @@ -103,19 +103,13 @@ public void run() */ @Override public void onConnOut(final SctpSocket s, final byte[] packetData) + throws IOException { - try - { - DatagramPacket packet - = new DatagramPacket( packetData, - packetData.length, - remoteIp, - remotePort); - udpSocket.send(packet); - } - catch(IOException e) - { - logger.error("Error while trying to send SCTP packet", e); - } + DatagramPacket packet + = new DatagramPacket( packetData, + packetData.length, + remoteIp, + remotePort); + udpSocket.send(packet); } } diff --git a/test/org/jitsi/sctp4j/SctpNativeWrapperTest.java b/test/org/jitsi/sctp4j/SctpNativeWrapperTest.java new file mode 100644 index 0000000000000000000000000000000000000000..8069fe146b64e41518ba6b9710c9b78f7d4f8da1 --- /dev/null +++ b/test/org/jitsi/sctp4j/SctpNativeWrapperTest.java @@ -0,0 +1,222 @@ +/* + * Jitsi Videobridge, OpenSource video conferencing. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package org.jitsi.sctp4j; + +import org.junit.*; +import org.junit.runner.*; +import org.junit.runners.*; + +import java.io.*; + +import static org.junit.Assert.fail; + +/** + * Test for SCTP native wrapper. + * + * @author Pawel Domas + */ +@RunWith(JUnit4.class) +public class SctpNativeWrapperTest +{ + /** + * Tested socket instance. + */ + private SctpSocket testSocket; + + @Before + public void setUp() + { + Sctp.init(); + + testSocket = Sctp.createSocket(5000); + } + + @After + public void tearDown() + throws IOException + { + testSocket.close(); + + Sctp.finish(); + } + + /** + * Tests against JVM crash when operations are executed on closed socket. + */ + @Test + public void throwIOonClosedSocket() + { + + testSocket.close(); + + // SctpSocket.send + testIOException(new IOExceptionRun() + { + @Override + public void run() + throws IOException + { + testSocket.send(new byte[]{1,2,3}, false, 1, 1); + } + }); + + // SctpSocket.accept + testIOException(new IOExceptionRun() + { + @Override + public void run() + throws IOException + { + testSocket.accept(); + } + }); + + // SctpSocket.listen + testIOException(new IOExceptionRun() + { + @Override + public void run() + throws IOException + { + testSocket.listen(); + } + }); + + // SctpSocket.connect + testIOException(new IOExceptionRun() + { + @Override + public void run() + throws IOException + { + testSocket.connect(5001); + } + }); + + // SctpSocket.onConnIn + testIOException(new IOExceptionRun() + { + @Override + public void run() + throws IOException + { + testSocket.onConnIn(new byte[]{1, 2, 3, 4, 5}); + } + }); + } + + private void testIOException(IOExceptionRun methodRunCode) + { + try + { + methodRunCode.run(); + + // No IOException + fail("expected IOException"); + } + catch (IOException e) + { + // Success + } + } + + @Test(expected = NullPointerException.class) + public void testNPEinConstructor() + { + new SctpSocket(0, 0); + } + + /** + * Tests {@link SctpSocket#onConnIn(byte[])} method for invalid arguments. + */ + @Test + public void testOnConnIn() + throws IOException + { + // Expect NPE + try + { + testSocket.onConnIn(null); + fail("No NPE onConnIn called with null"); + } + catch (NullPointerException npe) + { + // OK + } + + // Test empty buffer, should not crash + testSocket.onConnIn(new byte[]{}); + } + + /** + * Reproduced JVM crash when socket is closed while having native code + * on the stack(that will be executed after the socket was closed). + */ + @Test + public void testCloseFromNotification() + throws IOException, InterruptedException + { + testSocket.setNotificationListener( + new SctpSocket.NotificationListener() + { + @Override + public void onSctpNotification(SctpSocket socket, + SctpNotification notification) + { + /** + * Notification is fired after usrsctp processed network packet + * which means we have native "on_network_in" on current stack. + * We own permission to SctpSocket monitor + * (because on_network_in requires that) and after we close the + * socket and return usrsctp stack will try to finish work after + * firing notification and it will operate on closed socket + * which will crash the JVM. + * + * To fix this problem after usrsctp calls "onSctpInboundPacket" + * it must be executed on different thread, so that the native + * one can continue and finish it's processing without affecting + * {@link SctpSocket#socketPtr}(protected by the lock when + * accessed from java code). + * + * FIXME: + * We can either detect this situation(and throw exception) or + * spawn new thread in "onSctpInboundPacket". For the time + * being the second option was applied. + */ + if(notification.sn_type == SctpNotification.SCTP_ASSOC_CHANGE) + { + socket.close(); + } + } + }); + + SctpSocket s2 = Sctp.createSocket(5001); + + DirectLink link = new DirectLink(testSocket, s2); + testSocket.setLink(link); + s2.setLink(link); + + s2.listen(); + testSocket.connect(5001); + + Thread.sleep(500); + + s2.close(); + } + + /** + * Interface used to test whether enclosed code + * will throw an <tt>IOException</tt>. + */ + private interface IOExceptionRun + { + /** + * Runs the code that shall throw <tt>IOException</tt>. + */ + public void run() throws IOException; + } +} diff --git a/test/org/jitsi/sctp4j/SctpTestSuite.java b/test/org/jitsi/sctp4j/SctpTestSuite.java new file mode 100644 index 0000000000000000000000000000000000000000..4a862f9854329514e05e9e79c189dfafdd015979 --- /dev/null +++ b/test/org/jitsi/sctp4j/SctpTestSuite.java @@ -0,0 +1,25 @@ +/* + * Jitsi Videobridge, OpenSource video conferencing. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package org.jitsi.sctp4j; + +import org.junit.runner.*; +import org.junit.runners.*; + +/** + * Test suite for SCTP. + * + * @author Pawel Domas + */ +@RunWith(Suite.class) +@Suite.SuiteClasses( + { + SctpNativeWrapperTest.class, + SctpTransferTest.class + }) +public class SctpTestSuite +{ +} diff --git a/test/org/jitsi/sctp4j/SctpTransferTest.java b/test/org/jitsi/sctp4j/SctpTransferTest.java new file mode 100644 index 0000000000000000000000000000000000000000..ccbea3f161510bf04c2c16b623cf623a3f70428b --- /dev/null +++ b/test/org/jitsi/sctp4j/SctpTransferTest.java @@ -0,0 +1,124 @@ +/* + * Jitsi Videobridge, OpenSource video conferencing. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package org.jitsi.sctp4j; + +import org.junit.*; + +import java.io.*; +import java.util.*; + +import static org.junit.Assert.assertArrayEquals; + +/** + * Transfer tests. + * + * @author Pawel Domas + */ +public class SctpTransferTest +{ + private SctpSocket peerA; + + private final int portA = 5000; + + private SctpSocket peerB; + + private final int portB = 5001; + + private final Object transferLock = new Object(); + + private byte[] receivedData = null; + + @Before + public void setUp() + { + Sctp.init(); + + peerA = Sctp.createSocket(portA); + + peerB = Sctp.createSocket(portB); + } + + @After + public void tearDown() + throws IOException + { + peerA.close(); + + peerB.close(); + + Sctp.finish(); + } + + public static byte[] createRandomData(int size) + { + byte[] dummy = new byte[size]; + new Random().nextBytes(dummy); + return dummy; + } + + /** + * Tests the transfer with random link failures and packet loss. + * + * @throws Exception + */ + @Test + public void testSocketBrokenLink() + throws Exception + { + TestLink link = new TestLink(peerA, peerB, + 0.2, /* loss rate */ + 0.1 /* error rate */); + + peerA.setLink(link); + peerB.setLink(link); + + peerA.connect(portB); + peerB.connect(portA); + + byte[] toSendA = createRandomData(2*1024); + for(int i=0; i < 10; i++) + { + try + { + testTransferPart(peerA, peerB, toSendA, 5000); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } + } + + private void testTransferPart(SctpSocket sender, SctpSocket receiver, + byte[] testData, long timeout) + throws Exception + { + receiver.setDataCallback(new SctpDataCallback() + { + @Override + public void onSctpPacket(byte[] data, int sid, int ssn, int tsn, + long ppid, + int context, int flags) + { + synchronized (transferLock) + { + receivedData = data; + transferLock.notifyAll(); + } + } + }); + + sender.send(testData, true, 0, 0); + + synchronized (transferLock) + { + transferLock.wait(timeout); + + assertArrayEquals(testData, receivedData); + } + } +} diff --git a/test/org/jitsi/sctp4j/TestLink.java b/test/org/jitsi/sctp4j/TestLink.java new file mode 100644 index 0000000000000000000000000000000000000000..32197040dd6015e41e6678de2cd6e1ce12e51d19 --- /dev/null +++ b/test/org/jitsi/sctp4j/TestLink.java @@ -0,0 +1,54 @@ +/* + * Jitsi Videobridge, OpenSource video conferencing. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package org.jitsi.sctp4j; + +import java.io.*; + +/** + * The link that loses some packets and produces IOExceptions from time to time. + * + * @author Pawel Domas + */ +public class TestLink + extends DirectLink +{ + private final double lossRate; + + private final double errorRate; + + public TestLink(SctpSocket a, SctpSocket b, + double lossRate, double errorRate) + { + super(a, b); + + this.lossRate = lossRate; + + this.errorRate = errorRate; + } + + @Override + public void onConnOut(SctpSocket s, byte[] packet) + throws IOException + { + double r = Math.random(); + + if(r < (errorRate + lossRate)) + { + if (r < errorRate) + { + throw new IOException("Link failure"); + } + else + { + // Packet lost, nothing happens + return; + } + } + // Eventually pass the data + super.onConnOut(s, packet); + } +}