tech.pylons.ipc.HttpIpcWire.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of libpylons Show documentation
Show all versions of libpylons Show documentation
Library providing common functionality for interacting with the Pylons ecosystem
The newest version!
package tech.pylons.ipc
import tech.pylons.lib.logging.LogEvent
import tech.pylons.lib.logging.LogTag
import tech.pylons.lib.logging.Logger
import org.apache.commons.codec.binary.Hex
import java.net.ServerSocket
import java.nio.ByteBuffer
import java.nio.charset.Charset
private const val HANDSHAKE_MAGIC = "__PYLONS_WALLET_SERVER"
private const val HANDSHAKE_REPLY_MAGIC = "__PYLONS_WALLET_CLIENT"
object HttpIpcWire {
private val ascii = Charset.forName("US-ASCII")
private fun readNext(): ByteArray? {
Logger.implementation.log(LogEvent.AWAIT_TCP, "{\"info\":\"reading bytes from client\"}", LogTag.info)
ServerSocket(50001).use { serverSocket ->
serverSocket.accept().use { socket ->
Logger.implementation.log(LogEvent.ACCEPTED_CLIENT, "{\"client\":\"${socket.localAddress}\"}", LogTag.info)
socket.getInputStream().use {
while (it.available() == 0) Thread.sleep(100)
val lenBuffer = ByteArray(4)
var l = it.read(lenBuffer, 0, lenBuffer.size)
if (l != 4) {
throw Exception("Got $l bytes down when getting data length; expected a 32-bit integer (4 octets)")
}
val len = ByteBuffer.wrap(lenBuffer).int
val dataBuffer = ByteArray(len)
l = it.read(dataBuffer, 0, len)
if (l != len) {
throw Exception("Got $l bytes down; expected $len")
}
Logger.implementation.log(
LogEvent.GOT_BYTES,
"""{"msg":"${ascii.decode(ByteBuffer.wrap(dataBuffer)).subSequence(0, len)}"}""", LogTag.info)
if (len == 0) return null
val m = ByteArray(len)
for (i in 0 until len) m[i] = dataBuffer[i]
return m
}
}
}
}
fun writeString(s: String) =
writeBytes(ascii.encode(s).array())
private fun writeBytes(bytes: ByteArray) {
Logger.implementation.log(
LogEvent.AWAIT_TCP,
"{\"info\":\"Start write... (${bytes.size}) bytes\"}", LogTag.info)
ServerSocket(50001).use { serverSocket ->
serverSocket.accept().use { socket ->
Logger.implementation.log(
LogEvent.ACCEPTED_CLIENT,
"{\"client\":\"${socket.localAddress}\"}", LogTag.info)
socket.getOutputStream().use {
val lenBuffer = ByteArray(4)
ByteBuffer.wrap(lenBuffer).putInt(bytes.size)
it.write(lenBuffer + bytes)
it.flush()
Logger.implementation.log(
LogEvent.SENT_BYTES,
"{\"data\":\"${bytes}\"}", LogTag.info)
}
}
}
}
private fun getStringRaw(): String {
var m: ByteArray? = null
while (m == null) m = readNext()
return ascii.decode(ByteBuffer.wrap(m)).toString()
}
private fun checkForHandshake(): String {
// HACK: We use reflection to get current process handle here to ensure we compile on java 8.
// It'll crash if it actually runs on j8, but that's fine; this code should never actually
// run on Android devices. Reflection is obv. considerably slower, but that doesn't
// matter b/c we only grab the PID once when we attach to a process.
val pid = (Class.forName("java.lang.ProcessHandle").getMethod("current").invoke(null) as ProcessHandle).pid()
val byteBuffer = ByteBuffer.allocate(Long.SIZE_BYTES + 4).putInt(IPCLayer.implementation!!.walletId).putLong(pid)
writeBytes(ascii.encode(HANDSHAKE_MAGIC).array() + byteBuffer.array())
println("getting reply now")
return getStringRaw()
}
fun doHandshake () {
println("start handshake")
val handshakeReply = checkForHandshake()
println("foo")
if (!handshakeReply.startsWith(HANDSHAKE_REPLY_MAGIC)) {
Logger.implementation.log(
LogEvent.IPC_HANDSHAKE_FAIL,
"""{"handshake_reply":"$handshakeReply"}""",
LogTag.walletError)
}
println("bar")
IPCLayer.implementation!!.clientId = handshakeReply.removePrefix(HANDSHAKE_REPLY_MAGIC).toInt()
println("handshake OK")
writeString("OKfillerfillerfillerfillerfillerfiller")
}
fun readMessage(): String? {
val bytes = readNext() ?: return null
val json = ascii.decode(ByteBuffer.wrap(bytes)).toString()
Logger.implementation.log(
LogEvent.PARSED_MESSAGE,
"""{"bytes":"${Hex.encodeHexString(bytes)}", "msg":{$json}""", LogTag.info)
return json
}
}