From 5390be6210cec69c59648953d10ad594473c7d6f Mon Sep 17 00:00:00 2001
From: brutzman <brutzman@nps.edu>
Date: Sun, 26 Dec 2021 09:34:59 -0800
Subject: [PATCH] support for plaintext and base64 encoding

---
 .../dis7/utilities/stream/PduPlayer.java      | 328 ++++++++++--------
 1 file changed, 190 insertions(+), 138 deletions(-)

diff --git a/src/edu/nps/moves/dis7/utilities/stream/PduPlayer.java b/src/edu/nps/moves/dis7/utilities/stream/PduPlayer.java
index ca164f6539..86b6a0ac6a 100644
--- a/src/edu/nps/moves/dis7/utilities/stream/PduPlayer.java
+++ b/src/edu/nps/moves/dis7/utilities/stream/PduPlayer.java
@@ -6,7 +6,6 @@ package edu.nps.moves.dis7.utilities.stream;
 
 import com.google.common.primitives.Longs;
 import edu.nps.moves.dis7.enumerations.DisPduType;
-
 import java.io.*;
 import java.net.DatagramPacket;
 import java.net.DatagramSocket;
@@ -37,7 +36,7 @@ public class PduPlayer {
     private Path disLogDirectory;
     private String ip;
     private int port;
-    private Thread thrd;
+    private Thread playerThread;
 
     static final String ENCODING_BASE64          = "ENCODING_BASE64";
     static final String ENCODING_PLAINTEXT       = "ENCODING_PLAINTEXT";
@@ -49,7 +48,7 @@ public class PduPlayer {
     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"; // 
 
-    private static String pduLogEncoding = ENCODING_PLAINTEXT; // TODO use Java enumerations, generalize/share across library
+    private static String pduLogEncoding = PduRecorder.ENCODING_PLAINTEXT; // determined when reading file
 
     /** Constructor that spawns the player thread.
      * 
@@ -66,11 +65,11 @@ public class PduPlayer {
         this.port = port;
         this.netSend = sendToNet;
 
-        thrd = new Thread(() -> begin());
-        thrd.setPriority(Thread.NORM_PRIORITY);
-        thrd.setName("PlayerThread");
-        thrd.setDaemon(true);
-        thrd.start();
+        playerThread = new Thread(() -> { } ); // begin() - avoid starting thread begin() without programmer direction?
+        playerThread.setPriority(Thread.NORM_PRIORITY);
+        playerThread.setName("PlayerThread");
+        playerThread.setDaemon(true);
+        playerThread.start();
     }
 
     private Integer scenarioPduCount = null;
@@ -99,51 +98,63 @@ public class PduPlayer {
 
     /** Thread process for this class */
     @SuppressWarnings("StatementWithEmptyBody")
-    public void begin() {
+    public void begin() 
+    {
         try {
-            System.out.println("PduPlayer begin() playing DIS logs.");
+            System.out.println("PduPlayer begin() playing DIS logs found in ancestor pduLog directory.");
             
             InetAddress addr = null;
             DatagramPacket datagramPacket;
-            DisPduType type;
+            DisPduType pduType;
             String tempString;
             String[] sa = null, splitString;
             String REGEX;
             Pattern pattern;
-            byte[] pduTimeBytes = null, bufferShort = null;
-            int[] arr;
+            byte[] pduTimeBytes = new byte[8]; // timestamp is always 8 bytes
+            byte[] byteBufferShort = null;
+            int[] intArray;
             IntBuffer intBuffer;
             int tempInt;
             ByteBuffer byteBuffer1, byteBuffer2;
             long pduTimeInterval, targetSimTime, now, sleepTime;
 
-            FilenameFilter filter = (dir, name) -> {
+            FilenameFilter filenameFilter = (dir, name) -> {
                 return name.endsWith(PduRecorder.DISLOG_FILE_EXTENSION) && !name.startsWith(".");
             };
 
-            File[] fs = disLogDirectory.toFile().listFiles(filter);
-            if (fs == null) {
-                fs = new File[0];
+            File[] filesArray = disLogDirectory.toFile().listFiles(filenameFilter);
+            if (filesArray == null) {
+                filesArray = new File[0];
             }
 
-            Arrays.sort(fs, (f1, f2) -> {
+            Arrays.sort(filesArray, (f1, f2) -> {
                 return f1.getName().compareTo(f2.getName());
             });
 
-            if (netSend) {
+            if (netSend) 
+            {
                 addr = InetAddress.getByName(ip);
                 datagramSocket = new DatagramSocket();
             }
             
             Base64.Decoder base64Decoder = Base64.getDecoder();
 
-            for (File f : fs) {
-                System.out.println("Replaying " + f.getAbsolutePath());
+            for (File f : filesArray) 
+            {
                 List<String> lines = Files.readAllLines(Path.of(f.getAbsolutePath()));
 
-                for (String line : lines) {
+                if      (f.getName().contains("BASE64") || lines.get(0).startsWith("AAAAA")) // TODO include header??
+                     pduLogEncoding = PduRecorder.ENCODING_BASE64;
+                else if (f.getName().contains("PLAINTEXT") || lines.get(0).contains("PLAINTEXT"))
+                     pduLogEncoding = PduRecorder.ENCODING_PLAINTEXT;
+                else pduLogEncoding = PduRecorder.ENCODING_BINARY;
+                
+                System.out.println("Replaying PDU log file with " + pduLogEncoding + ": " + f.getAbsolutePath());
+                    
+                for (String line : lines)
+                {
                     while (paused) {
-                        sleep(1000l); // TODO confirm: full second, was half second
+                        sleep(100l); // TODO confirm usability OK, currently 100 msec increments for pause
                     }
                     if (line.length() <= 0)
                         ; // blank lines ok
@@ -151,28 +162,37 @@ public class PduPlayer {
                         if (handleComment(line, f)) {
                             break;
                         }
-                    } else {
-                        
-                        switch (pduLogEncoding) {
-                            case "ENCODING_BASE64":
-                                sa = line.split(",");
+                    } 
+                    else
+                    {
+                        switch (pduLogEncoding) 
+                        {
+                            case PduRecorder.ENCODING_BASE64:
+                                sa = new String[1]; // one big string per line per PDU
+                                sa[0] = line;
                                 break;
 
-                            case "ENCODING_PLAINTEXT":
+                            case PduRecorder.ENCODING_PLAINTEXT:
 
                                 if (line.contains(PduRecorder.COMMENT_MARKER)) {
                                     line = line.substring(0, line.indexOf(PduRecorder.COMMENT_MARKER) - 1); //Delete appended Comments
                                 }
+                                if (line.contains("[") || line.contains("]"))
+                                {
+                                    System.out.println("*** [square brackets] no longer included in CSV PLAINTEXT data, ignored");
+                                    line = line.replace("[","").replace("]","");
+                                }
                                 //Pattern splitting needed for playback of unencoded streams
-                                REGEX = "\\],\\[";
+//                                REGEX = "\\],\\[";
+                                REGEX = ",";
                                 pattern = Pattern.compile(REGEX);
 
                                 sa = pattern.split(line);
-                                //Add the "]" to the end of sa[0]. It was taken off by the split
-                                sa[0] = sa[0].concat("]");
-                                //Add the "]" to the end of sa[0]. It was taken off by the split
-                                if (sa.length > 1)
-                                    sa[1] = "[".concat(sa[1]);
+//                                //Add the "]" to the end of sa[0]. It was taken off by the split
+//                                sa[0] = sa[0].concat("]");
+//                                //Add the "]" to the end of sa[0]. It was taken off by the split
+//                                if (sa.length > 1)
+//                                    sa[1] = "[".concat(sa[1]);
 
                                 break;
 
@@ -180,53 +200,62 @@ public class PduPlayer {
                                 System.err.println("Encoding'" + pduLogEncoding + " not recognized or supported");
                         }
 
-                        if (sa != null && sa.length != 2) {
-                            System.err.println("Error: parsing error.  Line follows.");
+                        // timestamp is 8 bytes, size of smallest PDU?
+                        if (pduLogEncoding.equals(PduRecorder.ENCODING_PLAINTEXT) && 
+                            (sa != null) && (sa.length < 8) && (sa.length != 0))
+                        {
+                            System.err.println("Error: ENCODING_PLAINTEXT parsing error due to line too short, offending line follows:");
                             System.err.println(line);
-                            byebye();
+                            exitWithFailure();
                         }
 
                         if (startNanoTime == null) {
-                            startNanoTime = System.nanoTime();
+                            startNanoTime = System.nanoTime(); // initialize
                         }
-                        
-                        switch (pduLogEncoding) {
-                            case "ENCODING_BASE64":
-                                pduTimeBytes = base64Decoder.decode(sa[0]);
+                        // get timestamp pduTimeBytes, i.e. 8 bytes represented by a Java long
+                        switch (pduLogEncoding)
+                        {
+                            case PduRecorder.ENCODING_BASE64:
+                                // no longer computed separately in BASE64, one single block is decompressed instead of two
+//                              pduTimeBytes = base64Decoder.decode(sa[0]); 
                                 break;
 
-                            case "ENCODING_PLAINTEXT":
-
-                                //Split first String into multiple Strings cotaining integers
-                                REGEX = ",";
-                                pattern = Pattern.compile(REGEX);
-
-                                sa[0] = sa[0].substring(1, sa[0].length() - 1);
-
-                                splitString = pattern.split(sa[0]);
-
-                                //Define an array to store the in values from the string and initalize it to a value drifferent from NULL
-                                arr = new int[splitString.length];
-
-                                //Test
-                                for (int x = 0; x < splitString.length; x++) {
-
-                                    tempString = splitString[x].trim();
-
-                                    tempInt = Integer.parseInt(tempString);
-                                    arr[x] = tempInt;
+                            case PduRecorder.ENCODING_PLAINTEXT:
+
+//                                // Split first String into multiple Strings cotaining integers
+//                                REGEX = ",";
+//                                pattern = Pattern.compile(REGEX);
+//
+////                                sa[0] = sa[0].substring(1, sa[0].length() - 1); // no longer has prepended [
+//
+//                                splitString = pattern.split(sa[0]);
+//
+//                                //Define an array to store the in values from the string and initalize it to a value different from NULL
+//                                intArray = new int[8];
+//
+//                                //Test
+//                                for (int x = 0; x < splitString.length; x++) {
+//
+//                                    tempString = splitString[x].trim();
+//
+//                                    tempInt = Integer.parseInt(tempString);
+//                                    arr[x] = tempInt;
+//                                }
+//                                // Credit:  https://stackoverflow.com/questions/1086054/how-to-convert-int-to-byte
+//                                byteBuffer1 = ByteBuffer.allocate(arr.length * 4);
+//                                intBuffer = byteBuffer1.asIntBuffer();
+//                                intBuffer.put(arr);
+//
+//                                pduTimeBytes = byteBuffer1.array();
+                                
+                                for (int i = 0; i < 8; i++)
+                                {
+                                    pduTimeBytes[i] = Byte.parseByte(sa[i]);
                                 }
-                                // Credit:  https://stackoverflow.com/questions/1086054/how-to-convert-int-to-byte
-                                byteBuffer1 = ByteBuffer.allocate(arr.length * 4);
-                                intBuffer = byteBuffer1.asIntBuffer();
-                                intBuffer.put(arr);
-
-                                pduTimeBytes = byteBuffer1.array();
                                 break;
 
                             default:
                                 System.err.println("Encoding'" + pduLogEncoding + " not recognized or supported");
-
                         }
 
                         pduTimeInterval = Longs.fromByteArray(pduTimeBytes);
@@ -234,83 +263,101 @@ public class PduPlayer {
 
                         targetSimTime = startNanoTime + pduTimeInterval;  // when we should send the packet
                         now = System.nanoTime();
-                        sleepTime = targetSimTime - now; //System.nanoTime(); // the difference between then and now
+                        sleepTime = targetSimTime - now; // the difference between then and now
 
-                        if (sleepTime > 20000000) { // 20 ms //
+                        if (sleepTime > 20000000) { // 20 msec
                             //System.out.println("sim interval = " + pduTimeInterval + ", sleeping for " + sleepTime/1000000l + " ms");
                             sleep(sleepTime / 1000000L, (int) (sleepTime % 1000000L));
                         }
 
-                        byte[] buffer;
-
-                        switch (pduLogEncoding) {
-                            case "ENCODING_BASE64":
-                                buffer = base64Decoder.decode(sa[1]);
-                                if (netSend) {
-                                    datagramPacket = new DatagramPacket(buffer, buffer.length, addr, port);
+                        // now get rest of buffer for PDU fields
+                        byte[] byteBuffer, pduBuffer;
+                        switch (pduLogEncoding)
+                        {
+                            case PduRecorder.ENCODING_BASE64:
+                                // TODO if string included prior comma, handle it
+                                byteBuffer = base64Decoder.decode(sa[0]); // no longer a pair of comma-separated blocks
+                                 pduBuffer = new byte[byteBuffer.length - 8];
+                                // get pduTimeBytes from first 8 characters, then remainder of buffer
+                                for (int i = 0; i < byteBuffer.length; i++)
+                                {
+                                    if (i < 8)
+                                        pduTimeBytes[i] = byteBuffer[i];
+                                    else pduBuffer[i-8] = byteBuffer[i];
+                                }
+                                
+                                if (netSend) 
+                                {
+                                    datagramPacket = new DatagramPacket(pduBuffer, pduBuffer.length, addr, port);
                                     datagramSocket.send(datagramPacket);
-                                    type = DisPduType.getEnumForValue(Byte.toUnsignedInt(buffer[2])); // 3rd byte
-                                    System.out.println("Sent PDU: " + type);
+                                    pduType = DisPduType.getEnumForValue(Byte.toUnsignedInt(pduBuffer[2])); // 3rd byte
+                                    System.out.println("Sent PDU: " + pduType);
                                 }
                                 break;
 
-                            case "ENCODING_PLAINTEXT":
+                            case PduRecorder.ENCODING_PLAINTEXT:
 
                                 //---Code Tobi for Plain Text---
                                 // Handle the second String
                                 // Split second String into multiple Strings containing integers
-                                REGEX = ",";
-                                pattern = Pattern.compile(REGEX);
-
-                                sa[1] = sa[1].substring(1, sa[1].length() - 1);
-
-                                splitString = pattern.split(sa[1]);
-
-                                //Define an array to store the in values from the string and initalize it to a value drifferent from NULL
-                                arr = new int[splitString.length];
-
-                                //Test
-                                for (int x = 0; x < splitString.length; x++) {
-
-                                    tempString = splitString[x].trim();
-
-                                    tempInt = Integer.parseInt(tempString);
-                                    arr[x] = tempInt;
-
-                                    //System.out.println(tempInt);
-                                }
-
-                                // Credit:  https://stackoverflow.com/questions/1086054/how-to-convert-int-to-byte
-                                byteBuffer2 = ByteBuffer.allocate(arr.length * 4);
-                                intBuffer = byteBuffer2.asIntBuffer();
-                                intBuffer.put(arr);
-
-                                buffer = byteBuffer2.array();
-
-                                //When the byteBuffer stores the array of Integers into the byte array it stores a 7 as 0 0 0 7.
-                                //Therefore a shortBuffer is created where only every fourth value is stored.
-                                //it must be done with modulo instead of testing for "0" because a "0" could be there as value and not as padding
-                                bufferShort = new byte[byteBuffer2.array().length / 4];
-
-                                int bufferShortCounter = 0;
-
-                                for (int i = 1; i < byteBuffer2.array().length; i++) {
-
-                                    if (((i + 1) % 4) == 0) {
-
-                                        bufferShort[bufferShortCounter] = buffer[i];
-                                        bufferShortCounter++;
-                                    }
+//                                REGEX = ",";
+//                                pattern = Pattern.compile(REGEX);
+//
+////                                sa[1] = sa[1].substring(1, sa[1].length() - 1); // no longer has prepended [
+//
+//                                splitString = pattern.split(sa[1]);
+//
+//                                // Define an array to store the in values from the string and initalize it to a value different from NULL
+//                                intArray = new int[splitString.length];
+//
+//                                //Test
+//                                for (int x = 0; x < splitString.length; x++) {
+//
+//                                    tempString = splitString[x].trim();
+//
+//                                    tempInt = Integer.parseInt(tempString);
+//                                    intArray[x] = tempInt;
+//
+//                                    //System.out.println(tempInt);
+//                                }
+//                                // Credit:  https://stackoverflow.com/questions/1086054/how-to-convert-int-to-byte
+//                                byteBuffer2 = ByteBuffer.allocate(intArray.length * 4);
+//                                intBuffer = byteBuffer2.asIntBuffer();
+//                                intBuffer.put(intArray);
+//
+//                                buffer = byteBuffer2.array();
+//
+//                                //When the byteBuffer stores the array of Integers into the byte array it stores a 7 as 0 0 0 7.
+//                                //Therefore a shortBuffer is created where only every fourth value is stored.
+//                                //it must be done with modulo instead of testing for "0" because a "0" could be there as value and not as padding
+//                                bufferShort = new byte[byteBuffer2.array().length / 4];
+//
+//                                int bufferShortCounter = 0;
+//
+//                                for (int i = 1; i < byteBuffer2.array().length; i++) {
+//
+//                                    if (((i + 1) % 4) == 0) {
+//
+//                                        bufferShort[bufferShortCounter] = buffer[i];
+//                                        bufferShortCounter++;
+//                                    }
+//                                }
+                                
+                                byteBufferShort = new byte[sa.length - 8]; // skip first 8 bytes used for timestamp
+                                for (int i = 8; i < byteBufferShort.length; i++)
+                                {
+                                    byteBufferShort[i-8] = Byte.parseByte(sa[i]);
                                 }
                                 if (netSend) {
-                                    datagramPacket = new DatagramPacket(bufferShort, bufferShort.length, addr, port);
+                                    datagramPacket = new DatagramPacket(byteBufferShort, byteBufferShort.length, addr, port);
                                     datagramSocket.send(datagramPacket);
+                                    pduType = DisPduType.getEnumForValue(Byte.toUnsignedInt(byteBufferShort[2])); // 3rd byte
+                                    System.out.println("Sent PDU: " + pduType);
+                                    
                                     // Add Points to X3D Components
-                                    globalByteBufferForX3dInterPolators = bufferShort.clone();
-                                    x3dInterpolators.addPointsToMap(globalByteBufferForX3dInterPolators); // gets cloned again
-                                    x3dLineSet.addPointsToMap(globalByteBufferForX3dInterPolators); // gets cloned again
-                                    type = DisPduType.getEnumForValue(Byte.toUnsignedInt(bufferShort[2])); // 3rd byte
-                                    System.out.println("Sent PDU: " + type);
+//                                    globalByteBufferForX3dInterPolators = bufferShort.clone();
+//                                    x3dInterpolators.addPointsToMap(globalByteBufferForX3dInterPolators); // gets cloned again
+//                                    x3dLineSet.addPointsToMap(globalByteBufferForX3dInterPolators); // gets cloned again
                                 }
                                 break;
 
@@ -318,17 +365,17 @@ public class PduPlayer {
                                 break;
                         }
 
-                        //ToDo: Is this also necessary for buffershort? If yes, put it inside the switch/Case statement
+                        // TODO Is this also necessary for buffershort? If yes, put it inside the switch/Case statement
                         if (rawListener != null) {
-                            rawListener.receiveBytes(bufferShort);
+                            rawListener.receiveBytes(byteBufferShort);
                         }
                         pduCount++;
                         if (scenarioPduCount != null) {
-                            scenarioPduCount++;
+                            scenarioPduCount++; // also increment overall PDU count
                         }
 
                         if (showPduCountsOneTime || pduCount % 5 == 0) {
-                            showCounts();
+//                            showCounts(); // TODO
                         }
                     }
                 }
@@ -344,7 +391,8 @@ public class PduPlayer {
             }
         } catch (IOException ex) {
             System.err.println("Exception reading/writing pdus: " + ex.getClass().getSimpleName() + ": " + ex.getLocalizedMessage());
-            thrd = null;
+            ex.printStackTrace();
+            playerThread = null;
             closer();
         }
     }
@@ -360,7 +408,7 @@ public class PduPlayer {
         showPduCountsOneTime = false;
     }
 
-    private void byebye() throws IOException {
+    private void exitWithFailure() throws IOException {
         System.out.println("Replay stopped.");
         closer();
         throw new IOException("PduPlayer parsing error");
@@ -390,6 +438,8 @@ public class PduPlayer {
             System.out.print("Total PDUs: ");
             showCounts();
             System.out.println();
+            System.out.flush();
+            System.err.flush();
             System.out.println("End of replay from " + f.getName());
 //            System.out.println(line.substring(PduRecorder.FINISH_COMMENT_MARKER.length()));
 
@@ -441,8 +491,10 @@ public class PduPlayer {
         String multicastAddress = DEFAULT_MULTICAST_ADDRESS;
         int       multicastPort = DEFAULT_MULTICAST_PORT;
         boolean sendToNet = true;
-    
+        
+        // create instance of class in this static block
         PduPlayer pduPlayer = new PduPlayer(multicastAddress, multicastPort, Path.of(outputDirectory), sendToNet);
-        pduPlayer.begin();
+        // thread automatically starts up when class is instantiated
+        pduPlayer.begin(); // default is self test through all logs in ancestor pduLog subdirectory
     }
 }
-- 
GitLab