Made base Kademlia routing

This commit is contained in:
ChronosX88 2019-04-23 15:15:37 +04:00
commit 6e9fed4dc6
33 changed files with 2350 additions and 0 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,2 @@
#Mon Apr 22 12:19:07 MSK 2019
gradle.version=4.10.3

Binary file not shown.

View File

1
.idea/.name Normal file
View File

@ -0,0 +1 @@
influencedht

View File

@ -0,0 +1,10 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<JetCodeStyleSettings>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</codeStyleSettings>
</code_scheme>
</component>

View File

@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

17
.idea/gradle.xml Normal file
View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
</set>
</option>
<option name="useQualifiedModuleNames" value="true" />
</GradleProjectSettings>
</option>
</component>
</project>

7
.idea/misc.xml Normal file
View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

618
.idea/workspace.xml Normal file
View File

@ -0,0 +1,618 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ChangeListManager">
<list default="true" id="a3421dfe-66c3-4c54-8bb2-850502ef6dd2" name="Default Changelist" comment="" />
<ignored path="$PROJECT_DIR$/out/" />
<ignored path="$PROJECT_DIR$/.gradle/" />
<ignored path="$PROJECT_DIR$/build/" />
<option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" />
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="DefaultGradleProjectSettings">
<option name="testRunner" value="GRADLE" />
<option name="delegatedBuild" value="true" />
<option name="isMigrated" value="true" />
</component>
<component name="ExternalProjectsData">
<projectState path="$PROJECT_DIR$">
<ProjectState />
</projectState>
</component>
<component name="FileEditorManager">
<leaf>
<file pinned="false" current-in-tab="true">
<entry file="file://$PROJECT_DIR$/src/main/kotlin/io/github/chronosx88/influencedht/core/PeerAddress.kt">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="-204">
<caret line="44" column="63" selection-start-line="44" selection-start-column="63" selection-end-line="44" selection-end-column="63" />
</state>
</provider>
</entry>
</file>
</leaf>
</component>
<component name="FileTemplateManagerImpl">
<option name="RECENT_TEMPLATES">
<list>
<option value="Kotlin File" />
<option value="Kotlin Enum" />
<option value="Class" />
<option value="Kotlin Object" />
<option value="Kotlin Class" />
</list>
</option>
</component>
<component name="FindInProjectRecents">
<findStrings>
<find>to</find>
<find>toByt</find>
</findStrings>
</component>
<component name="IdeDocumentHistory">
<option name="CHANGED_PATHS">
<list>
<option value="$PROJECT_DIR$/build.gradle" />
<option value="$PROJECT_DIR$/src/main/kotlin/io/github/chronosx88/influencedht/core/RPCActions.kt" />
<option value="$PROJECT_DIR$/src/main/kotlin/io/github/chronosx88/influencedht/core/rpc/RPCActions.kt" />
<option value="$PROJECT_DIR$/settings.gradle" />
<option value="$PROJECT_DIR$/src/main/kotlin/io/github/chronosx88/influencedht/core/KademliaID.kt" />
<option value="$PROJECT_DIR$/src/main/kotlin/io/github/chronosx88/influencedht/core/PeerSocketAddress.kt" />
<option value="$PROJECT_DIR$/src/main/kotlin/io/github/chronosx88/influencedht/core/Node.kt" />
<option value="$PROJECT_DIR$/src/main/kotlin/io/github/chronosx88/influencedht/core/routing/PeerContact.kt" />
<option value="$PROJECT_DIR$/src/main/java/io/gitub/chronosx88/influencedht/core/Number160.java" />
<option value="$PROJECT_DIR$/src/main/kotlin/io/github/chronosx88/influencedht/core/Utils.kt" />
<option value="$PROJECT_DIR$/src/main/kotlin/io/github/chronosx88/influencedht/core/KadConfig.kt" />
<option value="$PROJECT_DIR$/src/main/kotlin/io/github/chronosx88/influencedht/core/KadConfiguration.kt" />
<option value="$PROJECT_DIR$/src/main/kotlin/io/github/chronosx88/influencedht/core/DefaultIKadConfig.kt" />
<option value="$PROJECT_DIR$/src/main/kotlin/io/github/chronosx88/influencedht/core/IKadConfiguration.kt" />
<option value="$PROJECT_DIR$/src/main/kotlin/io/github/chronosx88/influencedht/core/DefaultKadConfig.kt" />
<option value="$PROJECT_DIR$/src/main/kotlin/io/github/chronosx88/influencedht/core/routing/KademliaBucket.kt" />
<option value="$PROJECT_DIR$/src/main/kotlin/io/github/chronosx88/influencedht/core/KeyComparator.kt" />
<option value="$PROJECT_DIR$/src/main/kotlin/io/github/chronosx88/influencedht/core/routing/KademliaRoutingTable.kt" />
<option value="$PROJECT_DIR$/src/main/kotlin/io/github/chronosx88/influencedht/core/PeerAddress.kt" />
</list>
</option>
</component>
<component name="ProjectFrameBounds" extendedState="6">
<option name="x" value="-25" />
<option name="y" value="324" />
<option name="width" value="1366" />
<option name="height" value="711" />
</component>
<component name="ProjectView">
<navigator proportions="" version="1">
<foldersAlwaysOnTop value="true" />
</navigator>
<panes>
<pane id="PackagesPane" />
<pane id="ProjectPane">
<subPane>
<expand>
<path>
<item name="influencedht" type="b2602c69:ProjectViewProjectNode" />
<item name="InfluenceDHT" type="8a07ba80:GradleTreeStructureProvider$GradleModuleDirectoryNode" />
</path>
<path>
<item name="influencedht" type="b2602c69:ProjectViewProjectNode" />
<item name="InfluenceDHT" type="8a07ba80:GradleTreeStructureProvider$GradleModuleDirectoryNode" />
<item name="src" type="462c0819:PsiDirectoryNode" />
</path>
<path>
<item name="influencedht" type="b2602c69:ProjectViewProjectNode" />
<item name="InfluenceDHT" type="8a07ba80:GradleTreeStructureProvider$GradleModuleDirectoryNode" />
<item name="src" type="462c0819:PsiDirectoryNode" />
<item name="main" type="8a07ba80:GradleTreeStructureProvider$GradleModuleDirectoryNode" />
</path>
<path>
<item name="influencedht" type="b2602c69:ProjectViewProjectNode" />
<item name="InfluenceDHT" type="8a07ba80:GradleTreeStructureProvider$GradleModuleDirectoryNode" />
<item name="src" type="462c0819:PsiDirectoryNode" />
<item name="main" type="8a07ba80:GradleTreeStructureProvider$GradleModuleDirectoryNode" />
<item name="java" type="462c0819:PsiDirectoryNode" />
</path>
<path>
<item name="influencedht" type="b2602c69:ProjectViewProjectNode" />
<item name="InfluenceDHT" type="8a07ba80:GradleTreeStructureProvider$GradleModuleDirectoryNode" />
<item name="src" type="462c0819:PsiDirectoryNode" />
<item name="main" type="8a07ba80:GradleTreeStructureProvider$GradleModuleDirectoryNode" />
<item name="java" type="462c0819:PsiDirectoryNode" />
<item name="core" type="462c0819:PsiDirectoryNode" />
</path>
<path>
<item name="influencedht" type="b2602c69:ProjectViewProjectNode" />
<item name="InfluenceDHT" type="8a07ba80:GradleTreeStructureProvider$GradleModuleDirectoryNode" />
<item name="src" type="462c0819:PsiDirectoryNode" />
<item name="main" type="8a07ba80:GradleTreeStructureProvider$GradleModuleDirectoryNode" />
<item name="kotlin" type="462c0819:PsiDirectoryNode" />
</path>
<path>
<item name="influencedht" type="b2602c69:ProjectViewProjectNode" />
<item name="InfluenceDHT" type="8a07ba80:GradleTreeStructureProvider$GradleModuleDirectoryNode" />
<item name="src" type="462c0819:PsiDirectoryNode" />
<item name="main" type="8a07ba80:GradleTreeStructureProvider$GradleModuleDirectoryNode" />
<item name="kotlin" type="462c0819:PsiDirectoryNode" />
<item name="influencedht" type="462c0819:PsiDirectoryNode" />
</path>
<path>
<item name="influencedht" type="b2602c69:ProjectViewProjectNode" />
<item name="InfluenceDHT" type="8a07ba80:GradleTreeStructureProvider$GradleModuleDirectoryNode" />
<item name="src" type="462c0819:PsiDirectoryNode" />
<item name="main" type="8a07ba80:GradleTreeStructureProvider$GradleModuleDirectoryNode" />
<item name="kotlin" type="462c0819:PsiDirectoryNode" />
<item name="influencedht" type="462c0819:PsiDirectoryNode" />
<item name="core" type="462c0819:PsiDirectoryNode" />
</path>
<path>
<item name="influencedht" type="b2602c69:ProjectViewProjectNode" />
<item name="InfluenceDHT" type="8a07ba80:GradleTreeStructureProvider$GradleModuleDirectoryNode" />
<item name="src" type="462c0819:PsiDirectoryNode" />
<item name="main" type="8a07ba80:GradleTreeStructureProvider$GradleModuleDirectoryNode" />
<item name="kotlin" type="462c0819:PsiDirectoryNode" />
<item name="influencedht" type="462c0819:PsiDirectoryNode" />
<item name="core" type="462c0819:PsiDirectoryNode" />
<item name="routing" type="462c0819:PsiDirectoryNode" />
</path>
<path>
<item name="influencedht" type="b2602c69:ProjectViewProjectNode" />
<item name="InfluenceDHT" type="8a07ba80:GradleTreeStructureProvider$GradleModuleDirectoryNode" />
<item name="src" type="462c0819:PsiDirectoryNode" />
<item name="main" type="8a07ba80:GradleTreeStructureProvider$GradleModuleDirectoryNode" />
<item name="kotlin" type="462c0819:PsiDirectoryNode" />
<item name="influencedht" type="462c0819:PsiDirectoryNode" />
<item name="core" type="462c0819:PsiDirectoryNode" />
<item name="rpc" type="462c0819:PsiDirectoryNode" />
</path>
<path>
<item name="influencedht" type="b2602c69:ProjectViewProjectNode" />
<item name="InfluenceDHT" type="8a07ba80:GradleTreeStructureProvider$GradleModuleDirectoryNode" />
<item name="src" type="462c0819:PsiDirectoryNode" />
<item name="test" type="8a07ba80:GradleTreeStructureProvider$GradleModuleDirectoryNode" />
</path>
<path>
<item name="influencedht" type="b2602c69:ProjectViewProjectNode" />
<item name="Scratches and Consoles" type="1a2a3e82:ScratchProjectViewPane$MyProjectNode" />
</path>
</expand>
<select />
</subPane>
</pane>
<pane id="Scope" />
</panes>
</component>
<component name="PropertiesComponent">
<property name="com.intellij.ide.scratch.LRUPopupBuilder$1/New Scratch File" value="KND" />
<property name="project.structure.last.edited" value="Project" />
<property name="project.structure.proportion" value="0.0" />
<property name="project.structure.side.proportion" value="0.0" />
<property name="settings.editor.selected.configurable" value="fileTemplates" />
</component>
<component name="RecentsManager">
<key name="MoveKotlinTopLevelDeclarationsDialog.RECENTS_KEY">
<recent name="io.github.chronosx88.influencedht.core.rpc" />
</key>
</component>
<component name="RunDashboard">
<option name="ruleStates">
<list>
<RuleState>
<option name="name" value="ConfigurationTypeDashboardGroupingRule" />
</RuleState>
<RuleState>
<option name="name" value="StatusDashboardGroupingRule" />
</RuleState>
</list>
</option>
</component>
<component name="SvnConfiguration">
<configuration />
</component>
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="a3421dfe-66c3-4c54-8bb2-850502ef6dd2" name="Default Changelist" comment="" />
<created>1555921109432</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1555921109432</updated>
</task>
<servers />
</component>
<component name="ToolWindowManager">
<frame x="-1" y="-1" width="1922" height="1037" extended-state="6" />
<editor active="true" />
<layout>
<window_info active="true" content_ui="combo" id="Project" order="0" visible="true" weight="0.2550586" />
<window_info id="Structure" order="1" side_tool="true" weight="0.25" />
<window_info id="Designer" order="2" />
<window_info id="UI Designer" order="3" />
<window_info id="Favorites" order="4" side_tool="true" />
<window_info anchor="bottom" id="Message" order="0" />
<window_info anchor="bottom" id="Find" order="1" weight="0.32932165" />
<window_info anchor="bottom" id="Run" order="2" />
<window_info anchor="bottom" id="Debug" order="3" weight="0.4" />
<window_info anchor="bottom" id="Cvs" order="4" weight="0.25" />
<window_info anchor="bottom" id="Inspection" order="5" weight="0.4" />
<window_info anchor="bottom" id="TODO" order="6" />
<window_info anchor="bottom" id="Version Control" order="7" />
<window_info anchor="bottom" id="Terminal" order="8" visible="true" weight="0.32932165" />
<window_info anchor="bottom" id="Event Log" order="9" sideWeight="0.5005325" side_tool="true" weight="0.32932165" />
<window_info anchor="bottom" id="Build" order="10" sideWeight="0.49946752" weight="0.32932165" />
<window_info anchor="right" id="Commander" internal_type="SLIDING" order="0" type="SLIDING" weight="0.4" />
<window_info anchor="right" id="Ant Build" order="1" weight="0.25" />
<window_info anchor="right" content_ui="combo" id="Hierarchy" order="2" weight="0.25" />
<window_info anchor="right" id="Palette" order="3" />
<window_info anchor="right" id="Maven" order="4" />
<window_info anchor="right" id="Gradle" order="5" />
<window_info anchor="right" id="Palette&#9;" order="6" />
</layout>
</component>
<component name="editorHistoryManager">
<entry file="file://$APPLICATION_CONFIG_DIR$/consoles/jshell/jshell_console.snippet">
<provider selected="true" editor-type-id="text-editor" />
</entry>
<entry file="file://$PROJECT_DIR$/gradle.properties">
<provider selected="true" editor-type-id="text-editor" />
</entry>
<entry file="file://$PROJECT_DIR$/src/main/kotlin/io/github/chronosx88/influencedht/1.kt" />
<entry file="file://$APPLICATION_CONFIG_DIR$/scratches/scratch.def">
<provider selected="true" editor-type-id="text-editor" />
</entry>
<entry file="file://$PROJECT_DIR$/build.gradle">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="51">
<caret line="3" column="55" selection-start-line="3" selection-start-column="55" selection-end-line="3" selection-end-column="55" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/settings.gradle">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="34">
<caret line="2" lean-forward="true" selection-start-line="2" selection-end-line="2" />
</state>
</provider>
</entry>
<entry file="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.3.30/1093224407fb1a6f5b79358044a2836bb3d899fd/kotlin-stdlib-1.3.30-sources.jar!/kotlin/Number.kt">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="119">
<caret line="21" column="22" selection-start-line="21" selection-start-column="22" selection-end-line="21" selection-end-column="22" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/main/kotlin/io/github/chronosx88/influencedht/core/KademliaID.kt">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="408">
<caret line="116" column="5" selection-start-line="116" selection-start-column="5" selection-end-line="116" selection-end-column="5" />
</state>
</provider>
</entry>
<entry file="jar:///usr/lib/jvm/java-8-jdk/src.zip!/java/awt/image/DataBuffer.java">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="296">
<caret line="69" column="22" selection-start-line="69" selection-start-column="22" selection-end-line="69" selection-end-column="22" />
</state>
</provider>
</entry>
<entry file="jar:///usr/lib/jvm/java-8-jdk/src.zip!/java/nio/ByteBuffer.java">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="-1183">
<caret line="261" column="22" selection-start-line="261" selection-start-column="22" selection-end-line="261" selection-end-column="22" />
</state>
</provider>
</entry>
<entry file="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.3.30/1093224407fb1a6f5b79358044a2836bb3d899fd/kotlin-stdlib-1.3.30-sources.jar!/kotlin/text/StringsJVM.kt">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="296">
<caret line="410" column="25" selection-start-line="410" selection-start-column="25" selection-end-line="410" selection-end-column="25" />
</state>
</provider>
</entry>
<entry file="jar:///usr/lib/jvm/java-8-jdk/src.zip!/java/net/InetSocketAddress.java">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="595">
<caret line="63" column="16" selection-start-line="63" selection-start-column="16" selection-end-line="63" selection-end-column="16" />
</state>
</provider>
</entry>
<entry file="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-common/1.3.30/9c981bc2a33066176e605b58a84170b8116e9d8c/kotlin-stdlib-common-1.3.30-sources.jar!/kotlin/collections/Sets.kt">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="296">
<caret line="33" column="28" lean-forward="true" selection-start-line="33" selection-start-column="28" selection-end-line="33" selection-end-column="28" />
</state>
</provider>
</entry>
<entry file="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.3.30/1093224407fb1a6f5b79358044a2836bb3d899fd/kotlin-stdlib-1.3.30-sources.jar!/kotlin/Collections.kt">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="279">
<caret line="255" column="3" lean-forward="true" selection-start-line="255" selection-start-column="3" selection-end-line="255" selection-end-column="3" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/main/kotlin/io/github/chronosx88/influencedht/core/PeerSocketAddress.kt">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="153">
<caret line="12" column="6" selection-start-line="12" selection-start-column="6" selection-end-line="12" selection-end-column="6" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/main/kotlin/io/github/chronosx88/influencedht/core/PeerAddress.kt">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="289">
<caret line="34" column="6" selection-start-line="34" selection-start-column="6" selection-end-line="34" selection-end-column="6" />
</state>
</provider>
</entry>
<entry file="jar:///usr/lib/jvm/java-8-jdk/src.zip!/java/net/InetAddress.java">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="296">
<caret line="670" column="18" selection-start-line="670" selection-start-column="18" selection-end-line="670" selection-end-column="18" />
<folding>
<element signature="e#24226#24227#0" expanded="true" />
<element signature="e#24253#24254#0" expanded="true" />
</folding>
</state>
</provider>
</entry>
<entry file="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.3.30/1093224407fb1a6f5b79358044a2836bb3d899fd/kotlin-stdlib-1.3.30-sources.jar!/kotlin/io/IOStreams.kt">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="685">
<caret line="133" column="23" selection-start-line="133" selection-start-column="23" selection-end-line="133" selection-end-column="23" />
</state>
</provider>
</entry>
<entry file="jar:///usr/lib/jvm/java-8-jdk/src.zip!/java/io/FilterInputStream.java">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="296">
<caret line="81" column="15" selection-start-line="81" selection-start-column="15" selection-end-line="81" selection-end-column="15" />
</state>
</provider>
</entry>
<entry file="jar:///usr/lib/jvm/java-8-jdk/src.zip!/java/io/DataInputStream.java">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="755">
<caret line="98" column="21" selection-start-line="98" selection-start-column="21" selection-end-line="98" selection-end-column="21" />
</state>
</provider>
</entry>
<entry file="jar:///usr/lib/jvm/java-8-jdk/src.zip!/java/util/Comparator.java">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="-163">
<caret line="108" column="17" selection-start-line="108" selection-start-column="17" selection-end-line="108" selection-end-column="17" />
</state>
</provider>
</entry>
<entry file="jar:///usr/lib/jvm/java-8-jdk/src.zip!/javax/print/attribute/IntegerSyntax.java">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="296">
<caret line="44" column="22" selection-start-line="44" selection-start-column="22" selection-end-line="44" selection-end-column="22" />
</state>
</provider>
</entry>
<entry file="jar:///usr/lib/jvm/java-8-jdk/src.zip!/java/lang/Integer.java">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="296">
<caret line="1322" column="36" selection-start-line="1322" selection-start-column="36" selection-end-line="1322" selection-end-column="36" />
</state>
</provider>
</entry>
<entry file="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.3.30/1093224407fb1a6f5b79358044a2836bb3d899fd/kotlin-stdlib-1.3.30-sources.jar!/kotlin/Primitives.kt">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="2336">
<caret line="649" column="21" selection-start-line="649" selection-start-column="21" selection-end-line="649" selection-end-column="21" />
</state>
</provider>
</entry>
<entry file="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-common/1.3.30/9c981bc2a33066176e605b58a84170b8116e9d8c/kotlin-stdlib-common-1.3.30-sources.jar!/kotlin/experimental/bitwiseOperations.kt">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="119">
<caret line="10" column="29" selection-start-line="10" selection-start-column="29" selection-end-line="10" selection-end-column="29" />
</state>
</provider>
</entry>
<entry file="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.3.30/1093224407fb1a6f5b79358044a2836bb3d899fd/kotlin-stdlib-1.3.30-sources.jar!/kotlin/Any.kt">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="340">
<caret line="34" column="29" selection-start-line="34" selection-start-column="29" selection-end-line="34" selection-end-column="29" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/main/kotlin/io/github/chronosx88/influencedht/core/Utils.kt">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="748">
<caret line="139" column="5" selection-start-line="139" selection-start-column="5" selection-end-line="139" selection-end-column="5" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/main/kotlin/io/github/chronosx88/influencedht/core/IKadConfiguration.kt">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="748">
<caret line="50" column="7" selection-start-line="50" selection-start-column="7" selection-end-line="50" selection-end-column="7" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/main/kotlin/io/github/chronosx88/influencedht/core/DefaultKadConfig.kt">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="170">
<caret line="10" column="34" selection-start-line="10" selection-start-column="34" selection-end-line="10" selection-end-column="34" />
</state>
</provider>
</entry>
<entry file="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.3.30/1093224407fb1a6f5b79358044a2836bb3d899fd/kotlin-stdlib-1.3.30-sources.jar!/kotlin/jvm/annotations/JvmFlagAnnotations.kt">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="714">
<caret line="45" column="31" selection-start-line="45" selection-start-column="31" selection-end-line="45" selection-end-column="31" />
</state>
</provider>
</entry>
<entry file="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.3.30/1093224407fb1a6f5b79358044a2836bb3d899fd/kotlin-stdlib-1.3.30-sources.jar!/kotlin/Library.kt">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="296">
<caret line="35" column="38" selection-start-line="35" selection-start-column="38" selection-end-line="35" selection-end-column="38" />
</state>
</provider>
</entry>
<entry file="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.3.30/1093224407fb1a6f5b79358044a2836bb3d899fd/kotlin-stdlib-1.3.30-sources.jar!/kotlin/Array.kt">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="102">
<caret line="9" column="78" selection-start-line="9" selection-start-column="78" selection-end-line="9" selection-end-column="78" />
</state>
</provider>
</entry>
<entry file="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.3.30/1093224407fb1a6f5b79358044a2836bb3d899fd/kotlin-stdlib-1.3.30-sources.jar!/kotlin/text/TypeAliases.kt">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="102">
<caret line="9" column="33" selection-start-line="9" selection-start-column="27" selection-end-line="9" selection-end-column="33" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/main/kotlin/io/github/chronosx88/influencedht/core/routing/PeerContact.kt">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="255">
<caret line="15" column="17" selection-start-line="15" selection-start-column="17" selection-end-line="15" selection-end-column="17" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/main/kotlin/io/github/chronosx88/influencedht/core/routing/KademliaBucket.kt">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="2720">
<caret line="160" column="15" selection-start-line="160" selection-start-column="15" selection-end-line="160" selection-end-column="15" />
<folding>
<element signature="e#56#138#0" expanded="true" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/main/java/io/gitub/chronosx88/influencedht/core/Number160.java">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="2553">
<caret line="467" column="51" lean-forward="true" selection-start-line="467" selection-start-column="51" selection-end-line="467" selection-end-column="51" />
<folding>
<element signature="imports" expanded="true" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/main/kotlin/io/github/chronosx88/influencedht/core/rpc/RPCActions.kt">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="85">
<caret line="5" column="10" selection-start-line="5" selection-start-column="10" selection-end-line="5" selection-end-column="10" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/main/kotlin/io/github/chronosx88/influencedht/core/KeyComparator.kt">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="221">
<caret line="13" column="66" selection-start-line="13" selection-start-column="66" selection-end-line="13" selection-end-column="66" />
<folding>
<element signature="e#48#121#0" expanded="true" />
</folding>
</state>
</provider>
</entry>
<entry file="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.3.30/1093224407fb1a6f5b79358044a2836bb3d899fd/kotlin-stdlib-1.3.30-sources.jar!/kotlin/ArrayIntrinsics.kt">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="153">
<caret line="23" column="45" selection-start-line="23" selection-start-column="45" selection-end-line="23" selection-end-column="45" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/main/kotlin/io/github/chronosx88/influencedht/core/routing/KademliaRoutingTable.kt">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="476">
<caret line="71" column="29" selection-start-line="71" selection-start-column="29" selection-end-line="71" selection-end-column="29" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/src/main/kotlin/io/github/chronosx88/influencedht/core/PeerAddress.kt">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="-204">
<caret line="44" column="63" selection-start-line="44" selection-start-column="63" selection-end-line="44" selection-end-column="63" />
</state>
</provider>
</entry>
</component>
<component name="masterDetails">
<states>
<state key="ArtifactsStructureConfigurable.UI">
<settings>
<artifact-editor />
<splitter-proportions>
<option name="proportions">
<list>
<option value="0.2" />
</list>
</option>
</splitter-proportions>
</settings>
</state>
<state key="FacetStructureConfigurable.UI">
<settings>
<splitter-proportions>
<option name="proportions">
<list>
<option value="0.2" />
</list>
</option>
</splitter-proportions>
</settings>
</state>
<state key="GlobalLibrariesConfigurable.UI">
<settings>
<splitter-proportions>
<option name="proportions">
<list>
<option value="0.2" />
</list>
</option>
</splitter-proportions>
</settings>
</state>
<state key="JdkListConfigurable.UI">
<settings>
<splitter-proportions>
<option name="proportions">
<list>
<option value="0.2" />
</list>
</option>
</splitter-proportions>
</settings>
</state>
<state key="ModuleStructureConfigurable.UI">
<settings>
<splitter-proportions>
<option name="proportions">
<list>
<option value="0.2" />
</list>
</option>
</splitter-proportions>
</settings>
</state>
<state key="ProjectJDKs.UI">
<settings>
<last-edited>1.8</last-edited>
<splitter-proportions>
<option name="proportions">
<list>
<option value="0.2" />
</list>
</option>
</splitter-proportions>
</settings>
</state>
<state key="ProjectLibrariesConfigurable.UI">
<settings>
<splitter-proportions>
<option name="proportions">
<list>
<option value="0.2" />
</list>
</option>
</splitter-proportions>
</settings>
</state>
</states>
</component>
</project>

