More work done on cleaning up stack traces

old_release
nathan 2020-08-22 22:58:03 +02:00
parent 8bca94683f
commit 389af93f0a
19 changed files with 693 additions and 1974 deletions

585
LICENSE
View File

@ -1,114 +1,527 @@
- Network -
- Network - Encrypted, high-performance, and event-driven/reactive network stack for Java 11+
[The Apache Software License, Version 2.0]
https://git.dorkbox.com/dorkbox/Network
Copyright 2019 - The Apache Software License, Version 2.0
Dorkbox LLC
Encrypted, high-performance, and event-driven/reactive network stack for Java 6+
- Bennidi Iterator -
https://github.com/bennidi/mbassador
Copyright 2012 - MIT License
Benjamin Diedrichsen
Fast iterators from the MBassador project
- BouncyCastle -
http://www.bouncycastle.org
Copyright 2009 - MIT License
The Legion Of The Bouncy Castle
- Dorkbox Utils -
https://git.dorkbox.com/dorkbox/Utilities
Copyright 2019 - The Apache Software License, Version 2.0
Copyright 2020
Dorkbox LLC
Extra license information
- KryoNet RMI -
[BSD 3-Clause License]
https://github.com/EsotericSoftware/kryonet
Copyright 2008
Nathan Sweet
- FastThreadLocal -
https://github.com/LWJGL/lwjgl3/blob/5819c9123222f6ce51f208e022cb907091dd8023/modules/core/src/main/java/org/lwjgl/system/FastThreadLocal.java
Copyright 2014 - BSD 3-Clause License
Lightweight Java Game Library Project
Riven
- LAN HostDiscovery from Apache Commons JCS -
[The Apache Software License, Version 2.0]
https://issues.apache.org/jira/browse/JCS-40
Copyright 2014
The Apache Software Foundation
- MathUtils, IntArray, IntMap -
[The Apache Software License, Version 2.0]
http://github.com/libgdx/libgdx
Copyright 2013
Mario Zechner <badlogicgames@gmail.com>
Nathan Sweet <nathan.sweet@gmail.com>
- Javassist -
http://www.csg.is.titech.ac.jp/~chiba/java
Copyright 1999 - BSD 3-Clause License
Shigeru Chiba
Bill Burke
Jason T. Greene
Licensed under the MPL/LGPL/Apache triple license
- Netty (Various network + platform utilities) - An event-driven asynchronous network application framework
[The Apache Software License, Version 2.0]
https://netty.io
Copyright 2014
The Netty Project
Contributors. See source NOTICE
- Kotlin -
[The Apache Software License, Version 2.0]
https://github.com/JetBrains/kotlin
Copyright 1980
JetBrains s.r.o. and Kotlin Programming Language contributors
Kotlin Compiler, Test Data+Libraries, and Tools repository contain third-party code, to which different licenses may apply
See: https://github.com/JetBrains/kotlin/blob/master/license/README.md
- Kryo -
https://github.com/EsotericSoftware/kryo
Copyright 2008 - BSD 3-Clause License
Nathan Sweet
- kotlinx.coroutines - Library support for Kotlin coroutines with multiplatform support
[The Apache Software License, Version 2.0]
https://github.com/Kotlin/kotlinx.coroutines
Copyright 2020
JetBrains s.r.o.
- Aeron - Efficient reliable UDP unicast, UDP multicast, and IPC message transport
[The Apache Software License, Version 2.0]
https://github.com/real-logic/aeron
Copyright 1980
Real Logic Limited
- kryo-serializers -
https://github.com/magro/kryo-serializers
Copyright 2010 - The Apache Software License, Version 2.0
Martin Grotzke
Rafael Winterhalter
- Kryo - Fast and efficient binary object graph serialization framework for Java
[BSD 3-Clause License]
https://github.com/EsotericSoftware/kryo
Copyright 2020
Nathan Sweet
Extra license information
- ReflectASM -
[BSD 3-Clause License]
https://github.com/EsotericSoftware/reflectasm
Nathan Sweet
- KryoNet RMI -
https://github.com/EsotericSoftware/kryonet
Copyright 2008 - BSD 3-Clause License
Nathan Sweet
- Objenesis -
[The Apache Software License, Version 2.0]
http://objenesis.org
Objenesis Team and all contributors
- MinLog-SLF4J -
[BSD 3-Clause License]
https://github.com/EsotericSoftware/minlog
Nathan Sweet
- LAN HostDiscovery from Apache Commons JCS -
https://issues.apache.org/jira/browse/JCS-40
Copyright 2014 - The Apache Software License, Version 2.0
The Apache Software Foundation
- Kryo Serializers - Extra kryo serializers
[The Apache Software License, Version 2.0]
https://github.com/magro/kryo-serializers
Copyright 2019
Martin Grotzke
Rafael Winterhalter
- LZ4 and XXhash - LZ4 compression for Java, based on Yann Collet's work
[The Apache Software License, Version 2.0]
https://github.com/jpountz/lz4-java
http://code.google.com/p/lz4/
Copyright 2014
Yann Collet
Adrien Grand
- LZ4 and XXhash -
https://github.com/jpountz/lz4-java
Copyright 2011, 2012 - The Apache Software License, Version 2.0
Yann Collet
Adrien Grand
- Conversant Disruptor - Disruptor is the highest performing intra-thread transfer mechanism available in Java.
[The Apache Software License, Version 2.0]
https://github.com/conversant/disruptor
Copyright 2020
Conversant, Inc
- TypeTools - A simple, zero-dependency library for working with types. Supports Java 1.6+ and Android.
[The Apache Software License, Version 2.0]
https://github.com/jhalterman/typetools
Copyright 2020
Jonathan Halterman and friends
- MathUtils, IntArray, IntMap -
http://github.com/libgdx/libgdx/
Copyright 2013 - The Apache Software License, Version 2.0
Mario Zechner <badlogicgames@gmail.com>
Nathan Sweet <nathan.sweet@gmail.com>
- kotlin-logging - Lightweight logging framework for Kotlin
[The Apache Software License, Version 2.0]
https://github.com/MicroUtils/kotlin-logging
Copyright 2020
Ohad Shai
- SLF4J - Simple facade or abstraction for various logging frameworks
[MIT License]
http://www.slf4j.org
Copyright 2019
QOS.ch
- MinLog-SLF4J -
https://git.dorkbox.com/dorkbox/MinLog-SLF4J
https://github.com/EsotericSoftware/minlog
Copyright 2008 - The Apache Software License, Version 2.0
dorkbox, llc
Nathan Sweet
Dan Brown
Drop-in replacement for MinLog to log through SLF4j.
- Utilities - Utilities for use within Java projects
[The Apache Software License, Version 2.0]
https://git.dorkbox.com/dorkbox/Utilities
Copyright 2020
Dorkbox LLC
Extra license information
- MersenneTwisterFast -
[BSD 3-Clause License]
https://git.dorkbox.com/dorkbox/Utilities
Copyright 2003
Sean Luke
Michael Lecuyer (portions Copyright 1993
- ObjectPool -
https://git.dorkbox.com/dorkbox/ObjectPool
Copyright 2019 - The Apache Software License, Version 2.0
dorkbox, llc
- FileUtil (code from FilenameUtils.java for normalize + dependencies) -
[The Apache Software License, Version 2.0]
https://git.dorkbox.com/dorkbox/Utilities
http://commons.apache.org/proper/commons-io/
Copyright 2013
The Apache Software Foundation
Kevin A. Burton
Scott Sanders
Daniel Rall
Christoph.Reck
Peter Donald
Jeff Turner
Matthew Hawthorne
Martin Cooper
Jeremias Maerki
Stephen Colebourne
- FastThreadLocal -
[BSD 3-Clause License]
https://git.dorkbox.com/dorkbox/Utilities
https://github.com/LWJGL/lwjgl3/blob/5819c9123222f6ce51f208e022cb907091dd8023/modules/core/src/main/java/org/lwjgl/system/FastThreadLocal.java
https://github.com/riven8192/LibStruct/blob/master/src/net/indiespot/struct/runtime/FastThreadLocal.java
Copyright 2014
Lightweight Java Game Library Project
Riven
- ReflectASM -
https://github.com/EsotericSoftware/reflectasm
Copyright 2008 - BSD 3-Clause License
Nathan Sweet
- Base64Fast -
[BSD 3-Clause License]
https://git.dorkbox.com/dorkbox/Utilities
http://migbase64.sourceforge.net/
Copyright 2004
Mikael Grev, MiG InfoCom AB. (base64@miginfocom.com)
- BCrypt -
[BSD 2-Clause "Simplified" or "FreeBSD" license]
https://git.dorkbox.com/dorkbox/Utilities
http://www.mindrot.org/projects/jBCrypt
Copyright 2006
Damien Miller (djm@mindrot.org)
GWT modified version
- SLF4J -
http://www.slf4j.org
Copyright 2008 - MIT License
QOS.ch
- Bias, BinarySearch -
[MIT License]
https://git.dorkbox.com/dorkbox/Utilities
https://github.com/timboudreau/util
Copyright 2013
Tim Boudreau
- ConcurrentEntry -
[The Apache Software License, Version 2.0]
https://git.dorkbox.com/dorkbox/Utilities
Copyright 2016
bennidi
dorkbox
- TypeTools -
https://github.com/jhalterman/typetools/
Copyright 2017 - The Apache Software License, Version 2.0
Jonathan Halterman
Tools for resolving generic types
- Byte Utils (UByte, UInteger, ULong, Unsigned, UNumber, UShort) -
[The Apache Software License, Version 2.0]
https://git.dorkbox.com/dorkbox/Utilities
https://github.com/jOOQ/jOOQ/tree/master/jOOQ/src/main/java/org/jooq/types
Copyright 2017
Data Geekery GmbH (http://www.datageekery.com)
Lukas Eder
Ed Schaller
Jens Nerche
Ivan Sokolov
- Collection Utilities (Array, ArrayMap, BooleanArray, ByteArray, CharArray, FloatArray, IdentityMap, IntArray, IntFloatMap, IntIntMap, IntMap, IntSet, LongArray, LongMap, ObjectFloatMap, ObjectIntMap, ObjectMap, ObjectSet, OrderedMap, OrderedSet) -
[The Apache Software License, Version 2.0]
https://git.dorkbox.com/dorkbox/Utilities
https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/utils
Copyright 2011
LibGDX
Mario Zechner (badlogicgames@gmail.com)
Nathan Sweet (nathan.sweet@gmail.com)
- Predicate -
[The Apache Software License, Version 2.0]
https://git.dorkbox.com/dorkbox/Utilities
https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/utils
Copyright 2011
LibGDX
Mario Zechner (badlogicgames@gmail.com)
Nathan Sweet (nathan.sweet@gmail.com)
xoppa
- Select, QuickSelect -
[The Apache Software License, Version 2.0]
https://git.dorkbox.com/dorkbox/Utilities
https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/utils
Copyright 2011
LibGDX
Mario Zechner (badlogicgames@gmail.com)
Nathan Sweet (nathan.sweet@gmail.com)
Jon Renner
- TimSort, ComparableTimSort -
[The Apache Software License, Version 2.0]
https://git.dorkbox.com/dorkbox/Utilities
https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/utils
Copyright 2008
The Android Open Source Project
- Modified hex conversion utility methods -
[The Apache Software License, Version 2.0]
https://git.dorkbox.com/dorkbox/Utilities
https://netty.io
Copyright 2014
The Netty Project
- JNA - Simplified native library access for Java.
[The Apache Software License, Version 2.0]
https://github.com/twall/jna
Copyright 2019
Timothy Wall
- JNA-Platform - Mappings for a number of commonly used platform functions
[The Apache Software License, Version 2.0]
https://github.com/twall/jna
Copyright 2019
Timothy Wall
- Java Uuid Generator - A set of Java classes for working with UUIDs
[The Apache Software License, Version 2.0]
https://github.com/cowtowncoder/java-uuid-generator
Copyright 2020
Tatu Saloranta (tatu.saloranta@iki.fi)
Contributors. See source release-notes/CREDITS
- Kryo - Fast and efficient binary object graph serialization framework for Java
[BSD 3-Clause License]
https://github.com/EsotericSoftware/kryo
Copyright 2020
Nathan Sweet
Extra license information
- ReflectASM -
[BSD 3-Clause License]
https://github.com/EsotericSoftware/reflectasm
Nathan Sweet
- Objenesis -
[The Apache Software License, Version 2.0]
http://objenesis.org
Objenesis Team and all contributors
- MinLog-SLF4J -
[BSD 3-Clause License]
https://github.com/EsotericSoftware/minlog
Nathan Sweet
- Kryo Serializers - Extra kryo serializers
[The Apache Software License, Version 2.0]
https://github.com/magro/kryo-serializers
Copyright 2019
Martin Grotzke
Rafael Winterhalter
- Netty - An event-driven asynchronous network application framework
[The Apache Software License, Version 2.0]
https://netty.io
Copyright 2020
The Netty Project
Contributors. See source NOTICE
- Bouncy Castle Crypto - Lightweight cryptography API and JCE Extension
[The Apache Software License, Version 2.0]
http://www.bouncycastle.org
Copyright 2020
The Legion of the Bouncy Castle Inc
- Lightweight Java Game Library - Java library that enables cross-platform access to popular native APIs
[BSD 3-Clause License]
https://github.com/LWJGL/lwjgl3
Copyright 2019
Lightweight Java Game Library
- TypeTools - A simple, zero-dependency library for working with types. Supports Java 1.6+ and Android.
[The Apache Software License, Version 2.0]
https://github.com/jhalterman/typetools
Copyright 2020
Jonathan Halterman and friends
- Eclipse Platform - Frameworks and common services to support the use of Eclipse and it's tools (SWT)
[Eclipse Public License (EPL)]
https://projects.eclipse.org/projects/eclipse.platform
Copyright 2019
The Eclipse Foundation, Inc.
- SLF4J - Simple facade or abstraction for various logging frameworks
[MIT License]
http://www.slf4j.org
Copyright 2019
QOS.ch
- XZ for Java - Complete implementation of XZ data compression in pure Java
[Public Domain, per Creative Commons CC0]
https://tukaani.org/xz/java.html
Copyright 2018
Lasse Collin
Igor Pavlov
- Executor - Shell, JVM, and SSH command execution on Linux, MacOS, or Windows for Java 11+
[The Apache Software License, Version 2.0]
https://git.dorkbox.com/dorkbox/Executor
Copyright 2020
Dorkbox LLC
Extra license information
- ZT Process Executor -
[The Apache Software License, Version 2.0]
https://github.com/zeroturnaround/zt-exec
Copyright 2014
ZeroTurnaround LLC
- Apache Commons Exec -
[The Apache Software License, Version 2.0]
https://commons.apache.org/proper/commons-exec/
Copyright 2014
The Apache Software Foundation
- Kotlin -
[The Apache Software License, Version 2.0]
https://github.com/JetBrains/kotlin
Copyright 1980
JetBrains s.r.o. and Kotlin Programming Language contributors
Kotlin Compiler, Test Data+Libraries, and Tools repository contain third-party code, to which different licenses may apply
See: https://github.com/JetBrains/kotlin/blob/master/license/README.md
- kotlinx.coroutines - Library support for Kotlin coroutines with multiplatform support
[The Apache Software License, Version 2.0]
https://github.com/Kotlin/kotlinx.coroutines
Copyright 2020
JetBrains s.r.o.
- kotlin-logging - Lightweight logging framework for Kotlin
[The Apache Software License, Version 2.0]
https://github.com/MicroUtils/kotlin-logging
Copyright 2020
Ohad Shai
- SLF4J - Simple facade or abstraction for various logging frameworks
[MIT License]
http://www.slf4j.org
Copyright 2019
QOS.ch
- SSHJ - SSHv2 library for Java
[The Apache Software License, Version 2.0]
https://github.com/hierynomus/sshj
Copyright 2020
Jeroen van Erp
SSHJ Contributors
Extra license information
- Apache MINA -
[The Apache Software License, Version 2.0]
https://mina.apache.org/sshd-project/
The Apache Software Foundation
- Apache Commons-Net -
[The Apache Software License, Version 2.0]
https://commons.apache.org/proper/commons-net/
The Apache Software Foundation
- JZlib -
[The Apache Software License, Version 2.0]
http://www.jcraft.com/jzlib
Atsuhiko Yamanaka
JCraft, Inc.
- Bouncy Castle Crypto -
[The Apache Software License, Version 2.0]
http://www.bouncycastle.org
The Legion of the Bouncy Castle Inc
- ed25519-java -
[Public Domain, per Creative Commons CC0]
https://github.com/str4d/ed25519-java
https://github.com/str4d
- NetworkUtils - Utilities for managing network configurations, IP/MAC address conversion, and ping (via OS native commands)
[The Apache Software License, Version 2.0]
https://git.dorkbox.com/dorkbox/NetworkUtils
Copyright 2020
Dorkbox LLC
Extra license information
- Netty -
[The Apache Software License, Version 2.0]
https://netty.io/
Copyright 2014
The Netty Project
This product contains a modified portion of Netty Network Utils
- Apache Harmony -
[The Apache Software License, Version 2.0]
http://archive.apache.org/dist/harmony/
Copyright 2010
The Apache Software Foundation
This product contains a modified portion of 'Apache Harmony', an open source Java SE
- Kotlin -
[The Apache Software License, Version 2.0]
https://github.com/JetBrains/kotlin
Copyright 1980
JetBrains s.r.o. and Kotlin Programming Language contributors
Kotlin Compiler, Test Data+Libraries, and Tools repository contain third-party code, to which different licenses may apply
See: https://github.com/JetBrains/kotlin/blob/master/license/README.md
- kotlin-logging - Lightweight logging framework for Kotlin
[The Apache Software License, Version 2.0]
https://github.com/MicroUtils/kotlin-logging
Copyright 2020
Ohad Shai
- SLF4J - Simple facade or abstraction for various logging frameworks
[MIT License]
http://www.slf4j.org
Copyright 2019
QOS.ch
- Executor - Shell, JVM, and SSH command execution on Linux, MacOS, or Windows for Java 11+
[The Apache Software License, Version 2.0]
https://git.dorkbox.com/dorkbox/Executor
Copyright 2020
Dorkbox LLC
Extra license information
- ZT Process Executor -
[The Apache Software License, Version 2.0]
https://github.com/zeroturnaround/zt-exec
Copyright 2014
ZeroTurnaround LLC
- Apache Commons Exec -
[The Apache Software License, Version 2.0]
https://commons.apache.org/proper/commons-exec/
Copyright 2014
The Apache Software Foundation
- Kotlin -
[The Apache Software License, Version 2.0]
https://github.com/JetBrains/kotlin
Copyright 1980
JetBrains s.r.o. and Kotlin Programming Language contributors
Kotlin Compiler, Test Data+Libraries, and Tools repository contain third-party code, to which different licenses may apply
See: https://github.com/JetBrains/kotlin/blob/master/license/README.md
- kotlinx.coroutines - Library support for Kotlin coroutines with multiplatform support
[The Apache Software License, Version 2.0]
https://github.com/Kotlin/kotlinx.coroutines
Copyright 2020
JetBrains s.r.o.
- kotlin-logging - Lightweight logging framework for Kotlin
[The Apache Software License, Version 2.0]
https://github.com/MicroUtils/kotlin-logging
Copyright 2020
Ohad Shai
- SLF4J - Simple facade or abstraction for various logging frameworks
[MIT License]
http://www.slf4j.org
Copyright 2019
QOS.ch
- SSHJ - SSHv2 library for Java
[The Apache Software License, Version 2.0]
https://github.com/hierynomus/sshj
Copyright 2020
Jeroen van Erp
SSHJ Contributors
Extra license information
- Apache MINA -
[The Apache Software License, Version 2.0]
https://mina.apache.org/sshd-project/
The Apache Software Foundation
- Apache Commons-Net -
[The Apache Software License, Version 2.0]
https://commons.apache.org/proper/commons-net/
The Apache Software Foundation
- JZlib -
[The Apache Software License, Version 2.0]
http://www.jcraft.com/jzlib
Atsuhiko Yamanaka
JCraft, Inc.
- Bouncy Castle Crypto -
[The Apache Software License, Version 2.0]
http://www.bouncycastle.org
The Legion of the Bouncy Castle Inc
- ed25519-java -
[Public Domain, per Creative Commons CC0]
https://github.com/str4d/ed25519-java
https://github.com/str4d

View File

@ -15,7 +15,6 @@
*/
import dorkbox.gradle.kotlin
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import java.time.Instant
///////////////////////////////
@ -27,21 +26,20 @@ import java.time.Instant
plugins {
java
id("com.dorkbox.GradleUtils") version "1.8"
id("com.dorkbox.CrossCompile") version "1.1"
id("com.dorkbox.Licensing") version "1.4.2"
id("com.dorkbox.VersionUpdate") version "1.6.1"
id("com.dorkbox.GradlePublish") version "1.2"
id("com.dorkbox.GradleUtils") version "1.10"
id("com.dorkbox.Licensing") version "2.3"
id("com.dorkbox.VersionUpdate") version "2.0"
id("com.dorkbox.GradlePublish") version "1.6"
id("com.dorkbox.GradleModuleInfo") version "1.0"
kotlin("jvm") version "1.3.72"
kotlin("jvm") version "1.4.0"
}
object Extras {
// set for the project
const val description = "Encrypted, high-performance, and event-driven/reactive network stack for Java 11+"
const val group = "com.dorkbox"
const val version = "4.1"
const val version = "5.0-alpha3"
// set as project.ext
const val name = "Network"
@ -49,15 +47,8 @@ object Extras {
const val vendor = "Dorkbox LLC"
const val vendorUrl = "https://dorkbox.com"
const val url = "https://git.dorkbox.com/dorkbox/Network"
val buildDate = Instant.now().toString()
val JAVA_VERSION = JavaVersion.VERSION_11.toString()
const val KOTLIN_API_VERSION = "1.3"
const val KOTLIN_LANG_VERSION = "1.3"
const val bcVersion = "1.60"
const val atomicfuVer = "0.14.3"
const val coroutineVer = "1.3.7"
}
///////////////////////////////
@ -65,9 +56,22 @@ object Extras {
///////////////////////////////
GradleUtils.load("$projectDir/../../gradle.properties", Extras)
GradleUtils.fixIntellijPaths()
GradleUtils.defaultResolutionStrategy()
GradleUtils.compileConfiguration(JavaVersion.VERSION_11) { kotlinOptions ->
// see: https://kotlinlang.org/docs/reference/using-gradle.html
kotlinOptions.apply {
// enable the use of inline classes. see https://kotlinlang.org/docs/reference/inline-classes.html
freeCompilerArgs += "-Xinline-classes"
}
}
// ratelimiter, "other" package
// ping, rest of unit tests
// getConnectionUpgradeType
// java 14 is faster with aeron!
// NOTE: now using aeron instead of netty
// todo: remove BC! use conscrypt instead, or native java? (if possible. we are java 11 now, instead of 1.6)
// using netty IP filters for connections
// /*
// * Copyright 2014 The Netty Project
@ -100,120 +104,35 @@ GradleUtils.fixIntellijPaths()
// }
// NOTE: uses network util from netty!
licensing {
license(License.APACHE_2) {
author(Extras.vendor)
description(Extras.description)
url(Extras.url)
note(Extras.description)
}
license("Dorkbox Utils", License.APACHE_2) {
author(Extras.vendor)
url("https://git.dorkbox.com/dorkbox/Utilities")
}
license("Bennidi Iterator", License.MIT) {
copyright(2012)
author("Benjamin Diedrichsen")
url("https://github.com/bennidi/mbassador")
note("Fast iterators from the MBassador project")
}
license("BouncyCastle", License.MIT) {
copyright(2009)
author("The Legion Of The Bouncy Castle")
url("http://www.bouncycastle.org")
}
license("ObjectPool", License.APACHE_2) {
author("dorkbox, llc")
url("https://git.dorkbox.com/dorkbox/ObjectPool")
}
license("FastThreadLocal", License.BSD_3) {
copyright(2014)
author("Lightweight Java Game Library Project")
author("Riven")
url("https://github.com/LWJGL/lwjgl3/blob/5819c9123222f6ce51f208e022cb907091dd8023/modules/core/src/main/java/org/lwjgl/system/FastThreadLocal.java")
}
license("Javassist", License.BSD_3) {
copyright(1999)
author("Shigeru Chiba")
author("Bill Burke")
author("Jason T. Greene")
url("http://www.csg.is.titech.ac.jp/~chiba/java")
note("Licensed under the MPL/LGPL/Apache triple license")
}
license("Kryo", License.BSD_3) {
copyright(2008)
author("Nathan Sweet")
url("https://github.com/EsotericSoftware/kryo")
}
license("kryo-serializers", License.APACHE_2) {
copyright(2010)
author("Martin Grotzke")
author("Rafael Winterhalter")
url("https://github.com/magro/kryo-serializers")
}
license("KryoNet RMI", License.BSD_3) {
copyright(2008)
author("Nathan Sweet")
url("https://github.com/EsotericSoftware/kryonet")
}
license("LAN HostDiscovery from Apache Commons JCS", License.APACHE_2) {
copyright(2014)
author("The Apache Software Foundation")
url("https://issues.apache.org/jira/browse/JCS-40")
}
license("LZ4 and XXhash", License.APACHE_2) {
copyright(2011)
copyright(2012)
author("Yann Collet")
author("Adrien Grand")
url("https://github.com/jpountz/lz4-java")
}
license("MathUtils, IntArray, IntMap", License.APACHE_2) {
copyright(2013)
author("Mario Zechner <badlogicgames@gmail.com>")
author("Nathan Sweet <nathan.sweet@gmail.com>")
url("http://github.com/libgdx/libgdx/")
}
license("MinLog-SLF4J", License.APACHE_2) {
copyright(2008)
author("dorkbox, llc")
author("Nathan Sweet")
author("Dan Brown")
url("https://git.dorkbox.com/dorkbox/MinLog-SLF4J")
url("https://github.com/EsotericSoftware/minlog")
note("Drop-in replacement for MinLog to log through SLF4j.")
}
license("ReflectASM", License.BSD_3) {
copyright(2008)
author("Nathan Sweet")
url("https://github.com/EsotericSoftware/reflectasm")
}
license("SLF4J", License.MIT) {
copyright(2008)
author("QOS.ch")
url("http://www.slf4j.org")
}
license("TypeTools", License.APACHE_2) {
copyright(2017)
author("Jonathan Halterman")
url("https://github.com/jhalterman/typetools/")
note("Tools for resolving generic types")
extra("KryoNet RMI", License.BSD_3) {
it.copyright(2008)
it.author("Nathan Sweet")
it.url("https://github.com/EsotericSoftware/kryonet")
}
extra("LAN HostDiscovery from Apache Commons JCS", License.APACHE_2) {
it.copyright(2014)
it.author("The Apache Software Foundation")
it.url("https://issues.apache.org/jira/browse/JCS-40")
}
extra("MathUtils, IntArray, IntMap", License.APACHE_2) {
it.copyright(2013)
it.author("Mario Zechner <badlogicgames@gmail.com>")
it.author("Nathan Sweet <nathan.sweet@gmail.com>")
it.url("http://github.com/libgdx/libgdx")
}
extra("Netty (Various network + platform utilities)", License.APACHE_2) {
it.copyright(2014)
it.description("An event-driven asynchronous network application framework")
it.author("The Netty Project")
it.author("Contributors. See source NOTICE")
it.url("https://netty.io")
}
}
}
@ -243,7 +162,7 @@ sourceSets {
}
kotlin {
setSrcDirs(listOf("src"))
setSrcDirs(listOf("test"))
// want to include java files for the source. 'setSrcDirs' resets includes...
include("**/*.java", "**/*.kt")
@ -256,43 +175,6 @@ repositories {
jcenter()
}
///////////////////////////////
////// Task defaults
///////////////////////////////
tasks.withType<JavaCompile> {
doFirst {
println("\tCompiling classes to Java $sourceCompatibility")
}
options.encoding = "UTF-8"
sourceCompatibility = Extras.JAVA_VERSION
targetCompatibility = Extras.JAVA_VERSION
}
tasks.withType<KotlinCompile> {
doFirst {
println("\tCompiling classes to Kotlin, Java ${kotlinOptions.jvmTarget}")
}
sourceCompatibility = Extras.JAVA_VERSION
targetCompatibility = Extras.JAVA_VERSION
// see: https://kotlinlang.org/docs/reference/using-gradle.html
kotlinOptions {
jvmTarget = Extras.JAVA_VERSION
apiVersion = Extras.KOTLIN_API_VERSION
languageVersion = Extras.KOTLIN_LANG_VERSION
// enable the use of inline classes. see https://kotlinlang.org/docs/reference/inline-classes.html
freeCompilerArgs += "-Xinline-classes"
}
}
tasks.withType<Jar> {
duplicatesStrategy = DuplicatesStrategy.FAIL
}
tasks.jar.get().apply {
manifest {
// https://docs.oracle.com/javase/tutorial/deployment/jar/packageman.html
@ -311,69 +193,42 @@ tasks.jar.get().apply {
}
dependencies {
implementation(kotlin("stdlib-jdk8"))
implementation("org.jetbrains.kotlinx:atomicfu:${Extras.atomicfuVer}")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:${Extras.coroutineVer}")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-common:${Extras.coroutineVer}")
implementation("org.jetbrains.kotlinx:atomicfu:0.14.4")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9")
// https://github.com/real-logic/aeron
val aeronVer = "1.28.2"
val aeronVer = "1.29.0"
implementation("io.aeron:aeron-client:$aeronVer")
implementation("io.aeron:aeron-driver:$aeronVer")
implementation("io.netty:netty-buffer:4.1.49.Final")
implementation("com.esotericsoftware:kryo:5.0.0-RC6")
implementation("com.esotericsoftware:kryo:5.0.0-RC8")
implementation("de.javakaffee:kryo-serializers:0.45")
implementation("net.jpountz.lz4:lz4:1.3.0")
// this is NOT the same thing as LMAX disruptor.
// This is just a really fast queue (where LMAX is a fast queue + other things w/ a difficult DSL)
// https://github.com/conversant/disruptor_benchmark
// https://www.youtube.com/watch?v=jVMOgQgYzWU
implementation("com.conversantmedia:disruptor:1.2.15")
implementation("com.conversantmedia:disruptor:1.2.17")
// todo: remove BC! use conscrypt instead, or native java? (if possible. we are java 11 now, instead of 1.6)
// java 14 is faster with aeron!
// implementation("org.bouncycastle:bcprov-jdk15on:${Extras.bcVersion}")
// implementation("org.bouncycastle:bcpg-jdk15on:${Extras.bcVersion}")
// implementation("org.bouncycastle:bcmail-jdk15on:${Extras.bcVersion}")
// implementation("org.bouncycastle:bctls-jdk15on:${Extras.bcVersion}")
implementation("net.jodah:typetools:0.6.2")
implementation("de.javakaffee:kryo-serializers:0.45")
implementation("org.javassist:javassist:3.27.0-GA")
implementation("com.dorkbox:ObjectPool:2.12")
implementation("com.dorkbox:Utilities:1.5.3")
implementation("com.dorkbox:Utilities:1.6")
implementation("com.dorkbox:NetworkUtils:1.1")
// https://github.com/MicroUtils/kotlin-logging
implementation("io.github.microutils:kotlin-logging:1.7.9") // slick kotlin wrapper for slf4j
implementation("io.github.microutils:kotlin-logging:1.8.3") // slick kotlin wrapper for slf4j
implementation("org.slf4j:slf4j-api:1.7.30")
testImplementation("junit:junit:4.13")
testImplementation("ch.qos.logback:logback-classic:1.2.3")
}
configurations.all {
resolutionStrategy {
// fail eagerly on version conflict (includes transitive dependencies)
// e.g. multiple different versions of the same dependency (group and name are equal)
failOnVersionConflict()
// if there is a version we specified, USE THAT VERSION (over transitive versions)
preferProjectModules()
// cache dynamic versions for 10 minutes
cacheDynamicVersionsFor(10 * 60, "seconds")
// don't cache changing modules at all
cacheChangingModulesFor(0, "seconds")
}
}
publishToSonatype {
groupId = Extras.group
artifactId = Extras.id

View File

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.4.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5.1-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@ -1,506 +0,0 @@
/*
* Copyright 2010 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.network.connection.registration.remote;
import java.net.InetSocketAddress;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.spec.ECParameterSpec;
import dorkbox.network.connection.EndPoint;
import dorkbox.network.connection.registration.MetaChannel;
import dorkbox.network.connection.registration.Registration;
import dorkbox.network.connection.registration.UpgradeType;
import dorkbox.network.pipeline.tcp.KryoDecoderTcpCompression;
import dorkbox.network.pipeline.tcp.KryoDecoderTcpCrypto;
import dorkbox.network.pipeline.tcp.KryoDecoderTcpNone;
import dorkbox.util.crypto.CryptoECC;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelPromise;
import io.netty.channel.EventLoopGroup;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.AttributeKey;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
import io.netty.util.concurrent.GenericFutureListener;
public abstract
class RegistrationRemoteHandler<T extends RegistrationWrapper> extends RegistrationHandler<T> {
static final AttributeKey<LinkedList> MESSAGES = AttributeKey.valueOf(RegistrationRemoteHandler.class, "messages");
static final String DELETE_IP = "eleteIP"; // purposefully missing the "D", since that is a system parameter, which starts with "-D"
static final ECParameterSpec eccSpec = ECNamedCurveTable.getParameterSpec(CryptoECC.curve25519);
private static final String UDP_DECODE = "ud1";
private static final String UDP_DECODE_NONE = "ud2";
private static final String UDP_DECODE_COMPRESS = "ud3";
private static final String UDP_DECODE_CRYPTO = "ud4";
private static final String TCP_DECODE = "td1";
private static final String TCP_DECODE_NONE = "td2";
private static final String TCP_DECODE_COMPRESS = "td3";
private static final String TCP_DECODE_CRYPTO = "td4";
private static final String IDLE_HANDLER = "idle";
private static final String IDLE_HANDLER_FULL = "idleFull";
private static final String UDP_ENCODE = "ue";
private static final String UDP_ENCODE_NONE = "ue2";
private static final String UDP_ENCODE_COMPRESS = "ue3";
private static final String UDP_ENCODE_CRYPTO = "ue4";
private static final String TCP_ENCODE = "te1";
private static final String TCP_ENCODE_NONE = "te2";
private static final String TCP_ENCODE_COMPRESS = "te3";
private static final String TCP_ENCODE_CRYPTO = "te4";
RegistrationRemoteHandler(final String name, final T registrationWrapper, final EventLoopGroup workerEventLoop) {
super(name, workerEventLoop);
}
/**
* STEP 1: Channel is first created
*/
@Override
protected
void initChannel(final Channel channel) {
ChannelPipeline pipeline = channel.pipeline();
Class<? extends Channel> channelClass = channel.getClass();
// because of the way TCP works, we have to have special readers/writers. For UDP, all data must be in a single packet.
// boolean isTcpChannel = ConnectionImpl.isTcpChannel(channelClass);
// boolean isUdpChannel = !isTcpChannel && ConnectionImpl.isUdpChannel(channelClass);
//
// if (isTcpChannel) {
// ///////////////////////
// // DECODE (or upstream)
// ///////////////////////
// pipeline.addFirst(TCP_DECODE, new KryoDecoderTcp(registrationWrapper.getSerialization())); // cannot be shared because of possible fragmentation.
// }
// else if (isUdpChannel) {
// // can be shared because there cannot be fragmentation for our UDP packets. If there is, we throw an error and continue...
// // pipeline.addFirst(UDP_DECODE, this.registrationWrapper.kryoUdpDecoder);
// }
//
// // this makes the proper event get raised in the registrationHandler to kill NEW idle connections. Once "connected" they last a lot longer.
// // we ALWAYS have this initial IDLE handler, so we don't have to worry about a SLOW-LORIS ATTACK against the server.
// // in Seconds -- not shared, because it is per-connection
// pipeline.addFirst(IDLE_HANDLER, new IdleStateHandler(2, 0, 0));
//
// if (isTcpChannel) {
// /////////////////////////
// // ENCODE (or downstream)
// /////////////////////////
// // pipeline.addFirst(TCP_ENCODE, this.registrationWrapper.kryoTcpEncoder); // this is shared
// }
// else if (isUdpChannel) {
// // pipeline.addFirst(UDP_ENCODE, this.registrationWrapper.kryoUdpEncoder);
// }
}
/**
* STEP 2: Channel is now active. (if debug is enabled...) Debug output, so we can tell what direction the connection is in the log
*/
@Override
public
void channelActive(ChannelHandlerContext context) throws Exception {
// add the channel so we can access it later.
// do NOT want to add UDP channels, since they are tracked differently.
// if (this.logger.isDebugEnabled()) {
// Channel channel = context.channel();
// Class<? extends Channel> channelClass = channel.getClass();
// boolean isUdp = ConnectionImpl.isUdpChannel(channelClass);
//
// StringBuilder stringBuilder = new StringBuilder(96);
//
// stringBuilder.append("Connected to remote ");
// if (ConnectionImpl.isTcpChannel(channelClass)) {
// stringBuilder.append("TCP");
// }
// else if (isUdp) {
// stringBuilder.append("UDP");
// }
// else if (ConnectionImpl.isLocalChannel(channelClass)) {
// stringBuilder.append("LOCAL");
// }
// else {
// stringBuilder.append("UNKNOWN");
// }
//
// stringBuilder.append(" connection [");
// EndPoint.Companion.getHostDetails(stringBuilder, channel.localAddress());
//
// stringBuilder.append(getConnectionDirection());
// EndPoint.Companion.getHostDetails(stringBuilder, channel.remoteAddress());
// stringBuilder.append("]");
//
// this.logger.debug(stringBuilder.toString());
// }
}
/**
* Invoked when a {@link Channel} has been idle for a while.
*/
@Override
public
void userEventTriggered(ChannelHandlerContext context, Object event) throws Exception {
if (event instanceof IdleStateEvent) {
if (((IdleStateEvent) event).state() == IdleState.ALL_IDLE) {
// this IS BAD, because we specify an idle handler to prevent slow-loris type attacks on the webserver
Channel channel = context.channel();
channel.close();
return;
}
}
super.userEventTriggered(context, event);
}
@Override
public
void exceptionCaught(ChannelHandlerContext context, Throwable cause) throws Exception {
Channel channel = context.channel();
this.logger.error("Unexpected exception while trying to send/receive data on remote network channel. ({})" +
System.getProperty("line.separator"), channel.remoteAddress(), cause);
if (channel.isOpen()) {
channel.close();
}
}
/**
* @return the direction that traffic is going to this handler (" <== " or " ==> ")
*/
protected abstract
String getConnectionDirection();
/**
* upgrades a channel ONE channel at a time
*/
final
void upgradeDecoders(final int upgradeType, final Channel channel, final MetaChannel metaChannel) {
ChannelPipeline pipeline = channel.pipeline();
try {
if (metaChannel.tcpChannel == channel) {
// cannot be shared because of possible fragmentation.
switch (upgradeType) {
case (UpgradeType.NONE) :
pipeline.replace(TCP_DECODE, TCP_DECODE_NONE, new KryoDecoderTcpNone(registrationWrapper.getSerialization()));
break;
case (UpgradeType.COMPRESS) :
pipeline.replace(TCP_DECODE, TCP_DECODE_COMPRESS, new KryoDecoderTcpCompression(registrationWrapper.getSerialization()));
break;
case (UpgradeType.ENCRYPT) :
pipeline.replace(TCP_DECODE, TCP_DECODE_CRYPTO, new KryoDecoderTcpCrypto(registrationWrapper.getSerialization()));
break;
default:
throw new IllegalArgumentException("Unable to upgrade TCP connection pipeline for type: " + upgradeType);
}
}
else if (metaChannel.udpChannel == channel) {
// shared decoder
switch (upgradeType) {
case (UpgradeType.NONE) :
// pipeline.replace(UDP_DECODE, UDP_DECODE_NONE, this.registrationWrapper.kryoUdpDecoderNone);
break;
case (UpgradeType.COMPRESS) :
// pipeline.replace(UDP_DECODE, UDP_DECODE_COMPRESS, this.registrationWrapper.kryoUdpDecoderCompression);
break;
case (UpgradeType.ENCRYPT) :
// pipeline.replace(UDP_DECODE, UDP_DECODE_CRYPTO, this.registrationWrapper.kryoUdpDecoderCrypto);
break;
default:
throw new IllegalArgumentException("Unable to upgrade UDP connection pipeline for type: " + upgradeType);
}
}
} catch (Exception e) {
logger.error("Error during connection pipeline upgrade", e);
}
}
/**
* upgrades a channel ONE channel at a time
*/
final
void upgradeEncoders(final int upgradeType, final Channel channel, final MetaChannel metaChannel) {
ChannelPipeline pipeline = channel.pipeline();
if (metaChannel.tcpChannel == channel) {
// shared encoder
switch (upgradeType) {
case (UpgradeType.NONE) :
// pipeline.replace(TCP_ENCODE, TCP_ENCODE_NONE, this.registrationWrapper.kryoTcpEncoderNone);
break;
case (UpgradeType.COMPRESS) :
// pipeline.replace(TCP_ENCODE, TCP_ENCODE_COMPRESS, this.registrationWrapper.kryoTcpEncoderCompression);
break;
case (UpgradeType.ENCRYPT) :
// pipeline.replace(TCP_ENCODE, TCP_ENCODE_CRYPTO, this.registrationWrapper.kryoTcpEncoderCrypto);
break;
default:
throw new IllegalArgumentException("Unable to upgrade TCP connection pipeline for type: " + upgradeType);
}
}
else if (metaChannel.udpChannel == channel) {
// shared encoder
switch (upgradeType) {
case (UpgradeType.NONE) :
// pipeline.replace(UDP_ENCODE, UDP_ENCODE_NONE, this.registrationWrapper.kryoUdpEncoderNone);
break;
case (UpgradeType.COMPRESS) :
// pipeline.replace(UDP_ENCODE, UDP_ENCODE_COMPRESS, this.registrationWrapper.kryoUdpEncoderCompression);
break;
case (UpgradeType.ENCRYPT) :
// pipeline.replace(UDP_ENCODE, UDP_ENCODE_CRYPTO, this.registrationWrapper.kryoUdpEncoderCrypto);
break;
default:
throw new IllegalArgumentException("Unable to upgrade UDP connection pipeline for type: " + upgradeType);
}
}
}
final
void logChannelUpgrade(final int upgradeType, final Channel channel, final MetaChannel metaChannel) {
if (this.logger.isInfoEnabled()) {
final String channelType;
if (metaChannel.tcpChannel == channel) {
// cannot be shared because of possible fragmentation.
switch (upgradeType) {
case (UpgradeType.NONE) :
channelType = "TCP simple";
break;
case (UpgradeType.COMPRESS) :
channelType = "TCP compression";
break;
case (UpgradeType.ENCRYPT) :
channelType = "TCP encrypted";
break;
default:
this.logger.error("Unable to upgrade TCP connection pipeline for type: " + upgradeType);
return;
}
}
else if (metaChannel.udpChannel == channel) {
// shared decoder
switch (upgradeType) {
case (UpgradeType.NONE) :
channelType = "UDP simple";
break;
case (UpgradeType.COMPRESS) :
channelType = "UDP compression";
break;
case (UpgradeType.ENCRYPT) :
channelType = "UDP encrypted";
break;
default:
this.logger.error("Unable to upgrade UDP connection pipeline for type: " + upgradeType);
return;
}
}
else {
this.logger.error("Unable to upgrade unknown connection pipeline for type: " + upgradeType);
return;
}
StringBuilder stringBuilder = new StringBuilder(96);
stringBuilder.append(channelType);
stringBuilder.append(" connection [");
EndPoint.Companion.getHostDetails(stringBuilder, channel.localAddress());
stringBuilder.append(getConnectionDirection());
EndPoint.Companion.getHostDetails(stringBuilder, channel.remoteAddress());
stringBuilder.append("]");
this.logger.info(stringBuilder.toString());
}
}
final
void cleanupPipeline(final Channel channel, final MetaChannel metaChannel, final Runnable preConnectRunnable, final Runnable postConnectRunnable) {
final int idleTimeout = this.registrationWrapper.getIdleTimeout();
try {
// channel should NEVER == null! (we will always have TCP or UDP!)
// we also ONLY want to add this to a single cleanup, NOT BOTH, because this must only run once!!
final ChannelPromise channelPromise = channel.newPromise();
channelPromise.addListener(new FutureListener<Void>() {
@Override
public
void operationComplete(final Future<Void> future) throws Exception {
// this runs on the channel's event loop
logger.trace("Connection connected");
if (preConnectRunnable != null) {
preConnectRunnable.run();
}
// safe cast, because it's always this way...
registrationWrapper.connectionConnected0(metaChannel.connection);
if (postConnectRunnable != null) {
postConnectRunnable.run();
}
}
});
if (metaChannel.tcpChannel != null) {
cleanupPipeline0(idleTimeout, metaChannel.tcpChannel, channelPromise);
}
if (metaChannel.udpChannel != null) {
cleanupPipeline0(idleTimeout, metaChannel.udpChannel, channelPromise);
}
} catch (Exception e) {
logger.error("Error during pipeline replace", e);
}
}
private
void cleanupPipeline0(final int idleTimeout, final Channel channel, final ChannelPromise channelPromise) {
final ChannelPipeline pipeline = channel.pipeline();
if (idleTimeout > 0) {
pipeline.replace(IDLE_HANDLER, IDLE_HANDLER_FULL, new IdleStateHandler(0, 0, idleTimeout, TimeUnit.MILLISECONDS));
}
else {
pipeline.remove(IDLE_HANDLER);
}
// we also DEREGISTER from the HANDSHAKE event-loop and run on the worker event-loop!
ChannelFuture future = channel.deregister();
future.addListener(new GenericFutureListener<Future<? super Void>>() {
@Override
public
void operationComplete(final Future<? super Void> f) throws Exception {
if (f.isSuccess()) {
workerEventLoop.register(channel);
// TCP and UDP register on DIFFERENT event loops. This channel promise ONLY runs on the same worker as the
// channel that called `cleanupPipeline`
if (channelPromise.channel() == channel) {
workerEventLoop.register(channelPromise);
}
}
}
});
}
// whoa! Didn't send valid public key info!
boolean invalidPublicKey(final Registration message, final String type) {
if (message.publicKey == null) {
logger.error("Null ECC public key during " + type + " handshake. This shouldn't happen!");
return true;
}
return false;
}
// want to validate the public key used! This is similar to how SSH works, in that once we use a public key, we want to validate
// against that ip-address::key pair, so we can better protect against MITM/spoof attacks.
boolean invalidRemoteAddress(final MetaChannel metaChannel,
final Registration message,
final String type,
final InetSocketAddress remoteAddress) {
boolean valid = registrationWrapper.validateRemoteAddress(metaChannel, remoteAddress, message.publicKey);
if (!valid) {
//whoa! abort since something messed up! (log happens inside of validate method)
String hostAddress = remoteAddress.getAddress()
.getHostAddress();
logger.error("Invalid ECC public key for server IP {} during {} handshake. WARNING. The server has changed!", hostAddress, type);
logger.error("Fix by adding the argument -D{} {} when starting the client.", DELETE_IP, hostAddress);
return true;
}
return false;
}
/**
* have to have a way for us to store messages in case the remote end calls "onConnect()" and sends messages before we are ready.
*/
void prepChannelForOutOfOrderMessages(final Channel channel) {
// this could POSSIBLY be screwed up when getting created, so we make sure to only create the list ONE time
channel.attr(MESSAGES)
.setIfAbsent(new LinkedList<Object>());
}
/**
* have to have a way for us to store messages in case the remote end calls "onConnect()" and sends messages before we are ready.
*/
void saveOutOfOrderMessage(final Channel channel, final Object message) {
// this will ALWAYS have already been created, or IF NULL -- then something really screwed up!
LinkedList list = channel.attr(MESSAGES)
.get();
if (list == null) {
logger.error("Completely screwed up message order from server!");
shutdown(channel, 0);
return;
}
//noinspection unchecked
list.add(message);
}
/**
* Gets all of the messages for this channel that were "out of order" (onMessage called before onConnect finished).
*/
List<Object> getOutOfOrderMessagesAndReset(final Channel channel) {
LinkedList messages = channel.attr(MESSAGES)
.getAndSet(null);
return messages;
}
}

View File

@ -1,317 +0,0 @@
/*
* Copyright 2010 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.network.connection.registration.remote;
import java.math.BigInteger;
import java.net.InetSocketAddress;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import javax.crypto.spec.SecretKeySpec;
import org.bouncycastle.crypto.BasicAgreement;
import org.bouncycastle.crypto.agreement.ECDHCBasicAgreement;
import org.bouncycastle.crypto.digests.SHA384Digest;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import com.esotericsoftware.kryo.KryoException;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import dorkbox.network.connection.ConnectionImpl;
import dorkbox.network.connection.RegistrationWrapperClient;
import dorkbox.network.connection.registration.MetaChannel;
import dorkbox.network.connection.registration.Registration;
import dorkbox.util.crypto.CryptoECC;
import dorkbox.util.exceptions.SecurityException;
import dorkbox.util.serialization.EccPublicKeySerializer;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.EventLoopGroup;
public
class RegistrationRemoteHandlerClient extends RegistrationRemoteHandler<RegistrationWrapperClient> {
RegistrationRemoteHandlerClient(final String name, final RegistrationWrapperClient registrationWrapper, final EventLoopGroup workerEventLoop) {
super(name, registrationWrapper, workerEventLoop);
// check to see if we need to delete an IP address as commanded from the user prompt
String ipAsString = System.getProperty(DELETE_IP);
if (ipAsString != null) {
System.setProperty(DELETE_IP, "");
byte[] address = null;
try {
String[] split = ipAsString.split("\\.");
if (split.length == 4) {
address = new byte[4];
for (int i = 0; i < split.length; i++) {
int asInt = Integer.parseInt(split[i]);
if (asInt >= 0 && asInt <= 255) {
//noinspection NumericCastThatLosesPrecision
address[i] = (byte) Integer.parseInt(split[i]);
}
else {
address = null;
break;
}
}
}
} catch (Exception e) {
address = null;
}
if (address != null) {
try {
registrationWrapper.removeRegisteredServerKey(address);
} catch (SecurityException e) {
this.logger.error(e.getMessage(), e);
}
}
}
// end command
}
/**
* @return the direction that traffic is going to this handler (" <== " or " ==> ")
*/
@Override
protected
String getConnectionDirection() {
return " ==> ";
}
@SuppressWarnings("Duplicates")
void readClient(final ChannelHandlerContext context, final Channel channel, final Registration registration, final String type, final MetaChannel metaChannel) {
final InetSocketAddress remoteAddress = (InetSocketAddress) channel.remoteAddress();
// IN: session ID + public key + ecc parameters (which are a nonce. the SERVER defines what these are)
// OUT: remote ECDH shared payload
if (metaChannel.secretKey == null && registration.publicKey != null) {
// whoa! Didn't send valid public key info!
if (invalidPublicKey(registration, type)) {
shutdown(channel, registration.sessionID);
return;
}
// want to validate the public key used! This is similar to how SSH works, in that once we use a public key, we want to validate
// against that ip-address::key pair, so we can better protect against MITM/spoof attacks.
if (invalidRemoteAddress(metaChannel, registration, type, remoteAddress)) {
// whoa! abort since something messed up! (log and recording if key changed happens inside of validate method)
shutdown(channel, registration.sessionID);
return;
}
// save off remote public key. This is ALWAYS the same, where the ECDH changes every time...
metaChannel.publicKey = registration.publicKey;
// It is OK that we generate a new ECC keypair for ECDHE every time that we connect from the client.
// The server rotates keys every XXXX seconds, since this step is expensive (and the server is the 'trusted' endpoint).
metaChannel.ecdhKey = CryptoECC.generateKeyPair(eccSpec, registrationWrapper.getSecureRandom());
Registration outboundRegister = new Registration(metaChannel.sessionId);
Output output = new Output(1024);
EccPublicKeySerializer.write(output, (ECPublicKeyParameters) metaChannel.ecdhKey.getPublic());
outboundRegister.payload = output.toBytes();
channel.writeAndFlush(outboundRegister);
return;
}
// IN: remote ECDH shared payload
// OUT: hasMore=true if we have more registrations to do, false otherwise
if (metaChannel.secretKey == null) {
/*
* Diffie-Hellman-Merkle key exchange for the AES key
* http://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange
*/
byte[] ecdhPubKeyBytes = Arrays.copyOfRange(registration.payload, 0, registration.payload.length);
ECPublicKeyParameters ecdhPubKey;
try {
ecdhPubKey = EccPublicKeySerializer.read(new Input(ecdhPubKeyBytes));
} catch (KryoException e) {
logger.error("Invalid decode of ECDH public key. Aborting.");
shutdown(channel, registration.sessionID);
return;
}
BasicAgreement agreement = new ECDHCBasicAgreement();
agreement.init(metaChannel.ecdhKey.getPrivate());
BigInteger shared = agreement.calculateAgreement(ecdhPubKey);
// now we setup our AES key based on our shared secret! (from ECDH)
// the shared secret is different each time a connection is made
byte[] keySeed = shared.toByteArray();
SHA384Digest sha384 = new SHA384Digest();
byte[] digest = new byte[sha384.getDigestSize()];
sha384.update(keySeed, 0, keySeed.length);
sha384.doFinal(digest, 0);
byte[] key = org.bouncycastle.util.Arrays.copyOfRange(digest, 0, 32); // 256bit keysize (32 bytes)
metaChannel.secretKey = new SecretKeySpec(key, "AES");
Registration outboundRegister = new Registration(metaChannel.sessionId);
// do we have any more registrations?
outboundRegister.hasMore = registrationWrapper.hasMoreRegistrations();
if (outboundRegister.hasMore) {
metaChannel.totalProtocols.incrementAndGet();
}
channel.writeAndFlush(outboundRegister);
// wait for ack from the server before registering the next protocol
return;
}
// IN: upgrade>0 if we must upgrade this connection
// can this pipeline can now be upgraded
int upgradeType = registration.upgradeType;
if (upgradeType > 0) {
// upgrade the connection to an none/compression/encrypted connection
upgradeEncoders(upgradeType, channel, metaChannel);
upgradeDecoders(upgradeType, channel, metaChannel);
logChannelUpgrade(upgradeType, channel, metaChannel);
}
// IN: hasMore=true if we have more registrations to do, false otherwise
if (registrationWrapper.hasMoreRegistrations()) {
logger.trace("Starting another protocol registration");
metaChannel.totalProtocols.incrementAndGet();
registrationWrapper.startNextProtocolRegistration();
return;
}
//
//
// we only get this when we are 100% done with the registration and upgrade of all connection types.
//
// THIS is the last channel registered!
//
// we don't verify anything on the CLIENT. We only verify on the server.
// we don't support registering NEW classes after the client starts.
// this will perform channel WRITE on whatever channel is the last channel registered!
if (!registration.upgraded) {
// this only get's called once. Ever other time the server talks to the client now, "upgraded" will be true.
// this can ONLY be created when all protocols are registered!
metaChannel.connection = this.registrationWrapper.connection0(metaChannel, remoteAddress);
if (metaChannel.tcpChannel != null) {
// metaChannel.tcpChannel.pipeline().addLast(CONNECTION_HANDLER, metaChannel.connection);
}
if (metaChannel.udpChannel != null) {
// metaChannel.udpChannel.pipeline().addLast(CONNECTION_HANDLER, metaChannel.connection);
}
// this tells the server we are now "upgraded" and can continue
boolean hasErrors = !registrationWrapper.initClassRegistration(channel, registration);
if (hasErrors) {
// abort if something messed up!
shutdown(channel, registration.sessionID);
}
// the server will ping back when it is ready to connect
return;
}
// NOTE: The server will ALWAYS call "onConnect" before we do!
// it does this via sending the client the "upgraded" signal JUST before it calls "onConnect"
// It will upgrade the different connections INDIVIDUALLY, and whichever one arrives first will start the process
// and the "late" message will be ignored
if (!metaChannel.canUpgradePipeline.compareAndSet(true, false)) {
return;
}
// remove ourselves from handling any more messages, because we are done.
// since only the FIRST registration gets here, setup other ones as well (since they are no longer needed)
if (metaChannel.tcpChannel != null) {
// the "other" channel is the TCP channel that we have to cleanup
metaChannel.tcpChannel.pipeline().remove(RegistrationRemoteHandlerClientTCP.class);
}
if (metaChannel.udpChannel != null) {
// the "other" channel is the UDP channel that we have to cleanup
metaChannel.udpChannel.pipeline().remove(RegistrationRemoteHandlerClientUDP.class);
}
// remove the ConnectionWrapper (that was used to upgrade the connection) and cleanup the pipeline
// always wait until AFTER the server calls "onConnect", then we do this
Runnable postConnectRunnable = new Runnable() {
@Override
public
void run() {
// this method runs after all of the channels have be correctly updated
// get all of the out of order messages that we missed
List<Object> messages = new LinkedList<Object>();
if (metaChannel.tcpChannel != null) {
List<Object> list = getOutOfOrderMessagesAndReset(metaChannel.tcpChannel);
if (list != null) {
logger.trace("Getting deferred TCP messages: {}", list.size());
messages.addAll(list);
}
}
if (metaChannel.udpChannel != null) {
List<Object> list = getOutOfOrderMessagesAndReset(metaChannel.udpChannel);
if (list != null) {
logger.trace("Getting deferred UDP messages: {}", list.size());
messages.addAll(list);
}
}
// now call 'onMessage' in the connection object with our messages!
try {
ConnectionImpl connection = metaChannel.connection;
for (Object message : messages) {
logger.trace(" deferred onMessage({}, {})", connection.id(), message);
try {
// connection.channelRead(null, message);
} catch (Exception e) {
logger.error("Error running deferred messages!", e);
}
}
} catch (Exception e) {
logger.error("Error initialising deferred messages!", e);
}
}
};
cleanupPipeline(channel, metaChannel, null, postConnectRunnable);
}
}

View File

@ -1,91 +0,0 @@
/*
* Copyright 2010 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.network.connection.registration.remote;
import dorkbox.network.connection.RegistrationWrapperClient;
import dorkbox.network.connection.registration.MetaChannel;
import dorkbox.network.connection.registration.Registration;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.EventLoopGroup;
public
class RegistrationRemoteHandlerClientTCP extends RegistrationRemoteHandlerClient {
public
RegistrationRemoteHandlerClientTCP(final String name,
final RegistrationWrapperClient registrationWrapper,
final EventLoopGroup workerEventLoop) {
super(name, registrationWrapper, workerEventLoop);
}
/**
* STEP 2: Channel is now active. Start the registration process
*/
@Override
public
void channelActive(final ChannelHandlerContext context) throws Exception {
super.channelActive(context);
logger.trace("Starting a new TCP Connection. Sending request to server");
Registration registration = new Registration(0);
registration.publicKey = this.registrationWrapper.getPublicKey();
// client start the handshake with a registration packet
context.channel().writeAndFlush(registration);
}
@SuppressWarnings({"AutoUnboxing", "AutoBoxing", "Duplicates"})
@Override
public
void channelRead(final ChannelHandlerContext context, final Object message) throws Exception {
Channel channel = context.channel();
if (message instanceof Registration) {
Registration registration = (Registration) message;
MetaChannel metaChannel;
int sessionId = registration.sessionID;
if (sessionId == 0) {
logger.error("Invalid TCP channel session ID 0!");
shutdown(channel, 0);
return;
}
else {
metaChannel = registrationWrapper.getSession(sessionId);
// TCP channel registration is ALWAYS first, so this is the correct way to do this.
if (metaChannel == null) {
metaChannel = registrationWrapper.createSession(sessionId);
metaChannel.tcpChannel = channel;
logger.debug("New TCP connection. Saving meta-channel id: {}", metaChannel.sessionId);
}
// have to add a way for us to store messages in case the remote end calls "onConnect()" and sends messages before we are ready.
prepChannelForOutOfOrderMessages(channel);
}
logger.trace("TCP read");
readClient(context, channel, registration, "TCP client", metaChannel);
}
else {
logger.trace("Out of order TCP message from server!");
saveOutOfOrderMessage(channel, message);
}
}
}

View File

@ -1,113 +0,0 @@
/*
* Copyright 2010 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.network.connection.registration.remote;
import java.io.IOException;
import java.net.InetSocketAddress;
import dorkbox.network.connection.RegistrationWrapperClient;
import dorkbox.network.connection.registration.MetaChannel;
import dorkbox.network.connection.registration.Registration;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.EventLoopGroup;
@SuppressWarnings("Duplicates")
public
class RegistrationRemoteHandlerClientUDP extends RegistrationRemoteHandlerClient {
public
RegistrationRemoteHandlerClientUDP(final String name,
final RegistrationWrapperClient registrationWrapper,
final EventLoopGroup workerEventLoop) {
super(name, registrationWrapper, workerEventLoop);
}
/**
* STEP 2: Channel is now active. Start the registration process
*/
@Override
public
void channelActive(final ChannelHandlerContext context) throws Exception {
super.channelActive(context);
Channel channel = context.channel();
// have to add a way for us to store messages in case the remote end calls "onConnect()" and sends messages before we are ready.
// note: UDP channels are also unique (just like TCP channels) because of the SessionManager we added
prepChannelForOutOfOrderMessages(channel);
InetSocketAddress udpRemoteAddress = (InetSocketAddress) channel.remoteAddress();
if (udpRemoteAddress != null) {
Registration outboundRegister = new Registration(0);
outboundRegister.publicKey = this.registrationWrapper.getPublicKey();
// check to see if we have an already existing TCP connection to the server, so we can reuse the MetaChannel.
// UDP will always be registered after TCP
MetaChannel firstSession = this.registrationWrapper.getFirstSession();
if (firstSession != null) {
outboundRegister.sessionID = firstSession.sessionId;
outboundRegister.hasMore = registrationWrapper.hasMoreRegistrations();
}
// no size info, since this is UDP, it is not segmented
channel.writeAndFlush(outboundRegister);
}
else {
throw new IOException("UDP cannot connect to remote server! No remote address specified!");
}
}
@Override
public
void channelRead(final ChannelHandlerContext context, Object message) throws Exception {
// REGISTRATION is the ONLY thing NOT encrypted. ALSO, this handler is REMOVED once registration is complete
Channel channel = context.channel();
if (message instanceof Registration) {
Registration registration = (Registration) message;
MetaChannel metaChannel;
int sessionId = registration.sessionID;
if (sessionId == 0) {
logger.error("Invalid UDP channel session ID 0!");
return;
}
else {
metaChannel = registrationWrapper.getSession(sessionId);
if (metaChannel == null) {
metaChannel = registrationWrapper.createSession(sessionId);
logger.debug("New UDP connection. Saving meta-channel id: {}", metaChannel.sessionId);
}
else if (metaChannel.udpChannel == null) {
logger.debug("Using TCP connection meta-channel for UDP connection");
}
// in the event that we start with a TCP channel first, we still have to set the UDP channel
metaChannel.udpChannel = channel;
}
readClient(context, channel, registration, "UDP client", metaChannel);
}
else {
logger.trace("Out of order UDP message from server!");
saveOutOfOrderMessage(channel, message);
}
}
}

View File

@ -1,314 +0,0 @@
/*
* Copyright 2010 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.network.connection.registration.remote;
import java.math.BigInteger;
import java.net.InetSocketAddress;
import java.security.SecureRandom;
import java.util.concurrent.TimeUnit;
import javax.crypto.spec.SecretKeySpec;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.BasicAgreement;
import org.bouncycastle.crypto.agreement.ECDHCBasicAgreement;
import org.bouncycastle.crypto.digests.SHA384Digest;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.util.Arrays;
import com.esotericsoftware.kryo.KryoException;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import dorkbox.network.connection.EndPoint;
import dorkbox.network.connection.RegistrationWrapper.STATE;
import dorkbox.network.connection.RegistrationWrapperServer;
import dorkbox.network.connection.registration.MetaChannel;
import dorkbox.network.connection.registration.Registration;
import dorkbox.util.crypto.CryptoECC;
import dorkbox.util.serialization.EccPublicKeySerializer;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.EventLoopGroup;
public
class RegistrationRemoteHandlerServer extends RegistrationRemoteHandler<RegistrationWrapperServer> {
private static final long ECDH_TIMEOUT = TimeUnit.MINUTES.toNanos(10L); // 10 minutes in nanoseconds
private static final ECParameterSpec eccSpec = ECNamedCurveTable.getParameterSpec(CryptoECC.curve25519);
private final Object ecdhKeyLock = new Object();
private AsymmetricCipherKeyPair ecdhKeyPair;
private volatile long ecdhTimeout = System.nanoTime();
RegistrationRemoteHandlerServer(final String name, final RegistrationWrapperServer registrationWrapper, final EventLoopGroup workerEventLoop) {
super(name, registrationWrapper, workerEventLoop);
}
/**
* STEP 1: Channel is first created
*/
@Override
protected
void initChannel(final Channel channel) {
// check to see if this connection is permitted.
final InetSocketAddress remoteAddress = (InetSocketAddress) channel.remoteAddress();
if (!registrationWrapper.acceptRemoteConnection(remoteAddress)) {
StringBuilder stringBuilder = new StringBuilder();
EndPoint.Companion.getHostDetails(stringBuilder, remoteAddress);
logger.error("Remote connection [{}] is not permitted! Aborting connection process.", stringBuilder.toString());
shutdown(channel, 0);
return;
}
super.initChannel(channel);
}
/**
* @return the direction that traffic is going to this handler (" <== " or " ==> ")
*/
@Override
protected
String getConnectionDirection() {
return " <== ";
}
/**
* Rotates the ECDH key every 10 minutes, as this is a VERY expensive calculation to keep on doing for every connection.
*/
private
AsymmetricCipherKeyPair getEchdKeyOnRotate(final SecureRandom secureRandom) {
if (this.ecdhKeyPair == null || System.nanoTime() - this.ecdhTimeout > ECDH_TIMEOUT) {
synchronized (this.ecdhKeyLock) {
this.ecdhTimeout = System.nanoTime();
this.ecdhKeyPair = CryptoECC.generateKeyPair(eccSpec, secureRandom);
}
}
return this.ecdhKeyPair;
}
/*
* UDP has a VERY limited size, so we have to break up registration steps into the following
* 1) session ID == 0 -> exchange session ID and public keys (session ID != 0 now)
* 2) session ID != 0 -> establish ECDH shared secret as AES key/iv
*/
@SuppressWarnings("Duplicates")
void readServer(final ChannelHandlerContext context, final Channel channel, final Registration registration, final String type, final MetaChannel metaChannel) {
final InetSocketAddress remoteAddress = (InetSocketAddress) channel.remoteAddress();
// IN: session ID == 0 (start of new connection)
// OUT: session ID + public key + ecc parameters (which are a nonce. the SERVER defines what these are)
if (registration.sessionID == 0) {
// whoa! Didn't send valid public key info!
if (invalidPublicKey(registration, type)) {
shutdown(channel, registration.sessionID);
return;
}
// want to validate the public key used! This is similar to how SSH works, in that once we use a public key, we want to validate
// against that ip-address::key pair, so we can better protect against MITM/spoof attacks.
if (invalidRemoteAddress(metaChannel, registration, type, remoteAddress)) {
// whoa! abort since something messed up! (log and recording if key changed happens inside of validate method)
shutdown(channel, registration.sessionID);
return;
}
// save off remote public key. This is ALWAYS the same, where the ECDH changes every time...
metaChannel.publicKey = registration.publicKey;
// tell the client to continue it's registration process.
Registration outboundRegister = new Registration(metaChannel.sessionId);
outboundRegister.publicKey = registrationWrapper.getPublicKey();
outboundRegister.eccParameters = CryptoECC.generateSharedParameters(registrationWrapper.getSecureRandom());
channel.writeAndFlush(outboundRegister);
return;
}
// IN: remote ECDH shared payload
// OUT: server ECDH shared payload
if (metaChannel.secretKey == null) {
/*
* Diffie-Hellman-Merkle key exchange for the AES key
* http://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange
*/
// the ECDH key will ROTATE every 10 minutes, since generating it for EVERY connection is expensive
// and since we are combining ECDHE+ECC public/private keys for each connection, other
// connections cannot break someone else's connection, since they are still protected by their own private keys.
metaChannel.ecdhKey = getEchdKeyOnRotate(registrationWrapper.getSecureRandom());
byte[] ecdhPubKeyBytes = java.util.Arrays.copyOfRange(registration.payload, 0, registration.payload.length);
ECPublicKeyParameters ecdhPubKey;
try {
ecdhPubKey = EccPublicKeySerializer.read(new Input(ecdhPubKeyBytes));
} catch (KryoException e) {
logger.error("Invalid decode of ECDH public key. Aborting.");
shutdown(channel, registration.sessionID);
return;
}
BasicAgreement agreement = new ECDHCBasicAgreement();
agreement.init(metaChannel.ecdhKey.getPrivate());
BigInteger shared = agreement.calculateAgreement(ecdhPubKey);
// now we setup our AES key based on our shared secret! (from ECDH)
// the shared secret is different each time a connection is made
byte[] keySeed = shared.toByteArray();
SHA384Digest sha384 = new SHA384Digest();
byte[] digest = new byte[sha384.getDigestSize()];
sha384.update(keySeed, 0, keySeed.length);
sha384.doFinal(digest, 0);
byte[] key = Arrays.copyOfRange(digest, 0, 32); // 256bit keysize (32 bytes)
metaChannel.secretKey = new SecretKeySpec(key, "AES");
Registration outboundRegister = new Registration(metaChannel.sessionId);
Output output = new Output(1024);
EccPublicKeySerializer.write(output, (ECPublicKeyParameters) metaChannel.ecdhKey.getPublic());
outboundRegister.payload = output.toBytes();
channel.writeAndFlush(outboundRegister);
return;
}
// NOTE: if we have more registrations, we will "bounce back" that status so the client knows what to do.
// IN: hasMore=true if we have more registrations to do, false otherwise
// Some cases we want to SKIP encryption (ie, loopback or specific IP/CIDR addresses)
// OTHERWISE ALWAYS upgrade the connection at this point.
// IN: upgraded=false if we haven't upgraded to encryption yet (this will always be the case right after encryption is setup)
if (!registration.upgraded) {
// this is the last protocol registered
if (!registration.hasMore) {
// this can ONLY be created when all protocols are registered!
// this must happen before we verify class registrations.
metaChannel.connection = this.registrationWrapper.connection0(metaChannel, remoteAddress);
if (metaChannel.tcpChannel != null) {
// metaChannel.tcpChannel.pipeline().addLast(CONNECTION_HANDLER, metaChannel.connection);
}
if (metaChannel.udpChannel != null) {
// metaChannel.udpChannel.pipeline().addLast(CONNECTION_HANDLER, metaChannel.connection);
}
}
// If we are loopback or the client is a specific IP/CIDR address, then we do things differently. The LOOPBACK address will never encrypt or compress the traffic.
byte upgradeType = registrationWrapper.getConnectionUpgradeType(remoteAddress);
registration.upgradeType = upgradeType;
// upgrade the connection to a none/compression/encrypted connection
upgradeDecoders(upgradeType, channel, metaChannel);
// bounce back to the client so it knows we received it
channel.write(registration);
upgradeEncoders(upgradeType, channel, metaChannel);
logChannelUpgrade(upgradeType, channel, metaChannel);
channel.flush();
return;
}
//
//
// we only get this when we are 100% done with encrypting/etc the connections
//
//
// upgraded=true when the client will send their class registration data. VERIFY IT IS CORRECT!
STATE state = registrationWrapper.verifyClassRegistration(metaChannel, registration);
if (state == STATE.ERROR) {
// abort! There was an error
shutdown(channel, registration.sessionID);
return;
}
else if (state == STATE.WAIT) {
return;
}
// else, continue.
//
//
// we only get this when we are 100% done with validation of class registrations. The last protocol to register gets us here.
//
//
// remove ourselves from handling any more messages, because we are done.
ChannelHandler handler = context.handler();
channel.pipeline().remove(handler);
// since only the LAST registration gets here, setup other ones as well (since they are no longer needed)
if (channel == metaChannel.tcpChannel && metaChannel.udpChannel != null) {
// the "other" channel is the UDP channel that we have to cleanup
metaChannel.udpChannel.pipeline().remove(RegistrationRemoteHandlerServerUDP.class);
}
else if (channel == metaChannel.udpChannel && metaChannel.tcpChannel != null) {
// the "other" channel is the TCP channel that we have to cleanup
metaChannel.tcpChannel.pipeline().remove(RegistrationRemoteHandlerServerTCP.class);
}
// remove the ConnectionWrapper (that was used to upgrade the connection) and cleanup the pipeline
Runnable preConnectRunnable = new Runnable() {
@Override
public
void run() {
// this method BEFORE the "onConnect()" runs and only after all of the channels have be correctly updated
// this tells the client we are ready to connect (we just bounce back "upgraded" over TCP, preferably).
// only the FIRST one to arrive at the client will actually setup the pipeline
Registration reg = new Registration(registration.sessionID);
reg.upgraded = true;
// there is a risk of UDP losing the packet, so if we can send via TCP, then we do so.
if (metaChannel.tcpChannel != null) {
logger.trace("Sending TCP upgraded command");
metaChannel.tcpChannel.writeAndFlush(reg);
}
else if (metaChannel.udpChannel != null) {
logger.trace("Sending UDP upgraded command");
metaChannel.udpChannel.writeAndFlush(reg);
}
else {
logger.error("This shouldn't happen!");
}
}
};
cleanupPipeline(channel, metaChannel, preConnectRunnable, null);
}
}

View File

@ -1,84 +0,0 @@
/*
* Copyright 2010 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.network.connection.registration.remote;
import dorkbox.network.connection.ConnectionImpl;
import dorkbox.network.connection.RegistrationWrapperServer;
import dorkbox.network.connection.registration.MetaChannel;
import dorkbox.network.connection.registration.Registration;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.EventLoopGroup;
public
class RegistrationRemoteHandlerServerTCP extends RegistrationRemoteHandlerServer {
public
RegistrationRemoteHandlerServerTCP(final String name,
final RegistrationWrapperServer registrationWrapper,
final EventLoopGroup workerEventLoop) {
super(name, registrationWrapper, workerEventLoop);
}
/**
* STEP 3-XXXXX: We pass registration messages around until we the registration handshake is complete!
*/
@SuppressWarnings("Duplicates")
@Override
public
void channelRead(ChannelHandlerContext context, Object message) throws Exception {
Channel channel = context.channel();
if (message instanceof Registration) {
Registration registration = (Registration) message;
MetaChannel metaChannel;
int sessionId = registration.sessionID;
if (sessionId == 0) {
metaChannel = registrationWrapper.createSession();
metaChannel.tcpChannel = channel;
// TODO: use this: channel.voidPromise();
logger.debug("New TCP connection. Saving meta-channel id: {}", metaChannel.sessionId);
}
else {
metaChannel = registrationWrapper.getSession(sessionId);
if (metaChannel == null) {
logger.error("Error getting invalid TCP channel session ID {}! MetaChannel is null!", sessionId);
shutdown(channel, sessionId);
return;
}
}
readServer(context, channel, registration, "TCP server", metaChannel);
}
else {
logger.error("Error registering TCP with remote client!");
// this is what happens when the registration happens too quickly...
Object connection = context.pipeline().last();
if (connection instanceof ConnectionImpl) {
// ((ConnectionImpl) connection).channelRead(context, message);
}
else {
shutdown(channel, 0);
}
}
}
}

View File

@ -1,89 +0,0 @@
/*
* Copyright 2010 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.network.connection.registration.remote;
import dorkbox.network.connection.ConnectionImpl;
import dorkbox.network.connection.RegistrationWrapperServer;
import dorkbox.network.connection.registration.MetaChannel;
import dorkbox.network.connection.registration.Registration;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.EventLoopGroup;
@SuppressWarnings("Duplicates")
@Sharable
public
class RegistrationRemoteHandlerServerUDP extends RegistrationRemoteHandlerServer {
public
RegistrationRemoteHandlerServerUDP(final String name,
final RegistrationWrapperServer registrationWrapper,
final EventLoopGroup workerEventLoop) {
super(name, registrationWrapper, workerEventLoop);
}
/**
* STEP 3-XXXXX: We pass registration messages around until we the registration handshake is complete!
*/
@Override
public
void channelRead(final ChannelHandlerContext context, Object message) throws Exception {
Channel channel = context.channel();
if (message instanceof Registration) {
Registration registration = (Registration) message;
MetaChannel metaChannel;
int sessionId = 0;
sessionId = registration.sessionID;
if (sessionId == 0) {
metaChannel = registrationWrapper.createSession();
metaChannel.udpChannel = channel;
logger.debug("New UDP connection. Saving meta-channel id: {}", metaChannel.sessionId);
}
else {
metaChannel = registrationWrapper.getSession(sessionId);
if (metaChannel == null) {
logger.error("Error getting invalid UDP channel session ID {}! MetaChannel is null!", sessionId);
shutdown(channel, sessionId);
return;
}
// in the event that we start with a TCP channel first, we still have to set the UDP channel
metaChannel.udpChannel = channel;
}
readServer(context, channel, registration, "UDP server", metaChannel);
}
else if (message instanceof io.netty.channel.socket.DatagramPacket) {
logger.error("Error registering UDP with remote client!");
shutdown(channel, 0);
}
else {
logger.error("Error registering UDP with remote client! Attempting to queue message: " + message.getClass());
// this is what happens when the registration happens too quickly...
Object connection = context.pipeline().last();
if (connection instanceof ConnectionImpl) {
// ((ConnectionImpl) connection).channelRead(context, message);
}
else {
shutdown(channel, 0);
}
}
}
}

