Filter zero bytes from CRaSH input stream. (#460)

* Work around JavaFX injecting 0 bytes into JediTerm's STDIN stream.

* Add (disabled) unit tests for running JediTerm in both Swing and JavaFX.

* Remove tests for running JediTerm under Swing and JavaFX.
This commit is contained in:
Chris Rankin 2017-03-30 11:04:51 +01:00 committed by GitHub
parent 78a0024e00
commit ade9a7dba8
8 changed files with 176 additions and 47 deletions

View File

@ -29,6 +29,7 @@ buildscript {
ext.typesafe_config_version = '1.3.1'
ext.fileupload_version = '1.3.2'
ext.junit_version = '4.12'
ext.mockito_version = '1.10.19'
ext.jopt_simple_version = '5.0.2'
ext.jansi_version = '1.14'
ext.hibernate_version = '5.2.6.Final'

View File

@ -58,7 +58,7 @@
<AppenderRef ref="RollingFile-Appender" />
</Logger>
<Logger name="org.apache.activemq.artemis.core.server" level="error" additivity="false">
<AppenderRef ref="RollingFile-Appender"/>
<AppenderRef ref="RollingFile-Appender"/>
</Logger>
</Loggers>
</Configuration>
</Configuration>

View File

@ -23,7 +23,10 @@ apply plugin: 'application'
evaluationDependsOn(':tools:explorer:capsule')
mainClassName = 'net.corda.demobench.DemoBench'
applicationDefaultJvmArgs = ['-Djava.util.logging.config.class=net.corda.demobench.config.LoggingConfig', '-Dorg.jboss.logging.provider=slf4j']
applicationDefaultJvmArgs = [
'-Djava.util.logging.config.class=net.corda.demobench.config.LoggingConfig',
'-Dorg.jboss.logging.provider=slf4j'
]
repositories {
flatDir {
@ -66,6 +69,7 @@ dependencies {
testCompile "junit:junit:$junit_version"
testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
testCompile "org.mockito:mockito-core:$mockito_version"
}
jar {

View File

@ -1,42 +0,0 @@
package net.corda.demobench.pty;
import com.jediterm.terminal.ProcessTtyConnector;
import com.pty4j.PtyProcess;
import com.pty4j.WinSize;
import java.nio.charset.Charset;
/**
* Copied from JediTerm pty.
* JediTerm is not available in any Maven repository.
* @author traff
*/
public class PtyProcessTtyConnector extends ProcessTtyConnector {
private final PtyProcess myProcess;
private final String name;
PtyProcessTtyConnector(String name, PtyProcess process, Charset charset) {
super(process, charset);
myProcess = process;
this.name = name;
}
@Override
protected void resizeImmediately() {
if (getPendingTermSize() != null && getPendingPixelSize() != null) {
myProcess.setWinSize(
new WinSize(getPendingTermSize().width, getPendingTermSize().height, getPendingPixelSize().width, getPendingPixelSize().height));
}
}
@Override
public boolean isConnected() {
return myProcess.isRunning();
}
@Override
public String getName() {
return name;
}
}

View File

@ -0,0 +1,34 @@
package net.corda.demobench.pty
import com.jediterm.terminal.ProcessTtyConnector
import com.pty4j.PtyProcess
import com.pty4j.WinSize
import java.nio.charset.Charset
/**
* Copied from JediTerm pty.
* JediTerm is not available in any Maven repository.
* @author traff
*/
class PtyProcessTtyConnector(
private val name: String,
private val process: PtyProcess,
charset: Charset
) : ProcessTtyConnector(process.zeroFiltered(), charset) {
override fun getName() = name
override fun isConnected() = process.isRunning
override fun resizeImmediately() {
if (pendingTermSize != null && pendingPixelSize != null) {
process.winSize = WinSize(
pendingTermSize.width,
pendingTermSize.height,
pendingPixelSize.width,
pendingPixelSize.height
)
}
}
}

View File

@ -9,7 +9,6 @@ import net.corda.core.utilities.loggerFor
import java.awt.*
import java.io.IOException
import java.nio.charset.StandardCharsets.UTF_8
import java.util.*
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
@ -44,7 +43,7 @@ class R3Pty(val name: String, settings: SettingsProvider, dimension: Dimension,
fun run(args: Array<String>, envs: Map<String, String>, workingDir: String?) {
check(!terminal.isSessionRunning, { "${terminal.sessionName} is already running" })
val environment = HashMap<String, String>(envs)
val environment = envs.toMutableMap()
if (!UIUtil.isWindows) {
environment["TERM"] = "xterm"
@ -64,4 +63,7 @@ class R3Pty(val name: String, settings: SettingsProvider, dimension: Dimension,
session.start()
}
@Throws(InterruptedException::class)
fun waitFor(): Int? = terminal.ttyConnector?.waitFor()
}

View File

@ -0,0 +1,72 @@
package net.corda.demobench.pty
import java.io.FilterOutputStream
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
/**
* Removes any zero byte values from the output stream.
* This stream is connected to a terminal's STDIN, and
* any zeros received will trigger unwanted key mappings.
* JavaFX seems to be inserting these zeros into the
* stream as it tries to inter-operate with Swing.
*/
private class ZeroFilter(output: OutputStream) : FilterOutputStream(output) {
@Throws(IOException::class)
override fun write(b: Int) {
if (b != 0) {
super.write(b)
}
}
@Throws(IOException::class)
override fun write(raw: ByteArray, offset: Int, len: Int) {
val filtered = ByteArray(len)
var count = 0
var i = 0
while (i < len) {
val b = raw[offset + i]
if (b != 0.toByte()) {
filtered[count] = b
++count
}
++i
}
super.write(filtered, 0, count)
}
}
/**
* Wraps a process's output stream with a zero filter.
*/
private class ZeroFilteringProcess(private val process: Process) : Process() {
private val output: OutputStream
init {
this.output = ZeroFilter(process.outputStream)
}
override fun getOutputStream() = output
override fun getInputStream(): InputStream = process.inputStream
override fun getErrorStream(): InputStream = process.errorStream
@Throws(InterruptedException::class)
override fun waitFor() = process.waitFor()
override fun destroy() = process.destroy()
override fun exitValue() = process.exitValue()
}
/**
* Applies the ZeroFilter to this process.
*/
fun Process.zeroFiltered(): Process = ZeroFilteringProcess(this)

View File

@ -0,0 +1,58 @@
package net.corda.demobench.pty
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.mockito.Mockito.*
import java.io.ByteArrayOutputStream
import java.io.OutputStream
import java.nio.charset.StandardCharsets.UTF_8
class ZeroFilterTest {
private lateinit var output: ByteArrayOutputStream
private lateinit var filter: OutputStream
@Before
fun setup() {
output = ByteArrayOutputStream()
val process = mock(Process::class.java)
`when`(process.outputStream).thenReturn(output)
filter = process.zeroFiltered().outputStream
verify(process).outputStream
}
@Test
fun `non-zero is OK`() {
for (c in 'A'..'Z') {
filter.write(c.toInt())
}
assertEquals(26, output.size())
}
@Test
fun `zero is removed`() {
filter.write(0)
assertEquals(0, output.size())
}
@Test
fun `zero is removed from array`() {
val input = "He\u0000l\u0000lo".toByteArray(UTF_8)
filter.write(input)
assertEquals(5, output.size())
assertEquals("Hello", output.toString("UTF-8"))
}
@Test
fun `zero is removed starting from offset`() {
val input = "H\u0000el\u0000lo W\u0000or\u0000ld!\u0000".toByteArray(UTF_8)
val offset = input.indexOf('W'.toByte())
filter.write(input, offset, input.size - offset)
assertEquals(6, output.size())
assertEquals("World!", output.toString("UTF-8"))
}
}