26
build.gradle Normal file
View File

@ -0,0 +1,26 @@
plugins {
id 'java'
id 'org.jetbrains.kotlin.jvm' version '1.3.30'
id 'com.github.johnrengelman.shadow' version '4.0.4'
}
group 'io.github.chronosx88'
version '0.1'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
testCompile group: 'junit', name: 'junit', version: '4.12'
}
compileKotlin {
kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
kotlinOptions.jvmTarget = "1.8"
}

1
gradle.properties Normal file
View File

@ -0,0 +1 @@
kotlin.code.style=official

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,6 @@
#Mon Apr 22 12:19:51 MSK 2019
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.3-all.zip

172
gradlew vendored Executable file
View File

@ -0,0 +1,172 @@
#!/usr/bin/env sh
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"

84
gradlew.bat vendored Normal file
View File

@ -0,0 +1,84 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

2
settings.gradle Normal file
View File

@ -0,0 +1,2 @@
rootProject.name = 'InfluenceDHT'

View File

@ -0,0 +1,534 @@
package io.gitub.chronosx88.influencedht.core;
/*
* Copyright 2019 Thomas Bocek
*
* 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.
*/
import io.github.chronosx88.influencedht.core.Utils;
import java.util.Random;
/**
* This class represents a 160 bit number. This class is preferred over BigInteger as we always have 160bit, and thus,
* methods can be optimized.
*
* @author Thomas Bocek
*/
public final class Number160 extends Number implements Comparable<Number160> {
private static final long serialVersionUID = -6386562272459272306L;
// This key has *always* 160 bit. Do not change.
public static final int BITS = 160;
private static final long LONG_MASK = 0xffffffffL;
private static final int BYTE_MASK = 0xff;
private static final int CHAR_MASK = 0xf;
private static final int STRING_LENGTH = 42;
// a map used for String <-> Key conversion
private static final char[] DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c',
'd', 'e', 'f' };
// size of the backing integer array
public static final int INT_ARRAY_SIZE = BITS / Integer.SIZE;
// size of a byte array
public static final int BYTE_ARRAY_SIZE = BITS / Byte.SIZE;
public static final int CHARS_PER_INT = 8;
// backing integer array
private final int[] val;
// constants
public static final Number160 ZERO = new Number160(0);
public static final Number160 ONE = new Number160(1);
public static final Number160 MAX_VALUE = new Number160(new int[] { -1, -1, -1, -1, -1 });
/**
* Create a Key with value 0.
*/
public Number160() {
this.val = new int[INT_ARRAY_SIZE];
}
/**
* Create an instance with an integer array. This integer array will be copied into the backing array.
*
* @param val
* The value to copy to the backing array. Since this class stores 160bit numbers, the array needs to be
* of size 5 or smaller.
*/
public Number160(final int... val) {
if (val.length > INT_ARRAY_SIZE) {
throw new IllegalArgumentException(String.format("Can only deal with arrays of size smaller or equal to %s. Provided array has %s length.", INT_ARRAY_SIZE, val.length));
}
this.val = new int[INT_ARRAY_SIZE];
final int len = val.length;
for (int i = len - 1, j = INT_ARRAY_SIZE - 1; i >= 0; i--, j--) {
this.val[j] = val[i];
}
}
/**
* Create a Key from a string. The string has to be of length 42 to fit into the backing array. Note that this
* string is *always* in hexadecimal, there is no 0x... required before the number.
*
* @param val
* The characters allowed are [0-9a-f], which is in hexadecimal
*/
public Number160(final String val) {
if (val.length() > STRING_LENGTH) {
throw new IllegalArgumentException(String.format("Can only deal with strings of size smaller or equal to %s. Provided string has %s length.", STRING_LENGTH, val.length()));
}
if (val.indexOf("0x") != 0) {
throw new IllegalArgumentException(val
+ " is not in hexadecimal form. Decimal form is not supported yet");
}
this.val = new int[INT_ARRAY_SIZE];
final char[] tmp = val.toCharArray();
final int len = tmp.length;
for (int i = STRING_LENGTH - len, j = 2; i < (STRING_LENGTH - 2); i++, j++) {
this.val[i >> 3] <<= 4;
int digit = Character.digit(tmp[j], 16);
if (digit < 0) {
throw new RuntimeException("Not a hexadecimal number \"" + tmp[j]
+ "\". The range is [0-9a-f]");
}
// += or |= does not matter here
this.val[i >> 3] += digit & CHAR_MASK;
}
}
/**
* Creates a Key with the integer value.
*
* @param val
* integer value
*/
public Number160(final int val) {
this.val = new int[INT_ARRAY_SIZE];
this.val[INT_ARRAY_SIZE - 1] = val;
}
/**
* Creates a Key with the long value.
*
* @param val
* long value
*/
public Number160(final long val) {
this.val = new int[INT_ARRAY_SIZE];
this.val[INT_ARRAY_SIZE - 1] = (int) val;
this.val[INT_ARRAY_SIZE - 2] = (int) (val >> Integer.SIZE);
}
/**
* Creates a new Key using the byte array. The array is copied to the backing int[]
*
* @param val
* byte array
*/
public Number160(final byte[] val) {
this(val, 0, val.length);
}
/**
* Creates a new Key using the byte array. The array is copied to the backing int[] starting at the given offest.
*
* @param val
* byte array
* @param offset
* The offset where to start
* @param length
* the length to read
*/
public Number160(final byte[] val, final int offset, final int length) {
if (length > BYTE_ARRAY_SIZE) {
throw new IllegalArgumentException(String.format("Can only deal with byte arrays of size smaller or equal to %s. Provided array has %s length.", BYTE_ARRAY_SIZE, length));
}
this.val = new int[INT_ARRAY_SIZE];
for (int i = length + offset - 1, j = BYTE_ARRAY_SIZE - 1, k = 0; i >= offset; i--, j--, k++) {
// += or |= does not matter here
this.val[j >> 2] |= (val[i] & BYTE_MASK) << ((k % 4) << 3);
}
}
/**
* Creates a new Key with random values in it.
*
* @param random
* The object to create pseudo random numbers. For testing and debugging, the seed in the random class
* can be set to make the random values repeatable.
*/
public Number160(final Random random) {
this.val = new int[INT_ARRAY_SIZE];
for (int i = 0; i < INT_ARRAY_SIZE; i++) {
this.val[i] = random.nextInt();
}
}
/**
* Creates a new key with a long for the first 64bits, and using the lower 96bits for the rest.
*
* @param timestamp
* The long value that will be set in the beginning (most significant)
* @param number96
* The rest will be filled with this number
*/
public Number160(final long timestamp, Number160 number96) {
this.val = new int[INT_ARRAY_SIZE];
this.val[0] = (int) (timestamp >> Integer.SIZE);
this.val[1] = (int) timestamp;
this.val[2] = number96.val[2];
this.val[3] = number96.val[3];
this.val[4] = number96.val[4];
}
/**
* @return The first (most significant) 64bits
*/
public long timestamp() {
return ((this.val[0] & LONG_MASK) << Integer.SIZE) + (this.val[1] & LONG_MASK);
}
/**
* @return The lower (least significant) 96 bits
*/
public Number160 number96() {
return new Number160(0, 0, this.val[2], this.val[3], this.val[4]);
}
/**
* Xor operation. This operation is ~2.5 times faster than with BigInteger
*
* @param key
* The second operand for the xor operation
* @return A new key with the result of the xor operation
*/
public Number160 xor(final Number160 key) {
final int[] result = new int[INT_ARRAY_SIZE];
for (int i = 0; i < INT_ARRAY_SIZE; i++) {
result[i] = this.val[i] ^ key.val[i];
}
return new Number160(result);
}
/**
* Returns a copy of the backing array, which is always of size 5.
*
* @return a copy of the backing array
*/
public int[] toIntArray() {
final int[] retVal = new int[INT_ARRAY_SIZE];
for (int i = 0; i < INT_ARRAY_SIZE; i++) {
retVal[i] = this.val[i];
}
return retVal;
}
/**
* Fills the byte array with this number.
*
* @param me
* the byte array
* @param offset
* where to start in the byte array
* @return the offset we have read
*/
public int toByteArray(final byte[] me, final int offset) {
if (offset + BYTE_ARRAY_SIZE > me.length) {
throw new RuntimeException("array too small");
}
for (int i = 0; i < INT_ARRAY_SIZE; i++) {
// multiply by four
final int idx = offset + (i << 2);
me[idx + 0] = (byte) (val[i] >> 24);
me[idx + 1] = (byte) (val[i] >> 16);
me[idx + 2] = (byte) (val[i] >> 8);
me[idx + 3] = (byte) (val[i]);
}
return offset + BYTE_ARRAY_SIZE;
}
/**
* Returns a byte array, which is always of size 20.
*
* @return a byte array
*/
public byte[] toByteArray() {
final byte[] retVal = new byte[BYTE_ARRAY_SIZE];
toByteArray(retVal, 0);
return retVal;
}
/**
* Shows the content in a human readable manner.
*
* @param removeLeadingZero
* Indicates if leading zeros should be removed
* @return A human readable representation of this key
*/
public String toString(final boolean removeLeadingZero) {
boolean removeZero = removeLeadingZero;
final StringBuilder sb = new StringBuilder("0x");
for (int i = 0; i < INT_ARRAY_SIZE; i++) {
toHex(val[i], removeZero, sb);
if (removeZero && val[i] != 0) {
removeZero = false;
}
}
return sb.toString();
}
/**
* Checks if this number is zero.
*
* @return True if this number is zero, false otherwise
*/
public boolean isZero() {
for (int i = 0; i < INT_ARRAY_SIZE; i++) {
if (this.val[i] != 0) {
return false;
}
}
return true;
}
/**
* Calculates the number of bits used to represent this number. All leading (leftmost) zero bits are ignored
*
* @return The bits used
*/
public int bitLength() {
int bits = 0;
for (int i = 0; i < INT_ARRAY_SIZE; i++) {
if (this.val[i] != 0) {
bits += Integer.SIZE - Integer.numberOfLeadingZeros(this.val[i]);
bits += Integer.SIZE * (INT_ARRAY_SIZE - ++i);
break;
}
}
return bits;
}
@Override
public String toString() {
return toString(true);
}
@Override
public double doubleValue() {
double d = 0;
for (int i = 0; i < INT_ARRAY_SIZE; i++) {
d *= LONG_MASK + 1;
d += this.val[i] & LONG_MASK;
}
return d;
}
@Override
public float floatValue() {
return (float) doubleValue();
}
@Override
public int intValue() {
return this.val[INT_ARRAY_SIZE - 1];
}
/**
* For debugging...
*
* @param pos
* the position in the internal array
* @return the long of the unsigned int
*/
long unsignedInt(final int pos) {
return this.val[pos] & LONG_MASK;
}
@Override
public long longValue() {
return ((this.val[INT_ARRAY_SIZE - 1] & LONG_MASK) << Integer.SIZE)
+ (this.val[INT_ARRAY_SIZE - 2] & LONG_MASK);
}
@Override
public int compareTo(final Number160 o) {
for (int i = 0; i < INT_ARRAY_SIZE; i++) {
long b1 = val[i] & LONG_MASK;
long b2 = o.val[i] & LONG_MASK;
if (b1 < b2) {
return -1;
} else if (b1 > b2) {
return 1;
}
}
return 0;
}
@Override
public boolean equals(final Object obj) {
if (!(obj instanceof Number160)) {
return false;
}
if (obj == this) {
return true;
}
final Number160 key = (Number160) obj;
for (int i = 0; i < INT_ARRAY_SIZE; i++) {
if (key.val[i] != val[i]) {
return false;
}
}
return true;
}
@Override
public int hashCode() {
int hashCode = 0;
for (int i = 0; i < INT_ARRAY_SIZE; i++) {
hashCode = (int) (31 * hashCode + (val[i] & LONG_MASK));
}
return hashCode;
}
/**
* Convert an integer to hex value.
*
* @param integer2
* The integer to convert
* @param removeLeadingZero
* indicate if leading zeros should be ignored
* @param sb
* The string builder where to store the result
*/
private static void toHex(final int integer2, final boolean removeLeadingZero, final StringBuilder sb) {
// 4 bits form a char, thus we have 160/4=40 chars in a key, with an
// integer array size of 5, this gives 8 chars per integer
final char[] buf = new char[CHARS_PER_INT];
int charPos = CHARS_PER_INT;
int integer = integer2;
for (int i = 0; i < CHARS_PER_INT && !(removeLeadingZero && integer == 0); i++) {
buf[--charPos] = DIGITS[integer & CHAR_MASK];
// for hexadecimal, we have 4 bits per char, which ranges from
// [0-9a-f]
integer >>>= 4;
}
sb.append(buf, charPos, (CHARS_PER_INT - charPos));
}
/**
* Create a new Number160 from the integer, which fills all the 160bits. A new random object will be created.
*
* @param integerValue
* The value to hash from
* @return A hash from based on pseudo random, to fill the 160bits
*/
public static Number160 createHash(final int integerValue) {
Random r = new Random(integerValue);
return new Number160(r);
}
/**
* Creates a new Number160 from the long, which fills all the 160 bits. A new random object will be created, thus, its
* thread safe
*
* @param longValue
* The value to hash from (seed)
* @return A hash based on pseudo random, to fill the 160bits
*/
public static Number160 createHash(final long longValue) {
Random r = new Random(longValue);
return new Number160(r);
}
/**
* Creates a new Number160 using SHA1 on the string.
*
* @param string
* The value to hash from
* @return A hash based on SHA1 of the string
*/
public static Number160 createHash(final String string) {
return Utils.makeSHAHash(string);
}
/**
* Counts the number of leading 0's in this NodeId
*
* @return Integer The number of leading 0's
*/
public int getFirstSetBitIndex()
{
int prefixLength = 0;
for (byte b : this.toByteArray())
{
if (b == 0)
{
prefixLength += 8;
}
else
{
/* If the byte is not 0, we need to count how many MSBs are 0 */
int count = 0;
for (int i = 7; i >= 0; i--)
{
boolean a = (b & (1 << i)) == 0;
if (a)
{
count++;
}
else
{
break; // Reset the count if we encounter a non-zero number
}
}
/* Add the count of MSB 0s to the prefix length */
prefixLength += count;
/* Break here since we've now covered the MSB 0s */
break;
}
}
return prefixLength;
}
/**
* Gets the distance from this ID to another ID
*
* @param to
*
* @return Integer The distance
*/
public int getDistance(Number160 to)
{
// Compute the xor of this and to
// Get the index i of the first set bit of the xor returned NodeId
// The distance between them is ID_LENGTH - i
return BITS - this.xor(to).getFirstSetBitIndex();
}
}