View File

@ -127,7 +127,7 @@ open class Connection(connectionParameters: ConnectionParams<*>) {
private val expirationTime = System.currentTimeMillis() +
TimeUnit.SECONDS.toMillis(endPoint.config.connectionCleanupTimeoutInSeconds.toLong())
private val logger = endPoint.logger
val logger = endPoint.logger
// private val needsLock = AtomicBoolean(false)
@ -396,7 +396,7 @@ open class Connection(connectionParameters: ConnectionParams<*>) {
suspend fun onDisconnect(function: suspend (Connection) -> Unit) {
// make sure we atomically create the listener manager, if necessary
listenerManager.getAndUpdate { origManager ->
origManager ?: ListenerManager(logger)
origManager ?: ListenerManager()
}
listenerManager.value!!.onDisconnect(function)
@ -408,7 +408,7 @@ open class Connection(connectionParameters: ConnectionParams<*>) {
suspend fun <MESSAGE> onMessage(function: suspend (Connection, MESSAGE) -> Unit) {
// make sure we atomically create the listener manager, if necessary
listenerManager.getAndUpdate { origManager ->
origManager ?: ListenerManager(logger)
origManager ?: ListenerManager()
}
listenerManager.value!!.onMessage(function)

View File

@ -108,7 +108,7 @@ internal constructor(val type: Class<*>, internal val config: Configuration) : A
internal val actionDispatch = CoroutineScope(Dispatchers.Default)
// internal val connectionActor = actionDispatch.connectionActor()
internal val listenerManager = ListenerManager<CONNECTION>(logger)
internal val listenerManager = ListenerManager<CONNECTION>()
internal val connections = ConnectionManager<CONNECTION>(logger, config)
internal val mediaDriverContext: MediaDriver.Context

View File

@ -24,13 +24,12 @@ import dorkbox.util.collections.IdentityMap
import kotlinx.atomicfu.atomic
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import mu.KLogger
import net.jodah.typetools.TypeResolver
/**
* Manages all of the different connect/disconnect/etc listeners
*/
internal class ListenerManager<CONNECTION: Connection>(private val logger: KLogger) {
internal class ListenerManager<CONNECTION: Connection> {
companion object {
/**
* Specifies the load-factor for the IdentityMap used to manage keeping track of the number of connections + listeners
@ -38,39 +37,6 @@ internal class ListenerManager<CONNECTION: Connection>(private val logger: KLogg
@Property
val LOAD_FACTOR = 0.8f
/**
* Remove from the stacktrace until we get to the invoke site (ie: remove kotlin coroutine info + dorkbox network call stack)
*
* Neither of these are useful in resolving exception handling from a users perspective, and only clutter the stacktrace.
*/
fun cleanStackTrace(localThrowable: Throwable, invokingClass: Class<*>, remoteException: Exception? = null) {
val myClassName = invokingClass.name
val stackTrace = localThrowable.stackTrace
var newStartIndex = 0
for (element in stackTrace) {
newStartIndex++
if (element.className == myClassName && element.methodName == "invoke") {
// we do this 1 more time, because we want to remove the proxy invocation off the stack as well.
newStartIndex++
break
}
}
if (remoteException == null) {
// no remote exception, just cleanup our own callstack
localThrowable.stackTrace = stackTrace.copyOfRange(newStartIndex, stackTrace.size)
} else {
// merge this info into the remote exception, so we can get the correct call stack info
val newStack = Array<StackTraceElement>(remoteException.stackTrace.size + stackTrace.size - newStartIndex) { stackTrace[0] }
remoteException.stackTrace.copyInto(newStack)
stackTrace.copyInto(newStack, remoteException.stackTrace.size, newStartIndex)
remoteException.stackTrace = newStack
}
}
/**
* Remove from the stacktrace (going in reverse), kotlin coroutine info + dorkbox network call stack.
*

View File

@ -16,7 +16,6 @@
package dorkbox.network.rmi
import dorkbox.network.connection.Connection
import dorkbox.network.connection.ListenerManager
import dorkbox.network.rmi.messages.MethodRequest
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.runBlocking
@ -257,13 +256,13 @@ internal class RmiClient(val isGlobal: Boolean,
val fancyName = RmiUtils.makeFancyMethodName(method)
val exception = TimeoutException("Response timed out: $fancyName")
// from top down, clean up the coroutine stack
ListenerManager.cleanStackTrace(exception, RmiClient::class.java)
RmiUtils.cleanStackTraceForProxy(exception, RmiClient::class.java)
continuation.resumeWithException(exception)
}
is Exception -> {
// reconstruct the stack trace, so the calling method knows where the method invocation happened, and can trace the call
// this stack will ALWAYS run up to this method (so we remove from the top->down, to get to the call site)
ListenerManager.cleanStackTrace(Exception(), RmiClient::class.java, any)
RmiUtils.cleanStackTraceForProxy(Exception(), RmiClient::class.java, any)
continuation.resumeWithException(any)
}
else -> {
@ -280,13 +279,13 @@ internal class RmiClient(val isGlobal: Boolean,
val fancyName = RmiUtils.makeFancyMethodName(method)
val exception = TimeoutException("Response timed out: $fancyName")
// from top down, clean up the coroutine stack
ListenerManager.cleanStackTrace(exception, RmiClient::class.java)
RmiUtils.cleanStackTraceForProxy(exception, RmiClient::class.java)
throw exception
}
is Exception -> {
// reconstruct the stack trace, so the calling method knows where the method invocation happened, and can trace the call
// this stack will ALWAYS run up to this method (so we remove from the top->down, to get to the call site)
ListenerManager.cleanStackTrace(Exception(), RmiClient::class.java, any)
RmiUtils.cleanStackTraceForProxy(Exception(), RmiClient::class.java, any)
throw any
}
else -> {

View File

@ -17,7 +17,6 @@ package dorkbox.network.rmi
import dorkbox.network.connection.Connection
import dorkbox.network.connection.EndPoint
import dorkbox.network.connection.ListenerManager
import dorkbox.network.rmi.messages.ConnectionObjectCreateRequest
import dorkbox.network.rmi.messages.ConnectionObjectCreateResponse
import dorkbox.network.rmi.messages.GlobalObjectCreateRequest
@ -379,7 +378,7 @@ internal class RmiManagerGlobal(logger: KLogger,
insideResult.initCause(null)
}
ListenerManager.cleanStackTraceReverse(insideResult as Throwable)
RmiUtils.cleanStackTraceForImpl(insideResult as Exception, true)
val fancyName = RmiUtils.makeFancyMethodName(cachedMethod)
logger.error("Error invoking method: $fancyName", insideResult)
}
@ -419,7 +418,7 @@ internal class RmiManagerGlobal(logger: KLogger,
result.initCause(null)
}
ListenerManager.cleanStackTraceReverse(result as Throwable)
RmiUtils.cleanStackTraceForImpl(result as Exception, false)
val fancyName = RmiUtils.makeFancyMethodName(cachedMethod)
logger.error("Error invoking method: $fancyName", result)
}

View File

@ -470,101 +470,87 @@ object RmiUtils {
/**
* Remove from the stacktrace the "slice" of stack that relates to "dorkbox.network." package
*
* Then remove from the last occurrence of "dorkbox.network." ALL "kotlinx.coroutines." and "kotlin.coroutines." stacks
*
* We do this because these stack frames are not useful in resolving exception handling from a users perspective, and only clutter the stacktrace.
*/
fun cleanStackTraceForProxy(localThrowable: Throwable, invokingClass: Class<*>, remoteException: Exception? = null) {
val myClassName = invokingClass.name
val stackTrace = localThrowable.stackTrace
var newStartIndex = 0
for (element in stackTrace) {
newStartIndex++
if (element.className == myClassName && element.methodName == "invoke") {
// we do this 1 more time, because we want to remove the proxy invocation off the stack as well.
newStartIndex++
break
}
}
if (remoteException == null) {
// no remote exception, just cleanup our own callstack
localThrowable.stackTrace = stackTrace.copyOfRange(newStartIndex, stackTrace.size)
} else {
// merge this info into the remote exception, so we can get the correct call stack info
val newStack = Array<StackTraceElement>(remoteException.stackTrace.size + stackTrace.size - newStartIndex) { stackTrace[0] }
remoteException.stackTrace.copyInto(newStack)
stackTrace.copyInto(newStack, remoteException.stackTrace.size, newStartIndex)
remoteException.stackTrace = newStack
}
}
/**
* Remove from the stacktrace (going in reverse), kotlin coroutine info + dorkbox network call stack.
*
* Neither of these are useful in resolving exception handling from a users perspective, and only clutter the stacktrace.
*/
fun cleanStackTraceForImpl(exception: Exception, isSuspendFunction: Boolean) {
val packageName = RmiUtils::class.java.packageName
val stackTrace = exception.stackTrace
var newEndIndex = -1 // because we index by size, but access from 0
// step 1: starting at 0, find the start of our RMI method invocation
for (element in stackTrace) {
if (element.className.startsWith(packageName)) {
break
} else {
newEndIndex++
}
}
// step 2: starting at newEndIndex -> 0, find the start of reflection information (we are java11+ ONLY, so this is easy)
for (i in newEndIndex downTo 0) {
// this will be either JAVA reflection or ReflectASM reflection
val stackModule = stackTrace[i].moduleName
if (stackModule == "java.base") {
newEndIndex--
} else {
break
}
}
newEndIndex++ // have to add 1 back, because a copy must be by size (and we access from 0)
if (newEndIndex > 0) {
// if we are Java reflection, we are correct.
// if we are ReflectASM reflection, there is ONE stack frame extra we have to remove
if (stackTrace[newEndIndex].className == CachedAsmMethod::class.java.name) {
newEndIndex--
}
// if we are a KOTLIN suspend function, there is ONE stack frame extra we have to remove
if (isSuspendFunction) {
newEndIndex--
}
//
// suspend fun <T : Any> Call<T>.await(): T {
// return suspendCancellableCoroutine { continuation ->
// continuation.invokeOnCancellation {
// cancel()
// }
// enqueue(object : Callback<T> {
// override fun onResponse(call: Call<T>, response: PingResult.Response<T>) {
// if (response.isSuccessful) {
// val body = response.body()
// if (body == null) {
// val invocation = call.request().tag(Invocation::class.java)!!
// val method = invocation.method()
// val e = KotlinNullPointerException("Response from " +
// method.declaringClass.name +
// '.' +
// method.name +
// " was null but response body type was declared as non-null")
// continuation.resumeWithException(e)
// } else {
// continuation.resume(body)
// }
// } else {
// continuation.resumeWithException(HttpException(response))
// }
// }
//
// override fun onFailure(call: Call<T>, t: Throwable) {
// continuation.resumeWithException(t)
// }
// })
// }
// }
//
// @JvmName("awaitNullable")
// suspend fun <T : Any> Call<T?>.await(): T? {
// return suspendCancellableCoroutine { continuation ->
// continuation.invokeOnCancellation {
// cancel()
// }
// enqueue(object : Callback<T?> {
// override fun onResponse(call: Call<T?>, response: PingResult.Response<T?>) {
// if (response.isSuccessful) {
// continuation.resume(response.body())
// } else {
// continuation.resumeWithException(HttpException(response))
// }
// }
//
// override fun onFailure(call: Call<T?>, t: Throwable) {
// continuation.resumeWithException(t)
// }
// })
// }
// }
//
// suspend fun <T> Call<T>.awaitResponse(): PingResult.Response<T> {
// return suspendCancellableCoroutine { continuation ->
// continuation.invokeOnCancellation {
// cancel()
// }
// enqueue(object : Callback<T> {
// override fun onResponse(call: Call<T>, response: PingResult.Response<T>) {
// continuation.resume(response)
// }
//
// override fun onFailure(call: Call<T>, t: Throwable) {
// continuation.resumeWithException(t)
// }
// })
// }
// }
//
// /**
// * Force the calling coroutine to suspend before throwing [this].
// *
// * This is needed when a checked exception is synchronously caught in a [java.lang.reflect.Proxy]
// * invocation to avoid being wrapped in [java.lang.reflect.UndeclaredThrowableException].
// *
// * The implementation is derived from:
// * https://github.com/Kotlin/kotlinx.coroutines/pull/1667#issuecomment-556106349
// */
// suspend fun Exception.suspendAndThrow(): Nothing {
// kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn<Unit> { continuation: Continuation<Unit> ->
// Dispatchers.Default.dispatch(continuation.context, Runnable {
// continuation.intercepted().resumeWithException(this@suspendAndThrow)
// })
//
// kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED
// }
// }
exception.stackTrace = stackTrace.copyOfRange(0, newEndIndex)
}
}
}

View File

@ -59,8 +59,8 @@ class RmiTest : BaseTest() {
// Default behavior. RMI is transparent, method calls behave like normal
// (return values and exceptions are returned, call is synchronous)
System.err.println("hashCode: " + test.hashCode())
System.err.println("toString: $test")
connection.logger.error("hashCode: " + test.hashCode())
connection.logger.error("toString: $test")
test.withSuspend("test", 32)
val s1 = test.withSuspendAndReturn("test", 32)
@ -78,15 +78,14 @@ class RmiTest : BaseTest() {
// Test that RMI correctly waits for the remotely invoked method to exit
remoteObject.responseTimeout = 5000
test.moo("You should see this two seconds before...", 2000)
println("...This")
connection.logger.error("...This")
remoteObject.responseTimeout = 3000
// Try exception handling
try {
test.throwException()
} catch (e: UnsupportedOperationException) {
System.err.println("\tExpected exception (exception log should also be on the object impl side).")
e.printStackTrace()
connection.logger.error("Expected exception (exception log should also be on the object impl side).", e)
caught = true
}
Assert.assertTrue(caught)
@ -95,7 +94,7 @@ class RmiTest : BaseTest() {
try {
test.throwSuspendException()
} catch (e: UnsupportedOperationException) {
System.err.println("\tExpected exception (exception log should also be on the object impl side).")
connection.logger.error("\tExpected exception (exception log should also be on the object impl side).")
e.printStackTrace()
caught = true
}
@ -106,7 +105,7 @@ class RmiTest : BaseTest() {
// Non-blocking call tests
// Non-blocking call tests
// Non-blocking call tests
System.err.println("I'm currently async: ${remoteObject.async}. Now testing ASYNC")
connection.logger.error("I'm currently async: ${remoteObject.async}. Now testing ASYNC")
remoteObject.async = true
@ -123,7 +122,7 @@ class RmiTest : BaseTest() {
try {
test.throwException()
} catch (e: IllegalStateException) {
System.err.println("\tExpected exception (exception log should also be on the object impl side).")
connection.logger.error("\tExpected exception (exception log should also be on the object impl side).")
e.printStackTrace()
caught = true
}
@ -134,13 +133,12 @@ class RmiTest : BaseTest() {
try {
test.throwSuspendException()
} catch (e: IllegalStateException) {
System.err.println("\tExpected exception (exception log should also be on the object impl side).")
connection.logger.error("\tExpected exception (exception log should also be on the object impl side).")
e.printStackTrace()
caught = true
}
// exceptions are not caught when async = true!
Assert.assertFalse(caught)
caught = false
// Call will time out if non-blocking isn't working properly
@ -150,9 +148,9 @@ class RmiTest : BaseTest() {
// should wait for a small time
remoteObject.async = false
remoteObject.responseTimeout = 6000
println("You should see this 2 seconds before")
connection.logger.error("You should see this 2 seconds before")
val slow = test.slow()
println("...This")
connection.logger.error("...This")
Assert.assertEquals(slow.toDouble(), 123.0, 0.0001)
@ -162,7 +160,7 @@ class RmiTest : BaseTest() {
m.text = "sometext"
connection.send(m)
println("Finished tests")
connection.logger.error("Finished tests")
}
fun register(manager: NetworkSerializationManager) {

View File

@ -21,9 +21,9 @@ package dorkboxTest.network.rmi.classes
interface TestCow : TestCowBase {
fun moo()
fun moo(value: String)
fun moo(value: String, delay: Long)
suspend fun moo(value: String, delay: Long)
fun id(): Int
fun slow(): Float
suspend fun slow(): Float
suspend fun withSuspend(value: String, v2: Int)
suspend fun withSuspendAndReturn(value: String, v2: Int): Int

View File

@ -15,6 +15,7 @@
*/
package dorkboxTest.network.rmi.classes
import dorkbox.network.connection.Connection
import kotlinx.coroutines.delay
class TestCowImpl(val id: Int) : TestCowBaseImpl(), TestCow {
@ -22,46 +23,62 @@ class TestCowImpl(val id: Int) : TestCowBaseImpl(), TestCow {
private var moos = 0
override fun moo() {
throw RuntimeException("Should never be executed!")
}
fun moo(connection: Connection) {
moos++
println("Moo! $moos")
connection.logger.error("Moo! $moos")
}
override fun moo(value: String) {
moos += 2
println("Moo! $moos: $value")
throw RuntimeException("Should never be executed!")
}
override fun moo(value: String, delay: Long) {
fun moo(connection: Connection, value: String) {
moos += 2
connection.logger.error("Moo! $moos: $value")
}
override suspend fun moo(value: String, delay: Long) {
throw RuntimeException("Should never be executed!")
}
suspend fun moo(connection: Connection, value: String, delay: Long) {
moos += 4
println("Moo! $moos: $value ($delay)")
try {
Thread.sleep(delay)
} catch (e: InterruptedException) {
e.printStackTrace()
}
connection.logger.error("Moo! $moos: $value ($delay)")
delay(delay)
}
override fun id(): Int {
return id
}
override fun slow(): Float {
println("Slowdown!!")
try {
Thread.sleep(2000)
} catch (e: InterruptedException) {
e.printStackTrace()
}
override suspend fun slow(): Float {
throw RuntimeException("Should never be executed!")
}
suspend fun slow(connection: Connection): Float {
connection.logger.error("Slowdown!!")
delay(2000)
return 123.0f
}
override suspend fun withSuspend(value: String, v2: Int) {
println("Suspending!")
throw RuntimeException("Should never be executed!")
}
suspend fun withSuspend(connection: Connection, value: String, v2: Int) {
connection.logger.error("Suspending!")
delay(2000)
}
override suspend fun withSuspendAndReturn(value: String, v2: Int): Int {
println("Suspending with return!")
throw RuntimeException("Should never be executed!")
}
suspend fun withSuspendAndReturn(connection: Connection, value: String, v2: Int): Int {
connection.logger.error("Suspending with return!")
delay(2000)
return v2
}