Blob inspector: add support for hex and base64 inputs.

Also rename the file to make it easier to navigate to it in IntelliJ
by keyboard.
This commit is contained in:
Mike Hearn 2018-07-04 20:32:58 +02:00
parent 21754e4323
commit 20c03d2196
3 changed files with 78 additions and 25 deletions

View File

@ -8,25 +8,48 @@ by allowing the contents of a binary blob file (or URL end-point) to be output i
The latest version of the tool can be downloaded from `here <https://www.corda.net/downloads/>`_.
To run simply pass in the file or URL as the first parameter:
To run simply pass in the file or URL as the first parameter::
``java -jar blob-inspector.jar <file or URL>``
java -jar blob-inspector.jar <file or URL>
Use the ``--help`` flag for a full list of command line options.
When inspecting your custom data structures, there's no need to include the jars containing the class definitions for them
in the classpath. The blob inspector (or rather the serialization framework) is able to synthesis any classes found in the
in the classpath. The blob inspector (or rather the serialization framework) is able to synthesize any classes found in the
blob that aren't on the classpath.
SerializedBytes
~~~~~~~~~~~~~~~
Supported formats
~~~~~~~~~~~~~~~~~
One thing to note is that the binary blob may contain embedded ``SerializedBytes`` objects. Rather than printing these
out as a Base64 string, the blob inspector will first materialise them into Java objects and then output those. You will
see this when dealing with classes such as ``SignedData`` or other structures that attach a signature, such as the
``nodeInfo-*`` files or the ``network-parameters`` file in the node's directory. For example, the output of a node-info
file may look like:
The inspector can read **input data** in three formats: raw binary, hex encoded text and base64 encoded text. For instance
if you have retrieved your binary data and it looks like this::
636f7264610100000080c562000000000001d0000030720000000300a3226e65742e636f7264613a38674f537471464b414a5055...
then you have hex encoded data. If it looks like this it's base64 encoded::
Y29yZGEBAAAAgMViAAAAAAAB0AAAMHIAAAADAKMibmV0LmNvcmRhOjhnT1N0cUZLQUpQVWVvY2Z2M1NlU1E9PdAAACc1AAAAAgCjIm5l...
And if it looks like something vomited over your screen it's raw binary. You don't normally need to care about these
differences because the tool will try every format until it works.
Something that's useful to know about Corda's format is that it always starts with the word "corda" in binary. Try
hex decoding 636f726461 using the `online hex decoder tool here <https://convertstring.com/EncodeDecode/HexDecode>`_
to see for yourself.
**Output data** can be in either a slightly extended form of YaML or JSON. YaML (Yet another markup language) is a bit
easier to read for humans and is the default. JSON can of course be parsed by any JSON library in any language.
.. note:: One thing to note is that the binary blob may contain embedded ``SerializedBytes`` objects. Rather than printing these
out as a Base64 string, the blob inspector will first materialise them into Java objects and then output those. You will
see this when dealing with classes such as ``SignedData`` or other structures that attach a signature, such as the
``nodeInfo-*`` files or the ``network-parameters`` file in the node's directory.
Example
~~~~~~~
Here's what a node-info file from the node's data directory may look like:
**-\\-format=YAML**
::

View File