View File

@ -0,0 +1,29 @@
package io.github.chronosx88.influencedht.core
class DefaultKadConfig: IKadConfiguration {
private val RESTORE_INTERVAL = (60 * 1000).toLong() // in milliseconds
private val RESPONSE_TIMEOUT: Long = 2000
private val OPERATION_TIMEOUT: Long = 2000
private val CONCURRENCY = 10
private val K = 5
private val RCSIZE = 3
private val STALE = 1
private val IS_TESTING = false
override val isTesting: Boolean
get() = IS_TESTING
override val restoreInterval: Long
get() = RESTORE_INTERVAL
override val responseTimeout: Long
get() = RESPONSE_TIMEOUT
override val operationTimeout: Long
get() = OPERATION_TIMEOUT
override val maxConcurrentMessagesTransiting: Int
get() = CONCURRENCY
override val k: Int
get() = K
override val replacementCacheSize: Int
get() = RCSIZE
override val stale: Int
get() = STALE
}

View File

@ -0,0 +1,52 @@
package io.github.chronosx88.influencedht.core
/**
* Interface that defines a IKadConfiguration object
*
* @author Joshua Kissoon
*/
interface IKadConfiguration {
/**
* @return Whether we're in a testing or production system.
*/
val isTesting: Boolean
/**
* @return Interval in milliseconds between execution of RestoreOperations.
*/
val restoreInterval: Long
/**
* If no reply received from a node in this period (in milliseconds)
* consider the node unresponsive.
*
* @return The time it takes to consider a node unresponsive
*/
val responseTimeout: Long
/**
* @return Maximum number of milliseconds for performing an operation.
*/
val operationTimeout: Long
/**
* @return Maximum number of concurrent messages in transit.
*/
val maxConcurrentMessagesTransiting: Int
/**
* @return K-Value used throughout Kademlia
*/
val k: Int
/**
* @return Size of replacement cache.
*/
val replacementCacheSize: Int
/**
* @return # of times a node can be marked as stale before it is actually removed.
*/
val stale: Int
}

