180 lines
6.6 KiB
Markdown
180 lines
6.6 KiB
Markdown
# Kiloparsec
|
|
|
|
The new generation of __PARanoid SECurity__ protocol, advanced, faster, more secure. It also allows connecting any "
|
|
block device" transport to the same local interface. Out if the box it
|
|
provides the following transports:
|
|
|
|
| name | JVM | JS | native |
|
|
|----------------|-----|----|-------------------|
|
|
| TCP/IP server | ✓ | | β @0.2.5-SNAPSHOT |
|
|
| TCP/IP client | ✓ | | β @0.2.5-SNAPSHOT |
|
|
| Websock server | ✓ | | |
|
|
| Websock client | ✓ | ✓ | ✓ |
|
|
|
|
At the moment we're working on supporting TCP/IP on most native targets. This feature is planned to rach public beta in
|
|
August and production in early september 2024.
|
|
|
|
## TCP/IP transport
|
|
|
|
It is the fastest. JVM implementation uses nio2 async sockets and optimizes TCP socket to play
|
|
well with blocks (smart NO_DELAY mode). It is multiplatform, nut lacks of async TCP/IP support
|
|
on natvic targetm this is where I need help having little time. I'd prefer to use something asyn like UV on native
|
|
targets.
|
|
|
|
I know no existing way to implement it in KotlinJS for the modern browsers.
|
|
|
|
## Websock server
|
|
|
|
While it is much slower than pure TCP, it is still faster than any http-based transport. It uses binary frames based on
|
|
the Ktor server framework to easily integrate with web services. We recommend using it instead of a classic HTTP API as
|
|
it beats it in terms of speed and server load even with HTTP/2.
|
|
|
|
We recommend to create the `KiloInterface<S>` instance and connect it to the websock and tcp servers in real
|
|
applications to get easy access from anywhere.
|
|
|
|
# Usage
|
|
|
|
Th elibrary should be used as maven dependency, not as source.
|
|
|
|
## Adding dependency
|
|
|
|
### Declare maven repository:
|
|
|
|
Add the private repository to your `build.gradle.kts`, like:
|
|
|
|
```kotlin
|
|
repositories {
|
|
maven("https://gitea.sergeych.net/api/packages/SergeychWorks/maven")
|
|
}
|
|
```
|
|
|
|
### Add dependency block
|
|
|
|
It could be, depending on your project structure, something like:
|
|
|
|
```kotlin
|
|
val commonMain by getting {
|
|
dependencies {
|
|
api("net.sergeych:kiloparsec:0.2.4")
|
|
}
|
|
}
|
|
```
|
|
|
|
## Create shared interface for your server and the client
|
|
|
|
It could be a multiplatform library that exports it or just a shared or copied source file declaring structures
|
|
and functions available, like:
|
|
|
|
```kotlin
|
|
// Api.kt
|
|
|
|
@Serializable
|
|
class FooArgs(val text: String, val number: Int = 42)
|
|
|
|
// Server-side interface
|
|
val cmdSetFoo by command<FooArgs, Unit>()
|
|
val cmdGetFoo by command<Unit, FooArgs>()
|
|
val cmdPing by command<String, String>()
|
|
val cmdCheckConnected by command<Unit, Boolean>()
|
|
|
|
// client-side interface (called from the server)
|
|
val cmdPushClient by command<String, Unit>()
|
|
```
|
|
|
|
## Call it from the client:
|
|
|
|
Remember, we need to implement client interface `cmdPushClient` in our example, so we need to provide
|
|
local interace too:
|
|
|
|
```kotlin
|
|
// Unit: no session on the client:
|
|
val client = websocketClient<Unit>("wss://your.host.com/kp") {
|
|
// This is server-callable function we export:
|
|
on(cmdPushClient) {
|
|
"server push: $it"
|
|
}
|
|
}
|
|
|
|
// If we want to collect connected state changes (this is optional)
|
|
launch {
|
|
client.connectedStateFlow.collect {
|
|
if (it)
|
|
println("I am connected")
|
|
else
|
|
println("trying to connect...")
|
|
}
|
|
}
|
|
|
|
// now we can call server's functions
|
|
client.call(cmdSetFoo, FooArgs("bar", 117))
|
|
assertEquals(FooArgs("bar", 117), client.call(cmdGetFoo))
|
|
|
|
```
|
|
|
|
## Create ktor-based server
|
|
|
|
Normally server side needs some session. It is convenient and avoid sending repeating data on each request speeding up
|
|
the protocol. With KILOPARSEC it is rather basic operation:\
|
|
|
|
~~~kotlin
|
|
// Our session just keeps Foo for cmd{Get|Set}Foo:
|
|
data class Session(var fooState: FooArgs? = null)
|
|
|
|
// Let's now provide interface we export, it will be used on each connection automatically:
|
|
|
|
// Note server interface uses Session:
|
|
val serverInterface = KiloInterface<Session>().apply {
|
|
onConnected {
|
|
// Do some initialization
|
|
session.fooState = null
|
|
}
|
|
// Exceptions are passed through the network and re-created (re-thrown) on other side:
|
|
on(cmdGetFoo) { session.fooState ?: throw IllegalStateException("foo is not yet set") }
|
|
on(cmdSetFoo) { session.fooState = it }
|
|
}
|
|
|
|
// now create server using ktor (see ktor project for more):
|
|
|
|
val ns: NettyApplicationEngine = embeddedServer(Netty, port = 8080, host = "0.0.0.0", module = {
|
|
setupWebsocketServer(serverInterface) { Session() }
|
|
}).start(wait = false)
|
|
|
|
|
|
~~~
|
|
|
|
# Details
|
|
|
|
It is not compatible with parsec family and no more based on an Universa crypto library. To better fit
|
|
the modern state of threats and rate of cyber crimes, KiloParsec uses more encryption and random key exchange on each
|
|
and every connection (while parsec caches session keys to avoid time-consuming keys exchange). For the same reason,
|
|
keys cryptography for session is shifted to use ed25519 curves which are supposed to provide agreeable strength with
|
|
enough speed to protect every connection with a unique new keys. Also, we completely get rid of SHA2.
|
|
|
|
Kiloparsec also uses a denser binary format [bipack](https://gitea.sergeych.net/SergeychWorks/mp_bintools), no more
|
|
key-values,
|
|
which reveals much less on the inner data structure, providing advanced
|
|
typed RPC interfaces with kotlinx.serialization. There is also Rust
|
|
implementation [bipack_ru](https://gitea.sergeych.net/DiWAN/bipack_ru).
|
|
The architecture allows connecting same functional interfaces to several various type channels at once.
|
|
|
|
Also, the difference from parsecs is that there are no more unencrypted layer commands available to users.
|
|
All RPC is performed over the encrypted connection.
|
|
|
|
# Technical description
|
|
|
|
Kiloparsec is a dull-duplex fully async (coroutine based) Remote Procedure Call protocol with typed parameters
|
|
and support for serializing exceptions (e.g. exception thrown while executing remote command will be caught and
|
|
rethrown at the caller context).
|
|
|
|
Kiloparsec is not REST, it _has advanced session mechanisms_ and built-in authentication based on the same curve keys.
|
|
Integrated tools to prevent MITM attacks include also non-transferred independently generated token that is calculated
|
|
independently on the ends and is never transferred with the network. Comparing it somehow (visually, with QR code, etc)
|
|
could add a very robust guarantee of the connection safety and ingenuity.
|
|
|
|
Kiloparsec has built-in completely asynchronous (coroutine based top-down) transport layer based on TCP (JVM only as for
|
|
now) and the same async Websocket-based transport based on KTOR. Websocket client is multiplatform, though the server is
|
|
JVM only insofar.
|
|
|
|
# Licensing
|
|
|
|
Currently, you need to obtain a license from https://8-rays.dev or Sergey Chernov. |