@ -6,6 +6,7 @@ apply plugin: 'com.jfrog.artifactory'
dependencies {
compile project(':client:jackson')
compile "info.picocli:picocli:$picocli_version"
compile "com.google.guava:guava:$guava_version"
compile "org.slf4j:jul-to-slf4j:$slf4j_version"
compile "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version"
compile "com.jcabi:jcabi-manifests:$jcabi_manifests_version"

View File

@ -2,6 +2,7 @@ package net.corda.blobinspector
import com.fasterxml.jackson.core.JsonFactory
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
import com.google.common.io.BaseEncoding
import com.jcabi.manifests.Manifests
import net.corda.client.jackson.JacksonSupport
import net.corda.core.internal.isRegularFile
@ -43,18 +44,21 @@ fun main(args: Array<String>) {
}
@Command(
name = "Blob Inspector",
name = "blob-inspector",
versionProvider = CordaVersionProvider::class,
mixinStandardHelpOptions = true, // add --help and --version options,
mixinStandardHelpOptions = true, // add --help and --version options,
showDefaultValues = true,
description = ["Inspect AMQP serialised binary blobs"]
description = ["Convert AMQP serialised binary blobs to text"]
)
class BlobInspector : Runnable {
@Parameters(index = "0", paramLabel = "SOURCE", description = ["URL or file path to the blob"], converter = [SourceConverter::class])
var source: URL? = null
private var source: URL? = null
@Option(names = ["--format"], paramLabel = "type", description = ["Output format. Possible values: [YAML, JSON]"])
private var formatType: FormatType = FormatType.YAML
private var formatType: OutputFormatType = OutputFormatType.YAML
@Option(names = ["--input-format"], paramLabel = "type", description = ["Input format. If the file can't be decoded with the given value it's auto-detected, so you should never normally need to specify this. Possible values: [BINARY, HEX, BASE64]"])
private var inputFormatType: InputFormatType = InputFormatType.BINARY
@Option(names = ["--full-parties"],
description = ["Display the owningKey and certPath properties of Party and PartyAndReference objects respectively"])
@ -73,15 +77,12 @@ class BlobInspector : Runnable {
System.setProperty("logLevel", "trace")
}
val bytes = source!!.readBytes().run {
require(size > amqpMagic.size) { "Insufficient bytes for AMQP blob" }
sequence()
}
require(bytes.take(amqpMagic.size) == amqpMagic) { "Not an AMQP blob" }
val inputBytes = source!!.readBytes()
val bytes = parseToBinaryRelaxed(inputFormatType, inputBytes)
?: throw IllegalArgumentException("Error: this input does not appear to be encoded in Corda's AMQP extended format, sorry.")
if (schema) {
val envelope = DeserializationInput.getEnvelope(bytes)
val envelope = DeserializationInput.getEnvelope(bytes.sequence())
out.println(envelope.schema)
out.println()
out.println(envelope.transformsSchema)
@ -89,8 +90,8 @@ class BlobInspector : Runnable {
}
val factory = when (formatType) {
FormatType.YAML -> YAMLFactory()
FormatType.JSON -> JsonFactory()
OutputFormatType.YAML -> YAMLFactory()
OutputFormatType.JSON -> JsonFactory()
}
val mapper = JacksonSupport.createNonRpcMapper(factory, fullParties)
@ -105,6 +106,32 @@ class BlobInspector : Runnable {
}
}
private fun parseToBinaryRelaxed(format: InputFormatType, inputBytes: ByteArray): ByteArray? {
// Try the format the user gave us first, then try the others.
return parseToBinary(format, inputBytes) ?: parseToBinary(InputFormatType.HEX, inputBytes)
?: parseToBinary(InputFormatType.BASE64, inputBytes) ?: parseToBinary(InputFormatType.BINARY, inputBytes)
}
private fun parseToBinary(format: InputFormatType, inputBytes: ByteArray): ByteArray? {
try {
val bytes = when (format) {
InputFormatType.BINARY -> inputBytes
InputFormatType.HEX -> BaseEncoding.base16().decode(String(inputBytes).trim().toUpperCase())
InputFormatType.BASE64 -> BaseEncoding.base64().decode(String(inputBytes).trim())
}
require(bytes.size > amqpMagic.size) { "Insufficient bytes for AMQP blob" }
return if (bytes.copyOf(amqpMagic.size).contentEquals(amqpMagic.bytes)) {
if (verbose)
println("Parsing input as $format")
bytes
} else {
null // Not an AMQP blob.
}
} catch (t: Throwable) {
return null // Failed to parse in some other way.
}
}
private fun initialiseSerialization() {
// Deserialise with the lenient carpenter as we only care for the AMQP field getters
_contextSerializationEnv.set(SerializationEnvironmentImpl(
@ -121,6 +148,7 @@ private object AMQPInspectorSerializationScheme : AbstractAMQPSerializationSchem
override fun canDeserializeVersion(magic: CordaSerializationMagic, target: SerializationContext.UseCase): Boolean {
return magic == amqpMagic
}
override fun rpcClientSerializerFactory(context: SerializationContext) = throw UnsupportedOperationException()
override fun rpcServerSerializerFactory(context: SerializationContext) = throw UnsupportedOperationException()
}
@ -146,5 +174,6 @@ private class CordaVersionProvider : IVersionProvider {
}
}
private enum class FormatType { YAML, JSON }
private enum class OutputFormatType { YAML, JSON }
private enum class InputFormatType { BINARY, HEX, BASE64 }