View File

@ -0,0 +1,30 @@
package io.github.chronosx88.influencedht.core
import io.gitub.chronosx88.influencedht.core.Number160
import java.util.*
/**
* A Comparator to compare 2 keys to a given key
*
* @author Joshua Kissoon
*/
class KeyComparator(private val key: Number160): Comparator<PeerAddress> {
/**
* Compare two objects which must both be of type `PeerAddress`
* and determine which is closest to the identifier specified in the
* constructor.
*
* @param n1 Node 1 to compare distance from the key
* @param n2 Node 2 to compare distance from the key
*/
override fun compare(n1: PeerAddress, n2: PeerAddress): Int {
var b1 = n1.nodeId
var b2 = n2.nodeId
b1 = b1!!.xor(key)
b2 = b2!!.xor(key)
return b1.compareTo(b2)
}
}

View File

@ -0,0 +1,112 @@
package io.github.chronosx88.influencedht.core
import io.gitub.chronosx88.influencedht.core.Number160
import java.io.DataInputStream
import java.io.DataOutputStream
import java.io.IOException
import java.lang.IllegalArgumentException
import java.net.InetAddress
import java.net.InetSocketAddress
/**
* A PeerAddress in the Kademlia network - Contains basic node network information.
*
* @author Joshua Kissoon
*/
class PeerAddress {
/**
* @return The NodeId object of this node
*/
var nodeId: Number160? = null
private set
private var inetAddress: InetAddress? = null
private var port: Int = 0
private val strRep: String
/**
* Creates a SocketAddress for this node
*
* @return
*/
val socketAddress: InetSocketAddress
get() = InetSocketAddress(this.inetAddress, this.port)
constructor(nid: Number160, ip: InetAddress, port: Int) {
this.nodeId = nid
this.inetAddress = ip
this.port = port
this.strRep = this.nodeId!!.toString()
}
/**
* Load the PeerAddress's data from a DataInput stream
*
* @param inputStream Stream which contains this PeerAddress
*
* @throws IOException
*/
@Throws(IOException::class)
constructor(inputStream: DataInputStream) {
this.fromStream(inputStream)
this.strRep = this.nodeId!!.toString()
}
/**
* Set the InetAddress of this node
*
* @param addr The new InetAddress of this node
*/
fun setInetAddress(addr: InetAddress) {
this.inetAddress = addr
}
@Throws(IOException::class)
fun toStream(out: DataOutputStream) {
/* Add the NodeId to the stream */
out.write(this.nodeId!!.toByteArray())
/* Add the PeerAddress's IP address to the stream */
val a = inetAddress!!.address
if (a.size != 4) {
throw IllegalArgumentException("Expected InetAddress of 4 bytes, got " + a.size)
}
out.write(a)
/* Add the port to the stream */
out.writeInt(port)
}
@Throws(IOException::class)
fun fromStream(inputStream: DataInputStream) {
/* Load the NodeId */
val nodeIDBinary = ByteArray(Number160.BYTE_ARRAY_SIZE)
inputStream.readFully(nodeIDBinary)
this.nodeId = Number160(nodeIDBinary)
/* Load the IP Address */
val ip = ByteArray(4)
inputStream.readFully(ip)
this.inetAddress = InetAddress.getByAddress(ip)
/* Read inputStream the port */
this.port = inputStream.readInt()
}
override fun equals(o: Any?): Boolean {
if (o is PeerAddress) {
val n = o as PeerAddress?
return if (n === this) {
true
} else this.nodeId!!.equals(n!!.nodeId)
}
return false
}
override fun hashCode(): Int {
return this.nodeId!!.hashCode()
}
override fun toString(): String {
return this.nodeId!!.toString()
}
}

