CORDA-1709 - The MVP blob inspector, able to inspect network service blobs (#3503)

* Cleanup and improvements to the serialisation format of JacksonSupport (needed for CORDA-1238) (#3102)

Also deprecated all the public members that shouldn't have leaked into the public API.

(cherry picked from commit 3bb95c3)

* CORDA-1238: Updated JacksonSupport to support SerializedBytes, CertPath, X509Certificate and the signature classes (#3145)

SerializedBytes are first converted to the object it represents before being serialised as a pojo.

These changes will be needed to support the the blob inspector when it will output to YAML/JSON.

(cherry picked from commit b031e66)

* Cherry picked part of commit 824adca to port over *only* the JackSupport refactoring.

* CORDA-1238: Moved the blob inspector out of experimental and wired it to JackonSupport (#3224)

The existing output format was not complete and so was deleted to avoid it becoming a tech debt. We can always resurrect it at a later point.

(cherry picked from commit 4e0378d)

* Added back support for parsing OpaqueBytes as UTF-8 strings in JacksonSupport (#3240)

(cherry picked from commit d772bc8)

* Cleaned up blob inspector doc (#3284)

(cherry picked from commit b7fbebb)

* Blobinspector: trace level logging with --verbose (#3313)

(cherry picked from commit 6a2e50b)

* Cherry picked part of commit 3046843 to fix issue with --version

* Fixes to the api file
This commit is contained in:
Shams Asari
2018-07-03 19:58:13 +01:00
committed by Katelyn Baker
parent 00c9b8ce49
commit 9fc108aa1e
27 changed files with 1317 additions and 332 deletions

View File

@ -0,0 +1,29 @@
apply plugin: 'java'
apply plugin: 'kotlin'
dependencies {
compile project(':client:jackson')
compile project(':node-api')
compile 'info.picocli:picocli:3.0.0'
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"
testCompile project(':test-utils')
testCompile "junit:junit:$junit_version"
}
jar {
from(configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }) {
exclude "META-INF/*.SF"
exclude "META-INF/*.DSA"
exclude "META-INF/*.RSA"
}
baseName 'blobinspector'
manifest {
attributes(
'Automatic-Module-Name': 'net.corda.blobinspector',
'Main-Class': 'net.corda.blobinspector.MainKt'
)
}
}

View File

@ -0,0 +1,139 @@
package net.corda.blobinspector
import com.fasterxml.jackson.core.JsonFactory
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
import com.jcabi.manifests.Manifests
import net.corda.client.jackson.JacksonSupport
import net.corda.core.internal.isRegularFile
import net.corda.core.internal.rootMessage
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.internal.SerializationEnvironmentImpl
import net.corda.core.serialization.internal._contextSerializationEnv
import net.corda.core.utilities.ByteSequence
import net.corda.core.utilities.sequence
import net.corda.nodeapi.internal.serialization.AMQP_P2P_CONTEXT
import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl
import net.corda.nodeapi.internal.serialization.amqp.AbstractAMQPSerializationScheme
import net.corda.nodeapi.internal.serialization.amqp.AmqpHeaderV1_0
import net.corda.nodeapi.internal.serialization.amqp.DeserializationInput
import picocli.CommandLine
import picocli.CommandLine.*
import java.net.MalformedURLException
import java.net.URL
import java.nio.file.Paths
import kotlin.system.exitProcess
fun main(args: Array<String>) {
val main = Main()
try {
CommandLine.run(main, *args)
} catch (e: ExecutionException) {
val throwable = e.cause ?: e
if (main.verbose) {
throwable.printStackTrace()
} else {
System.err.println("*ERROR*: ${throwable.rootMessage ?: "Use --verbose for more details"}")
}
exitProcess(1)
}
}
@Command(
name = "Blob Inspector",
versionProvider = CordaVersionProvider::class,
mixinStandardHelpOptions = true, // add --help and --version options,
showDefaultValues = true,
description = arrayOf("Inspect AMQP serialised binary blobs")
)
class Main : Runnable {
@Parameters(index = "0", paramLabel = "SOURCE", description = arrayOf("URL or file path to the blob"), converter = arrayOf(SourceConverter::class))
private var source: URL? = null
@Option(names = arrayOf("--format"), paramLabel = "type", description = arrayOf("Output format. Possible values: [YAML, JSON]"))
private var formatType: FormatType = FormatType.YAML
@Option(names = arrayOf("--full-parties"),
description = arrayOf("Display the owningKey and certPath properties of Party and PartyAndReference objects respectively"))
private var fullParties: Boolean = false
@Option(names = arrayOf("--schema"), description = arrayOf("Print the blob's schema first"))
private var schema: Boolean = false
@Option(names = arrayOf("--verbose"), description = arrayOf("Enable verbose output"))
var verbose: Boolean = false
override fun run() {
if (verbose) {
System.setProperty("logLevel", "trace")
}
val bytes = source!!.readBytes().run {
require(size > AmqpHeaderV1_0.size) { "Insufficient bytes for AMQP blob" }
sequence()
}
require(bytes.take(AmqpHeaderV1_0.size) == AmqpHeaderV1_0) { "Not an AMQP blob" }
if (schema) {
val envelope = DeserializationInput.getEnvelope(bytes)
println(envelope.schema)
println()
println(envelope.transformsSchema)
println()
}
initialiseSerialization()
val factory = when (formatType) {
FormatType.YAML -> YAMLFactory()
FormatType.JSON -> JsonFactory()
}
val mapper = JacksonSupport.createNonRpcMapper(factory, fullParties)
val deserialized = bytes.deserialize<Any>()
println(deserialized.javaClass.name)
mapper.writeValue(System.out, deserialized)
}
private fun initialiseSerialization() {
_contextSerializationEnv.set(SerializationEnvironmentImpl(
SerializationFactoryImpl().apply {
registerScheme(AMQPInspectorSerializationScheme)
},
AMQP_P2P_CONTEXT
))
}
}
private object AMQPInspectorSerializationScheme : AbstractAMQPSerializationScheme(emptyList()) {
override fun canDeserializeVersion(byteSequence: ByteSequence, target: SerializationContext.UseCase): Boolean {
return byteSequence == AmqpHeaderV1_0 && target == SerializationContext.UseCase.P2P
}
override fun rpcClientSerializerFactory(context: SerializationContext) = throw UnsupportedOperationException()
override fun rpcServerSerializerFactory(context: SerializationContext) = throw UnsupportedOperationException()
}
private class SourceConverter : ITypeConverter<URL> {
override fun convert(value: String): URL {
return try {
URL(value)
} catch (e: MalformedURLException) {
val path = Paths.get(value)
require(path.isRegularFile()) { "$path is not a file" }
path.toUri().toURL()
}
}
}
private class CordaVersionProvider : IVersionProvider {
override fun getVersion(): Array<String> {
return arrayOf(
"Version: ${Manifests.read("Corda-Release-Version")}",
"Revision: ${Manifests.read("Corda-Revision")}"
)
}
}
private enum class FormatType { YAML, JSON }

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="info">
<Properties>
<Property name="logLevel">off</Property>
</Properties>
<Appenders>
<Console name="STDOUT" target="SYSTEM_OUT" ignoreExceptions="false">
<PatternLayout pattern="[%C{1}.%M] %m%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="${sys:logLevel}">
<AppenderRef ref="STDOUT"/>
</Root>
</Loggers>
</Configuration>