diff --git a/tools/decode-packets/src/main/scala/net/psforever/tools/decodePackets/DecodePackets.scala b/tools/decode-packets/src/main/scala/net/psforever/tools/decodePackets/DecodePackets.scala index 3ffc9d56f..25b1128e5 100644 --- a/tools/decode-packets/src/main/scala/net/psforever/tools/decodePackets/DecodePackets.scala +++ b/tools/decode-packets/src/main/scala/net/psforever/tools/decodePackets/DecodePackets.scala @@ -12,7 +12,7 @@ import scopt.OParser import scala.collection.parallel.CollectionConverters._ import scala.io.{Codec, Source} import scala.sys.process._ -import scala.util.{Try, Using} +import scala.util.Using case class Config( inDir: String = System.getProperty("user.dir"), @@ -41,7 +41,7 @@ object DecodePackets { .text("Input directory"), opt[Unit]('p', "preprocessed") .action((_, c) => c.copy(preprocessed = true)) - .text("Files are already preprocessed gcapy ascii files (do not call gcapy)"), + .text("Files are already preprocessed gcapy ASCII files"), opt[Unit]('s', "skip-existing") .action((_, c) => c.copy(skipExisting = true)) .text("Skip files that already exist in out-dir"), @@ -91,6 +91,12 @@ object DecodePackets { FileUtils.forceDelete(tmpFolder) } + /** + * Enumerate over file names found in the given directory for later. + * @param directory where the files are found + * @param opts configuration entity where the files are collated + * @return configuration entity + */ private def getAllFilesFromDirectory(directory: String, opts: Config): Config = { val inDir = Paths.get(directory) if (Files.exists(inDir) && Files.isDirectory(inDir)) { @@ -108,28 +114,37 @@ object DecodePackets { } } + /** + * The primary entry point into the process of parsing the packet capture files + * and producing the decoded packet data. + * Should be configurable for whatever state that the packet capture file can be structured. + * @param files all of the discovered files for consideration + * @param extension file extension being concatenated + * @param temporaryDirectory destination directory where files temporarily exist while being written + * @param outDirectory destination directory where the files are stored after being written + * @param skipExisting do not write a file that already exists at the destination directory + * @param readDecodeAndWrite next step of the file decoding process + */ private def decodeFilesUsing( files: Seq[File], extension: String, temporaryDirectory: File, outDirectory: String, skipExisting: Boolean, - decodingFunc: (File,BufferedWriter)=>Try[List[String]] + readDecodeAndWrite: (File,BufferedWriter)=>Unit ): Unit = { files.par.foreach { file => val fileName = file.getName.split(extension)(0) - val outFilePath = outDirectory + "/" + fileName + ".txt" + val fileNameWithExtension = fileName + ".txt" + val outFilePath = outDirectory + "/" + fileNameWithExtension val outFile = new File(outFilePath) if (skipExisting && outFile.exists()) { println(s"file $fileName skipped due to params") } else { - val tmpFilePath = temporaryDirectory.getAbsolutePath + "/" + fileName + ".txt" + val tmpFilePath = temporaryDirectory.getAbsolutePath + "/" + fileNameWithExtension val writer = new BufferedWriter(new FileWriter(new File(tmpFilePath), false)) try { - decodingFunc(file, writer).collect { lines => - println(s"${lines.size} lines read from file $fileName") - processAndRewriteFileContents(writer, lines) - } + readDecodeAndWrite(file, writer) writer.close() Files.move(Paths.get(tmpFilePath), Paths.get(outFilePath), StandardCopyOption.REPLACE_EXISTING) } catch { @@ -142,22 +157,41 @@ object DecodePackets { } } - private def preprocessed(file: File, writer: BufferedWriter): Try[List[String]] = { + /** + * Read data from ASCII transcribed gcapy files. + * @param file file to read + * @param writer writer for output + */ + private def preprocessed(file: File, writer: BufferedWriter): Unit = { Using(Source.fromFile(file.getAbsolutePath)(decoder)) { source => - source.getLines().toList + println(s"${decodeFileContents(writer, source.getLines())} lines read from file ${file.getName}") } } - private def gcapy(file: File, writer: BufferedWriter): Try[List[String]] = { + /** + * Read data from gcapy files. + * @param file file to read + * @param writer writer for output + */ + private def gcapy(file: File, writer: BufferedWriter): Unit = { Using(Source.fromString(s"gcapy -xa '${file.getAbsolutePath}'" !!)) { source => - source.getLines().toList + println(s"${decodeFileContents(writer, source.getLines())} lines read from file ${file.getName}") } } + /** + * Decode each line from the original file, decode it, then write it to the output file. + * @param writer writer for output + * @param lines raw packet data from the source + * @throws java.io.IOException if writing data goes incorrectly + * @return number of lines read from the source + */ @throws(classOf[IOException]) - private def processAndRewriteFileContents(writer: BufferedWriter, lines: List[String]): Unit = { + private def decodeFileContents(writer: BufferedWriter, lines: Iterator[String]): Int = { var linesToSkip = 0 + var linesRead: Int = 0 for (line <- lines.drop(1)) { + linesRead += 1 if (linesToSkip > 0) { linesToSkip -= 1 } else { @@ -186,12 +220,25 @@ object DecodePackets { writer.newLine() } } + linesRead } - /** Traverse down any nested packets such as SlottedMetaPacket, MultiPacket and MultiPacketEx and add indent for each layer down - * The number of lines to skip will be returned so duplicate lines following SlottedMetaPackets in the gcapy output can be filtered out - */ - private def recursivelyHandleNestedPacket(decodedLine: String, writer: BufferedWriter, depth: Int = 0): Int = { + /** + * Traverse down any nested packets such as `SlottedMetaPacket`, `MultiPacket`, and `MultiPacketEx` + * and add indent for each layer down. + * A number of lines to skip will be returned so duplicate lines following the nested packet can be filtered out. + * @param decodedLine decoded packet data + * @param writer writer for output + * @param depth the number of layers to indent + * @throws java.io.IOException if writing data goes incorrectly + * @return current indent layer + */ + @throws(classOf[IOException]) + private def recursivelyHandleNestedPacket( + decodedLine: String, + writer: BufferedWriter, + depth: Int = 0 + ): Int = { if (decodedLine.indexOf("Failed to parse") >= 0) return depth val regex = "(0x[a-f0-9]+)".r val matches = regex.findAllIn(decodedLine) @@ -213,6 +260,11 @@ object DecodePackets { linesToSkip } + /** + * Reformat data common to gcapy packet data files and their derivation form of ASCII transcription. + * @param line original string + * @return transformed string + */ private def shortGcapyString(line: String): String = { val regex = "Game record ([0-9]+) at ([0-9.]+s) is from ([S|C]).* to ([S|C]).*contents (.*)".r line match { @@ -222,11 +274,21 @@ object DecodePackets { } } + /** + * A nested packet contains more packets. + * @param decodedLine decoded packet data + * @return `true`, if the packet is nested; `false`, otherwise + */ private def isNestedPacket(decodedLine: String): Boolean = { // Also matches MultiPacketEx decodedLine.indexOf("MultiPacket") >= 0 || decodedLine.indexOf("SlottedMetaPacket") >= 0 } + /** + * Actually decode the packet data. + * @param hexString raw packet data + * @return decoded packet data + */ private def decodePacket(hexString: String): String = { PacketCoding.decodePacket(ByteVector.fromValidHex(hexString)) match { case Successful(value) => value.toString