View File

@ -0,0 +1,141 @@
package io.github.chronosx88.influencedht.core
import io.gitub.chronosx88.influencedht.core.Number160
import java.net.Inet4Address
import java.net.Inet6Address
import java.net.InetAddress
import java.net.UnknownHostException
import java.nio.ByteBuffer
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import java.util.*
import kotlin.experimental.and
object Utils {
const val IPV4_BYTES = 4
const val IPV6_BYTES = 16
const val BYTE_BITS = 8
const val MASK_0F = 0xf // 00000000 00000000 00000000 00001111
const val MASK_80 = 0x80 // 00000000 00000000 00000000 10000000
const val MASK_FF = 0xff // 00000000 00000000 00000000 11111111
const val BYTE_BYTE_SIZE = 1 // 8 bits
const val SHORT_BYTE_SIZE = 2 // 16 bits
const val INTEGER_BYTE_SIZE = 4 // 32 bits
const val LONG_BYTE_SIZE = 8 // 64 bits
val EMPTY_BYTE_ARRAY = ByteArray(0)
@JvmStatic
fun makeSHAHash(strInput: String): Number160 {
val buffer = strInput.toByteArray()
return makeSHAHash(buffer)
}
@JvmStatic
fun makeSHAHash(buffer: ByteBuffer): Number160 {
try {
val md = MessageDigest.getInstance("SHA-1")
md.update(buffer)
val digest = md.digest()
return Number160(digest)
} catch (e: NoSuchAlgorithmException) {
e.printStackTrace()
return Number160()
}
}
@JvmStatic
fun makeSHAHash(buffer: ByteArray): Number160 {
return makeSHAHash(ByteBuffer.wrap(buffer))
}
/**
* Converts a byte array to a Inet4Address.
*
* @param src
* the byte array
* @param offset
* where to start in the byte array
* @return The Inet4Address
*
* @exception IndexOutOfBoundsException
* if copying would cause access of data outside array bounds for `src`.
* @exception NullPointerException
* if either `src` is `null`.
*/
@JvmStatic
fun inet4FromBytes(src: ByteArray, offset: Int): InetAddress {
// IPv4 is 32 bit
val tmp2 = ByteArray(Utils.IPV4_BYTES)
System.arraycopy(src, offset, tmp2, 0, Utils.IPV4_BYTES)
try {
return Inet4Address.getByAddress(tmp2)
} catch (e: UnknownHostException) {
/*
* This really shouldn't happen in practice since all our byte sequences have the right length. However
* {@link InetAddress#getByAddress} is documented as potentially throwing this
* "if IP address is of illegal length".
*/
throw IllegalArgumentException(
String.format(
"Host address '%s' is not a valid IPv4 address.", Arrays.toString(tmp2)
), e
)
}
}
/**
* Converts a byte array to a Inet6Address.
*
* @param me
* me the byte array
* @param offset
* where to start in the byte array
* @return The Inet6Address
*
* @exception IndexOutOfBoundsException
* if copying would cause access of data outside array bounds for `src`.
* @exception NullPointerException
* if either `src` is `null`.
*/
@JvmStatic
fun inet6FromBytes(me: ByteArray, offset: Int): InetAddress {
// IPv6 is 128 bit
val tmp2 = ByteArray(Utils.IPV6_BYTES)
System.arraycopy(me, offset, tmp2, 0, Utils.IPV6_BYTES)
try {
return Inet6Address.getByAddress(tmp2)
} catch (e: UnknownHostException) {
/*
* This really shouldn't happen in practice since all our byte sequences have the right length. However
* {@link InetAddress#getByAddress} is documented as potentially throwing this
* "if IP address is of illegal length".
*/
throw IllegalArgumentException(
String.format(
"Host address '%s' is not a valid IPv4 address.", Arrays.toString(tmp2)
), e
)
}
}
/**
* Convert a byte to a bit set. BitSet.valueOf(new byte[] {b}) is only available in 1.7, so we need to do this on
* our own.
*
* @param b
* The byte to be converted
* @return The resulting bit set
*/
@JvmStatic
fun createBitSet(b: Byte): BitSet {
val bitSet = BitSet(8)
for (i in 0 until Utils.BYTE_BITS) {
val value = b and (1 shl i).toByte()
bitSet.set(i, value.toInt() != 0)
}
return bitSet
}
}

