【代码札记】从零开始的点对点聊天系统 P5
本文继续介绍如何将前文的i2p-p2p-chat接入Minecraft中。这是本系列的最后一篇文章。上文说上文说到了启动节点。但是之后还得发消息，除了公屏发消息，还得有一个私信消息的入口，除此之外还要让用户能够在游戏内复制到他的I2P地址，然后发送给其他玩家，让其他玩家在游戏内连接到这个地址，这就引出了一个指令系统。最后为了便别哪些玩家连上了我们，我们还希望有一个地方能展示这些信息。拦截消息为了避免意外发送消息从而被举报/陷害（还记得一开始我提到的gaslighting漏洞吗），最好的办法当然就是直接拦截所有消息，除非用户使用特别的命令要求向MC的公屏中发送消息。经过一番搜索，最终发现在ClientPlayerEntity类中，sendChatMessageInternal方法是整个发送消息过程中最关键的一环，它接收各种形式的调用（例如用户在聊天框中使用回车，或者其他mod以用户的名字发送消息），将实际要发送的消息封装成数据包发送给服务器。也就是说，只要我们能够拦截这个方法，我们就可以终止消息发送，并获得待发送的消息。得益于mixin的cancellable特性，我们可以指示mixin框架提前终止方法的调用，即插入我们自己的return指令：@Mixin(ClientPlayerEntity.class) public abstract class MixinClientPlayerEntity { @Inject(method = "sendChatMessageInternal", at = @At("HEAD"), cancellable = true) private void onSendChatMessageInternal(String message, Text preview, CallbackInfo ci) { if (ChatHelper.shouldSendPlain(message)) { // this message should be sent through minecraft's internal chat // stop further processing return; } if (CommandHelper.isCommand(message)) { CommandHelper.handleCommand(message); } else { // broadcast message var playerList = MinecraftHelper.getPlayerList(); ClientModEntry.getPeer().sendRequest( new TextMessageRequest("broadcast", message), s -> s.useContextSync(c -> c.getUsername() != null && playerList.contains(c.getUsername())) ); // show the message to ourselves ChatHelper.addBroadcastMessageToChat( Objects.requireNonNull(MinecraftHelper.getCurrentUsername()), Text.literal(message) ); } // cancel sending ci.cancel(); } }这里有三个设计，首先是决定要不要通过MC原本的机制发送消息，如果是，则直接跳过我们下面的代码，进入MC原版的流程。然后是判断用户的输入是待发送的消息，还是输入给mod的指令。指令系统的设计将在下一节描述。最后是发送公屏消息，在原版MC中，消息发送给服务器，服务器转发给包括发送者在内的所有玩家，这个时候玩家自己也能看到消息。但在我们的Mod中，玩家并不与自己建立连接，因此把消息发送给其他人后，还要自己把发送的消息添加到聊天框中。关于使用MC原本机制发送消息的判断，这里引入了如下机制： @JvmStatic private val plainMessagePendingQueue = ConcurrentHashMap<String, Int>() private fun addPlainMessage(message: String) { plainMessagePendingQueue.compute(message) { _, oldCount -> // add 1 if is old message, otherwise count as 1 for new messages oldCount?.let { it + 1 } ?: 1 } } @JvmStatic fun shouldSendPlain(message: String): Boolean { var isFound = false plainMessagePendingQueue.compute(message) { _, oldCount -> oldCount?.let { count -> isFound = true if (count <= 1) { // delete this null } else { // count down by 1 count - 1 } } } return isFound } @JvmStatic fun sendPlainMessage(message: String) { addPlainMessage(message) MinecraftClient.getInstance()?.player?.sendChatMessage(message, null) ?: run {// failed to send message logger.warn { "Failed to get current player, thus, failed to send message: $message" } shouldSendPlain(message) // remove the count } }首先是一个plainMessagePendingQueue队列，其实是一个Map，它记录了待发送消息的次数。例如玩家使用命令发送了5次“哈哈哈”，那只需要记录一个“哈哈哈”的键，其值为5即可。每次查询“哈哈哈”是否应当通过原版机制发送，都会引发计数器减一，减为0后直接删除。这一逻辑是通过sendPlainMessage调用addPlainMessage方法来保证的。这里的时序是这样的：用户通过指令发送一条消息，指令将待发送的消息添加到队列中，然后调用MC原版发送消息的逻辑，原版逻辑执行到我们的注入代码时，注入代码检测到待发送的消息在队列中，于是跳过注入，将控制权交还给原版机制。这里的重点在于我们总是先添加队列，再发送消息。而使用并发安全的Map则是因为触发指令的顺序是不固定的。指令系统MC本身有一套指令系统，可以直接拿来用，大多数模组也是这么用的。但这个是客户端模组，也就是说，你注册了原版的MC指令，但加入了服务器，而服务器不知道这个指令，那只有在单机下才能识别指令，加入服务器后指令就不可用了。我们当然不希望这样。但考虑到原版MC的指令也不过是一条特殊前缀的聊天消息，既然我们能够拦截聊天消息，为什么不能一块处理指令？我们的指令以!开头。例如!msg player123 hahaha是让mod发送一条hahaha给玩家player123。当然，我们可以用when和字符串操作来匹配所有指令，但是这样的话整个mixin代码就会非常臃肿。和Lang一样，我们期望一种树状的指令（指令还可以有子指令，例如!i2p status这种，i2p包含了所有I2P相关的指令，而status则是查询状态的子指令）。但与Lang不同，Lang是一种静态资源，它的键在编译时期就已经固定在对应的json文件中了，所以应当使用sealed class来形成一棵静态的树；而指令则更像是一种动态产生的结构，换句话说，它会更频繁的修改，所以这里使用了类似DSL的方式去构造指令树。描述指令的类很简单：class MCACommand( val commandName: String, val description: String, val parameterList: List<Pair<String, String>> = emptyList(), val subCommands: Set<MCACommand> = emptySet(), actionInternal: ((String) -> Unit)? = null ) { private val action: (String) -> Unit = actionInternal ?: { message -> // ...... } init { require(!commandName.contains(" ")) { "Command name contains space" } require(commandName != "help") { "Command help is a internal command" } if (actionInternal == null) { // contains sub command require(subCommands.isNotEmpty()) { "Commands without action should have subcommands" } } } operator fun invoke(parameter: String) = action.invoke(parameter) override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is MCACommand) return false if (commandName != other.commandName) return false return true } override fun hashCode(): Int { return commandName.hashCode() } }每个指令有5个参数：指令名、描述、参数列表（参数名、描述）、子命令集和动作。每个指令是否相等是看指令名的，因为不同的指令不会出现在同一层，我们只需要保证一个set中的指令是唯一的即可。通过init方法可以看到，这些参数有一些限制：首先指令名不能包含空格；然后是指令名不能为help，这个指令稍后会说，它是一个自动生成的帮助命令；最后如果action为空（它是一个中间指令，例如!i2p status中的i2p），那么他的子指令必须不为空。如果构造的时候动作为空，也就是说这个指令是一个中间指令，那么我们给他一个默认的动作来寻找对应的子指令： private val action: (String) -> Unit = actionInternal ?: { message -> var firstSpace: Int = message.indexOf(' ') if (firstSpace == -1) { firstSpace = message.length } val command = message.substring(0, firstSpace) subCommands.find { it.commandName == command } ?.let { it(message.substring(firstSpace).trimStart()) } ?: run { ChatHelper.addSystemMessageToChat(Text.literal("Unknown subcommand: $command")) } }这里提取消息中的第一个命令，也就是第一个空格前面的内容，然后按照寻找与之匹配的子指令，然后把余下的部分交给子指令处理。也就是说，如果模组收到一个i2p status，那么会把i2p对应的指令匹配出来，把剩余的status交给I2P指令去处理，形成一个递归调用。考虑到指令应该不会太长，所以大概率不会爆栈。接下来我们看看DSL语法。最终我们要得到一系列一级命令，而这些命令可能又有自己的子命令，二级三级等等，但那是一级命令本身去调用处理的，我们需要解析用户输入的消息，并找到这些一级命令。我们可以直接setOf(...)，但是这样的话DSL不太好写，因为这里面可以执行任何命令，而我们想让DSL只在特定范围内执行。为了解决这个问题，我们可以用buildSet {...}。与setOf不同之处在于，前者提供了一种操作Set本身的能力。使用setOf只是简单的把参数变成一个Set，而buildSet则传给你一个MutableSet，这个就是最终的结果，你可以在lambda里面增加、删除、对这个set做任何操作，因此我们可以在里面注册命令： private fun MutableSet<MCACommand>.registerCommand( commandName: String, description: String, parameterList: List<Pair<String, String>>, action: (String) -> Unit ) = require( this.add( MCACommand( commandName = commandName, description = description, parameterList = parameterList, actionInternal = action ) ) ) { "Duplicated command name: $commandName" }这里我们注册了一个命令，使用起来类似这样： registerCommand( commandName = "foo", description = "test", parameterList = listOf() ) { logger.info { "Bar!" } }当然，我们也可以注册子命令： private fun MutableSet<MCACommand>.registerCommand( path: String, description: String, block: MutableSet<MCACommand>.() -> Unit ) = require( this.add( MCACommand( commandName = path, description = description, subCommands = buildSet(block) ) ) ) { "Duplicated command name: $path" }用起来是这样的： registerCommand( path = "i2p", description = "I2P related operations" ) { registerCommand( commandName = "regen", description = "generate a new i2p identity", parameterList = listOf() ) { ConfigFile.ModConfig.use { this.discardI2PKey() } ConfigFile.ModConfig.save() ChatHelper.addSystemMessageToChat( Text.literal("Done. Please restart your game.") ) } }值得注意的是，注册中间命令的时候我们也用到了buildSet，也就是说，中间命令的子命令既可以是中间命令，也可以是具有动作的实际命令。只需要两个类似DSL的函数，我们就可以方便且灵活的定义命令了。很优雅，不是么？具体的命令就不一一介绍了，有兴趣的话可以参考这里的代码。这些指令提供了连接命令和将连接命令复制到剪切板的功能。接下来介绍一下处理命令的逻辑： @JvmStatic fun handleCommand(rawMessage: String) { if (!isCommand(rawMessage)) return val message = rawMessage.removePrefix(commandPrefix) // find first word var firstSpace: Int = message.indexOf(' ') if (firstSpace == -1) { firstSpace = message.length } val command = message.substring(0, firstSpace) if (command == "help") { // ...... } else { commands.find { it.commandName == command } ?.let { it(message.substring(firstSpace).trimStart()) } ?: run { ChatHelper.addSystemMessageToChat(Text.literal("Unknown command: $command")) } } }首先需要确保传入的消息是!开头的，去掉开头的叹号，拿到第一个命令。如果是help的话，我们需要特殊处理一下。如果不是的话，那我们直接从命令列表里找命令，然后开始递归调用。对于help命令，处理逻辑如下： val path = mutableListOf<MCACommand>() var searchScope = commands.toList() var rest = if (firstSpace >= message.length) "" else message.substring(firstSpace + 1) while (searchScope.isNotEmpty() && rest.isNotEmpty()) { val c = searchScope.filter { rest.startsWith(it.commandName) }.maxByOrNull { it.commandName.length } if (c != null) { path.add(c) searchScope = c.subCommands.toList() rest = rest.substring(c.commandName.length).trim() } else { val commandFullPath = path.joinToString(separator = " ", prefix = commandPrefix) { it.commandName } ChatHelper.addSystemMessageToChat( Text.literal( "Command $commandFullPath don't have sub command ${rest.split(" ")[0]}" ) ) return } } val commandFullPath = path.joinToString(separator = " ", prefix = commandPrefix) { it.commandName } if (path.isEmpty()) { // a !help with no parameter ChatHelper.addSystemMessageToChat(Text.literal( "Commands:\n" + commands.joinToString("\n") { " " + it.commandName + ": " + it.description } )) } else { if (path.last().subCommands.isNotEmpty()) { ChatHelper.addSystemMessageToChat(Text.literal( "Sub commands for $commandFullPath:\n" + path.last().subCommands.joinToString("\n") { " ${it.commandName}: ${it.description}" } )) } else { val c = path.last() ChatHelper.addSystemMessageToChat( Text.literal( "Command $commandFullPath ${ c.parameterList.joinToString(" ") { "<${it.first}>" } }: " + c.description + "\n" + c.parameterList.joinToString("\n") { " ${it.first}: ${it.second}" } ) ) } }首先有个path来保存搜索的路径。!help会打印所有可用的一级命令，而!help i2p会打印出i2p命令的所有子命令，而!help i2p status会打印关于status子命令的帮助。但status并不知道它之前的命令是什么，因此我们需要想办法保存下来。searchScope是当前搜索的范围，默认从一级命令开始搜索，如果匹配到了二级命令，那就用二级命令的子命令作为搜索范围，以此类推。rest则是剩余的消息，例如!help i2p status的rest首先是i2p status，在搜索到i2p后，rest就变成了status，最后变成空字符串，搜索结束。之后的代码就相当于把递归调用转换成了循环，不断搜索匹配到的命令，并保存到path中。如果中间没有找到命令，则直接提示失败并提前返回。搜索结束后，如果path是空的，则直接打印所有一级命令即可。如果不是的话，取决于最后一个命令的类型：如果是一个具体的命令，那就打印它的参数列表和描述；如果是一个中间命令，那就打印它所有的子命令。至此，让这个模组正常运转的功能就都已经实现了。使用模组的时候可以加入一个服务器，然后使用!gen命令，就会把对应的连接命令复制到剪切板，你可以通过微信等方法发送给你的朋友，然后你的朋友直接在聊天框中贴入，这个会作为!connect命令执行。这样你和朋友就可以连接并发送消息了。借助PEX，你和你的朋友会自动交换双方已经连上的节点，从而省去了和其他玩家一一交换connect命令的麻烦。同时，模组会定期保存已连接的会话地址。当你下次上线时，模组会自动从缓存中找到你朋友的地址并连接——再也不需要!connect命令了。按照设计，这个模组随着使用时间增长，体验也会变得越来越好。增强体验话是这么说，但有没有可能让体验更进一步呢？玩家列表比如说多人模式下按Tab，可以列出玩家列表。能不能在这个玩家列表中标注出谁与我相连了呢？当然可以！阅读了一些mojang的屎山之后，我发现可以借助PlayerListHud类的getPlayerName方法来实现。这个方法原本是针对每个待显示玩家名称进行装饰的。也就是说，每一个玩家名在显示前都会经过这个方法处理，得到的就是即将显示在玩家列表中的名字。修改如下：@Mixin(PlayerListHud.class) public abstract class MixinPlayerListHud { @Inject(method = "getPlayerName", at = @At("RETURN"), cancellable = true) private void onGetPlayerName(PlayerListEntry entry, CallbackInfoReturnable<Text> cir) { var text = cir.getReturnValue(); var realName = entry.getProfile().getName(); if (realName.equals(MinecraftHelper.getCurrentUsername())) { cir.setReturnValue(Text.translatable("%s (%s)", text, Text.literal("self"))); cir.cancel(); return; } try { // potential error: getPeer() not initialized var peerSession = ClientModEntry.getPeer().dumpSessions().stream() .filter(s -> !s.isClosed() && s.useContextSync(c -> realName.equals(c.getUsername()))) .findAny(); if (peerSession.isPresent()) { var source = peerSession.get().useContextSync(MCAContext::getSessionSource); var direction = ""; switch (source) { case CLIENT -> direction = "out"; case SERVER -> direction = "in"; } cir.setReturnValue(Text.translatable("%s (%s)", text, Text.literal(direction))); cir.cancel(); } } catch (Throwable ignored) { } } }这里写的比较粗糙，因为我默认用户名就是玩家名，但像是Hypixel这种会修改玩家名的，我不知道用起来是什么样的，大概率会不好使吧。总之这段代码的做用就是判断给定的用户名是否与已知的会话匹配，如果匹配则标注出方向（出入站，即谁先发起的连接），不匹配的话就什么也不做。这样当我们按下Tab时，就可以直接从玩家列表中看到谁与我们相连了。聊天内容检测地址前面说过，玩家可以通过!gen命令获得连接命令，然后发送给其他玩家。但有时候大家在一起玩，还没有好到要加微信的地步，这怎么办呢？我们可以从过MC本身发送这个命令，但是70个字符长的地址，MC又没有从聊天框复制内容的能力，你指望用户一个一个敲？这不好吧。如果我们可以监听聊天消息，在搜索到地址的时候提示用户，这岂不是更好一些？又看了很久mojang的屎山，我发现MessageHandler类的onChatMessage可以用于截获收到的消息。这里我们不对消息进行修改，只是进行监听：@Mixin(MessageHandler.class) public abstract class MixinMessageHandler { @Inject(method = "onChatMessage", at = @At("RETURN")) private void onChatMessage(SignedMessage message, MessageType.Parameters params, CallbackInfo ci) { var text = message.signedBody().content().plain(); if (text.contains(".b32.i2p")) { // the b32 address: {52 chars}.b32.i2p var prefix = text.split("\\.b32\\.i2p")[0]; if (prefix.length() >= 52) { var b32 = prefix.substring(prefix.length() - 52) + ".b32.i2p"; if (b32.equals(ClientModEntry.getPeer().getMyB32Address())) { // is ourselves return; } ChatHelper.addSystemMessageToChat(Text.translatable( "%s sent a b32 address in chat, %s", params.name(), Text.literal("click [here] to connect").fillStyle(Style.EMPTY.withClickEvent( new ClickEvent( ClickEvent.Action.SUGGEST_COMMAND, "!connect " + b32 ) )) )); } } } }如果我们在聊天消息中发现了I2P的b32地址，也就是包含在connect命令里面的地址，那我们就插入一条消息，告诉用户。并且利用ClickEvent功能让用户在点击消息的时候，自动生成一个connect命令，用户直接回车就可以连接。岂不美哉？后记这一个系列到此为止终于完结了。可喜可贺，可喜可贺。接下来我可能会玩一玩kotlinx coroutine相关的东西，也可能深入一下I2P，谁知道呢。我对未来之事无法给出任何保证，因为我是好奇心驱动的。也许过几天看到了更加感兴趣的事情也说不定。总之，感谢大家的阅读，我们下一篇文章见~（考虑到我每月一更的习惯，可能得明年1月了，LOL）-全文完-
【代码札记】从零开始的点对点聊天系统 P4
本文介绍如何将前文的i2p-p2p-chat接入Minecraft中。准备工作前文中我们完成了i2p-p2p-chat的编写，这个库封装了所有基于I2P的点对点通讯的操作。本文中，我们需要在Minecraft这部分做出修改。首先是要找到拦截和注入消息的位置，拦截到的消息将有我们的模组发送，而模组接收到的消息需要注入到Minecraft原本的聊天框中。然后就是想办法引入一套命令系统：公屏聊天当然不需要，但我们还得能够私聊，况且我们还得想办法让用户分享他们自己的地址。最后我们还得搞一套简单的配置系统，用来持久化地存储我们的私钥和曾经见过的节点。配置系统配置系统应该是整个模组最简单的部分了。我们的模组有两个需要存储的配置：一个是控制模组行为的配置文件；另一个则是存储见过的节点。前者无需过多解释，对于后者，建立这种缓存是为了当我们在服务器中遇到以前见过的用户名，则可以直接向过去见过的地址发起连接。因为我们的隧道私钥是随Minecraft实例持久化保存的，除非用户选择刷新私钥或者更换了实例，在大多数情况下，我们应该可以通过旧的地址直接连上用户，从而跳过了PEX和手工分享地址。这也使得整个mod的体验随着使用时间增加而变好。配置文件的格式当然是JSON，虽然后者用KV数据库性能会更好，但是我的宗旨是能不用数据库就不用数据库，最好一个map就能解决。两个配置的模型定义如下：@Serializable class MCAConfig( @Serializable(with = ByteArrayAsBase64Serializer::class) private var _i2pKey: ByteArray = I2PHelper.generateDestinationKey(), @Serializable(with = DurationSerializer ::class) var pexInterval: Duration = Duration.ofSeconds(45), @Serializable(with = DurationSerializer::class) var lastSeenUpdateInterval: Duration = Duration.ofMinutes(3), @Serializable(with = DurationSerializer::class) var sessionRemoverInterval: Duration = Duration.ofMinutes(2), @Serializable(with = DurationSerializer::class) var autoConnectInterval: Duration = Duration.ofSeconds(15), ) { val i2pDestinationKey get() = _i2pKey fun discardI2PKey() { _i2pKey = I2PHelper.generateDestinationKey() } } @Serializable class PeerRepoEntry( @Serializable(with = DestinationSerializer::class) var destination: Destination, @Serializable(with = ZonedDateTimeAsTimestampSerializer::class) private var _lastSeen: ZonedDateTime ) { val lastSeen get() = _lastSeen fun seen(dest: Destination) { destination = dest _lastSeen = ZonedDateTime.now() } } 其中MCAConfig就是控制行为的配置文件了。里面的_i2pKey就是我们I2P隧道的私钥，他的类型是ByteArray，但实际用的时候需要转换成Destination，于是就有了后面的i2pDestinationKey，当然，为了实现刷新功能，还有discardI2PKey方法。其他的各种Interval则决定了定时任务的执行间隔，比如多久进行一次PEX，多久记录一次最后见到的节点信息，多久进行一次会话检查（关闭不在同一个服务器中的会话），多久尝试一次从缓存中连接节点。下面的PeerRepoEntry就是一个节点缓存条目。它记录了节点的地址和最后一次见到的时间。这里本来想实现一个简单的最久淘汰策略，但是后来给忘了。不过忘了也不会有太严重的后果，一个条目不会超过1KB，因此见过一千个玩家才占用1MB空间，见过一百万个玩家才占用1GB空间。这个文件可以随时被删掉，唯一的影响就是下一次你见到这个玩家，mod不会自动连接了。有了模型，我们得想办法提供一个与配置文件交互的接口。这里我们选择使用sealed class来实现。首先我们需要一个能够输出多行的Json：private val json = Json { prettyPrint = true ignoreUnknownKeys = true encodeDefaults = true }漂亮打印使得用户能够以人类可读的形式修改配置；而忽略未知的键则有助于提供额外的兼容性：让我们在后续版本废弃了一些选项，JSON并不会因为这些已经废弃的选项而报错；最后的编码默认值则是将我们的默认值打印到配置文件中，以便用户修改。我们的sealed class定义如下：sealed class ConfigFile<T>( private val filename: String, private val serializer: KSerializer<T>, defaultValue: T, ) { /** * Current value. It use default value unless we overwrite it when [loadOrCreate] * */ private var value: T = defaultValue /** * The actual [Path] for this config file. * */ private val configPath = FabricLoader.getInstance().configDir.resolve(this.filename) init { reload() // write the latest save() } /** * Load from disk, create with default value if not exist. * */ private fun loadOrCreate(): T { // ensure we don't read and write against the same config at the same time synchronized(this) { return if (Files.exists(configPath)) { // read it json.decodeFromString(serializer, configPath.readText()) } else { //create it @Suppress("BlockingMethodInNonBlockingContext") Files.createDirectories(configPath.parent) configPath.writeText(json.encodeToString(serializer, value)) value } } } /** * Use the config sync. * */ fun <R> use(block: T.() -> R): R { synchronized(this) { return block(value) } } /** * Discard changes and reload latest from disk. * Same as [loadOrCreate] * */ fun reload() { value = loadOrCreate() } /** * Save the current value info disk. * */ fun save() { synchronized(this) { configPath.writeText(json.encodeToString(serializer, value)) } } }ConfigFile有三个构造函数：文件路径、序列化器和一个默认值。每个实例都有一个value，默认是默认值。在实例化过程中，先尝试从磁盘中载入配置，如果配置存在，则从配置中解析并代替默认值；如果文件不存在，则将默认值的内容写入文件，并返回默认值。除了常用的重载和保存，还提供了一个线程安全的use方法。这个方法保证对于value的访问是同步的，这样我们就不必为忘记加锁而烦恼了。具体的两个实例如下： object ModConfig : ConfigFile<MCAConfig>( filename = "minecraft-chat-alternative/config.json", serializer = MCAConfig.serializer(), defaultValue = MCAConfig() ) @Suppress("OPT_IN_USAGE") object PeerRepo : ConfigFile<MutableMap<String, PeerRepoEntry>>( filename = "minecraft-chat-alternative/peer_repo.json", serializer = object : KSerializer<MutableMap<String, PeerRepoEntry>> { val delegate = MapSerializer(String.serializer(), PeerRepoEntry.serializer()) override val descriptor: SerialDescriptor = SerialDescriptor( "MutableMap<String, PeerRepoEntry>", delegate.descriptor ) override fun deserialize(decoder: Decoder): MutableMap<String, PeerRepoEntry> { return decoder.decodeSerializableValue(delegate).toMutableMap() } override fun serialize(encoder: Encoder, value: MutableMap<String, PeerRepoEntry>) { encoder.encodeSerializableValue(delegate, value) } }, defaultValue = mutableMapOf() ) { /** * Update from session list. * */ fun updateLastSeenFromSessions(sessions: List<PeerSession<MCAContext>>) { sessions.forEach { s -> if (s.isClosed()) return@forEach val username = s.useContextSync { username } ?: return@forEach this.use { if (containsKey(username)) get(username)!!.seen(s.getPeerDestination()) else put(username, PeerRepoEntry(s.getPeerDestination(), ZonedDateTime.now())) } } } }MCAConfig那个挺简单的，后面那个PeerRepo就有一些复杂了。对于Map类型，在Java中没有过多的区分，但kotlin这边会区分是只读Map还是可修改的MutableMap。而从json反序列化出来的都是Map（因为内置的MapSerializer）要解决这个问题，有两种方法：一种是实现一个自定义序列化器，底层委托给MapSerializer；另一种则是修改sealed class，加一个mapper，对反序列化结果进行处理。按道理来说，后者应该更好一些（但可能需要更复杂的类型，例如KSerializer和(U) -> T），但不知道怎么回事，当时我选了第一种方法。会话上下文虽然有了配置系统，但我们还没办法直接启动节点：我们还需要一个上下文来保存会话状态。好在这个mod并不需要什么复杂的状态：class MCAContext( override val sessionSource: SessionSource ) : SessionContext { var username: String? = null private set override val nickname: String? by this::username override var peerInfo: PeerInfo? = null set(value) { username = value?.getUsername() field = value } private var authAccepted: Boolean = false override fun onAuthAccepted() { authAccepted = true } override fun isAccepted(): Boolean = authAccepted }这里面有个username，其实用nickname也无妨，但是我觉得username对于MC来说更贴切一些，于是使用属性代理把对nickname的访问变成对username的访问。证书签名与验证等一下！你以为有了上下文就可以启动节点了？在此之前我们还要处理一个问题：怎么用Mojang的证书来签名我们的PeerInfo，以及用它来验证别人发来的PeerInfo呢？签名倒是好办：private fun createPeerInfo(peer: Peer<MCAContext>): PeerInfo { val (session, profileKeys) = MinecraftClient.getInstance() ?.let { it.session to it.profileKeys } ?: error("Minecraft instance is null") val (username, uuid) = session ?.let { (it.username ?: error("Cannot get current username")) to it.uuidOrNull } ?: error("Minecraft session is null") val (pubKey, signer) = profileKeys ?.let { (it.publicKey.orElse(null) ?: error("Cannot get player's public key") ) to (it.signer ?: error("Cannot get signer")) } ?: error("Cannot get profile key") val info = mapOf<String, String>( PeerInfo.INFO_KEY_DEST to peer.getMyDestination().toBase64(), "minecraft.username" to username, "minecraft.profile_uuid" to (uuid?.toString() ?: error("Player's uuid is null")), ) val dataToSign = PeerInfo.getDataForSign(info) val signature = signer.sign(dataToSign) return PeerInfo( info = info, signatureBase64 = Base64.getEncoder().encodeToString(signature), publicKeyBase64 = pubKey.toBase64() ) }你会发现这里除了用户名，我们还额外加了一个profile uuid。这个东西是你每次使用正版登录，mojang随机给你分配的一个uuid，并不保证和你的账号绑定，但一般情况下是一一对应的，而且可以凭这个uuid在mojang的服务中代表你这个正版账号。private fun verifyPeerInfo(peerInfo: PeerInfo): Boolean { try { val profileUUID = peerInfo.info["minecraft.profile_uuid"] ?.let { UUID.fromString(it) } ?: return false val publicKeyData = peerInfo.getPublicKeyBytes().paresPublicKeyData() val pubKey = MinecraftClient.getInstance()?.let { PlayerPublicKey.verifyAndDecode( it.servicesSignatureVerifier, profileUUID, publicKeyData, PlayerPublicKey.EXPIRATION_GRACE_PERIOD ) } ?: error("Minecraft instance is null") val verifier = pubKey.createSignatureInstance() val payload = peerInfo.getInfoBencodeBytes() val signature = peerInfo.getSignatureBytes() return verifier.validate(payload, signature) } catch (_: Throwable) { // if any error, invalid peer info return false } }你会发现，在验证过程中我们调用了MC的代码，这里面用到了uuid，而非用户名。有了签名和验证节点信息的能力，我们就可以启动节点了，对吧？i18n与注入我知道你很急，但是你先别急，一会儿有你急的。我们启动节点之前要提供一个Handler，在接收到TextMessage的时候需要调用我们的代码。而我们的代码需要把接收到的消息注入到MC原本的聊天框中。但是我们不能楞把消息插到聊天框里，我们想要一种格式，例如：[I2p] Hahaha this is a message!类似这种，我们希望来自I2P的消息与来自MC的消息区分开。实现这种效果最简单的方式就是用String format，但是直接把格式写死在代码里好像不太好，例如别人发送私信给我们的时候：[I2P] whispers to you: This is a private message!!!如果是中文的话，我们想翻译成：[I2P] 私信对你说：This is a private message!!!i18n这显然是硬编码格式做不到的。但好在Mojang也考虑到了这一点，引入了Lang。我们可以在en_us.json中写英文的格式，在zh_cn.json中写中文的格式，只要符合对应的语言代码，我们可以翻译成任何一种语言：{ "chat.mca.system": "[MCA] %s", "chat.mca.system.narrate": "MCA: %s", "chat.mca.i2p.broadcast": "[I2P] <%s> %s", "chat.mca.i2p.broadcast.narrate": "%s on I2P says %s", "chat.mca.i2p.incoming": "[I2P] <%s> whispers to you: %s", "chat.mca.i2p.incoming.narrate": "%s on I2P whispers to you: %s", "chat.mca.i2p.outgoing": "You whisper to [I2P] <%s>: %s", "chat.mca.i2p.outgoing.narrate": "You whisper to %s on I2P: %s", "text.mca.system.connection.incoming": "Accept connection from %s", "text.mca.system.connection.outgoing": "Connected to %s", "text.mca.system.connection.disconnected": "Disconnected by %s, because %s", "text.mca.system.connection.socket_closed": "Socket with %s closed", "text.mca.system.error.general": "Error occurred, see log for more info", "text.mca.system.error.peer_stopped": "I2P Peer stopped. This should never have happened.", "text.mca.system.error.on_error_reply": "Player %s replies an error to your previous request. See log for more info." }这个JSON看起来挺结构化的，但是用起来的话完全就是个字符串。例如我需要引用收到私信的格式时，我需要在代码中指定chat.mca.i2p.incoming这个键。这种字符串没法保证这个翻译键存在，而这个键明显有一种树状结构，所以，老规矩，sealed class：sealed class Lang( vararg path: String ) { private val fullPath: String = path.joinToString(".") protected fun translate(vararg args: Any): MinecraftText { require(Language.getInstance().get(fullPath) != fullPath) { "Missing translation key: $fullPath" } return MinecraftText.translatable(fullPath, *args) } }这个sealed class接收一个path数组，用来表示完整的key。例如chat.mca.i2p.incoming就变成了["chat", "mca", "i2p", "incoming"]，而后者可以通过树形结构产生，例如表示Chat类的有一个chat.mca前缀，而Chat的子类有i2p的前缀，借助vararg，我们可以这么写：Lang("chat.mca", *path)，在Chat类实例化的过程中，把自己的前缀加到前面，然后把子类的路径放在后面，有点类似递归的意思。而那个translate方法则起到了一个检查的效果，如果引用了未找到的翻译键，那么直接报错引发崩溃。具体来说，Lang下面有两个子类，一个是直接显示到聊天框里面的chat.mca.*，另一类是作为聊天消息的一部分解析的text.mca.*。对于聊天类消息： sealed class ChatMessage( vararg path: String ) : Lang("chat.mca", *path) { sealed class SystemMessage( vararg path: String ) : ChatMessage("system", *path) { object Text : SystemMessage() object Narrate : SystemMessage("narrate") fun translate(message: MinecraftText): MinecraftText = super.translate(message) } sealed class I2PMessage( vararg path: String ) : ChatMessage("i2p", *path) { sealed class Broadcast( vararg path: String ) : I2PMessage("broadcast", *path) { object Text : Broadcast() object Narrate : Broadcast("narrate") fun translate(username: MinecraftText, content: MinecraftText): MinecraftText = super.translate(username, content) } sealed class Incoming( vararg path: String ) : I2PMessage("incoming", *path) { object Text : Incoming() object Narrate : Incoming("narrate") fun translate(username: MinecraftText, content: MinecraftText): MinecraftText = super.translate(username, content) } sealed class Outgoing( vararg path: String ) : I2PMessage("outgoing", *path) { object Text : Outgoing() object Narrate : Outgoing("narrate") fun translate(username: MinecraftText, content: MinecraftText): MinecraftText = super.translate(username, content) } } }这里面基本上就是对Lang的建模。对于聊天消息，除了显示成文本的键，例如chat.mca.i2p.broadcast，还有对应的需要讲述人念出来的键chat.mca.i2p.broadcast.narrate。对于chat.mca.i2p.broadcast，作为一个可以被引用的键，它应该是Object，但它底下又有一个narrate，它得是sealed class。为了解决这个问题，引入了一个名叫Text，但path为空的object，从而保证Text的路径和父对象的路径相同，而与Text同级的Narrate则用于.narrate后缀。此外每个object还都有一个translate方法，这个调用了超类的translate，保证对引用键的检查，同时用代码约束了参数。类似地，具有text.mca.*前缀的TextMessage也是如此，只不过没有了narrate，更加简单一些： sealed class TextMessage( vararg path: String ) : Lang("text.mca", *path) { sealed class SystemMessage( vararg path: String ) : TextMessage("system", *path) { sealed class Connection( vararg path: String ) : SystemMessage("connection", *path) { object Incoming : Connection("incoming") { fun translate(username: MinecraftText): MinecraftText = super.translate(username) } object Outgoing : Connection("outgoing") { fun translate(username: MinecraftText): MinecraftText = super.translate(username) } object Disconnected : Connection("disconnected") { fun translate(username: MinecraftText, reason: MinecraftText): MinecraftText = super.translate(username, reason) } object SocketClosed : Connection("socket_closed") { fun translate(username: MinecraftText): MinecraftText = super.translate(username) } } sealed class Error( vararg path: String ) : SystemMessage("error", *path) { object General : Error("general") { fun translate(): MinecraftText = super.translate() } object PeerStopped : Error("peer_stopped") { fun translate(): MinecraftText = super.translate() } object PeerReportedError : Error("on_error_reply") { fun translate(username: MinecraftText): MinecraftText = super.translate(username) } } } }当然，这里需要注意的就是不要引用错父类，不然整个路径就都变了。此外，定义sealed class不一定要定义在父类里面，但这样有一个好处，使得我们可以用类似键的语法来使用：Lang.ChatMessage.I2PMessage.Outgoing.Text.translate(......)这就像是chat.mca.i2p.outgoing，我觉得很好。消息注入有了这个方便的i18n接口，我们就可以考虑消息注入了。经过一番摸索，可以使用如下代码插入一条消息： fun addRawMessage(message: Text, narrateMessage: Text) { val minecraft = MinecraftClient.getInstance()!! minecraft.inGameHud?.chatHud?.addMessage(message) minecraft.narratorManager?.narrateChatMessage { narrateMessage } }当然，narrator是可选的，不过既然原版MC都做了，我们也不应该忽略。每个人都应当能够享受游戏的乐趣，对吧？这个方法只是将一条消息插入到聊天框中，为了应用格式，我们可以针对不同的消息类型使用不同的方法，例如： fun addSystemMessageToChat(content: Text) { val textChatComponent = Lang.ChatMessage.SystemMessage.Text .translate(content) val narrateChatComponent = Lang.ChatMessage.SystemMessage.Narrate .translate(content) addRawMessage(textChatComponent, narrateChatComponent) } fun addBroadcastMessageToChat(username: String, content: Text) { val textChatComponent = Lang.ChatMessage.I2PMessage.Broadcast.Text.translate( Text.literal(username).fillStyle( Style.EMPTY.withClickEvent( ClickEvent( ClickEvent.Action.SUGGEST_COMMAND, String.format("!dm %s ", username) ) ) ), content ) val narrateChatComponent = Lang.ChatMessage.I2PMessage.Broadcast.Narrate.translate( Text.literal(username), content ) addRawMessage(textChatComponent, narrateChatComponent) }诸如此类的。启动节点好了，现在可以考虑启动节点了。我们可以在mod加载时启动节点并启动各种定时任务：@Environment(EnvType.CLIENT) object ClientModEntry : ClientModInitializer { private val logger = KotlinLogging.logger { } private val json = Json { ignoreUnknownKeys = true serializersModule = getSerializersModule() } private val threadPool = I2PHelper.createThreadPool(Runtime.getRuntime().availableProcessors()) private var peer: Peer<MCAContext>? = null @JvmStatic fun getPeer(): Peer<MCAContext> = peer ?: error("Peer not initialized") override fun onInitializeClient() { logger.info { "Hello!" } logger.info { "Creating peer..." } peer = createPeer( json = json, username = MinecraftHelper.getCurrentUsername()!!, protocolName = "minecraft-chat-alternative-protocol-20221121", destinationKey = ConfigFile.ModConfig.use { i2pDestinationKey }, threadPool = threadPool, pexInterval = ConfigFile.ModConfig.use { pexInterval } ) logger.info { "Peer created!" } // start doing things with that peer // update last seen runTask({ lastSeenUpdateInterval }) { peer -> ConfigFile.PeerRepo.updateLastSeenFromSessions(peer.dumpSessions()) ConfigFile.PeerRepo.save() } // remove invalid session runTask({ sessionRemoverInterval }) { peer -> peer.disconnect("not in same server") { val username = it.useContextSync { username } ?: return@disconnect false username !in MinecraftHelper.getPlayerList() } } // connect from peer repo runTask({ autoConnectInterval }) { peer -> val alreadyConnected = peer.dumpSessions().mapNotNull { it.useContextSync { username } } ConfigFile.PeerRepo.use { entries.filter { it.key in MinecraftHelper.getPlayerList() && it.key !in alreadyConnected }.forEach { entry -> I2PHelper.runThread { try { peer.connect(entry.value.destination) } catch (_: Throwable) { } } } } } } private fun runTask(duration: MCAConfig.() -> Duration, block: (Peer<MCAContext>) -> Unit) { I2PHelper.runThread { val peer = getPeer() val interval = ConfigFile.ModConfig.use { duration() }.toMillis() while (!peer.isClosed()) { MinecraftHelper.sleep(interval) block(peer) } logger.warn { "Peer stopped! Trigger crash" } MinecraftHelper.crash(IllegalStateException(Lang.TextMessage.SystemMessage.Error.PeerStopped.translate().string)) } } }这里我们启动节点，并拉起了一系列定时运行的任务，例如每隔一段时间就记录当前已经连接的会话；每隔一段时间与不在玩家列表中的玩家断开连接；定时从缓存的列表中寻找玩家地址并连接。在创建节点时，主要的Handler有这些： // for AuthRequest createMessageHandlerWithReply { _, session, messageId, payload: AuthRequest, _ -> if (verifyPeerInfo(payload.peerInfo)) { val username = payload.peerInfo.getUsername() if (username in MinecraftHelper.getPlayerList()) { logger.info { "Authed ${session.getDisplayName()} as $username" } ChatHelper.addSystemMessageToChat( Lang.TextMessage.SystemMessage.Connection.Incoming .translate(Text.literal(payload.peerInfo.getUsername())) ) AuthAcceptedReply(messageId) } else { // not in the same server AuthenticationFailedError("Not in the same server", messageId) } } else { AuthenticationFailedError("Verification failed", messageId) } }, // for AuthAcceptedReply createMessageHandlerNoReply { _, session, _: AuthAcceptedReply, _ -> ChatHelper.addSystemMessageToChat( Lang.TextMessage.SystemMessage.Connection.Outgoing .translate(Text.literal(session.useContextSync { username })) ) }, // for TextRequest createMessageHandlerWithReply { _, session, messageId, payload: TextMessageRequest, _ -> val username = session.useContextSync { username } ?: error("Unauthorized message") when (payload.scope) { "broadcast" -> ChatHelper.addBroadcastMessageToChat( username, Text.literal(payload.content) ) "whisper" -> ChatHelper.addIncomingMessageToChat( username, Text.literal(payload.content) ) else -> logger.warn { "Unknown message scope '${payload.scope}', content: ${payload.content}" } } NoContentReply(messageId) }, // for ByeRequest createMessageHandlerNoReply { _, session, payload: ByeRequest, _ -> ChatHelper.addSystemMessageToChat( Lang.TextMessage.SystemMessage.Connection.Disconnected .translate( Text.literal(session.getDisplayName()), Text.literal(payload.reason) ) ) },大多数回复都是在聊天框中打印对应的提示信息，比如收到了谁的消息，谁断开了连接，我们连上了谁，谁连上了我们，之类的。在验证部分，除了确保对方是正版账号之外，还得确认他确实和我们处于同一个世界/服务器，不然真就跨服聊天了。后续考虑到篇幅，这一篇文章中引用了不少代码，而余下的内容还有不少。主要是拦截消息和一套指令系统，还有一些提升体验的mixin，这些我打算留到P5再写。那么这篇文章先到此为止，我们下一篇再见。-全文完-
【代码札记】从零开始的点对点聊天系统 P3
本文介绍如何使用前文构造的节点，以及Minecraft模组编写的一些内容。上一篇文章中介绍了这套系统中关于I2P部分的设计，这一篇文章中将介绍如何在Minecraft中使用。节点使用眼尖的同学可能已经注意到了，上一篇中我们并没有在节点中处理消息，而是将处理的部分委派给了GeneralIncomingMessageHandler和MessagePayloadHandler。但你会发现，诸如PEX这种比较内部的消息，好像也需要下游使用者自己处理，这是否有些不方便了？我也是这么想的，所以在正式使用之前，还要介绍一下预先实现的一些Handler。内置Handler这些Handler都是基于MessagePayloadHandler实现的，并且通过kotlin的inline和reified关键字，可以在一定程度上消除既要给出泛型，又要给出KClazz实例的情况： object : MessagePayloadHandler<C, SomeKindOfPayload>(SomeKindOfPayload::class) { // ...... }就这一个声明就要重复两次相同的内容，你说烦不烦？反正我很烦。但是有了reifield，事情就简单多了：inline fun <C : SessionContext, reified T : MessagePayload> createMessageHandlerWithReply( crossinline block: ( peer: Peer<C>, session: PeerSession<C>, messageId: UUID, payload: T, threadPool: ThreadPoolExecutor ) -> MessagePayload ): MessagePayloadHandler<C, T> = object : MessagePayloadHandler<C, T>(T::class) { override fun handleTyped( peer: Peer<C>, session: PeerSession<C>, messageId: UUID, payload: T, threadPool: ThreadPoolExecutor ): MessagePayload = block(peer, session, messageId, payload, threadPool) }这个函数创建了一个带有响应的处理器。调用时，Kotlin编译器可以通过上下文推断出C和T的具体类型，并且在编译时将内联替换回原位，这样便可以拿到泛型的KClazz实例了，就像这样：fun <ContextType : SessionContext> createPEXRequestHandler( logger: KLogger, filter: (PeerInfo) -> Boolean, onException: (Throwable, PeerInfo) -> Unit ): MessagePayloadHandler<ContextType, PEXRequest> = createMessageHandlerWithReply { peer, session, messageId, payload, threadPool -> threadPool.execute { peer.handlePEX(session, payload.peerInfos, logger, filter, onException) } PEXReply(messageId, peer.dumpKnownPeer()) }这里我们创建了一个处理PEXRequest的处理器，而我们只在函数返回值那里写了一次类型，后面对于createMessageHandlerWithReply调用则直接省略了类型声明。要不说Kotlin是生产力呢。类似地，我们还可以创建一个针对PEXReply处理的，但由于这个回复不会再有回复了，可以利用如下函数创建：inline fun <C : SessionContext, reified T : MessagePayload> createMessageHandlerNoReply( crossinline block: ( peer: Peer<C>, session: PeerSession<C>, payload: T, threadPool: ThreadPoolExecutor ) -> Unit ): MessagePayloadHandler<C, T> = object : MessagePayloadHandler<C, T>(T::class) { override fun handleTyped( peer: Peer<C>, session: PeerSession<C>, messageId: UUID, payload: T, threadPool: ThreadPoolExecutor ): MessagePayload? = block(peer, session, payload, threadPool).let { null } } fun <ContextType : SessionContext> createPEXReplyHandler( logger: KLogger, filter: (PeerInfo) -> Boolean, onException: (Throwable, PeerInfo) -> Unit ): MessagePayloadHandler<ContextType, PEXReply> = createMessageHandlerNoReply { peer, session, payload, threadPool -> threadPool.execute { peer.handlePEX(session, payload.peerInfos, logger, filter, onException) } }createMessageHandlerNoReply保证handleTyped方法一定回返回null，从而保证没有后续回复。整个PEX的处理逻辑都委托给了handlePEX：private fun <ContextType : SessionContext> Peer<ContextType>.handlePEX( session: PeerSession<ContextType>, peerInfos: List<PeerInfo>, logger: KLogger, filter: (PeerInfo) -> Boolean, onException: (Throwable, PeerInfo) -> Unit ) { var count = 0 peerInfos .filter(filter) .forEach { peerInfo -> val addr = peerInfo.info[PeerInfo.INFO_KEY_DEST] ?: return@forEach if (addr.endsWith(".b32.i2p")) { this.addNewPeer(addr) { onException(it, peerInfo) } count++ } else { try { val dest = Destination(addr) this.addNewPeer(dest) { onException(it, peerInfo) } count++ } catch (e: DataFormatException) { onException(e, peerInfo) } } } logger.debug { "Got $count peers from ${session.getDisplayName()}" } }逻辑也不是很复杂，收到PEX相关的请求后，提取其中的PeerInfo，依次进行检查，然后加入新节点队列并计数。最后打印一个调试信息。原本这个日志是info级别的，但是太频繁了，看着眼晕，就给调成debug了。当然了，最后还有一个用于处理NoContentReply的：fun <C : SessionContext> createNoContentReplyHandler(): MessagePayloadHandler<C, NoContentReply> = createMessageHandlerNoReply { _, _, _, _ -> }使用样例当然了，在Minecraft模组中使用Peer还需要一些前期铺垫，例如说找到拦截发送消息的地方，找到注入消息的地方，利用Minecraft的证书来签名和验证PeerInfo。在这里仅仅是一个展示，暂且与Minecraft无关。首先我们需要一个会话上下文，决定我们这个应用中都要有什么内容：class ExampleContext( override val sessionSource: SessionSource ) : SessionContext { override val nickname: String? get() = peerInfo?.info?.get("peer.username") override var peerInfo: PeerInfo? = null private var authAccepted: Boolean = false override fun isAuthed(): Boolean = super.isAuthed() && nickname != null override fun onAuthAccepted() { authAccepted = true } override fun isAccepted(): Boolean = authAccepted }它就是一个SessionContext接口的实例化，本身没什么具体的功能。然后我们要创建一个GeneralIncomingMessageHandler的实例，用来处理接收到的消息： val messageHandler: IncomingMessageHandler<ExampleContext> = GeneralIncomingMessageHandler( // For auth, just take the username. You may run your own signature check, etc. createMessageHandlerWithReply { _, session, messageId, payload: AuthRequest, _ -> payload.peerInfo.info["peer.username"] ?.let { logger.info { "Authed ${session.getDisplayName()} as $it" } AuthAcceptedReply(messageId) } ?: AuthenticationFailedError("Missing key 'peer.username'", messageId) }, // print text message createMessageHandlerWithReply { _, session, messageId, payload: TextMessageRequest, _ -> logger.info { "Get ${payload.scope} message from peer ${session.getDisplayName()}: ${payload.content}" } NoContentReply(messageId) }, // print disconnect message createMessageHandlerNoReply { _, session, payload: ByeRequest, _ -> logger.info { "Peer ${session.getDisplayName()} disconnected: ${payload.reason}" } }, // handler for AuthAcceptedReply object : MessagePayloadHandler<ExampleContext, AuthAcceptedReply>(AuthAcceptedReply::class) { override fun handleTyped( peer: Peer<ExampleContext>, session: PeerSession<ExampleContext>, messageId: UUID, payload: AuthAcceptedReply, threadPool: ThreadPoolExecutor ): MessagePayload? = null }, // handle PEX request createPEXRequestHandler( logger = logger, filter = { true } ) { t, p -> logger.info(t) { "Failed to connect PEX discovered peer $p" } }, // handle PEX reply, here we do nothing about verification createPEXReplyHandler( logger = logger, filter = { true } ) { t, p -> logger.info(t) { "Failed to connect PEX discovered peer $p" } }, // do nothing on no content reply (204 OK) createNoContentReplyHandler(), // for unsupported message error createMessageHandlerNoReply { _, session, payload: UnsupportedMessageError, _ -> logger.warn { "Peer ${session.getDisplayName()} doesn't support payload: ${payload.payloadJvmName}" } }, // You don't need rest of the handlers, they just make sure everything // is handled by printing incoming messages on screen. createMessageHandlerWithReply { _, session, messageId, payload: MessagePayload, _ -> logger.warn { "Got unhandled message from ${session.getDisplayName()}: $payload" } UnsupportedMessageError(messageId, payload::class.jvmName) } )其实就是之前介绍过的Handler的大串烧。在验证方面，我们使用peer.username来存储节点自己的用户名，然后收到消息基本上就是打印到日志。然后我们需要一个I2PSocketManager和其他一些东西： val manager = I2PSocketManagerFactory.createManager() val json = Json { serializersModule = getSerializersModule() } val threadPool = I2PHelper.createThreadPool(32) val peer = Peer( json, manager, "test", { ExampleContext(it) }, { PeerInfo( mapOf( PeerInfo.INFO_KEY_DEST to it.getMyDestination().toBase64(), "peer.username" to name ), "", "" ) }, messageHandler, threadPool, pexInterval = 10 * 1000, logger = logger )我们可以把上面的过程封装成一个函数，叫做createPeer，这样我们在测试的时候就可以创建好多个Peer一起测试了。比如一个简单的节点通信：object TwoPeerExample { private val json = Json { serializersModule = getSerializersModule() } private val threadPool = I2PHelper.createThreadPool(16) @JvmStatic fun main(args: Array<String>) { val logger = KotlinLogging.logger { } logger.info { "Creating peers..." } val peer1 = createPeer(json, "Peer1", threadPool) val peer2 = createPeer(json, "Peer2", threadPool) logger.info { "Peer1 connect to peer2..." } peer1.connect(peer2.getMyDestination()) logger.info { "Waiting for auth..." } Thread.sleep(5 * 1000) logger.info { "Known peers\nPeer1: ${peer1.dumpKnownPeer()}\nPeer2: ${peer2.dumpKnownPeer()}" } logger.info { "Peer2 -> Peer1" } peer2.sendRequest(TextMessageRequest("public", "test")) { true } Thread.sleep(5 * 1000) logger.info { "Waiting for PEX" } Thread.sleep(30 * 1000) logger.info { "Peer1 Bye" } peer1.close() Thread.sleep(5 * 1000) logger.info { "Peer2 Bye" } peer2.close() Thread.sleep(30 * 1000) } }可以看到这个日志：15:35:13.252 INFO [main] i.s.i.p.c.e.TwoPeerExample - Creating peers... 15:35:14.966 INFO [main] Peer1 - I'm hxqop2amtqcgdru2vrjym7l4uuqqcakigaz6py2fulfzp274cpkq.b32.i2p 15:35:16.561 INFO [main] Peer2 - I'm tvswjwqdphnilwxjlngceiehjvdtyc6g3lhp5awcapuss2sta4sq.b32.i2p 15:35:16.562 INFO [main] i.s.i.p.c.e.TwoPeerExample - Peer1 connect to peer2... 15:35:16.563 INFO [main] Peer1 - Connecting to peer tvswjwqdphnilwxjlngceiehjvdtyc6g3lhp5awcapuss2sta4sq.b32.i2p 15:35:16.674 INFO [main] i.s.i.p.c.e.TwoPeerExample - Waiting for auth... 15:35:16.686 INFO [Thread-8] Peer2 - Authed hxqop2amtqcgdru2vrjym7l4uuqqcakigaz6py2fulfzp274cpkq.b32.i2p as Peer1 15:35:16.686 INFO [Thread-3] Peer1 - Authed tvswjwqdphnilwxjlngceiehjvdtyc6g3lhp5awcapuss2sta4sq.b32.i2p as Peer2 15:35:21.675 INFO [main] i.s.i.p.c.e.TwoPeerExample - Known peers Peer1: [PeerInfo(info={peer.dest=ViAWAQMCSPvUZHwpc-iqelgr3hXyfTfFYLKX~olOTNEBmzQFmc-rPPg1eEOY9vxnsz1bzwKmohXLbF3dWW1ZM8Jer9tCjfQ4ikO97AnHMu5zWhTL-nAl7c-dborNuVRgcpyWXBpU228Xv4Nv~rYtSJdFaZ5wpTKbRRP~DxwYMjmTQqvO70ML3j~9Ct2bCvzvw6BR4ugtYIbsrHuot8aDuvDdlS5Wz00HNQOmTbmTXLWGhPV~cy2~Fv4-YyYWJEqp8K8M-1J7Zvy~rMkQ15F53PpRQBwVDh22rqfU8dqXThMUXzxkoXRxvab5ZwO3X6N8gjhHz3mXiXFUt-b3h19j8-8SBQxrboPW83Nj3b7JNSgjngg7xZVrA7n3FU5qMdM1J6v7En~Xy1EGYDVOErMz4gXW-tql-qApdc5SD2BzMKJRdWvbXMfFKV1NFbkhgORp6v2dgtJ0NYwFBidg9c7m2TT3P0FeSyZScPMu4h2PsntQ-2oTRfsHbcn4lyYYlprZBQAEAAcAAA==, peer.username=Peer2}, signatureBase64=, publicKeyBase64=)] Peer2: [PeerInfo(info={peer.dest=UwhhLujvI1KRq-XZl0C0h6H0ypdXELV2HVQQeMgRH9S5UVjER3hL6k--DhFHhGhDZH1PV07oNx67Fji483WTkI7Ah0r3gPFjfVk3g54y1o6sWRSe~xN0E9Q~eFaSQd1FfvNxm8MSNTbvMk78O-t-hztUB~cNr6FVnJSsSAWQZj03RwOl0SgFBTzcy2MF1TggJo2gVik07szBfzlT67ahDHM4ui~NXB-mGcTjlOJ2j0Q~-UR6TEqS9zLRTlevcpnDUmhl9ARgIEzTYHW0tWq0-01mUu8abeNj80jMv~SzSCNIoRST3gtiBKrToER~O9fIyV-6zFZ6jN5sT-dz94Zt395xl8NIdR2hzxU4OdCJsyNq1FX-KIXhOlMsdbSG2sc3VJ-NRcbYVpcixzjlN-3H2IDxsKeab~nZvgsHz8G65yYvc72cPcE6~uRWzv2boibCBd4OFwHn5FoNrLZfPJPt6As4xsx8UejoDtmqM8Jwj7w00gKfKNRMFdbqEK-kzPdqBQAEAAcAAA==, peer.username=Peer1}, signatureBase64=, publicKeyBase64=)] 15:35:21.676 INFO [main] i.s.i.p.c.e.TwoPeerExample - Peer2 -> Peer1 15:35:21.679 INFO [Thread-3] Peer1 - Get public message from peer Peer2 (tvswjwqdphnilwxjlngceiehjvdtyc6g3lhp5awcapuss2sta4sq.b32.i2p): test 15:35:26.691 INFO [main] i.s.i.p.c.e.TwoPeerExample - Waiting for PEX 15:35:56.695 INFO [main] i.s.i.p.c.e.TwoPeerExample - Peer1 Bye 15:35:56.700 WARN [Thread-12] Peer1 - Socket from Peer2 (tvswjwqdphnilwxjlngceiehjvdtyc6g3lhp5awcapuss2sta4sq.b32.i2p) closed due to IO exception 15:35:56.701 WARN [Thread-11] Peer2 - Socket from Peer1 (hxqop2amtqcgdru2vrjym7l4uuqqcakigaz6py2fulfzp274cpkq.b32.i2p) closed due to IO exception 15:35:56.701 WARN [Thread-4] Peer1 - Server socket hxqop2amtqcgdru2vrjym7l4uuqqcakigaz6py2fulfzp274cpkq.b32.i2p closed or interrupted! 15:35:56.702 INFO [Thread-8] Peer2 - Peer Peer1 (hxqop2amtqcgdru2vrjym7l4uuqqcakigaz6py2fulfzp274cpkq.b32.i2p) disconnected: Peer closing 15:35:56.702 WARN [Thread-4] Peer1 - Peer stop handle incoming connections: hxqop2amtqcgdru2vrjym7l4uuqqcakigaz6py2fulfzp274cpkq.b32.i2p 15:35:56.704 WARN [Thread-8] Peer2 - Sending message to closed socket Peer1 (hxqop2amtqcgdru2vrjym7l4uuqqcakigaz6py2fulfzp274cpkq.b32.i2p) 15:36:01.704 INFO [main] i.s.i.p.c.e.TwoPeerExample - Peer2 Bye 15:36:01.704 WARN [Thread-9] Peer2 - Server socket tvswjwqdphnilwxjlngceiehjvdtyc6g3lhp5awcapuss2sta4sq.b32.i2p closed or interrupted! 15:36:01.704 WARN [Thread-9] Peer2 - Peer stop handle incoming connections: tvswjwqdphnilwxjlngceiehjvdtyc6g3lhp5awcapuss2sta4sq.b32.i2p 15:36:05.452 WARN [Thread-5] Peer1 - Stop PEX due to peer hxqop2amtqcgdru2vrjym7l4uuqqcakigaz6py2fulfzp274cpkq.b32.i2p closed 15:36:06.966 WARN [Thread-10] Peer2 - Stop PEX due to peer tvswjwqdphnilwxjlngceiehjvdtyc6g3lhp5awcapuss2sta4sq.b32.i2p closed看来Peer1确实收到了Peer2的消息。接下来还可以测试一下PEX功能：object JoinPeersExample { private val json = Json { serializersModule = getSerializersModule() } private val threadPool = I2PHelper.createThreadPool(32) @JvmStatic fun main(args: Array<String>) { val logger = KotlinLogging.logger { } val peerList = mutableListOf<Peer<ExampleContext>>() // during the loop, each time we create a new peer, connecting to 1 existing peer // then let the PEX do its job, and check if it connects to all existing peer logger.info { "Creating peers..." } repeat(20) { index -> val peer = createPeer(json, "Peer$index", threadPool) if (peerList.isNotEmpty()) { val existingPeer = peerList.random() peer.connect(existingPeer.getMyDestination()) } peerList.add(peer) } // for now, peers are partly interconnected, give PEX some time logger.info { "Waiting PEX..." } // keep checking loop@ while (true) { for (peer in peerList) { // known peers are connected and authed val count = peer.dumpKnownPeer().mapNotNull { it.info[PeerInfo.INFO_KEY_DEST] }.count() if (count != peerList.size - 1) { // continue while loop continue@loop } } break } logger.info { "Every peer knows ${peerList.size - 1} peers!" } } }在这个测试中，我们依次创建20个节点，并让新创建的节点随机连接到一个节点，这样可以保证构成一个连通图。通过一段时间的PEX，应该能够变成一个完全图，从而确认我们的PEX动作是有效的。如果你没听懂我在说什么的话，翻译一下就是：通过让新节点随机连接到一个已知节点，能够保证在初始状态下是互相可达的。例如P1连接到了P2，P2连接到了P3，虽然P1和P3没有直连，但他们有一个共同的P2，使得P3的信息可以通过P2交换给P1，反之亦然。而完全图则是说图里的任意两个节点之间都有边，即我们创建的20个节点，任意两个节点都建立了直连关系。这说明我们的PEX成功地将节点的信息交换分发给了其他节点。而完全图的一个特点就是N个节点的完全图，每个节点一定有N-1条边。所以20个节点，每个节点一定连接到了其他19个节点。通过检查每个节点的会话数量我们就可以判断是否已经达到了完全图。当然，日志非常杂乱：15:38:20.921 INFO [main] i.s.i.p.c.e.JoinPeersExample - Creating peers... 15:38:22.692 INFO [main] Peer0 - I'm tpie4r2ggnxfliortm5itnxngamqcfb47623rfzwcus26u7atd7q.b32.i2p 15:38:25.165 INFO [main] Peer1 - I'm nrqyhc2ct4ngvh7qg5vjwzxxc3hxlmmap7k3kx3oelnco4bnde2a.b32.i2p 15:38:25.170 INFO [main] Peer1 - Connecting to peer tpie4r2ggnxfliortm5itnxngamqcfb47623rfzwcus26u7atd7q.b32.i2p 15:38:25.281 INFO [Thread-8] Peer1 - Authed tpie4r2ggnxfliortm5itnxngamqcfb47623rfzwcus26u7atd7q.b32.i2p as Peer0 15:38:25.281 INFO [Thread-3] Peer0 - Authed nrqyhc2ct4ngvh7qg5vjwzxxc3hxlmmap7k3kx3oelnco4bnde2a.b32.i2p as Peer1 15:38:27.614 INFO [main] Peer2 - I'm i4hz7f7tit7iz3bnfdz3di2fryqc43a7ufslavc2ubo74u2jb3qq.b32.i2p 15:38:27.615 INFO [main] Peer2 - Connecting to peer nrqyhc2ct4ngvh7qg5vjwzxxc3hxlmmap7k3kx3oelnco4bnde2a.b32.i2p 15:38:27.619 INFO [Thread-15] Peer2 - Authed nrqyhc2ct4ngvh7qg5vjwzxxc3hxlmmap7k3kx3oelnco4bnde2a.b32.i2p as Peer1 15:38:27.619 INFO [Thread-8] Peer1 - Authed i4hz7f7tit7iz3bnfdz3di2fryqc43a7ufslavc2ubo74u2jb3qq.b32.i2p as Peer2 15:38:29.518 INFO [main] Peer3 - I'm rf2y74dydm5zmk7bzgkcpu4fglzd6qhrax37tocswjwuupp4bnya.b32.i2p 15:38:29.518 INFO [main] Peer3 - Connecting to peer nrqyhc2ct4ngvh7qg5vjwzxxc3hxlmmap7k3kx3oelnco4bnde2a.b32.i2p 15:38:29.522 INFO [Thread-22] Peer3 - Authed nrqyhc2ct4ngvh7qg5vjwzxxc3hxlmmap7k3kx3oelnco4bnde2a.b32.i2p as Peer1 15:38:29.523 INFO [Thread-8] Peer1 - Authed rf2y74dydm5zmk7bzgkcpu4fglzd6qhrax37tocswjwuupp4bnya.b32.i2p as Peer3 15:38:31.020 INFO [main] Peer4 - I'm watq5tahqwzsgza2svjahhtsaifqbqxzdqt5ddhwbpdmur3usbca.b32.i2p 15:38:31.020 INFO [main] Peer4 - Connecting to peer nrqyhc2ct4ngvh7qg5vjwzxxc3hxlmmap7k3kx3oelnco4bnde2a.b32.i2p 15:38:31.024 INFO [Thread-29] Peer4 - Authed nrqyhc2ct4ngvh7qg5vjwzxxc3hxlmmap7k3kx3oelnco4bnde2a.b32.i2p as Peer1 15:38:31.025 INFO [Thread-8] Peer1 - Authed watq5tahqwzsgza2svjahhtsaifqbqxzdqt5ddhwbpdmur3usbca.b32.i2p as Peer4 15:38:32.778 INFO [Thread-39] Peer0 - Connecting to peer rf2y74dydm5zmk7bzgkcpu4fglzd6qhrax37tocswjwuupp4bnya.b32.i2p 15:38:32.778 INFO [Thread-40] Peer0 - Connecting to peer watq5tahqwzsgza2svjahhtsaifqbqxzdqt5ddhwbpdmur3usbca.b32.i2p 15:38:32.778 INFO [Thread-41] Peer0 - Connecting to peer i4hz7f7tit7iz3bnfdz3di2fryqc43a7ufslavc2ubo74u2jb3qq.b32.i2p 15:38:32.782 INFO [Thread-15] Peer2 - Authed tpie4r2ggnxfliortm5itnxngamqcfb47623rfzwcus26u7atd7q.b32.i2p as Peer0 15:38:32.783 INFO [Thread-3] Peer0 - Authed i4hz7f7tit7iz3bnfdz3di2fryqc43a7ufslavc2ubo74u2jb3qq.b32.i2p as Peer2 15:38:32.783 INFO [Thread-22] Peer3 - Authed tpie4r2ggnxfliortm5itnxngamqcfb47623rfzwcus26u7atd7q.b32.i2p as Peer0 15:38:32.783 INFO [Thread-3] Peer0 - Authed rf2y74dydm5zmk7bzgkcpu4fglzd6qhrax37tocswjwuupp4bnya.b32.i2p as Peer3 15:38:32.783 INFO [Thread-29] Peer4 - Authed tpie4r2ggnxfliortm5itnxngamqcfb47623rfzwcus26u7atd7q.b32.i2p as Peer0 15:38:32.784 INFO [Thread-3] Peer0 - Authed watq5tahqwzsgza2svjahhtsaifqbqxzdqt5ddhwbpdmur3usbca.b32.i2p as Peer4 15:38:32.938 INFO [main] Peer5 - I'm rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p 15:38:32.938 INFO [main] Peer5 - Connecting to peer nrqyhc2ct4ngvh7qg5vjwzxxc3hxlmmap7k3kx3oelnco4bnde2a.b32.i2p 15:38:32.942 INFO [Thread-50] Peer5 - Authed nrqyhc2ct4ngvh7qg5vjwzxxc3hxlmmap7k3kx3oelnco4bnde2a.b32.i2p as Peer1 15:38:32.942 INFO [Thread-8] Peer1 - Authed rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p as Peer5 15:38:35.228 INFO [Thread-65] Peer0 - Connecting to peer rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p 15:38:35.230 INFO [Thread-70] Peer2 - Connecting to peer rf2y74dydm5zmk7bzgkcpu4fglzd6qhrax37tocswjwuupp4bnya.b32.i2p 15:38:35.230 INFO [Thread-69] Peer5 - Connecting to peer i4hz7f7tit7iz3bnfdz3di2fryqc43a7ufslavc2ubo74u2jb3qq.b32.i2p 15:38:35.230 INFO [Thread-71] Peer4 - Connecting to peer rf2y74dydm5zmk7bzgkcpu4fglzd6qhrax37tocswjwuupp4bnya.b32.i2p 15:38:35.230 INFO [Thread-36] Peer2 - Connecting to peer watq5tahqwzsgza2svjahhtsaifqbqxzdqt5ddhwbpdmur3usbca.b32.i2p 15:38:35.230 INFO [Thread-35] Peer2 - Connecting to peer rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p 15:38:35.230 INFO [Thread-38] Peer5 - Connecting to peer tpie4r2ggnxfliortm5itnxngamqcfb47623rfzwcus26u7atd7q.b32.i2p 15:38:35.231 INFO [Thread-73] Peer3 - Connecting to peer rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p 15:38:35.231 INFO [Thread-79] Peer5 - Connecting to peer watq5tahqwzsgza2svjahhtsaifqbqxzdqt5ddhwbpdmur3usbca.b32.i2p 15:38:35.231 INFO [Thread-78] Peer5 - Connecting to peer rf2y74dydm5zmk7bzgkcpu4fglzd6qhrax37tocswjwuupp4bnya.b32.i2p 15:38:35.232 INFO [Thread-76] Peer3 - Connecting to peer watq5tahqwzsgza2svjahhtsaifqbqxzdqt5ddhwbpdmur3usbca.b32.i2p 15:38:35.233 INFO [Thread-75] Peer4 - Connecting to peer rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p 15:38:35.235 WARN [Thread-4] Peer0 - Duplicated connection from rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p 15:38:35.236 WARN [Thread-51] Peer5 - Duplicated connection from tpie4r2ggnxfliortm5itnxngamqcfb47623rfzwcus26u7atd7q.b32.i2p 15:38:35.238 WARN [Thread-82] Peer5 - Socket from tpie4r2ggnxfliortm5itnxngamqcfb47623rfzwcus26u7atd7q.b32.i2p closed due to IO exception 15:38:35.238 WARN [Thread-80] Peer0 - Socket from rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p closed due to IO exception 15:38:35.241 INFO [Thread-22] Peer3 - Authed i4hz7f7tit7iz3bnfdz3di2fryqc43a7ufslavc2ubo74u2jb3qq.b32.i2p as Peer2 15:38:35.242 INFO [Thread-15] Peer2 - Authed rf2y74dydm5zmk7bzgkcpu4fglzd6qhrax37tocswjwuupp4bnya.b32.i2p as Peer3 15:38:35.243 WARN [Thread-23] Peer3 - Duplicated connection from rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p 15:38:35.245 WARN [Thread-87] Peer5 - Socket from rf2y74dydm5zmk7bzgkcpu4fglzd6qhrax37tocswjwuupp4bnya.b32.i2p closed due to IO exception 15:38:35.246 INFO [Thread-50] Peer5 - Authed rf2y74dydm5zmk7bzgkcpu4fglzd6qhrax37tocswjwuupp4bnya.b32.i2p as Peer3 15:38:35.247 INFO [Thread-22] Peer3 - Authed rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p as Peer5 15:38:35.247 WARN [Thread-51] Peer5 - Duplicated connection from i4hz7f7tit7iz3bnfdz3di2fryqc43a7ufslavc2ubo74u2jb3qq.b32.i2p 15:38:35.250 WARN [Thread-90] Peer2 - Socket from rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p closed due to IO exception 15:38:35.250 WARN [Thread-35] Peer2 - Failed to write socket. Socket rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p closed! 15:38:35.251 INFO [Thread-15] Peer2 - Authed rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p as Peer5 15:38:35.252 INFO [Thread-50] Peer5 - Authed i4hz7f7tit7iz3bnfdz3di2fryqc43a7ufslavc2ubo74u2jb3qq.b32.i2p as Peer2 15:38:35.253 INFO [Thread-29] Peer4 - Authed i4hz7f7tit7iz3bnfdz3di2fryqc43a7ufslavc2ubo74u2jb3qq.b32.i2p as Peer2 15:38:35.254 INFO [Thread-15] Peer2 - Authed watq5tahqwzsgza2svjahhtsaifqbqxzdqt5ddhwbpdmur3usbca.b32.i2p as Peer4 15:38:35.255 WARN [Thread-30] Peer4 - Duplicated connection from rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p 15:38:35.257 WARN [Thread-30] Peer4 - Duplicated connection from rf2y74dydm5zmk7bzgkcpu4fglzd6qhrax37tocswjwuupp4bnya.b32.i2p 15:38:35.257 WARN [Thread-94] Peer5 - Socket from watq5tahqwzsgza2svjahhtsaifqbqxzdqt5ddhwbpdmur3usbca.b32.i2p closed due to IO exception 15:38:35.258 INFO [Thread-50] Peer5 - Authed watq5tahqwzsgza2svjahhtsaifqbqxzdqt5ddhwbpdmur3usbca.b32.i2p as Peer4 15:38:35.259 INFO [Thread-29] Peer4 - Authed rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p as Peer5 15:38:35.259 WARN [Thread-96] Peer3 - Socket from watq5tahqwzsgza2svjahhtsaifqbqxzdqt5ddhwbpdmur3usbca.b32.i2p closed due to IO exception 15:38:35.261 INFO [Thread-22] Peer3 - Authed watq5tahqwzsgza2svjahhtsaifqbqxzdqt5ddhwbpdmur3usbca.b32.i2p as Peer4 15:38:35.261 INFO [Thread-29] Peer4 - Authed rf2y74dydm5zmk7bzgkcpu4fglzd6qhrax37tocswjwuupp4bnya.b32.i2p as Peer3 15:38:35.442 INFO [main] Peer6 - I'm bllxyyq35xiubizjhimwhjmsfhlwpn5bdqx73jldbzdrbhemjm6a.b32.i2p 15:38:35.442 INFO [main] Peer6 - Connecting to peer nrqyhc2ct4ngvh7qg5vjwzxxc3hxlmmap7k3kx3oelnco4bnde2a.b32.i2p 15:38:35.446 INFO [Thread-99] Peer6 - Authed nrqyhc2ct4ngvh7qg5vjwzxxc3hxlmmap7k3kx3oelnco4bnde2a.b32.i2p as Peer1 15:38:35.447 INFO [Thread-8] Peer1 - Authed bllxyyq35xiubizjhimwhjmsfhlwpn5bdqx73jldbzdrbhemjm6a.b32.i2p as Peer6 15:38:37.090 INFO [main] Peer7 - I'm ofbpsthifxk7oogt6rwk4lhukzsoljmrmchhtkrnraj6nkgefj6q.b32.i2p 15:38:37.091 INFO [main] Peer7 - Connecting to peer nrqyhc2ct4ngvh7qg5vjwzxxc3hxlmmap7k3kx3oelnco4bnde2a.b32.i2p 15:38:37.094 INFO [Thread-106] Peer7 - Authed nrqyhc2ct4ngvh7qg5vjwzxxc3hxlmmap7k3kx3oelnco4bnde2a.b32.i2p as Peer1 15:38:37.094 INFO [Thread-8] Peer1 - Authed ofbpsthifxk7oogt6rwk4lhukzsoljmrmchhtkrnraj6nkgefj6q.b32.i2p as Peer7 15:38:37.683 INFO [Thread-72] Peer5 - Connecting to peer tpie4r2ggnxfliortm5itnxngamqcfb47623rfzwcus26u7atd7q.b32.i2p 15:38:37.685 INFO [Thread-76] Peer0 - Connecting to peer rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p 15:38:37.688 WARN [Thread-51] Peer5 - Duplicated connection from tpie4r2ggnxfliortm5itnxngamqcfb47623rfzwcus26u7atd7q.b32.i2p 15:38:37.688 INFO [Thread-75] Peer2 - Connecting to peer ofbpsthifxk7oogt6rwk4lhukzsoljmrmchhtkrnraj6nkgefj6q.b32.i2p 15:38:37.688 INFO [Thread-56] Peer2 - Connecting to peer bllxyyq35xiubizjhimwhjmsfhlwpn5bdqx73jldbzdrbhemjm6a.b32.i2p 15:38:37.689 WARN [Thread-113] Peer0 - Socket from rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p closed due to IO exception 15:38:37.690 INFO [Thread-3] Peer0 - Authed rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p as Peer5 15:38:37.691 INFO [Thread-50] Peer5 - Authed tpie4r2ggnxfliortm5itnxngamqcfb47623rfzwcus26u7atd7q.b32.i2p as Peer0 15:38:37.692 INFO [Thread-15] Peer2 - Authed bllxyyq35xiubizjhimwhjmsfhlwpn5bdqx73jldbzdrbhemjm6a.b32.i2p as Peer6 15:38:37.693 INFO [Thread-106] Peer7 - Authed i4hz7f7tit7iz3bnfdz3di2fryqc43a7ufslavc2ubo74u2jb3qq.b32.i2p as Peer2 15:38:37.693 INFO [Thread-99] Peer6 - Authed i4hz7f7tit7iz3bnfdz3di2fryqc43a7ufslavc2ubo74u2jb3qq.b32.i2p as Peer2 15:38:37.693 INFO [Thread-15] Peer2 - Authed ofbpsthifxk7oogt6rwk4lhukzsoljmrmchhtkrnraj6nkgefj6q.b32.i2p as Peer7 15:38:38.848 INFO [main] Peer8 - I'm schx2gn2ei2ov3oae2ctx7lrxs6cjidfrrfe6zqno73vyjpcktza.b32.i2p 15:38:38.848 INFO [main] Peer8 - Connecting to peer watq5tahqwzsgza2svjahhtsaifqbqxzdqt5ddhwbpdmur3usbca.b32.i2p 15:38:38.852 INFO [Thread-120] Peer8 - Authed watq5tahqwzsgza2svjahhtsaifqbqxzdqt5ddhwbpdmur3usbca.b32.i2p as Peer4 15:38:38.853 INFO [Thread-29] Peer4 - Authed schx2gn2ei2ov3oae2ctx7lrxs6cjidfrrfe6zqno73vyjpcktza.b32.i2p as Peer8 15:38:39.611 INFO [Thread-37] Peer3 - Connecting to peer bllxyyq35xiubizjhimwhjmsfhlwpn5bdqx73jldbzdrbhemjm6a.b32.i2p 15:38:39.611 INFO [Thread-73] Peer3 - Connecting to peer ofbpsthifxk7oogt6rwk4lhukzsoljmrmchhtkrnraj6nkgefj6q.b32.i2p 15:38:39.614 INFO [Thread-22] Peer3 - Authed bllxyyq35xiubizjhimwhjmsfhlwpn5bdqx73jldbzdrbhemjm6a.b32.i2p as Peer6 15:38:39.614 INFO [Thread-99] Peer6 - Authed rf2y74dydm5zmk7bzgkcpu4fglzd6qhrax37tocswjwuupp4bnya.b32.i2p as Peer3 15:38:39.615 INFO [Thread-22] Peer3 - Authed ofbpsthifxk7oogt6rwk4lhukzsoljmrmchhtkrnraj6nkgefj6q.b32.i2p as Peer7 15:38:39.615 INFO [Thread-106] Peer7 - Authed rf2y74dydm5zmk7bzgkcpu4fglzd6qhrax37tocswjwuupp4bnya.b32.i2p as Peer3 15:38:39.621 INFO [Thread-67] Peer3 - Connecting to peer schx2gn2ei2ov3oae2ctx7lrxs6cjidfrrfe6zqno73vyjpcktza.b32.i2p 15:38:39.623 INFO [Thread-22] Peer3 - Authed schx2gn2ei2ov3oae2ctx7lrxs6cjidfrrfe6zqno73vyjpcktza.b32.i2p as Peer8 15:38:39.624 INFO [Thread-120] Peer8 - Authed rf2y74dydm5zmk7bzgkcpu4fglzd6qhrax37tocswjwuupp4bnya.b32.i2p as Peer3 15:38:40.601 INFO [main] Peer9 - I'm wnek7voalfcbm7wjxmny5jvfall2sgjigcumahajbm3ld2c7g5ta.b32.i2p 15:38:40.602 INFO [main] Peer9 - Connecting to peer watq5tahqwzsgza2svjahhtsaifqbqxzdqt5ddhwbpdmur3usbca.b32.i2p 15:38:40.604 INFO [Thread-133] Peer9 - Authed watq5tahqwzsgza2svjahhtsaifqbqxzdqt5ddhwbpdmur3usbca.b32.i2p as Peer4 15:38:40.605 INFO [Thread-29] Peer4 - Authed wnek7voalfcbm7wjxmny5jvfall2sgjigcumahajbm3ld2c7g5ta.b32.i2p as Peer9 15:38:41.102 INFO [Thread-78] Peer1 - Connecting to peer wnek7voalfcbm7wjxmny5jvfall2sgjigcumahajbm3ld2c7g5ta.b32.i2p 15:38:41.102 INFO [Thread-42] Peer2 - Connecting to peer wnek7voalfcbm7wjxmny5jvfall2sgjigcumahajbm3ld2c7g5ta.b32.i2p 15:38:41.102 INFO [Thread-66] Peer2 - Connecting to peer schx2gn2ei2ov3oae2ctx7lrxs6cjidfrrfe6zqno73vyjpcktza.b32.i2p 15:38:41.102 INFO [Thread-63] Peer3 - Connecting to peer wnek7voalfcbm7wjxmny5jvfall2sgjigcumahajbm3ld2c7g5ta.b32.i2p 15:38:41.102 INFO [Thread-57] Peer0 - Connecting to peer wnek7voalfcbm7wjxmny5jvfall2sgjigcumahajbm3ld2c7g5ta.b32.i2p 15:38:41.102 INFO [Thread-56] Peer0 - Connecting to peer schx2gn2ei2ov3oae2ctx7lrxs6cjidfrrfe6zqno73vyjpcktza.b32.i2p 15:38:41.102 INFO [Thread-77] Peer5 - Connecting to peer schx2gn2ei2ov3oae2ctx7lrxs6cjidfrrfe6zqno73vyjpcktza.b32.i2p 15:38:41.102 INFO [Thread-64] Peer5 - Connecting to peer wnek7voalfcbm7wjxmny5jvfall2sgjigcumahajbm3ld2c7g5ta.b32.i2p 15:38:41.102 INFO [Thread-65] Peer1 - Connecting to peer schx2gn2ei2ov3oae2ctx7lrxs6cjidfrrfe6zqno73vyjpcktza.b32.i2p 15:38:41.106 INFO [Thread-40] Peer8 - Connecting to peer wnek7voalfcbm7wjxmny5jvfall2sgjigcumahajbm3ld2c7g5ta.b32.i2p 15:38:41.106 INFO [Thread-60] Peer8 - Connecting to peer nrqyhc2ct4ngvh7qg5vjwzxxc3hxlmmap7k3kx3oelnco4bnde2a.b32.i2p 15:38:41.107 INFO [Thread-67] Peer8 - Connecting to peer rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p 15:38:41.107 INFO [Thread-66] Peer9 - Connecting to peer rf2y74dydm5zmk7bzgkcpu4fglzd6qhrax37tocswjwuupp4bnya.b32.i2p 15:38:41.107 INFO [Thread-69] Peer9 - Connecting to peer nrqyhc2ct4ngvh7qg5vjwzxxc3hxlmmap7k3kx3oelnco4bnde2a.b32.i2p 15:38:41.107 INFO [Thread-79] Peer9 - Connecting to peer rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p 15:38:41.107 INFO [Thread-72] Peer4 - Connecting to peer bllxyyq35xiubizjhimwhjmsfhlwpn5bdqx73jldbzdrbhemjm6a.b32.i2p 15:38:41.107 INFO [Thread-61] Peer4 - Connecting to peer ofbpsthifxk7oogt6rwk4lhukzsoljmrmchhtkrnraj6nkgefj6q.b32.i2p 15:38:41.107 INFO [Thread-74] Peer9 - Connecting to peer schx2gn2ei2ov3oae2ctx7lrxs6cjidfrrfe6zqno73vyjpcktza.b32.i2p 15:38:41.109 WARN [Thread-51] Peer5 - Duplicated connection from schx2gn2ei2ov3oae2ctx7lrxs6cjidfrrfe6zqno73vyjpcktza.b32.i2p 15:38:41.109 INFO [Thread-120] Peer8 - Authed tpie4r2ggnxfliortm5itnxngamqcfb47623rfzwcus26u7atd7q.b32.i2p as Peer0 15:38:41.110 INFO [Thread-3] Peer0 - Authed schx2gn2ei2ov3oae2ctx7lrxs6cjidfrrfe6zqno73vyjpcktza.b32.i2p as Peer8 15:38:41.110 INFO [Thread-120] Peer8 - Authed i4hz7f7tit7iz3bnfdz3di2fryqc43a7ufslavc2ubo74u2jb3qq.b32.i2p as Peer2 15:38:41.110 INFO [Thread-15] Peer2 - Authed schx2gn2ei2ov3oae2ctx7lrxs6cjidfrrfe6zqno73vyjpcktza.b32.i2p as Peer8 15:38:41.110 INFO [Thread-3] Peer0 - Authed wnek7voalfcbm7wjxmny5jvfall2sgjigcumahajbm3ld2c7g5ta.b32.i2p as Peer9 15:38:41.111 INFO [Thread-133] Peer9 - Authed i4hz7f7tit7iz3bnfdz3di2fryqc43a7ufslavc2ubo74u2jb3qq.b32.i2p as Peer2 15:38:41.112 INFO [Thread-15] Peer2 - Authed wnek7voalfcbm7wjxmny5jvfall2sgjigcumahajbm3ld2c7g5ta.b32.i2p as Peer9 15:38:41.112 INFO [Thread-133] Peer9 - Authed tpie4r2ggnxfliortm5itnxngamqcfb47623rfzwcus26u7atd7q.b32.i2p as Peer0 15:38:41.112 WARN [Thread-150] Peer8 - Socket from rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p closed due to IO exception 15:38:41.112 WARN [Thread-121] Peer8 - Duplicated connection from nrqyhc2ct4ngvh7qg5vjwzxxc3hxlmmap7k3kx3oelnco4bnde2a.b32.i2p 15:38:41.113 INFO [Thread-50] Peer5 - Authed schx2gn2ei2ov3oae2ctx7lrxs6cjidfrrfe6zqno73vyjpcktza.b32.i2p as Peer8 15:38:41.113 INFO [Thread-120] Peer8 - Authed rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p as Peer5 15:38:41.115 INFO [Thread-99] Peer6 - Authed watq5tahqwzsgza2svjahhtsaifqbqxzdqt5ddhwbpdmur3usbca.b32.i2p as Peer4 15:38:41.115 WARN [Thread-157] Peer1 - Socket from schx2gn2ei2ov3oae2ctx7lrxs6cjidfrrfe6zqno73vyjpcktza.b32.i2p closed due to IO exception 15:38:41.115 INFO [Thread-29] Peer4 - Authed bllxyyq35xiubizjhimwhjmsfhlwpn5bdqx73jldbzdrbhemjm6a.b32.i2p as Peer6 15:38:41.116 INFO [Thread-120] Peer8 - Authed nrqyhc2ct4ngvh7qg5vjwzxxc3hxlmmap7k3kx3oelnco4bnde2a.b32.i2p as Peer1 15:38:41.116 INFO [Thread-29] Peer4 - Authed ofbpsthifxk7oogt6rwk4lhukzsoljmrmchhtkrnraj6nkgefj6q.b32.i2p as Peer7 15:38:41.117 WARN [Thread-9] Peer1 - Duplicated connection from wnek7voalfcbm7wjxmny5jvfall2sgjigcumahajbm3ld2c7g5ta.b32.i2p 15:38:41.117 INFO [Thread-8] Peer1 - Authed schx2gn2ei2ov3oae2ctx7lrxs6cjidfrrfe6zqno73vyjpcktza.b32.i2p as Peer8 15:38:41.117 INFO [Thread-106] Peer7 - Authed watq5tahqwzsgza2svjahhtsaifqbqxzdqt5ddhwbpdmur3usbca.b32.i2p as Peer4 15:38:41.119 WARN [Thread-161] Peer9 - Socket from nrqyhc2ct4ngvh7qg5vjwzxxc3hxlmmap7k3kx3oelnco4bnde2a.b32.i2p closed due to IO exception 15:38:41.119 INFO [Thread-133] Peer9 - Authed nrqyhc2ct4ngvh7qg5vjwzxxc3hxlmmap7k3kx3oelnco4bnde2a.b32.i2p as Peer1 15:38:41.120 WARN [Thread-134] Peer9 - Duplicated connection from rf2y74dydm5zmk7bzgkcpu4fglzd6qhrax37tocswjwuupp4bnya.b32.i2p 15:38:41.120 INFO [Thread-8] Peer1 - Authed wnek7voalfcbm7wjxmny5jvfall2sgjigcumahajbm3ld2c7g5ta.b32.i2p as Peer9 15:38:41.121 WARN [Thread-23] Peer3 - Duplicated connection from wnek7voalfcbm7wjxmny5jvfall2sgjigcumahajbm3ld2c7g5ta.b32.i2p 15:38:41.121 WARN [Thread-163] Peer3 - Socket from wnek7voalfcbm7wjxmny5jvfall2sgjigcumahajbm3ld2c7g5ta.b32.i2p closed due to IO exception 15:38:41.122 WARN [Thread-134] Peer9 - Duplicated connection from rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p 15:38:41.122 WARN [Thread-151] Peer9 - Socket from rf2y74dydm5zmk7bzgkcpu4fglzd6qhrax37tocswjwuupp4bnya.b32.i2p closed due to IO exception 15:38:41.123 WARN [Thread-134] Peer9 - Duplicated connection from schx2gn2ei2ov3oae2ctx7lrxs6cjidfrrfe6zqno73vyjpcktza.b32.i2p 15:38:41.123 WARN [Thread-64] Peer5 - Failed to write socket. Socket wnek7voalfcbm7wjxmny5jvfall2sgjigcumahajbm3ld2c7g5ta.b32.i2p closed! 15:38:41.123 WARN [Thread-164] Peer5 - Socket from wnek7voalfcbm7wjxmny5jvfall2sgjigcumahajbm3ld2c7g5ta.b32.i2p closed due to IO exception 15:38:41.124 INFO [Thread-50] Peer5 - Authed wnek7voalfcbm7wjxmny5jvfall2sgjigcumahajbm3ld2c7g5ta.b32.i2p as Peer9 15:38:41.124 INFO [Thread-133] Peer9 - Authed rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p as Peer5 15:38:41.124 WARN [Thread-166] Peer8 - Socket from wnek7voalfcbm7wjxmny5jvfall2sgjigcumahajbm3ld2c7g5ta.b32.i2p closed due to IO exception 15:38:41.125 INFO [Thread-120] Peer8 - Authed wnek7voalfcbm7wjxmny5jvfall2sgjigcumahajbm3ld2c7g5ta.b32.i2p as Peer9 15:38:41.125 INFO [Thread-133] Peer9 - Authed schx2gn2ei2ov3oae2ctx7lrxs6cjidfrrfe6zqno73vyjpcktza.b32.i2p as Peer8 15:38:42.075 INFO [main] Peer10 - I'm rhthygtrs7nmdsajl5l64xha325bxwa2pnf3mdavylrjelwktuyq.b32.i2p 15:38:42.075 INFO [main] Peer10 - Connecting to peer wnek7voalfcbm7wjxmny5jvfall2sgjigcumahajbm3ld2c7g5ta.b32.i2p 15:38:42.078 INFO [Thread-169] Peer10 - Authed wnek7voalfcbm7wjxmny5jvfall2sgjigcumahajbm3ld2c7g5ta.b32.i2p as Peer9 15:38:42.078 INFO [Thread-133] Peer9 - Authed rhthygtrs7nmdsajl5l64xha325bxwa2pnf3mdavylrjelwktuyq.b32.i2p as Peer10 15:38:42.846 INFO [Thread-41] Peer3 - Connecting to peer wnek7voalfcbm7wjxmny5jvfall2sgjigcumahajbm3ld2c7g5ta.b32.i2p 15:38:42.846 INFO [Thread-37] Peer9 - Connecting to peer rf2y74dydm5zmk7bzgkcpu4fglzd6qhrax37tocswjwuupp4bnya.b32.i2p 15:38:42.850 WARN [Thread-134] Peer9 - Duplicated connection from rf2y74dydm5zmk7bzgkcpu4fglzd6qhrax37tocswjwuupp4bnya.b32.i2p 15:38:42.850 WARN [Thread-23] Peer3 - Duplicated connection from wnek7voalfcbm7wjxmny5jvfall2sgjigcumahajbm3ld2c7g5ta.b32.i2p 15:38:42.851 WARN [Thread-176] Peer3 - Socket from wnek7voalfcbm7wjxmny5jvfall2sgjigcumahajbm3ld2c7g5ta.b32.i2p closed due to IO exception 15:38:42.851 WARN [Thread-175] Peer9 - Socket from rf2y74dydm5zmk7bzgkcpu4fglzd6qhrax37tocswjwuupp4bnya.b32.i2p closed due to IO exception 15:38:42.859 INFO [Thread-35] Peer0 - Connecting to peer ofbpsthifxk7oogt6rwk4lhukzsoljmrmchhtkrnraj6nkgefj6q.b32.i2p 15:38:42.859 INFO [Thread-56] Peer0 - Connecting to peer bllxyyq35xiubizjhimwhjmsfhlwpn5bdqx73jldbzdrbhemjm6a.b32.i2p 15:38:42.860 INFO [Thread-64] Peer0 - Connecting to peer rhthygtrs7nmdsajl5l64xha325bxwa2pnf3mdavylrjelwktuyq.b32.i2p 15:38:42.862 INFO [Thread-106] Peer7 - Authed tpie4r2ggnxfliortm5itnxngamqcfb47623rfzwcus26u7atd7q.b32.i2p as Peer0 15:38:42.862 INFO [Thread-3] Peer0 - Authed ofbpsthifxk7oogt6rwk4lhukzsoljmrmchhtkrnraj6nkgefj6q.b32.i2p as Peer7 15:38:42.862 INFO [Thread-99] Peer6 - Authed tpie4r2ggnxfliortm5itnxngamqcfb47623rfzwcus26u7atd7q.b32.i2p as Peer0 15:38:42.862 INFO [Thread-3] Peer0 - Authed bllxyyq35xiubizjhimwhjmsfhlwpn5bdqx73jldbzdrbhemjm6a.b32.i2p as Peer6 15:38:42.863 INFO [Thread-3] Peer0 - Authed rhthygtrs7nmdsajl5l64xha325bxwa2pnf3mdavylrjelwktuyq.b32.i2p as Peer10 15:38:42.863 INFO [Thread-169] Peer10 - Authed tpie4r2ggnxfliortm5itnxngamqcfb47623rfzwcus26u7atd7q.b32.i2p as Peer0 15:38:43.032 INFO [Thread-76] Peer9 - Connecting to peer rf2y74dydm5zmk7bzgkcpu4fglzd6qhrax37tocswjwuupp4bnya.b32.i2p 15:38:43.032 INFO [Thread-60] Peer3 - Connecting to peer wnek7voalfcbm7wjxmny5jvfall2sgjigcumahajbm3ld2c7g5ta.b32.i2p 15:38:43.034 INFO [Thread-67] Peer5 - Connecting to peer rhthygtrs7nmdsajl5l64xha325bxwa2pnf3mdavylrjelwktuyq.b32.i2p 15:38:43.034 INFO [Thread-62] Peer5 - Connecting to peer ofbpsthifxk7oogt6rwk4lhukzsoljmrmchhtkrnraj6nkgefj6q.b32.i2p 15:38:43.034 INFO [Thread-72] Peer5 - Connecting to peer bllxyyq35xiubizjhimwhjmsfhlwpn5bdqx73jldbzdrbhemjm6a.b32.i2p 15:38:43.036 WARN [Thread-23] Peer3 - Duplicated connection from wnek7voalfcbm7wjxmny5jvfall2sgjigcumahajbm3ld2c7g5ta.b32.i2p 15:38:43.036 WARN [Thread-134] Peer9 - Duplicated connection from rf2y74dydm5zmk7bzgkcpu4fglzd6qhrax37tocswjwuupp4bnya.b32.i2p 15:38:43.037 WARN [Thread-184] Peer3 - Socket from wnek7voalfcbm7wjxmny5jvfall2sgjigcumahajbm3ld2c7g5ta.b32.i2p closed due to IO exception 15:38:43.038 WARN [Thread-183] Peer9 - Socket from rf2y74dydm5zmk7bzgkcpu4fglzd6qhrax37tocswjwuupp4bnya.b32.i2p closed due to IO exception 15:38:43.038 INFO [Thread-169] Peer10 - Authed rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p as Peer5 15:38:43.039 INFO [Thread-99] Peer6 - Authed rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p as Peer5 15:38:43.039 INFO [Thread-50] Peer5 - Authed bllxyyq35xiubizjhimwhjmsfhlwpn5bdqx73jldbzdrbhemjm6a.b32.i2p as Peer6 15:38:43.039 INFO [Thread-50] Peer5 - Authed rhthygtrs7nmdsajl5l64xha325bxwa2pnf3mdavylrjelwktuyq.b32.i2p as Peer10 15:38:43.040 INFO [Thread-50] Peer5 - Authed ofbpsthifxk7oogt6rwk4lhukzsoljmrmchhtkrnraj6nkgefj6q.b32.i2p as Peer7 15:38:43.040 INFO [Thread-106] Peer7 - Authed rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p as Peer5 15:38:43.840 INFO [main] Peer11 - I'm qbyruqsi52v3heijxnxh5hvvtpsxerlkwwpqkirc45gymfjlpcca.b32.i2p 15:38:43.840 INFO [main] Peer11 - Connecting to peer bllxyyq35xiubizjhimwhjmsfhlwpn5bdqx73jldbzdrbhemjm6a.b32.i2p 15:38:43.843 INFO [Thread-192] Peer11 - Authed bllxyyq35xiubizjhimwhjmsfhlwpn5bdqx73jldbzdrbhemjm6a.b32.i2p as Peer6 15:38:43.844 INFO [Thread-99] Peer6 - Authed qbyruqsi52v3heijxnxh5hvvtpsxerlkwwpqkirc45gymfjlpcca.b32.i2p as Peer11 15:38:45.301 INFO [Thread-64] Peer3 - Connecting to peer wnek7voalfcbm7wjxmny5jvfall2sgjigcumahajbm3ld2c7g5ta.b32.i2p 15:38:45.301 INFO [Thread-40] Peer9 - Connecting to peer bllxyyq35xiubizjhimwhjmsfhlwpn5bdqx73jldbzdrbhemjm6a.b32.i2p 15:38:45.301 INFO [Thread-60] Peer8 - Connecting to peer ofbpsthifxk7oogt6rwk4lhukzsoljmrmchhtkrnraj6nkgefj6q.b32.i2p 15:38:45.301 INFO [Thread-57] Peer9 - Connecting to peer rf2y74dydm5zmk7bzgkcpu4fglzd6qhrax37tocswjwuupp4bnya.b32.i2p 15:38:45.302 INFO [Thread-38] Peer8 - Connecting to peer bllxyyq35xiubizjhimwhjmsfhlwpn5bdqx73jldbzdrbhemjm6a.b32.i2p 15:38:45.302 INFO [Thread-41] Peer7 - Connecting to peer wnek7voalfcbm7wjxmny5jvfall2sgjigcumahajbm3ld2c7g5ta.b32.i2p 15:38:45.302 INFO [Thread-39] Peer7 - Connecting to peer schx2gn2ei2ov3oae2ctx7lrxs6cjidfrrfe6zqno73vyjpcktza.b32.i2p 15:38:45.303 INFO [Thread-58] Peer7 - Connecting to peer bllxyyq35xiubizjhimwhjmsfhlwpn5bdqx73jldbzdrbhemjm6a.b32.i2p 15:38:45.304 INFO [Thread-71] Peer6 - Connecting to peer schx2gn2ei2ov3oae2ctx7lrxs6cjidfrrfe6zqno73vyjpcktza.b32.i2p 15:38:45.304 INFO [Thread-65] Peer1 - Connecting to peer rhthygtrs7nmdsajl5l64xha325bxwa2pnf3mdavylrjelwktuyq.b32.i2p 15:38:45.305 INFO [Thread-68] Peer6 - Connecting to peer ofbpsthifxk7oogt6rwk4lhukzsoljmrmchhtkrnraj6nkgefj6q.b32.i2p 15:38:45.306 WARN [Thread-23] Peer3 - Duplicated connection from wnek7voalfcbm7wjxmny5jvfall2sgjigcumahajbm3ld2c7g5ta.b32.i2p 15:38:45.306 WARN [Thread-134] Peer9 - Duplicated connection from rf2y74dydm5zmk7bzgkcpu4fglzd6qhrax37tocswjwuupp4bnya.b32.i2p 15:38:45.307 WARN [Thread-121] Peer8 - Duplicated connection from ofbpsthifxk7oogt6rwk4lhukzsoljmrmchhtkrnraj6nkgefj6q.b32.i2p 15:38:45.307 INFO [Thread-76] Peer1 - Connecting to peer qbyruqsi52v3heijxnxh5hvvtpsxerlkwwpqkirc45gymfjlpcca.b32.i2p 15:38:45.307 INFO [Thread-99] Peer6 - Authed wnek7voalfcbm7wjxmny5jvfall2sgjigcumahajbm3ld2c7g5ta.b32.i2p as Peer9 15:38:45.307 INFO [Thread-42] Peer9 - Connecting to peer ofbpsthifxk7oogt6rwk4lhukzsoljmrmchhtkrnraj6nkgefj6q.b32.i2p 15:38:45.308 INFO [Thread-133] Peer9 - Authed bllxyyq35xiubizjhimwhjmsfhlwpn5bdqx73jldbzdrbhemjm6a.b32.i2p as Peer6 15:38:45.308 WARN [Thread-202] Peer9 - Socket from rf2y74dydm5zmk7bzgkcpu4fglzd6qhrax37tocswjwuupp4bnya.b32.i2p closed due to IO exception 15:38:45.308 WARN [Thread-199] Peer3 - Socket from wnek7voalfcbm7wjxmny5jvfall2sgjigcumahajbm3ld2c7g5ta.b32.i2p closed due to IO exception 15:38:45.309 WARN [Thread-121] Peer8 - Duplicated connection from bllxyyq35xiubizjhimwhjmsfhlwpn5bdqx73jldbzdrbhemjm6a.b32.i2p 15:38:45.309 WARN [Thread-107] Peer7 - Duplicated connection from schx2gn2ei2ov3oae2ctx7lrxs6cjidfrrfe6zqno73vyjpcktza.b32.i2p 15:38:45.309 WARN [Thread-205] Peer7 - Socket from schx2gn2ei2ov3oae2ctx7lrxs6cjidfrrfe6zqno73vyjpcktza.b32.i2p closed due to IO exception 15:38:45.309 INFO [Thread-8] Peer1 - Authed rhthygtrs7nmdsajl5l64xha325bxwa2pnf3mdavylrjelwktuyq.b32.i2p as Peer10 15:38:45.309 INFO [Thread-169] Peer10 - Authed nrqyhc2ct4ngvh7qg5vjwzxxc3hxlmmap7k3kx3oelnco4bnde2a.b32.i2p as Peer1 15:38:45.310 WARN [Thread-200] Peer8 - Socket from ofbpsthifxk7oogt6rwk4lhukzsoljmrmchhtkrnraj6nkgefj6q.b32.i2p closed due to IO exception 15:38:45.310 WARN [Thread-100] Peer6 - Duplicated connection from schx2gn2ei2ov3oae2ctx7lrxs6cjidfrrfe6zqno73vyjpcktza.b32.i2p 15:38:45.310 WARN [Thread-208] Peer6 - Socket from schx2gn2ei2ov3oae2ctx7lrxs6cjidfrrfe6zqno73vyjpcktza.b32.i2p closed due to IO exception 15:38:45.311 INFO [Thread-8] Peer1 - Authed qbyruqsi52v3heijxnxh5hvvtpsxerlkwwpqkirc45gymfjlpcca.b32.i2p as Peer11 15:38:45.311 INFO [Thread-192] Peer11 - Authed nrqyhc2ct4ngvh7qg5vjwzxxc3hxlmmap7k3kx3oelnco4bnde2a.b32.i2p as Peer1 15:38:45.312 WARN [Thread-204] Peer8 - Socket from bllxyyq35xiubizjhimwhjmsfhlwpn5bdqx73jldbzdrbhemjm6a.b32.i2p closed due to IO exception 15:38:45.312 WARN [Thread-100] Peer6 - Duplicated connection from ofbpsthifxk7oogt6rwk4lhukzsoljmrmchhtkrnraj6nkgefj6q.b32.i2p 15:38:45.313 WARN [Thread-212] Peer7 - Socket from bllxyyq35xiubizjhimwhjmsfhlwpn5bdqx73jldbzdrbhemjm6a.b32.i2p closed due to IO exception 15:38:45.315 WARN [Thread-58] Peer7 - Sending message to closed socket bllxyyq35xiubizjhimwhjmsfhlwpn5bdqx73jldbzdrbhemjm6a.b32.i2p 15:38:45.316 INFO [Thread-106] Peer7 - Authed bllxyyq35xiubizjhimwhjmsfhlwpn5bdqx73jldbzdrbhemjm6a.b32.i2p as Peer6 15:38:45.316 INFO [Thread-99] Peer6 - Authed ofbpsthifxk7oogt6rwk4lhukzsoljmrmchhtkrnraj6nkgefj6q.b32.i2p as Peer7 15:38:45.317 WARN [Thread-107] Peer7 - Duplicated connection from wnek7voalfcbm7wjxmny5jvfall2sgjigcumahajbm3ld2c7g5ta.b32.i2p 15:38:45.318 WARN [Thread-214] Peer9 - Socket from ofbpsthifxk7oogt6rwk4lhukzsoljmrmchhtkrnraj6nkgefj6q.b32.i2p closed due to IO exception 15:38:45.318 INFO [Thread-133] Peer9 - Authed ofbpsthifxk7oogt6rwk4lhukzsoljmrmchhtkrnraj6nkgefj6q.b32.i2p as Peer7 15:38:45.319 INFO [Thread-106] Peer7 - Authed wnek7voalfcbm7wjxmny5jvfall2sgjigcumahajbm3ld2c7g5ta.b32.i2p as Peer9 15:38:45.532 INFO [Thread-78] Peer9 - Connecting to peer rf2y74dydm5zmk7bzgkcpu4fglzd6qhrax37tocswjwuupp4bnya.b32.i2p 15:38:45.532 INFO [Thread-39] Peer9 - Connecting to peer qbyruqsi52v3heijxnxh5hvvtpsxerlkwwpqkirc45gymfjlpcca.b32.i2p 15:38:45.532 INFO [Thread-65] Peer4 - Connecting to peer qbyruqsi52v3heijxnxh5hvvtpsxerlkwwpqkirc45gymfjlpcca.b32.i2p 15:38:45.532 INFO [Thread-66] Peer7 - Connecting to peer qbyruqsi52v3heijxnxh5hvvtpsxerlkwwpqkirc45gymfjlpcca.b32.i2p 15:38:45.534 INFO [Thread-76] Peer5 - Connecting to peer qbyruqsi52v3heijxnxh5hvvtpsxerlkwwpqkirc45gymfjlpcca.b32.i2p 15:38:45.535 INFO [Thread-36] Peer2 - Connecting to peer qbyruqsi52v3heijxnxh5hvvtpsxerlkwwpqkirc45gymfjlpcca.b32.i2p 15:38:45.535 INFO [Thread-73] Peer3 - Connecting to peer qbyruqsi52v3heijxnxh5hvvtpsxerlkwwpqkirc45gymfjlpcca.b32.i2p 15:38:45.536 INFO [Thread-37] Peer11 - Connecting to peer rf2y74dydm5zmk7bzgkcpu4fglzd6qhrax37tocswjwuupp4bnya.b32.i2p 15:38:45.536 INFO [Thread-63] Peer11 - Connecting to peer i4hz7f7tit7iz3bnfdz3di2fryqc43a7ufslavc2ubo74u2jb3qq.b32.i2p 15:38:45.536 INFO [Thread-75] Peer11 - Connecting to peer tpie4r2ggnxfliortm5itnxngamqcfb47623rfzwcus26u7atd7q.b32.i2p 15:38:45.536 INFO [Thread-62] Peer6 - Connecting to peer schx2gn2ei2ov3oae2ctx7lrxs6cjidfrrfe6zqno73vyjpcktza.b32.i2p 15:38:45.536 INFO [Thread-79] Peer6 - Connecting to peer rhthygtrs7nmdsajl5l64xha325bxwa2pnf3mdavylrjelwktuyq.b32.i2p 15:38:45.537 INFO [Thread-42] Peer0 - Connecting to peer qbyruqsi52v3heijxnxh5hvvtpsxerlkwwpqkirc45gymfjlpcca.b32.i2p 15:38:45.540 INFO [Thread-22] Peer3 - Authed wnek7voalfcbm7wjxmny5jvfall2sgjigcumahajbm3ld2c7g5ta.b32.i2p as Peer9 15:38:45.540 INFO [Thread-192] Peer11 - Authed watq5tahqwzsgza2svjahhtsaifqbqxzdqt5ddhwbpdmur3usbca.b32.i2p as Peer4 15:38:45.541 INFO [Thread-57] Peer11 - Connecting to peer ofbpsthifxk7oogt6rwk4lhukzsoljmrmchhtkrnraj6nkgefj6q.b32.i2p 15:38:45.541 INFO [Thread-61] Peer11 - Connecting to peer rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p 15:38:45.541 INFO [Thread-133] Peer9 - Authed rf2y74dydm5zmk7bzgkcpu4fglzd6qhrax37tocswjwuupp4bnya.b32.i2p as Peer3 15:38:45.542 INFO [Thread-99] Peer6 - Authed rhthygtrs7nmdsajl5l64xha325bxwa2pnf3mdavylrjelwktuyq.b32.i2p as Peer10 15:38:45.542 INFO [Thread-133] Peer9 - Authed qbyruqsi52v3heijxnxh5hvvtpsxerlkwwpqkirc45gymfjlpcca.b32.i2p as Peer11 15:38:45.543 INFO [Thread-120] Peer8 - Authed bllxyyq35xiubizjhimwhjmsfhlwpn5bdqx73jldbzdrbhemjm6a.b32.i2p as Peer6 15:38:45.543 INFO [Thread-192] Peer11 - Authed wnek7voalfcbm7wjxmny5jvfall2sgjigcumahajbm3ld2c7g5ta.b32.i2p as Peer9 15:38:45.544 WARN [Thread-107] Peer7 - Duplicated connection from qbyruqsi52v3heijxnxh5hvvtpsxerlkwwpqkirc45gymfjlpcca.b32.i2p 15:38:45.544 INFO [Thread-99] Peer6 - Authed schx2gn2ei2ov3oae2ctx7lrxs6cjidfrrfe6zqno73vyjpcktza.b32.i2p as Peer8 15:38:45.544 INFO [Thread-169] Peer10 - Authed bllxyyq35xiubizjhimwhjmsfhlwpn5bdqx73jldbzdrbhemjm6a.b32.i2p as Peer6 15:38:45.545 WARN [Thread-193] Peer11 - Duplicated connection from ofbpsthifxk7oogt6rwk4lhukzsoljmrmchhtkrnraj6nkgefj6q.b32.i2p 15:38:45.546 WARN [Thread-230] Peer11 - Socket from ofbpsthifxk7oogt6rwk4lhukzsoljmrmchhtkrnraj6nkgefj6q.b32.i2p closed due to IO exception 15:38:45.547 WARN [Thread-229] Peer7 - Socket from qbyruqsi52v3heijxnxh5hvvtpsxerlkwwpqkirc45gymfjlpcca.b32.i2p closed due to IO exception 15:38:45.547 WARN [Thread-193] Peer11 - Duplicated connection from rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p 15:38:45.549 WARN [Thread-51] Peer5 - Duplicated connection from qbyruqsi52v3heijxnxh5hvvtpsxerlkwwpqkirc45gymfjlpcca.b32.i2p 15:38:45.549 WARN [Thread-232] Peer5 - Socket from qbyruqsi52v3heijxnxh5hvvtpsxerlkwwpqkirc45gymfjlpcca.b32.i2p closed due to IO exception 15:38:45.549 WARN [Thread-193] Peer11 - Duplicated connection from i4hz7f7tit7iz3bnfdz3di2fryqc43a7ufslavc2ubo74u2jb3qq.b32.i2p 15:38:45.551 WARN [Thread-231] Peer11 - Socket from rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p closed due to IO exception 15:38:45.551 WARN [Thread-16] Peer2 - Duplicated connection from qbyruqsi52v3heijxnxh5hvvtpsxerlkwwpqkirc45gymfjlpcca.b32.i2p 15:38:45.551 WARN [Thread-233] Peer2 - Socket from qbyruqsi52v3heijxnxh5hvvtpsxerlkwwpqkirc45gymfjlpcca.b32.i2p closed due to IO exception 15:38:45.551 WARN [Thread-193] Peer11 - Duplicated connection from rf2y74dydm5zmk7bzgkcpu4fglzd6qhrax37tocswjwuupp4bnya.b32.i2p 15:38:45.552 WARN [Thread-226] Peer11 - Socket from i4hz7f7tit7iz3bnfdz3di2fryqc43a7ufslavc2ubo74u2jb3qq.b32.i2p closed due to IO exception 15:38:45.552 WARN [Thread-234] Peer3 - Socket from qbyruqsi52v3heijxnxh5hvvtpsxerlkwwpqkirc45gymfjlpcca.b32.i2p closed due to IO exception 15:38:45.553 WARN [Thread-193] Peer11 - Duplicated connection from tpie4r2ggnxfliortm5itnxngamqcfb47623rfzwcus26u7atd7q.b32.i2p 15:38:45.553 WARN [Thread-73] Peer3 - Failed to write socket. Socket qbyruqsi52v3heijxnxh5hvvtpsxerlkwwpqkirc45gymfjlpcca.b32.i2p closed! 15:38:45.553 INFO [Thread-22] Peer3 - Authed qbyruqsi52v3heijxnxh5hvvtpsxerlkwwpqkirc45gymfjlpcca.b32.i2p as Peer11 15:38:45.554 WARN [Thread-4] Peer0 - Duplicated connection from qbyruqsi52v3heijxnxh5hvvtpsxerlkwwpqkirc45gymfjlpcca.b32.i2p 15:38:45.554 WARN [Thread-236] Peer0 - Socket from qbyruqsi52v3heijxnxh5hvvtpsxerlkwwpqkirc45gymfjlpcca.b32.i2p closed due to IO exception 15:38:45.554 INFO [Thread-192] Peer11 - Authed rf2y74dydm5zmk7bzgkcpu4fglzd6qhrax37tocswjwuupp4bnya.b32.i2p as Peer3 15:38:45.554 WARN [Thread-228] Peer11 - Socket from tpie4r2ggnxfliortm5itnxngamqcfb47623rfzwcus26u7atd7q.b32.i2p closed due to IO exception 15:38:45.591 INFO [Thread-29] Peer4 - Authed qbyruqsi52v3heijxnxh5hvvtpsxerlkwwpqkirc45gymfjlpcca.b32.i2p as Peer11 15:38:45.969 INFO [main] Peer12 - I'm j46fcgrfzyfta45nofbbw6e63e342zsa4awdm7aac6xdhhso6jeq.b32.i2p 15:38:45.969 INFO [main] Peer12 - Connecting to peer rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p 15:38:45.972 INFO [Thread-238] Peer12 - Authed rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p as Peer5 15:38:45.973 INFO [Thread-50] Peer5 - Authed j46fcgrfzyfta45nofbbw6e63e342zsa4awdm7aac6xdhhso6jeq.b32.i2p as Peer12 15:38:47.165 INFO [Thread-72] Peer7 - Connecting to peer qbyruqsi52v3heijxnxh5hvvtpsxerlkwwpqkirc45gymfjlpcca.b32.i2p 15:38:47.165 INFO [Thread-68] Peer7 - Connecting to peer rhthygtrs7nmdsajl5l64xha325bxwa2pnf3mdavylrjelwktuyq.b32.i2p 15:38:47.165 INFO [Thread-71] Peer7 - Connecting to peer schx2gn2ei2ov3oae2ctx7lrxs6cjidfrrfe6zqno73vyjpcktza.b32.i2p 15:38:47.168 INFO [Thread-78] Peer7 - Connecting to peer j46fcgrfzyfta45nofbbw6e63e342zsa4awdm7aac6xdhhso6jeq.b32.i2p 15:38:47.169 INFO [Thread-169] Peer10 - Authed ofbpsthifxk7oogt6rwk4lhukzsoljmrmchhtkrnraj6nkgefj6q.b32.i2p as Peer7 15:38:47.170 INFO [Thread-106] Peer7 - Authed rhthygtrs7nmdsajl5l64xha325bxwa2pnf3mdavylrjelwktuyq.b32.i2p as Peer10 15:38:47.170 INFO [Thread-192] Peer11 - Authed ofbpsthifxk7oogt6rwk4lhukzsoljmrmchhtkrnraj6nkgefj6q.b32.i2p as Peer7 15:38:47.171 INFO [Thread-120] Peer8 - Authed ofbpsthifxk7oogt6rwk4lhukzsoljmrmchhtkrnraj6nkgefj6q.b32.i2p as Peer7 15:38:47.171 INFO [Thread-106] Peer7 - Authed qbyruqsi52v3heijxnxh5hvvtpsxerlkwwpqkirc45gymfjlpcca.b32.i2p as Peer11 15:38:47.171 INFO [Thread-106] Peer7 - Authed schx2gn2ei2ov3oae2ctx7lrxs6cjidfrrfe6zqno73vyjpcktza.b32.i2p as Peer8 15:38:47.171 INFO [Thread-106] Peer7 - Authed j46fcgrfzyfta45nofbbw6e63e342zsa4awdm7aac6xdhhso6jeq.b32.i2p as Peer12 15:38:47.172 INFO [Thread-238] Peer12 - Authed ofbpsthifxk7oogt6rwk4lhukzsoljmrmchhtkrnraj6nkgefj6q.b32.i2p as Peer7 15:38:47.475 INFO [main] Peer13 - I'm gij4i6myrxluz7idq5rmroghy4zhjodpqx76mjnhzxmys6hgeqsa.b32.i2p 15:38:47.475 INFO [main] Peer13 - Connecting to peer schx2gn2ei2ov3oae2ctx7lrxs6cjidfrrfe6zqno73vyjpcktza.b32.i2p 15:38:47.478 INFO [Thread-253] Peer13 - Authed schx2gn2ei2ov3oae2ctx7lrxs6cjidfrrfe6zqno73vyjpcktza.b32.i2p as Peer8 15:38:47.478 INFO [Thread-120] Peer8 - Authed gij4i6myrxluz7idq5rmroghy4zhjodpqx76mjnhzxmys6hgeqsa.b32.i2p as Peer13 15:38:47.792 INFO [Thread-73] Peer2 - Connecting to peer qbyruqsi52v3heijxnxh5hvvtpsxerlkwwpqkirc45gymfjlpcca.b32.i2p 15:38:47.793 INFO [Thread-38] Peer2 - Connecting to peer rhthygtrs7nmdsajl5l64xha325bxwa2pnf3mdavylrjelwktuyq.b32.i2p 15:38:47.793 INFO [Thread-71] Peer2 - Connecting to peer j46fcgrfzyfta45nofbbw6e63e342zsa4awdm7aac6xdhhso6jeq.b32.i2p 15:38:47.795 INFO [Thread-41] Peer2 - Connecting to peer gij4i6myrxluz7idq5rmroghy4zhjodpqx76mjnhzxmys6hgeqsa.b32.i2p 15:38:47.795 INFO [Thread-15] Peer2 - Authed qbyruqsi52v3heijxnxh5hvvtpsxerlkwwpqkirc45gymfjlpcca.b32.i2p as Peer11 15:38:47.795 INFO [Thread-192] Peer11 - Authed i4hz7f7tit7iz3bnfdz3di2fryqc43a7ufslavc2ubo74u2jb3qq.b32.i2p as Peer2 15:38:47.796 INFO [Thread-169] Peer10 - Authed i4hz7f7tit7iz3bnfdz3di2fryqc43a7ufslavc2ubo74u2jb3qq.b32.i2p as Peer2 15:38:47.796 INFO [Thread-15] Peer2 - Authed rhthygtrs7nmdsajl5l64xha325bxwa2pnf3mdavylrjelwktuyq.b32.i2p as Peer10 15:38:47.797 INFO [Thread-15] Peer2 - Authed j46fcgrfzyfta45nofbbw6e63e342zsa4awdm7aac6xdhhso6jeq.b32.i2p as Peer12 15:38:47.797 INFO [Thread-238] Peer12 - Authed i4hz7f7tit7iz3bnfdz3di2fryqc43a7ufslavc2ubo74u2jb3qq.b32.i2p as Peer2 15:38:47.798 INFO [Thread-15] Peer2 - Authed gij4i6myrxluz7idq5rmroghy4zhjodpqx76mjnhzxmys6hgeqsa.b32.i2p as Peer13 15:38:47.798 INFO [Thread-253] Peer13 - Authed i4hz7f7tit7iz3bnfdz3di2fryqc43a7ufslavc2ubo74u2jb3qq.b32.i2p as Peer2 15:38:48.974 INFO [Thread-59] Peer4 - Connecting to peer gij4i6myrxluz7idq5rmroghy4zhjodpqx76mjnhzxmys6hgeqsa.b32.i2p 15:38:48.974 INFO [Thread-58] Peer6 - Connecting to peer gij4i6myrxluz7idq5rmroghy4zhjodpqx76mjnhzxmys6hgeqsa.b32.i2p 15:38:48.974 INFO [Thread-79] Peer0 - Connecting to peer gij4i6myrxluz7idq5rmroghy4zhjodpqx76mjnhzxmys6hgeqsa.b32.i2p 15:38:48.975 INFO [Thread-74] Peer9 - Connecting to peer gij4i6myrxluz7idq5rmroghy4zhjodpqx76mjnhzxmys6hgeqsa.b32.i2p 15:38:48.974 INFO [Thread-68] Peer5 - Connecting to peer gij4i6myrxluz7idq5rmroghy4zhjodpqx76mjnhzxmys6hgeqsa.b32.i2p 15:38:48.976 INFO [Thread-61] Peer7 - Connecting to peer gij4i6myrxluz7idq5rmroghy4zhjodpqx76mjnhzxmys6hgeqsa.b32.i2p 15:38:48.977 INFO [Thread-72] Peer3 - Connecting to peer gij4i6myrxluz7idq5rmroghy4zhjodpqx76mjnhzxmys6hgeqsa.b32.i2p 15:38:48.977 INFO [Thread-57] Peer1 - Connecting to peer gij4i6myrxluz7idq5rmroghy4zhjodpqx76mjnhzxmys6hgeqsa.b32.i2p 15:38:48.978 INFO [Thread-76] Peer8 - Connecting to peer j46fcgrfzyfta45nofbbw6e63e342zsa4awdm7aac6xdhhso6jeq.b32.i2p 15:38:48.979 INFO [Thread-41] Peer8 - Connecting to peer rhthygtrs7nmdsajl5l64xha325bxwa2pnf3mdavylrjelwktuyq.b32.i2p 15:38:48.979 INFO [Thread-71] Peer13 - Connecting to peer rf2y74dydm5zmk7bzgkcpu4fglzd6qhrax37tocswjwuupp4bnya.b32.i2p 15:38:48.979 INFO [Thread-62] Peer13 - Connecting to peer nrqyhc2ct4ngvh7qg5vjwzxxc3hxlmmap7k3kx3oelnco4bnde2a.b32.i2p 15:38:48.979 INFO [Thread-59] Peer13 - Connecting to peer rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p 15:38:48.979 INFO [Thread-40] Peer13 - Connecting to peer bllxyyq35xiubizjhimwhjmsfhlwpn5bdqx73jldbzdrbhemjm6a.b32.i2p 15:38:48.980 INFO [Thread-29] Peer4 - Authed gij4i6myrxluz7idq5rmroghy4zhjodpqx76mjnhzxmys6hgeqsa.b32.i2p as Peer13 15:38:48.980 INFO [Thread-253] Peer13 - Authed watq5tahqwzsgza2svjahhtsaifqbqxzdqt5ddhwbpdmur3usbca.b32.i2p as Peer4 15:38:48.981 INFO [Thread-36] Peer8 - Connecting to peer qbyruqsi52v3heijxnxh5hvvtpsxerlkwwpqkirc45gymfjlpcca.b32.i2p 15:38:48.982 INFO [Thread-253] Peer13 - Authed tpie4r2ggnxfliortm5itnxngamqcfb47623rfzwcus26u7atd7q.b32.i2p as Peer0 15:38:48.983 INFO [Thread-3] Peer0 - Authed gij4i6myrxluz7idq5rmroghy4zhjodpqx76mjnhzxmys6hgeqsa.b32.i2p as Peer13 15:38:48.983 WARN [Thread-51] Peer5 - Duplicated connection from gij4i6myrxluz7idq5rmroghy4zhjodpqx76mjnhzxmys6hgeqsa.b32.i2p 15:38:48.984 WARN [Thread-254] Peer13 - Duplicated connection from rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p 15:38:48.984 INFO [Thread-120] Peer8 - Authed j46fcgrfzyfta45nofbbw6e63e342zsa4awdm7aac6xdhhso6jeq.b32.i2p as Peer12 15:38:48.984 INFO [Thread-238] Peer12 - Authed schx2gn2ei2ov3oae2ctx7lrxs6cjidfrrfe6zqno73vyjpcktza.b32.i2p as Peer8 15:38:48.985 INFO [Thread-120] Peer8 - Authed rhthygtrs7nmdsajl5l64xha325bxwa2pnf3mdavylrjelwktuyq.b32.i2p as Peer10 15:38:48.985 INFO [Thread-169] Peer10 - Authed schx2gn2ei2ov3oae2ctx7lrxs6cjidfrrfe6zqno73vyjpcktza.b32.i2p as Peer8 15:38:48.985 INFO [Thread-78] Peer13 - Connecting to peer ofbpsthifxk7oogt6rwk4lhukzsoljmrmchhtkrnraj6nkgefj6q.b32.i2p 15:38:48.985 WARN [Thread-273] Peer5 - Socket from gij4i6myrxluz7idq5rmroghy4zhjodpqx76mjnhzxmys6hgeqsa.b32.i2p closed due to IO exception 15:38:48.991 WARN [Thread-275] Peer13 - Socket from rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p closed due to IO exception 15:38:48.991 INFO [Thread-192] Peer11 - Authed schx2gn2ei2ov3oae2ctx7lrxs6cjidfrrfe6zqno73vyjpcktza.b32.i2p as Peer8 15:38:48.991 INFO [Thread-120] Peer8 - Authed qbyruqsi52v3heijxnxh5hvvtpsxerlkwwpqkirc45gymfjlpcca.b32.i2p as Peer11 15:38:48.993 INFO [Thread-253] Peer13 - Authed wnek7voalfcbm7wjxmny5jvfall2sgjigcumahajbm3ld2c7g5ta.b32.i2p as Peer9 15:38:48.993 INFO [Thread-133] Peer9 - Authed gij4i6myrxluz7idq5rmroghy4zhjodpqx76mjnhzxmys6hgeqsa.b32.i2p as Peer13 15:38:48.993 WARN [Thread-254] Peer13 - Duplicated connection from ofbpsthifxk7oogt6rwk4lhukzsoljmrmchhtkrnraj6nkgefj6q.b32.i2p 15:38:48.994 WARN [Thread-254] Peer13 - Duplicated connection from nrqyhc2ct4ngvh7qg5vjwzxxc3hxlmmap7k3kx3oelnco4bnde2a.b32.i2p 15:38:48.994 WARN [Thread-285] Peer7 - Socket from gij4i6myrxluz7idq5rmroghy4zhjodpqx76mjnhzxmys6hgeqsa.b32.i2p closed due to IO exception 15:38:48.994 INFO [Thread-106] Peer7 - Authed gij4i6myrxluz7idq5rmroghy4zhjodpqx76mjnhzxmys6hgeqsa.b32.i2p as Peer13 15:38:48.995 WARN [Thread-254] Peer13 - Duplicated connection from rf2y74dydm5zmk7bzgkcpu4fglzd6qhrax37tocswjwuupp4bnya.b32.i2p 15:38:48.995 INFO [Thread-253] Peer13 - Authed ofbpsthifxk7oogt6rwk4lhukzsoljmrmchhtkrnraj6nkgefj6q.b32.i2p as Peer7 15:38:48.995 WARN [Thread-287] Peer1 - Socket from gij4i6myrxluz7idq5rmroghy4zhjodpqx76mjnhzxmys6hgeqsa.b32.i2p closed due to IO exception 15:38:48.995 INFO [Thread-8] Peer1 - Authed gij4i6myrxluz7idq5rmroghy4zhjodpqx76mjnhzxmys6hgeqsa.b32.i2p as Peer13 15:38:48.995 WARN [Thread-289] Peer3 - Socket from gij4i6myrxluz7idq5rmroghy4zhjodpqx76mjnhzxmys6hgeqsa.b32.i2p closed due to IO exception 15:38:48.996 WARN [Thread-72] Peer3 - Failed to write socket. Socket gij4i6myrxluz7idq5rmroghy4zhjodpqx76mjnhzxmys6hgeqsa.b32.i2p closed! 15:38:48.996 WARN [Thread-254] Peer13 - Duplicated connection from bllxyyq35xiubizjhimwhjmsfhlwpn5bdqx73jldbzdrbhemjm6a.b32.i2p 15:38:48.996 INFO [Thread-253] Peer13 - Authed nrqyhc2ct4ngvh7qg5vjwzxxc3hxlmmap7k3kx3oelnco4bnde2a.b32.i2p as Peer1 15:38:48.996 INFO [Thread-22] Peer3 - Authed gij4i6myrxluz7idq5rmroghy4zhjodpqx76mjnhzxmys6hgeqsa.b32.i2p as Peer13 15:38:48.996 INFO [Thread-253] Peer13 - Authed rf2y74dydm5zmk7bzgkcpu4fglzd6qhrax37tocswjwuupp4bnya.b32.i2p as Peer3 15:38:48.997 WARN [Thread-291] Peer6 - Socket from gij4i6myrxluz7idq5rmroghy4zhjodpqx76mjnhzxmys6hgeqsa.b32.i2p closed due to IO exception 15:38:48.997 INFO [Thread-99] Peer6 - Authed gij4i6myrxluz7idq5rmroghy4zhjodpqx76mjnhzxmys6hgeqsa.b32.i2p as Peer13 15:38:49.007 INFO [Thread-253] Peer13 - Authed bllxyyq35xiubizjhimwhjmsfhlwpn5bdqx73jldbzdrbhemjm6a.b32.i2p as Peer6 15:38:49.089 INFO [main] Peer14 - I'm jrwx5zmqrm5zau5bkqmtsdfc52vmpmeuk7prbhi2xferteevk7eq.b32.i2p 15:38:49.089 INFO [main] Peer14 - Connecting to peer bllxyyq35xiubizjhimwhjmsfhlwpn5bdqx73jldbzdrbhemjm6a.b32.i2p 15:38:49.092 INFO [Thread-294] Peer14 - Authed bllxyyq35xiubizjhimwhjmsfhlwpn5bdqx73jldbzdrbhemjm6a.b32.i2p as Peer6 15:38:49.092 INFO [Thread-99] Peer6 - Authed jrwx5zmqrm5zau5bkqmtsdfc52vmpmeuk7prbhi2xferteevk7eq.b32.i2p as Peer14 15:38:49.710 INFO [Thread-70] Peer5 - Connecting to peer gij4i6myrxluz7idq5rmroghy4zhjodpqx76mjnhzxmys6hgeqsa.b32.i2p 15:38:49.710 INFO [Thread-62] Peer5 - Connecting to peer qbyruqsi52v3heijxnxh5hvvtpsxerlkwwpqkirc45gymfjlpcca.b32.i2p 15:38:49.710 INFO [Thread-69] Peer13 - Connecting to peer rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p 15:38:49.710 INFO [Thread-77] Peer13 - Connecting to peer qbyruqsi52v3heijxnxh5hvvtpsxerlkwwpqkirc45gymfjlpcca.b32.i2p 15:38:49.711 INFO [Thread-39] Peer0 - Connecting to peer qbyruqsi52v3heijxnxh5hvvtpsxerlkwwpqkirc45gymfjlpcca.b32.i2p 15:38:49.711 INFO [Thread-79] Peer11 - Connecting to peer gij4i6myrxluz7idq5rmroghy4zhjodpqx76mjnhzxmys6hgeqsa.b32.i2p 15:38:49.711 INFO [Thread-40] Peer11 - Connecting to peer rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p 15:38:49.712 INFO [Thread-78] Peer11 - Connecting to peer tpie4r2ggnxfliortm5itnxngamqcfb47623rfzwcus26u7atd7q.b32.i2p 15:38:49.714 INFO [Thread-64] Peer3 - Connecting to peer rhthygtrs7nmdsajl5l64xha325bxwa2pnf3mdavylrjelwktuyq.b32.i2p 15:38:49.714 WARN [Thread-193] Peer11 - Duplicated connection from rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p 15:38:49.714 WARN [Thread-51] Peer5 - Duplicated connection from qbyruqsi52v3heijxnxh5hvvtpsxerlkwwpqkirc45gymfjlpcca.b32.i2p 15:38:49.715 INFO [Thread-38] Peer3 - Connecting to peer j46fcgrfzyfta45nofbbw6e63e342zsa4awdm7aac6xdhhso6jeq.b32.i2p 15:38:49.715 WARN [Thread-301] Peer11 - Socket from rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p closed due to IO exception 15:38:49.716 WARN [Thread-193] Peer11 - Duplicated connection from tpie4r2ggnxfliortm5itnxngamqcfb47623rfzwcus26u7atd7q.b32.i2p 15:38:49.716 WARN [Thread-302] Peer5 - Socket from qbyruqsi52v3heijxnxh5hvvtpsxerlkwwpqkirc45gymfjlpcca.b32.i2p closed due to IO exception 15:38:49.716 WARN [Thread-51] Peer5 - Duplicated connection from gij4i6myrxluz7idq5rmroghy4zhjodpqx76mjnhzxmys6hgeqsa.b32.i2p 15:38:49.718 WARN [Thread-39] Peer0 - Sending message to closed socket qbyruqsi52v3heijxnxh5hvvtpsxerlkwwpqkirc45gymfjlpcca.b32.i2p 15:38:49.719 INFO [Thread-169] Peer10 - Authed rf2y74dydm5zmk7bzgkcpu4fglzd6qhrax37tocswjwuupp4bnya.b32.i2p as Peer3 15:38:49.719 WARN [Thread-305] Peer13 - Socket from rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p closed due to IO exception 15:38:49.719 INFO [Thread-50] Peer5 - Authed gij4i6myrxluz7idq5rmroghy4zhjodpqx76mjnhzxmys6hgeqsa.b32.i2p as Peer13 15:38:49.719 WARN [Thread-254] Peer13 - Duplicated connection from qbyruqsi52v3heijxnxh5hvvtpsxerlkwwpqkirc45gymfjlpcca.b32.i2p 15:38:49.719 INFO [Thread-253] Peer13 - Authed rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p as Peer5 15:38:49.719 WARN [Thread-307] Peer0 - Socket from qbyruqsi52v3heijxnxh5hvvtpsxerlkwwpqkirc45gymfjlpcca.b32.i2p closed due to IO exception 15:38:49.720 INFO [Thread-192] Peer11 - Authed tpie4r2ggnxfliortm5itnxngamqcfb47623rfzwcus26u7atd7q.b32.i2p as Peer0 15:38:49.720 INFO [Thread-3] Peer0 - Authed qbyruqsi52v3heijxnxh5hvvtpsxerlkwwpqkirc45gymfjlpcca.b32.i2p as Peer11 15:38:49.720 INFO [Thread-22] Peer3 - Authed rhthygtrs7nmdsajl5l64xha325bxwa2pnf3mdavylrjelwktuyq.b32.i2p as Peer10 15:38:49.720 INFO [Thread-238] Peer12 - Authed rf2y74dydm5zmk7bzgkcpu4fglzd6qhrax37tocswjwuupp4bnya.b32.i2p as Peer3 15:38:49.720 INFO [Thread-22] Peer3 - Authed j46fcgrfzyfta45nofbbw6e63e342zsa4awdm7aac6xdhhso6jeq.b32.i2p as Peer12 15:38:49.720 INFO [Thread-69] Peer3 - Connecting to peer jrwx5zmqrm5zau5bkqmtsdfc52vmpmeuk7prbhi2xferteevk7eq.b32.i2p 15:38:49.720 WARN [Thread-313] Peer11 - Socket from gij4i6myrxluz7idq5rmroghy4zhjodpqx76mjnhzxmys6hgeqsa.b32.i2p closed due to IO exception 15:38:49.721 INFO [Thread-192] Peer11 - Authed gij4i6myrxluz7idq5rmroghy4zhjodpqx76mjnhzxmys6hgeqsa.b32.i2p as Peer13 15:38:49.721 INFO [Thread-253] Peer13 - Authed qbyruqsi52v3heijxnxh5hvvtpsxerlkwwpqkirc45gymfjlpcca.b32.i2p as Peer11 15:38:49.722 INFO [Thread-22] Peer3 - Authed jrwx5zmqrm5zau5bkqmtsdfc52vmpmeuk7prbhi2xferteevk7eq.b32.i2p as Peer14 15:38:49.723 INFO [Thread-294] Peer14 - Authed rf2y74dydm5zmk7bzgkcpu4fglzd6qhrax37tocswjwuupp4bnya.b32.i2p as Peer3 15:38:50.710 INFO [Thread-79] Peer5 - Connecting to peer qbyruqsi52v3heijxnxh5hvvtpsxerlkwwpqkirc45gymfjlpcca.b32.i2p 15:38:50.710 INFO [Thread-67] Peer13 - Connecting to peer rhthygtrs7nmdsajl5l64xha325bxwa2pnf3mdavylrjelwktuyq.b32.i2p 15:38:50.713 INFO [Thread-50] Peer5 - Authed qbyruqsi52v3heijxnxh5hvvtpsxerlkwwpqkirc45gymfjlpcca.b32.i2p as Peer11 15:38:50.713 INFO [Thread-192] Peer11 - Authed rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p as Peer5 15:38:50.713 INFO [Thread-169] Peer10 - Authed gij4i6myrxluz7idq5rmroghy4zhjodpqx76mjnhzxmys6hgeqsa.b32.i2p as Peer13 15:38:50.713 INFO [Thread-72] Peer4 - Connecting to peer rhthygtrs7nmdsajl5l64xha325bxwa2pnf3mdavylrjelwktuyq.b32.i2p 15:38:50.713 INFO [Thread-41] Peer11 - Connecting to peer rhthygtrs7nmdsajl5l64xha325bxwa2pnf3mdavylrjelwktuyq.b32.i2p 15:38:50.714 INFO [Thread-253] Peer13 - Authed rhthygtrs7nmdsajl5l64xha325bxwa2pnf3mdavylrjelwktuyq.b32.i2p as Peer10 15:38:50.716 INFO [Thread-56] Peer9 - Connecting to peer j46fcgrfzyfta45nofbbw6e63e342zsa4awdm7aac6xdhhso6jeq.b32.i2p 15:38:50.717 INFO [Thread-169] Peer10 - Authed watq5tahqwzsgza2svjahhtsaifqbqxzdqt5ddhwbpdmur3usbca.b32.i2p as Peer4 15:38:50.718 INFO [Thread-169] Peer10 - Authed qbyruqsi52v3heijxnxh5hvvtpsxerlkwwpqkirc45gymfjlpcca.b32.i2p as Peer11 15:38:50.718 INFO [Thread-192] Peer11 - Authed rhthygtrs7nmdsajl5l64xha325bxwa2pnf3mdavylrjelwktuyq.b32.i2p as Peer10 15:38:50.718 INFO [Thread-37] Peer9 - Connecting to peer jrwx5zmqrm5zau5bkqmtsdfc52vmpmeuk7prbhi2xferteevk7eq.b32.i2p 15:38:50.719 INFO [Thread-29] Peer4 - Authed rhthygtrs7nmdsajl5l64xha325bxwa2pnf3mdavylrjelwktuyq.b32.i2p as Peer10 15:38:50.719 INFO [Thread-133] Peer9 - Authed j46fcgrfzyfta45nofbbw6e63e342zsa4awdm7aac6xdhhso6jeq.b32.i2p as Peer12 15:38:50.720 INFO [Thread-238] Peer12 - Authed wnek7voalfcbm7wjxmny5jvfall2sgjigcumahajbm3ld2c7g5ta.b32.i2p as Peer9 15:38:50.721 INFO [Thread-133] Peer9 - Authed jrwx5zmqrm5zau5bkqmtsdfc52vmpmeuk7prbhi2xferteevk7eq.b32.i2p as Peer14 15:38:50.721 INFO [Thread-294] Peer14 - Authed wnek7voalfcbm7wjxmny5jvfall2sgjigcumahajbm3ld2c7g5ta.b32.i2p as Peer9 15:38:50.820 INFO [main] Peer15 - I'm qesvfdvtjzi3r5ihvk2dskfb63s6os7sgjoxbxvdbfzo3z74ojxq.b32.i2p 15:38:50.821 INFO [main] Peer15 - Connecting to peer wnek7voalfcbm7wjxmny5jvfall2sgjigcumahajbm3ld2c7g5ta.b32.i2p 15:38:50.823 INFO [Thread-330] Peer15 - Authed wnek7voalfcbm7wjxmny5jvfall2sgjigcumahajbm3ld2c7g5ta.b32.i2p as Peer9 15:38:50.823 INFO [Thread-133] Peer9 - Authed qesvfdvtjzi3r5ihvk2dskfb63s6os7sgjoxbxvdbfzo3z74ojxq.b32.i2p as Peer15 15:38:51.215 INFO [Thread-57] Peer4 - Connecting to peer jrwx5zmqrm5zau5bkqmtsdfc52vmpmeuk7prbhi2xferteevk7eq.b32.i2p 15:38:51.215 INFO [Thread-76] Peer4 - Connecting to peer qesvfdvtjzi3r5ihvk2dskfb63s6os7sgjoxbxvdbfzo3z74ojxq.b32.i2p 15:38:51.215 INFO [Thread-56] Peer4 - Connecting to peer j46fcgrfzyfta45nofbbw6e63e342zsa4awdm7aac6xdhhso6jeq.b32.i2p 15:38:51.218 INFO [Thread-238] Peer12 - Authed watq5tahqwzsgza2svjahhtsaifqbqxzdqt5ddhwbpdmur3usbca.b32.i2p as Peer4 15:38:51.219 INFO [Thread-29] Peer4 - Authed j46fcgrfzyfta45nofbbw6e63e342zsa4awdm7aac6xdhhso6jeq.b32.i2p as Peer12 15:38:51.219 INFO [Thread-330] Peer15 - Authed watq5tahqwzsgza2svjahhtsaifqbqxzdqt5ddhwbpdmur3usbca.b32.i2p as Peer4 15:38:51.219 INFO [Thread-29] Peer4 - Authed qesvfdvtjzi3r5ihvk2dskfb63s6os7sgjoxbxvdbfzo3z74ojxq.b32.i2p as Peer15 15:38:51.219 INFO [Thread-294] Peer14 - Authed watq5tahqwzsgza2svjahhtsaifqbqxzdqt5ddhwbpdmur3usbca.b32.i2p as Peer4 15:38:51.219 INFO [Thread-29] Peer4 - Authed jrwx5zmqrm5zau5bkqmtsdfc52vmpmeuk7prbhi2xferteevk7eq.b32.i2p as Peer14 15:38:52.177 INFO [Thread-59] Peer10 - Connecting to peer j46fcgrfzyfta45nofbbw6e63e342zsa4awdm7aac6xdhhso6jeq.b32.i2p 15:38:52.177 INFO [Thread-78] Peer10 - Connecting to peer qesvfdvtjzi3r5ihvk2dskfb63s6os7sgjoxbxvdbfzo3z74ojxq.b32.i2p 15:38:52.177 INFO [Thread-61] Peer10 - Connecting to peer jrwx5zmqrm5zau5bkqmtsdfc52vmpmeuk7prbhi2xferteevk7eq.b32.i2p 15:38:52.181 INFO [Thread-169] Peer10 - Authed qesvfdvtjzi3r5ihvk2dskfb63s6os7sgjoxbxvdbfzo3z74ojxq.b32.i2p as Peer15 15:38:52.187 INFO [Thread-169] Peer10 - Authed j46fcgrfzyfta45nofbbw6e63e342zsa4awdm7aac6xdhhso6jeq.b32.i2p as Peer12 15:38:52.187 INFO [Thread-238] Peer12 - Authed rhthygtrs7nmdsajl5l64xha325bxwa2pnf3mdavylrjelwktuyq.b32.i2p as Peer10 15:38:52.187 INFO [Thread-294] Peer14 - Authed rhthygtrs7nmdsajl5l64xha325bxwa2pnf3mdavylrjelwktuyq.b32.i2p as Peer10 15:38:52.187 INFO [Thread-330] Peer15 - Authed rhthygtrs7nmdsajl5l64xha325bxwa2pnf3mdavylrjelwktuyq.b32.i2p as Peer10 15:38:52.187 INFO [Thread-169] Peer10 - Authed jrwx5zmqrm5zau5bkqmtsdfc52vmpmeuk7prbhi2xferteevk7eq.b32.i2p as Peer14 15:38:52.967 INFO [Thread-56] Peer0 - Connecting to peer j46fcgrfzyfta45nofbbw6e63e342zsa4awdm7aac6xdhhso6jeq.b32.i2p 15:38:52.968 INFO [Thread-68] Peer0 - Connecting to peer jrwx5zmqrm5zau5bkqmtsdfc52vmpmeuk7prbhi2xferteevk7eq.b32.i2p 15:38:52.970 INFO [Thread-66] Peer0 - Connecting to peer qesvfdvtjzi3r5ihvk2dskfb63s6os7sgjoxbxvdbfzo3z74ojxq.b32.i2p 15:38:52.972 INFO [Thread-3] Peer0 - Authed j46fcgrfzyfta45nofbbw6e63e342zsa4awdm7aac6xdhhso6jeq.b32.i2p as Peer12 15:38:52.972 INFO [Thread-238] Peer12 - Authed tpie4r2ggnxfliortm5itnxngamqcfb47623rfzwcus26u7atd7q.b32.i2p as Peer0 15:38:52.972 INFO [Thread-3] Peer0 - Authed jrwx5zmqrm5zau5bkqmtsdfc52vmpmeuk7prbhi2xferteevk7eq.b32.i2p as Peer14 15:38:52.973 INFO [Thread-294] Peer14 - Authed tpie4r2ggnxfliortm5itnxngamqcfb47623rfzwcus26u7atd7q.b32.i2p as Peer0 15:38:52.973 INFO [Thread-3] Peer0 - Authed qesvfdvtjzi3r5ihvk2dskfb63s6os7sgjoxbxvdbfzo3z74ojxq.b32.i2p as Peer15 15:38:52.973 INFO [Thread-330] Peer15 - Authed tpie4r2ggnxfliortm5itnxngamqcfb47623rfzwcus26u7atd7q.b32.i2p as Peer0 15:38:53.121 INFO [Thread-35] Peer13 - Connecting to peer j46fcgrfzyfta45nofbbw6e63e342zsa4awdm7aac6xdhhso6jeq.b32.i2p 15:38:53.122 INFO [Thread-60] Peer11 - Connecting to peer j46fcgrfzyfta45nofbbw6e63e342zsa4awdm7aac6xdhhso6jeq.b32.i2p 15:38:53.122 INFO [Thread-71] Peer1 - Connecting to peer j46fcgrfzyfta45nofbbw6e63e342zsa4awdm7aac6xdhhso6jeq.b32.i2p 15:38:53.122 INFO [Thread-78] Peer6 - Connecting to peer j46fcgrfzyfta45nofbbw6e63e342zsa4awdm7aac6xdhhso6jeq.b32.i2p 15:38:53.124 INFO [Thread-253] Peer13 - Authed j46fcgrfzyfta45nofbbw6e63e342zsa4awdm7aac6xdhhso6jeq.b32.i2p as Peer12 15:38:53.124 INFO [Thread-238] Peer12 - Authed gij4i6myrxluz7idq5rmroghy4zhjodpqx76mjnhzxmys6hgeqsa.b32.i2p as Peer13 15:38:53.125 INFO [Thread-62] Peer5 - Connecting to peer qesvfdvtjzi3r5ihvk2dskfb63s6os7sgjoxbxvdbfzo3z74ojxq.b32.i2p 15:38:53.125 INFO [Thread-66] Peer5 - Connecting to peer jrwx5zmqrm5zau5bkqmtsdfc52vmpmeuk7prbhi2xferteevk7eq.b32.i2p 15:38:53.125 INFO [Thread-238] Peer12 - Authed qbyruqsi52v3heijxnxh5hvvtpsxerlkwwpqkirc45gymfjlpcca.b32.i2p as Peer11 15:38:53.127 INFO [Thread-238] Peer12 - Authed bllxyyq35xiubizjhimwhjmsfhlwpn5bdqx73jldbzdrbhemjm6a.b32.i2p as Peer6 15:38:53.127 INFO [Thread-8] Peer1 - Authed j46fcgrfzyfta45nofbbw6e63e342zsa4awdm7aac6xdhhso6jeq.b32.i2p as Peer12 15:38:53.128 INFO [Thread-238] Peer12 - Authed nrqyhc2ct4ngvh7qg5vjwzxxc3hxlmmap7k3kx3oelnco4bnde2a.b32.i2p as Peer1 15:38:53.129 INFO [Thread-99] Peer6 - Authed j46fcgrfzyfta45nofbbw6e63e342zsa4awdm7aac6xdhhso6jeq.b32.i2p as Peer12 15:38:53.130 INFO [Thread-50] Peer5 - Authed qesvfdvtjzi3r5ihvk2dskfb63s6os7sgjoxbxvdbfzo3z74ojxq.b32.i2p as Peer15 15:38:53.130 INFO [Thread-330] Peer15 - Authed rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p as Peer5 15:38:53.130 INFO [Thread-50] Peer5 - Authed jrwx5zmqrm5zau5bkqmtsdfc52vmpmeuk7prbhi2xferteevk7eq.b32.i2p as Peer14 15:38:53.130 INFO [Thread-294] Peer14 - Authed rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p as Peer5 15:38:53.130 INFO [Thread-192] Peer11 - Authed j46fcgrfzyfta45nofbbw6e63e342zsa4awdm7aac6xdhhso6jeq.b32.i2p as Peer12 15:38:53.306 INFO [main] Peer16 - I'm iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p 15:38:53.307 INFO [main] Peer16 - Connecting to peer bllxyyq35xiubizjhimwhjmsfhlwpn5bdqx73jldbzdrbhemjm6a.b32.i2p 15:38:53.310 INFO [Thread-367] Peer16 - Authed bllxyyq35xiubizjhimwhjmsfhlwpn5bdqx73jldbzdrbhemjm6a.b32.i2p as Peer6 15:38:53.310 INFO [Thread-99] Peer6 - Authed iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p as Peer16 15:38:53.941 INFO [Thread-64] Peer11 - Connecting to peer jrwx5zmqrm5zau5bkqmtsdfc52vmpmeuk7prbhi2xferteevk7eq.b32.i2p 15:38:53.942 INFO [Thread-59] Peer11 - Connecting to peer qesvfdvtjzi3r5ihvk2dskfb63s6os7sgjoxbxvdbfzo3z74ojxq.b32.i2p 15:38:53.947 INFO [Thread-192] Peer11 - Authed jrwx5zmqrm5zau5bkqmtsdfc52vmpmeuk7prbhi2xferteevk7eq.b32.i2p as Peer14 15:38:53.947 INFO [Thread-192] Peer11 - Authed qesvfdvtjzi3r5ihvk2dskfb63s6os7sgjoxbxvdbfzo3z74ojxq.b32.i2p as Peer15 15:38:53.947 INFO [Thread-294] Peer14 - Authed qbyruqsi52v3heijxnxh5hvvtpsxerlkwwpqkirc45gymfjlpcca.b32.i2p as Peer11 15:38:53.947 INFO [Thread-330] Peer15 - Authed qbyruqsi52v3heijxnxh5hvvtpsxerlkwwpqkirc45gymfjlpcca.b32.i2p as Peer11 15:38:53.948 INFO [Thread-69] Peer11 - Connecting to peer iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p 15:38:53.951 INFO [Thread-192] Peer11 - Authed iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p as Peer16 15:38:53.951 INFO [Thread-367] Peer16 - Authed qbyruqsi52v3heijxnxh5hvvtpsxerlkwwpqkirc45gymfjlpcca.b32.i2p as Peer11 15:38:54.713 INFO [main] Peer17 - I'm qrca24rlefllwaj3dilluvsq75vndqaygoruwhoxupotbyr76gka.b32.i2p 15:38:54.713 INFO [main] Peer17 - Connecting to peer iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p 15:38:54.718 INFO [Thread-367] Peer16 - Authed qrca24rlefllwaj3dilluvsq75vndqaygoruwhoxupotbyr76gka.b32.i2p as Peer17 15:38:54.718 INFO [Thread-380] Peer17 - Authed iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p as Peer16 15:38:55.389 INFO [Thread-66] Peer1 - Connecting to peer qesvfdvtjzi3r5ihvk2dskfb63s6os7sgjoxbxvdbfzo3z74ojxq.b32.i2p 15:38:55.389 INFO [Thread-63] Peer1 - Connecting to peer jrwx5zmqrm5zau5bkqmtsdfc52vmpmeuk7prbhi2xferteevk7eq.b32.i2p 15:38:55.394 INFO [Thread-330] Peer15 - Authed nrqyhc2ct4ngvh7qg5vjwzxxc3hxlmmap7k3kx3oelnco4bnde2a.b32.i2p as Peer1 15:38:55.394 INFO [Thread-38] Peer1 - Connecting to peer iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p 15:38:55.394 INFO [Thread-8] Peer1 - Authed qesvfdvtjzi3r5ihvk2dskfb63s6os7sgjoxbxvdbfzo3z74ojxq.b32.i2p as Peer15 15:38:55.394 INFO [Thread-294] Peer14 - Authed nrqyhc2ct4ngvh7qg5vjwzxxc3hxlmmap7k3kx3oelnco4bnde2a.b32.i2p as Peer1 15:38:55.394 INFO [Thread-8] Peer1 - Authed jrwx5zmqrm5zau5bkqmtsdfc52vmpmeuk7prbhi2xferteevk7eq.b32.i2p as Peer14 15:38:55.397 INFO [Thread-8] Peer1 - Authed iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p as Peer16 15:38:55.397 INFO [Thread-367] Peer16 - Authed nrqyhc2ct4ngvh7qg5vjwzxxc3hxlmmap7k3kx3oelnco4bnde2a.b32.i2p as Peer1 15:38:55.622 INFO [Thread-56] Peer3 - Connecting to peer iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p 15:38:55.622 INFO [Thread-75] Peer9 - Connecting to peer iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p 15:38:55.622 INFO [Thread-76] Peer0 - Connecting to peer iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p 15:38:55.622 INFO [Thread-36] Peer13 - Connecting to peer jrwx5zmqrm5zau5bkqmtsdfc52vmpmeuk7prbhi2xferteevk7eq.b32.i2p 15:38:55.622 INFO [Thread-77] Peer13 - Connecting to peer iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p 15:38:55.622 INFO [Thread-58] Peer4 - Connecting to peer iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p 15:38:55.624 INFO [Thread-67] Peer12 - Connecting to peer jrwx5zmqrm5zau5bkqmtsdfc52vmpmeuk7prbhi2xferteevk7eq.b32.i2p 15:38:55.624 INFO [Thread-64] Peer12 - Connecting to peer iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p 15:38:55.624 INFO [Thread-78] Peer10 - Connecting to peer iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p 15:38:55.624 INFO [Thread-35] Peer8 - Connecting to peer jrwx5zmqrm5zau5bkqmtsdfc52vmpmeuk7prbhi2xferteevk7eq.b32.i2p 15:38:55.625 INFO [Thread-71] Peer8 - Connecting to peer iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p 15:38:55.625 INFO [Thread-66] Peer7 - Connecting to peer jrwx5zmqrm5zau5bkqmtsdfc52vmpmeuk7prbhi2xferteevk7eq.b32.i2p 15:38:55.625 INFO [Thread-63] Peer7 - Connecting to peer iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p 15:38:55.627 INFO [Thread-39] Peer5 - Connecting to peer iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p 15:38:55.627 INFO [Thread-69] Peer6 - Connecting to peer qesvfdvtjzi3r5ihvk2dskfb63s6os7sgjoxbxvdbfzo3z74ojxq.b32.i2p 15:38:55.628 INFO [Thread-367] Peer16 - Authed rf2y74dydm5zmk7bzgkcpu4fglzd6qhrax37tocswjwuupp4bnya.b32.i2p as Peer3 15:38:55.628 INFO [Thread-22] Peer3 - Authed iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p as Peer16 15:38:55.629 INFO [Thread-238] Peer12 - Authed jrwx5zmqrm5zau5bkqmtsdfc52vmpmeuk7prbhi2xferteevk7eq.b32.i2p as Peer14 15:38:55.629 INFO [Thread-294] Peer14 - Authed gij4i6myrxluz7idq5rmroghy4zhjodpqx76mjnhzxmys6hgeqsa.b32.i2p as Peer13 15:38:55.630 INFO [Thread-253] Peer13 - Authed jrwx5zmqrm5zau5bkqmtsdfc52vmpmeuk7prbhi2xferteevk7eq.b32.i2p as Peer14 15:38:55.631 INFO [Thread-294] Peer14 - Authed j46fcgrfzyfta45nofbbw6e63e342zsa4awdm7aac6xdhhso6jeq.b32.i2p as Peer12 15:38:55.631 INFO [Thread-367] Peer16 - Authed tpie4r2ggnxfliortm5itnxngamqcfb47623rfzwcus26u7atd7q.b32.i2p as Peer0 15:38:55.631 INFO [Thread-3] Peer0 - Authed iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p as Peer16 15:38:55.632 INFO [Thread-133] Peer9 - Authed iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p as Peer16 15:38:55.632 INFO [Thread-75] Peer16 - Connecting to peer ofbpsthifxk7oogt6rwk4lhukzsoljmrmchhtkrnraj6nkgefj6q.b32.i2p 15:38:55.632 INFO [Thread-35] Peer14 - Connecting to peer i4hz7f7tit7iz3bnfdz3di2fryqc43a7ufslavc2ubo74u2jb3qq.b32.i2p 15:38:55.632 INFO [Thread-367] Peer16 - Authed wnek7voalfcbm7wjxmny5jvfall2sgjigcumahajbm3ld2c7g5ta.b32.i2p as Peer9 15:38:55.633 INFO [Thread-330] Peer15 - Authed bllxyyq35xiubizjhimwhjmsfhlwpn5bdqx73jldbzdrbhemjm6a.b32.i2p as Peer6 15:38:55.633 INFO [Thread-58] Peer16 - Connecting to peer j46fcgrfzyfta45nofbbw6e63e342zsa4awdm7aac6xdhhso6jeq.b32.i2p 15:38:55.633 INFO [Thread-106] Peer7 - Authed jrwx5zmqrm5zau5bkqmtsdfc52vmpmeuk7prbhi2xferteevk7eq.b32.i2p as Peer14 15:38:55.633 INFO [Thread-367] Peer16 - Authed gij4i6myrxluz7idq5rmroghy4zhjodpqx76mjnhzxmys6hgeqsa.b32.i2p as Peer13 15:38:55.634 INFO [Thread-294] Peer14 - Authed ofbpsthifxk7oogt6rwk4lhukzsoljmrmchhtkrnraj6nkgefj6q.b32.i2p as Peer7 15:38:55.634 INFO [Thread-99] Peer6 - Authed qesvfdvtjzi3r5ihvk2dskfb63s6os7sgjoxbxvdbfzo3z74ojxq.b32.i2p as Peer15 15:38:55.634 INFO [Thread-294] Peer14 - Authed schx2gn2ei2ov3oae2ctx7lrxs6cjidfrrfe6zqno73vyjpcktza.b32.i2p as Peer8 15:38:55.635 INFO [Thread-253] Peer13 - Authed iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p as Peer16 15:38:55.635 INFO [Thread-78] Peer16 - Connecting to peer i4hz7f7tit7iz3bnfdz3di2fryqc43a7ufslavc2ubo74u2jb3qq.b32.i2p 15:38:55.636 INFO [Thread-367] Peer16 - Authed watq5tahqwzsgza2svjahhtsaifqbqxzdqt5ddhwbpdmur3usbca.b32.i2p as Peer4 15:38:55.636 INFO [Thread-77] Peer14 - Connecting to peer iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p 15:38:55.636 INFO [Thread-120] Peer8 - Authed jrwx5zmqrm5zau5bkqmtsdfc52vmpmeuk7prbhi2xferteevk7eq.b32.i2p as Peer14 15:38:55.636 INFO [Thread-169] Peer10 - Authed iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p as Peer16 15:38:55.636 INFO [Thread-367] Peer16 - Authed rhthygtrs7nmdsajl5l64xha325bxwa2pnf3mdavylrjelwktuyq.b32.i2p as Peer10 15:38:55.636 INFO [Thread-64] Peer16 - Connecting to peer jrwx5zmqrm5zau5bkqmtsdfc52vmpmeuk7prbhi2xferteevk7eq.b32.i2p 15:38:55.636 WARN [Thread-239] Peer12 - Duplicated connection from iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p 15:38:55.636 INFO [Thread-35] Peer16 - Connecting to peer schx2gn2ei2ov3oae2ctx7lrxs6cjidfrrfe6zqno73vyjpcktza.b32.i2p 15:38:55.637 WARN [Thread-368] Peer16 - Duplicated connection from j46fcgrfzyfta45nofbbw6e63e342zsa4awdm7aac6xdhhso6jeq.b32.i2p 15:38:55.637 INFO [Thread-42] Peer6 - Connecting to peer qrca24rlefllwaj3dilluvsq75vndqaygoruwhoxupotbyr76gka.b32.i2p 15:38:55.637 INFO [Thread-15] Peer2 - Authed jrwx5zmqrm5zau5bkqmtsdfc52vmpmeuk7prbhi2xferteevk7eq.b32.i2p as Peer14 15:38:55.638 INFO [Thread-294] Peer14 - Authed i4hz7f7tit7iz3bnfdz3di2fryqc43a7ufslavc2ubo74u2jb3qq.b32.i2p as Peer2 15:38:55.638 INFO [Thread-75] Peer16 - Connecting to peer rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p 15:38:55.638 WARN [Thread-416] Peer12 - Socket from iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p closed due to IO exception 15:38:55.638 INFO [Thread-15] Peer2 - Authed iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p as Peer16 15:38:55.638 WARN [Thread-418] Peer16 - Socket from j46fcgrfzyfta45nofbbw6e63e342zsa4awdm7aac6xdhhso6jeq.b32.i2p closed due to IO exception 15:38:55.639 INFO [Thread-367] Peer16 - Authed i4hz7f7tit7iz3bnfdz3di2fryqc43a7ufslavc2ubo74u2jb3qq.b32.i2p as Peer2 15:38:55.640 WARN [Thread-121] Peer8 - Duplicated connection from iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p 15:38:55.640 INFO [Thread-99] Peer6 - Authed qrca24rlefllwaj3dilluvsq75vndqaygoruwhoxupotbyr76gka.b32.i2p as Peer17 15:38:55.640 WARN [Thread-368] Peer16 - Duplicated connection from schx2gn2ei2ov3oae2ctx7lrxs6cjidfrrfe6zqno73vyjpcktza.b32.i2p 15:38:55.641 INFO [Thread-380] Peer17 - Authed bllxyyq35xiubizjhimwhjmsfhlwpn5bdqx73jldbzdrbhemjm6a.b32.i2p as Peer6 15:38:55.641 WARN [Thread-422] Peer8 - Socket from iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p closed due to IO exception 15:38:55.641 WARN [Thread-425] Peer16 - Socket from schx2gn2ei2ov3oae2ctx7lrxs6cjidfrrfe6zqno73vyjpcktza.b32.i2p closed due to IO exception 15:38:55.642 WARN [Thread-368] Peer16 - Duplicated connection from ofbpsthifxk7oogt6rwk4lhukzsoljmrmchhtkrnraj6nkgefj6q.b32.i2p 15:38:55.643 WARN [Thread-107] Peer7 - Duplicated connection from iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p 15:38:55.643 WARN [Thread-368] Peer16 - Duplicated connection from rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p 15:38:55.643 WARN [Thread-427] Peer7 - Socket from iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p closed due to IO exception 15:38:55.644 WARN [Thread-51] Peer5 - Duplicated connection from iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p 15:38:55.644 WARN [Thread-415] Peer16 - Socket from ofbpsthifxk7oogt6rwk4lhukzsoljmrmchhtkrnraj6nkgefj6q.b32.i2p closed due to IO exception 15:38:55.644 WARN [Thread-368] Peer16 - Duplicated connection from jrwx5zmqrm5zau5bkqmtsdfc52vmpmeuk7prbhi2xferteevk7eq.b32.i2p 15:38:55.644 WARN [Thread-428] Peer5 - Socket from iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p closed due to IO exception 15:38:55.645 WARN [Thread-295] Peer14 - Duplicated connection from iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p 15:38:55.645 WARN [Thread-426] Peer16 - Socket from rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p closed due to IO exception 15:38:55.645 WARN [Thread-429] Peer14 - Socket from iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p closed due to IO exception 15:38:55.646 WARN [Thread-423] Peer16 - Socket from jrwx5zmqrm5zau5bkqmtsdfc52vmpmeuk7prbhi2xferteevk7eq.b32.i2p closed due to IO exception 15:38:55.665 INFO [Thread-29] Peer4 - Authed iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p as Peer16 15:38:56.044 INFO [Thread-62] Peer12 - Connecting to peer qesvfdvtjzi3r5ihvk2dskfb63s6os7sgjoxbxvdbfzo3z74ojxq.b32.i2p 15:38:56.044 INFO [Thread-58] Peer12 - Connecting to peer iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p 15:38:56.045 INFO [Thread-78] Peer12 - Connecting to peer qrca24rlefllwaj3dilluvsq75vndqaygoruwhoxupotbyr76gka.b32.i2p 15:38:56.054 INFO [Thread-238] Peer12 - Authed iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p as Peer16 15:38:56.054 INFO [Thread-367] Peer16 - Authed j46fcgrfzyfta45nofbbw6e63e342zsa4awdm7aac6xdhhso6jeq.b32.i2p as Peer12 15:38:56.055 INFO [Thread-238] Peer12 - Authed qesvfdvtjzi3r5ihvk2dskfb63s6os7sgjoxbxvdbfzo3z74ojxq.b32.i2p as Peer15 15:38:56.055 INFO [Thread-330] Peer15 - Authed j46fcgrfzyfta45nofbbw6e63e342zsa4awdm7aac6xdhhso6jeq.b32.i2p as Peer12 15:38:56.057 INFO [Thread-238] Peer12 - Authed qrca24rlefllwaj3dilluvsq75vndqaygoruwhoxupotbyr76gka.b32.i2p as Peer17 15:38:56.057 INFO [Thread-380] Peer17 - Authed j46fcgrfzyfta45nofbbw6e63e342zsa4awdm7aac6xdhhso6jeq.b32.i2p as Peer12 15:38:56.258 INFO [main] Peer18 - I'm yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p 15:38:56.258 INFO [main] Peer18 - Connecting to peer qrca24rlefllwaj3dilluvsq75vndqaygoruwhoxupotbyr76gka.b32.i2p 15:38:56.260 INFO [Thread-437] Peer18 - Authed qrca24rlefllwaj3dilluvsq75vndqaygoruwhoxupotbyr76gka.b32.i2p as Peer17 15:38:56.260 INFO [Thread-380] Peer17 - Authed yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p as Peer18 15:38:57.276 INFO [Thread-73] Peer7 - Connecting to peer iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p 15:38:57.276 INFO [Thread-39] Peer7 - Connecting to peer qesvfdvtjzi3r5ihvk2dskfb63s6os7sgjoxbxvdbfzo3z74ojxq.b32.i2p 15:38:57.279 INFO [Thread-42] Peer7 - Connecting to peer qrca24rlefllwaj3dilluvsq75vndqaygoruwhoxupotbyr76gka.b32.i2p 15:38:57.280 INFO [Thread-367] Peer16 - Authed ofbpsthifxk7oogt6rwk4lhukzsoljmrmchhtkrnraj6nkgefj6q.b32.i2p as Peer7 15:38:57.280 INFO [Thread-106] Peer7 - Authed iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p as Peer16 15:38:57.280 INFO [Thread-106] Peer7 - Authed qesvfdvtjzi3r5ihvk2dskfb63s6os7sgjoxbxvdbfzo3z74ojxq.b32.i2p as Peer15 15:38:57.280 INFO [Thread-330] Peer15 - Authed ofbpsthifxk7oogt6rwk4lhukzsoljmrmchhtkrnraj6nkgefj6q.b32.i2p as Peer7 15:38:57.282 INFO [Thread-106] Peer7 - Authed qrca24rlefllwaj3dilluvsq75vndqaygoruwhoxupotbyr76gka.b32.i2p as Peer17 15:38:57.282 INFO [Thread-380] Peer17 - Authed ofbpsthifxk7oogt6rwk4lhukzsoljmrmchhtkrnraj6nkgefj6q.b32.i2p as Peer7 15:38:57.586 INFO [Thread-70] Peer16 - Connecting to peer schx2gn2ei2ov3oae2ctx7lrxs6cjidfrrfe6zqno73vyjpcktza.b32.i2p 15:38:57.586 INFO [Thread-58] Peer16 - Connecting to peer rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p 15:38:57.586 INFO [Thread-62] Peer16 - Connecting to peer jrwx5zmqrm5zau5bkqmtsdfc52vmpmeuk7prbhi2xferteevk7eq.b32.i2p 15:38:57.587 INFO [Thread-77] Peer8 - Connecting to peer iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p 15:38:57.587 INFO [Thread-36] Peer14 - Connecting to peer iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p 15:38:57.588 INFO [Thread-67] Peer5 - Connecting to peer iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p 15:38:57.591 WARN [Thread-121] Peer8 - Duplicated connection from iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p 15:38:57.591 INFO [Thread-57] Peer13 - Connecting to peer qesvfdvtjzi3r5ihvk2dskfb63s6os7sgjoxbxvdbfzo3z74ojxq.b32.i2p 15:38:57.591 WARN [Thread-368] Peer16 - Duplicated connection from schx2gn2ei2ov3oae2ctx7lrxs6cjidfrrfe6zqno73vyjpcktza.b32.i2p 15:38:57.592 WARN [Thread-451] Peer16 - Socket from schx2gn2ei2ov3oae2ctx7lrxs6cjidfrrfe6zqno73vyjpcktza.b32.i2p closed due to IO exception 15:38:57.593 WARN [Thread-450] Peer8 - Socket from iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p closed due to IO exception 15:38:57.593 WARN [Thread-368] Peer16 - Duplicated connection from jrwx5zmqrm5zau5bkqmtsdfc52vmpmeuk7prbhi2xferteevk7eq.b32.i2p 15:38:57.594 WARN [Thread-295] Peer14 - Duplicated connection from iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p 15:38:57.594 WARN [Thread-454] Peer14 - Socket from iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p closed due to IO exception 15:38:57.594 WARN [Thread-368] Peer16 - Duplicated connection from rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p 15:38:57.595 WARN [Thread-452] Peer16 - Socket from jrwx5zmqrm5zau5bkqmtsdfc52vmpmeuk7prbhi2xferteevk7eq.b32.i2p closed due to IO exception 15:38:57.596 WARN [Thread-51] Peer5 - Duplicated connection from iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p 15:38:57.596 WARN [Thread-455] Peer5 - Socket from iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p closed due to IO exception 15:38:57.596 INFO [Thread-253] Peer13 - Authed qesvfdvtjzi3r5ihvk2dskfb63s6os7sgjoxbxvdbfzo3z74ojxq.b32.i2p as Peer15 15:38:57.596 INFO [Thread-330] Peer15 - Authed gij4i6myrxluz7idq5rmroghy4zhjodpqx76mjnhzxmys6hgeqsa.b32.i2p as Peer13 15:38:57.597 WARN [Thread-449] Peer16 - Socket from rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p closed due to IO exception 15:38:57.607 INFO [Thread-38] Peer13 - Connecting to peer qrca24rlefllwaj3dilluvsq75vndqaygoruwhoxupotbyr76gka.b32.i2p 15:38:57.610 INFO [Thread-253] Peer13 - Authed qrca24rlefllwaj3dilluvsq75vndqaygoruwhoxupotbyr76gka.b32.i2p as Peer17 15:38:57.610 INFO [Thread-380] Peer17 - Authed gij4i6myrxluz7idq5rmroghy4zhjodpqx76mjnhzxmys6hgeqsa.b32.i2p as Peer13 15:38:57.887 INFO [Thread-57] Peer8 - Connecting to peer iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p 15:38:57.887 INFO [Thread-69] Peer14 - Connecting to peer iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p 15:38:57.888 INFO [Thread-66] Peer16 - Connecting to peer rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p 15:38:57.888 INFO [Thread-35] Peer16 - Connecting to peer schx2gn2ei2ov3oae2ctx7lrxs6cjidfrrfe6zqno73vyjpcktza.b32.i2p 15:38:57.888 INFO [Thread-58] Peer16 - Connecting to peer jrwx5zmqrm5zau5bkqmtsdfc52vmpmeuk7prbhi2xferteevk7eq.b32.i2p 15:38:57.890 INFO [Thread-78] Peer5 - Connecting to peer iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p 15:38:57.891 INFO [Thread-41] Peer2 - Connecting to peer qesvfdvtjzi3r5ihvk2dskfb63s6os7sgjoxbxvdbfzo3z74ojxq.b32.i2p 15:38:57.891 WARN [Thread-121] Peer8 - Duplicated connection from iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p 15:38:57.893 WARN [Thread-368] Peer16 - Duplicated connection from schx2gn2ei2ov3oae2ctx7lrxs6cjidfrrfe6zqno73vyjpcktza.b32.i2p 15:38:57.894 WARN [Thread-461] Peer16 - Socket from schx2gn2ei2ov3oae2ctx7lrxs6cjidfrrfe6zqno73vyjpcktza.b32.i2p closed due to IO exception 15:38:57.895 WARN [Thread-459] Peer8 - Socket from iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p closed due to IO exception 15:38:57.895 WARN [Thread-368] Peer16 - Duplicated connection from jrwx5zmqrm5zau5bkqmtsdfc52vmpmeuk7prbhi2xferteevk7eq.b32.i2p 15:38:57.895 INFO [Thread-65] Peer2 - Connecting to peer qrca24rlefllwaj3dilluvsq75vndqaygoruwhoxupotbyr76gka.b32.i2p 15:38:57.896 WARN [Thread-295] Peer14 - Duplicated connection from iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p 15:38:57.896 WARN [Thread-368] Peer16 - Duplicated connection from rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p 15:38:57.896 WARN [Thread-464] Peer14 - Socket from iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p closed due to IO exception 15:38:57.897 INFO [Thread-330] Peer15 - Authed i4hz7f7tit7iz3bnfdz3di2fryqc43a7ufslavc2ubo74u2jb3qq.b32.i2p as Peer2 15:38:57.897 INFO [Thread-15] Peer2 - Authed qesvfdvtjzi3r5ihvk2dskfb63s6os7sgjoxbxvdbfzo3z74ojxq.b32.i2p as Peer15 15:38:57.898 WARN [Thread-51] Peer5 - Duplicated connection from iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p 15:38:57.899 WARN [Thread-463] Peer16 - Socket from jrwx5zmqrm5zau5bkqmtsdfc52vmpmeuk7prbhi2xferteevk7eq.b32.i2p closed due to IO exception 15:38:57.900 WARN [Thread-466] Peer5 - Socket from iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p closed due to IO exception 15:38:57.900 WARN [Thread-460] Peer16 - Socket from rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p closed due to IO exception 15:38:57.900 INFO [Thread-15] Peer2 - Authed qrca24rlefllwaj3dilluvsq75vndqaygoruwhoxupotbyr76gka.b32.i2p as Peer17 15:38:57.900 INFO [Thread-380] Peer17 - Authed i4hz7f7tit7iz3bnfdz3di2fryqc43a7ufslavc2ubo74u2jb3qq.b32.i2p as Peer2 15:38:58.656 INFO [main] Peer19 - I'm 34mu7guz63w2bzsh3fyjxtpz3evoi7wxaa5tv5ipecq3cnwtptsq.b32.i2p 15:38:58.657 INFO [main] Peer19 - Connecting to peer iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p 15:38:58.660 INFO [Thread-470] Peer19 - Authed iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p as Peer16 15:38:58.660 INFO [Thread-367] Peer16 - Authed 34mu7guz63w2bzsh3fyjxtpz3evoi7wxaa5tv5ipecq3cnwtptsq.b32.i2p as Peer19 15:38:58.660 INFO [main] i.s.i.p.c.e.JoinPeersExample - Waiting PEX... 15:38:59.047 INFO [Thread-39] Peer8 - Connecting to peer qesvfdvtjzi3r5ihvk2dskfb63s6os7sgjoxbxvdbfzo3z74ojxq.b32.i2p 15:38:59.048 INFO [Thread-61] Peer8 - Connecting to peer iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p 15:38:59.049 INFO [Thread-38] Peer8 - Connecting to peer qrca24rlefllwaj3dilluvsq75vndqaygoruwhoxupotbyr76gka.b32.i2p 15:38:59.056 INFO [Thread-120] Peer8 - Authed qrca24rlefllwaj3dilluvsq75vndqaygoruwhoxupotbyr76gka.b32.i2p as Peer17 15:38:59.056 INFO [Thread-120] Peer8 - Authed iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p as Peer16 15:38:59.056 INFO [Thread-120] Peer8 - Authed qesvfdvtjzi3r5ihvk2dskfb63s6os7sgjoxbxvdbfzo3z74ojxq.b32.i2p as Peer15 15:38:59.063 INFO [Thread-330] Peer15 - Authed schx2gn2ei2ov3oae2ctx7lrxs6cjidfrrfe6zqno73vyjpcktza.b32.i2p as Peer8 15:38:59.063 INFO [Thread-367] Peer16 - Authed schx2gn2ei2ov3oae2ctx7lrxs6cjidfrrfe6zqno73vyjpcktza.b32.i2p as Peer8 15:38:59.063 INFO [Thread-380] Peer17 - Authed schx2gn2ei2ov3oae2ctx7lrxs6cjidfrrfe6zqno73vyjpcktza.b32.i2p as Peer8 15:38:59.174 INFO [Thread-67] Peer14 - Connecting to peer qesvfdvtjzi3r5ihvk2dskfb63s6os7sgjoxbxvdbfzo3z74ojxq.b32.i2p 15:38:59.174 INFO [Thread-73] Peer14 - Connecting to peer iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p 15:38:59.176 INFO [Thread-56] Peer14 - Connecting to peer qrca24rlefllwaj3dilluvsq75vndqaygoruwhoxupotbyr76gka.b32.i2p 15:38:59.179 INFO [Thread-294] Peer14 - Authed iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p as Peer16 15:38:59.179 INFO [Thread-294] Peer14 - Authed qesvfdvtjzi3r5ihvk2dskfb63s6os7sgjoxbxvdbfzo3z74ojxq.b32.i2p as Peer15 15:38:59.182 INFO [Thread-367] Peer16 - Authed jrwx5zmqrm5zau5bkqmtsdfc52vmpmeuk7prbhi2xferteevk7eq.b32.i2p as Peer14 15:38:59.182 INFO [Thread-330] Peer15 - Authed jrwx5zmqrm5zau5bkqmtsdfc52vmpmeuk7prbhi2xferteevk7eq.b32.i2p as Peer14 15:38:59.183 INFO [Thread-294] Peer14 - Authed qrca24rlefllwaj3dilluvsq75vndqaygoruwhoxupotbyr76gka.b32.i2p as Peer17 15:38:59.183 INFO [Thread-380] Peer17 - Authed jrwx5zmqrm5zau5bkqmtsdfc52vmpmeuk7prbhi2xferteevk7eq.b32.i2p as Peer14 15:38:59.782 INFO [Thread-73] Peer16 - Connecting to peer rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p 15:38:59.784 INFO [Thread-72] Peer3 - Connecting to peer qesvfdvtjzi3r5ihvk2dskfb63s6os7sgjoxbxvdbfzo3z74ojxq.b32.i2p 15:38:59.784 INFO [Thread-40] Peer5 - Connecting to peer iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p 15:38:59.785 WARN [Thread-368] Peer16 - Duplicated connection from rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p 15:38:59.786 INFO [Thread-39] Peer3 - Connecting to peer qrca24rlefllwaj3dilluvsq75vndqaygoruwhoxupotbyr76gka.b32.i2p 15:38:59.786 WARN [Thread-51] Peer5 - Duplicated connection from iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p 15:38:59.786 WARN [Thread-489] Peer5 - Socket from iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p closed due to IO exception 15:38:59.787 WARN [Thread-487] Peer16 - Socket from rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p closed due to IO exception 15:38:59.789 INFO [Thread-22] Peer3 - Authed qesvfdvtjzi3r5ihvk2dskfb63s6os7sgjoxbxvdbfzo3z74ojxq.b32.i2p as Peer15 15:38:59.789 INFO [Thread-22] Peer3 - Authed qrca24rlefllwaj3dilluvsq75vndqaygoruwhoxupotbyr76gka.b32.i2p as Peer17 15:38:59.790 INFO [Thread-380] Peer17 - Authed rf2y74dydm5zmk7bzgkcpu4fglzd6qhrax37tocswjwuupp4bnya.b32.i2p as Peer3 15:38:59.790 INFO [Thread-330] Peer15 - Authed rf2y74dydm5zmk7bzgkcpu4fglzd6qhrax37tocswjwuupp4bnya.b32.i2p as Peer3 15:38:59.791 INFO [Thread-72] Peer3 - Connecting to peer 34mu7guz63w2bzsh3fyjxtpz3evoi7wxaa5tv5ipecq3cnwtptsq.b32.i2p 15:38:59.802 INFO [Thread-22] Peer3 - Authed 34mu7guz63w2bzsh3fyjxtpz3evoi7wxaa5tv5ipecq3cnwtptsq.b32.i2p as Peer19 15:38:59.802 INFO [Thread-470] Peer19 - Authed rf2y74dydm5zmk7bzgkcpu4fglzd6qhrax37tocswjwuupp4bnya.b32.i2p as Peer3 15:39:00.795 INFO [Thread-67] Peer15 - Connecting to peer iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p 15:39:00.795 INFO [Thread-35] Peer5 - Connecting to peer iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p 15:39:00.795 INFO [Thread-74] Peer16 - Connecting to peer qesvfdvtjzi3r5ihvk2dskfb63s6os7sgjoxbxvdbfzo3z74ojxq.b32.i2p 15:39:00.795 INFO [Thread-62] Peer16 - Connecting to peer rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p 15:39:00.797 INFO [Thread-39] Peer9 - Connecting to peer qrca24rlefllwaj3dilluvsq75vndqaygoruwhoxupotbyr76gka.b32.i2p 15:39:00.798 WARN [Thread-51] Peer5 - Duplicated connection from iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p 15:39:00.798 WARN [Thread-368] Peer16 - Duplicated connection from rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p 15:39:00.799 WARN [Thread-496] Peer16 - Socket from rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p closed due to IO exception 15:39:00.799 WARN [Thread-495] Peer5 - Socket from iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p closed due to IO exception 15:39:00.799 WARN [Thread-368] Peer16 - Duplicated connection from qesvfdvtjzi3r5ihvk2dskfb63s6os7sgjoxbxvdbfzo3z74ojxq.b32.i2p 15:39:00.799 INFO [Thread-71] Peer9 - Connecting to peer 34mu7guz63w2bzsh3fyjxtpz3evoi7wxaa5tv5ipecq3cnwtptsq.b32.i2p 15:39:00.800 WARN [Thread-331] Peer15 - Duplicated connection from iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p 15:39:00.800 WARN [Thread-499] Peer15 - Socket from iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p closed due to IO exception 15:39:00.801 WARN [Thread-497] Peer16 - Socket from qesvfdvtjzi3r5ihvk2dskfb63s6os7sgjoxbxvdbfzo3z74ojxq.b32.i2p closed due to IO exception 15:39:00.803 INFO [Thread-133] Peer9 - Authed qrca24rlefllwaj3dilluvsq75vndqaygoruwhoxupotbyr76gka.b32.i2p as Peer17 15:39:00.804 INFO [Thread-380] Peer17 - Authed wnek7voalfcbm7wjxmny5jvfall2sgjigcumahajbm3ld2c7g5ta.b32.i2p as Peer9 15:39:00.804 INFO [Thread-133] Peer9 - Authed 34mu7guz63w2bzsh3fyjxtpz3evoi7wxaa5tv5ipecq3cnwtptsq.b32.i2p as Peer19 15:39:00.804 INFO [Thread-470] Peer19 - Authed wnek7voalfcbm7wjxmny5jvfall2sgjigcumahajbm3ld2c7g5ta.b32.i2p as Peer9 15:39:00.898 INFO [Thread-73] Peer15 - Connecting to peer 34mu7guz63w2bzsh3fyjxtpz3evoi7wxaa5tv5ipecq3cnwtptsq.b32.i2p 15:39:00.904 INFO [Thread-41] Peer15 - Connecting to peer iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p 15:39:00.905 INFO [Thread-56] Peer15 - Connecting to peer qrca24rlefllwaj3dilluvsq75vndqaygoruwhoxupotbyr76gka.b32.i2p 15:39:00.907 INFO [Thread-470] Peer19 - Authed qesvfdvtjzi3r5ihvk2dskfb63s6os7sgjoxbxvdbfzo3z74ojxq.b32.i2p as Peer15 15:39:00.907 INFO [Thread-330] Peer15 - Authed 34mu7guz63w2bzsh3fyjxtpz3evoi7wxaa5tv5ipecq3cnwtptsq.b32.i2p as Peer19 15:39:00.909 INFO [Thread-330] Peer15 - Authed iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p as Peer16 15:39:00.909 INFO [Thread-367] Peer16 - Authed qesvfdvtjzi3r5ihvk2dskfb63s6os7sgjoxbxvdbfzo3z74ojxq.b32.i2p as Peer15 15:39:00.910 INFO [Thread-330] Peer15 - Authed qrca24rlefllwaj3dilluvsq75vndqaygoruwhoxupotbyr76gka.b32.i2p as Peer17 15:39:00.910 INFO [Thread-380] Peer17 - Authed qesvfdvtjzi3r5ihvk2dskfb63s6os7sgjoxbxvdbfzo3z74ojxq.b32.i2p as Peer15 15:39:01.316 INFO [Thread-72] Peer16 - Connecting to peer rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p 15:39:01.317 INFO [Thread-40] Peer5 - Connecting to peer iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p 15:39:01.319 INFO [Thread-60] Peer4 - Connecting to peer qrca24rlefllwaj3dilluvsq75vndqaygoruwhoxupotbyr76gka.b32.i2p 15:39:01.319 WARN [Thread-368] Peer16 - Duplicated connection from rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p 15:39:01.320 INFO [Thread-41] Peer4 - Connecting to peer 34mu7guz63w2bzsh3fyjxtpz3evoi7wxaa5tv5ipecq3cnwtptsq.b32.i2p 15:39:01.320 WARN [Thread-51] Peer5 - Duplicated connection from iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p 15:39:01.320 WARN [Thread-510] Peer5 - Socket from iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p closed due to IO exception 15:39:01.321 WARN [Thread-509] Peer16 - Socket from rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p closed due to IO exception 15:39:01.323 INFO [Thread-29] Peer4 - Authed qrca24rlefllwaj3dilluvsq75vndqaygoruwhoxupotbyr76gka.b32.i2p as Peer17 15:39:01.323 INFO [Thread-380] Peer17 - Authed watq5tahqwzsgza2svjahhtsaifqbqxzdqt5ddhwbpdmur3usbca.b32.i2p as Peer4 15:39:01.323 INFO [Thread-29] Peer4 - Authed 34mu7guz63w2bzsh3fyjxtpz3evoi7wxaa5tv5ipecq3cnwtptsq.b32.i2p as Peer19 15:39:01.324 INFO [Thread-470] Peer19 - Authed watq5tahqwzsgza2svjahhtsaifqbqxzdqt5ddhwbpdmur3usbca.b32.i2p as Peer4 15:39:02.244 INFO [Thread-61] Peer5 - Connecting to peer iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p 15:39:02.244 INFO [Thread-75] Peer16 - Connecting to peer rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p 15:39:02.247 WARN [Thread-51] Peer5 - Duplicated connection from iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p 15:39:02.247 WARN [Thread-368] Peer16 - Duplicated connection from rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p 15:39:02.247 INFO [Thread-58] Peer10 - Connecting to peer qrca24rlefllwaj3dilluvsq75vndqaygoruwhoxupotbyr76gka.b32.i2p 15:39:02.247 WARN [Thread-516] Peer16 - Socket from rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p closed due to IO exception 15:39:02.248 WARN [Thread-515] Peer5 - Socket from iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p closed due to IO exception 15:39:02.252 INFO [Thread-39] Peer10 - Connecting to peer 34mu7guz63w2bzsh3fyjxtpz3evoi7wxaa5tv5ipecq3cnwtptsq.b32.i2p 15:39:02.264 INFO [Thread-169] Peer10 - Authed qrca24rlefllwaj3dilluvsq75vndqaygoruwhoxupotbyr76gka.b32.i2p as Peer17 15:39:02.264 INFO [Thread-380] Peer17 - Authed rhthygtrs7nmdsajl5l64xha325bxwa2pnf3mdavylrjelwktuyq.b32.i2p as Peer10 15:39:02.265 INFO [Thread-169] Peer10 - Authed 34mu7guz63w2bzsh3fyjxtpz3evoi7wxaa5tv5ipecq3cnwtptsq.b32.i2p as Peer19 15:39:02.265 INFO [Thread-470] Peer19 - Authed rhthygtrs7nmdsajl5l64xha325bxwa2pnf3mdavylrjelwktuyq.b32.i2p as Peer10 15:39:03.039 INFO [Thread-79] Peer5 - Connecting to peer iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p 15:39:03.040 INFO [Thread-37] Peer16 - Connecting to peer rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p 15:39:03.042 WARN [Thread-51] Peer5 - Duplicated connection from iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p 15:39:03.042 WARN [Thread-368] Peer16 - Duplicated connection from rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p 15:39:03.043 WARN [Thread-521] Peer5 - Socket from iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p closed due to IO exception 15:39:03.043 WARN [Thread-522] Peer16 - Socket from rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p closed due to IO exception 15:39:03.044 INFO [Thread-66] Peer0 - Connecting to peer 34mu7guz63w2bzsh3fyjxtpz3evoi7wxaa5tv5ipecq3cnwtptsq.b32.i2p 15:39:03.047 INFO [Thread-3] Peer0 - Authed 34mu7guz63w2bzsh3fyjxtpz3evoi7wxaa5tv5ipecq3cnwtptsq.b32.i2p as Peer19 15:39:03.048 INFO [Thread-470] Peer19 - Authed tpie4r2ggnxfliortm5itnxngamqcfb47623rfzwcus26u7atd7q.b32.i2p as Peer0 15:39:03.051 INFO [Thread-75] Peer0 - Connecting to peer qrca24rlefllwaj3dilluvsq75vndqaygoruwhoxupotbyr76gka.b32.i2p 15:39:03.053 INFO [Thread-3] Peer0 - Authed qrca24rlefllwaj3dilluvsq75vndqaygoruwhoxupotbyr76gka.b32.i2p as Peer17 15:39:03.053 INFO [Thread-380] Peer17 - Authed tpie4r2ggnxfliortm5itnxngamqcfb47623rfzwcus26u7atd7q.b32.i2p as Peer0 15:39:03.206 INFO [Thread-35] Peer5 - Connecting to peer iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p 15:39:03.207 INFO [Thread-67] Peer5 - Connecting to peer 34mu7guz63w2bzsh3fyjxtpz3evoi7wxaa5tv5ipecq3cnwtptsq.b32.i2p 15:39:03.226 INFO [Thread-68] Peer5 - Connecting to peer qrca24rlefllwaj3dilluvsq75vndqaygoruwhoxupotbyr76gka.b32.i2p 15:39:03.228 INFO [Thread-367] Peer16 - Authed rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p as Peer5 15:39:03.228 INFO [Thread-50] Peer5 - Authed iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p as Peer16 15:39:03.229 INFO [Thread-50] Peer5 - Authed 34mu7guz63w2bzsh3fyjxtpz3evoi7wxaa5tv5ipecq3cnwtptsq.b32.i2p as Peer19 15:39:03.229 INFO [Thread-470] Peer19 - Authed rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p as Peer5 15:39:03.229 INFO [Thread-50] Peer5 - Authed qrca24rlefllwaj3dilluvsq75vndqaygoruwhoxupotbyr76gka.b32.i2p as Peer17 15:39:03.230 INFO [Thread-380] Peer17 - Authed rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p as Peer5 15:39:03.383 INFO [Thread-57] Peer17 - Connecting to peer nrqyhc2ct4ngvh7qg5vjwzxxc3hxlmmap7k3kx3oelnco4bnde2a.b32.i2p 15:39:03.383 INFO [Thread-70] Peer17 - Connecting to peer 34mu7guz63w2bzsh3fyjxtpz3evoi7wxaa5tv5ipecq3cnwtptsq.b32.i2p 15:39:03.384 INFO [Thread-72] Peer17 - Connecting to peer qbyruqsi52v3heijxnxh5hvvtpsxerlkwwpqkirc45gymfjlpcca.b32.i2p 15:39:03.384 INFO [Thread-41] Peer2 - Connecting to peer 34mu7guz63w2bzsh3fyjxtpz3evoi7wxaa5tv5ipecq3cnwtptsq.b32.i2p 15:39:03.384 INFO [Thread-78] Peer19 - Connecting to peer gij4i6myrxluz7idq5rmroghy4zhjodpqx76mjnhzxmys6hgeqsa.b32.i2p 15:39:03.385 INFO [Thread-76] Peer19 - Connecting to peer qrca24rlefllwaj3dilluvsq75vndqaygoruwhoxupotbyr76gka.b32.i2p 15:39:03.385 INFO [Thread-69] Peer19 - Connecting to peer j46fcgrfzyfta45nofbbw6e63e342zsa4awdm7aac6xdhhso6jeq.b32.i2p 15:39:03.385 INFO [Thread-75] Peer19 - Connecting to peer bllxyyq35xiubizjhimwhjmsfhlwpn5bdqx73jldbzdrbhemjm6a.b32.i2p 15:39:03.386 INFO [Thread-67] Peer19 - Connecting to peer i4hz7f7tit7iz3bnfdz3di2fryqc43a7ufslavc2ubo74u2jb3qq.b32.i2p 15:39:03.386 INFO [Thread-73] Peer19 - Connecting to peer jrwx5zmqrm5zau5bkqmtsdfc52vmpmeuk7prbhi2xferteevk7eq.b32.i2p 15:39:03.386 INFO [Thread-63] Peer19 - Connecting to peer schx2gn2ei2ov3oae2ctx7lrxs6cjidfrrfe6zqno73vyjpcktza.b32.i2p 15:39:03.386 INFO [Thread-59] Peer19 - Connecting to peer qbyruqsi52v3heijxnxh5hvvtpsxerlkwwpqkirc45gymfjlpcca.b32.i2p 15:39:03.386 INFO [Thread-64] Peer6 - Connecting to peer 34mu7guz63w2bzsh3fyjxtpz3evoi7wxaa5tv5ipecq3cnwtptsq.b32.i2p 15:39:03.387 INFO [Thread-60] Peer12 - Connecting to peer 34mu7guz63w2bzsh3fyjxtpz3evoi7wxaa5tv5ipecq3cnwtptsq.b32.i2p 15:39:03.389 INFO [Thread-72] Peer1 - Connecting to peer 34mu7guz63w2bzsh3fyjxtpz3evoi7wxaa5tv5ipecq3cnwtptsq.b32.i2p 15:39:03.389 WARN [Thread-16] Peer2 - Duplicated connection from 34mu7guz63w2bzsh3fyjxtpz3evoi7wxaa5tv5ipecq3cnwtptsq.b32.i2p 15:39:03.389 INFO [Thread-66] Peer19 - Connecting to peer nrqyhc2ct4ngvh7qg5vjwzxxc3hxlmmap7k3kx3oelnco4bnde2a.b32.i2p 15:39:03.389 INFO [Thread-39] Peer19 - Connecting to peer ofbpsthifxk7oogt6rwk4lhukzsoljmrmchhtkrnraj6nkgefj6q.b32.i2p 15:39:03.390 INFO [Thread-192] Peer11 - Authed qrca24rlefllwaj3dilluvsq75vndqaygoruwhoxupotbyr76gka.b32.i2p as Peer17 15:39:03.391 INFO [Thread-380] Peer17 - Authed nrqyhc2ct4ngvh7qg5vjwzxxc3hxlmmap7k3kx3oelnco4bnde2a.b32.i2p as Peer1 15:39:03.391 INFO [Thread-380] Peer17 - Authed qbyruqsi52v3heijxnxh5hvvtpsxerlkwwpqkirc45gymfjlpcca.b32.i2p as Peer11 15:39:03.391 INFO [Thread-253] Peer13 - Authed 34mu7guz63w2bzsh3fyjxtpz3evoi7wxaa5tv5ipecq3cnwtptsq.b32.i2p as Peer19 15:39:03.391 INFO [Thread-77] Peer16 - Connecting to peer yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p 15:39:03.392 INFO [Thread-120] Peer8 - Authed 34mu7guz63w2bzsh3fyjxtpz3evoi7wxaa5tv5ipecq3cnwtptsq.b32.i2p as Peer19 15:39:03.392 WARN [Thread-471] Peer19 - Duplicated connection from i4hz7f7tit7iz3bnfdz3di2fryqc43a7ufslavc2ubo74u2jb3qq.b32.i2p 15:39:03.392 INFO [Thread-8] Peer1 - Authed qrca24rlefllwaj3dilluvsq75vndqaygoruwhoxupotbyr76gka.b32.i2p as Peer17 15:39:03.393 WARN [Thread-550] Peer19 - Socket from i4hz7f7tit7iz3bnfdz3di2fryqc43a7ufslavc2ubo74u2jb3qq.b32.i2p closed due to IO exception 15:39:03.393 WARN [Thread-537] Peer2 - Socket from 34mu7guz63w2bzsh3fyjxtpz3evoi7wxaa5tv5ipecq3cnwtptsq.b32.i2p closed due to IO exception 15:39:03.393 WARN [Thread-471] Peer19 - Duplicated connection from qrca24rlefllwaj3dilluvsq75vndqaygoruwhoxupotbyr76gka.b32.i2p 15:39:03.394 WARN [Thread-381] Peer17 - Duplicated connection from 34mu7guz63w2bzsh3fyjxtpz3evoi7wxaa5tv5ipecq3cnwtptsq.b32.i2p 15:39:03.394 WARN [Thread-471] Peer19 - Duplicated connection from bllxyyq35xiubizjhimwhjmsfhlwpn5bdqx73jldbzdrbhemjm6a.b32.i2p 15:39:03.395 WARN [Thread-554] Peer17 - Socket from 34mu7guz63w2bzsh3fyjxtpz3evoi7wxaa5tv5ipecq3cnwtptsq.b32.i2p closed due to IO exception 15:39:03.395 INFO [Thread-294] Peer14 - Authed 34mu7guz63w2bzsh3fyjxtpz3evoi7wxaa5tv5ipecq3cnwtptsq.b32.i2p as Peer19 15:39:03.395 INFO [Thread-192] Peer11 - Authed 34mu7guz63w2bzsh3fyjxtpz3evoi7wxaa5tv5ipecq3cnwtptsq.b32.i2p as Peer19 15:39:03.395 WARN [Thread-541] Peer19 - Socket from qrca24rlefllwaj3dilluvsq75vndqaygoruwhoxupotbyr76gka.b32.i2p closed due to IO exception 15:39:03.395 WARN [Thread-100] Peer6 - Duplicated connection from 34mu7guz63w2bzsh3fyjxtpz3evoi7wxaa5tv5ipecq3cnwtptsq.b32.i2p 15:39:03.396 WARN [Thread-555] Peer6 - Socket from 34mu7guz63w2bzsh3fyjxtpz3evoi7wxaa5tv5ipecq3cnwtptsq.b32.i2p closed due to IO exception 15:39:03.396 WARN [Thread-471] Peer19 - Duplicated connection from j46fcgrfzyfta45nofbbw6e63e342zsa4awdm7aac6xdhhso6jeq.b32.i2p 15:39:03.396 INFO [Thread-367] Peer16 - Authed yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p as Peer18 15:39:03.396 INFO [Thread-437] Peer18 - Authed iwnx3fejnv4mngilk2t4yaab2jlchocp4fxgif5xjtjjxcsymyxa.b32.i2p as Peer16 15:39:03.396 WARN [Thread-545] Peer19 - Socket from bllxyyq35xiubizjhimwhjmsfhlwpn5bdqx73jldbzdrbhemjm6a.b32.i2p closed due to IO exception 15:39:03.396 INFO [Thread-470] Peer19 - Authed gij4i6myrxluz7idq5rmroghy4zhjodpqx76mjnhzxmys6hgeqsa.b32.i2p as Peer13 15:39:03.397 INFO [Thread-470] Peer19 - Authed jrwx5zmqrm5zau5bkqmtsdfc52vmpmeuk7prbhi2xferteevk7eq.b32.i2p as Peer14 15:39:03.397 WARN [Thread-471] Peer19 - Duplicated connection from nrqyhc2ct4ngvh7qg5vjwzxxc3hxlmmap7k3kx3oelnco4bnde2a.b32.i2p 15:39:03.397 INFO [Thread-106] Peer7 - Authed 34mu7guz63w2bzsh3fyjxtpz3evoi7wxaa5tv5ipecq3cnwtptsq.b32.i2p as Peer19 15:39:03.397 WARN [Thread-239] Peer12 - Duplicated connection from 34mu7guz63w2bzsh3fyjxtpz3evoi7wxaa5tv5ipecq3cnwtptsq.b32.i2p 15:39:03.397 INFO [Thread-470] Peer19 - Authed schx2gn2ei2ov3oae2ctx7lrxs6cjidfrrfe6zqno73vyjpcktza.b32.i2p as Peer8 15:39:03.397 WARN [Thread-557] Peer12 - Socket from 34mu7guz63w2bzsh3fyjxtpz3evoi7wxaa5tv5ipecq3cnwtptsq.b32.i2p closed due to IO exception 15:39:03.397 INFO [Thread-470] Peer19 - Authed qbyruqsi52v3heijxnxh5hvvtpsxerlkwwpqkirc45gymfjlpcca.b32.i2p as Peer11 15:39:03.397 INFO [Thread-470] Peer19 - Authed ofbpsthifxk7oogt6rwk4lhukzsoljmrmchhtkrnraj6nkgefj6q.b32.i2p as Peer7 15:39:03.398 WARN [Thread-9] Peer1 - Duplicated connection from 34mu7guz63w2bzsh3fyjxtpz3evoi7wxaa5tv5ipecq3cnwtptsq.b32.i2p 15:39:03.398 WARN [Thread-558] Peer1 - Socket from 34mu7guz63w2bzsh3fyjxtpz3evoi7wxaa5tv5ipecq3cnwtptsq.b32.i2p closed due to IO exception 15:39:03.398 WARN [Thread-544] Peer19 - Socket from j46fcgrfzyfta45nofbbw6e63e342zsa4awdm7aac6xdhhso6jeq.b32.i2p closed due to IO exception 15:39:03.398 WARN [Thread-552] Peer19 - Socket from nrqyhc2ct4ngvh7qg5vjwzxxc3hxlmmap7k3kx3oelnco4bnde2a.b32.i2p closed due to IO exception 15:39:04.009 INFO [Thread-40] Peer6 - Connecting to peer 34mu7guz63w2bzsh3fyjxtpz3evoi7wxaa5tv5ipecq3cnwtptsq.b32.i2p 15:39:04.009 INFO [Thread-62] Peer12 - Connecting to peer 34mu7guz63w2bzsh3fyjxtpz3evoi7wxaa5tv5ipecq3cnwtptsq.b32.i2p 15:39:04.010 INFO [Thread-68] Peer2 - Connecting to peer 34mu7guz63w2bzsh3fyjxtpz3evoi7wxaa5tv5ipecq3cnwtptsq.b32.i2p 15:39:04.011 INFO [Thread-65] Peer17 - Connecting to peer 34mu7guz63w2bzsh3fyjxtpz3evoi7wxaa5tv5ipecq3cnwtptsq.b32.i2p 15:39:04.012 INFO [Thread-74] Peer19 - Connecting to peer nrqyhc2ct4ngvh7qg5vjwzxxc3hxlmmap7k3kx3oelnco4bnde2a.b32.i2p 15:39:04.012 INFO [Thread-42] Peer19 - Connecting to peer i4hz7f7tit7iz3bnfdz3di2fryqc43a7ufslavc2ubo74u2jb3qq.b32.i2p 15:39:04.013 INFO [Thread-99] Peer6 - Authed 34mu7guz63w2bzsh3fyjxtpz3evoi7wxaa5tv5ipecq3cnwtptsq.b32.i2p as Peer19 15:39:04.014 WARN [Thread-16] Peer2 - Duplicated connection from 34mu7guz63w2bzsh3fyjxtpz3evoi7wxaa5tv5ipecq3cnwtptsq.b32.i2p 15:39:04.014 INFO [Thread-238] Peer12 - Authed 34mu7guz63w2bzsh3fyjxtpz3evoi7wxaa5tv5ipecq3cnwtptsq.b32.i2p as Peer19 15:39:04.014 INFO [Thread-76] Peer19 - Connecting to peer qrca24rlefllwaj3dilluvsq75vndqaygoruwhoxupotbyr76gka.b32.i2p 15:39:04.014 INFO [Thread-470] Peer19 - Authed bllxyyq35xiubizjhimwhjmsfhlwpn5bdqx73jldbzdrbhemjm6a.b32.i2p as Peer6 15:39:04.015 INFO [Thread-470] Peer19 - Authed j46fcgrfzyfta45nofbbw6e63e342zsa4awdm7aac6xdhhso6jeq.b32.i2p as Peer12 15:39:04.015 WARN [Thread-471] Peer19 - Duplicated connection from i4hz7f7tit7iz3bnfdz3di2fryqc43a7ufslavc2ubo74u2jb3qq.b32.i2p 15:39:04.015 INFO [Thread-8] Peer1 - Authed 34mu7guz63w2bzsh3fyjxtpz3evoi7wxaa5tv5ipecq3cnwtptsq.b32.i2p as Peer19 15:39:04.016 INFO [Thread-470] Peer19 - Authed nrqyhc2ct4ngvh7qg5vjwzxxc3hxlmmap7k3kx3oelnco4bnde2a.b32.i2p as Peer1 15:39:04.016 INFO [Thread-40] Peer11 - Connecting to peer yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p 15:39:04.016 WARN [Thread-566] Peer19 - Socket from i4hz7f7tit7iz3bnfdz3di2fryqc43a7ufslavc2ubo74u2jb3qq.b32.i2p closed due to IO exception 15:39:04.016 WARN [Thread-564] Peer2 - Socket from 34mu7guz63w2bzsh3fyjxtpz3evoi7wxaa5tv5ipecq3cnwtptsq.b32.i2p closed due to IO exception 15:39:04.017 WARN [Thread-381] Peer17 - Duplicated connection from 34mu7guz63w2bzsh3fyjxtpz3evoi7wxaa5tv5ipecq3cnwtptsq.b32.i2p 15:39:04.017 WARN [Thread-471] Peer19 - Duplicated connection from qrca24rlefllwaj3dilluvsq75vndqaygoruwhoxupotbyr76gka.b32.i2p 15:39:04.018 WARN [Thread-568] Peer19 - Socket from qrca24rlefllwaj3dilluvsq75vndqaygoruwhoxupotbyr76gka.b32.i2p closed due to IO exception 15:39:04.018 WARN [Thread-567] Peer17 - Socket from 34mu7guz63w2bzsh3fyjxtpz3evoi7wxaa5tv5ipecq3cnwtptsq.b32.i2p closed due to IO exception 15:39:04.019 INFO [Thread-192] Peer11 - Authed yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p as Peer18 15:39:04.019 INFO [Thread-437] Peer18 - Authed qbyruqsi52v3heijxnxh5hvvtpsxerlkwwpqkirc45gymfjlpcca.b32.i2p as Peer11 15:39:04.792 INFO [Thread-71] Peer1 - Connecting to peer yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p 15:39:04.792 INFO [Thread-37] Peer0 - Connecting to peer yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p 15:39:04.795 INFO [Thread-57] Peer17 - Connecting to peer 34mu7guz63w2bzsh3fyjxtpz3evoi7wxaa5tv5ipecq3cnwtptsq.b32.i2p 15:39:04.795 INFO [Thread-39] Peer13 - Connecting to peer yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p 15:39:04.796 INFO [Thread-78] Peer6 - Connecting to peer yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p 15:39:04.796 INFO [Thread-437] Peer18 - Authed tpie4r2ggnxfliortm5itnxngamqcfb47623rfzwcus26u7atd7q.b32.i2p as Peer0 15:39:04.796 INFO [Thread-68] Peer9 - Connecting to peer yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p 15:39:04.796 INFO [Thread-58] Peer3 - Connecting to peer yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p 15:39:04.796 INFO [Thread-79] Peer15 - Connecting to peer yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p 15:39:04.796 INFO [Thread-67] Peer18 - Connecting to peer ofbpsthifxk7oogt6rwk4lhukzsoljmrmchhtkrnraj6nkgefj6q.b32.i2p 15:39:04.796 INFO [Thread-60] Peer4 - Connecting to peer yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p 15:39:04.797 INFO [Thread-56] Peer18 - Connecting to peer qesvfdvtjzi3r5ihvk2dskfb63s6os7sgjoxbxvdbfzo3z74ojxq.b32.i2p 15:39:04.797 INFO [Thread-72] Peer18 - Connecting to peer bllxyyq35xiubizjhimwhjmsfhlwpn5bdqx73jldbzdrbhemjm6a.b32.i2p 15:39:04.797 INFO [Thread-64] Peer18 - Connecting to peer watq5tahqwzsgza2svjahhtsaifqbqxzdqt5ddhwbpdmur3usbca.b32.i2p 15:39:04.797 INFO [Thread-66] Peer18 - Connecting to peer j46fcgrfzyfta45nofbbw6e63e342zsa4awdm7aac6xdhhso6jeq.b32.i2p 15:39:04.797 INFO [Thread-70] Peer18 - Connecting to peer i4hz7f7tit7iz3bnfdz3di2fryqc43a7ufslavc2ubo74u2jb3qq.b32.i2p 15:39:04.797 INFO [Thread-437] Peer18 - Authed nrqyhc2ct4ngvh7qg5vjwzxxc3hxlmmap7k3kx3oelnco4bnde2a.b32.i2p as Peer1 15:39:04.797 INFO [Thread-76] Peer18 - Connecting to peer jrwx5zmqrm5zau5bkqmtsdfc52vmpmeuk7prbhi2xferteevk7eq.b32.i2p 15:39:04.797 INFO [Thread-63] Peer18 - Connecting to peer schx2gn2ei2ov3oae2ctx7lrxs6cjidfrrfe6zqno73vyjpcktza.b32.i2p 15:39:04.798 INFO [Thread-59] Peer18 - Connecting to peer rhthygtrs7nmdsajl5l64xha325bxwa2pnf3mdavylrjelwktuyq.b32.i2p 15:39:04.798 INFO [Thread-37] Peer14 - Connecting to peer yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p 15:39:04.798 INFO [Thread-73] Peer8 - Connecting to peer yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p 15:39:04.799 INFO [Thread-75] Peer5 - Connecting to peer yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p 15:39:04.800 INFO [Thread-470] Peer19 - Authed qrca24rlefllwaj3dilluvsq75vndqaygoruwhoxupotbyr76gka.b32.i2p as Peer17 15:39:04.800 INFO [Thread-380] Peer17 - Authed 34mu7guz63w2bzsh3fyjxtpz3evoi7wxaa5tv5ipecq3cnwtptsq.b32.i2p as Peer19 15:39:04.801 INFO [Thread-437] Peer18 - Authed ofbpsthifxk7oogt6rwk4lhukzsoljmrmchhtkrnraj6nkgefj6q.b32.i2p as Peer7 15:39:04.801 INFO [Thread-74] Peer18 - Connecting to peer rf2y74dydm5zmk7bzgkcpu4fglzd6qhrax37tocswjwuupp4bnya.b32.i2p 15:39:04.801 INFO [Thread-42] Peer18 - Connecting to peer rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p 15:39:04.801 INFO [Thread-253] Peer13 - Authed yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p as Peer18 15:39:04.802 INFO [Thread-8] Peer1 - Authed yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p as Peer18 15:39:04.802 WARN [Thread-100] Peer6 - Duplicated connection from yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p 15:39:04.802 WARN [Thread-438] Peer18 - Duplicated connection from bllxyyq35xiubizjhimwhjmsfhlwpn5bdqx73jldbzdrbhemjm6a.b32.i2p 15:39:04.803 INFO [Thread-437] Peer18 - Authed gij4i6myrxluz7idq5rmroghy4zhjodpqx76mjnhzxmys6hgeqsa.b32.i2p as Peer13 15:39:04.803 INFO [Thread-437] Peer18 - Authed i4hz7f7tit7iz3bnfdz3di2fryqc43a7ufslavc2ubo74u2jb3qq.b32.i2p as Peer2 15:39:04.803 WARN [Thread-588] Peer6 - Socket from yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p closed due to IO exception 15:39:04.803 INFO [Thread-437] Peer18 - Authed j46fcgrfzyfta45nofbbw6e63e342zsa4awdm7aac6xdhhso6jeq.b32.i2p as Peer12 15:39:04.803 WARN [Thread-438] Peer18 - Duplicated connection from qesvfdvtjzi3r5ihvk2dskfb63s6os7sgjoxbxvdbfzo3z74ojxq.b32.i2p 15:39:04.803 INFO [Thread-238] Peer12 - Authed yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p as Peer18 15:39:04.804 INFO [Thread-437] Peer18 - Authed rhthygtrs7nmdsajl5l64xha325bxwa2pnf3mdavylrjelwktuyq.b32.i2p as Peer10 15:39:04.804 INFO [Thread-169] Peer10 - Authed yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p as Peer18 15:39:04.804 WARN [Thread-331] Peer15 - Duplicated connection from yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p 15:39:04.804 INFO [Thread-38] Peer18 - Connecting to peer wnek7voalfcbm7wjxmny5jvfall2sgjigcumahajbm3ld2c7g5ta.b32.i2p 15:39:04.805 WARN [Thread-593] Peer15 - Socket from yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p closed due to IO exception 15:39:04.805 INFO [Thread-15] Peer2 - Authed yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p as Peer18 15:39:04.805 INFO [Thread-106] Peer7 - Authed yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p as Peer18 15:39:04.805 WARN [Thread-23] Peer3 - Duplicated connection from yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p 15:39:04.805 WARN [Thread-589] Peer18 - Socket from bllxyyq35xiubizjhimwhjmsfhlwpn5bdqx73jldbzdrbhemjm6a.b32.i2p closed due to IO exception 15:39:04.806 WARN [Thread-590] Peer18 - Socket from qesvfdvtjzi3r5ihvk2dskfb63s6os7sgjoxbxvdbfzo3z74ojxq.b32.i2p closed due to IO exception 15:39:04.806 WARN [Thread-594] Peer18 - Socket from rf2y74dydm5zmk7bzgkcpu4fglzd6qhrax37tocswjwuupp4bnya.b32.i2p closed due to IO exception 15:39:04.807 INFO [Thread-437] Peer18 - Authed rf2y74dydm5zmk7bzgkcpu4fglzd6qhrax37tocswjwuupp4bnya.b32.i2p as Peer3 15:39:04.807 WARN [Thread-438] Peer18 - Duplicated connection from wnek7voalfcbm7wjxmny5jvfall2sgjigcumahajbm3ld2c7g5ta.b32.i2p 15:39:04.807 INFO [Thread-22] Peer3 - Authed yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p as Peer18 15:39:04.807 WARN [Thread-438] Peer18 - Duplicated connection from watq5tahqwzsgza2svjahhtsaifqbqxzdqt5ddhwbpdmur3usbca.b32.i2p 15:39:04.808 WARN [Thread-438] Peer18 - Duplicated connection from jrwx5zmqrm5zau5bkqmtsdfc52vmpmeuk7prbhi2xferteevk7eq.b32.i2p 15:39:04.808 WARN [Thread-30] Peer4 - Duplicated connection from yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p 15:39:04.808 WARN [Thread-599] Peer4 - Socket from yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p closed due to IO exception 15:39:04.809 WARN [Thread-295] Peer14 - Duplicated connection from yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p 15:39:04.809 WARN [Thread-134] Peer9 - Duplicated connection from yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p 15:39:04.809 WARN [Thread-600] Peer14 - Socket from yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p closed due to IO exception 15:39:04.809 WARN [Thread-601] Peer9 - Socket from yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p closed due to IO exception 15:39:04.809 WARN [Thread-438] Peer18 - Duplicated connection from schx2gn2ei2ov3oae2ctx7lrxs6cjidfrrfe6zqno73vyjpcktza.b32.i2p 15:39:04.810 WARN [Thread-585] Peer18 - Socket from watq5tahqwzsgza2svjahhtsaifqbqxzdqt5ddhwbpdmur3usbca.b32.i2p closed due to IO exception 15:39:04.810 WARN [Thread-121] Peer8 - Duplicated connection from yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p 15:39:04.810 WARN [Thread-602] Peer8 - Socket from yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p closed due to IO exception 15:39:04.810 WARN [Thread-586] Peer18 - Socket from jrwx5zmqrm5zau5bkqmtsdfc52vmpmeuk7prbhi2xferteevk7eq.b32.i2p closed due to IO exception 15:39:04.811 WARN [Thread-438] Peer18 - Duplicated connection from rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p 15:39:04.811 WARN [Thread-587] Peer18 - Socket from schx2gn2ei2ov3oae2ctx7lrxs6cjidfrrfe6zqno73vyjpcktza.b32.i2p closed due to IO exception 15:39:04.811 WARN [Thread-597] Peer18 - Socket from wnek7voalfcbm7wjxmny5jvfall2sgjigcumahajbm3ld2c7g5ta.b32.i2p closed due to IO exception 15:39:04.811 WARN [Thread-51] Peer5 - Duplicated connection from yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p 15:39:04.811 WARN [Thread-603] Peer5 - Socket from yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p closed due to IO exception 15:39:04.812 WARN [Thread-595] Peer18 - Socket from rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p closed due to IO exception 15:39:04.836 INFO [Thread-3] Peer0 - Authed yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p as Peer18 15:39:05.470 INFO [Thread-72] Peer18 - Connecting to peer wnek7voalfcbm7wjxmny5jvfall2sgjigcumahajbm3ld2c7g5ta.b32.i2p 15:39:05.471 INFO [Thread-76] Peer18 - Connecting to peer qesvfdvtjzi3r5ihvk2dskfb63s6os7sgjoxbxvdbfzo3z74ojxq.b32.i2p 15:39:05.471 INFO [Thread-41] Peer18 - Connecting to peer bllxyyq35xiubizjhimwhjmsfhlwpn5bdqx73jldbzdrbhemjm6a.b32.i2p 15:39:05.471 INFO [Thread-36] Peer18 - Connecting to peer watq5tahqwzsgza2svjahhtsaifqbqxzdqt5ddhwbpdmur3usbca.b32.i2p 15:39:05.471 INFO [Thread-73] Peer18 - Connecting to peer 34mu7guz63w2bzsh3fyjxtpz3evoi7wxaa5tv5ipecq3cnwtptsq.b32.i2p 15:39:05.471 INFO [Thread-57] Peer18 - Connecting to peer rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p 15:39:05.471 INFO [Thread-67] Peer18 - Connecting to peer schx2gn2ei2ov3oae2ctx7lrxs6cjidfrrfe6zqno73vyjpcktza.b32.i2p 15:39:05.471 INFO [Thread-42] Peer15 - Connecting to peer yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p 15:39:05.471 INFO [Thread-68] Peer19 - Connecting to peer yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p 15:39:05.471 INFO [Thread-79] Peer19 - Connecting to peer i4hz7f7tit7iz3bnfdz3di2fryqc43a7ufslavc2ubo74u2jb3qq.b32.i2p 15:39:05.471 INFO [Thread-37] Peer18 - Connecting to peer jrwx5zmqrm5zau5bkqmtsdfc52vmpmeuk7prbhi2xferteevk7eq.b32.i2p 15:39:05.472 INFO [Thread-40] Peer6 - Connecting to peer yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p 15:39:05.472 INFO [Thread-60] Peer5 - Connecting to peer yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p 15:39:05.474 INFO [Thread-75] Peer14 - Connecting to peer yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p 15:39:05.474 WARN [Thread-438] Peer18 - Duplicated connection from 34mu7guz63w2bzsh3fyjxtpz3evoi7wxaa5tv5ipecq3cnwtptsq.b32.i2p 15:39:05.475 WARN [Thread-471] Peer19 - Duplicated connection from yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p 15:39:05.475 INFO [Thread-29] Peer4 - Authed yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p as Peer18 15:39:05.475 INFO [Thread-437] Peer18 - Authed watq5tahqwzsgza2svjahhtsaifqbqxzdqt5ddhwbpdmur3usbca.b32.i2p as Peer4 15:39:05.475 WARN [Thread-612] Peer19 - Socket from yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p closed due to IO exception 15:39:05.475 INFO [Thread-437] Peer18 - Authed wnek7voalfcbm7wjxmny5jvfall2sgjigcumahajbm3ld2c7g5ta.b32.i2p as Peer9 15:39:05.475 INFO [Thread-133] Peer9 - Authed yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p as Peer18 15:39:05.476 INFO [Thread-437] Peer18 - Authed schx2gn2ei2ov3oae2ctx7lrxs6cjidfrrfe6zqno73vyjpcktza.b32.i2p as Peer8 15:39:05.476 INFO [Thread-120] Peer8 - Authed yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p as Peer18 15:39:05.476 WARN [Thread-331] Peer15 - Duplicated connection from yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p 15:39:05.476 WARN [Thread-438] Peer18 - Duplicated connection from qesvfdvtjzi3r5ihvk2dskfb63s6os7sgjoxbxvdbfzo3z74ojxq.b32.i2p 15:39:05.476 INFO [Thread-15] Peer2 - Authed 34mu7guz63w2bzsh3fyjxtpz3evoi7wxaa5tv5ipecq3cnwtptsq.b32.i2p as Peer19 15:39:05.477 WARN [Thread-615] Peer15 - Socket from yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p closed due to IO exception 15:39:05.477 WARN [Thread-438] Peer18 - Duplicated connection from rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p 15:39:05.477 WARN [Thread-608] Peer18 - Socket from 34mu7guz63w2bzsh3fyjxtpz3evoi7wxaa5tv5ipecq3cnwtptsq.b32.i2p closed due to IO exception 15:39:05.477 WARN [Thread-616] Peer18 - Socket from qesvfdvtjzi3r5ihvk2dskfb63s6os7sgjoxbxvdbfzo3z74ojxq.b32.i2p closed due to IO exception 15:39:05.478 WARN [Thread-51] Peer5 - Duplicated connection from yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p 15:39:05.478 WARN [Thread-619] Peer5 - Socket from yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p closed due to IO exception 15:39:05.478 WARN [Thread-438] Peer18 - Duplicated connection from bllxyyq35xiubizjhimwhjmsfhlwpn5bdqx73jldbzdrbhemjm6a.b32.i2p 15:39:05.479 WARN [Thread-610] Peer18 - Socket from rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p closed due to IO exception 15:39:05.479 INFO [Thread-470] Peer19 - Authed i4hz7f7tit7iz3bnfdz3di2fryqc43a7ufslavc2ubo74u2jb3qq.b32.i2p as Peer2 15:39:05.479 WARN [Thread-438] Peer18 - Duplicated connection from jrwx5zmqrm5zau5bkqmtsdfc52vmpmeuk7prbhi2xferteevk7eq.b32.i2p 15:39:05.479 WARN [Thread-100] Peer6 - Duplicated connection from yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p 15:39:05.479 WARN [Thread-620] Peer6 - Socket from yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p closed due to IO exception 15:39:05.480 WARN [Thread-295] Peer14 - Duplicated connection from yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p 15:39:05.480 WARN [Thread-617] Peer18 - Socket from bllxyyq35xiubizjhimwhjmsfhlwpn5bdqx73jldbzdrbhemjm6a.b32.i2p closed due to IO exception 15:39:05.480 WARN [Thread-621] Peer14 - Socket from yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p closed due to IO exception 15:39:05.480 WARN [Thread-618] Peer18 - Socket from jrwx5zmqrm5zau5bkqmtsdfc52vmpmeuk7prbhi2xferteevk7eq.b32.i2p closed due to IO exception 15:39:05.698 INFO [Thread-65] Peer6 - Connecting to peer yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p 15:39:05.704 INFO [Thread-99] Peer6 - Authed yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p as Peer18 15:39:05.704 INFO [Thread-437] Peer18 - Authed bllxyyq35xiubizjhimwhjmsfhlwpn5bdqx73jldbzdrbhemjm6a.b32.i2p as Peer6 15:39:06.101 INFO [Thread-77] Peer14 - Connecting to peer yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p 15:39:06.103 INFO [Thread-65] Peer15 - Connecting to peer yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p 15:39:06.103 INFO [Thread-71] Peer19 - Connecting to peer yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p 15:39:06.104 INFO [Thread-437] Peer18 - Authed jrwx5zmqrm5zau5bkqmtsdfc52vmpmeuk7prbhi2xferteevk7eq.b32.i2p as Peer14 15:39:06.106 INFO [Thread-437] Peer18 - Authed qesvfdvtjzi3r5ihvk2dskfb63s6os7sgjoxbxvdbfzo3z74ojxq.b32.i2p as Peer15 15:39:06.107 INFO [Thread-437] Peer18 - Authed 34mu7guz63w2bzsh3fyjxtpz3evoi7wxaa5tv5ipecq3cnwtptsq.b32.i2p as Peer19 15:39:06.112 INFO [Thread-294] Peer14 - Authed yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p as Peer18 15:39:06.114 INFO [Thread-330] Peer15 - Authed yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p as Peer18 15:39:06.115 INFO [Thread-35] Peer18 - Connecting to peer rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p 15:39:06.116 INFO [Thread-470] Peer19 - Authed yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p as Peer18 15:39:06.117 INFO [Thread-437] Peer18 - Authed rppvgkvdgqxio62yczjnawsdrft6xwrwiv4bfll4fo42p3dwml5a.b32.i2p as Peer5 15:39:06.117 INFO [Thread-50] Peer5 - Authed yfifmh3fohy5ki6vdn2zua6carlbwyi44pqywj6a5vnnbwrjnueq.b32.i2p as Peer18 15:39:06.118 INFO [main] i.s.i.p.c.e.JoinPeersExample - Every peer knows 19 peers!在日志里你会看到各个节点相互连接、认证，交换PEX，然后连接新的节点。其中你会注意到有些节点报出了Duplicated connection，并且另一些节点发现他们的连接因为IO错误中断，这就是我在上一篇文章中提到的极端状况。因为在测试用我们的节点都是在一台电脑上跑的，因为多线程的缘故，服务端线程更容易被其他线程抢走控制权，从而引发这种问题。但好在由于节点创建的时间差和线程调度的随机性，这种问题可以通过下一次PEX交换来解决。Minecraft模组其实一开始推动我学Java的，就是想给Minecraft写模组。没想到学了这么多年Java，这才写了自己的第一个模组。由于版本是1.19.2，所以我选了Fabric框架（其实Forge也没差，但是Forge社区似乎对于旧版本不太友好，我比较膈应这一点）。我觉得在MC模组历史上，一个划时代的产物就是Mixin的出现。在Mixin之前，我们能做的似乎就只有通过调用Mojang的代码来完成操作，如果我们想拦截什么东西，就必须引导用户在我们模组内进行操作。像是聊天拦截这种事情就会变得很难做。而Mixin则赋予了开发者操作原版类的能力：现在我们可以直接将我们的代码混入Mojang的代码中，对原版MC的机制进行修改。例如我们可以修改参与发送消息的代码，让我们的代码拦截到用户即将发送的消息，并提前结束这个处理流程。不过Mixin并非十全十美。首先一点就是兼容性的问题：如果一个mod依赖原版的某一个操作，而你的代码刚好干扰了这个流程，那很可能就会引发异常或者崩溃，这一点没办法在编写和加载mod时发现，只能在运行时阶段观测到。此外由于Minecraft的代码是经过混淆的，所以这就导致了大家对于命名的分歧。以前有MCP，现在有YARN，这两个都是社区推动的，独立于Mojang的反编译。也就是根据代码的做用，为每一个变量尽可能安排一个有意义的名字。为什么说“独立于Mojang”呢？因为从某个版本开始，Mojang也开始提供他们的命名映射了，但是这个映射使用起来并不开放（你需要遵守Mojang的协议），并且这对开发者消化他们的意大利面并没有什么太多帮助，所以大部分开发者还是倾向于使用社区映射（当然，也有来自于Mojang的员工开发mod，这个时候使用官方映射对他们来说会更熟悉一些）。除了命名的问题，还有一个问题：Mojang的代码没有注释。Yarn尝试对一些关键方法做出注释，但覆盖率不是特别高。至于官方映射，那就更不可能给你注释了。所以看代码全靠猜。在1.8.9的时代，整个游戏还没有那么复杂，代码读起来还相对简单；到了1.19.2，基本上什么都要靠猜，大部分时候还需要你写一个简单的demo来验证你的猜测。属实是pain in the ass。这里的观点其实有失偏颇了。写模组是不是pain in the ass还取决于你要做什么。大部分模组甚至压根儿不需要mixin，mojang自己的代码对于注册物品之类的常规操作其实有着不错的接口。对于常用操作来说，社区也提供了自己的解决方案（例如ConfigMenu之类的库）。只是在我的需求中，我需要深入Mojang的代码，劫持原版代码的行为，然后引入我自己的逻辑，这一点就搞得有点麻烦了，因为没有人做过类似的事情，所以谷歌也爱莫能助。另外一点，也是和这个mod的特性有关。就是前文说到的利用Mojang签发的证书来签发我们的节点认证消息。那么问题来了：Mojang只给正版用户签发，而一般来说mod不会挑正版还是盗版。但这个mod没有正版无法运作，所以测试起来也很麻烦，我有两个号，一个是我平时玩的，另一个是小号。勉强能够测试，我可不想为此再买一个正版。好在最后还是把Mod写出来了，Mixin只有三处，我认为应该是比较准确的，既能保证完成对应的功能，又尽可能确保注入的代码不会影响其他逻辑。这一部分仅仅是引入Minecraft模组的内容。关于如何编写模组，我建议参考一下官方教程，我这里不会手把手从建立项目开始，而是和前面的内容类似，说说看大体思路。如果需要代码参考的话，可以移步hurui200320/i2p-p2p-chat和hurui200320/MinecraftChatAlternative这两个仓库。感谢大家的阅读，我们下一篇文章再见。-全文完-
【代码札记】从零开始的点对点聊天系统 P2
本文介绍这个点对点聊天系统的节点设计。预备上一篇文章中介绍了这套系统中的消息结构设计，这一篇文章中将介绍一个节点是怎么实现的。在上一篇文章中，我决定采用异步消息的设计，但为了避免收到一个消息就启动一个线程，这样对操作系统调度有压力，在编写同步控制的时候也会有额外的麻烦，所以我决定参考Redis的处理方式。在正文开始之前，这里想做一个小铺垫，主要是I2PHelper这个类，我在里面写了一些工具方法，最主要的有两个： @JvmStatic fun runThread(name: String? = null, task: () -> Unit) { val t = I2PAppThread { try { task() } catch (t: Throwable) { logger.error(t) { "Uncaught exception cause thread terminated" } } } t.isDaemon = true name?.let { t.name = it } t.start() } @JvmStatic fun createThreadPool(nThreads: Int): ThreadPoolExecutor = ThreadPoolExecutor( nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, LinkedBlockingQueue(), { I2PAppThread(it).apply { isDaemon = true } }, ThreadPoolExecutor.CallerRunsPolicy() )这两个工具方法应该是用的最多的。I2P自己的库中提供了一个叫做I2PAppThread的类，他的做用与Thread类似，不同之处在于前者提供了I2P提供的OOM监听器，推荐在使用I2P相关资源的时候使用这个线程。所以基于这种推荐，在使用I2P相关资源的时候将待执行的操作包装到I2PAppThread里，以及让线程池构造I2PAppThread实例。不过据我后来观察，这个应该是作为嵌入式路由时使用的，也就是应用程序自己跑一个I2P路由的时候用。如果使用外部路由的话（就像这个库，它要求使用者自己单独运行一个I2P路由，运行时将通过I2CP连接到这个外部路由），这个OOM应该没有什么用，所以之后使用协程代替这部分的代码，应该不会引发什么问题。会话在开始介绍节点实现之前，我想介绍一下这个叫做会话（Session）的设计。对于一个节点来说，其他节点的信息无外乎两个：与对方节点建立的I2P连接和与这个连接相关的状态。前者是节点通讯的基础，后者则是应用程序正常工作所依赖的基础，例如对方节点的验证信息，前文提到但没有实现的各类计时器，还有来自应用程序的各种状态信息（会话上下文）。会话上下文为了尽可能保持通用，与连接相关的状态设计为一个接口：interface SessionContext { val sessionSource: SessionSource var peerInfo: PeerInfo? val nickname: String? fun isAuthed(): Boolean = peerInfo != null fun onAuthAccepted() fun isAccepted(): Boolean }这里的SessionSource是一个枚举：enum class SessionSource { CLIENT, SERVER }当会话来源为SERVER的时候，就说明这个会话是我们的Server部分创建的，也就是说，这个会话是别人发起的，我们接收的；如果是CLIENT，就意味着这个会话是我们的Client部分创建的，即我们发起，别人接收。这个字段对于这个库和后面MC聊天都没什么用，但是有些应用程序可能会依赖我们自己的角色产生不同的行为，这一点可以通过检查会话来源实现。考虑到我们在PEX动作中交换节点信息，与其单独维护一个列表来存储PeerInfo，我们不妨直接把PeerInfo存到每一个会话的上下文中。关于NickName，这个是一个后期引入的字段，主要是调试起来太麻烦。调试时节点的地址通常都是随机生成的，于是打印的日志也全部都是随机地址，虽然日志一开始会打印出来Peer1的地址是AAA，peer2的地址是BBB，但是这看起来还是太令人头大了。所以便引入了NickName字段，在通过认证后，可以从PeerInfo里提取用户名等信息作为这个会话的昵称，这样打印日志的时候就可以采用“昵称（地址）”的方式，即“Peer1（AAA）”，这样我们即看到了节点的地址，又看到了这个节点是Peer1，不会再被随机地址搞得乱七八糟了。后面还针对验证机制引入了两个字段：一个是判断对方是否通过我方的验证（isAuthed），另一个是我方的验证是否被对方接收（isAccepted），并且在收到AuthAcceptedReply时将会调用onAuthAccepted来进行一些自定义操作，例如允许我方向对方发送消息，或者在认证之后开始一些协商。这个会话上下文不要求线程安全，这是为了避免后续使用时疏忽大意引发问题。在会话中，所有对上下文的访问将由同步锁保护。虽然在一定程度上会降低性能，但是避免了并发可能带来的问题。会话有了上下文，我们就可以设计会话了。在节点运行的过程中，节点需要做的事情就是接收入站连接，或者发起出站连接，总之这两者的操作都是得到一个I2PSocket，接下来节点需要初始化一个会话，然后对于这个Socket的操作则完全委托给会话处理（因为会话里保存了针对这个连接的状态信息，任何会话外的操作都可能引发状态不同步）。会话的构造函数如下：class PeerSession<ContextType : SessionContext>( peerInfo: PeerInfo, applicationName: String, private val json: Json, private val socket: I2PSocket, private val sessionContext: ContextType, private val incomingMessageQueue: BlockingQueue<MessageQueueEntry<ContextType>>, private val logger: KLogger, private val onSocketClosed: (PeerSession<ContextType>) -> Unit = {}, )前两个参数用于创建会话后立刻发送认证请求，并不会持久化保存。后面的json对象则是用来序列化（发送）和反序列化（接收）消息的。Socket则不言而喻，就是前文提到的用于通讯的I2PSocket，上下文也是如此。这个上下文对象是由节点提供的，按照设计，上下文对于不同来源的连接可以有所区别，但不应当针对具体的连接产生区别。例如服务器部分收到的连接与客户端发出的连接具有不同的初始状态，但同样是服务器的入站连接，所有节点的入站连接都应当得到相同的上下文。再后面有一个所有会话共享的入站消息队列。会话中只负责解析入站消息，并不处理。解析后的消息由读取线程放入这个消息队列，然后进入下一轮读取。后面的logger实际上是为了保持日志干净设计的，我在设计的时候希望各个类打印日志的时候不要各自为政，而是同一个节点的组件都应该以这个节点的名义打印日志，虽然这会给排查问题带来一些麻烦（现在我们不知道是哪个组件打印的日志了），但通过一些特殊的技巧（例如不同组件打日志时使用唯一的字符串来描述，通过搜索这个字符串便可以找到打印日志的点）可以缓解。这一点在一个进程中运行多个节点进行测试时显得尤为重要。想想看，你有10个叫做“PeerSession”的logger同时打日志，你知道哪一行是哪个节点打印的吗?最后有一个简单的回调，将会在I2PSocket结束时被调用。无论是用户主动关闭会话，还是会话底层的套接字中断，这个回调都会被调用，因为在调用时，这个会话已经不再具备通信的功能了。在节点中我们使用了一个HashMap来存储当前活跃的会话，而会话可能会因为各种各样的原因随时中断，因此将清理过期会话的代码交给会话自己来执行，可以降低编码的复杂度。当然，我们可以拉一个线程定期清理失效的会话，但这样毕竟还是占用了一个线程。不够优雅。当然，只有这些还不够。第一点我们就会发现，I2PSocket好像直接用不太方便啊。我们想按行收发文本数据，那最方便的还得是BufferedWriter和BufferedReader。所以还要初始化这两个东西： private val input = socket.inputStream.bufferedReader() private val output = socket.outputStream.bufferedWriter()有了这些，就可以执行我们的构造函数了（在Kotlin中，构造函数的形参是写在类声明上的，而实际的过程则由init函数体来承载）：init { runThread { try { input.useLines { lines -> // read line one by one forever lines.forEach { base64Line -> val jsonText = try { Base64.getDecoder().decode(base64Line).decodeToString() } catch (e: IllegalArgumentException) { sendError(InvalidIncomingDataError("Malformed base64: ${e.message}")) return@forEach } val message = try { json.decodeFromString<P2PMessage>(jsonText) } catch (e: SerializationException) { sendError(InvalidIncomingDataError("Error when deserializing json: ${e.message}")) close("Malformed data") return@forEach } catch (e: IllegalArgumentException) { sendError(InvalidIncomingDataError("Malformed json: ${e.message}")) close("Malformed data") return@forEach } // parsing ok, add to queue incomingMessageQueue.add(MessageQueueEntry(this, message)) } } } catch (_: IOException) { logger.warn { "Socket from ${getDisplayName()} closed due to IO exception" } } catch (t: Throwable) { logger.warn(t) { "Socket from ${getDisplayName()} closed!" } } // something happened, close the socket close() } // send auth sendMessage(P2PMessage.createRequest(AuthRequest(applicationName, peerInfo))) }这里我们一上来就拉起一个线程，它的工作很简单：从输入流中读取一行，尝试解析。解析成功就加入消息队列，解析失败则返回一个Error。这里并没有使用循环，而是利用了Kotlin对于Java类型的扩展，例如Reader.useLines会将输入流转化成一个序列（Sequence，类似于Java 8引入的Stream），我们对这个序列调用foreach，就相当于我们写一个循环，在循环内读一行的操作了。如果底层的套接字被关闭，则foreach会抛出异常，我们在最外层捕获异常即可。当然，捕获到异常之后也很好处理：打日志，关闭会话。这里要介绍一下那个MessageQueueEntry：data class MessageQueueEntry<ContextType : SessionContext>( val session: PeerSession<ContextType>, val message: P2PMessage )本质上非常简单：这个消息来自于哪个会话，以及这个消息是什么。在拉起读取线程之后，我们立即向对方发送一个认证请求。按照前文设计，我们应该在验证被接受之前阻挡其他消息的发送，但是这个逻辑写起来有点麻烦，所以我便寄希望于我们产生其他消息之前，我们的验证请求已经被处理。如果对方节点有大量消息积压，那这个假设可能会失败，导致我们作为合法的节点被中断连接。这个问题可以通过每个会话一个消息队列来缓解，但是这样就会导致一个会话有两个线程。假设我们在服务器中有30个玩家，那这就是60个常驻后台的进程。前台还要处理MC相关的东西，这对于性能的影响应该不会太小。当然，你可能注意到这里面用到了一些非常规的函数，例如close(String)： fun close(reason: String?) { synchronized(socket) { if (socket.isClosed) return reason?.let { sendRequest(ByeRequest(it)) } try { socket.close() input.close() output.close() } catch (_: IOException) { // It won't hurt anything if the close failed. // It might be already broken/closed. } onSocketClosed(this) } } override fun close() { close(null) }后面的无参数close好理解，因为我继承了AutoCloseable，但它调用了第一个close。这个close是在关闭前向对方主动发送一个ByeRequest，还记得上一篇文章吗？我们说，如果能够主动通知对方关闭资源，那就比让对方过一会儿才发现我们已经关闭了要好。对于MC来说，服务器告诉客户端XXX已经退出了服务器，但我们的Mod还认为这个连接依旧可用，这就会给玩家带来困惑。不过话虽这么说，我们的系统并不依赖这个功能。也就是说，如果对方客户端意外崩溃，我们还是会在一段时间后发现问题，继而释放资源，调用关闭回调，清理资源。当然，你可能还注意到了，这个sessionContext是private修饰的，应用程序用不到啊，你这上下文不就有跟没有一样了吗？这个也好解决： fun <T> useContextSync(block: ContextType.() -> T): T = synchronized(sessionContext) { block(sessionContext) }只要设计这么一个方法就可以了。方法在执行的时候先获取锁，然后再执行用户的操作，最后把返回值带出来。最后简单介绍一下写的部分： private fun sendMessage(message: P2PMessage) { synchronized(socket) { if (socket.isClosed) { logger.warn { "Sending message to closed socket ${getDisplayName()}" } return } val jsonText = json.encodeToString(message) logger.debug { "Send to ${getDisplayName()}: $jsonText" } val base64 = Base64.getEncoder().encodeToString(jsonText.encodeToByteArray()) try { output.write(base64) if (!base64.endsWith("\n")) output.newLine() output.flush() } catch (e: IOException) { logger.warn { "Failed to write socket. Socket ${getDisplayName()} closed!" } // failed to send due to I/O errors // then close the socket close() } } }发送消息之前先要检查套接字是否已经关闭，关闭的话就直接返回。没有关闭则尝试发送信息，发送成功则万事大吉。发送失败则说明这个套接字已经不可用了，直接调用close回收资源。节点那么，会话设计好了，就可以开始看节点了。入站消息处理用户需要通过节点这个对象来实现数据的收发，发当然好办，我们提供一个发送的函数，需要发消息的时候调用就好了，可是收有点麻烦：你不知道消息什么时候来。这个可以通过回调来解决：interface IncomingMessageHandler<ContextType : SessionContext> { fun handle( peer: Peer<ContextType>, session: PeerSession<ContextType>, messageId: UUID, payload: MessagePayload, threadPool: ThreadPoolExecutor ) }这是一个处理器，对于每一个入站消息，节点将调用这个handle方法进行处理。前几个参数都好理解，最后一个线程池的作用是允许处理消息时采用异步计算提高性能，例如使用CompletableFuture等。在设计中，一个消息应当被精确处理一次，所以注册一大堆IncomingMessageHandler不合适，所以我们的Peer只接收一个Handler，由handler根据消息类型去处理。当然，可以让用户自己写，但是考虑到我们已经内置了一些消息，我们也应该针对这些消息写一个可拓展的处理器：abstract class MessagePayloadHandler<ContextType : SessionContext, PayloadType : MessagePayload>( internal val payloadType: KClass<PayloadType> ) { fun isInterface(): Boolean = payloadType.java.isInterface fun isAbstractClass(): Boolean = payloadType.isAbstract fun canHandle(value: MessagePayload): Boolean = payloadType.isInstance(value) private fun cast(value: MessagePayload): PayloadType = payloadType.cast(value) fun handle( peer: Peer<ContextType>, session: PeerSession<ContextType>, messageId: UUID, payload: MessagePayload, threadPool: ThreadPoolExecutor ): MessagePayload? = handleTyped(peer, session, messageId, cast(payload), threadPool) protected abstract fun handleTyped( peer: Peer<ContextType>, session: PeerSession<ContextType>, messageId: UUID, payload: PayloadType, threadPool: ThreadPoolExecutor ): MessagePayload? }IncomingMessageHandler不能注册多个，但是我们可以自己拓展一个MessagePayloadHandler，针对特定的PayloadType进行处理，然后我们把这些Handler收集起来，根据消息的运行时类型来分发处理。你会发现上面要求了一个internal val payloadType: KClass<PayloadType>，这是因为泛型发生在类声明，而Kotlin的reified只能用在函数上。所以不得已，为了要后期能够将抽象的MessagePayload转化成具体的实现类型，只能在构造时带一个KClass用来在运行时判断（canHandle）、转换（cast）。当然，不一定非要限制具体类型，用户也可以针对一类消息进行处理，比如说接收ErrorMessagePayload。所以这里还引入了对于接口和抽象类的判断（isInterface和isAbstractClass）。在分发消息时，先搜索具体的实现（不是接口，不是抽象类），然后搜索抽象类，最后搜索接口。能够使用上述MessagePayloadHandler的Handler实现如下：class GeneralIncomingMessageHandler<ContextType : SessionContext>( /** * Search class, if not found then abstract class, if not found then interface. * */ vararg _handlers: MessagePayloadHandler<ContextType, *> ) : IncomingMessageHandler<ContextType> { // ...... }这个Handler接收一个列表，这个列表我们不能直接用，在初始化的时候需要进行一些检查： init { val set = mutableSetOf<KClass<*>>() _handlers.forEach { require(!set.contains(it.payloadType)) { "Duplicated handlers for payload ${it.payloadType.jvmName}. Each type of payload can be only handled by one handler." } set.add(it.payloadType) } } // copy array into a immutable list private val handlers = _handlers.toList()这里要保证针对每一种消息类型，只能有一个Handler。然后就是重点的handle方法： override fun handle( peer: Peer<ContextType>, session: PeerSession<ContextType>, messageId: UUID, payload: MessagePayload, threadPool: ThreadPoolExecutor ) { // pre-process, for unauthorized messages if (session.useContextSync { !isAuthed() }) { if (payload !is AuthRequest && payload !is ByeRequest) { // reject all messages other than auth request and bye request session.sendError(UnauthorizedError(messageId)) session.close("Unauthorized session") return // stop processing } } // for all bye request, close session if (payload is ByeRequest) { session.close() } // perform additional check on auth request if (payload is AuthRequest) { // check application // Different applications might have different custom payloads if (peer.applicationName != payload.applicationName) { // wrong application name, reject session.sendError(AuthenticationFailedError("Different application", messageId)) session.close("Failed to auth: different application") return // stop processing } // check destination try { // additional check on auth, make sure the dest is correct val dest = Destination(payload.peerInfo.info[PeerInfo.INFO_KEY_DEST]) require(dest == session.getPeerDestination()) } catch (t: Throwable) { session.sendError(AuthenticationFailedError("Destination not match", messageId)) session.close("Failed to auth: wrong dest") return // stop processing if failed } } // find normal class, then abstract class, then interface val handler = handlers.filterNot { it.isInterface() || it.isAbstractClass() }.find { it.canHandle(payload) } ?: handlers.filter { !it.isInterface() && it.isAbstractClass() }.find { it.canHandle(payload) } ?: handlers.filter { it.isInterface() }.find { it.canHandle(payload) } if (handler == null) { logger.warn { "Message handler not found for type ${payload::class.jvmName}\n" + "Message from ${session.getDisplayName()}: $payload" } if (payload is RequestMessagePayload || payload is ReplyMessagePayload) { // send error if the message is request or reply session.sendError(UnsupportedMessageError(messageId, payload::class.jvmName)) } return } // handle message var reply = handler.handle(peer, session, messageId, payload, threadPool) if (payload is RequestMessagePayload && reply == null) { // make sure request always get reply reply = NoContentReply(messageId) } if (reply != null) { // decide if we should send reply val shouldSendReply = when (payload) { is RequestMessagePayload -> reply is ReplyMessagePayload || reply is ErrorMessagePayload is ReplyMessagePayload -> reply is ErrorMessagePayload else -> false } if (shouldSendReply) { session.sendPayload(reply) } // post-process for auth if (payload is AuthRequest) { if (reply is ErrorMessagePayload) { // auth failed, disconnect session.close("Authentication failed") } else { // auth ok session.useContextSync { peerInfo = payload.peerInfo onAuthAccepted() } } } } }整体流程分三部分：预处理、处理、后处理。预处理部分是硬编码的逻辑，例如拒绝未验证的消息，针对AuthRequest、ByeRequest等进行额外的检查、处理等。然后针对消息类型搜索对应的handler，如果没搜到，打印一条日志，并回复给对方一个类似HTTP 501 Unimplemented错误，告诉对方本节点无法处理这类消息。处理完成后得到一个reply。在回复方面没法很好的限制Handler，只能在代码里进行检查，根据上一篇文章的规则：Request可以返回Reply和ErrorReply可以返回ErrorError不接受任何返回检查通过后则发送返回的消息。此外对于认证失败的认证请求，直接断开连接。认证成功则自动将PeerInfo保存到Context中，并调用回调。节点实现解决了处理入站消息的问题，我们可以正经开始实现节点了。实例化一个节点需要如下信息：class Peer<ContextType : SessionContext>( private val json: Json, private val socketManager: I2PSocketManager, val applicationName: String, private val sessionContextProvider: (SessionSource) -> ContextType, private val selfPeerInfoProvider: (Peer<ContextType>) -> PeerInfo, private val messageHandler: IncomingMessageHandler<ContextType>, private val threadPool: ThreadPoolExecutor, private val pexInterval: Long = 10 * 1000, private val logger: KLogger = KotlinLogging.logger { }, private val onSessionSocketClose: (PeerSession<ContextType>) -> Unit = {} ) : AutoCloseable { // ...... }节点用到的参数有点多，首先是Json实例，用于解析消息的；然后是一个I2PSocketManager，主要用于获取I2P的入站连接，以及发起出站连接；applicationName则用于判断AuthRequest，不同applicationName的节点不能相互通过认证；sessionContextProvider用于根据会话来源创建会话上下文；selfPeerInfoProvider用于提供本节点的节点信息，用户可以在这里附加额外的信息，处理签名逻辑；messageHandler就是前文介绍的消息处理器；threadPool是用于处理耗时操作的线程池，也就是实际传给Handler的线程池；pexInterval决定了多久执行一次PEX操作；logger可以有用户自定义，默认的话是使用类名；onSessionSocketClose则为用户提供了观测会话结束的能力，除了节点需要用来清理资源之外，用户可以在这里执行额外的操作，例如在聊天框打印消息告诉用户XXX已经断开连接。除此之外，我们还需要初始化一些东西： private val incomingMessageQueue: BlockingQueue<MessageQueueEntry<ContextType>> = LinkedBlockingQueue() private val serverSocket: I2PServerSocket = socketManager.serverSocket private val sessions = ConcurrentHashMap<Destination, PeerSession<ContextType>>() private val closedFlag = AtomicBoolean(false) private val newPeerQueue: BlockingQueue<Pair<() -> Unit, (Throwable) -> Unit>> = LinkedBlockingQueue()前四个比较好理解，它们分别是：入站消息队列、从SocketManager处获取的服务端Socket、存储所有Session的Map，以及一个记录当前节点是否关闭的标志。存储会话使用Map主要是为了快速查找目的地是否有重复，如果要连接的地址已经是一个已知且存活的会话，那么我们就不必重复连接了。最后一个可能有些费解，为什么新节点队列是一个lambda组成的双元组呢？这一点后面再说。关于实例化的过程，主要是拉起各个工作线程： private fun createPeerSession( sessionSource: SessionSource, socket: I2PSocket ): PeerSession<ContextType> = PeerSession( selfPeerInfoProvider(this), applicationName, json, socket, sessionContextProvider(sessionSource), incomingMessageQueue, logger ) { sessions.remove(it.getPeerDestination()); onSessionSocketClose(it) } init { // handle new peers runThread { // ...... } } // handle incoming message runThread { // ....... } // handle server part runThread { // ...... } // exchange peer every X minutes runThread { // ...... } // print self info logger.info { "I'm ${getMyB32Address()}" } }这里启动了四个线程。首先是处理新节点的，这些节点的来源是PEX，交换得到的节点全部放到上面那个很奇怪的队列中，然后这个线程从队列中获取信息，进行连接。但是问题来了：I2P的隧道有两种连接方式：一种是使用Destination直接连接，相当于发快递时写好了地址，直接派送；还有一种方式是通过哈希，类似于你只提供了对方的电话，快递员需要联络对方才能得直具体地址。在I2P中，Destination保存了对方隧道的公钥等信息，通过Base64编码之后有500多字符，不利于分享（Minecraft一行消息只能发一二百个字符）；而哈希地址则不超过96个字符，我们使用的是52个字符的b32地址，但代价就是连接之前需要向路由查询Destination，于是便催生了如下两个连接方法： fun connect(b32: String) { val nameService = I2PAppContext.getGlobalContext().namingService() val dest = nameService.lookup(b32) ?: throw UnknownHostException("Host not found: $b32") connect(dest) } fun connect(dest: Destination) { if (closedFlag.get()) return // do not connect to ourselves if (dest == getMyDestination()) return sessions.compute(dest) { _, oldSession -> if (oldSession != null && !oldSession.isClosed()) { // old still alive, do nothing and return old one return@compute oldSession } // make new connection logger.info { "Connecting to peer ${dest.toBase32()}" } val socket = socketManager.connect(dest) createPeerSession(SessionSource.CLIENT, socket) } }第一个connect接收b32地址，查询成为Destination对象，然后交给第二个connect方法。后者检查地址是否重复，并进行连接。鉴于这里有两种连接方法，而且一个是字符串，一个是对象，我们不应该将他们混着存到一个List<Object>里，这个是可以实现目的的，但并不安全，也不值得提倡。针对这两种connect方法，有两种增加新节点的方法： fun addNewPeer( b32: String, errorHandler: (t: Throwable) -> Unit ) { newPeerQueue.add({ connect(b32) } to errorHandler) } fun addNewPeer( dest: Destination, errorHandler: (t: Throwable) -> Unit ) { newPeerQueue.add({ connect(dest) } to errorHandler) }你看，无论哪种地址类型，我们只要在闭包中处理连接的逻辑，最终得到的都是一个抽象的函数，而那个errorHandler则提供了用户观测连接失败的能力。我们只要在处理新节点队列的线程中执行这个闭包就可以了： // handle new peers runThread { while (!closedFlag.get()) { val (connect, handleError) = newPeerQueue.take() threadPool.execute { try { connect() } catch (t: Throwable) { handleError(t) } } } }我们依次从队列中取出连接新节点的Lambda，在线程池中执行，如果捕获到错误，就调用处理错误的回调。能够处理新节点之后，我们更进一步，开始拉起消息处理的线程： // handle incoming message runThread { // if peer closed, no need to process messages while (!closedFlag.get()) { val entry = incomingMessageQueue.take() // do things sync, ensure first in first handled messageHandler.handle( this, entry.session, entry.message.id, entry.message.payload, threadPool ) } }这里判断了一下节点是否关闭，如果节点已经关闭了，那么就没有必要处理消息了——处理了你也发不出去回复。每次从队列中拿一个消息，拿到之后便调用handle进行处理。按道理来说，这个handle应该包裹在try catch中，以防止意外的异常提前结束消息处理进程，导致消息积压。不知道怎么搞的，我当时给忘了，以后一定修。但很奇怪的是，上面的忘了，下面服务端那块又想起来了。不愧是我。有了处理入站消息的能力，接下来便可以拉起服务端了： // handle server part runThread { while (!closedFlag.get()) { try { val socket = serverSocket.accept() ?: continue sessions.compute(socket.peerDestination) { dest, oldSession -> if (oldSession != null && !oldSession.isClosed()) { // old still alive, kill new one and return old one logger.warn { "Duplicated connection from ${dest.toBase32()}" } socket.reset() return@compute oldSession } // accept new socket createPeerSession(SessionSource.SERVER, socket) } } catch (e: ConnectException) { logger.warn { "Server socket ${getMyB32Address()} closed or interrupted!" } break } catch (e: I2PException) { logger.warn { "I2CP session for ${getMyB32Address()} closed or broken!" } break } catch (_: Throwable) { } } logger.warn { "Peer stop handle incoming connections: ${getMyB32Address()}" } close() }在服务端接收到入站套接字之后，对来源进行了检查：如果我们已经有了对应的出站连接，那么为了避免重复，应该拒绝这个入站连接。但是有一种比较极端的情况：我们发起出站连接，对方接受了连接（accept返回了非null值）我们知道对方接收了连接（connect成功返回），因此我们创建了一个会话（称为出站会话）对方节点的服务端线程被暂停，对方的PEX开始工作，对方借由PEX得知了我们的存在，由于对方节点仅接收了我们的套接字，尚未创建会话，对方向我们发起了入站连接我们接受了对方的入站连接，但发现已经重复了，我们立刻断开连接（socket.reset()），但此时对方的connect成功返回，对方创建了一个会话（称为入站会话）对方节点的服务端线程恢复执行，发现了PEX创建的会话，对方认定我们的出站会话与上一步的入站会话冲突，对方也reset了我们的套接字最后，我们的出站连接被中断，对方拥有的入站会话已经被关闭。这个情况导致了双方节点尝试互相连接对方，但谁也没连上谁。不过从现实情况来讲，要达成这种条件也需要运气，我姑且不认为这是个问题。最后，当然就是拉起那个PEX进程： // exchange peer every X minutes runThread { var lastActionTime = System.currentTimeMillis() while (!closedFlag.get()) { try { while (System.currentTimeMillis() - lastActionTime < pexInterval) Thread.sleep(1000) val request = PEXRequest(dumpKnownPeer()) logger.debug { "Doing PEX for peer ${getMyB32Address()}" } sessions.filter { it.value.useContextSync { isAuthed() && isAccepted() } && !it.value.isClosed() } .forEach { threadPool.execute { it.value.sendRequest(request) } } lastActionTime = System.currentTimeMillis() } catch (_: InterruptedException) { } catch (t: Throwable) { logger.warn(t) { "Failed to PEX" } lastActionTime = System.currentTimeMillis() } } logger.warn { "Stop PEX due to peer ${getMyB32Address()} closed" } }这里就是一个简单的循环，每隔一段时间对于经过验证且没有关闭的会话发出PEX请求。实例初始化完成之后，下面介绍一些方法。前面介绍过的addNewPeer和connect就不介绍了，已经说过了。由于我们本身不处理PEX，所以需要Handler通过传入的peer参数来获取，这样一来我们就需要对应的方法： fun dumpKnownPeer(): List<PeerInfo> = sessions.mapNotNull { it.value.useContextSync { peerInfo } }.toList() fun dumpSessions(): List<PeerSession<ContextType>> = sessions.values.toList() dumpKnownPeer将我们已知的所有会话的PeerInfo拿出来，而dumpSessions则直接列出我们的会话。以及，除了connect，我们还需要断开连接的能力： fun disconnect(reason: String = "Disconnect by user", filter: (PeerSession<ContextType>) -> Boolean) { if (closedFlag.get()) return sessions.forEach { if (filter(it.value)) it.value.close(reason) } }还有发送消息的能力： fun sendRequest(message: RequestMessagePayload, filter: (PeerSession<ContextType>) -> Boolean) { if (closedFlag.get()) return sessions.forEach { if (filter(it.value)) threadPool.execute { it.value.sendRequest(message) } } }这里我们只能发送请求，因为上一篇文章中规定，所有动作必须由请求发起。当然，最后还有关闭： fun isClosed(): Boolean = closedFlag.get() override fun close() { closedFlag.set(true) sessions.forEach { it.value.close("Peer closing") } serverSocket.close() }总结本文介绍了一大堆东西，他们是i2p-p2p-chat的核心内容。写起来我就觉得比较乱，读起来可能就会更乱吧。总之这一篇文章先到此为止。在下一篇文章中我将介绍一些常用的消息处理器，比如针对PEX请求的处理，以及由此产生的工具方法，还有Peer的使用方法展示，以及最重要的，将这个系列过渡到Minecraft部分。感谢大家的耐心阅读，我们下一篇文章见。-全文完-
【代码札记】从零开始的点对点聊天系统 P1
本文介绍这个点对点聊天系统的整体设计。其实在决定这一篇文章内容的时候犹豫了好久。周末看了Kotlin中文开发者大会，发现了协程这个东西，有点类似于Java即将实现的纤程——一种轻量化的线程，不需要操作系统来进行调度，因此能够节省线程切换引发的开销。看到了这种性能提升，我也想把他引入到这个系统中。但是这样的话又要等好久才能把代码定形，到时候指不定又看到了什么新东西，不断优化代码，最后文章怎么也写不出来。所以最后思来想去，决定先按照现在的样子写文章，以后再专门说优化。前言点对点系统是一种非常棒的框架。像是C/S和B/S这些传统框架中，总会有明确的主从关系。用户作为客户端，是被动的一方，需要向服务器，也就是主动的一方发起请求，然后获得主动方返回的结果。如果出于各种原因，客户端无法连接服务器，或者服务器崩了，那么整套系统也就不能用了。为了防止系统出现问题，领域内的先贤们搞出了诸如高可用、主从备份等各种各样的手段来尽量避免故障。而点对点系统则用另一种方式解决服务故障：如果把服务器分散给每一个用户，那么只要有用户使用，就有用户提供服务，而这个系统就永远也不会宕机。我想大家既然能点进来这篇文章，那应该都是用过BitTorrent的人。如果你对这个名字不熟悉，那你肯定知道「种子」和「迅雷」。BitTorrent就是一个点对点内容共享系统，系统中的每一个节点（参与者）既作为客户端向别人请求数据，又作为服务端向别人提供自己有的数据，从而实现了一种人人为我，我为人人的境界。但是点对点系统一般来说都比较难设计，即便设计出来了，使用体验上也不是很好。这就是为什么，现在能使用的大多数APP全部都是C/S结构的——要提升用户体验，只要在服务端投入即可。点对点系统要提升体验，就涉及到节点间互联质量，系统的公平机制，攻击防范等。节点间互联质量主要与网络有关，其中影响最大的就是防火墙。关于公平机制和攻击防范，主要是在设计上考虑如何防止系统被滥用，虽然比较复杂，但好在相对容易实现。本系列意图实现一个点对点的聊天系统，我想把主要的精力放在协议设计上，数据传输则委托给I2P处理。I2P也是由Java编写的，并提供了一个不错的API来供其他Java程序使用。此外为了防止I2P通讯部分的代码与MC部分的代码耦合在一起，我把这两部分的代码完全分开了。使用I2P处理通讯的部分放在了hurui200320/i2p-p2p-chat这个仓库，而与MC模组有关的代码则放到了hurui200320/MinecraftChatAlternative这个仓库里。关于I2P本身的内容我就不多说了，网络上有不少关于I2P的介绍。不过这里需要提及的一点是，I2P遵守的是比较纯粹的开源风格，也就是所谓的“As Is - No Warranty”。现在的主要开发者从创始人手中接下项目后，不接受任何来自社区的货币捐助，二十多年来完全靠他们对于隐私与自由的热爱在维护这个庞大的软件。我尝试着阅读过他的源代码，看到了2003年的Java，以及当时使用的构建工具。可以说在我的认知里，我还从来没有见过这样的项目。说回到“As Is - No Warranty”，作为开发者，他们会尽力让软件变得“能用”，但是他们并没有义务为你修复bug。所以如果你因为这个系列也喜欢上了I2P的功能，但在使用的过程中遇到了一些问题，我希望你可以自己动手做一些简单的调试工作，不要在语气上“指责”开发者（这一点经常无意识的发生，使得你在提出issue的时候听起来像是在指责你这个开发者在发布软件之前没有充分测试代码，从而给我带来了麻烦）。提供帮助，不要指责。协议设计关于这一套系统的协议，经过一番修修改改，最终是这个样子的。同步还是异步整个系统的消息采用异步的方式处理，从而实现复用同一条TCP链接。在早期设计中使用同步方式处理消息，这就意味着一个组件发出消息后必须占用这一条TCP直到收到回复，此时不能接收其他消息，也不能发送其他消息。这种情况下节点间的一条TCP连接就变成了半双工的方式，并且客户端和服务端需要轮流征用一条TCP，控制逻辑比较复杂。为了实现简单的全双工，可以让节点之间建立两条TCP连接，每条按单工方式使用。这样写起来非常简单：服务端只需要监听入站消息并处理，客户端使用单独的TCP连接发送出站消息。这种设计的好处是不需要太复杂的同步控制，但坏处也很明显，一对节点需要两条TCP，如果要和N个节点通讯，那就得2N条链接。如果其中一条链接中断了，我们便失去了发送或接收的能力，针对这种情况的错误处理又引入了额外的复杂性。总的来说，虽然异步设计会带来一定的复杂度，但整体框架建立起来之后的好处就非常可观了：单条链接实现全双工，可以提供类似Redis的单线程消息处理（和性能），整体消息设计也更加灵活。消息结构在消息本身的设计上，我采用了相对简单但具有可拓展性的设计：@Serializable data class P2PMessage( @Serializable(with = UUIDAsStringSerializer::class) val id: UUID, @Contextual val payload: MessagePayload )每个消息都具有一个会话内唯一的UUID，和一个消息负载。由于我们是异步处理消息，因此接收到之后不会立刻收到回复，但如果要让对方知道回复的是哪个一个请求，那么这个请求就必须具有一个编号。关于消息负载，它是一个抽象的接口：interface MessagePayload { val messageType: Type enum class Type { REQUEST, REPLY, ERROR } }这里规定消息负载有三种类型：请求、回复和错误。请求如同字面意思，一方想要发起一个操作，例如向你发送一个聊天消息。回复则是表示对先前请求的成功响应，例如我方已经收到并妥当处理了你的聊天消息。而错误则是表示先前请求未能得到正确处理，例如你的聊天消息未经验证，或者你的请求不符合参数要求。由于这三种请求都有各自的共性，于是设计抽象类来描述这三类请求的大致细节：abstract class RequestMessagePayload : MessagePayload { final override val messageType: MessagePayload.Type = MessagePayload.Type.REQUEST } abstract class ReplyMessagePayload : MessagePayload { abstract val replyTo: UUID final override val messageType: MessagePayload.Type = MessagePayload.Type.REPLY } abstract class ErrorMessagePayload : MessagePayload { abstract val errorAt: UUID abstract val msg: String final override val messageType: MessagePayload.Type = MessagePayload.Type.ERROR } 可以注意到的是，这里我覆盖了消息类型，并限定他们是final，从而阻止他们的子类修改消息类型。因为这些接口要开放给其他代码使用，我当然不想有调皮的开发者继承了RequestPayload，但把消息类型改成Reply，这样不太好。请求类是最简单的，只要说明消息类型，具体的内容可以根据请求动作的需求自行增添参数。回复也相对简单，只是强制要求了replyTo字段，即你这个回复是回复先前哪个消息的。最后是错误类，除了要求发生错误的消息ID，还要包含一个描述性的字符串来告诉哪里出错了，类似于Exception中的message字段。这三个类型的时序约束是：一个动作必须由Request发起，不可以使用Reply或Error发起一个动作。Request请求存在两种可能的响应：Reply，成功处理并返回了结果；Error：未能正确处理请求收到Reply后，有两种可能的操作：若回复数据正确，则什么都不做，本次动作结束；若回复数据有误，则返回Error。收到Error后，不再进行任何回复。Error响应意味着本次动作终止。身份验证在具体消息设计上，我想引入一个验证系统。在旧版本的MC中，客户端需要完全信任服务器，如果服务器捏造消息，我们也无从得知。在1.19引入了密码学签名系统后，每个玩家都会有一个临时的、由Mojang签发的证书。玩家发出的消息都会由这个证书进行签名，以确保其没有被修改、捏造，这也是举报系统的依据所在。既然Mojang在游戏里用了这套系统，那我们也可以利用这套系统来保证其他节点并没有捏造聊天消息/冒充别人。但是每条消息都签名，这听起来像是做了一套Mojang之外的审查系统。所以我们并不使用这套机制来签名聊天消息。我们使用这套机制来验证节点的链接。在发送聊天消息之前，节点需要与其他节点建立连接。连接建立后无论是出站方向还是入站方向，都应该立刻发送一个验证请求，并在验证通过之前阻挡/推迟其他消息的发送。这个验证请求的设计如下：@Serializable @SerialName("AuthRequest") data class AuthRequest( val applicationName: String, val peerInfo: PeerInfo ) : RequestMessagePayload()这里有两个字段：应用名称和节点信息。应用名称保证验证时使用统一框架的不同应用程序不会意外的认证。例如我用这个框架做了个MC的聊天系统，别人搞了个类似BT下载的东西，都用了我这个i2p-p2p-chat库，他们各自也都有自己的扩展消息，为了避免意外连接两个不同系统的节点，应用名称不同则直接认定验证失败，并断开连接。节点信息则为了适应不同应用程序，设计成了一个可签名的Map：@Serializable data class PeerInfo( /** * Put want ever you want here. * It will be encoded as bencode. * */ @Serializable(with = MapAsBencodeSerializer::class) val info: Map<String, String>, /** * The signature of the bencode info. * */ val signatureBase64: String, /** * The public key of that signature * */ val publicKeyBase64: String )这里的info便是Map本身，它会被编码成bencode格式，用于传输和签名。下面的签名则是保证info字段没有被篡改，最后附上用于签名的公钥，以便接收者确认身份。但是这里有一个问题：如果别人收到了这个认证请求，保存下载，再转发给别人，宣称他就是你，这怎么办？为了解决这个问题，我们可以在info中加一个字段peer.dest，用来存储请求者的源地址。与TCP连接不同，I2P的连接地址是用户侧的私钥决定的，而私钥不应该被共享。只要别人没有这个私钥，就不可能伪造你的源地址。私钥是随机生成的，但是发生碰撞的几率非常小（你想想看，整个加密货币的安全性就是建立在随机生成的私钥上的，如果你随便一生成就撞到了别人钱包的私钥，那怎么了得）。通过检查这个字段我们便可以知道对方是不是info中声称的请求者。此外考虑到Mojang签发的证书本身有效期较短，因此这里对签名就不再额外设置有效期限制了。如果你的私钥意外泄漏，别人冒充了你，那你应该及时联系你的好友，告诉他们发生了什么，并告诉他们警惕冒充者。对应地，验证请求伴有两个响应：@Serializable @SerialName("AuthAcceptedReply") data class AuthAcceptedReply( @Serializable(with = UUIDAsStringSerializer::class) override val replyTo: UUID ) : ReplyMessagePayload() @Serializable @SerialName("AuthenticationFailedError") data class AuthenticationFailedError( val cause: String, @Serializable(with = UUIDAsStringSerializer::class) override val errorAt: UUID ) : ErrorMessagePayload() { override val msg: String = "Failed to authenticate your credential" }很明显，一种是明确告诉对方验证成功，这样对方就可以开始向你发送其他请求了；一种是告诉对方验证失败，并告诉对方因何失败（cause字段）。取决于具体实现，你可以给对方一次机会，也可以直接断开连接。在这里，针对验证失败的会话，我选择直接断开连接，以避免DDOS攻击。目前还没有针对验证进行计时，因此一种潜在的攻击方法就是建立连接后不发出任何消息，通过建立足够多的连接，从而耗尽用户的资源（一条连接就需要一条线程接收入站消息）。最根本的解决方法就是在建立连接后开始计时，但这种攻击也可以通过使用纤程进行缓解（根据官方的例子，10万个纤程可以轻松地被处理，而10万个纤程很可能耗尽JVM的内存）。节点交换验证通过后我们便可以相信这条连接就是来自它所声称的用户。但问题是，我们怎么知道别人的地址并发起连接？最早我想使用Tracker。所谓Tracker，是BT下载技术中的一个角色，它负责追踪和记录不同种子的客户端情况。正在下载的节点可以向Tracker宣告自己的存在，而新来的节点也可以向Tracker询问现有的节点信息，从而与他们建立连接。但是吧，I2P上的Tracker我怎么也调不通，于是我就放弃了。在经过一番研究后，我发现BT下载中还有一种叫做PEX的技术，它是对BT协议的扩展，全称叫Peer Exchange，允许节点之间直接交换他们已知的节点信息，从而绕过Tracker。换言之，实现一种连接一个节点便能得知其他节点的效果。在这里，我们加入一个Minecraft服务器之后，只要想办法连上一个节点，就可以通过这个节点得知他所连接的其他节点，最终我们可以连上这个服务器内的所有玩家。当然，这只是一种理想情况。现实中的情况可能更加复杂，例如玩家之前形成了相互隔离的网，一部分人互相连通，而另一部分人互相连通，但这两拨人互不知道彼此的存在。此外，在初期面临一个bootstrap的问题，即大家都不知道彼此的存在，可能会形成好多相互隔绝的、零散的网。这个需要应用程序自己想办法将他们联通起来，例如提供用户之间相互连接的指令。在后面MC的部分，我们提供了用户复制自己地址，并连接他人地址的功能。节点交换的请求如下：@Serializable @SerialName("PEXRequest") data class PEXRequest( val peerInfos: List<PeerInfo> ) : RequestMessagePayload()内容很简单，直接把已知的PeerInfo转发出去就好了，这个PeerInfo的签名可以保证它没有被人篡改，又由于这个里面包含了节点的地址（节点的源地址也是对方提供服务的地址），我们可以验证后直接发起连接。当然了，为了减少请求次数，我们可以在回复里放上我们所知的节点信息：@Serializable @SerialName("PEXReply") data class PEXReply( @Serializable(with = UUIDAsStringSerializer::class) override val replyTo: UUID, val peerInfos: List<PeerInfo> ) : ReplyMessagePayload()这样，完成PEX动作之后，请求者和接收者都成功交换了节点数据。但在这一版的实现中，大家都只对发起请求计时了，而没有理会接收回复。因此经常会出现节点A发起了PEX，然后没一会儿节点B又向节点A发起PEX，尽管A刚刚才和B交换完。发送文字消息其实严格来说这个请求并不应当包含在框架内，因为框架才不理会你要发什么。但是考虑到文字消息好像还是挺常用，因此也就集成在框架里面了。定义如下：@Serializable @SerialName("TextMessageRequest") data class TextMessageRequest( val scope: String, val content: String ) : RequestMessagePayload()这个消息包含两个字段，其中scope指的是发送消息的范围，在MC中，我们有公共聊天和私聊，scope用于鉴别这两种聊天类型。然后就是content，它包含了聊天内容本身。回复如下：@Serializable @SerialName("NoContentReply") data class NoContentReply( @Serializable(with = UUIDAsStringSerializer::class) override val replyTo: UUID ) : ReplyMessagePayload()这个回复在语义上更像是HTTP 204，也就是你的请求处理成功了，但我没有什么要回复给你的数据。在其他单向动作中也可以使用这个回复。主动断开连接由于各种情况，我们需要主动断开连接。例如玩家退出了当前服务器，那我们就应该断开所有节点。如果能够在玩家关闭游戏之前主动告诉其他节点，那么相比让其他节点等待，然后发现连接中断，不如尽早告诉他们，这样对方可以提前关闭资源，回收内存。@Serializable @SerialName("ByeRequest") data class ByeRequest( val reason: String = "" ) : RequestMessagePayload()这个请求比较特殊，它不具备任何回复。一般来说一个请求必须跟着一个响应，要么成功要么失败。但是这个动作导致连接被双方关闭（发送方在发送完成后立即关闭连接，接收方收到后也立即关闭连接），无法继续通讯，因此这是一个有去无回的动作。消息发送格式以上是消息的内容，但是我们怎么编码、发送呢？最简单的方法还是一行一行发文本。虽然使用二进制编码比较高效，例如Protocol Buffer，但是它也是最难用的，你需要提前给每一种消息设计格式。而Json，相比之下则要灵活很多。通过配置序列化器（Serializer），可以将json输出为一行，这样我们读取的时候就可以直接readLine，而不必考虑消息长度不同。在消息的序列化和反序列化上，我选择使用kotlinx-serialization，它与Gson、Jackson等库不一样，它的所有代码都是编译时期处理的，因此不需要反射这些乱七八糟的东西。用起来有一些限制，但我们也能获得相应的好处（例如摆脱了烦人的反射，获得了更好的性能等）。对于原生类型，Kotlinx提供了对应的serializer，但是从上面的代码中可以看到，诸如UUID这些类型需要我们手动处理一下：object UUIDAsStringSerializer : KSerializer<UUID> { override val descriptor = PrimitiveSerialDescriptor("UUID", PrimitiveKind.STRING) override fun deserialize(decoder: Decoder): UUID = UUID.fromString(decoder.decodeString()) override fun serialize(encoder: Encoder, value: UUID) = encoder.encodeString(value.toString()) } object MapAsBencodeSerializer : KSerializer<Map<String, String>> { override val descriptor = PrimitiveSerialDescriptor("Map<String, *>", PrimitiveKind.STRING) override fun deserialize(decoder: Decoder): Map<String, String> = Bencode().decode(decoder.decodeString().encodeToByteArray(), Type.DICTIONARY) .mapValues { if (it.value !is String) { throw IllegalArgumentException("Expecting String as value, but got ${it.value::class.jvmName}") } it.value as String } override fun serialize(encoder: Encoder, value: Map<String, String>) = encoder.encodeString(Bencode().encode(value).decodeToString()) }这里我们将UUID和字符串类型的Map都编码成字符串。UUID本身就可以用字符串表达和解析，问题不大。Map的话可以使用bencode编码，这个编码是源自于BT协议的，非常好用。而对于反序列化时的多态，即P2PMessage的Payload字段是一个接口，而我们有许多已知和未知的实现，怎么在编译期保证这些子类都能被正确的序列化、反序列化呢？fun getSerializersModule( customMessagePayload: PolymorphicModuleBuilder<MessagePayload>.() -> Unit = {}, customSerializersModule: SerializersModuleBuilder.() -> Unit = {}, ): SerializersModule = SerializersModule { polymorphic(MessagePayload::class) { subclass(AuthRequest::class) subclass(TextMessageRequest::class) subclass(PEXRequest::class) subclass(ByeRequest::class) subclass(NoContentReply::class) subclass(AuthAcceptedReply::class) subclass(PEXReply::class) subclass(InvalidIncomingDataError::class) subclass(AuthenticationFailedError::class) subclass(UnauthorizedError::class) subclass(InvalidScopeError::class) subclass(UnsupportedMessageError::class) customMessagePayload() } customSerializersModule() }我们可以在构造Json实例的时候使用自定义的SerializersModule，在其中注册我们的多态。这里默认注册了上述消息，如果用户拓展了消息，可以通过lambda的方式一并注册进来。使用的时候如下所示：val json = Json { serializersModule = getSerializersModule( customMessagePayload = { // register here subclass(SomeTypeOfRequest::class) } ) }这时构造的json就可以解析我们自定义的SomeTypeOfRequest请求了。总结本篇介绍了这个系统中最基础的消息设计与大体交互流程。下一篇中将介绍如何利用I2P实现节点。-全文完-
【代码札记】从零开始的点对点聊天系统 P0
本篇为该系列的介绍。该系列将讲述我为Minecraft构造一个点对点聊天系统的过程。起因事情的起因是微软和Mojang决定在Minecraft 1.19中引入消息签名和举报封禁系统。关于这个系统的争论这里就不多说了，这里我推荐大家观看Aizistral的Youtube频道，他制作了一系列视频讲述了这个机制的原理和潜在的漏洞。总地来说，微软和Mojang的这一系列操作，从技术上讲并不安全（可以利用诸如gaslighting等漏洞捏造举报记录并陷害别人），从道理上来讲也不合适（由此引发的封禁会导致正版玩家在封禁期间无法在任何版本上加入多人游玩，因为Mojang的登录认证服务器会拒绝处理你的请求，而这会影响到所有版本）。而Mod社区作为反击，出现了诸如NoChatReports等模组来禁用消息签名，进而阻止其他玩家举报使用者。而我已经想给Minecraft写mod很久了，但是苦于没有想法，也一直没有入门的动力。最近在研究I2P，全称The Invisible Internet Project，翻译过来就是隐匿网。它的功能比Tor还要厉害。Tor提供了隐匿互联网踪迹的功能，同时还提供了所谓的暗网（基于电路交换）；而I2P则压根不考虑隐匿互联网行踪，它直接搞了个Internet over Internet，它所使用的协议比Tor更加具有抵抗力。听说这个项目目前是由俄罗斯人领导开发的，只能说，俄罗斯还是高手在民间。因为I2P也是Java写的，我也刚好写Java，所以就想着能不能给这个项目贡献一些自己的力量，但发现自己的水平还是不太行，只能围绕着I2P做一些外围的开发。于是我便有了一个想法：如果我们劫持Minecraft的聊天系统，岂不是从根本上解决问题？在原版Minecraft中，发送聊天消息主要是这个流程：聊天相关的UI -> 处理消息（签名、加入数据包队列） -> 发送给服务器服务器收到玩家的消息之后，服务器首先检查消息签名，然后将用户信息与签名的消息一起转发给每一个人：收到服务器转发的消息 -> 验证签名 -> 放入聊天栏如果我们的模组能够劫持这个过程，在处理消息的时候将消息交给模组处理，从而避免将消息签名、发送给服务器；同时模组将接收到的消息直接放入聊天栏，这样不就完全绕开了他们的消息签名那一套？之后无论微软和Mojang怎么改，只要他们还在游戏内提供聊天功能，这个办法就永远不会过时。EULA？狗屁！结果为什么没有经过呢？因为经过是本系列后续文章的事儿。既然如此，说干就干。经过一番折腾，最终得到了两个Repo：hurui200320/i2p-p2p-chat：这个Repo里面的东西是基于I2P构建的一个点对点通信框架hurui200320/MinecraftChatAlternative：这个Repo则是基于上面的框架，为Minecraft 1.19.2编写的mod从结果来说，这是我第一次为Minecraft写mod，也是第一次设计点对点系统。要我自己说，我觉得还不错；但是客观上讲，其实还是差远了。作为Mod来说，这个mod缺乏用户友好的教程、文档和游戏内配置等；作为一个点对点系统来说，好多功能和细节还有待打磨，同时在设计这个系统的时候，对于防范攻击并没有太多的考量与设计，真要是用起来的话，还得重新审视一下这方面的问题。不过从过程来说，我确实学到了不少东西。我的毕业设计是做了一个可以集群部署的系统，集群内同步的方式依赖于第三方服务（例如可集群部署的数据库和Etcd），而在设计模式上也是C/S模式，具有明显的主从关系。但是点对点系统不太一样，当然也可以按照主从模式来设计——最开始我就是这样写的：每个节点有一个服务端和客户端，节点之间必须有两条TCP连接才可以通信：服务端的入站用来接收消息，客户端的出站用来发送消息。我当然知道TCP可以全双工，但是那样的话就要考虑复用和同步控制的问题。总之，随着项目的进行，我逐渐累积了一些点对点系统的经验，最终当然还是设计成了单个TCP连接。也正是出于此，我想将我在其中学到的一些经验整理出来与大家分享，因此也便有了这个系列。考虑到本篇只是起到一个开篇、引路的做用，因此就不多啰嗦了。我们下一篇见，大家敬请期待。