diff --git a/src/edu/nps/moves/dis7/utilities/DisThreadedNetworkInterface.java b/src/edu/nps/moves/dis7/utilities/DisThreadedNetworkInterface.java index b8b70c2868ed0e896368399095e77969c7aa2ff4..3268176ea2998d9508bfad60aa8fae0b5c91404e 100644 --- a/src/edu/nps/moves/dis7/utilities/DisThreadedNetworkInterface.java +++ b/src/edu/nps/moves/dis7/utilities/DisThreadedNetworkInterface.java @@ -4,18 +4,14 @@ */ package edu.nps.moves.dis7.utilities; -import edu.nps.moves.dis7.pdus.Pdu; -import edu.nps.moves.dis7.pdus.DisTime; import edu.nps.moves.dis7.enumerations.DisPduType; - +import edu.nps.moves.dis7.pdus.DisTime; +import edu.nps.moves.dis7.pdus.Pdu; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.IOException; - import java.net.*; - import java.nio.ByteBuffer; - import java.util.*; import java.util.concurrent.LinkedBlockingQueue; import java.util.logging.Level; @@ -484,6 +480,7 @@ public class DisThreadedNetworkInterface try { // operations are finished dos.close(); + this.close(); } catch (IOException e) {} // shutting down, no need to report exception }; @@ -522,11 +519,11 @@ public class DisThreadedNetworkInterface @Deprecated public void kill() { - finishOperations(); + setKillSentinel(); } /** Finish pending send/receive activity and then close. */ - public void finishOperations() + public void setKillSentinel() { killed = true; // set loop sentinel for threads to finish } @@ -535,7 +532,19 @@ public class DisThreadedNetworkInterface * Synchronized to prevent interleaved invocation. */ public synchronized void close() { - finishOperations(); + setKillSentinel(); + + try + { + senderThread.join(2000); // wait for thread to die, msec max duration + receiverThread.join(2000); // wait for thread to die, msec max duration + } + catch (InterruptedException ie) + { + System.err.println ("*** DisThreadedNetworkInterface thread join() failed to wait for threads to die"); + System.err.flush(); + ie.printStackTrace(System.err); + } if (datagramSocket != null && !datagramSocket.isClosed()) { @@ -569,8 +578,8 @@ public class DisThreadedNetworkInterface } catch (InterruptedException ie) { - System.err.flush(); System.err.println ("*** " + getClass().getName() + ".sleep(" + duration + ") failed to sleep"); + System.err.flush(); ie.printStackTrace(System.err); } } diff --git a/src/edu/nps/moves/dis7/utilities/stream/PduRecorder.java b/src/edu/nps/moves/dis7/utilities/stream/PduRecorder.java index 4547751ad0cda521bb571ecf07e441954f6fd825..1c536829b5f7b2a6ff42a9909de87acff2b050e1 100644 --- a/src/edu/nps/moves/dis7/utilities/stream/PduRecorder.java +++ b/src/edu/nps/moves/dis7/utilities/stream/PduRecorder.java @@ -2,6 +2,7 @@ package edu.nps.moves.dis7.utilities.stream; import com.google.common.primitives.Longs; import edu.nps.moves.dis7.enumerations.DisPduType; +import edu.nps.moves.dis7.pdus.Pdu; import edu.nps.moves.dis7.utilities.DisThreadedNetworkInterface; import edu.nps.moves.dis7.utilities.PduFactory; import java.io.BufferedWriter; @@ -12,9 +13,11 @@ import java.io.PrintWriter; import java.io.Writer; import java.nio.file.Path; import java.text.SimpleDateFormat; +import java.util.ArrayList; import java.util.Arrays; import java.util.Base64; import java.util.Calendar; +import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import org.apache.commons.io.FilenameUtils; @@ -45,25 +48,30 @@ public class PduRecorder implements PduReceiver public static final String COMMENT_MARKER = "#"; static final String OUTPUT_DIRECTORY_DEFAULT = "./pduLog"; - String outputDirectory = OUTPUT_DIRECTORY_DEFAULT; - Path outputDirectoryPath; + private String outputDirectory = OUTPUT_DIRECTORY_DEFAULT; + private Path outputDirectoryPath; static final String DEFAULT_FILE_PREFIX = "PduCaptureLog"; static final String DISLOG_FILE_EXTENSION = ".dislog"; static final String DEFAULT_FILE_NAME = DEFAULT_FILE_PREFIX + DISLOG_FILE_EXTENSION; static final String START_COMMENT_MARKER = COMMENT_MARKER + " Start, "; static final String FINISH_COMMENT_MARKER = COMMENT_MARKER + " Finish, "; + static final String ENCODING_BASE64 = "ENCODING_BASE64"; static final String ENCODING_PLAINTEXT = "ENCODING_PLAINTEXT"; + // not yet implemented static final String ENCODING_BINARY = "ENCODING_BINARY"; // TODO likely requires different code path static final String ENCODING_XML = "ENCODING_XML"; // TODO, repeat Open-DIS version 4 effort + static final String ENCODING_CDIS = "ENCODING_CDIS"; // future work based on new SISO standard static final String ENCODING_EXI = "ENCODING_EXI"; // TODO, use Exificient or Nagasena libraries static final String ENCODING_JSON = "ENCODING_JSON"; // TODO, repeat Open-DIS version 4 effort static final String ENCODING_MAK_DATA_LOGGER = "ENCODING_MAK_DATA_LOGGER"; // verbose pretty-print. perhaps output only (MAK format itself is binary) static final String ENCODING_WIRESHARK_DATA_LOGGER = "ENCODING_WIRESHARK_DATA_LOGGER"; // - static final String ENCODING_CDIS = "ENCODING_CDIS"; // future work based on new SISO standard + + static final List<String> ENCODING_OPTIONS_LIST = new ArrayList<>(); + static final List<String> ENCODING_OPTIONS_TODO = new ArrayList<>(); - private static String pduLogEncoding = ENCODING_PLAINTEXT; // TODO use Java enumerations, generalize/share across library + private static String encodingPduLog = ENCODING_PLAINTEXT; // default private String TRACE_PREFIX = ("[pduRecorder " + getDescriptor()).trim() + "] "; private String descriptor = new String(); @@ -80,6 +88,23 @@ public class PduRecorder implements PduReceiver private final int pduCount = 0; // debug private boolean headerWritten = false; private boolean running = true; // starts recording by default + + private void initialize() + { + if (ENCODING_OPTIONS_LIST.isEmpty()) + { + ENCODING_OPTIONS_LIST.add(ENCODING_PLAINTEXT ); + ENCODING_OPTIONS_LIST.add(ENCODING_BASE64 ); + // not yet implemented + ENCODING_OPTIONS_TODO.add(ENCODING_BINARY ); // IEEE DIS format + ENCODING_OPTIONS_TODO.add(ENCODING_XML ); + ENCODING_OPTIONS_TODO.add(ENCODING_CDIS ); + ENCODING_OPTIONS_TODO.add(ENCODING_EXI ); + ENCODING_OPTIONS_TODO.add(ENCODING_JSON ); + ENCODING_OPTIONS_TODO.add(ENCODING_MAK_DATA_LOGGER ); + ENCODING_OPTIONS_TODO.add(ENCODING_WIRESHARK_DATA_LOGGER); + } + } /** * Default constructor that uses default values for output directory, DIS address and port. @@ -89,7 +114,7 @@ public class PduRecorder implements PduReceiver */ public PduRecorder() throws IOException { - this (OUTPUT_DIRECTORY_DEFAULT, DEFAULT_DIS_ADDRESS, DEFAULT_DIS_PORT); + this (OUTPUT_DIRECTORY_DEFAULT, DEFAULT_DIS_ADDRESS, DEFAULT_DIS_PORT); } /** @@ -103,7 +128,7 @@ public class PduRecorder implements PduReceiver */ public PduRecorder(String initialOutputDirectory) throws IOException { - this(initialOutputDirectory, DEFAULT_DIS_ADDRESS, DEFAULT_DIS_PORT); + this(initialOutputDirectory, DEFAULT_DIS_ADDRESS, DEFAULT_DIS_PORT); } /** Constructor to let the user specify all required parameters. @@ -117,6 +142,7 @@ public class PduRecorder implements PduReceiver @SuppressWarnings("Convert2Lambda") public PduRecorder(String initialOutputDirectory, String initialAddress, int initialPort) { + initialize(); outputDirectoryPath = new File(initialOutputDirectory).toPath(); setDescriptor("PduRecorder"); // default setAddress(initialAddress); @@ -128,16 +154,35 @@ public class PduRecorder implements PduReceiver * * @return the pduLogEncoding */ - public static String getPduLogEncoding() + public static String getEncodingPduLog() { - return pduLogEncoding; + return encodingPduLog; } /** - * @param newPduLogEncoding the pduLogEncoding to set + * @param newEncodingPduLog the pduLogEncoding to set */ - public static void setPduLogEncoding(String newPduLogEncoding) { - pduLogEncoding = newPduLogEncoding; + public void setEncodingPduLog(String newEncodingPduLog) + { + newEncodingPduLog = newEncodingPduLog.trim(); + String errorMessage = "*** setEncodingPduLog(" + newEncodingPduLog + ") "; + if (ENCODING_OPTIONS_LIST.contains(newEncodingPduLog)) + { + encodingPduLog = newEncodingPduLog; + return; + } + else if (ENCODING_OPTIONS_TODO.contains(newEncodingPduLog)) + { + errorMessage += " is not yet a supported encoding"; + } + else + { + errorMessage += " is not yet a recognizeded encoding"; + } + errorMessage += ", encodingPduLog=" + encodingPduLog + " is unchanged"; + System.err.println (errorMessage); + System.err.flush(); // since network threads may be occurring + // return } /** Resume instance operation @@ -154,7 +199,7 @@ public class PduRecorder implements PduReceiver else running = true; } - /** Start instance operation + /** Start instance operation, resetting network interface and file recording * @see stop() * @see pause() * @see resume() @@ -162,12 +207,12 @@ public class PduRecorder implements PduReceiver public void start() { try { - logFile = createUniquePduLogFile(outputDirectoryPath, getLogFileName()); + logFile = createUniquePduLogFile(getOutputDirectoryPath(), getLogFileName()); logFileWriter = new PrintWriter(new BufferedWriter(new FileWriter(logFile))); } catch (IOException ex) { - System.err.println("Exception when creating log file in directory=" + outputDirectoryPath.toAbsolutePath() + "\n" + + System.err.println("Exception when creating log file in directory=" + getOutputDirectoryPath().toAbsolutePath() + "\n" + " " + ex.getClass().getSimpleName() + ": " + ex.getLocalizedMessage()); } if (disThreadedNetworkInterface == null) @@ -207,7 +252,8 @@ public class PduRecorder implements PduReceiver if (disThreadedNetworkInterface != null) { disThreadedNetworkInterface.removeRawListener(disRawPduListener); - disThreadedNetworkInterface.finishOperations(); + disThreadedNetworkInterface.setKillSentinel(); + disThreadedNetworkInterface = null; // ensure reset upon re-start } writeFooter(); @@ -247,7 +293,7 @@ public class PduRecorder implements PduReceiver byte[] buffsized = Arrays.copyOf(newBuffer, newLength); DisPduType pduType; - switch (pduLogEncoding) + switch (encodingPduLog) { case ENCODING_BASE64: sb.append(base64Encoder.encodeToString(timeByteArray)); @@ -266,7 +312,7 @@ public class PduRecorder implements PduReceiver break; default: - System.err.println ("Encoding " + pduLogEncoding + " not recognized or supported"); + System.err.println ("Encoding " + encodingPduLog + " not recognized or supported"); } if (!headerWritten) { writeHeader(); @@ -316,7 +362,7 @@ public class PduRecorder implements PduReceiver try { - logFileWriter.write(START_COMMENT_MARKER + pduLogEncoding + ", " + TRACE_PREFIX + timeStamp + ", DIS capture file, " + logFile.getPath()); + logFileWriter.write(START_COMMENT_MARKER + encodingPduLog + ", " + TRACE_PREFIX + timeStamp + ", DIS capture file, " + logFile.getPath()); ((PrintWriter) logFileWriter).println(); } catch (IOException ex) @@ -331,7 +377,7 @@ public class PduRecorder implements PduReceiver try { - logFileWriter.write(FINISH_COMMENT_MARKER + pduLogEncoding + ", " + TRACE_PREFIX + timeStamp + ", DIS capture file, " + logFile.getPath()); + logFileWriter.write(FINISH_COMMENT_MARKER + encodingPduLog + ", " + TRACE_PREFIX + timeStamp + ", DIS capture file, " + logFile.getPath()); ((PrintWriter) logFileWriter).println(); } catch (IOException ex) @@ -392,56 +438,89 @@ public class PduRecorder implements PduReceiver return running; } - /** Entry point invocation. Saves a PDU output log to ./pduLog. Invoking the - * edu.nps.moves.dis7.examples.PduReaderPlayer will playback all logs written - * to the log directory + /** + * Entry point invocation, runs selfTest(). + * + * @param args none supported, TODO offer path/filename + */ + public static void main(String[] args) + { + try + { + PduRecorder pduRecorder = new PduRecorder(); + pduRecorder.selfTest(args); + } + catch (IOException ioe) + { + ioe.printStackTrace(); + } + } + + /** This selfTest() method saves a PDU output log to project pduLog/ directory. + * Separately Invoking the edu.nps.moves.dis7.examples.PduReaderPlayer will playback all logs + * written to that log directory * * @param args none supported, TODO offer path/filename */ - public static void main(String[] args) + public void selfTest(String[] args) { + initialize(); System.out.println("dis7.utilities.stream.PduRecorder main() performs self-test by sending full set of PDUs"); - PduFactory factory = new PduFactory(); //default appid, country, etc. - - PduRecorder pduRecorder; - try { + PduFactory factory = new PduFactory(); // default appid, country, etc. + PduRecorder pduRecorder; + DisThreadedNetworkInterface disNetworkInterface; + try + { pduRecorder = new PduRecorder(); // default address, port, output directory path - pduRecorder.setDescriptor("PduRecorder main() self test"); -// pduRecorder.setPort(1); // option to avoid listening to other PDU streams during self test - pduRecorder.start(); } - catch(IOException ex) { - System.err.println("Exception creating recorder: " + ex.getLocalizedMessage()); + catch(IOException ex) + { + System.err.println("Exception creating PduRecorder: " + ex.getLocalizedMessage()); System.err.println(ex.getStackTrace()); return; } - System.out.println("dis7.utilities.stream.PduRecorder pduRecorder started... isRunning()=" + pduRecorder.isRunning()); - DisPduType allPDUTypesArray[] = DisPduType.values(); System.out.println("dis7.utilities.stream.PduRecorder allPDUTypesArray created, length=" + allPDUTypesArray.length + " ..."); System.out.flush(); // ensure all output sent - Arrays.stream(allPDUTypesArray).forEach((DisPduType pduTypeValue)-> + + for (String currentEncoding : ENCODING_OPTIONS_LIST) { - if (pduTypeValue != DisPduType.OTHER) - { - try { - pduRecorder.getDisThreadedNetworkInterface().send(factory.createPdu(pduTypeValue)); - Thread.sleep (50L); // let send/receive threads and streams catch up - } - catch (InterruptedException ex) { - System.err.println("Exception sending Pdu: "+ex.getLocalizedMessage()); - } - } - else - { - System.err.println("Found pduTypeValue=DisPduType.OTHER=" + pduTypeValue); - } - }); - System.out.flush(); - System.err.flush(); // ensure all output sent - pduRecorder.stop(); - System.out.println("dis7.utilities.stream.PduRecorder pduRecorder complete... isRunning()=" + pduRecorder.isRunning()); + System.out.println("================================================="); + System.out.println("Test PduRecorder encoding " + currentEncoding); + pduRecorder.setEncodingPduLog(currentEncoding); + pduRecorder.setDescriptor("PduRecorder main() self test"); +// pduRecorder.setPort(1); // option to avoid listening to other PDU streams during self test + pduRecorder.setLogFileName("PduRecorderSelfTest" + currentEncoding + "_" + DEFAULT_FILE_PREFIX + DISLOG_FILE_EXTENSION); + pduRecorder.start(); + disNetworkInterface = pduRecorder.getDisThreadedNetworkInterface(); // must reinitialize after each start + System.out.println("dis7.utilities.stream.PduRecorder pduRecorder started... isRunning()=" + pduRecorder.isRunning()); + + for (int i=1; i < allPDUTypesArray.length; i = i + 1) + { + DisPduType pduTypeValue = allPDUTypesArray[i]; + if (pduTypeValue != DisPduType.OTHER) + { + try { + Pdu nextPdu = factory.createPdu(allPDUTypesArray[i]); + disNetworkInterface.send(nextPdu); + Thread.sleep (50L); // let send/receive threads and streams catch up + } + catch (InterruptedException ex) { + System.err.println("Exception sending Pdu " + pduTypeValue + ": " + ex.getLocalizedMessage()); + } + } + else + { + System.err.println("Found pduTypeValue=DisPduType.OTHER=" + pduTypeValue); + } + }; + System.out.flush(); + System.err.flush(); // ensure all output sent + pduRecorder.stop(); + System.out.println("dis7.utilities.stream.PduRecorder pduRecorder complete... isRunning()=" + pduRecorder.isRunning()); + } + // end of loop ENCODING_OPTIONS_LIST } /** * Get current multicast (or unicast) network address for send and receive connections. @@ -458,10 +537,10 @@ public class PduRecorder implements PduReceiver * @param newAddress the new network address to set */ public void setAddress(String newAddress) { - this.disAddress = newAddress; - if (isRunning()) + if (isRunning() && (this.disAddress != newAddress)) System.out.println(TRACE_PREFIX + "*** warning, attempting to change network address while running..."); // TODO warn if netIF already created + this.disAddress = newAddress; } /** Get network port used, multicast or unicast. * @see <a href="https://en.wikipedia.org/wiki/Port_(computer_networking)">https://en.wikipedia.org/wiki/Port_(computer_networking)</a> @@ -478,10 +557,10 @@ public class PduRecorder implements PduReceiver */ public void setPort(int newPortValue) { - this.disPort = newPortValue; - if (isRunning()) + if (isRunning() && (this.disPort != newPortValue)) System.out.println(TRACE_PREFIX + "*** warning, attempting to change network port while running..."); // TODO warn if netIF already created + this.disPort = newPortValue; } /** * Get simple descriptor (such as parent class name) for this network interface, used in trace statements @@ -502,7 +581,7 @@ public class PduRecorder implements PduReceiver } /** * Set whether or not trace statements are provided when packets are sent or received. - * @param newValue the verbose status to set. Also resets verboseReceipt and verboseSending to match. + * @param newValue the verbose status to set, also resets verboseReceipt and verboseSending to match. * @see verboseReceipt * @see verboseSending */ @@ -609,4 +688,31 @@ public class PduRecorder implements PduReceiver public void setLogFileName(String logFileName) { this.logFileName = logFileName; } + + /** + * Get output directory for this PduRecorder + * @return the outputDirectory + */ + public String getOutputDirectory() { + return outputDirectory; + } + + /** + * Set output directory for this PduRecorder + * @param outputDirectory the outputDirectory to set + */ + public void setOutputDirectory(String outputDirectory) { + this.outputDirectory = outputDirectory; + outputDirectoryPath = new File(outputDirectory).toPath(); + } + + /** + * Get output directory path for this PduRecorder. + * To change outputDirectoryPath, invoke setOutputDirectory(). + * @see setOutputDirectory(String) + * @return the outputDirectoryPath + */ + public Path getOutputDirectoryPath() { + return outputDirectoryPath; + } } diff --git a/src/edu/nps/moves/dis7/utilities/stream/PduRecorderSelfTest.dislog b/src/edu/nps/moves/dis7/utilities/stream/PduRecorderSelfTestENCODING_PLAINTEXT_PduCaptureLog.dislog similarity index 100% rename from src/edu/nps/moves/dis7/utilities/stream/PduRecorderSelfTest.dislog rename to src/edu/nps/moves/dis7/utilities/stream/PduRecorderSelfTestENCODING_PLAINTEXT_PduCaptureLog.dislog