View File

@ -0,0 +1,203 @@
package io.github.chronosx88.influencedht.core.routing
import io.github.chronosx88.influencedht.core.IKadConfiguration
import io.github.chronosx88.influencedht.core.PeerAddress
import java.util.*
/**
* A bucket in the Kademlia routing table
*
* @property depth How deep is this bucket in the Routing Table
* @property config Kademlia configuration
*
* @author Joshua Kissoon
*/
class KademliaBucket(@get:Synchronized val depth: Int, private val config: IKadConfiguration) {
/* Contacts stored in this routing table */
private val contacts: TreeSet<PeerContact> = TreeSet()
/* A set of last seen contacts that can replace any current contact that is unresponsive */
private val replacementCache: TreeSet<PeerContact> = TreeSet()
@Synchronized
fun insert(c: PeerContact) {
if (this.contacts.contains(c)) {
/**
* If the contact is already in the bucket, lets update that we've seen it
* We need to remove and re-add the contact to get the Sorted Set to update sort order
*/
val tmp = this.removeFromContacts(c.getPeerAddress())
tmp.setSeenNow()
tmp.resetStaleCount()
this.contacts.add(tmp)
} else {
/* If the bucket is filled, so put the contacts in the replacement cache */
if (contacts.size >= this.config.k) {
/* If the cache is empty, we check if any contacts are stale and replace the stalest one */
var stalest: PeerContact? = null
for (tmp in this.contacts) {
if (tmp.staleCount() >= this.config.stale) {
/* Contact is stale */
if (stalest == null) {
stalest = tmp
} else if (tmp.staleCount() > stalest.staleCount()) {
stalest = tmp
}
}
}
/* If we have a stale contact, remove it and add the new contact to the bucket */
if (stalest != null) {
this.contacts.remove(stalest)
this.contacts.add(c)
} else {
/* No stale contact, lets insert this into replacement cache */
this.insertIntoReplacementCache(c)
}
} else {
this.contacts.add(c)
}
}
}
@Synchronized
fun insert(n: PeerAddress) {
this.insert(PeerContact(n))
}
@Synchronized
fun containsContact(c: PeerContact): Boolean {
return this.contacts.contains(c)
}
@Synchronized
fun containsNode(n: PeerAddress): Boolean {
return this.containsContact(PeerContact(n))
}
@Synchronized
fun removeContact(c: PeerContact): Boolean {
/* If the contact does not exist, then we failed to remove it */
if (!this.contacts.contains(c)) {
return false
}
/* Contact exist, lets remove it only if our replacement cache has a replacement */
if (!this.replacementCache.isEmpty()) {
/* Replace the contact with one from the replacement cache */
this.contacts.remove(c)
val replacement = this.replacementCache.first()
this.contacts.add(replacement)
this.replacementCache.remove(replacement)
} else {
/* There is no replacement, just increment the contact's stale count */
this.getFromContacts(c.getPeerAddress()).incrementStaleCount()
}
return true
}
@Synchronized
private fun getFromContacts(n: PeerAddress): PeerContact {
for (c in this.contacts) {
if (c.getPeerAddress().equals(n)) {
return c
}
}
/* This contact does not exist */
throw NoSuchElementException("The contact does not exist in the contacts list.")
}
@Synchronized
private fun removeFromContacts(n: PeerAddress): PeerContact {
for (c in this.contacts) {
if (c.getPeerAddress().equals(n)) {
this.contacts.remove(c)
return c
}
}
/* We got here means this element does not exist */
throw NoSuchElementException("Node does not exist in the replacement cache. ")
}
@Synchronized
fun removeNode(n: PeerAddress): Boolean {
return this.removeContact(PeerContact(n))
}
@Synchronized
fun numContacts(): Int {
return this.contacts.size
}
@Synchronized
fun getContacts(): List<PeerContact> {
val ret = ArrayList<PeerContact>()
/* If we have no contacts, return the blank arraylist */
if (this.contacts.isEmpty()) {
return ret
}
/* We have contacts, lets copy put them into the arraylist and return */
for (c in this.contacts) {
ret.add(c)
}
return ret
}
/**
* When the bucket is filled, we keep extra contacts in the replacement cache.
*/
@Synchronized
private fun insertIntoReplacementCache(c: PeerContact) {
/* Just return if this contact is already in our replacement cache */
if (this.replacementCache.contains(c)) {
// If the contact is already in the bucket, lets update that we've seen it
// We need to remove and re-add the contact to get the Sorted Set to update sort order
val tmp = this.removeFromReplacementCache(c.getPeerAddress())
tmp.setSeenNow()
this.replacementCache.add(tmp)
} else if (this.replacementCache.size > this.config.k) {
/* if our cache is filled, we remove the least recently seen contact */
this.replacementCache.remove(this.replacementCache.last())
this.replacementCache.add(c)
} else {
this.replacementCache.add(c)
}
}
@Synchronized
private fun removeFromReplacementCache(n: PeerAddress): PeerContact {
for (c in this.replacementCache) {
if (c.getPeerAddress().equals(n)) {
this.replacementCache.remove(c)
return c
}
}
/* We got here means this element does not exist */
throw NoSuchElementException("Node does not exist in the replacement cache. ")
}
@Synchronized
override fun toString(): String {
val sb = StringBuilder("Bucket at depth: ")
sb.append(this.depth)
sb.append("\n Nodes: \n")
for (n in this.contacts) {
sb.append("Node: ")
sb.append(n.getPeerAddress().nodeId.toString())
sb.append(" (stale: ")
sb.append(n.staleCount())
sb.append(")")
sb.append("\n")
}
return sb.toString()
}
}

