// This is a test application for experimenting // with the NORM API implementation during its // development. A better-documented and complete // example of the NORM API usage will be provided // when the NORM API is more complete. #include "normApi.h" #include "protokit.h" // for protolib debug, stuff, etc #include #include // for srand() #ifdef UNIX #include // for "sleep()" #endif // UNIX int main(int argc, char* argv[]) { printf("normTest starting (sizeof(NormSize) = %d)...\n", (int)sizeof(NormSize)); NormInstanceHandle instance = NormCreateInstance(); #ifdef WIN32 const char* cachePath = "C:\\Adamson\\Temp\\cache\\"; #else const char* cachePath = "/tmp/"; #endif // if/else WIN32/UNIX NormSetCacheDirectory(instance, cachePath); // Here's a trick to generate a _hopefully_ unique NormNodeId // based on an XOR of the system's IP address and the process id. // (We use ProtoAddress::GetEndIdentifier() to get the local // "default" IP address for the system) // (Note that passing "NORM_NODE_ANY" to the last arg of // NormCreateSession() does a similar thing but without // the processId XOR ... perhaps we should add the processId // hack to that default "NORM_NODE_ANY" local NormNodeId picker??? ProtoAddress localAddr; if (!localAddr.ResolveLocalAddress()) { fprintf(stderr, "normTest: error resolving local IP address\n"); NormDestroyInstance(instance); return -1; } NormNodeId localId = localAddr.EndIdentifier(); #ifdef WIN32 DWORD processId = GetCurrentProcessId(); #else pid_t processId = getpid(); #endif // if/else WIN32/UNIX localId ^= (NormNodeId)processId; // If needed, permutate to a valid, random NormNodeId while ((NORM_NODE_ANY == localId) || (NORM_NODE_NONE == localId)) { localId ^= (NormNodeId)rand(); } //localId = 15; // for testing purposes // Create a NORM session instance NormSessionHandle session = NormCreateSession(instance, "224.1.1.1", 6001, localId); //NormSetTOS(session, 0x20); // NOTE: These are debugging routines available (not for normal app use) // (IMPORTANT NOTE: On Win32 builds with Norm.dll, this "SetDebugLevel()" has no // effect on the Protolib instance in the NORM DLL !!! // (TBD - provide a "NormSetDebugLevel()" function for this purpose!) SetDebugLevel(2); // Uncomment to turn on debug NORM message tracing //NormSetMessageTrace(session, true); // Uncomment to turn on some random packet loss //NormSetTxLoss(session, 10.0); // 10% packet loss //NormSetRxLoss(session, 20.0); struct timeval currentTime; ProtoSystemTime(currentTime); // Uncomment to get different packet loss patterns from run to run // (and a different sender sessionId) srand(currentTime.tv_sec); // seed random number generator NormSetGrttEstimate(session, 0.001); // 1 msec initial grtt NormSetTransmitRate(session, 1.0e+07); // in bits/second // Uncomment to enable TCP-friendly congestion control (overrides NormSetTransmitRate()) //NormSetCongestionControl(session, true); //NormSetTransmitRateBounds(session, 1000.0, -1.0); NormSetDefaultRepairBoundary(session, NORM_BOUNDARY_BLOCK); // Uncomment to use a _specific_ transmit port number // (Can be the same as session port (rx port), but this // is _not_ recommended when unicast feedback may be // possible!) //NormSetTxPort(session, 6001); //NormSetDefaultUnicastNack(session, true); // Uncomment to allow reuse of rx port // (This allows multiple "normTest" instances on the same machine // for the same NormSession - note those instances must use // unique local NormNodeIds (see NormCreateSession() above). NormSetRxPortReuse(session, true); // Uncomment to receive your own traffic NormSetLoopback(session, true); //NormSetSilentReceiver(session, true); // Uncomment this line to participate as a receiver //NormStartReceiver(session, 1024*1024); // Uncomment to set large rx socket buffer size // (might be needed for high rate sessions) NormSetRxSocketBuffer(session, 512000); // We use a random "sessionId" NormSessionId sessionId = (NormSessionId)rand(); // Uncomment the following line to start sender NormStartSender(session, sessionId, 1024*1024, 1400, 64, 8); //NormSetAutoParity(session, 6); // Uncomment to set large tx socket buffer size // (might be needed for high rate sessions) //NormSetTxSocketBuffer(session, 512000); NormAddAckingNode(session, NORM_NODE_NONE); //15); //NormGetLocalNodeId(session)); NormObjectHandle stream = NORM_OBJECT_INVALID; #ifdef WIN32 const char* filePath = "C:\\Adamson\\Images\\Art\\giger205.jpg"; #else const char* filePath = "/home/adamson/images/art/giger/giger205.jpg"; #endif //const char* filePath = "/home/adamson/pkgs/rh73.tgz"; const char* fileName = "file1.jpg"; const char* fileName2 = "file2.jpg"; // Uncomment this line to send a stream instead of the file stream = NormStreamOpen(session, 4*1024*1024); // NORM_FLUSH_PASSIVE automatically flushes full writes to // the stream. NormStreamSetAutoFlush(stream, NORM_FLUSH_PASSIVE); // Some variable for stream input/output char txBuffer[8192], rxBuffer[8192]; int txIndex = 0; int txLen = 0; int rxIndex = 0; #define STREAM_MSG_PREFIX_SIZE 1000 // we pad with 'a' character value (the msgPrefix is a prefix) char msgPrefix[STREAM_MSG_PREFIX_SIZE]; memset(msgPrefix, 'a', STREAM_MSG_PREFIX_SIZE); bool msgSync = false; int msgCount = 0; int recvCount = -1; // used to monitor reliable stream reception int sendCount = 0; int sendMax = -1;//300;//-1; // -1 means unlimited int fileMax = 1; NormObjectHandle txFile = NORM_OBJECT_INVALID; NormEvent theEvent; while (NormGetNextEvent(instance, &theEvent)) { switch (theEvent.type) { case NORM_TX_QUEUE_VACANCY: case NORM_TX_QUEUE_EMPTY: /*if (NORM_TX_QUEUE_VACANCY == theEvent.type) TRACE("NORM_TX_QUEUE_VACANCY ...\n"); else TRACE("NORM_TX_QUEUE_EMPTY ...\n");*/ if ((sendMax > 0) && (sendCount >= sendMax)) break; if (NORM_OBJECT_INVALID != theEvent.object) { // We loop here to keep stream buffer full .... bool keepWriting = true; while (keepWriting) { if (0 == txLen) { memset(txBuffer, 'a', STREAM_MSG_PREFIX_SIZE); // Write a message into the "txBuffer" for transmission sprintf(txBuffer+STREAM_MSG_PREFIX_SIZE, " normTest says hello %d ...\n", sendCount); txLen = strlen(txBuffer); } unsigned int want = txLen - txIndex; unsigned int put = NormStreamWrite(stream, txBuffer+txIndex, want); if (put != want) keepWriting = false; txIndex += put; if (txIndex == txLen) { // _Instead_ of "NormStreamSetAutoFlush(stream, NORM_FLUSH_PASSIVE)" above // _and_ "NormStreamMarkEom()" here, I could have used // "NormStreamFlush(stream, true)" here to perform explicit flushing // and EOM marking in one fell swoop. That would be a simpler approach // for apps where very big stream messages might need to be written with // multiple calls to "NormStreamWrite()" NormStreamMarkEom(stream); txLen = txIndex = 0; sendCount++; if (sendCount == 15) { //NormSetWatermark(session, stream); } if ((sendMax > 0) && (sendCount >= sendMax)) { // Uncomment to gracefully shut down the stream // after "sendMax" messages NormStreamClose(stream, true); keepWriting = false; } } } // end while(keepWriting) } else if (NORM_OBJECT_INVALID == stream) // we weren't sending a stream that finished { if ((fileMax < 0) || (sendCount < fileMax)) { const char* namePtr = (0 == (sendCount & 0x01)) ? fileName : fileName2; txFile = NormFileEnqueue(session, filePath, namePtr, strlen(fileName)); // Repeatedly queue our file for sending if (NORM_OBJECT_INVALID == txFile) { DMSG(0, "normTest: error queuing file: %s\n", filePath); break; } else { TRACE("QUEUED FILE ...\n"); sendCount++; if (sendCount < 2) NormSetWatermark(session, txFile); } } } break; case NORM_TX_FLUSH_COMPLETED: DMSG(2, "normTest: NORM_TX_FLUSH_COMPLETED event ...\n"); // This line of code "requeues" the last file sent // (Good for use with silent receivers ... can repeat transmit object this way) NormRequeueObject(session, txFile); break; case NORM_TX_WATERMARK_COMPLETED: DMSG(2, "normTest: NORM_TX_WATERMARK_COMPLETED event ...\n"); break; case NORM_TX_OBJECT_SENT: DMSG(2, "normTest: NORM_TX_WATERMARK_COMPLETED event ...\n"); break; case NORM_TX_OBJECT_PURGED: DMSG(2, "normTest: NORM_TX_OBJECT_PURGED event ...\n"); break; case NORM_RX_OBJECT_NEW: DMSG(3, "normTest: NORM_RX_OBJECT_NEW event ...\n"); break; case NORM_RX_OBJECT_INFO: // Assume info contains '/' delimited string if (NORM_OBJECT_FILE == NormObjectGetType(theEvent.object)) { char fileName[PATH_MAX]; strcpy(fileName, cachePath); int pathLen = strlen(fileName); unsigned short nameLen = PATH_MAX - pathLen; nameLen = NormObjectGetInfo(theEvent.object, fileName+pathLen, nameLen); fileName[nameLen + pathLen] = '\0'; char* ptr = fileName + 5; while ('\0' != *ptr) { if ('/' == *ptr) *ptr = PROTO_PATH_DELIMITER; ptr++; } if (!NormFileRename(theEvent.object, fileName)) TRACE("normTest: NormSetFileName(%s) error\n", fileName); DMSG(3, "normTest: recv'd info for file: %s\n", fileName); } break; case NORM_RX_OBJECT_UPDATED: { //TRACE("normTest: NORM_RX_OBJECT_UPDATE event ...\n"); if (NORM_OBJECT_STREAM != NormObjectGetType(theEvent.object)) break; unsigned int len; do { if (!msgSync) msgSync = NormStreamSeekMsgStart(theEvent.object); if (!msgSync) break; len = 8191 - rxIndex; if (NormStreamRead(theEvent.object, rxBuffer+rxIndex, &len)) { rxIndex += len; rxBuffer[rxIndex] = '\0'; if (rxIndex > 0) { // The '\n' indicates we have received an entire message int parsedLen = 0; bool searching = true; char* startPtr = rxBuffer; while (searching) { char* endPtr = strchr(startPtr, '\n'); if (endPtr) { // Save sub-string message length parsedLen += (endPtr - startPtr + 1); // Validate received message string if (0 == memcmp(rxBuffer, msgPrefix, STREAM_MSG_PREFIX_SIZE)) { char* ptr = strstr(startPtr, "hello"); if (ptr) { int value; if (1 == sscanf(ptr, "hello %d", &value)) { msgCount++; // successful recv if (recvCount >= 0) { if (0 != (value - recvCount)) TRACE("WARNING! possible break? value:%d recvCount:%d\n", value, recvCount); } //else // TRACE("validated recv msg len:%d\n", len); recvCount = value+1; if (0 == msgCount % 1000) { TRACE("normTest: status> msgCount:%d of total:%d (%lf)\n", msgCount, recvCount, 100.0*((double)msgCount)/((double)recvCount)); } } else { TRACE("couldn't find index!? len:%d in %s\n", len, ptr); ASSERT(0); } } else { TRACE("couldn't find \"hello\"!? len:%d in %s\n", len, rxBuffer); ASSERT(0); } } else { TRACE("invalid received message!?\n"); ASSERT(0); } if (parsedLen >= rxIndex) { rxIndex = 0; searching = false; } else { startPtr = rxBuffer + parsedLen; } } // end if (endPtr) else if (parsedLen > 0) { memmove(rxBuffer, rxBuffer+parsedLen, rxIndex - parsedLen); rxIndex -= parsedLen; searching = false; } else { searching = false; } } // end while(searching) } } else { TRACE("normTest: error reading stream\n"); TRACE("status: msgCount:%d of total:%d (%lf)\n", msgCount, recvCount, 100.0*((double)msgCount)/((double)recvCount)); msgSync = false; rxIndex = 0; break; } } while (0 != len); break; } case NORM_RX_OBJECT_COMPLETED: TRACE("normTest: NORM_RX_OBJECT_COMPLETED event ...\n"); //NormStopReceiver(session); break; case NORM_RX_OBJECT_ABORTED: TRACE("normTest: NORM_RX_OBJECT_ABORTED event ...\n"); break; case NORM_GRTT_UPDATED: //TRACE("normTest: NORM_GRTT_UPDATED event ...\n"); break; default: TRACE("Got event type: %d\n", theEvent.type); } // end switch(theEvent.type) // Uncomment to exit program after sending "sendMax" messages or files if ((sendMax > 0) && (sendCount >= sendMax)) break; } // end while (NormGetNextEvent()) #ifdef WIN32 Sleep(30000); #else sleep(30); // allows time for cleanup if we're sending to someone else #endif // if/else WIN32/UNIX //NormStreamClose(stream); // stream is already closed in loop above NormStopReceiver(session); NormStopSender(session); NormDestroySession(session); NormDestroyInstance(instance); fprintf(stderr, "normTest: Done.\n"); return 0; } // end main()