View File

@ -0,0 +1,194 @@
package io.github.chronosx88.influencedht.core.routing
import io.github.chronosx88.influencedht.core.IKadConfiguration
import io.github.chronosx88.influencedht.core.KeyComparator
import io.github.chronosx88.influencedht.core.PeerAddress
import io.gitub.chronosx88.influencedht.core.Number160
import java.util.*
/**
* Implementation of a Kademlia routing table
*
* @author Joshua Kissoon
*/
class KademliaRoutingTable(
private val localNode: PeerAddress// The current node
, @field:Transient private var config: IKadConfiguration
) {
/**
* @return Bucket[] The buckets in this Kad Instance
*/
/**
* Set the KadBuckets of this routing table, mainly used when retrieving saved state
*
* @param buckets
*/
@Transient
lateinit var buckets: Array<KademliaBucket>
/**
* @return List A List of all Nodes in this KademliaRoutingTable
*/
val allNodes: List<PeerAddress>
@Synchronized get() {
val nodes = ArrayList<PeerAddress>()
for (b in this.buckets!!) {
for (c in b.getContacts()) {
nodes.add(c.getPeerAddress())
}
}
return nodes
}
/**
* @return List A List of all Nodes in this KademliaRoutingTable
*/
val allContacts: List<PeerContact>
get() {
val contacts = ArrayList<PeerContact>()
for (b in this.buckets!!) {
contacts.addAll(b.getContacts())
}
return contacts
}
init {
/* Initialize all of the buckets to a specific depth */
this.initialize()
/* Insert the local node */
this.insert(localNode)
}
/**
* Initialize the KademliaRoutingTable to it's default state
*/
fun initialize() {
this.buckets = emptyArray()
for (i in 0 until Number160.BITS) {
buckets[i] = KademliaBucket(i, this.config)
}
}
fun setConfiguration(config: IKadConfiguration) {
this.config = config
}
/**
* Adds a contact to the routing table based on how far it is from the LocalNode.
*
* @param c The contact to add
*/
@Synchronized
fun insert(c: PeerContact) {
this.buckets!![this.getBucketId(c.getPeerAddress().nodeId)].insert(c)
}
/**
* Adds a node to the routing table based on how far it is from the LocalNode.
*
* @param n The node to add
*/
@Synchronized
fun insert(n: PeerAddress) {
this.buckets!![this.getBucketId(n.nodeId)].insert(n)
}
/**
* Compute the bucket ID in which a given node should be placed; the bucketId is computed based on how far the node is away from the Local PeerAddress.
*
* @param nid The NodeId for which we want to find which bucket it belong to
*
* @return Integer The bucket ID in which the given node should be placed.
*/
fun getBucketId(nid: Number160?): Int {
val bId = this.localNode.nodeId!!.xor(nid).bitLength() - 1
/* If we are trying to insert a node into it's own routing table, then the bucket ID will be -1, so let's just keep it in bucket 0 */
return if (bId < 0) 0 else bId
}
/**
* Find the closest set of contacts to a given NodeId
*
* @param target The NodeId to find contacts close to
* @param numNodesRequired The number of contacts to find
*
* @return List A List of contacts closest to target
*/
@Synchronized
fun findClosest(target: Number160, numNodesRequired: Int): List<PeerAddress> {
val sortedSet = TreeSet(KeyComparator(target))
sortedSet.addAll(this.allNodes)
val closest = ArrayList<PeerAddress>(numNodesRequired)
/* Now we have the sorted set, lets get the top numRequired */
var count = 0
for (n in sortedSet) {
closest.add(n)
if (++count == numNodesRequired) {
break
}
}
return closest
}
/**
* Method used by operations to notify the routing table of any contacts that have been unresponsive.
*
* @param contacts The set of unresponsive contacts
*/
fun setUnresponsiveContacts(contacts: List<PeerAddress>) {
if (contacts.isEmpty()) {
return
}
for (n in contacts) {
this.setUnresponsiveContact(n)
}
}
/**
* Method used by operations to notify the routing table of any contacts that have been unresponsive.
*
* @param n
*/
@Synchronized
fun setUnresponsiveContact(n: PeerAddress) {
val bucketId = this.getBucketId(n.nodeId)
/* Remove the contact from the bucket */
this.buckets!![bucketId].removeNode(n)
}
@Synchronized
override fun toString(): String {
val sb = StringBuilder("\n ***************** \n")
var totalContacts = 0
for (b in this.buckets!!) {
if (b.numContacts() > 0) {
totalContacts += b.numContacts()
sb.append("# nodes in Bucket with depth ")
sb.append(b.depth)
sb.append(": ")
sb.append(b.numContacts())
sb.append("\n")
sb.append(b.toString())
sb.append("\n")
}
}
sb.append("\nTotal Contacts: ")
sb.append(totalContacts)
sb.append("\n\n")
sb.append(" ******************** ")
return sb.toString()
}
}

View File

@ -0,0 +1,97 @@
package io.github.chronosx88.influencedht.core.routing
import io.github.chronosx88.influencedht.core.PeerAddress
/**
* Keeps information about contacts of the Node; Contacts are stored in the Buckets in the Routing Table.
*
* Contacts are used instead of nodes because more information is needed than just the node information.
* - Information such as
* -- Last seen time
*
* @author Joshua Kissoon
* @since 20140425
* @updated 20140426
*/
class PeerContact(peerAddress: PeerAddress): Comparable<PeerContact> {
private val n: PeerAddress = peerAddress
private var lastSeen: Long = 0
/**
* Stale as described by Kademlia paper page 64
* When a contact fails to respond, if the replacement cache is empty and there is no replacement for the contact,
* just mark it as stale.
*
* Now when a new contact is added, if the contact is stale, it is removed.
*/
private var staleCount: Int = 0
/**
* Create a contact object
*
* @param n The node associated with this contact
*/
init {
this.lastSeen = System.currentTimeMillis() / 1000L
}
fun getPeerAddress(): PeerAddress {
return this.n
}
/**
* When a Node sees a contact a gain, the Node will want to update that it's seen recently,
* this method updates the last seen timestamp for this contact.
*/
fun setSeenNow() {
this.lastSeen = System.currentTimeMillis() / 1000L
}
/**
* When last was this contact seen?
*
* @return long The last time this contact was seen.
*/
fun lastSeen(): Long {
return this.lastSeen
}
override fun equals(c: Any?): Boolean {
return (c as? PeerContact)?.getPeerAddress()?.equals(this.getPeerAddress()) ?: false
}
/**
* Increments the amount of times this count has failed to respond to a request.
*/
fun incrementStaleCount() {
staleCount++
}
/**
* @return Integer Stale count
*/
fun staleCount(): Int {
return this.staleCount
}
/**
* Reset the stale count of the contact if it's recently seen
*/
fun resetStaleCount() {
this.staleCount = 0
}
override fun compareTo(o: PeerContact): Int {
if (this.getPeerAddress().equals(o.getPeerAddress())) {
return 0
}
return if (this.lastSeen() > o.lastSeen()) 1 else -1
}
override fun hashCode(): Int {
return this.getPeerAddress().hashCode()
}
}

View File

@ -0,0 +1,7 @@
package io.github.chronosx88.influencedht.core.rpc
enum class RPCActions {
PUT,
GET,
REMOVE
}