From fa0e53289c14f7aa500c1c3651ac7effc9781261 Mon Sep 17 00:00:00 2001 From: Christian Basler Date: Tue, 6 Jun 2017 16:36:07 +0200 Subject: [PATCH 01/10] Migrated core and extension modules to Kotlin (Except BitmessageContext and Bytes) --- build.gradle | 13 + core/build.gradle | 2 +- .../dissem/bitmessage/BitmessageContext.java | 27 +- .../bitmessage/DefaultMessageListener.java | 191 ----- .../bitmessage/DefaultMessageListener.kt | 168 ++++ .../ch/dissem/bitmessage/InternalContext.java | 326 -------- .../ch/dissem/bitmessage/InternalContext.kt | 235 ++++++ .../dissem/bitmessage/ProofOfWorkService.java | 125 --- .../dissem/bitmessage/ProofOfWorkService.kt | 124 +++ .../dissem/bitmessage/constants/constants.kt | 27 + .../ch/dissem/bitmessage/entity/Addr.java | 83 -- .../java/ch/dissem/bitmessage/entity/Addr.kt | 43 + .../bitmessage/entity/BitmessageAddress.java | 277 ------- .../bitmessage/entity/BitmessageAddress.kt | 195 +++++ .../bitmessage/entity/CustomMessage.java | 111 --- .../dissem/bitmessage/entity/CustomMessage.kt | 84 ++ .../entity/{Encrypted.java => Encrypted.kt} | 17 +- .../ch/dissem/bitmessage/entity/GetData.java | 84 -- .../ch/dissem/bitmessage/entity/GetData.kt | 48 ++ .../java/ch/dissem/bitmessage/entity/Inv.java | 82 -- .../java/ch/dissem/bitmessage/entity/Inv.kt | 44 ++ ...{MessagePayload.java => MessagePayload.kt} | 10 +- .../bitmessage/entity/NetworkMessage.java | 151 ---- .../bitmessage/entity/NetworkMessage.kt | 126 +++ .../bitmessage/entity/ObjectMessage.java | 271 ------- .../dissem/bitmessage/entity/ObjectMessage.kt | 228 ++++++ .../dissem/bitmessage/entity/Plaintext.java | 733 ------------------ .../ch/dissem/bitmessage/entity/Plaintext.kt | 706 +++++++++++++++++ ...laintextHolder.java => PlaintextHolder.kt} | 8 +- .../dissem/bitmessage/entity/Streamable.java | 31 - .../ch/dissem/bitmessage/entity/Streamable.kt | 30 + .../entity/{VerAck.java => VerAck.kt} | 27 +- .../ch/dissem/bitmessage/entity/Version.java | 246 ------ .../ch/dissem/bitmessage/entity/Version.kt | 204 +++++ .../bitmessage/entity/payload/Broadcast.java | 114 --- .../bitmessage/entity/payload/Broadcast.kt | 78 ++ .../bitmessage/entity/payload/CryptoBox.java | 214 ----- .../bitmessage/entity/payload/CryptoBox.kt | 210 +++++ .../entity/payload/GenericPayload.java | 88 --- .../entity/payload/GenericPayload.kt | 60 ++ .../bitmessage/entity/payload/GetPubkey.java | 82 -- .../bitmessage/entity/payload/GetPubkey.kt | 65 ++ .../dissem/bitmessage/entity/payload/Msg.java | 135 ---- .../dissem/bitmessage/entity/payload/Msg.kt | 103 +++ .../entity/payload/ObjectPayload.java | 66 -- .../entity/payload/ObjectPayload.kt | 44 ++ .../{ObjectType.java => ObjectType.kt} | 23 +- .../bitmessage/entity/payload/Pubkey.java | 121 --- .../bitmessage/entity/payload/Pubkey.kt | 112 +++ .../bitmessage/entity/payload/V2Pubkey.java | 133 ---- .../bitmessage/entity/payload/V2Pubkey.kt | 93 +++ .../bitmessage/entity/payload/V3Pubkey.java | 175 ----- .../bitmessage/entity/payload/V3Pubkey.kt | 150 ++++ .../entity/payload/V4Broadcast.java | 67 -- .../bitmessage/entity/payload/V4Broadcast.kt | 59 ++ .../bitmessage/entity/payload/V4Pubkey.java | 191 ----- .../bitmessage/entity/payload/V4Pubkey.kt | 139 ++++ .../entity/payload/V5Broadcast.java | 66 -- .../bitmessage/entity/payload/V5Broadcast.kt | 58 ++ .../entity/valueobject/ExtendedEncoding.java | 76 -- .../entity/valueobject/ExtendedEncoding.kt | 51 ++ .../entity/valueobject/InventoryVector.java | 82 -- .../entity/valueobject/InventoryVector.kt | 61 ++ .../bitmessage/entity/valueobject/Label.java | 89 --- .../bitmessage/entity/valueobject/Label.kt | 53 ++ .../entity/valueobject/NetworkAddress.java | 246 ------ .../entity/valueobject/NetworkAddress.kt | 183 +++++ .../entity/valueobject/PrivateKey.java | 198 ----- .../entity/valueobject/PrivateKey.kt | 171 ++++ .../valueobject/extended/Attachment.java | 100 --- .../entity/valueobject/extended/Attachment.kt | 90 +++ .../entity/valueobject/extended/Message.java | 219 ------ .../entity/valueobject/extended/Message.kt | 184 +++++ .../entity/valueobject/extended/Vote.java | 114 --- .../entity/valueobject/extended/Vote.kt | 89 +++ ...ception.java => AddressFormatException.kt} | 12 +- .../ApplicationException.kt} | 15 +- .../exception/DecryptionFailedException.kt | 19 + .../InsufficientProofOfWorkException.java | 30 - .../InsufficientProofOfWorkException.kt | 25 + .../{NodeException.java => NodeException.kt} | 18 +- .../dissem/bitmessage/factory/BufferPool.java | 92 --- .../dissem/bitmessage/factory/BufferPool.kt | 79 ++ .../factory/ExtendedEncodingFactory.java | 62 -- .../factory/ExtendedEncodingFactory.kt | 74 ++ .../ch/dissem/bitmessage/factory/Factory.java | 217 ------ .../ch/dissem/bitmessage/factory/Factory.kt | 206 +++++ .../bitmessage/factory/V3MessageFactory.java | 236 ------ .../bitmessage/factory/V3MessageFactory.kt | 230 ++++++ .../bitmessage/factory/V3MessageReader.java | 189 ----- .../bitmessage/factory/V3MessageReader.kt | 190 +++++ .../ports/AbstractCryptography.java | 214 ----- .../bitmessage/ports/AbstractCryptography.kt | 205 +++++ .../ports/AbstractMessageRepository.java | 162 ---- .../ports/AbstractMessageRepository.kt | 126 +++ ...ssRepository.java => AddressRepository.kt} | 39 +- .../{Cryptography.java => Cryptography.kt} | 163 ++-- .../bitmessage/ports/CustomCommandHandler.kt} | 15 +- .../bitmessage/ports/DefaultLabeler.java | 92 --- .../dissem/bitmessage/ports/DefaultLabeler.kt | 77 ++ .../ports/{Inventory.java => Inventory.kt} | 28 +- .../ports/{Labeler.java => Labeler.kt} | 35 +- .../bitmessage/ports/MessageRepository.java | 75 -- .../bitmessage/ports/MessageRepository.kt | 74 ++ .../ports/MultiThreadedPOWEngine.java | 128 --- .../ports/MultiThreadedPOWEngine.kt | 119 +++ ...{NetworkHandler.java => NetworkHandler.kt} | 58 +- .../{NodeRegistry.java => NodeRegistry.kt} | 16 +- .../bitmessage/ports/NodeRegistryHelper.java | 54 -- .../bitmessage/ports/NodeRegistryHelper.kt | 65 ++ ...OfWorkEngine.java => ProofOfWorkEngine.kt} | 16 +- .../ports/ProofOfWorkRepository.java | 45 -- .../bitmessage/ports/ProofOfWorkRepository.kt | 46 ++ .../bitmessage/ports/SimplePOWEngine.java | 50 -- .../bitmessage/ports/SimplePOWEngine.kt | 39 + .../{AccessCounter.java => AccessCounter.kt} | 56 +- .../ch/dissem/bitmessage/utils/Base58.java | 168 ---- .../java/ch/dissem/bitmessage/utils/Base58.kt | 161 ++++ .../ch/dissem/bitmessage/utils/Bytes.java | 6 +- ...{CallbackWaiter.java => CallbackWaiter.kt} | 37 +- .../dissem/bitmessage/utils/Collections.java | 71 -- .../ch/dissem/bitmessage/utils/Collections.kt | 70 ++ .../bitmessage/utils/ConversationService.java | 142 ---- .../bitmessage/utils/ConversationService.kt | 120 +++ .../dissem/bitmessage/utils/DebugUtils.java | 48 -- .../ch/dissem/bitmessage/utils/DebugUtils.kt | 47 ++ .../ch/dissem/bitmessage/utils/Decode.java | 152 ---- .../java/ch/dissem/bitmessage/utils/Decode.kt | 114 +++ .../ch/dissem/bitmessage/utils/Encode.java | 207 ----- .../java/ch/dissem/bitmessage/utils/Encode.kt | 165 ++++ .../ch/dissem/bitmessage/utils/Numbers.java | 10 - .../Numbers.kt} | 10 +- .../utils/{Points.java => Points.kt} | 24 +- .../ch/dissem/bitmessage/utils/Property.java | 91 --- .../ch/dissem/bitmessage/utils/Property.kt | 68 ++ .../utils/{Singleton.java => Singleton.kt} | 22 +- .../dissem/bitmessage/utils/SqlStrings.java | 59 -- .../ch/dissem/bitmessage/utils/SqlStrings.kt | 37 + .../utils/{Strings.java => Strings.kt} | 32 +- .../java/ch/dissem/bitmessage/utils/TTL.java | 44 -- .../java/ch/dissem/bitmessage/utils/TTL.kt | 49 ++ .../utils/ThreadFactoryBuilder.java | 65 -- .../bitmessage/utils/ThreadFactoryBuilder.kt | 63 ++ .../ch/dissem/bitmessage/utils/UnixTime.java | 52 -- .../ch/dissem/bitmessage/utils/UnixTime.kt | 43 + .../bitmessage/BitmessageContextTest.java | 321 -------- .../bitmessage/BitmessageContextTest.kt | 319 ++++++++ .../ch/dissem/bitmessage/DecryptionTest.java | 55 -- .../ch/dissem/bitmessage/DecryptionTest.kt | 50 ++ .../DefaultMessageListenerTest.java | 157 ---- .../bitmessage/DefaultMessageListenerTest.kt | 128 +++ .../ch/dissem/bitmessage/EncryptionTest.java | 64 -- .../ch/dissem/bitmessage/EncryptionTest.kt | 57 ++ .../bitmessage/ProofOfWorkServiceTest.java | 110 --- .../bitmessage/ProofOfWorkServiceTest.kt | 100 +++ .../ch/dissem/bitmessage/SignatureTest.java | 69 -- .../ch/dissem/bitmessage/SignatureTest.kt | 62 ++ .../entity/BitmessageAddressTest.java | 160 ---- .../entity/BitmessageAddressTest.kt | 159 ++++ .../entity/ExtendedEncodingTest.java | 82 -- .../bitmessage/entity/ExtendedEncodingTest.kt | 93 +++ .../bitmessage/entity/SerializationTest.java | 198 ----- .../bitmessage/entity/SerializationTest.kt | 197 +++++ .../ports/ProofOfWorkEngineTest.java | 72 -- .../bitmessage/ports/ProofOfWorkEngineTest.kt | 68 ++ .../bitmessage/testutils/TestInventory.java | 81 -- .../bitmessage/testutils/TestInventory.kt | 65 ++ .../ch/dissem/bitmessage/utils/BytesTest.java | 97 --- .../ch/dissem/bitmessage/utils/BytesTest.kt | 94 +++ .../bitmessage/utils/CollectionsTest.java | 35 - .../{StringsTest.java => CollectionsTest.kt} | 19 +- .../utils/ConversationServiceTest.java | 126 --- .../utils/ConversationServiceTest.kt | 124 +++ .../utils/{DecodeTest.java => DecodeTest.kt} | 33 +- .../dissem/bitmessage/utils/EncodeTest.java | 124 --- .../ch/dissem/bitmessage/utils/EncodeTest.kt | 121 +++ .../bitmessage/utils/MessageMatchers.java | 46 -- ...{SqlStringsTest.java => SqlStringsTest.kt} | 16 +- .../ch/dissem/bitmessage/utils/StringsTest.kt | 29 + .../ch/dissem/bitmessage/utils/TestBase.kt} | 22 +- .../ch/dissem/bitmessage/utils/TestUtils.java | 96 --- .../ch/dissem/bitmessage/utils/TestUtils.kt | 133 ++++ .../org.mockito.plugins.MockMaker | 1 + .../bitmessage/security/CryptographyTest.java | 57 +- .../bitmessage/security/CryptographyTest.java | 69 +- .../java/ch/dissem/bitmessage/SystemTest.java | 16 + .../ch/dissem/bitmessage/TestListener.java | 16 + .../dissem/bitmessage/TestNodeRegistry.java | 8 +- .../extensions/CryptoCustomMessage.java | 146 ---- .../extensions/pow/ProofOfWorkRequest.java | 134 ---- .../extensions/CryptoCustomMessage.kt | 146 ++++ .../extensions/pow/ProofOfWorkRequest.kt | 89 +++ .../extensions/CryptoCustomMessageTest.java | 86 -- .../extensions/CryptoCustomMessageTest.kt | 84 ++ gradle.properties | 5 +- .../networking/AbstractConnection.java | 24 +- .../networking/ConnectionOrganizer.java | 2 +- .../networking/DefaultNetworkHandler.java | 7 +- .../bitmessage/networking/ServerRunnable.java | 2 - .../networking/nio/NioNetworkHandler.java | 19 +- .../repository/JdbcAddressRepository.java | 36 +- .../bitmessage/repository/JdbcHelper.java | 8 - .../bitmessage/repository/JdbcInventory.java | 40 +- .../repository/JdbcMessageRepository.java | 6 +- .../repository/JdbcNodeRegistry.java | 20 +- .../repository/JdbcProofOfWorkRepository.java | 74 +- .../repository/JdbcAddressRepositoryTest.java | 5 +- .../repository/JdbcMessageRepositoryTest.java | 25 +- .../repository/JdbcNodeRegistryTest.java | 2 - .../JdbcProofOfWorkRepositoryTest.java | 165 ---- .../JdbcProofOfWorkRepositoryTest.kt | 166 ++++ .../bitmessage/repository/TestBase.java | 38 - .../dissem/bitmessage/repository/TestBase.kt | 38 + 213 files changed, 9846 insertions(+), 11067 deletions(-) delete mode 100644 core/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java create mode 100644 core/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.kt delete mode 100644 core/src/main/java/ch/dissem/bitmessage/InternalContext.java create mode 100644 core/src/main/java/ch/dissem/bitmessage/InternalContext.kt delete mode 100644 core/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.java create mode 100644 core/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.kt create mode 100644 core/src/main/java/ch/dissem/bitmessage/constants/constants.kt delete mode 100644 core/src/main/java/ch/dissem/bitmessage/entity/Addr.java create mode 100644 core/src/main/java/ch/dissem/bitmessage/entity/Addr.kt delete mode 100644 core/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java create mode 100644 core/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.kt delete mode 100644 core/src/main/java/ch/dissem/bitmessage/entity/CustomMessage.java create mode 100644 core/src/main/java/ch/dissem/bitmessage/entity/CustomMessage.kt rename core/src/main/java/ch/dissem/bitmessage/entity/{Encrypted.java => Encrypted.kt} (63%) delete mode 100644 core/src/main/java/ch/dissem/bitmessage/entity/GetData.java create mode 100644 core/src/main/java/ch/dissem/bitmessage/entity/GetData.kt delete mode 100644 core/src/main/java/ch/dissem/bitmessage/entity/Inv.java create mode 100644 core/src/main/java/ch/dissem/bitmessage/entity/Inv.kt rename core/src/main/java/ch/dissem/bitmessage/entity/{MessagePayload.java => MessagePayload.kt} (80%) delete mode 100644 core/src/main/java/ch/dissem/bitmessage/entity/NetworkMessage.java create mode 100644 core/src/main/java/ch/dissem/bitmessage/entity/NetworkMessage.kt delete mode 100644 core/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java create mode 100644 core/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.kt delete mode 100644 core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java create mode 100644 core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.kt rename core/src/main/java/ch/dissem/bitmessage/entity/{PlaintextHolder.java => PlaintextHolder.kt} (80%) delete mode 100644 core/src/main/java/ch/dissem/bitmessage/entity/Streamable.java create mode 100644 core/src/main/java/ch/dissem/bitmessage/entity/Streamable.kt rename core/src/main/java/ch/dissem/bitmessage/entity/{VerAck.java => VerAck.kt} (63%) delete mode 100644 core/src/main/java/ch/dissem/bitmessage/entity/Version.java create mode 100644 core/src/main/java/ch/dissem/bitmessage/entity/Version.kt delete mode 100644 core/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.java create mode 100644 core/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.kt delete mode 100644 core/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.java create mode 100644 core/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.kt delete mode 100644 core/src/main/java/ch/dissem/bitmessage/entity/payload/GenericPayload.java create mode 100644 core/src/main/java/ch/dissem/bitmessage/entity/payload/GenericPayload.kt delete mode 100644 core/src/main/java/ch/dissem/bitmessage/entity/payload/GetPubkey.java create mode 100644 core/src/main/java/ch/dissem/bitmessage/entity/payload/GetPubkey.kt delete mode 100644 core/src/main/java/ch/dissem/bitmessage/entity/payload/Msg.java create mode 100644 core/src/main/java/ch/dissem/bitmessage/entity/payload/Msg.kt delete mode 100644 core/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectPayload.java create mode 100644 core/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectPayload.kt rename core/src/main/java/ch/dissem/bitmessage/entity/payload/{ObjectType.java => ObjectType.kt} (64%) delete mode 100644 core/src/main/java/ch/dissem/bitmessage/entity/payload/Pubkey.java create mode 100644 core/src/main/java/ch/dissem/bitmessage/entity/payload/Pubkey.kt delete mode 100644 core/src/main/java/ch/dissem/bitmessage/entity/payload/V2Pubkey.java create mode 100644 core/src/main/java/ch/dissem/bitmessage/entity/payload/V2Pubkey.kt delete mode 100644 core/src/main/java/ch/dissem/bitmessage/entity/payload/V3Pubkey.java create mode 100644 core/src/main/java/ch/dissem/bitmessage/entity/payload/V3Pubkey.kt delete mode 100644 core/src/main/java/ch/dissem/bitmessage/entity/payload/V4Broadcast.java create mode 100644 core/src/main/java/ch/dissem/bitmessage/entity/payload/V4Broadcast.kt delete mode 100644 core/src/main/java/ch/dissem/bitmessage/entity/payload/V4Pubkey.java create mode 100644 core/src/main/java/ch/dissem/bitmessage/entity/payload/V4Pubkey.kt delete mode 100644 core/src/main/java/ch/dissem/bitmessage/entity/payload/V5Broadcast.java create mode 100644 core/src/main/java/ch/dissem/bitmessage/entity/payload/V5Broadcast.kt delete mode 100644 core/src/main/java/ch/dissem/bitmessage/entity/valueobject/ExtendedEncoding.java create mode 100644 core/src/main/java/ch/dissem/bitmessage/entity/valueobject/ExtendedEncoding.kt delete mode 100644 core/src/main/java/ch/dissem/bitmessage/entity/valueobject/InventoryVector.java create mode 100644 core/src/main/java/ch/dissem/bitmessage/entity/valueobject/InventoryVector.kt delete mode 100644 core/src/main/java/ch/dissem/bitmessage/entity/valueobject/Label.java create mode 100644 core/src/main/java/ch/dissem/bitmessage/entity/valueobject/Label.kt delete mode 100644 core/src/main/java/ch/dissem/bitmessage/entity/valueobject/NetworkAddress.java create mode 100644 core/src/main/java/ch/dissem/bitmessage/entity/valueobject/NetworkAddress.kt delete mode 100644 core/src/main/java/ch/dissem/bitmessage/entity/valueobject/PrivateKey.java create mode 100644 core/src/main/java/ch/dissem/bitmessage/entity/valueobject/PrivateKey.kt delete mode 100644 core/src/main/java/ch/dissem/bitmessage/entity/valueobject/extended/Attachment.java create mode 100644 core/src/main/java/ch/dissem/bitmessage/entity/valueobject/extended/Attachment.kt delete mode 100644 core/src/main/java/ch/dissem/bitmessage/entity/valueobject/extended/Message.java create mode 100644 core/src/main/java/ch/dissem/bitmessage/entity/valueobject/extended/Message.kt delete mode 100644 core/src/main/java/ch/dissem/bitmessage/entity/valueobject/extended/Vote.java create mode 100644 core/src/main/java/ch/dissem/bitmessage/entity/valueobject/extended/Vote.kt rename core/src/main/java/ch/dissem/bitmessage/exception/{AddressFormatException.java => AddressFormatException.kt} (67%) rename core/src/main/java/ch/dissem/bitmessage/{ports/CustomCommandHandler.java => exception/ApplicationException.kt} (69%) create mode 100644 core/src/main/java/ch/dissem/bitmessage/exception/DecryptionFailedException.kt delete mode 100644 core/src/main/java/ch/dissem/bitmessage/exception/InsufficientProofOfWorkException.java create mode 100644 core/src/main/java/ch/dissem/bitmessage/exception/InsufficientProofOfWorkException.kt rename core/src/main/java/ch/dissem/bitmessage/exception/{NodeException.java => NodeException.kt} (64%) delete mode 100644 core/src/main/java/ch/dissem/bitmessage/factory/BufferPool.java create mode 100644 core/src/main/java/ch/dissem/bitmessage/factory/BufferPool.kt delete mode 100644 core/src/main/java/ch/dissem/bitmessage/factory/ExtendedEncodingFactory.java create mode 100644 core/src/main/java/ch/dissem/bitmessage/factory/ExtendedEncodingFactory.kt delete mode 100644 core/src/main/java/ch/dissem/bitmessage/factory/Factory.java create mode 100644 core/src/main/java/ch/dissem/bitmessage/factory/Factory.kt delete mode 100644 core/src/main/java/ch/dissem/bitmessage/factory/V3MessageFactory.java create mode 100644 core/src/main/java/ch/dissem/bitmessage/factory/V3MessageFactory.kt delete mode 100644 core/src/main/java/ch/dissem/bitmessage/factory/V3MessageReader.java create mode 100644 core/src/main/java/ch/dissem/bitmessage/factory/V3MessageReader.kt delete mode 100644 core/src/main/java/ch/dissem/bitmessage/ports/AbstractCryptography.java create mode 100644 core/src/main/java/ch/dissem/bitmessage/ports/AbstractCryptography.kt delete mode 100644 core/src/main/java/ch/dissem/bitmessage/ports/AbstractMessageRepository.java create mode 100644 core/src/main/java/ch/dissem/bitmessage/ports/AbstractMessageRepository.kt rename core/src/main/java/ch/dissem/bitmessage/ports/{AddressRepository.java => AddressRepository.kt} (63%) rename core/src/main/java/ch/dissem/bitmessage/ports/{Cryptography.java => Cryptography.kt} (65%) rename core/src/{test/java/ch/dissem/bitmessage/utils/TestBase.java => main/java/ch/dissem/bitmessage/ports/CustomCommandHandler.kt} (66%) delete mode 100644 core/src/main/java/ch/dissem/bitmessage/ports/DefaultLabeler.java create mode 100644 core/src/main/java/ch/dissem/bitmessage/ports/DefaultLabeler.kt rename core/src/main/java/ch/dissem/bitmessage/ports/{Inventory.java => Inventory.kt} (65%) rename core/src/main/java/ch/dissem/bitmessage/ports/{Labeler.java => Labeler.kt} (56%) delete mode 100644 core/src/main/java/ch/dissem/bitmessage/ports/MessageRepository.java create mode 100644 core/src/main/java/ch/dissem/bitmessage/ports/MessageRepository.kt delete mode 100644 core/src/main/java/ch/dissem/bitmessage/ports/MultiThreadedPOWEngine.java create mode 100644 core/src/main/java/ch/dissem/bitmessage/ports/MultiThreadedPOWEngine.kt rename core/src/main/java/ch/dissem/bitmessage/ports/{NetworkHandler.java => NetworkHandler.kt} (58%) rename core/src/main/java/ch/dissem/bitmessage/ports/{NodeRegistry.java => NodeRegistry.kt} (69%) delete mode 100644 core/src/main/java/ch/dissem/bitmessage/ports/NodeRegistryHelper.java create mode 100644 core/src/main/java/ch/dissem/bitmessage/ports/NodeRegistryHelper.kt rename core/src/main/java/ch/dissem/bitmessage/ports/{ProofOfWorkEngine.java => ProofOfWorkEngine.kt} (77%) delete mode 100644 core/src/main/java/ch/dissem/bitmessage/ports/ProofOfWorkRepository.java create mode 100644 core/src/main/java/ch/dissem/bitmessage/ports/ProofOfWorkRepository.kt delete mode 100644 core/src/main/java/ch/dissem/bitmessage/ports/SimplePOWEngine.java create mode 100644 core/src/main/java/ch/dissem/bitmessage/ports/SimplePOWEngine.kt rename core/src/main/java/ch/dissem/bitmessage/utils/{AccessCounter.java => AccessCounter.kt} (52%) delete mode 100644 core/src/main/java/ch/dissem/bitmessage/utils/Base58.java create mode 100644 core/src/main/java/ch/dissem/bitmessage/utils/Base58.kt rename core/src/main/java/ch/dissem/bitmessage/utils/{CallbackWaiter.java => CallbackWaiter.kt} (52%) delete mode 100644 core/src/main/java/ch/dissem/bitmessage/utils/Collections.java create mode 100644 core/src/main/java/ch/dissem/bitmessage/utils/Collections.kt delete mode 100644 core/src/main/java/ch/dissem/bitmessage/utils/ConversationService.java create mode 100644 core/src/main/java/ch/dissem/bitmessage/utils/ConversationService.kt delete mode 100644 core/src/main/java/ch/dissem/bitmessage/utils/DebugUtils.java create mode 100644 core/src/main/java/ch/dissem/bitmessage/utils/DebugUtils.kt delete mode 100644 core/src/main/java/ch/dissem/bitmessage/utils/Decode.java create mode 100644 core/src/main/java/ch/dissem/bitmessage/utils/Decode.kt delete mode 100644 core/src/main/java/ch/dissem/bitmessage/utils/Encode.java create mode 100644 core/src/main/java/ch/dissem/bitmessage/utils/Encode.kt delete mode 100644 core/src/main/java/ch/dissem/bitmessage/utils/Numbers.java rename core/src/main/java/ch/dissem/bitmessage/{exception/DecryptionFailedException.java => utils/Numbers.kt} (73%) rename core/src/main/java/ch/dissem/bitmessage/utils/{Points.java => Points.kt} (52%) delete mode 100644 core/src/main/java/ch/dissem/bitmessage/utils/Property.java create mode 100644 core/src/main/java/ch/dissem/bitmessage/utils/Property.kt rename core/src/main/java/ch/dissem/bitmessage/utils/{Singleton.java => Singleton.kt} (58%) delete mode 100644 core/src/main/java/ch/dissem/bitmessage/utils/SqlStrings.java create mode 100644 core/src/main/java/ch/dissem/bitmessage/utils/SqlStrings.kt rename core/src/main/java/ch/dissem/bitmessage/utils/{Strings.java => Strings.kt} (50%) delete mode 100644 core/src/main/java/ch/dissem/bitmessage/utils/TTL.java create mode 100644 core/src/main/java/ch/dissem/bitmessage/utils/TTL.kt delete mode 100644 core/src/main/java/ch/dissem/bitmessage/utils/ThreadFactoryBuilder.java create mode 100644 core/src/main/java/ch/dissem/bitmessage/utils/ThreadFactoryBuilder.kt delete mode 100644 core/src/main/java/ch/dissem/bitmessage/utils/UnixTime.java create mode 100644 core/src/main/java/ch/dissem/bitmessage/utils/UnixTime.kt delete mode 100644 core/src/test/java/ch/dissem/bitmessage/BitmessageContextTest.java create mode 100644 core/src/test/java/ch/dissem/bitmessage/BitmessageContextTest.kt delete mode 100644 core/src/test/java/ch/dissem/bitmessage/DecryptionTest.java create mode 100644 core/src/test/java/ch/dissem/bitmessage/DecryptionTest.kt delete mode 100644 core/src/test/java/ch/dissem/bitmessage/DefaultMessageListenerTest.java create mode 100644 core/src/test/java/ch/dissem/bitmessage/DefaultMessageListenerTest.kt delete mode 100644 core/src/test/java/ch/dissem/bitmessage/EncryptionTest.java create mode 100644 core/src/test/java/ch/dissem/bitmessage/EncryptionTest.kt delete mode 100644 core/src/test/java/ch/dissem/bitmessage/ProofOfWorkServiceTest.java create mode 100644 core/src/test/java/ch/dissem/bitmessage/ProofOfWorkServiceTest.kt delete mode 100644 core/src/test/java/ch/dissem/bitmessage/SignatureTest.java create mode 100644 core/src/test/java/ch/dissem/bitmessage/SignatureTest.kt delete mode 100644 core/src/test/java/ch/dissem/bitmessage/entity/BitmessageAddressTest.java create mode 100644 core/src/test/java/ch/dissem/bitmessage/entity/BitmessageAddressTest.kt delete mode 100644 core/src/test/java/ch/dissem/bitmessage/entity/ExtendedEncodingTest.java create mode 100644 core/src/test/java/ch/dissem/bitmessage/entity/ExtendedEncodingTest.kt delete mode 100644 core/src/test/java/ch/dissem/bitmessage/entity/SerializationTest.java create mode 100644 core/src/test/java/ch/dissem/bitmessage/entity/SerializationTest.kt delete mode 100644 core/src/test/java/ch/dissem/bitmessage/ports/ProofOfWorkEngineTest.java create mode 100644 core/src/test/java/ch/dissem/bitmessage/ports/ProofOfWorkEngineTest.kt delete mode 100644 core/src/test/java/ch/dissem/bitmessage/testutils/TestInventory.java create mode 100644 core/src/test/java/ch/dissem/bitmessage/testutils/TestInventory.kt delete mode 100644 core/src/test/java/ch/dissem/bitmessage/utils/BytesTest.java create mode 100644 core/src/test/java/ch/dissem/bitmessage/utils/BytesTest.kt delete mode 100644 core/src/test/java/ch/dissem/bitmessage/utils/CollectionsTest.java rename core/src/test/java/ch/dissem/bitmessage/utils/{StringsTest.java => CollectionsTest.kt} (60%) delete mode 100644 core/src/test/java/ch/dissem/bitmessage/utils/ConversationServiceTest.java create mode 100644 core/src/test/java/ch/dissem/bitmessage/utils/ConversationServiceTest.kt rename core/src/test/java/ch/dissem/bitmessage/utils/{DecodeTest.java => DecodeTest.kt} (54%) delete mode 100644 core/src/test/java/ch/dissem/bitmessage/utils/EncodeTest.java create mode 100644 core/src/test/java/ch/dissem/bitmessage/utils/EncodeTest.kt delete mode 100644 core/src/test/java/ch/dissem/bitmessage/utils/MessageMatchers.java rename core/src/test/java/ch/dissem/bitmessage/utils/{SqlStringsTest.java => SqlStringsTest.kt} (65%) create mode 100644 core/src/test/java/ch/dissem/bitmessage/utils/StringsTest.kt rename core/src/{main/java/ch/dissem/bitmessage/exception/ApplicationException.java => test/java/ch/dissem/bitmessage/utils/TestBase.kt} (62%) delete mode 100644 core/src/test/java/ch/dissem/bitmessage/utils/TestUtils.java create mode 100644 core/src/test/java/ch/dissem/bitmessage/utils/TestUtils.kt create mode 100644 core/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker delete mode 100644 extensions/src/main/java/ch/dissem/bitmessage/extensions/CryptoCustomMessage.java delete mode 100644 extensions/src/main/java/ch/dissem/bitmessage/extensions/pow/ProofOfWorkRequest.java create mode 100644 extensions/src/main/kotlin/ch/dissem/bitmessage/extensions/CryptoCustomMessage.kt create mode 100644 extensions/src/main/kotlin/ch/dissem/bitmessage/extensions/pow/ProofOfWorkRequest.kt delete mode 100644 extensions/src/test/java/ch/dissem/bitmessage/extensions/CryptoCustomMessageTest.java create mode 100644 extensions/src/test/kotlin/ch/dissem/bitmessage/extensions/CryptoCustomMessageTest.kt delete mode 100644 repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepositoryTest.java create mode 100644 repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepositoryTest.kt delete mode 100644 repositories/src/test/java/ch/dissem/bitmessage/repository/TestBase.java create mode 100644 repositories/src/test/java/ch/dissem/bitmessage/repository/TestBase.kt diff --git a/build.gradle b/build.gradle index 78bf578..26aa243 100644 --- a/build.gradle +++ b/build.gradle @@ -1,9 +1,19 @@ +buildscript { + ext.kotlin_version = '1.1.2-2' + repositories { + mavenCentral() + } + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} plugins { id 'com.github.ben-manes.versions' version '0.14.0' } subprojects { apply plugin: 'java' + apply plugin: 'kotlin' apply plugin: 'maven' apply plugin: 'signing' apply plugin: 'jacoco' @@ -17,6 +27,9 @@ subprojects { mavenCentral() maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } } + dependencies { + compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version" + } test { testLogging { diff --git a/core/build.gradle b/core/build.gradle index 785507e..f43c541 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -28,6 +28,6 @@ dependencies { compile 'ch.dissem.msgpack:msgpack:1.0.0' testCompile 'junit:junit:4.12' testCompile 'org.hamcrest:hamcrest-library:1.3' - testCompile 'org.mockito:mockito-core:2.7.21' + testCompile 'com.nhaarman:mockito-kotlin:1.4.0' testCompile project(':cryptography-bc') } diff --git a/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java b/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java index 29639e7..4bf0adc 100644 --- a/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java +++ b/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java @@ -66,7 +66,22 @@ public class BitmessageContext { private final boolean sendPubkeyOnIdentityCreation; private BitmessageContext(Builder builder) { - ctx = new InternalContext(builder); + ctx = new InternalContext( + builder.cryptography, + builder.inventory, + builder.nodeRegistry, + builder.networkHandler, + builder.addressRepo, + builder.messageRepo, + builder.proofOfWorkRepository, + builder.proofOfWorkEngine, + builder.customCommandHandler, + builder.listener, + builder.labeler, + builder.port, + builder.connectionTTL, + builder.connectionLimit + ); labeler = builder.labeler; ctx.getProofOfWorkService().doMissingProofOfWork(30_000); // TODO: this should be configurable sendPubkeyOnIdentityCreation = builder.sendPubkeyOnIdentityCreation; @@ -103,7 +118,7 @@ public class BitmessageContext { } public BitmessageAddress joinChan(String passphrase, String address) { - BitmessageAddress chan = BitmessageAddress.chan(address, passphrase); + BitmessageAddress chan = BitmessageAddress.Companion.chan(address, passphrase); chan.setAlias(passphrase); ctx.getAddressRepository().save(chan); return chan; @@ -111,14 +126,14 @@ public class BitmessageContext { public BitmessageAddress createChan(String passphrase) { // FIXME: hardcoded stream number - BitmessageAddress chan = BitmessageAddress.chan(1, passphrase); + BitmessageAddress chan = BitmessageAddress.Companion.chan(1, passphrase); ctx.getAddressRepository().save(chan); return chan; } public List createDeterministicAddresses( String passphrase, int numberOfAddresses, long version, long stream, boolean shorter) { - List result = BitmessageAddress.deterministic( + List result = BitmessageAddress.Companion.deterministic( passphrase, numberOfAddresses, version, stream, shorter); for (int i = 0; i < result.size(); i++) { BitmessageAddress address = result.get(i); @@ -149,7 +164,7 @@ public class BitmessageContext { } public void send(final Plaintext msg) { - if (msg.getFrom() == null || msg.getFrom().getPrivateKey() == null) { + if (msg.getFrom().getPrivateKey() == null) { throw new IllegalArgumentException("'From' must be an identity, i.e. have a private key."); } labeler().markAsSending(msg); @@ -268,7 +283,7 @@ public class BitmessageContext { } private void tryToFindBroadcastsForAddress(BitmessageAddress address) { - for (ObjectMessage object : ctx.getInventory().getObjects(address.getStream(), Broadcast.getVersion(address), ObjectType.BROADCAST)) { + for (ObjectMessage object : ctx.getInventory().getObjects(address.getStream(), Broadcast.Companion.getVersion(address), ObjectType.BROADCAST)) { try { Broadcast broadcast = (Broadcast) object.getPayload(); broadcast.decrypt(address); diff --git a/core/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java b/core/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java deleted file mode 100644 index 4fb438f..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage; - -import ch.dissem.bitmessage.entity.BitmessageAddress; -import ch.dissem.bitmessage.entity.ObjectMessage; -import ch.dissem.bitmessage.entity.Plaintext; -import ch.dissem.bitmessage.entity.payload.*; -import ch.dissem.bitmessage.entity.valueobject.InventoryVector; -import ch.dissem.bitmessage.exception.DecryptionFailedException; -import ch.dissem.bitmessage.ports.Labeler; -import ch.dissem.bitmessage.ports.NetworkHandler; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.util.Arrays; -import java.util.List; - -import static ch.dissem.bitmessage.entity.Plaintext.Status.PUBKEY_REQUESTED; - -class DefaultMessageListener implements NetworkHandler.MessageListener, InternalContext.ContextHolder { - private final static Logger LOG = LoggerFactory.getLogger(DefaultMessageListener.class); - private final Labeler labeler; - private final BitmessageContext.Listener listener; - private InternalContext ctx; - - public DefaultMessageListener(Labeler labeler, BitmessageContext.Listener listener) { - this.labeler = labeler; - this.listener = listener; - } - - @Override - public void setContext(InternalContext context) { - this.ctx = context; - } - - @Override - @SuppressWarnings("ConstantConditions") - public void receive(ObjectMessage object) throws IOException { - ObjectPayload payload = object.getPayload(); - if (payload.getType() == null) { - if (payload instanceof GenericPayload) { - receive((GenericPayload) payload); - } - return; - } - - switch (payload.getType()) { - case GET_PUBKEY: { - receive(object, (GetPubkey) payload); - break; - } - case PUBKEY: { - receive(object, (Pubkey) payload); - break; - } - case MSG: { - receive(object, (Msg) payload); - break; - } - case BROADCAST: { - receive(object, (Broadcast) payload); - break; - } - default: { - throw new IllegalArgumentException("Unknown payload type " + payload.getType()); - } - } - } - - protected void receive(ObjectMessage object, GetPubkey getPubkey) { - BitmessageAddress identity = ctx.getAddressRepository().findIdentity(getPubkey.getRipeTag()); - if (identity != null && identity.getPrivateKey() != null && !identity.isChan()) { - LOG.info("Got pubkey request for identity " + identity); - // FIXME: only send pubkey if it wasn't sent in the last TTL.pubkey() days - ctx.sendPubkey(identity, object.getStream()); - } - } - - protected void receive(ObjectMessage object, Pubkey pubkey) throws IOException { - BitmessageAddress address; - try { - if (pubkey instanceof V4Pubkey) { - V4Pubkey v4Pubkey = (V4Pubkey) pubkey; - address = ctx.getAddressRepository().findContact(v4Pubkey.getTag()); - if (address != null) { - v4Pubkey.decrypt(address.getPublicDecryptionKey()); - } - } else { - address = ctx.getAddressRepository().findContact(pubkey.getRipe()); - } - if (address != null && address.getPubkey() == null) { - updatePubkey(address, pubkey); - } - } catch (DecryptionFailedException ignore) { - } - } - - private void updatePubkey(BitmessageAddress address, Pubkey pubkey) { - address.setPubkey(pubkey); - LOG.info("Got pubkey for contact " + address); - ctx.getAddressRepository().save(address); - List messages = ctx.getMessageRepository().findMessages(PUBKEY_REQUESTED, address); - LOG.info("Sending " + messages.size() + " messages for contact " + address); - for (Plaintext msg : messages) { - ctx.getLabeler().markAsSending(msg); - ctx.getMessageRepository().save(msg); - ctx.send(msg); - } - } - - protected void receive(ObjectMessage object, Msg msg) throws IOException { - for (BitmessageAddress identity : ctx.getAddressRepository().getIdentities()) { - try { - msg.decrypt(identity.getPrivateKey().getPrivateEncryptionKey()); - Plaintext plaintext = msg.getPlaintext(); - plaintext.setTo(identity); - if (!object.isSignatureValid(plaintext.getFrom().getPubkey())) { - LOG.warn("Msg with IV " + object.getInventoryVector() + " was successfully decrypted, but signature check failed. Ignoring."); - } else { - receive(object.getInventoryVector(), plaintext); - } - break; - } catch (DecryptionFailedException ignore) { - } - } - } - - protected void receive(GenericPayload ack) { - if (ack.getData().length == Msg.ACK_LENGTH) { - Plaintext msg = ctx.getMessageRepository().getMessageForAck(ack.getData()); - if (msg != null) { - ctx.getLabeler().markAsAcknowledged(msg); - ctx.getMessageRepository().save(msg); - } - } - } - - protected void receive(ObjectMessage object, Broadcast broadcast) throws IOException { - byte[] tag = broadcast instanceof V5Broadcast ? ((V5Broadcast) broadcast).getTag() : null; - for (BitmessageAddress subscription : ctx.getAddressRepository().getSubscriptions(broadcast.getVersion())) { - if (tag != null && !Arrays.equals(tag, subscription.getTag())) { - continue; - } - try { - broadcast.decrypt(subscription.getPublicDecryptionKey()); - if (!object.isSignatureValid(broadcast.getPlaintext().getFrom().getPubkey())) { - LOG.warn("Broadcast with IV " + object.getInventoryVector() + " was successfully decrypted, but signature check failed. Ignoring."); - } else { - receive(object.getInventoryVector(), broadcast.getPlaintext()); - } - } catch (DecryptionFailedException ignore) { - } - } - } - - protected void receive(InventoryVector iv, Plaintext msg) { - BitmessageAddress contact = ctx.getAddressRepository().getAddress(msg.getFrom().getAddress()); - if (contact != null && contact.getPubkey() == null) { - updatePubkey(contact, msg.getFrom().getPubkey()); - } - - msg.setInventoryVector(iv); - labeler.setLabels(msg); - ctx.getMessageRepository().save(msg); - listener.receive(msg); - - if (msg.getType() == Plaintext.Type.MSG && msg.getTo().has(Pubkey.Feature.DOES_ACK)) { - ObjectMessage ack = msg.getAckMessage(); - if (ack != null) { - ctx.getInventory().storeObject(ack); - ctx.getNetworkHandler().offer(ack.getInventoryVector()); - } - } - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.kt b/core/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.kt new file mode 100644 index 0000000..35a7628 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.kt @@ -0,0 +1,168 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage + +import ch.dissem.bitmessage.entity.BitmessageAddress +import ch.dissem.bitmessage.entity.ObjectMessage +import ch.dissem.bitmessage.entity.Plaintext +import ch.dissem.bitmessage.entity.Plaintext.Status.PUBKEY_REQUESTED +import ch.dissem.bitmessage.entity.payload.* +import ch.dissem.bitmessage.entity.valueobject.InventoryVector +import ch.dissem.bitmessage.exception.DecryptionFailedException +import ch.dissem.bitmessage.ports.Labeler +import ch.dissem.bitmessage.ports.NetworkHandler +import org.slf4j.LoggerFactory +import java.util.* + +internal open class DefaultMessageListener( + private val labeler: Labeler, + private val listener: BitmessageContext.Listener +) : NetworkHandler.MessageListener { + private var ctx by InternalContext + + override fun receive(`object`: ObjectMessage) { + val payload = `object`.payload + + when (payload.type) { + ObjectType.GET_PUBKEY -> { + receive(`object`, payload as GetPubkey) + } + ObjectType.PUBKEY -> { + receive(`object`, payload as Pubkey) + } + ObjectType.MSG -> { + receive(`object`, payload as Msg) + } + ObjectType.BROADCAST -> { + receive(`object`, payload as Broadcast) + } + null -> { + if (payload is GenericPayload) { + receive(payload) + } + } + else -> { + throw IllegalArgumentException("Unknown payload type " + payload.type!!) + } + } + } + + protected fun receive(`object`: ObjectMessage, getPubkey: GetPubkey) { + val identity = ctx.addressRepository.findIdentity(getPubkey.ripeTag) + if (identity != null && identity.privateKey != null && !identity.isChan) { + LOG.info("Got pubkey request for identity " + identity) + // FIXME: only send pubkey if it wasn't sent in the last TTL.pubkey() days + ctx.sendPubkey(identity, `object`.stream) + } + } + + protected fun receive(`object`: ObjectMessage, pubkey: Pubkey) { + val address: BitmessageAddress? + try { + if (pubkey is V4Pubkey) { + address = ctx.addressRepository.findContact(pubkey.tag) + if (address != null) { + pubkey.decrypt(address.publicDecryptionKey) + } + } else { + address = ctx.addressRepository.findContact(pubkey.ripe) + } + if (address != null && address.pubkey == null) { + updatePubkey(address, pubkey) + } + } catch (_: DecryptionFailedException) {} + + } + + private fun updatePubkey(address: BitmessageAddress, pubkey: Pubkey) { + address.pubkey = pubkey + LOG.info("Got pubkey for contact " + address) + ctx.addressRepository.save(address) + val messages = ctx.messageRepository.findMessages(PUBKEY_REQUESTED, address) + LOG.info("Sending " + messages.size + " messages for contact " + address) + for (msg in messages) { + ctx.labeler.markAsSending(msg) + ctx.messageRepository.save(msg) + ctx.send(msg) + } + } + + protected fun receive(`object`: ObjectMessage, msg: Msg) { + for (identity in ctx.addressRepository.getIdentities()) { + try { + msg.decrypt(identity.privateKey!!.privateEncryptionKey) + val plaintext = msg.plaintext!! + plaintext.to = identity + if (!`object`.isSignatureValid(plaintext.from.pubkey!!)) { + LOG.warn("Msg with IV " + `object`.inventoryVector + " was successfully decrypted, but signature check failed. Ignoring.") + } else { + receive(`object`.inventoryVector, plaintext) + } + break + } catch (_: DecryptionFailedException) {} + } + } + + protected fun receive(ack: GenericPayload) { + if (ack.data.size == Msg.ACK_LENGTH) { + ctx.messageRepository.getMessageForAck(ack.data)?.let { + ctx.labeler.markAsAcknowledged(it) + ctx.messageRepository.save(it) + } + } + } + + protected fun receive(`object`: ObjectMessage, broadcast: Broadcast) { + val tag = if (broadcast is V5Broadcast) broadcast.tag else null + for (subscription in ctx.addressRepository.getSubscriptions(broadcast.version)) { + if (tag != null && !Arrays.equals(tag, subscription.tag)) { + continue + } + try { + broadcast.decrypt(subscription.publicDecryptionKey) + if (!`object`.isSignatureValid(broadcast.plaintext!!.from.pubkey!!)) { + LOG.warn("Broadcast with IV " + `object`.inventoryVector + " was successfully decrypted, but signature check failed. Ignoring.") + } else { + receive(`object`.inventoryVector, broadcast.plaintext!!) + } + } catch (_: DecryptionFailedException) {} + } + } + + protected fun receive(iv: InventoryVector, msg: Plaintext) { + val contact = ctx.addressRepository.getAddress(msg.from.address) + if (contact != null && contact.pubkey == null) { + updatePubkey(contact, msg.from.pubkey!!) + } + + msg.inventoryVector = iv + labeler.setLabels(msg) + ctx.messageRepository.save(msg) + listener.receive(msg) + + if (msg.type == Plaintext.Type.MSG && msg.to!!.has(Pubkey.Feature.DOES_ACK)) { + msg.ackMessage?.let { + ctx.inventory.storeObject(it) + ctx.networkHandler.offer(it.inventoryVector) + } + } + } + + companion object { + private val LOG = LoggerFactory.getLogger(DefaultMessageListener::class.java) + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/InternalContext.java b/core/src/main/java/ch/dissem/bitmessage/InternalContext.java deleted file mode 100644 index 007e8f2..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/InternalContext.java +++ /dev/null @@ -1,326 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage; - -import ch.dissem.bitmessage.entity.*; -import ch.dissem.bitmessage.entity.payload.*; -import ch.dissem.bitmessage.exception.ApplicationException; -import ch.dissem.bitmessage.ports.*; -import ch.dissem.bitmessage.utils.Singleton; -import ch.dissem.bitmessage.utils.TTL; -import ch.dissem.bitmessage.utils.UnixTime; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.util.Arrays; -import java.util.List; -import java.util.TreeSet; -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; - -/** - * The internal context should normally only be used for port implementations. If you need it in your client - * implementation, you're either doing something wrong, something very weird, or the BitmessageContext should - * get extended. - * <p> - * On the other hand, if you need the BitmessageContext in a port implementation, the same thing might apply. - * </p> - */ -public class InternalContext { - private final static Logger LOG = LoggerFactory.getLogger(InternalContext.class); - - public final static long NETWORK_NONCE_TRIALS_PER_BYTE = 1000; - public final static long NETWORK_EXTRA_BYTES = 1000; - - private final Executor threadPool = Executors.newCachedThreadPool(); - - private final Cryptography cryptography; - private final Inventory inventory; - private final NodeRegistry nodeRegistry; - private final NetworkHandler networkHandler; - private final AddressRepository addressRepository; - private final MessageRepository messageRepository; - private final ProofOfWorkRepository proofOfWorkRepository; - private final ProofOfWorkEngine proofOfWorkEngine; - private final CustomCommandHandler customCommandHandler; - private final ProofOfWorkService proofOfWorkService; - private final Labeler labeler; - private final NetworkHandler.MessageListener networkListener; - - private final TreeSet<Long> streams = new TreeSet<>(); - private final int port; - private final long clientNonce; - private long connectionTTL; - private int connectionLimit; - - public InternalContext(BitmessageContext.Builder builder) { - this.cryptography = builder.cryptography; - this.inventory = builder.inventory; - this.nodeRegistry = builder.nodeRegistry; - this.networkHandler = builder.networkHandler; - this.addressRepository = builder.addressRepo; - this.messageRepository = builder.messageRepo; - this.proofOfWorkRepository = builder.proofOfWorkRepository; - this.proofOfWorkService = new ProofOfWorkService(); - this.proofOfWorkEngine = builder.proofOfWorkEngine; - this.clientNonce = cryptography.randomNonce(); - this.customCommandHandler = builder.customCommandHandler; - this.port = builder.port; - this.connectionLimit = builder.connectionLimit; - this.connectionTTL = builder.connectionTTL; - this.labeler = builder.labeler; - this.networkListener = new DefaultMessageListener(labeler, builder.listener); - - Singleton.initialize(cryptography); - - // TODO: streams of new identities and subscriptions should also be added. This works only after a restart. - for (BitmessageAddress address : addressRepository.getIdentities()) { - streams.add(address.getStream()); - } - for (BitmessageAddress address : addressRepository.getSubscriptions()) { - streams.add(address.getStream()); - } - if (streams.isEmpty()) { - streams.add(1L); - } - - init(cryptography, inventory, nodeRegistry, networkHandler, addressRepository, messageRepository, - proofOfWorkRepository, proofOfWorkService, proofOfWorkEngine, customCommandHandler, builder.labeler, - networkListener); - for (BitmessageAddress identity : addressRepository.getIdentities()) { - streams.add(identity.getStream()); - } - } - - private void init(Object... objects) { - for (Object o : objects) { - if (o instanceof ContextHolder) { - ((ContextHolder) o).setContext(this); - } - } - } - - public Cryptography getCryptography() { - return cryptography; - } - - public Inventory getInventory() { - return inventory; - } - - public NodeRegistry getNodeRegistry() { - return nodeRegistry; - } - - public NetworkHandler getNetworkHandler() { - return networkHandler; - } - - public AddressRepository getAddressRepository() { - return addressRepository; - } - - public MessageRepository getMessageRepository() { - return messageRepository; - } - - public ProofOfWorkRepository getProofOfWorkRepository() { - return proofOfWorkRepository; - } - - public ProofOfWorkEngine getProofOfWorkEngine() { - return proofOfWorkEngine; - } - - public ProofOfWorkService getProofOfWorkService() { - return proofOfWorkService; - } - - public Labeler getLabeler() { - return labeler; - } - - public NetworkHandler.MessageListener getNetworkListener() { - return networkListener; - } - - public long[] getStreams() { - long[] result = new long[streams.size()]; - int i = 0; - for (long stream : streams) { - result[i++] = stream; - } - return result; - } - - public int getPort() { - return port; - } - - public void send(final Plaintext plaintext) { - if (plaintext.getAckMessage() != null) { - long expires = UnixTime.now(+plaintext.getTTL()); - LOG.info("Expires at " + expires); - proofOfWorkService.doProofOfWorkWithAck(plaintext, expires); - } else { - send(plaintext.getFrom(), plaintext.getTo(), new Msg(plaintext), plaintext.getTTL()); - } - } - - public void send(final BitmessageAddress from, BitmessageAddress to, final ObjectPayload payload, - final long timeToLive) { - try { - final BitmessageAddress recipient = (to != null ? to : from); - long expires = UnixTime.now(+timeToLive); - LOG.info("Expires at " + expires); - final ObjectMessage object = new ObjectMessage.Builder() - .stream(recipient.getStream()) - .expiresTime(expires) - .payload(payload) - .build(); - if (object.isSigned()) { - object.sign(from.getPrivateKey()); - } - if (payload instanceof Broadcast) { - ((Broadcast) payload).encrypt(); - } else if (payload instanceof Encrypted) { - object.encrypt(recipient.getPubkey()); - } - proofOfWorkService.doProofOfWork(to, object); - } catch (IOException e) { - throw new ApplicationException(e); - } - } - - public void sendPubkey(final BitmessageAddress identity, final long targetStream) { - try { - long expires = UnixTime.now(TTL.pubkey()); - LOG.info("Expires at " + expires); - final ObjectMessage response = new ObjectMessage.Builder() - .stream(targetStream) - .expiresTime(expires) - .payload(identity.getPubkey()) - .build(); - response.sign(identity.getPrivateKey()); - response.encrypt(cryptography.createPublicKey(identity.getPublicDecryptionKey())); - // TODO: remember that the pubkey is just about to be sent, and on which stream! - proofOfWorkService.doProofOfWork(response); - } catch (IOException e) { - throw new ApplicationException(e); - } - } - - /** - * Be aware that if the pubkey already exists in the inventory, the metods will not request it and the callback - * for freshly received pubkeys will not be called. Instead the pubkey is added to the contact and stored on DB. - */ - public void requestPubkey(final BitmessageAddress contact) { - threadPool.execute(new Runnable() { - @Override - public void run() { - BitmessageAddress stored = addressRepository.getAddress(contact.getAddress()); - - tryToFindMatchingPubkey(contact); - if (contact.getPubkey() != null) { - if (stored != null) { - stored.setPubkey(contact.getPubkey()); - addressRepository.save(stored); - } else { - addressRepository.save(contact); - } - return; - } - - if (stored == null) { - addressRepository.save(contact); - } - - long expires = UnixTime.now(TTL.getpubkey()); - LOG.info("Expires at " + expires); - final ObjectMessage request = new ObjectMessage.Builder() - .stream(contact.getStream()) - .expiresTime(expires) - .payload(new GetPubkey(contact)) - .build(); - proofOfWorkService.doProofOfWork(request); - } - }); - } - - private void tryToFindMatchingPubkey(BitmessageAddress address) { - BitmessageAddress stored = addressRepository.getAddress(address.getAddress()); - if (stored != null) { - address.setAlias(stored.getAlias()); - address.setSubscribed(stored.isSubscribed()); - } - for (ObjectMessage object : inventory.getObjects(address.getStream(), address.getVersion(), ObjectType.PUBKEY)) { - try { - Pubkey pubkey = (Pubkey) object.getPayload(); - if (address.getVersion() == 4) { - V4Pubkey v4Pubkey = (V4Pubkey) pubkey; - if (Arrays.equals(address.getTag(), v4Pubkey.getTag())) { - v4Pubkey.decrypt(address.getPublicDecryptionKey()); - if (object.isSignatureValid(v4Pubkey)) { - address.setPubkey(v4Pubkey); - addressRepository.save(address); - break; - } else { - LOG.info("Found pubkey for " + address + " but signature is invalid"); - } - } - } else { - if (Arrays.equals(pubkey.getRipe(), address.getRipe())) { - address.setPubkey(pubkey); - addressRepository.save(address); - break; - } - } - } catch (Exception e) { - LOG.debug(e.getMessage(), e); - } - } - } - - public void resendUnacknowledged() { - List<Plaintext> messages = messageRepository.findMessagesToResend(); - for (Plaintext message : messages) { - send(message); - messageRepository.save(message); - } - } - - public long getClientNonce() { - return clientNonce; - } - - public long getConnectionTTL() { - return connectionTTL; - } - - public int getConnectionLimit() { - return connectionLimit; - } - - public CustomCommandHandler getCustomCommandHandler() { - return customCommandHandler; - } - - public interface ContextHolder { - void setContext(InternalContext context); - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/InternalContext.kt b/core/src/main/java/ch/dissem/bitmessage/InternalContext.kt new file mode 100644 index 0000000..e36a8ac --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/InternalContext.kt @@ -0,0 +1,235 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage + +import ch.dissem.bitmessage.entity.BitmessageAddress +import ch.dissem.bitmessage.entity.Encrypted +import ch.dissem.bitmessage.entity.ObjectMessage +import ch.dissem.bitmessage.entity.Plaintext +import ch.dissem.bitmessage.entity.payload.* +import ch.dissem.bitmessage.ports.* +import ch.dissem.bitmessage.utils.Singleton +import ch.dissem.bitmessage.utils.TTL +import ch.dissem.bitmessage.utils.UnixTime +import org.slf4j.LoggerFactory +import java.util.* +import java.util.concurrent.Executors +import kotlin.properties.Delegates +import kotlin.reflect.KProperty + +/** + * The internal context should normally only be used for port implementations. If you need it in your client + * implementation, you're either doing something wrong, something very weird, or the BitmessageContext should + * get extended. + * + * + * On the other hand, if you need the BitmessageContext in a port implementation, the same thing might apply. + * + */ +class InternalContext( + val cryptography: Cryptography, + val inventory: ch.dissem.bitmessage.ports.Inventory, + val nodeRegistry: NodeRegistry, + val networkHandler: NetworkHandler, + val addressRepository: AddressRepository, + val messageRepository: ch.dissem.bitmessage.ports.MessageRepository, + val proofOfWorkRepository: ProofOfWorkRepository, + val proofOfWorkEngine: ProofOfWorkEngine, + val customCommandHandler: CustomCommandHandler, + listener: BitmessageContext.Listener, + val labeler: Labeler, + + val port: Int, + val connectionTTL: Long, + val connectionLimit: Int +) { + + private val threadPool = Executors.newCachedThreadPool() + + val proofOfWorkService: ProofOfWorkService = ProofOfWorkService() + val networkListener: NetworkHandler.MessageListener = DefaultMessageListener(labeler, listener) + val clientNonce: Long = cryptography.randomNonce() + private val _streams = TreeSet<Long>() + val streams: LongArray + get() = _streams.toLongArray() + + init { + instance = this + Singleton.initialize(cryptography) + + // TODO: streams of new identities and subscriptions should also be added. This works only after a restart. + addressRepository.getIdentities().mapTo(_streams) { it.stream } + addressRepository.getSubscriptions().mapTo(_streams) { it.stream } + if (_streams.isEmpty()) { + _streams.add(1L) + } + + init(cryptography, inventory, nodeRegistry, networkHandler, addressRepository, messageRepository, + proofOfWorkRepository, proofOfWorkService, proofOfWorkEngine, customCommandHandler, labeler, + networkListener) + } + + private fun init(vararg objects: Any) { + objects.filter { it is ContextHolder }.forEach { (it as ContextHolder).setContext(this) } + } + + fun send(plaintext: Plaintext) { + if (plaintext.ackMessage != null) { + val expires = UnixTime.now + plaintext.ttl + LOG.info("Expires at " + expires) + proofOfWorkService.doProofOfWorkWithAck(plaintext, expires) + } else { + send(plaintext.from, plaintext.to, Msg(plaintext), plaintext.ttl) + } + } + + fun send(from: BitmessageAddress, to: BitmessageAddress?, payload: ObjectPayload, + timeToLive: Long) { + val recipient = to ?: from + val expires = UnixTime.now + timeToLive + LOG.info("Expires at " + expires) + val `object` = ObjectMessage( + stream = recipient.stream, + expiresTime = expires, + payload = payload + ) + if (`object`.isSigned) { + `object`.sign( + from.privateKey ?: throw IllegalArgumentException("The given sending address is no identity") + ) + } + if (payload is Broadcast) { + payload.encrypt() + } else if (payload is Encrypted) { + `object`.encrypt( + recipient.pubkey ?: throw IllegalArgumentException("The public key for the recipient isn't available") + ) + } + proofOfWorkService.doProofOfWork(to, `object`) + } + + fun sendPubkey(identity: BitmessageAddress, targetStream: Long) { + val expires = UnixTime.now + TTL.pubkey + LOG.info("Expires at " + expires) + val payload = identity.pubkey ?: throw IllegalArgumentException("The given address is no identity") + val response = ObjectMessage( + expiresTime = expires, + stream = targetStream, + payload = payload + ) + response.sign( + identity.privateKey ?: throw IllegalArgumentException("The given address is no identity") + ) + response.encrypt(cryptography.createPublicKey(identity.publicDecryptionKey)) + // TODO: remember that the pubkey is just about to be sent, and on which stream! + proofOfWorkService.doProofOfWork(response) + } + + /** + * Be aware that if the pubkey already exists in the inventory, the metods will not request it and the callback + * for freshly received pubkeys will not be called. Instead the pubkey is added to the contact and stored on DB. + */ + fun requestPubkey(contact: BitmessageAddress) { + threadPool.execute { + val stored = addressRepository.getAddress(contact.address) + + tryToFindMatchingPubkey(contact) + if (contact.pubkey != null) { + if (stored != null) { + stored.pubkey = contact.pubkey + addressRepository.save(stored) + } else { + addressRepository.save(contact) + } + return@execute + } + + if (stored == null) { + addressRepository.save(contact) + } + + val expires = UnixTime.now + TTL.getpubkey + LOG.info("Expires at " + expires) + val payload = GetPubkey(contact) + val request = ObjectMessage( + stream = contact.stream, + expiresTime = expires, + payload = payload + ) + proofOfWorkService.doProofOfWork(request) + } + } + + private fun tryToFindMatchingPubkey(address: BitmessageAddress) { + addressRepository.getAddress(address.address)?.let { + address.alias = it.alias + address.isSubscribed = it.isSubscribed + } + for (`object` in inventory.getObjects(address.stream, address.version, ObjectType.PUBKEY)) { + try { + val pubkey = `object`.payload as Pubkey + if (address.version == 4L) { + val v4Pubkey = pubkey as V4Pubkey + if (Arrays.equals(address.tag, v4Pubkey.tag)) { + v4Pubkey.decrypt(address.publicDecryptionKey) + if (`object`.isSignatureValid(v4Pubkey)) { + address.pubkey = v4Pubkey + addressRepository.save(address) + break + } else { + LOG.info("Found pubkey for $address but signature is invalid") + } + } + } else { + if (Arrays.equals(pubkey.ripe, address.ripe)) { + address.pubkey = pubkey + addressRepository.save(address) + break + } + } + } catch (e: Exception) { + LOG.debug(e.message, e) + } + } + } + + fun resendUnacknowledged() { + val messages = messageRepository.findMessagesToResend() + for (message in messages) { + send(message) + messageRepository.save(message) + } + } + + interface ContextHolder { + fun setContext(context: InternalContext) + } + + companion object { + private val LOG = LoggerFactory.getLogger(InternalContext::class.java) + + @JvmField val NETWORK_NONCE_TRIALS_PER_BYTE: Long = 1000 + @JvmField val NETWORK_EXTRA_BYTES: Long = 1000 + + private var instance: InternalContext by Delegates.notNull<InternalContext>() + + operator fun getValue(thisRef: Any?, property: KProperty<*>) = instance + operator fun setValue(thisRef: Any?, property: KProperty<*>, value: InternalContext) { + instance = value + } + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.java b/core/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.java deleted file mode 100644 index 14e7c8a..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.java +++ /dev/null @@ -1,125 +0,0 @@ -package ch.dissem.bitmessage; - -import ch.dissem.bitmessage.entity.*; -import ch.dissem.bitmessage.entity.payload.Msg; -import ch.dissem.bitmessage.entity.payload.Pubkey; -import ch.dissem.bitmessage.ports.Cryptography; -import ch.dissem.bitmessage.ports.MessageRepository; -import ch.dissem.bitmessage.ports.ProofOfWorkEngine; -import ch.dissem.bitmessage.ports.ProofOfWorkRepository; -import ch.dissem.bitmessage.ports.ProofOfWorkRepository.Item; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.util.List; -import java.util.Timer; -import java.util.TimerTask; - -import static ch.dissem.bitmessage.InternalContext.NETWORK_EXTRA_BYTES; -import static ch.dissem.bitmessage.InternalContext.NETWORK_NONCE_TRIALS_PER_BYTE; -import static ch.dissem.bitmessage.utils.Singleton.cryptography; - -/** - * @author Christian Basler - */ -public class ProofOfWorkService implements ProofOfWorkEngine.Callback, InternalContext.ContextHolder { - private final static Logger LOG = LoggerFactory.getLogger(ProofOfWorkService.class); - - private Cryptography cryptography; - private InternalContext ctx; - private ProofOfWorkRepository powRepo; - private MessageRepository messageRepo; - - public void doMissingProofOfWork(long delayInMilliseconds) { - final List<byte[]> items = powRepo.getItems(); - if (items.isEmpty()) return; - - // Wait for 30 seconds, to let the application start up before putting heavy load on the CPU - new Timer().schedule(new TimerTask() { - @Override - public void run() { - LOG.info("Doing POW for " + items.size() + " tasks."); - for (byte[] initialHash : items) { - Item item = powRepo.getItem(initialHash); - cryptography.doProofOfWork(item.object, item.nonceTrialsPerByte, item.extraBytes, - ProofOfWorkService.this); - } - } - }, delayInMilliseconds); - } - - public void doProofOfWork(ObjectMessage object) { - doProofOfWork(null, object); - } - - public void doProofOfWork(BitmessageAddress recipient, ObjectMessage object) { - Pubkey pubkey = recipient == null ? null : recipient.getPubkey(); - - long nonceTrialsPerByte = pubkey == null ? NETWORK_NONCE_TRIALS_PER_BYTE : pubkey.getNonceTrialsPerByte(); - long extraBytes = pubkey == null ? NETWORK_EXTRA_BYTES : pubkey.getExtraBytes(); - - powRepo.putObject(object, nonceTrialsPerByte, extraBytes); - if (object.getPayload() instanceof PlaintextHolder) { - Plaintext plaintext = ((PlaintextHolder) object.getPayload()).getPlaintext(); - plaintext.setInitialHash(cryptography.getInitialHash(object)); - messageRepo.save(plaintext); - } - cryptography.doProofOfWork(object, nonceTrialsPerByte, extraBytes, this); - } - - public void doProofOfWorkWithAck(Plaintext plaintext, long expirationTime) { - final ObjectMessage ack = plaintext.getAckMessage(); - messageRepo.save(plaintext); - Item item = new Item(ack, NETWORK_NONCE_TRIALS_PER_BYTE, NETWORK_EXTRA_BYTES, - expirationTime, plaintext); - powRepo.putObject(item); - cryptography.doProofOfWork(ack, NETWORK_NONCE_TRIALS_PER_BYTE, NETWORK_EXTRA_BYTES, this); - } - - @Override - public void onNonceCalculated(byte[] initialHash, byte[] nonce) { - Item item = powRepo.getItem(initialHash); - if (item.message == null) { - ObjectMessage object = item.object; - object.setNonce(nonce); - Plaintext plaintext = messageRepo.getMessage(initialHash); - if (plaintext != null) { - plaintext.setInventoryVector(object.getInventoryVector()); - plaintext.updateNextTry(); - ctx.getLabeler().markAsSent(plaintext); - messageRepo.save(plaintext); - } - try { - ctx.getNetworkListener().receive(object); - } catch (IOException e) { - LOG.debug(e.getMessage(), e); - } - ctx.getInventory().storeObject(object); - ctx.getNetworkHandler().offer(object.getInventoryVector()); - } else { - item.message.getAckMessage().setNonce(nonce); - final ObjectMessage object = new ObjectMessage.Builder() - .stream(item.message.getStream()) - .expiresTime(item.expirationTime) - .payload(new Msg(item.message)) - .build(); - if (object.isSigned()) { - object.sign(item.message.getFrom().getPrivateKey()); - } - if (object.getPayload() instanceof Encrypted) { - object.encrypt(item.message.getTo().getPubkey()); - } - doProofOfWork(item.message.getTo(), object); - } - powRepo.removeObject(initialHash); - } - - @Override - public void setContext(InternalContext ctx) { - this.ctx = ctx; - this.cryptography = cryptography(); - this.powRepo = ctx.getProofOfWorkRepository(); - this.messageRepo = ctx.getMessageRepository(); - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.kt b/core/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.kt new file mode 100644 index 0000000..2472d67 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.kt @@ -0,0 +1,124 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage + +import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_EXTRA_BYTES +import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_NONCE_TRIALS_PER_BYTE +import ch.dissem.bitmessage.entity.* +import ch.dissem.bitmessage.entity.payload.Msg +import ch.dissem.bitmessage.ports.ProofOfWorkEngine +import ch.dissem.bitmessage.ports.ProofOfWorkRepository.Item +import org.slf4j.LoggerFactory +import java.io.IOException +import java.util.* + +/** + * @author Christian Basler + */ +class ProofOfWorkService : ProofOfWorkEngine.Callback { + + private val ctx by InternalContext + private val cryptography by lazy { ctx.cryptography } + private val powRepo by lazy { ctx.proofOfWorkRepository } + private val messageRepo by lazy { ctx.messageRepository } + + fun doMissingProofOfWork(delayInMilliseconds: Long) { + val items = powRepo.getItems() + if (items.isEmpty()) return + + // Wait for 30 seconds, to let the application start up before putting heavy load on the CPU + Timer().schedule(object : TimerTask() { + override fun run() { + LOG.info("Doing POW for " + items.size + " tasks.") + for (initialHash in items) { + val (`object`, nonceTrialsPerByte, extraBytes) = powRepo.getItem(initialHash) + cryptography.doProofOfWork(`object`, nonceTrialsPerByte, extraBytes, + this@ProofOfWorkService) + } + } + }, delayInMilliseconds) + } + + fun doProofOfWork(`object`: ObjectMessage) { + doProofOfWork(null, `object`) + } + + fun doProofOfWork(recipient: BitmessageAddress?, `object`: ObjectMessage) { + val pubkey = recipient?.pubkey + + val nonceTrialsPerByte = pubkey?.nonceTrialsPerByte ?: NETWORK_NONCE_TRIALS_PER_BYTE + val extraBytes = pubkey?.extraBytes ?: NETWORK_EXTRA_BYTES + + powRepo.putObject(`object`, nonceTrialsPerByte, extraBytes) + if (`object`.payload is PlaintextHolder) { + `object`.payload.plaintext?.let { + it.initialHash = cryptography.getInitialHash(`object`) + messageRepo.save(it) + } + } + cryptography.doProofOfWork(`object`, nonceTrialsPerByte, extraBytes, this) + } + + fun doProofOfWorkWithAck(plaintext: Plaintext, expirationTime: Long) { + val ack = plaintext.ackMessage + messageRepo.save(plaintext) + val item = Item(ack!!, NETWORK_NONCE_TRIALS_PER_BYTE, NETWORK_EXTRA_BYTES, + expirationTime, plaintext) + powRepo.putObject(item) + cryptography.doProofOfWork(ack, NETWORK_NONCE_TRIALS_PER_BYTE, NETWORK_EXTRA_BYTES, this) + } + + override fun onNonceCalculated(initialHash: ByteArray, nonce: ByteArray) { + val (`object`, _, _, expirationTime, message) = powRepo.getItem(initialHash) + if (message == null) { + `object`.nonce = nonce + messageRepo.getMessage(initialHash)?.let { + it.inventoryVector = `object`.inventoryVector + it.updateNextTry() + ctx.labeler.markAsSent(it) + messageRepo.save(it) + } + try { + ctx.networkListener.receive(`object`) + } catch (e: IOException) { + LOG.debug(e.message, e) + } + + ctx.inventory.storeObject(`object`) + ctx.networkHandler.offer(`object`.inventoryVector) + } else { + message.ackMessage!!.nonce = nonce + val `object` = ObjectMessage.Builder() + .stream(message.stream) + .expiresTime(expirationTime!!) + .payload(Msg(message)) + .build() + if (`object`.isSigned) { + `object`.sign(message.from.privateKey!!) + } + if (`object`.payload is Encrypted) { + `object`.encrypt(message.to!!.pubkey!!) + } + doProofOfWork(message.to, `object`) + } + powRepo.removeObject(initialHash) + } + + companion object { + private val LOG = LoggerFactory.getLogger(ProofOfWorkService::class.java) + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/constants/constants.kt b/core/src/main/java/ch/dissem/bitmessage/constants/constants.kt new file mode 100644 index 0000000..ee4feee --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/constants/constants.kt @@ -0,0 +1,27 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.constants + +/** + * Created by chrigu on 03.06.17. + */ +object Network { + @JvmField val NETWORK_MAGIC_NUMBER = 8 + @JvmField val HEADER_SIZE = 24 + @JvmField val MAX_PAYLOAD_SIZE = 1600003 + @JvmField val MAX_MESSAGE_SIZE = HEADER_SIZE + MAX_PAYLOAD_SIZE +} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/Addr.java b/core/src/main/java/ch/dissem/bitmessage/entity/Addr.java deleted file mode 100644 index 73d9995..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/entity/Addr.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.entity; - -import ch.dissem.bitmessage.entity.valueobject.NetworkAddress; -import ch.dissem.bitmessage.utils.Encode; - -import java.io.IOException; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -/** - * The 'addr' command holds a list of known active Bitmessage nodes. - */ -public class Addr implements MessagePayload { - private static final long serialVersionUID = -5117688017050138720L; - - private final List<NetworkAddress> addresses; - - private Addr(Builder builder) { - addresses = builder.addresses; - } - - @Override - public Command getCommand() { - return Command.ADDR; - } - - public List<NetworkAddress> getAddresses() { - return addresses; - } - - @Override - public void write(OutputStream out) throws IOException { - Encode.varInt(addresses.size(), out); - for (NetworkAddress address : addresses) { - address.write(out); - } - } - - @Override - public void write(ByteBuffer buffer) { - Encode.varInt(addresses.size(), buffer); - for (NetworkAddress address : addresses) { - address.write(buffer); - } - } - - public static final class Builder { - private List<NetworkAddress> addresses = new ArrayList<>(); - - public Builder addresses(Collection<NetworkAddress> addresses){ - this.addresses.addAll(addresses); - return this; - } - - public Builder addAddress(final NetworkAddress address) { - this.addresses.add(address); - return this; - } - - public Addr build() { - return new Addr(this); - } - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/Addr.kt b/core/src/main/java/ch/dissem/bitmessage/entity/Addr.kt new file mode 100644 index 0000000..0e7e8f6 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/entity/Addr.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.entity + +import ch.dissem.bitmessage.entity.valueobject.NetworkAddress +import ch.dissem.bitmessage.utils.Encode +import java.io.OutputStream +import java.nio.ByteBuffer + +/** + * The 'addr' command holds a list of known active Bitmessage nodes. + */ +data class Addr constructor(val addresses: List<NetworkAddress>) : MessagePayload { + override val command: MessagePayload.Command = MessagePayload.Command.ADDR + + override fun write(out: OutputStream) { + Encode.varInt(addresses.size.toLong(), out) + for (address in addresses) { + address.write(out) + } + } + + override fun write(buffer: ByteBuffer) { + Encode.varInt(addresses.size.toLong(), buffer) + for (address in addresses) { + address.write(buffer) + } + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java b/core/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java deleted file mode 100644 index ce199c4..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java +++ /dev/null @@ -1,277 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.entity; - -import ch.dissem.bitmessage.entity.payload.Pubkey; -import ch.dissem.bitmessage.entity.payload.Pubkey.Feature; -import ch.dissem.bitmessage.entity.payload.V4Pubkey; -import ch.dissem.bitmessage.entity.valueobject.PrivateKey; -import ch.dissem.bitmessage.exception.ApplicationException; -import ch.dissem.bitmessage.utils.AccessCounter; -import ch.dissem.bitmessage.utils.Base58; -import ch.dissem.bitmessage.utils.Bytes; -import ch.dissem.bitmessage.utils.Encode; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; - -import static ch.dissem.bitmessage.utils.Decode.bytes; -import static ch.dissem.bitmessage.utils.Decode.varInt; -import static ch.dissem.bitmessage.utils.Singleton.cryptography; - -/** - * A Bitmessage address. Can be a user's private address, an address string without public keys or a recipient's address - * holding private keys. - */ -public class BitmessageAddress implements Serializable { - private static final long serialVersionUID = 2386328540805994064L; - - private final long version; - private final long stream; - private final byte[] ripe; - private final byte[] tag; - /** - * Used for V4 address encryption. It's easier to just create it regardless of address version. - */ - private final byte[] publicDecryptionKey; - - private String address; - - private PrivateKey privateKey; - private Pubkey pubkey; - - private String alias; - private boolean subscribed; - private boolean chan; - - BitmessageAddress(long version, long stream, byte[] ripe) { - try { - this.version = version; - this.stream = stream; - this.ripe = ripe; - - ByteArrayOutputStream os = new ByteArrayOutputStream(); - Encode.varInt(version, os); - Encode.varInt(stream, os); - if (version < 4) { - byte[] checksum = cryptography().sha512(os.toByteArray(), ripe); - this.tag = null; - this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32); - } else { - // for tag and decryption key, the checksum has to be created with 0x00 padding - byte[] checksum = cryptography().doubleSha512(os.toByteArray(), ripe); - this.tag = Arrays.copyOfRange(checksum, 32, 64); - this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32); - } - // but for the address and its checksum they need to be stripped - int offset = Bytes.numberOfLeadingZeros(ripe); - os.write(ripe, offset, ripe.length - offset); - byte[] checksum = cryptography().doubleSha512(os.toByteArray()); - os.write(checksum, 0, 4); - this.address = "BM-" + Base58.encode(os.toByteArray()); - } catch (IOException e) { - throw new ApplicationException(e); - } - } - - public BitmessageAddress(Pubkey publicKey) { - this(publicKey.getVersion(), publicKey.getStream(), publicKey.getRipe()); - this.pubkey = publicKey; - } - - public BitmessageAddress(String address, String passphrase) { - this(address); - this.privateKey = new PrivateKey(this, passphrase); - this.pubkey = this.privateKey.getPubkey(); - if (!Arrays.equals(ripe, privateKey.getPubkey().getRipe())) { - throw new IllegalArgumentException("Wrong address or passphrase"); - } - } - - public static BitmessageAddress chan(String address, String passphrase) { - BitmessageAddress result = new BitmessageAddress(address, passphrase); - result.chan = true; - return result; - } - - public static BitmessageAddress chan(long stream, String passphrase) { - PrivateKey privateKey = new PrivateKey(Pubkey.LATEST_VERSION, stream, passphrase); - BitmessageAddress result = new BitmessageAddress(privateKey); - result.chan = true; - return result; - } - - public static List<BitmessageAddress> deterministic(String passphrase, int numberOfAddresses, - long version, long stream, boolean shorter) { - List<BitmessageAddress> result = new ArrayList<>(numberOfAddresses); - List<PrivateKey> privateKeys = PrivateKey.deterministic(passphrase, numberOfAddresses, version, stream, shorter); - for (PrivateKey pk : privateKeys) { - result.add(new BitmessageAddress(pk)); - } - return result; - } - - public BitmessageAddress(PrivateKey privateKey) { - this(privateKey.getPubkey()); - this.privateKey = privateKey; - } - - public BitmessageAddress(String address) { - try { - this.address = address; - byte[] bytes = Base58.decode(address.substring(3)); - ByteArrayInputStream in = new ByteArrayInputStream(bytes); - AccessCounter counter = new AccessCounter(); - this.version = varInt(in, counter); - this.stream = varInt(in, counter); - this.ripe = Bytes.expand(bytes(in, bytes.length - counter.length() - 4), 20); - - // test checksum - byte[] checksum = cryptography().doubleSha512(bytes, bytes.length - 4); - byte[] expectedChecksum = bytes(in, 4); - for (int i = 0; i < 4; i++) { - if (expectedChecksum[i] != checksum[i]) - throw new IllegalArgumentException("Checksum of address failed"); - } - if (version < 4) { - checksum = cryptography().sha512(Arrays.copyOfRange(bytes, 0, counter.length()), ripe); - this.tag = null; - this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32); - } else { - checksum = cryptography().doubleSha512(Arrays.copyOfRange(bytes, 0, counter.length()), ripe); - this.tag = Arrays.copyOfRange(checksum, 32, 64); - this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32); - } - } catch (IOException e) { - throw new ApplicationException(e); - } - } - - public static byte[] calculateTag(long version, long stream, byte[] ripe) { - try { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - Encode.varInt(version, out); - Encode.varInt(stream, out); - out.write(ripe); - return Arrays.copyOfRange(cryptography().doubleSha512(out.toByteArray()), 32, 64); - } catch (IOException e) { - throw new ApplicationException(e); - } - } - - public long getStream() { - return stream; - } - - public long getVersion() { - return version; - } - - public Pubkey getPubkey() { - return pubkey; - } - - public void setPubkey(Pubkey pubkey) { - if (pubkey instanceof V4Pubkey) { - if (!Arrays.equals(tag, ((V4Pubkey) pubkey).getTag())) - throw new IllegalArgumentException("Pubkey has incompatible tag"); - } - if (!Arrays.equals(ripe, pubkey.getRipe())) - throw new IllegalArgumentException("Pubkey has incompatible ripe"); - this.pubkey = pubkey; - } - - /** - * @return the private key used to decrypt Pubkey objects (for v4 addresses) and broadcasts. - */ - public byte[] getPublicDecryptionKey() { - return publicDecryptionKey; - } - - public PrivateKey getPrivateKey() { - return privateKey; - } - - public String getAddress() { - return address; - } - - public String getAlias() { - return alias; - } - - public void setAlias(String alias) { - this.alias = alias; - } - - @Override - public String toString() { - return alias == null ? address : alias; - } - - public byte[] getRipe() { - return ripe; - } - - public byte[] getTag() { - return tag; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - BitmessageAddress address = (BitmessageAddress) o; - return Objects.equals(version, address.version) && - Objects.equals(stream, address.stream) && - Arrays.equals(ripe, address.ripe); - } - - @Override - public int hashCode() { - return Arrays.hashCode(ripe); - } - - public boolean isSubscribed() { - return subscribed; - } - - public void setSubscribed(boolean subscribed) { - this.subscribed = subscribed; - } - - public boolean isChan() { - return chan; - } - - public void setChan(boolean chan) { - this.chan = chan; - } - - public boolean has(Feature feature) { - if (pubkey == null || feature == null) { - return false; - } - return feature.isActive(pubkey.getBehaviorBitfield()); - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.kt b/core/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.kt new file mode 100644 index 0000000..c0e978b --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.kt @@ -0,0 +1,195 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.entity + +import ch.dissem.bitmessage.entity.payload.Pubkey +import ch.dissem.bitmessage.entity.payload.Pubkey.Feature +import ch.dissem.bitmessage.entity.payload.V4Pubkey +import ch.dissem.bitmessage.entity.valueobject.PrivateKey +import ch.dissem.bitmessage.utils.AccessCounter +import ch.dissem.bitmessage.utils.Base58 +import ch.dissem.bitmessage.utils.Bytes +import ch.dissem.bitmessage.utils.Decode.bytes +import ch.dissem.bitmessage.utils.Decode.varInt +import ch.dissem.bitmessage.utils.Encode +import ch.dissem.bitmessage.utils.Singleton.cryptography +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.io.Serializable +import java.util.* + +/** + * A Bitmessage address. Can be a user's private address, an address string without public keys or a recipient's address + * holding private keys. + */ +class BitmessageAddress : Serializable { + + val version: Long + val stream: Long + val ripe: ByteArray + val tag: ByteArray? + /** + * The private key used to decrypt Pubkey objects (for v4 addresses) and broadcasts. It's easier to just create + * it regardless of address version. + */ + val publicDecryptionKey: ByteArray + + val address: String + + var privateKey: PrivateKey? = null + private set + var pubkey: Pubkey? = null + set(pubkey) { + if (pubkey != null) { + if (pubkey is V4Pubkey) { + if (!Arrays.equals(tag, pubkey.tag)) + throw IllegalArgumentException("Pubkey has incompatible tag") + } + if (!Arrays.equals(ripe, pubkey.ripe)) + throw IllegalArgumentException("Pubkey has incompatible ripe") + field = pubkey + } + } + + + var alias: String? = null + var isSubscribed: Boolean = false + var isChan: Boolean = false + + internal constructor(version: Long, stream: Long, ripe: ByteArray) { + this.version = version + this.stream = stream + this.ripe = ripe + + val os = ByteArrayOutputStream() + Encode.varInt(version, os) + Encode.varInt(stream, os) + if (version < 4) { + val checksum = cryptography().sha512(os.toByteArray(), ripe) + this.tag = null + this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32) + } else { + // for tag and decryption key, the checksum has to be created with 0x00 padding + val checksum = cryptography().doubleSha512(os.toByteArray(), ripe) + this.tag = Arrays.copyOfRange(checksum, 32, 64) + this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32) + } + // but for the address and its checksum they need to be stripped + val offset = Bytes.numberOfLeadingZeros(ripe) + os.write(ripe, offset, ripe.size - offset) + val checksum = cryptography().doubleSha512(os.toByteArray()) + os.write(checksum, 0, 4) + this.address = "BM-" + Base58.encode(os.toByteArray()) + } + + constructor(publicKey: Pubkey) : this(publicKey.version, publicKey.stream, publicKey.ripe) { + this.pubkey = publicKey + } + + constructor(address: String, passphrase: String) : this(address) { + val key = PrivateKey(this, passphrase) + if (!Arrays.equals(ripe, key.pubkey.ripe)) { + throw IllegalArgumentException("Wrong address or passphrase") + } + this.privateKey = key + this.pubkey = key.pubkey + } + + constructor(privateKey: PrivateKey) : this(privateKey.pubkey) { + this.privateKey = privateKey + } + + constructor(address: String) { + this.address = address + val bytes = Base58.decode(address.substring(3)) + val `in` = ByteArrayInputStream(bytes) + val counter = AccessCounter() + this.version = varInt(`in`, counter) + this.stream = varInt(`in`, counter) + this.ripe = Bytes.expand(bytes(`in`, bytes.size - counter.length() - 4), 20) + + // test checksum + var checksum = cryptography().doubleSha512(bytes, bytes.size - 4) + val expectedChecksum = bytes(`in`, 4) + for (i in 0..3) { + if (expectedChecksum[i] != checksum[i]) + throw IllegalArgumentException("Checksum of address failed") + } + if (version < 4) { + checksum = cryptography().sha512(Arrays.copyOfRange(bytes, 0, counter.length()), ripe) + this.tag = null + this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32) + } else { + checksum = cryptography().doubleSha512(Arrays.copyOfRange(bytes, 0, counter.length()), ripe) + this.tag = Arrays.copyOfRange(checksum, 32, 64) + this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32) + } + } + + override fun toString(): String { + return alias ?: address + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is BitmessageAddress) return false + return version == other.version && + stream == other.stream && + Arrays.equals(ripe, other.ripe) + } + + override fun hashCode(): Int { + return Arrays.hashCode(ripe) + } + + fun has(feature: Feature?): Boolean { + return feature?.isActive(pubkey?.behaviorBitfield ?: 0) ?: false + } + + companion object { + @JvmStatic fun chan(address: String, passphrase: String): BitmessageAddress { + val result = BitmessageAddress(address, passphrase) + result.isChan = true + return result + } + + @JvmStatic fun chan(stream: Long, passphrase: String): BitmessageAddress { + val privateKey = PrivateKey(Pubkey.LATEST_VERSION, stream, passphrase) + val result = BitmessageAddress(privateKey) + result.isChan = true + return result + } + + @JvmStatic fun deterministic(passphrase: String, numberOfAddresses: Int, + version: Long, stream: Long, shorter: Boolean): List<BitmessageAddress> { + val result = ArrayList<BitmessageAddress>(numberOfAddresses) + val privateKeys = PrivateKey.deterministic(passphrase, numberOfAddresses, version, stream, shorter) + for (pk in privateKeys) { + result.add(BitmessageAddress(pk)) + } + return result + } + + @JvmStatic fun calculateTag(version: Long, stream: Long, ripe: ByteArray): ByteArray { + val out = ByteArrayOutputStream() + Encode.varInt(version, out) + Encode.varInt(stream, out) + out.write(ripe) + return Arrays.copyOfRange(cryptography().doubleSha512(out.toByteArray()), 32, 64) + } + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/CustomMessage.java b/core/src/main/java/ch/dissem/bitmessage/entity/CustomMessage.java deleted file mode 100644 index 439f003..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/entity/CustomMessage.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.entity; - -import ch.dissem.bitmessage.exception.ApplicationException; -import ch.dissem.bitmessage.utils.AccessCounter; -import ch.dissem.bitmessage.utils.Encode; - -import java.io.*; -import java.nio.ByteBuffer; - -import static ch.dissem.bitmessage.utils.Decode.bytes; -import static ch.dissem.bitmessage.utils.Decode.varString; - -/** - * @author Christian Basler - */ -public class CustomMessage implements MessagePayload { - private static final long serialVersionUID = -8932056829480326011L; - - public static final String COMMAND_ERROR = "ERROR"; - - private final String command; - private final byte[] data; - - public CustomMessage(String command) { - this.command = command; - this.data = null; - } - - public CustomMessage(String command, byte[] data) { - this.command = command; - this.data = data; - } - - public static CustomMessage read(InputStream in, int length) throws IOException { - AccessCounter counter = new AccessCounter(); - return new CustomMessage(varString(in, counter), bytes(in, length - counter.length())); - } - - @Override - public Command getCommand() { - return Command.CUSTOM; - } - - public String getCustomCommand() { - return command; - } - - public byte[] getData() { - if (data != null) { - return data; - } else { - try { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - write(out); - return out.toByteArray(); - } catch (IOException e) { - throw new ApplicationException(e); - } - } - } - - @Override - public void write(OutputStream out) throws IOException { - if (data != null) { - Encode.varString(command, out); - out.write(data); - } else { - throw new ApplicationException("Tried to write custom message without data. " + - "Programmer: did you forget to override #write()?"); - } - } - - @Override - public void write(ByteBuffer buffer) { - if (data != null) { - Encode.varString(command, buffer); - buffer.put(data); - } else { - throw new ApplicationException("Tried to write custom message without data. " + - "Programmer: did you forget to override #write()?"); - } - } - - public boolean isError() { - return COMMAND_ERROR.equals(command); - } - - public static CustomMessage error(String message) { - try { - return new CustomMessage(COMMAND_ERROR, message.getBytes("UTF-8")); - } catch (UnsupportedEncodingException e) { - throw new ApplicationException(e); - } - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/CustomMessage.kt b/core/src/main/java/ch/dissem/bitmessage/entity/CustomMessage.kt new file mode 100644 index 0000000..a303c94 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/entity/CustomMessage.kt @@ -0,0 +1,84 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.entity + +import ch.dissem.bitmessage.exception.ApplicationException +import ch.dissem.bitmessage.utils.AccessCounter +import ch.dissem.bitmessage.utils.Decode.bytes +import ch.dissem.bitmessage.utils.Decode.varString +import ch.dissem.bitmessage.utils.Encode +import java.io.ByteArrayOutputStream +import java.io.InputStream +import java.io.OutputStream +import java.nio.ByteBuffer + +/** + * @author Christian Basler + */ +open class CustomMessage(val customCommand: String, private val data: ByteArray? = null) : MessagePayload { + + override val command: MessagePayload.Command = MessagePayload.Command.CUSTOM + + val isError: Boolean + + fun getData(): ByteArray { + if (data != null) { + return data + } else { + val out = ByteArrayOutputStream() + write(out) + return out.toByteArray() + } + } + + override fun write(out: OutputStream) { + if (data != null) { + Encode.varString(customCommand, out) + out.write(data) + } else { + throw ApplicationException("Tried to write custom message without data. " + + "Programmer: did you forget to override #write()?") + } + } + + override fun write(buffer: ByteBuffer) { + if (data != null) { + Encode.varString(customCommand, buffer) + buffer.put(data) + } else { + throw ApplicationException("Tried to write custom message without data. " + + "Programmer: did you forget to override #write()?") + } + } + + companion object { + val COMMAND_ERROR = "ERROR" + + fun read(`in`: InputStream, length: Int): CustomMessage { + val counter = AccessCounter() + return CustomMessage(varString(`in`, counter), bytes(`in`, length - counter.length())) + } + + fun error(message: String): CustomMessage { + return CustomMessage(COMMAND_ERROR, message.toByteArray(charset("UTF-8"))) + } + } + + init { + this.isError = COMMAND_ERROR == customCommand + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/Encrypted.java b/core/src/main/java/ch/dissem/bitmessage/entity/Encrypted.kt similarity index 63% rename from core/src/main/java/ch/dissem/bitmessage/entity/Encrypted.java rename to core/src/main/java/ch/dissem/bitmessage/entity/Encrypted.kt index 8b15371..913fbc1 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/Encrypted.java +++ b/core/src/main/java/ch/dissem/bitmessage/entity/Encrypted.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015 Christian Basler + * Copyright 2017 Christian Basler * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,19 +14,18 @@ * limitations under the License. */ -package ch.dissem.bitmessage.entity; +package ch.dissem.bitmessage.entity -import ch.dissem.bitmessage.exception.DecryptionFailedException; - -import java.io.IOException; +import ch.dissem.bitmessage.exception.DecryptionFailedException /** * Used for objects that have encrypted content */ -public interface Encrypted { - void encrypt(byte[] publicKey) throws IOException; +interface Encrypted { + fun encrypt(publicKey: ByteArray) - void decrypt(byte[] privateKey) throws IOException, DecryptionFailedException; + @Throws(DecryptionFailedException::class) + fun decrypt(privateKey: ByteArray) - boolean isDecrypted(); + val isDecrypted: Boolean } diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/GetData.java b/core/src/main/java/ch/dissem/bitmessage/entity/GetData.java deleted file mode 100644 index 7d14fa0..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/entity/GetData.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.entity; - -import ch.dissem.bitmessage.entity.valueobject.InventoryVector; -import ch.dissem.bitmessage.utils.Encode; - -import java.io.IOException; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.util.LinkedList; -import java.util.List; - -/** - * The 'getdata' command is used to request objects from a node. - */ -public class GetData implements MessagePayload { - private static final long serialVersionUID = 1433878785969631061L; - - public static final int MAX_INVENTORY_SIZE = 50_000; - - List<InventoryVector> inventory; - - private GetData(Builder builder) { - inventory = builder.inventory; - } - - @Override - public Command getCommand() { - return Command.GETDATA; - } - - public List<InventoryVector> getInventory() { - return inventory; - } - - @Override - public void write(OutputStream out) throws IOException { - Encode.varInt(inventory.size(), out); - for (InventoryVector iv : inventory) { - iv.write(out); - } - } - - @Override - public void write(ByteBuffer buffer) { - Encode.varInt(inventory.size(), buffer); - for (InventoryVector iv : inventory) { - iv.write(buffer); - } - } - - public static final class Builder { - private List<InventoryVector> inventory = new LinkedList<>(); - - public Builder addInventoryVector(InventoryVector inventoryVector) { - this.inventory.add(inventoryVector); - return this; - } - - public Builder inventory(List<InventoryVector> inventory) { - this.inventory = inventory; - return this; - } - - public GetData build() { - return new GetData(this); - } - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/GetData.kt b/core/src/main/java/ch/dissem/bitmessage/entity/GetData.kt new file mode 100644 index 0000000..62fab2b --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/entity/GetData.kt @@ -0,0 +1,48 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.entity + +import ch.dissem.bitmessage.entity.valueobject.InventoryVector +import ch.dissem.bitmessage.utils.Encode +import java.io.OutputStream +import java.nio.ByteBuffer + +/** + * The 'getdata' command is used to request objects from a node. + */ +class GetData constructor(var inventory: List<InventoryVector>) : MessagePayload { + + override val command: MessagePayload.Command = MessagePayload.Command.GETDATA + + override fun write(out: OutputStream) { + Encode.varInt(inventory.size.toLong(), out) + for (iv in inventory) { + iv.write(out) + } + } + + override fun write(buffer: ByteBuffer) { + Encode.varInt(inventory.size.toLong(), buffer) + for (iv in inventory) { + iv.write(buffer) + } + } + + companion object { + @JvmField val MAX_INVENTORY_SIZE = 50000 + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/Inv.java b/core/src/main/java/ch/dissem/bitmessage/entity/Inv.java deleted file mode 100644 index 8d0f592..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/entity/Inv.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.entity; - -import ch.dissem.bitmessage.entity.valueobject.InventoryVector; -import ch.dissem.bitmessage.utils.Encode; - -import java.io.IOException; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.util.LinkedList; -import java.util.List; - -/** - * The 'inv' command holds up to 50000 inventory vectors, i.e. hashes of inventory items. - */ -public class Inv implements MessagePayload { - private static final long serialVersionUID = 3662992522956947145L; - - private List<InventoryVector> inventory; - - private Inv(Builder builder) { - inventory = builder.inventory; - } - - public List<InventoryVector> getInventory() { - return inventory; - } - - @Override - public Command getCommand() { - return Command.INV; - } - - @Override - public void write(OutputStream out) throws IOException { - Encode.varInt(inventory.size(), out); - for (InventoryVector iv : inventory) { - iv.write(out); - } - } - - @Override - public void write(ByteBuffer buffer) { - Encode.varInt(inventory.size(), buffer); - for (InventoryVector iv : inventory) { - iv.write(buffer); - } - } - - public static final class Builder { - private List<InventoryVector> inventory = new LinkedList<>(); - - public Builder addInventoryVector(InventoryVector inventoryVector) { - this.inventory.add(inventoryVector); - return this; - } - - public Builder inventory(List<InventoryVector> inventory) { - this.inventory = inventory; - return this; - } - - public Inv build() { - return new Inv(this); - } - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/Inv.kt b/core/src/main/java/ch/dissem/bitmessage/entity/Inv.kt new file mode 100644 index 0000000..42f963f --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/entity/Inv.kt @@ -0,0 +1,44 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.entity + +import ch.dissem.bitmessage.entity.valueobject.InventoryVector +import ch.dissem.bitmessage.utils.Encode +import java.io.OutputStream +import java.nio.ByteBuffer + +/** + * The 'inv' command holds up to 50000 inventory vectors, i.e. hashes of inventory items. + */ +class Inv constructor(val inventory: List<InventoryVector>) : MessagePayload { + + override val command: MessagePayload.Command = MessagePayload.Command.INV + + override fun write(out: OutputStream) { + Encode.varInt(inventory.size.toLong(), out) + for (iv in inventory) { + iv.write(out) + } + } + + override fun write(buffer: ByteBuffer) { + Encode.varInt(inventory.size.toLong(), buffer) + for (iv in inventory) { + iv.write(buffer) + } + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/MessagePayload.java b/core/src/main/java/ch/dissem/bitmessage/entity/MessagePayload.kt similarity index 80% rename from core/src/main/java/ch/dissem/bitmessage/entity/MessagePayload.java rename to core/src/main/java/ch/dissem/bitmessage/entity/MessagePayload.kt index 994952b..b57fc16 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/MessagePayload.java +++ b/core/src/main/java/ch/dissem/bitmessage/entity/MessagePayload.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015 Christian Basler + * Copyright 2017 Christian Basler * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,15 +14,15 @@ * limitations under the License. */ -package ch.dissem.bitmessage.entity; +package ch.dissem.bitmessage.entity /** * A command can hold a network message payload */ -public interface MessagePayload extends Streamable { - Command getCommand(); +interface MessagePayload : Streamable { + val command: Command - enum Command { + enum class Command { VERSION, VERACK, ADDR, INV, GETDATA, OBJECT, CUSTOM } } diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/NetworkMessage.java b/core/src/main/java/ch/dissem/bitmessage/entity/NetworkMessage.java deleted file mode 100644 index f27384e..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/entity/NetworkMessage.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.entity; - -import ch.dissem.bitmessage.exception.ApplicationException; -import ch.dissem.bitmessage.utils.Encode; - -import java.io.IOException; -import java.io.OutputStream; -import java.io.UnsupportedEncodingException; -import java.nio.ByteBuffer; -import java.security.GeneralSecurityException; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; - -import static ch.dissem.bitmessage.utils.Singleton.cryptography; - -/** - * A network message is exchanged between two nodes. - */ -public class NetworkMessage implements Streamable { - private static final long serialVersionUID = 702708857104464809L; - - /** - * Magic value indicating message origin network, and used to seek to next message when stream state is unknown - */ - public final static int MAGIC = 0xE9BEB4D9; - public final static byte[] MAGIC_BYTES = ByteBuffer.allocate(4).putInt(MAGIC).array(); - - private final MessagePayload payload; - - public NetworkMessage(MessagePayload payload) { - this.payload = payload; - } - - /** - * First 4 bytes of sha512(payload) - */ - private byte[] getChecksum(byte[] bytes) throws NoSuchProviderException, NoSuchAlgorithmException { - byte[] d = cryptography().sha512(bytes); - return new byte[]{d[0], d[1], d[2], d[3]}; - } - - /** - * The actual data, a message or an object. Not to be confused with objectPayload. - */ - public MessagePayload getPayload() { - return payload; - } - - @Override - public void write(OutputStream out) throws IOException { - // magic - Encode.int32(MAGIC, out); - - // ASCII string identifying the packet content, NULL padded (non-NULL padding results in packet rejected) - String command = payload.getCommand().name().toLowerCase(); - out.write(command.getBytes("ASCII")); - for (int i = command.length(); i < 12; i++) { - out.write('\0'); - } - - byte[] payloadBytes = Encode.bytes(payload); - - // Length of payload in number of bytes. Because of other restrictions, there is no reason why this length would - // ever be larger than 1600003 bytes. Some clients include a sanity-check to avoid processing messages which are - // larger than this. - Encode.int32(payloadBytes.length, out); - - // checksum - try { - out.write(getChecksum(payloadBytes)); - } catch (GeneralSecurityException e) { - throw new ApplicationException(e); - } - - // message payload - out.write(payloadBytes); - } - - /** - * A more efficient implementation of the write method, writing header data to the provided buffer and returning - * a new buffer containing the payload. - * - * @param headerBuffer where the header data is written to (24 bytes) - * @return a buffer containing the payload, ready to be read. - */ - public ByteBuffer writeHeaderAndGetPayloadBuffer(ByteBuffer headerBuffer) { - return ByteBuffer.wrap(writeHeader(headerBuffer)); - } - - /** - * For improved memory efficiency, you should use {@link #writeHeaderAndGetPayloadBuffer(ByteBuffer)} - * and write the header buffer as well as the returned payload buffer into the channel. - * - * @param buffer where everything gets written to. Needs to be large enough for the whole message - * to be written. - */ - @Override - public void write(ByteBuffer buffer) { - byte[] payloadBytes = writeHeader(buffer); - buffer.put(payloadBytes); - } - - private byte[] writeHeader(ByteBuffer out) { - // magic - Encode.int32(MAGIC, out); - - // ASCII string identifying the packet content, NULL padded (non-NULL padding results in packet rejected) - String command = payload.getCommand().name().toLowerCase(); - try { - out.put(command.getBytes("ASCII")); - } catch (UnsupportedEncodingException e) { - throw new ApplicationException(e); - } - for (int i = command.length(); i < 12; i++) { - out.put((byte) 0); - } - - byte[] payloadBytes = Encode.bytes(payload); - - // Length of payload in number of bytes. Because of other restrictions, there is no reason why this length would - // ever be larger than 1600003 bytes. Some clients include a sanity-check to avoid processing messages which are - // larger than this. - Encode.int32(payloadBytes.length, out); - - // checksum - try { - out.put(getChecksum(payloadBytes)); - } catch (GeneralSecurityException e) { - throw new ApplicationException(e); - } - - // message payload - return payloadBytes; - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/NetworkMessage.kt b/core/src/main/java/ch/dissem/bitmessage/entity/NetworkMessage.kt new file mode 100644 index 0000000..3b5369e --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/entity/NetworkMessage.kt @@ -0,0 +1,126 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.entity + +import ch.dissem.bitmessage.utils.Encode +import ch.dissem.bitmessage.utils.Singleton.cryptography +import java.io.IOException +import java.io.OutputStream +import java.nio.ByteBuffer + +/** + * A network message is exchanged between two nodes. + */ +data class NetworkMessage( + /** + * The actual data, a message or an object. Not to be confused with objectPayload. + */ + val payload: MessagePayload +) : Streamable { + + /** + * First 4 bytes of sha512(payload) + */ + private fun getChecksum(bytes: ByteArray): ByteArray { + val d = cryptography().sha512(bytes) + return byteArrayOf(d[0], d[1], d[2], d[3]) + } + + @Throws(IOException::class) + override fun write(out: OutputStream) { + // magic + Encode.int32(MAGIC.toLong(), out) + + // ASCII string identifying the packet content, NULL padded (non-NULL padding results in packet rejected) + val command = payload.command.name.toLowerCase() + out.write(command.toByteArray(charset("ASCII"))) + for (i in command.length..11) { + out.write(0x0) + } + + val payloadBytes = Encode.bytes(payload) + + // Length of payload in number of bytes. Because of other restrictions, there is no reason why this length would + // ever be larger than 1600003 bytes. Some clients include a sanity-check to avoid processing messages which are + // larger than this. + Encode.int32(payloadBytes.size.toLong(), out) + + // checksum + out.write(getChecksum(payloadBytes)) + + // message payload + out.write(payloadBytes) + } + + /** + * A more efficient implementation of the write method, writing header data to the provided buffer and returning + * a new buffer containing the payload. + + * @param headerBuffer where the header data is written to (24 bytes) + * * + * @return a buffer containing the payload, ready to be read. + */ + fun writeHeaderAndGetPayloadBuffer(headerBuffer: ByteBuffer): ByteBuffer { + return ByteBuffer.wrap(writeHeader(headerBuffer)) + } + + /** + * For improved memory efficiency, you should use [.writeHeaderAndGetPayloadBuffer] + * and write the header buffer as well as the returned payload buffer into the channel. + + * @param buffer where everything gets written to. Needs to be large enough for the whole message + * * to be written. + */ + override fun write(buffer: ByteBuffer) { + val payloadBytes = writeHeader(buffer) + buffer.put(payloadBytes) + } + + private fun writeHeader(out: ByteBuffer): ByteArray { + // magic + Encode.int32(MAGIC.toLong(), out) + + // ASCII string identifying the packet content, NULL padded (non-NULL padding results in packet rejected) + val command = payload.command.name.toLowerCase() + out.put(command.toByteArray(charset("ASCII"))) + + for (i in command.length..11) { + out.put(0.toByte()) + } + + val payloadBytes = Encode.bytes(payload) + + // Length of payload in number of bytes. Because of other restrictions, there is no reason why this length would + // ever be larger than 1600003 bytes. Some clients include a sanity-check to avoid processing messages which are + // larger than this. + Encode.int32(payloadBytes.size.toLong(), out) + + // checksum + out.put(getChecksum(payloadBytes)) + + // message payload + return payloadBytes + } + + companion object { + /** + * Magic value indicating message origin network, and used to seek to next message when stream state is unknown + */ + val MAGIC = 0xE9BEB4D9.toInt() + val MAGIC_BYTES = ByteBuffer.allocate(4).putInt(MAGIC).array() + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java b/core/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java deleted file mode 100644 index 8e386f7..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java +++ /dev/null @@ -1,271 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.entity; - -import ch.dissem.bitmessage.entity.payload.ObjectPayload; -import ch.dissem.bitmessage.entity.payload.ObjectType; -import ch.dissem.bitmessage.entity.payload.Pubkey; -import ch.dissem.bitmessage.entity.valueobject.InventoryVector; -import ch.dissem.bitmessage.entity.valueobject.PrivateKey; -import ch.dissem.bitmessage.exception.ApplicationException; -import ch.dissem.bitmessage.exception.DecryptionFailedException; -import ch.dissem.bitmessage.utils.Bytes; -import ch.dissem.bitmessage.utils.Encode; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.util.Arrays; -import java.util.Objects; - -import static ch.dissem.bitmessage.utils.Singleton.cryptography; - -/** - * The 'object' command sends an object that is shared throughout the network. - */ -public class ObjectMessage implements MessagePayload { - private static final long serialVersionUID = 2495752480120659139L; - - private byte[] nonce; - private long expiresTime; - private long objectType; - /** - * The object's version - */ - private long version; - private long stream; - - private ObjectPayload payload; - private byte[] payloadBytes; - - private ObjectMessage(Builder builder) { - nonce = builder.nonce; - expiresTime = builder.expiresTime; - objectType = builder.objectType; - version = builder.payload.getVersion(); - stream = builder.streamNumber > 0 ? builder.streamNumber : builder.payload.getStream(); - payload = builder.payload; - } - - @Override - public Command getCommand() { - return Command.OBJECT; - } - - public byte[] getNonce() { - return nonce; - } - - public void setNonce(byte[] nonce) { - this.nonce = nonce; - } - - public long getExpiresTime() { - return expiresTime; - } - - public long getType() { - return objectType; - } - - public ObjectPayload getPayload() { - return payload; - } - - public long getVersion() { - return version; - } - - public long getStream() { - return stream; - } - - public InventoryVector getInventoryVector() { - return InventoryVector.fromHash( - Bytes.truncate(cryptography().doubleSha512(nonce, getPayloadBytesWithoutNonce()), 32) - ); - } - - private boolean isEncrypted() { - return payload instanceof Encrypted && !((Encrypted) payload).isDecrypted(); - } - - public boolean isSigned() { - return payload.isSigned(); - } - - private byte[] getBytesToSign() { - try { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - writeHeaderWithoutNonce(out); - payload.writeBytesToSign(out); - return out.toByteArray(); - } catch (IOException e) { - throw new ApplicationException(e); - } - } - - public void sign(PrivateKey key) { - if (payload.isSigned()) { - payload.setSignature(cryptography().getSignature(getBytesToSign(), key)); - } - } - - public void decrypt(PrivateKey key) throws IOException, DecryptionFailedException { - if (payload instanceof Encrypted) { - ((Encrypted) payload).decrypt(key.getPrivateEncryptionKey()); - } - } - - public void decrypt(byte[] privateEncryptionKey) throws IOException, DecryptionFailedException { - if (payload instanceof Encrypted) { - ((Encrypted) payload).decrypt(privateEncryptionKey); - } - } - - public void encrypt(byte[] publicEncryptionKey) throws IOException { - if (payload instanceof Encrypted) { - ((Encrypted) payload).encrypt(publicEncryptionKey); - } - } - - public void encrypt(Pubkey publicKey) { - try { - if (payload instanceof Encrypted) { - ((Encrypted) payload).encrypt(publicKey.getEncryptionKey()); - } - } catch (IOException e) { - throw new ApplicationException(e); - } - } - - public boolean isSignatureValid(Pubkey pubkey) throws IOException { - if (isEncrypted()) throw new IllegalStateException("Payload must be decrypted first"); - return cryptography().isSignatureValid(getBytesToSign(), payload.getSignature(), pubkey); - } - - @Override - public void write(OutputStream out) throws IOException { - if (nonce == null) { - out.write(new byte[8]); - } else { - out.write(nonce); - } - out.write(getPayloadBytesWithoutNonce()); - } - - @Override - public void write(ByteBuffer buffer) { - if (nonce == null) { - buffer.put(new byte[8]); - } else { - buffer.put(nonce); - } - buffer.put(getPayloadBytesWithoutNonce()); - } - - private void writeHeaderWithoutNonce(OutputStream out) throws IOException { - Encode.int64(expiresTime, out); - Encode.int32(objectType, out); - Encode.varInt(version, out); - Encode.varInt(stream, out); - } - - public byte[] getPayloadBytesWithoutNonce() { - try { - if (payloadBytes == null) { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - writeHeaderWithoutNonce(out); - payload.write(out); - payloadBytes = out.toByteArray(); - } - return payloadBytes; - } catch (IOException e) { - throw new ApplicationException(e); - } - } - - public static final class Builder { - private byte[] nonce; - private long expiresTime; - private long objectType = -1; - private long streamNumber; - private ObjectPayload payload; - - public Builder nonce(byte[] nonce) { - this.nonce = nonce; - return this; - } - - public Builder expiresTime(long expiresTime) { - this.expiresTime = expiresTime; - return this; - } - - public Builder objectType(long objectType) { - this.objectType = objectType; - return this; - } - - public Builder objectType(ObjectType objectType) { - this.objectType = objectType.getNumber(); - return this; - } - - public Builder stream(long streamNumber) { - this.streamNumber = streamNumber; - return this; - } - - public Builder payload(ObjectPayload payload) { - this.payload = payload; - if (this.objectType == -1) - this.objectType = payload.getType().getNumber(); - return this; - } - - public ObjectMessage build() { - return new ObjectMessage(this); - } - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - ObjectMessage that = (ObjectMessage) o; - - return expiresTime == that.expiresTime && - objectType == that.objectType && - version == that.version && - stream == that.stream && - Objects.equals(payload, that.payload); - } - - @Override - public int hashCode() { - int result = Arrays.hashCode(nonce); - result = 31 * result + (int) (expiresTime ^ (expiresTime >>> 32)); - result = 31 * result + (int) (objectType ^ (objectType >>> 32)); - result = 31 * result + (int) (version ^ (version >>> 32)); - result = 31 * result + (int) (stream ^ (stream >>> 32)); - result = 31 * result + (payload != null ? payload.hashCode() : 0); - return result; - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.kt b/core/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.kt new file mode 100644 index 0000000..255ca48 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.kt @@ -0,0 +1,228 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.entity + +import ch.dissem.bitmessage.entity.payload.ObjectPayload +import ch.dissem.bitmessage.entity.payload.ObjectType +import ch.dissem.bitmessage.entity.payload.Pubkey +import ch.dissem.bitmessage.entity.valueobject.InventoryVector +import ch.dissem.bitmessage.entity.valueobject.PrivateKey +import ch.dissem.bitmessage.exception.ApplicationException +import ch.dissem.bitmessage.exception.DecryptionFailedException +import ch.dissem.bitmessage.utils.Bytes +import ch.dissem.bitmessage.utils.Encode +import ch.dissem.bitmessage.utils.Singleton.cryptography +import java.io.ByteArrayOutputStream +import java.io.IOException +import java.io.OutputStream +import java.nio.ByteBuffer +import java.util.* + +/** + * The 'object' command sends an object that is shared throughout the network. + */ +data class ObjectMessage( + var nonce: ByteArray? = null, + val expiresTime: Long, + val payload: ObjectPayload, + val type: Long, + /** + * The object's version + */ + val version: Long, + val stream: Long +) : MessagePayload { + + override val command: MessagePayload.Command = MessagePayload.Command.OBJECT + + constructor( + nonce: ByteArray? = null, + expiresTime: Long, + payload: ObjectPayload, + stream: Long + ) : this( + nonce, + expiresTime, + payload, + payload.type?.number ?: throw IllegalArgumentException("payload must have type defined"), + payload.version, + stream + ) + + val inventoryVector: InventoryVector + get() { + return InventoryVector(Bytes.truncate(cryptography().doubleSha512( + nonce ?: throw IllegalStateException("nonce must be set"), + payloadBytesWithoutNonce + ), 32)) + } + + private val isEncrypted: Boolean + get() = payload is Encrypted && !payload.isDecrypted + + val isSigned: Boolean + get() = payload.isSigned + + private val bytesToSign: ByteArray + get() { + try { + val out = ByteArrayOutputStream() + writeHeaderWithoutNonce(out) + payload.writeBytesToSign(out) + return out.toByteArray() + } catch (e: IOException) { + throw ApplicationException(e) + } + } + + fun sign(key: PrivateKey) { + if (payload.isSigned) { + payload.signature = cryptography().getSignature(bytesToSign, key) + } + } + + @Throws(DecryptionFailedException::class) + fun decrypt(key: PrivateKey) { + if (payload is Encrypted) { + payload.decrypt(key.privateEncryptionKey) + } + } + + @Throws(DecryptionFailedException::class) + fun decrypt(privateEncryptionKey: ByteArray) { + if (payload is Encrypted) { + payload.decrypt(privateEncryptionKey) + } + } + + fun encrypt(publicEncryptionKey: ByteArray) { + if (payload is Encrypted) { + payload.encrypt(publicEncryptionKey) + } + } + + fun encrypt(publicKey: Pubkey) { + try { + if (payload is Encrypted) { + payload.encrypt(publicKey.encryptionKey) + } + } catch (e: IOException) { + throw ApplicationException(e) + } + + } + + fun isSignatureValid(pubkey: Pubkey): Boolean { + if (isEncrypted) throw IllegalStateException("Payload must be decrypted first") + return cryptography().isSignatureValid(bytesToSign, payload.signature ?: return false, pubkey) + } + + override fun write(out: OutputStream) { + out.write(nonce ?: ByteArray(8)) + out.write(payloadBytesWithoutNonce) + } + + override fun write(buffer: ByteBuffer) { + buffer.put(nonce ?: ByteArray(8)) + buffer.put(payloadBytesWithoutNonce) + } + + private fun writeHeaderWithoutNonce(out: OutputStream) { + Encode.int64(expiresTime, out) + Encode.int32(type, out) + Encode.varInt(version, out) + Encode.varInt(stream, out) + } + + val payloadBytesWithoutNonce: ByteArray by lazy { + val out = ByteArrayOutputStream() + writeHeaderWithoutNonce(out) + payload.write(out) + out.toByteArray() + } + + class Builder { + private var nonce: ByteArray? = null + private var expiresTime: Long = 0 + private var objectType: Long? = null + private var streamNumber: Long = 0 + private var payload: ObjectPayload? = null + + fun nonce(nonce: ByteArray): Builder { + this.nonce = nonce + return this + } + + fun expiresTime(expiresTime: Long): Builder { + this.expiresTime = expiresTime + return this + } + + fun objectType(objectType: Long): Builder { + this.objectType = objectType + return this + } + + fun objectType(objectType: ObjectType): Builder { + this.objectType = objectType.number + return this + } + + fun stream(streamNumber: Long): Builder { + this.streamNumber = streamNumber + return this + } + + fun payload(payload: ObjectPayload): Builder { + this.payload = payload + if (this.objectType == null) + this.objectType = payload.type?.number + return this + } + + fun build(): ObjectMessage { + return ObjectMessage( + nonce = nonce, + expiresTime = expiresTime, + type = objectType!!, + version = payload!!.version, + stream = if (streamNumber > 0) streamNumber else payload!!.stream, + payload = payload!! + ) + } + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is ObjectMessage) return false + return expiresTime == other.expiresTime && + type == other.type && + version == other.version && + stream == other.stream && + payload == other.payload + } + + override fun hashCode(): Int { + var result = Arrays.hashCode(nonce) + result = 31 * result + (expiresTime xor expiresTime.ushr(32)).toInt() + result = 31 * result + (type xor type.ushr(32)).toInt() + result = 31 * result + (version xor version.ushr(32)).toInt() + result = 31 * result + (stream xor stream.ushr(32)).toInt() + result = 31 * result + (payload.hashCode()) + return result + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java b/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java deleted file mode 100644 index 141edfc..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java +++ /dev/null @@ -1,733 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.entity; - -import ch.dissem.bitmessage.entity.payload.Msg; -import ch.dissem.bitmessage.entity.payload.Pubkey.Feature; -import ch.dissem.bitmessage.entity.valueobject.ExtendedEncoding; -import ch.dissem.bitmessage.entity.valueobject.InventoryVector; -import ch.dissem.bitmessage.entity.valueobject.Label; -import ch.dissem.bitmessage.entity.valueobject.extended.Attachment; -import ch.dissem.bitmessage.entity.valueobject.extended.Message; -import ch.dissem.bitmessage.exception.ApplicationException; -import ch.dissem.bitmessage.factory.ExtendedEncodingFactory; -import ch.dissem.bitmessage.factory.Factory; -import ch.dissem.bitmessage.utils.*; - -import java.io.*; -import java.nio.ByteBuffer; -import java.util.*; -import java.util.Collections; - -import static ch.dissem.bitmessage.entity.Plaintext.Encoding.EXTENDED; -import static ch.dissem.bitmessage.entity.Plaintext.Encoding.SIMPLE; -import static ch.dissem.bitmessage.utils.Singleton.cryptography; - -/** - * The unencrypted message to be sent by 'msg' or 'broadcast'. - */ -public class Plaintext implements Streamable { - private static final long serialVersionUID = -5325729856394951079L; - - private final Type type; - private final BitmessageAddress from; - private final long encoding; - private final byte[] message; - private final byte[] ackData; - private final UUID conversationId; - private ExtendedEncoding extendedData; - private ObjectMessage ackMessage; - private Object id; - private InventoryVector inventoryVector; - private BitmessageAddress to; - private byte[] signature; - private Status status; - private Long sent; - private Long received; - - private Set<Label> labels; - private byte[] initialHash; - - private long ttl; - private int retries; - private Long nextTry; - - private Plaintext(Builder builder) { - id = builder.id; - inventoryVector = builder.inventoryVector; - type = builder.type; - from = builder.from; - to = builder.to; - encoding = builder.encoding; - message = builder.message; - ackData = builder.ackData; - if (builder.ackMessage != null && builder.ackMessage.length > 0) { - ackMessage = Factory.getObjectMessage( - 3, - new ByteArrayInputStream(builder.ackMessage), - builder.ackMessage.length); - } - signature = builder.signature; - status = builder.status; - sent = builder.sent; - received = builder.received; - labels = builder.labels; - ttl = builder.ttl; - retries = builder.retries; - nextTry = builder.nextTry; - conversationId = builder.conversation; - } - - public static Plaintext read(Type type, InputStream in) throws IOException { - return readWithoutSignature(type, in) - .signature(Decode.varBytes(in)) - .received(UnixTime.now()) - .build(); - } - - public static Plaintext.Builder readWithoutSignature(Type type, InputStream in) throws IOException { - long version = Decode.varInt(in); - return new Builder(type) - .addressVersion(version) - .stream(Decode.varInt(in)) - .behaviorBitfield(Decode.int32(in)) - .publicSigningKey(Decode.bytes(in, 64)) - .publicEncryptionKey(Decode.bytes(in, 64)) - .nonceTrialsPerByte(version >= 3 ? Decode.varInt(in) : 0) - .extraBytes(version >= 3 ? Decode.varInt(in) : 0) - .destinationRipe(type == Type.MSG ? Decode.bytes(in, 20) : null) - .encoding(Decode.varInt(in)) - .message(Decode.varBytes(in)) - .ackMessage(type == Type.MSG ? Decode.varBytes(in) : null); - } - - public InventoryVector getInventoryVector() { - return inventoryVector; - } - - public void setInventoryVector(InventoryVector inventoryVector) { - this.inventoryVector = inventoryVector; - } - - public Type getType() { - return type; - } - - public byte[] getMessage() { - return message; - } - - public BitmessageAddress getFrom() { - return from; - } - - public BitmessageAddress getTo() { - return to; - } - - public void setTo(BitmessageAddress to) { - if (this.to.getVersion() != 0) - throw new IllegalStateException("Correct address already set"); - if (!Arrays.equals(this.to.getRipe(), to.getRipe())) { - throw new IllegalArgumentException("RIPEs don't match"); - } - this.to = to; - } - - public Set<Label> getLabels() { - return labels; - } - - public Encoding getEncoding() { - return Encoding.fromCode(encoding); - } - - public long getStream() { - return from.getStream(); - } - - public byte[] getSignature() { - return signature; - } - - public void setSignature(byte[] signature) { - this.signature = signature; - } - - public boolean isUnread() { - for (Label label : labels) { - if (label.getType() == Label.Type.UNREAD) { - return true; - } - } - return false; - } - - public void write(OutputStream out, boolean includeSignature) throws IOException { - Encode.varInt(from.getVersion(), out); - Encode.varInt(from.getStream(), out); - if (from.getPubkey() == null) { - Encode.int32(0, out); - byte[] empty = new byte[64]; - out.write(empty); - out.write(empty); - if (from.getVersion() >= 3) { - Encode.varInt(0, out); - Encode.varInt(0, out); - } - } else { - Encode.int32(from.getPubkey().getBehaviorBitfield(), out); - out.write(from.getPubkey().getSigningKey(), 1, 64); - out.write(from.getPubkey().getEncryptionKey(), 1, 64); - if (from.getVersion() >= 3) { - Encode.varInt(from.getPubkey().getNonceTrialsPerByte(), out); - Encode.varInt(from.getPubkey().getExtraBytes(), out); - } - } - if (type == Type.MSG) { - out.write(to.getRipe()); - } - Encode.varInt(encoding, out); - Encode.varInt(message.length, out); - out.write(message); - if (type == Type.MSG) { - if (to.has(Feature.DOES_ACK) && getAckMessage() != null) { - ByteArrayOutputStream ack = new ByteArrayOutputStream(); - getAckMessage().write(ack); - Encode.varBytes(ack.toByteArray(), out); - } else { - Encode.varInt(0, out); - } - } - if (includeSignature) { - if (signature == null) { - Encode.varInt(0, out); - } else { - Encode.varInt(signature.length, out); - out.write(signature); - } - } - } - - public void write(ByteBuffer buffer, boolean includeSignature) { - Encode.varInt(from.getVersion(), buffer); - Encode.varInt(from.getStream(), buffer); - if (from.getPubkey() == null) { - Encode.int32(0, buffer); - byte[] empty = new byte[64]; - buffer.put(empty); - buffer.put(empty); - if (from.getVersion() >= 3) { - Encode.varInt(0, buffer); - Encode.varInt(0, buffer); - } - } else { - Encode.int32(from.getPubkey().getBehaviorBitfield(), buffer); - buffer.put(from.getPubkey().getSigningKey(), 1, 64); - buffer.put(from.getPubkey().getEncryptionKey(), 1, 64); - if (from.getVersion() >= 3) { - Encode.varInt(from.getPubkey().getNonceTrialsPerByte(), buffer); - Encode.varInt(from.getPubkey().getExtraBytes(), buffer); - } - } - if (type == Type.MSG) { - buffer.put(to.getRipe()); - } - Encode.varInt(encoding, buffer); - Encode.varInt(message.length, buffer); - buffer.put(message); - if (type == Type.MSG) { - if (to.has(Feature.DOES_ACK) && getAckMessage() != null) { - Encode.varBytes(Encode.bytes(getAckMessage()), buffer); - } else { - Encode.varInt(0, buffer); - } - } - if (includeSignature) { - if (signature == null) { - Encode.varInt(0, buffer); - } else { - Encode.varInt(signature.length, buffer); - buffer.put(signature); - } - } - } - - @Override - public void write(OutputStream out) throws IOException { - write(out, true); - } - - @Override - public void write(ByteBuffer buffer) { - write(buffer, true); - } - - public Object getId() { - return id; - } - - public void setId(long id) { - if (this.id != null) throw new IllegalStateException("ID already set"); - this.id = id; - } - - public Long getSent() { - return sent; - } - - public Long getReceived() { - return received; - } - - public Status getStatus() { - return status; - } - - public void setStatus(Status status) { - if (status != Status.RECEIVED && sent == null && status != Status.DRAFT) { - sent = UnixTime.now(); - } - this.status = status; - } - - public long getTTL() { - return ttl; - } - - public int getRetries() { - return retries; - } - - public Long getNextTry() { - return nextTry; - } - - public void updateNextTry() { - if (to != null) { - if (nextTry == null) { - if (sent != null && to.has(Feature.DOES_ACK)) { - nextTry = UnixTime.now(+ttl); - retries++; - } - } else { - nextTry = nextTry + (1 << retries) * ttl; - retries++; - } - } - } - - public String getSubject() { - Scanner s = new Scanner(new ByteArrayInputStream(message), "UTF-8"); - String firstLine = s.nextLine(); - if (encoding == EXTENDED.code) { - if (Message.TYPE.equals(getExtendedData().getType())) { - return ((Message) extendedData.getContent()).getSubject(); - } else { - return null; - } - } else if (encoding == SIMPLE.code) { - return firstLine.substring("Subject:".length()).trim(); - } else if (firstLine.length() > 50) { - return firstLine.substring(0, 50).trim() + "..."; - } else { - return firstLine; - } - } - - public String getText() { - if (encoding == EXTENDED.code) { - if (Message.TYPE.equals(getExtendedData().getType())) { - return ((Message) extendedData.getContent()).getBody(); - } else { - return null; - } - } else { - try { - String text = new String(message, "UTF-8"); - if (encoding == SIMPLE.code) { - return text.substring(text.indexOf("\nBody:") + 6); - } - return text; - } catch (UnsupportedEncodingException e) { - throw new ApplicationException(e); - } - } - } - - protected ExtendedEncoding getExtendedData() { - if (extendedData == null && encoding == EXTENDED.code) { - // TODO: make sure errors are properly handled - extendedData = ExtendedEncodingFactory.getInstance().unzip(message); - } - return extendedData; - } - - @SuppressWarnings("unchecked") - public <T extends ExtendedEncoding.ExtendedType> T getExtendedData(Class<T> type) { - ExtendedEncoding extendedData = getExtendedData(); - if (extendedData == null) { - return null; - } - if (type == null || type.isInstance(extendedData.getContent())) { - return (T) extendedData.getContent(); - } - return null; - } - - public List<InventoryVector> getParents() { - if (getExtendedData() != null && Message.TYPE.equals(getExtendedData().getType())) { - return ((Message) extendedData.getContent()).getParents(); - } else { - return Collections.emptyList(); - } - } - - public List<Attachment> getFiles() { - if (Message.TYPE.equals(getExtendedData().getType())) { - return ((Message) extendedData.getContent()).getFiles(); - } else { - return Collections.emptyList(); - } - } - - public UUID getConversationId() { - return conversationId; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Plaintext plaintext = (Plaintext) o; - return Objects.equals(encoding, plaintext.encoding) && - Objects.equals(from, plaintext.from) && - Arrays.equals(message, plaintext.message) && - Objects.equals(getAckMessage(), plaintext.getAckMessage()) && - Arrays.equals(to == null ? null : to.getRipe(), plaintext.to == null ? null : plaintext.to.getRipe()) && - Arrays.equals(signature, plaintext.signature) && - Objects.equals(status, plaintext.status) && - Objects.equals(sent, plaintext.sent) && - Objects.equals(received, plaintext.received) && - Objects.equals(labels, plaintext.labels); - } - - @Override - public int hashCode() { - return Objects.hash(from, encoding, message, ackData, to, signature, status, sent, received, labels); - } - - public void addLabels(Label... labels) { - if (labels != null) { - Collections.addAll(this.labels, labels); - } - } - - public void addLabels(Collection<Label> labels) { - if (labels != null) { - this.labels.addAll(labels); - } - } - - public void removeLabel(Label.Type type) { - Iterator<Label> iterator = labels.iterator(); - while (iterator.hasNext()) { - Label label = iterator.next(); - if (label.getType() == type) { - iterator.remove(); - } - } - } - - public byte[] getAckData() { - return ackData; - } - - public ObjectMessage getAckMessage() { - if (ackMessage == null) { - ackMessage = Factory.createAck(this); - } - return ackMessage; - } - - public void setInitialHash(byte[] initialHash) { - this.initialHash = initialHash; - } - - public byte[] getInitialHash() { - return initialHash; - } - - @Override - public String toString() { - String subject = getSubject(); - if (subject == null || subject.length() == 0) { - return Strings.hex(initialHash).toString(); - } else { - return subject; - } - } - - public enum Encoding { - IGNORE(0), TRIVIAL(1), SIMPLE(2), EXTENDED(3); - - long code; - - Encoding(long code) { - this.code = code; - } - - public long getCode() { - return code; - } - - public static Encoding fromCode(long code) { - for (Encoding e : values()) { - if (e.getCode() == code) { - return e; - } - } - return null; - } - } - - public enum Status { - DRAFT, - // For sent messages - PUBKEY_REQUESTED, - DOING_PROOF_OF_WORK, - SENT, - SENT_ACKNOWLEDGED, - RECEIVED - } - - public enum Type { - MSG, BROADCAST - } - - public static final class Builder { - private Object id; - private InventoryVector inventoryVector; - private Type type; - private BitmessageAddress from; - private BitmessageAddress to; - private long addressVersion; - private long stream; - private int behaviorBitfield; - private byte[] publicSigningKey; - private byte[] publicEncryptionKey; - private long nonceTrialsPerByte; - private long extraBytes; - private byte[] destinationRipe; - private long encoding; - private byte[] message = new byte[0]; - private byte[] ackData; - private byte[] ackMessage; - private byte[] signature; - private Long sent; - private Long received; - private Status status; - private Set<Label> labels = new LinkedHashSet<>(); - private long ttl; - private int retries; - private Long nextTry; - private UUID conversation; - - public Builder(Type type) { - this.type = type; - } - - public Builder id(Object id) { - this.id = id; - return this; - } - - public Builder IV(InventoryVector iv) { - this.inventoryVector = iv; - return this; - } - - public Builder from(BitmessageAddress address) { - from = address; - return this; - } - - public Builder to(BitmessageAddress address) { - if (type != Type.MSG && to != null) - throw new IllegalArgumentException("recipient address only allowed for msg"); - to = address; - return this; - } - - private Builder addressVersion(long addressVersion) { - this.addressVersion = addressVersion; - return this; - } - - private Builder stream(long stream) { - this.stream = stream; - return this; - } - - private Builder behaviorBitfield(int behaviorBitfield) { - this.behaviorBitfield = behaviorBitfield; - return this; - } - - private Builder publicSigningKey(byte[] publicSigningKey) { - this.publicSigningKey = publicSigningKey; - return this; - } - - private Builder publicEncryptionKey(byte[] publicEncryptionKey) { - this.publicEncryptionKey = publicEncryptionKey; - return this; - } - - private Builder nonceTrialsPerByte(long nonceTrialsPerByte) { - this.nonceTrialsPerByte = nonceTrialsPerByte; - return this; - } - - private Builder extraBytes(long extraBytes) { - this.extraBytes = extraBytes; - return this; - } - - private Builder destinationRipe(byte[] ripe) { - if (type != Type.MSG && ripe != null) throw new IllegalArgumentException("ripe only allowed for msg"); - this.destinationRipe = ripe; - return this; - } - - public Builder encoding(Encoding encoding) { - this.encoding = encoding.getCode(); - return this; - } - - private Builder encoding(long encoding) { - this.encoding = encoding; - return this; - } - - public Builder message(ExtendedEncoding message) { - this.encoding = EXTENDED.getCode(); - this.message = message.zip(); - return this; - } - - public Builder message(String subject, String message) { - try { - this.encoding = SIMPLE.getCode(); - this.message = ("Subject:" + subject + '\n' + "Body:" + message).getBytes("UTF-8"); - } catch (UnsupportedEncodingException e) { - throw new ApplicationException(e); - } - return this; - } - - public Builder message(byte[] message) { - this.message = message; - return this; - } - - public Builder ackMessage(byte[] ack) { - if (type != Type.MSG && ack != null) throw new IllegalArgumentException("ackMessage only allowed for msg"); - this.ackMessage = ack; - return this; - } - - public Builder ackData(byte[] ackData) { - if (type != Type.MSG && ackData != null) - throw new IllegalArgumentException("ackMessage only allowed for msg"); - this.ackData = ackData; - return this; - } - - public Builder signature(byte[] signature) { - this.signature = signature; - return this; - } - - public Builder sent(Long sent) { - this.sent = sent; - return this; - } - - public Builder received(Long received) { - this.received = received; - return this; - } - - public Builder status(Status status) { - this.status = status; - return this; - } - - public Builder labels(Collection<Label> labels) { - this.labels.addAll(labels); - return this; - } - - public Builder ttl(long ttl) { - this.ttl = ttl; - return this; - } - - public Builder retries(int retries) { - this.retries = retries; - return this; - } - - public Builder nextTry(Long nextTry) { - this.nextTry = nextTry; - return this; - } - - public Builder conversation(UUID id) { - this.conversation = id; - return this; - } - - public Plaintext build() { - if (from == null) { - from = new BitmessageAddress(Factory.createPubkey( - addressVersion, - stream, - publicSigningKey, - publicEncryptionKey, - nonceTrialsPerByte, - extraBytes, - behaviorBitfield - )); - } - if (to == null && type != Type.BROADCAST && destinationRipe != null) { - to = new BitmessageAddress(0, 0, destinationRipe); - } - if (type == Type.MSG && ackMessage == null && ackData == null) { - ackData = cryptography().randomBytes(Msg.ACK_LENGTH); - } - if (ttl <= 0) { - ttl = TTL.msg(); - } - if (conversation == null) { - conversation = UUID.randomUUID(); - } - return new Plaintext(this); - } - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.kt b/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.kt new file mode 100644 index 0000000..8946d73 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.kt @@ -0,0 +1,706 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.entity + +import ch.dissem.bitmessage.entity.Plaintext.Encoding.EXTENDED +import ch.dissem.bitmessage.entity.Plaintext.Encoding.SIMPLE +import ch.dissem.bitmessage.entity.payload.Msg +import ch.dissem.bitmessage.entity.payload.Pubkey.Feature +import ch.dissem.bitmessage.entity.valueobject.ExtendedEncoding +import ch.dissem.bitmessage.entity.valueobject.InventoryVector +import ch.dissem.bitmessage.entity.valueobject.Label +import ch.dissem.bitmessage.entity.valueobject.extended.Attachment +import ch.dissem.bitmessage.entity.valueobject.extended.Message +import ch.dissem.bitmessage.exception.ApplicationException +import ch.dissem.bitmessage.factory.ExtendedEncodingFactory +import ch.dissem.bitmessage.factory.Factory +import ch.dissem.bitmessage.utils.* +import ch.dissem.bitmessage.utils.Singleton.cryptography +import java.io.* +import java.nio.ByteBuffer +import java.util.* +import java.util.Collections +import kotlin.collections.HashSet + +/** + * The unencrypted message to be sent by 'msg' or 'broadcast'. + */ +class Plaintext private constructor( + val type: Type, + val from: BitmessageAddress, + to: BitmessageAddress?, + val encodingCode: Long, + val message: ByteArray, + val ackData: ByteArray?, + ackMessage: Lazy<ObjectMessage?>, + val conversationId: UUID = UUID.randomUUID(), + var inventoryVector: InventoryVector? = null, + var signature: ByteArray? = null, + val received: Long? = null, + var initialHash: ByteArray? = null, + ttl: Long = TTL.msg, + val labels: MutableSet<Label> = HashSet(), + status: Status +) : Streamable { + + var id: Any? = null + set(id) { + if (this.id != null) throw IllegalStateException("ID already set") + field = id + } + + var to: BitmessageAddress? = to + set(to) { + if (to == null) { + return + } + if (this.to != null) { + if (this.to!!.version != 0L) + throw IllegalStateException("Correct address already set") + if (!Arrays.equals(this.to!!.ripe, to.ripe)) { + throw IllegalArgumentException("RIPEs don't match") + } + } + field = to + } + + val stream: Long + get() = to?.stream ?: from.stream + + val extendedData: ExtendedEncoding? by lazy { + if (encodingCode == EXTENDED.code) { + ExtendedEncodingFactory.unzip(message) + } else { + null + } + } + + val ackMessage: ObjectMessage? by ackMessage + + var status: Status = status + set(status) { + if (status != Status.RECEIVED && sent == null && status != Status.DRAFT) { + sent = UnixTime.now + } + field = status + } + + val encoding: Encoding? by lazy { Encoding.fromCode(encodingCode) } + var sent: Long? = null + private set + var retries: Int = 0 + private set + var nextTry: Long? = null + private set + val ttl: Long = ttl + @JvmName("getTTL") get + + constructor( + type: Type, + from: BitmessageAddress, + to: BitmessageAddress?, + encoding: Encoding, + message: ByteArray, + ackData: ByteArray = cryptography().randomBytes(Msg.ACK_LENGTH), + conversationId: UUID = UUID.randomUUID(), + inventoryVector: InventoryVector? = null, + signature: ByteArray? = null, + received: Long? = null, + initialHash: ByteArray? = null, + ttl: Long = TTL.msg, + labels: MutableSet<Label> = HashSet(), + status: Status + ) : this( + type, + from, + to, + encoding.code, + message, + ackData, + lazy { Factory.createAck(from, ackData, ttl) }, + conversationId, + inventoryVector, + signature, + received, + initialHash, + ttl, + labels, + status + ) + + constructor( + type: Type, + from: BitmessageAddress, + to: BitmessageAddress?, + encoding: Long, + message: ByteArray, + ackMessage: ByteArray?, + conversationId: UUID = UUID.randomUUID(), + inventoryVector: InventoryVector? = null, + signature: ByteArray? = null, + received: Long? = null, + initialHash: ByteArray? = null, + ttl: Long = TTL.msg, + labels: MutableSet<Label> = HashSet(), + status: Status + ) : this( + type, + from, + to, + encoding, + message, + null, + lazy { + if (ackMessage != null && ackMessage.isNotEmpty()) { + Factory.getObjectMessage( + 3, + ByteArrayInputStream(ackMessage), + ackMessage.size) + } else null + }, + conversationId, + inventoryVector, + signature, + received, + initialHash, + ttl, + labels, + status + ) + + constructor(builder: Builder) : this( + builder.type, + builder.from!!, + builder.to, + builder.encoding, + builder.message, + builder.ackData, + lazy { + val ackMsg = builder.ackMessage + if (ackMsg != null && ackMsg.isNotEmpty()) { + Factory.getObjectMessage( + 3, + ByteArrayInputStream(ackMsg), + ackMsg.size) + } else { + Factory.createAck(builder.from!!, builder.ackData, builder.ttl) + } + }, + builder.conversation ?: UUID.randomUUID(), + builder.inventoryVector, + builder.signature, + builder.received, + null, + builder.ttl, + builder.labels, + builder.status ?: Status.RECEIVED + ) + + fun write(out: OutputStream, includeSignature: Boolean) { + Encode.varInt(from.version, out) + Encode.varInt(from.stream, out) + if (from.pubkey == null) { + Encode.int32(0, out) + val empty = ByteArray(64) + out.write(empty) + out.write(empty) + if (from.version >= 3) { + Encode.varInt(0, out) + Encode.varInt(0, out) + } + } else { + Encode.int32(from.pubkey!!.behaviorBitfield.toLong(), out) + out.write(from.pubkey!!.signingKey, 1, 64) + out.write(from.pubkey!!.encryptionKey, 1, 64) + if (from.version >= 3) { + Encode.varInt(from.pubkey!!.nonceTrialsPerByte, out) + Encode.varInt(from.pubkey!!.extraBytes, out) + } + } + if (type == Type.MSG) { + out.write(to!!.ripe) + } + Encode.varInt(encodingCode, out) + Encode.varInt(message.size.toLong(), out) + out.write(message) + if (type == Type.MSG) { + if (to?.has(Feature.DOES_ACK) ?: false) { + val ack = ByteArrayOutputStream() + ackMessage?.write(ack) + Encode.varBytes(ack.toByteArray(), out) + } else { + Encode.varInt(0, out) + } + } + if (includeSignature) { + if (signature == null) { + Encode.varInt(0, out) + } else { + Encode.varInt(signature!!.size.toLong(), out) + out.write(signature!!) + } + } + } + + fun write(buffer: ByteBuffer, includeSignature: Boolean) { + Encode.varInt(from.version, buffer) + Encode.varInt(from.stream, buffer) + if (from.pubkey == null) { + Encode.int32(0, buffer) + val empty = ByteArray(64) + buffer.put(empty) + buffer.put(empty) + if (from.version >= 3) { + Encode.varInt(0, buffer) + Encode.varInt(0, buffer) + } + } else { + Encode.int32(from.pubkey!!.behaviorBitfield.toLong(), buffer) + buffer.put(from.pubkey!!.signingKey, 1, 64) + buffer.put(from.pubkey!!.encryptionKey, 1, 64) + if (from.version >= 3) { + Encode.varInt(from.pubkey!!.nonceTrialsPerByte, buffer) + Encode.varInt(from.pubkey!!.extraBytes, buffer) + } + } + if (type == Type.MSG) { + buffer.put(to!!.ripe) + } + Encode.varInt(encodingCode, buffer) + Encode.varInt(message.size.toLong(), buffer) + buffer.put(message) + if (type == Type.MSG) { + if (to!!.has(Feature.DOES_ACK) && ackMessage != null) { + Encode.varBytes(Encode.bytes(ackMessage!!), buffer) + } else { + Encode.varInt(0, buffer) + } + } + if (includeSignature) { + val sig = signature + if (sig == null) { + Encode.varInt(0, buffer) + } else { + Encode.varInt(sig.size.toLong(), buffer) + buffer.put(sig) + } + } + } + + override fun write(out: OutputStream) { + write(out, true) + } + + override fun write(buffer: ByteBuffer) { + write(buffer, true) + } + + fun updateNextTry() { + if (to != null) { + if (nextTry == null) { + if (sent != null && to!!.has(Feature.DOES_ACK)) { + nextTry = UnixTime.now + ttl + retries++ + } + } else { + nextTry = nextTry!! + (1 shl retries) * ttl + retries++ + } + } + } + + val subject: String? + get() { + val s = Scanner(ByteArrayInputStream(message), "UTF-8") + val firstLine = s.nextLine() + if (encodingCode == EXTENDED.code) { + if (Message.TYPE == extendedData?.type) { + return (extendedData!!.content as Message?)?.subject + } else { + return null + } + } else if (encodingCode == SIMPLE.code) { + return firstLine.substring("Subject:".length).trim { it <= ' ' } + } else if (firstLine.length > 50) { + return firstLine.substring(0, 50).trim { it <= ' ' } + "..." + } else { + return firstLine + } + } + + val text: String? + get() { + if (encodingCode == EXTENDED.code) { + if (Message.TYPE == extendedData?.type) { + return (extendedData?.content as Message?)?.body + } else { + return null + } + } else { + val text = String(message) + if (encodingCode == SIMPLE.code) { + return text.substring(text.indexOf("\nBody:") + 6) + } + return text + } + } + + fun <T : ExtendedEncoding.ExtendedType> getExtendedData(type: Class<T>): T? { + val extendedData = extendedData ?: return null + if (type.isInstance(extendedData.content)) { + @Suppress("UNCHECKED_CAST") + return extendedData.content as T + } + return null + } + + val parents: List<InventoryVector> + get() { + val extendedData = extendedData ?: return emptyList() + if (Message.TYPE == extendedData.type) { + return (extendedData.content as Message).parents + } else { + return emptyList() + } + } + + val files: List<Attachment> + get() { + val extendedData = extendedData ?: return emptyList() + if (Message.TYPE == extendedData.type) { + return (extendedData.content as Message).files + } else { + return emptyList() + } + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is Plaintext) return false + return encoding == other.encoding && + from == other.from && + Arrays.equals(message, other.message) && + ackMessage == other.ackMessage && + Arrays.equals(to?.ripe, other.to?.ripe) && + Arrays.equals(signature, other.signature) && + status == other.status && + sent == other.sent && + received == other.received && + labels == other.labels + } + + override fun hashCode(): Int { + return Objects.hash(from, encoding, message, ackData, to, signature, status, sent, received, labels) + } + + fun addLabels(vararg labels: Label) { + Collections.addAll(this.labels, *labels) + } + + fun addLabels(labels: Collection<Label>?) { + if (labels != null) { + this.labels.addAll(labels) + } + } + + fun removeLabel(type: Label.Type) { + labels.removeIf { it.type == type } + } + + fun isUnread(): Boolean { + return labels.any { it.type == Label.Type.UNREAD } + } + + override fun toString(): String { + val subject = subject + if (subject?.isNotEmpty() ?: false) { + return subject!! + } else { + return Strings.hex( + initialHash ?: return super.toString() + ) + } + } + + enum class Encoding constructor(code: Long) { + IGNORE(0), TRIVIAL(1), SIMPLE(2), EXTENDED(3); + + var code: Long = 0 + internal set + + init { + this.code = code + } + + companion object { + + @JvmStatic fun fromCode(code: Long): Encoding? { + for (e in values()) { + if (e.code == code) { + return e + } + } + return null + } + } + } + + enum class Status { + DRAFT, + // For sent messages + PUBKEY_REQUESTED, + DOING_PROOF_OF_WORK, + SENT, + SENT_ACKNOWLEDGED, + RECEIVED + } + + enum class Type { + MSG, BROADCAST + } + + class Builder(internal val type: Type) { + internal var id: Any? = null + internal var inventoryVector: InventoryVector? = null + internal var from: BitmessageAddress? = null + internal var to: BitmessageAddress? = null + private var addressVersion: Long = 0 + private var stream: Long = 0 + private var behaviorBitfield: Int = 0 + private var publicSigningKey: ByteArray? = null + private var publicEncryptionKey: ByteArray? = null + private var nonceTrialsPerByte: Long = 0 + private var extraBytes: Long = 0 + private var destinationRipe: ByteArray? = null + internal var encoding: Long = 0 + internal var message = ByteArray(0) + internal var ackData: ByteArray? = null + internal var ackMessage: ByteArray? = null + internal var signature: ByteArray? = null + internal var sent: Long? = null + internal var received: Long? = null + internal var status: Status? = null + internal val labels = LinkedHashSet<Label>() + internal var ttl: Long = 0 + internal var retries: Int = 0 + internal var nextTry: Long? = null + internal var conversation: UUID? = null + + fun id(id: Any): Builder { + this.id = id + return this + } + + fun IV(iv: InventoryVector?): Builder { + this.inventoryVector = iv + return this + } + + fun from(address: BitmessageAddress): Builder { + from = address + return this + } + + fun to(address: BitmessageAddress): Builder { + if (type != Type.MSG && to != null) + throw IllegalArgumentException("recipient address only allowed for msg") + to = address + return this + } + + fun addressVersion(addressVersion: Long): Builder { + this.addressVersion = addressVersion + return this + } + + fun stream(stream: Long): Builder { + this.stream = stream + return this + } + + fun behaviorBitfield(behaviorBitfield: Int): Builder { + this.behaviorBitfield = behaviorBitfield + return this + } + + fun publicSigningKey(publicSigningKey: ByteArray): Builder { + this.publicSigningKey = publicSigningKey + return this + } + + fun publicEncryptionKey(publicEncryptionKey: ByteArray): Builder { + this.publicEncryptionKey = publicEncryptionKey + return this + } + + fun nonceTrialsPerByte(nonceTrialsPerByte: Long): Builder { + this.nonceTrialsPerByte = nonceTrialsPerByte + return this + } + + fun extraBytes(extraBytes: Long): Builder { + this.extraBytes = extraBytes + return this + } + + fun destinationRipe(ripe: ByteArray?): Builder { + if (type != Type.MSG && ripe != null) throw IllegalArgumentException("ripe only allowed for msg") + this.destinationRipe = ripe + return this + } + + fun encoding(encoding: Encoding): Builder { + this.encoding = encoding.code + return this + } + + fun encoding(encoding: Long): Builder { + this.encoding = encoding + return this + } + + fun message(message: ExtendedEncoding): Builder { + this.encoding = EXTENDED.code + this.message = message.zip() + return this + } + + fun message(subject: String, message: String): Builder { + try { + this.encoding = SIMPLE.code + this.message = "Subject:$subject\nBody:$message".toByteArray(charset("UTF-8")) + } catch (e: UnsupportedEncodingException) { + throw ApplicationException(e) + } + + return this + } + + fun message(message: ByteArray): Builder { + this.message = message + return this + } + + fun ackMessage(ack: ByteArray?): Builder { + if (type != Type.MSG && ack != null) throw IllegalArgumentException("ackMessage only allowed for msg") + this.ackMessage = ack + return this + } + + fun ackData(ackData: ByteArray?): Builder { + if (type != Type.MSG && ackData != null) + throw IllegalArgumentException("ackMessage only allowed for msg") + this.ackData = ackData + return this + } + + fun signature(signature: ByteArray): Builder { + this.signature = signature + return this + } + + fun sent(sent: Long?): Builder { + this.sent = sent + return this + } + + fun received(received: Long?): Builder { + this.received = received + return this + } + + fun status(status: Status): Builder { + this.status = status + return this + } + + fun labels(labels: Collection<Label>): Builder { + this.labels.addAll(labels) + return this + } + + fun ttl(ttl: Long): Builder { + this.ttl = ttl + return this + } + + fun retries(retries: Int): Builder { + this.retries = retries + return this + } + + fun nextTry(nextTry: Long?): Builder { + this.nextTry = nextTry + return this + } + + fun conversation(id: UUID): Builder { + this.conversation = id + return this + } + + fun build(): Plaintext { + if (from == null) { + from = BitmessageAddress(Factory.createPubkey( + addressVersion, + stream, + publicSigningKey!!, + publicEncryptionKey!!, + nonceTrialsPerByte, + extraBytes, + behaviorBitfield + )) + } + if (to == null && type != Type.BROADCAST && destinationRipe != null) { + to = BitmessageAddress(0, 0, destinationRipe!!) + } + if (type == Type.MSG && ackMessage == null && ackData == null) { + ackData = cryptography().randomBytes(Msg.ACK_LENGTH) + } + if (ttl <= 0) { + ttl = TTL.msg + } + return Plaintext(this) + } + } + + companion object { + + @JvmStatic fun read(type: Type, `in`: InputStream): Plaintext { + return readWithoutSignature(type, `in`) + .signature(Decode.varBytes(`in`)) + .received(UnixTime.now) + .build() + } + + @JvmStatic fun readWithoutSignature(type: Type, `in`: InputStream): Plaintext.Builder { + val version = Decode.varInt(`in`) + return Builder(type) + .addressVersion(version) + .stream(Decode.varInt(`in`)) + .behaviorBitfield(Decode.int32(`in`)) + .publicSigningKey(Decode.bytes(`in`, 64)) + .publicEncryptionKey(Decode.bytes(`in`, 64)) + .nonceTrialsPerByte(if (version >= 3) Decode.varInt(`in`) else 0) + .extraBytes(if (version >= 3) Decode.varInt(`in`) else 0) + .destinationRipe(if (type == Type.MSG) Decode.bytes(`in`, 20) else null) + .encoding(Decode.varInt(`in`)) + .message(Decode.varBytes(`in`)) + .ackMessage(if (type == Type.MSG) Decode.varBytes(`in`) else null) + } + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/PlaintextHolder.java b/core/src/main/java/ch/dissem/bitmessage/entity/PlaintextHolder.kt similarity index 80% rename from core/src/main/java/ch/dissem/bitmessage/entity/PlaintextHolder.java rename to core/src/main/java/ch/dissem/bitmessage/entity/PlaintextHolder.kt index 3133cab..e890b13 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/PlaintextHolder.java +++ b/core/src/main/java/ch/dissem/bitmessage/entity/PlaintextHolder.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015 Christian Basler + * Copyright 2017 Christian Basler * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,8 +14,8 @@ * limitations under the License. */ -package ch.dissem.bitmessage.entity; +package ch.dissem.bitmessage.entity -public interface PlaintextHolder { - Plaintext getPlaintext(); +interface PlaintextHolder { + val plaintext: Plaintext? } diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/Streamable.java b/core/src/main/java/ch/dissem/bitmessage/entity/Streamable.java deleted file mode 100644 index e75a926..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/entity/Streamable.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.entity; - -import java.io.IOException; -import java.io.OutputStream; -import java.io.Serializable; -import java.nio.ByteBuffer; - -/** - * An object that can be written to an {@link OutputStream} - */ -public interface Streamable extends Serializable { - void write(OutputStream stream) throws IOException; - - void write(ByteBuffer buffer); -} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/Streamable.kt b/core/src/main/java/ch/dissem/bitmessage/entity/Streamable.kt new file mode 100644 index 0000000..b8ad391 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/entity/Streamable.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.entity + +import java.io.OutputStream +import java.io.Serializable +import java.nio.ByteBuffer + +/** + * An object that can be written to an [OutputStream] + */ +interface Streamable : Serializable { + fun write(out: OutputStream) + + fun write(buffer: ByteBuffer) +} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/VerAck.java b/core/src/main/java/ch/dissem/bitmessage/entity/VerAck.kt similarity index 63% rename from core/src/main/java/ch/dissem/bitmessage/entity/VerAck.java rename to core/src/main/java/ch/dissem/bitmessage/entity/VerAck.kt index 3d30f32..268dabe 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/VerAck.java +++ b/core/src/main/java/ch/dissem/bitmessage/entity/VerAck.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015 Christian Basler + * Copyright 2017 Christian Basler * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,30 +14,27 @@ * limitations under the License. */ -package ch.dissem.bitmessage.entity; +package ch.dissem.bitmessage.entity -import java.io.IOException; -import java.io.OutputStream; -import java.nio.ByteBuffer; +import java.io.OutputStream +import java.nio.ByteBuffer /** * The 'verack' command answers a 'version' command, accepting the other node's version. */ -public class VerAck implements MessagePayload { - private static final long serialVersionUID = -4302074845199181687L; +class VerAck : MessagePayload { - @Override - public Command getCommand() { - return Command.VERACK; - } + override val command: MessagePayload.Command = MessagePayload.Command.VERACK - @Override - public void write(OutputStream stream) throws IOException { + override fun write(out: OutputStream) { // 'verack' doesn't have any payload, so there is nothing to write } - @Override - public void write(ByteBuffer buffer) { + override fun write(buffer: ByteBuffer) { // 'verack' doesn't have any payload, so there is nothing to write } + + companion object { + private val serialVersionUID = -4302074845199181687L + } } diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/Version.java b/core/src/main/java/ch/dissem/bitmessage/entity/Version.java deleted file mode 100644 index bfb4869..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/entity/Version.java +++ /dev/null @@ -1,246 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.entity; - -import ch.dissem.bitmessage.BitmessageContext; -import ch.dissem.bitmessage.entity.valueobject.NetworkAddress; -import ch.dissem.bitmessage.utils.Encode; -import ch.dissem.bitmessage.utils.UnixTime; - -import java.io.IOException; -import java.io.OutputStream; -import java.nio.ByteBuffer; - -/** - * The 'version' command advertises this node's latest supported protocol version upon initiation. - */ -public class Version implements MessagePayload { - private static final long serialVersionUID = 7219240857343176567L; - - /** - * Identifies protocol version being used by the node. Should equal 3. Nodes should disconnect if the remote node's - * version is lower but continue with the connection if it is higher. - */ - private final int version; - - /** - * bitfield of features to be enabled for this connection - */ - private final long services; - - /** - * standard UNIX timestamp in seconds - */ - private final long timestamp; - - /** - * The network address of the node receiving this message (not including the time or stream number) - */ - private final NetworkAddress addrRecv; - - /** - * The network address of the node emitting this message (not including the time or stream number and the ip itself - * is ignored by the receiver) - */ - private final NetworkAddress addrFrom; - - /** - * Random nonce used to detect connections to self. - */ - private final long nonce; - - /** - * User Agent (0x00 if string is 0 bytes long). Sending nodes must not include a user_agent longer than 5000 bytes. - */ - private final String userAgent; - - /** - * The stream numbers that the emitting node is interested in. Sending nodes must not include more than 160000 - * stream numbers. - */ - private final long[] streams; - - private Version(Builder builder) { - version = builder.version; - services = builder.services; - timestamp = builder.timestamp; - addrRecv = builder.addrRecv; - addrFrom = builder.addrFrom; - nonce = builder.nonce; - userAgent = builder.userAgent; - streams = builder.streamNumbers; - } - - public int getVersion() { - return version; - } - - public long getServices() { - return services; - } - - public boolean provides(Service service) { - return service != null && service.isEnabled(services); - } - - public long getTimestamp() { - return timestamp; - } - - public NetworkAddress getAddrRecv() { - return addrRecv; - } - - public NetworkAddress getAddrFrom() { - return addrFrom; - } - - public long getNonce() { - return nonce; - } - - public String getUserAgent() { - return userAgent; - } - - public long[] getStreams() { - return streams; - } - - @Override - public Command getCommand() { - return Command.VERSION; - } - - @Override - public void write(OutputStream stream) throws IOException { - Encode.int32(version, stream); - Encode.int64(services, stream); - Encode.int64(timestamp, stream); - addrRecv.write(stream, true); - addrFrom.write(stream, true); - Encode.int64(nonce, stream); - Encode.varString(userAgent, stream); - Encode.varIntList(streams, stream); - } - - @Override - public void write(ByteBuffer buffer) { - Encode.int32(version, buffer); - Encode.int64(services, buffer); - Encode.int64(timestamp, buffer); - addrRecv.write(buffer, true); - addrFrom.write(buffer, true); - Encode.int64(nonce, buffer); - Encode.varString(userAgent, buffer); - Encode.varIntList(streams, buffer); - } - - - public static final class Builder { - private int version; - private long services; - private long timestamp; - private NetworkAddress addrRecv; - private NetworkAddress addrFrom; - private long nonce; - private String userAgent; - private long[] streamNumbers; - - public Builder defaults(long clientNonce) { - version = BitmessageContext.CURRENT_VERSION; - services = Service.getServiceFlag(Service.NODE_NETWORK); - timestamp = UnixTime.now(); - userAgent = "/Jabit:0.0.1/"; - streamNumbers = new long[]{1}; - nonce = clientNonce; - return this; - } - - public Builder version(int version) { - this.version = version; - return this; - } - - public Builder services(Service... services) { - this.services = Service.getServiceFlag(services); - return this; - } - - public Builder services(long services) { - this.services = services; - return this; - } - - public Builder timestamp(long timestamp) { - this.timestamp = timestamp; - return this; - } - - public Builder addrRecv(NetworkAddress addrRecv) { - this.addrRecv = addrRecv; - return this; - } - - public Builder addrFrom(NetworkAddress addrFrom) { - this.addrFrom = addrFrom; - return this; - } - - public Builder nonce(long nonce) { - this.nonce = nonce; - return this; - } - - public Builder userAgent(String userAgent) { - this.userAgent = userAgent; - return this; - } - - public Builder streams(long... streamNumbers) { - this.streamNumbers = streamNumbers; - return this; - } - - public Version build() { - return new Version(this); - } - } - - public enum Service { - NODE_NETWORK(1); -// TODO: NODE_SSL(2); - - long flag; - - Service(long flag) { - this.flag = flag; - } - - public boolean isEnabled(long flag) { - return (flag & this.flag) != 0; - } - - public static long getServiceFlag(Service... services) { - long flag = 0; - for (Service service : services) { - flag |= service.flag; - } - return flag; - } - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/Version.kt b/core/src/main/java/ch/dissem/bitmessage/entity/Version.kt new file mode 100644 index 0000000..3a1c3f7 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/entity/Version.kt @@ -0,0 +1,204 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.entity + +import ch.dissem.bitmessage.BitmessageContext +import ch.dissem.bitmessage.entity.valueobject.NetworkAddress +import ch.dissem.bitmessage.utils.Encode +import ch.dissem.bitmessage.utils.UnixTime +import java.io.OutputStream +import java.nio.ByteBuffer + +/** + * The 'version' command advertises this node's latest supported protocol version upon initiation. + */ +class Version constructor( + /** + * Identifies protocol version being used by the node. Should equal 3. Nodes should disconnect if the remote node's + * version is lower but continue with the connection if it is higher. + */ + val version: Int = BitmessageContext.CURRENT_VERSION, + + /** + * bitfield of features to be enabled for this connection + */ + val services: Long = Version.Service.getServiceFlag(Version.Service.NODE_NETWORK), + + /** + * standard UNIX timestamp in seconds + */ + val timestamp: Long = UnixTime.now, + + /** + * The network address of the node receiving this message (not including the time or stream number) + */ + val addrRecv: NetworkAddress, + + /** + * The network address of the node emitting this message (not including the time or stream number and the ip itself + * is ignored by the receiver) + */ + val addrFrom: NetworkAddress, + + /** + * Random nonce used to detect connections to self. + */ + val nonce: Long, + + /** + * User Agent (0x00 if string is 0 bytes long). Sending nodes must not include a user_agent longer than 5000 bytes. + */ + val userAgent: String = "/Jabit:0.0.1/", + + /** + * The stream numbers that the emitting node is interested in. Sending nodes must not include more than 160000 + * stream numbers. + */ + val streams: LongArray = longArrayOf(1) +) : MessagePayload { + + fun provides(service: Service?): Boolean { + return service != null && service.isEnabled(services) + } + + override val command: MessagePayload.Command = MessagePayload.Command.VERSION + + override fun write(out: OutputStream) { + Encode.int32(version.toLong(), out) + Encode.int64(services, out) + Encode.int64(timestamp, out) + addrRecv.write(out, true) + addrFrom.write(out, true) + Encode.int64(nonce, out) + Encode.varString(userAgent, out) + Encode.varIntList(streams, out) + } + + override fun write(buffer: ByteBuffer) { + Encode.int32(version.toLong(), buffer) + Encode.int64(services, buffer) + Encode.int64(timestamp, buffer) + addrRecv.write(buffer, true) + addrFrom.write(buffer, true) + Encode.int64(nonce, buffer) + Encode.varString(userAgent, buffer) + Encode.varIntList(streams, buffer) + } + + class Builder { + private var version: Int = 0 + private var services: Long = 0 + private var timestamp: Long = 0 + private var addrRecv: NetworkAddress? = null + private var addrFrom: NetworkAddress? = null + private var nonce: Long = 0 + private var userAgent: String? = null + private var streamNumbers: LongArray? = null + + fun defaults(clientNonce: Long): Builder { + version = BitmessageContext.CURRENT_VERSION + services = Service.getServiceFlag(Service.NODE_NETWORK) + timestamp = UnixTime.now + userAgent = "/Jabit:0.0.1/" + streamNumbers = longArrayOf(1) + nonce = clientNonce + return this + } + + fun version(version: Int): Builder { + this.version = version + return this + } + + fun services(vararg services: Service): Builder { + this.services = Service.getServiceFlag(*services) + return this + } + + fun services(services: Long): Builder { + this.services = services + return this + } + + fun timestamp(timestamp: Long): Builder { + this.timestamp = timestamp + return this + } + + fun addrRecv(addrRecv: NetworkAddress): Builder { + this.addrRecv = addrRecv + return this + } + + fun addrFrom(addrFrom: NetworkAddress): Builder { + this.addrFrom = addrFrom + return this + } + + fun nonce(nonce: Long): Builder { + this.nonce = nonce + return this + } + + fun userAgent(userAgent: String): Builder { + this.userAgent = userAgent + return this + } + + fun streams(vararg streamNumbers: Long): Builder { + this.streamNumbers = streamNumbers + return this + } + + fun build(): Version { + val addrRecv = this.addrRecv + val addrFrom = this.addrFrom + if (addrRecv == null || addrFrom == null) { + throw IllegalStateException("Receiving and sending address must be set") + } + + return Version( + version = version, + services = services, + timestamp = timestamp, + addrRecv = addrRecv, addrFrom = addrFrom, + nonce = nonce, + userAgent = userAgent ?: "/Jabit:0.0.1/", + streams = streamNumbers ?: longArrayOf(1) + ) + } + } + + enum class Service constructor(internal var flag: Long) { + // TODO: NODE_SSL(2); + NODE_NETWORK(1); + + fun isEnabled(flag: Long): Boolean { + return (flag and this.flag) != 0L + } + + companion object { + fun getServiceFlag(vararg services: Service): Long { + var flag: Long = 0 + for (service in services) { + flag = flag or service.flag + } + return flag + } + } + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.java b/core/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.java deleted file mode 100644 index a583330..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.entity.payload; - -import ch.dissem.bitmessage.entity.BitmessageAddress; -import ch.dissem.bitmessage.entity.Encrypted; -import ch.dissem.bitmessage.entity.Plaintext; -import ch.dissem.bitmessage.entity.PlaintextHolder; -import ch.dissem.bitmessage.exception.DecryptionFailedException; - -import java.io.IOException; -import java.util.Objects; - -import static ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST; -import static ch.dissem.bitmessage.utils.Singleton.cryptography; - -/** - * Users who are subscribed to the sending address will see the message appear in their inbox. - * Broadcasts are version 4 or 5. - */ -public abstract class Broadcast extends ObjectPayload implements Encrypted, PlaintextHolder { - private static final long serialVersionUID = 4064521827582239069L; - - protected final long stream; - protected CryptoBox encrypted; - protected Plaintext plaintext; - - protected Broadcast(long version, long stream, CryptoBox encrypted, Plaintext plaintext) { - super(version); - this.stream = stream; - this.encrypted = encrypted; - this.plaintext = plaintext; - } - - public static long getVersion(BitmessageAddress address) { - return address.getVersion() < 4 ? 4 : 5; - } - - @Override - public boolean isSigned() { - return true; - } - - @Override - public byte[] getSignature() { - return plaintext.getSignature(); - } - - @Override - public void setSignature(byte[] signature) { - plaintext.setSignature(signature); - } - - @Override - public long getStream() { - return stream; - } - - @Override - public Plaintext getPlaintext() { - return plaintext; - } - - @Override - public void encrypt(byte[] publicKey) throws IOException { - this.encrypted = new CryptoBox(plaintext, publicKey); - } - - public void encrypt() throws IOException { - encrypt(cryptography().createPublicKey(plaintext.getFrom().getPublicDecryptionKey())); - } - - @Override - public void decrypt(byte[] privateKey) throws IOException, DecryptionFailedException { - plaintext = Plaintext.read(BROADCAST, encrypted.decrypt(privateKey)); - } - - public void decrypt(BitmessageAddress address) throws IOException, DecryptionFailedException { - decrypt(address.getPublicDecryptionKey()); - } - - @Override - public boolean isDecrypted() { - return plaintext != null; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Broadcast broadcast = (Broadcast) o; - return stream == broadcast.stream && - (Objects.equals(encrypted, broadcast.encrypted) || Objects.equals(plaintext, broadcast.plaintext)); - } - - @Override - public int hashCode() { - return Objects.hash(stream); - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.kt b/core/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.kt new file mode 100644 index 0000000..9cc5772 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.kt @@ -0,0 +1,78 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.entity.payload + +import ch.dissem.bitmessage.entity.BitmessageAddress +import ch.dissem.bitmessage.entity.Encrypted +import ch.dissem.bitmessage.entity.Plaintext +import ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST +import ch.dissem.bitmessage.entity.PlaintextHolder +import ch.dissem.bitmessage.exception.DecryptionFailedException +import ch.dissem.bitmessage.utils.Singleton.cryptography +import java.util.* + +/** + * Users who are subscribed to the sending address will see the message appear in their inbox. + * Broadcasts are version 4 or 5. + */ +abstract class Broadcast protected constructor(version: Long, override val stream: Long, protected var encrypted: CryptoBox?, override var plaintext: Plaintext?) : ObjectPayload(version), Encrypted, PlaintextHolder { + + override val isSigned: Boolean = true + + override var signature: ByteArray? + get() = plaintext?.signature + set(signature) { + plaintext?.signature = signature ?: throw IllegalStateException("no plaintext data available") + } + + override fun encrypt(publicKey: ByteArray) { + this.encrypted = CryptoBox(plaintext ?: throw IllegalStateException("no plaintext data available"), publicKey) + } + + fun encrypt() { + encrypt(cryptography().createPublicKey(plaintext?.from?.publicDecryptionKey ?: return)) + } + + @Throws(DecryptionFailedException::class) + override fun decrypt(privateKey: ByteArray) { + plaintext = Plaintext.read(BROADCAST, encrypted?.decrypt(privateKey) ?: return) + } + + @Throws(DecryptionFailedException::class) + fun decrypt(address: BitmessageAddress) { + decrypt(address.publicDecryptionKey) + } + + override val isDecrypted: Boolean + get() = plaintext != null + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is Broadcast) return false + return stream == other.stream && (encrypted == other.encrypted || plaintext == other.plaintext) + } + + override fun hashCode(): Int { + return Objects.hash(stream) + } + + companion object { + fun getVersion(address: BitmessageAddress): Long { + return if (address.version < 4) 4L else 5L + } + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.java b/core/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.java deleted file mode 100644 index f7f3c15..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.java +++ /dev/null @@ -1,214 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.entity.payload; - -import ch.dissem.bitmessage.entity.Streamable; -import ch.dissem.bitmessage.exception.ApplicationException; -import ch.dissem.bitmessage.exception.DecryptionFailedException; -import ch.dissem.bitmessage.utils.*; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.*; -import java.nio.ByteBuffer; -import java.util.Arrays; - -import static ch.dissem.bitmessage.entity.valueobject.PrivateKey.PRIVATE_KEY_SIZE; -import static ch.dissem.bitmessage.utils.Singleton.cryptography; - - -public class CryptoBox implements Streamable { - private static final long serialVersionUID = 7217659539975573852L; - private static final Logger LOG = LoggerFactory.getLogger(CryptoBox.class); - - private final byte[] initializationVector; - private final int curveType; - private final byte[] R; - private final byte[] mac; - private byte[] encrypted; - - - public CryptoBox(Streamable data, byte[] K) throws IOException { - this(Encode.bytes(data), K); - } - - public CryptoBox(byte[] data, byte[] K) throws IOException { - curveType = 0x02CA; - - // 1. The destination public key is called K. - // 2. Generate 16 random bytes using a secure random number generator. Call them IV. - initializationVector = cryptography().randomBytes(16); - - // 3. Generate a new random EC key pair with private key called r and public key called R. - byte[] r = cryptography().randomBytes(PRIVATE_KEY_SIZE); - R = cryptography().createPublicKey(r); - // 4. Do an EC point multiply with public key K and private key r. This gives you public key P. - byte[] P = cryptography().multiply(K, r); - byte[] X = Points.getX(P); - // 5. Use the X component of public key P and calculate the SHA512 hash H. - byte[] H = cryptography().sha512(X); - // 6. The first 32 bytes of H are called key_e and the last 32 bytes are called key_m. - byte[] key_e = Arrays.copyOfRange(H, 0, 32); - byte[] key_m = Arrays.copyOfRange(H, 32, 64); - // 7. Pad the input text to a multiple of 16 bytes, in accordance to PKCS7. - // 8. Encrypt the data with AES-256-CBC, using IV as initialization vector, key_e as encryption key and the padded input text as payload. Call the output cipher text. - encrypted = cryptography().crypt(true, data, key_e, initializationVector); - // 9. Calculate a 32 byte MAC with HMACSHA256, using key_m as salt and IV + R + cipher text as data. Call the output MAC. - mac = calculateMac(key_m); - - // The resulting data is: IV + R + cipher text + MAC - } - - private CryptoBox(Builder builder) { - initializationVector = builder.initializationVector; - curveType = builder.curveType; - R = cryptography().createPoint(builder.xComponent, builder.yComponent); - encrypted = builder.encrypted; - mac = builder.mac; - } - - public static CryptoBox read(InputStream stream, int length) throws IOException { - AccessCounter counter = new AccessCounter(); - return new Builder() - .IV(Decode.bytes(stream, 16, counter)) - .curveType(Decode.uint16(stream, counter)) - .X(Decode.shortVarBytes(stream, counter)) - .Y(Decode.shortVarBytes(stream, counter)) - .encrypted(Decode.bytes(stream, length - counter.length() - 32)) - .MAC(Decode.bytes(stream, 32)) - .build(); - } - - /** - * @param k a private key, typically should be 32 bytes long - * @return an InputStream yielding the decrypted data - * @throws DecryptionFailedException if the payload can't be decrypted using this private key - * @see <a href='https://bitmessage.org/wiki/Encryption#Decryption'>https://bitmessage.org/wiki/Encryption#Decryption</a> - */ - public InputStream decrypt(byte[] k) throws DecryptionFailedException { - // 1. The private key used to decrypt is called k. - // 2. Do an EC point multiply with private key k and public key R. This gives you public key P. - byte[] P = cryptography().multiply(R, k); - // 3. Use the X component of public key P and calculate the SHA512 hash H. - byte[] H = cryptography().sha512(Arrays.copyOfRange(P, 1, 33)); - // 4. The first 32 bytes of H are called key_e and the last 32 bytes are called key_m. - byte[] key_e = Arrays.copyOfRange(H, 0, 32); - byte[] key_m = Arrays.copyOfRange(H, 32, 64); - - // 5. Calculate MAC' with HMACSHA256, using key_m as salt and IV + R + cipher text as data. - // 6. Compare MAC with MAC'. If not equal, decryption will fail. - if (!Arrays.equals(mac, calculateMac(key_m))) { - throw new DecryptionFailedException(); - } - - // 7. Decrypt the cipher text with AES-256-CBC, using IV as initialization vector, key_e as decryption key - // and the cipher text as payload. The output is the padded input text. - return new ByteArrayInputStream(cryptography().crypt(false, encrypted, key_e, initializationVector)); - } - - private byte[] calculateMac(byte[] key_m) { - try { - ByteArrayOutputStream macData = new ByteArrayOutputStream(); - writeWithoutMAC(macData); - return cryptography().mac(key_m, macData.toByteArray()); - } catch (IOException e) { - throw new ApplicationException(e); - } - } - - private void writeWithoutMAC(OutputStream out) throws IOException { - out.write(initializationVector); - Encode.int16(curveType, out); - writeCoordinateComponent(out, Points.getX(R)); - writeCoordinateComponent(out, Points.getY(R)); - out.write(encrypted); - } - - private void writeCoordinateComponent(OutputStream out, byte[] x) throws IOException { - int offset = Bytes.numberOfLeadingZeros(x); - int length = x.length - offset; - Encode.int16(length, out); - out.write(x, offset, length); - } - - private void writeCoordinateComponent(ByteBuffer buffer, byte[] x) { - int offset = Bytes.numberOfLeadingZeros(x); - int length = x.length - offset; - Encode.int16(length, buffer); - buffer.put(x, offset, length); - } - - @Override - public void write(OutputStream stream) throws IOException { - writeWithoutMAC(stream); - stream.write(mac); - } - - @Override - public void write(ByteBuffer buffer) { - buffer.put(initializationVector); - Encode.int16(curveType, buffer); - writeCoordinateComponent(buffer, Points.getX(R)); - writeCoordinateComponent(buffer, Points.getY(R)); - buffer.put(encrypted); - buffer.put(mac); - } - - public static final class Builder { - private byte[] initializationVector; - private int curveType; - private byte[] xComponent; - private byte[] yComponent; - private byte[] encrypted; - private byte[] mac; - - public Builder IV(byte[] initializationVector) { - this.initializationVector = initializationVector; - return this; - } - - public Builder curveType(int curveType) { - if (curveType != 0x2CA) LOG.trace("Unexpected curve type " + curveType); - this.curveType = curveType; - return this; - } - - public Builder X(byte[] xComponent) { - this.xComponent = xComponent; - return this; - } - - public Builder Y(byte[] yComponent) { - this.yComponent = yComponent; - return this; - } - - private Builder encrypted(byte[] encrypted) { - this.encrypted = encrypted; - return this; - } - - public Builder MAC(byte[] mac) { - this.mac = mac; - return this; - } - - public CryptoBox build() { - return new CryptoBox(this); - } - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.kt b/core/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.kt new file mode 100644 index 0000000..15f8bef --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.kt @@ -0,0 +1,210 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.entity.payload + +import ch.dissem.bitmessage.entity.Streamable +import ch.dissem.bitmessage.entity.valueobject.PrivateKey.Companion.PRIVATE_KEY_SIZE +import ch.dissem.bitmessage.exception.DecryptionFailedException +import ch.dissem.bitmessage.utils.* +import ch.dissem.bitmessage.utils.Singleton.cryptography +import org.slf4j.LoggerFactory +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.io.InputStream +import java.io.OutputStream +import java.nio.ByteBuffer +import java.util.* + + +class CryptoBox : Streamable { + + private val initializationVector: ByteArray + private val curveType: Int + private val R: ByteArray + private val mac: ByteArray + private var encrypted: ByteArray + + constructor(data: Streamable, K: ByteArray) : this(Encode.bytes(data), K) + + constructor(data: ByteArray, K: ByteArray) { + curveType = 0x02CA + + // 1. The destination public key is called K. + // 2. Generate 16 random bytes using a secure random number generator. Call them IV. + initializationVector = cryptography().randomBytes(16) + + // 3. Generate a new random EC key pair with private key called r and public key called R. + val r = cryptography().randomBytes(PRIVATE_KEY_SIZE) + R = cryptography().createPublicKey(r) + // 4. Do an EC point multiply with public key K and private key r. This gives you public key P. + val P = cryptography().multiply(K, r) + val X = Points.getX(P) + // 5. Use the X component of public key P and calculate the SHA512 hash H. + val H = cryptography().sha512(X) + // 6. The first 32 bytes of H are called key_e and the last 32 bytes are called key_m. + val key_e = Arrays.copyOfRange(H, 0, 32) + val key_m = Arrays.copyOfRange(H, 32, 64) + // 7. Pad the input text to a multiple of 16 bytes, in accordance to PKCS7. + // 8. Encrypt the data with AES-256-CBC, using IV as initialization vector, key_e as encryption key and the padded input text as payload. Call the output cipher text. + encrypted = cryptography().crypt(true, data, key_e, initializationVector) + // 9. Calculate a 32 byte MAC with HMACSHA256, using key_m as salt and IV + R + cipher text as data. Call the output MAC. + mac = calculateMac(key_m) + + // The resulting data is: IV + R + cipher text + MAC + } + + private constructor(builder: Builder) { + initializationVector = builder.initializationVector!! + curveType = builder.curveType + R = cryptography().createPoint(builder.xComponent!!, builder.yComponent!!) + encrypted = builder.encrypted!! + mac = builder.mac!! + } + + /** + * @param k a private key, typically should be 32 bytes long + * * + * @return an InputStream yielding the decrypted data + * * + * @throws DecryptionFailedException if the payload can't be decrypted using this private key + * * + * @see [https://bitmessage.org/wiki/Encryption.Decryption](https://bitmessage.org/wiki/Encryption.Decryption) + */ + @Throws(DecryptionFailedException::class) + fun decrypt(k: ByteArray): InputStream { + // 1. The private key used to decrypt is called k. + // 2. Do an EC point multiply with private key k and public key R. This gives you public key P. + val P = cryptography().multiply(R, k) + // 3. Use the X component of public key P and calculate the SHA512 hash H. + val H = cryptography().sha512(Arrays.copyOfRange(P, 1, 33)) + // 4. The first 32 bytes of H are called key_e and the last 32 bytes are called key_m. + val key_e = Arrays.copyOfRange(H, 0, 32) + val key_m = Arrays.copyOfRange(H, 32, 64) + + // 5. Calculate MAC' with HMACSHA256, using key_m as salt and IV + R + cipher text as data. + // 6. Compare MAC with MAC'. If not equal, decryption will fail. + if (!Arrays.equals(mac, calculateMac(key_m))) { + throw DecryptionFailedException() + } + + // 7. Decrypt the cipher text with AES-256-CBC, using IV as initialization vector, key_e as decryption key + // and the cipher text as payload. The output is the padded input text. + return ByteArrayInputStream(cryptography().crypt(false, encrypted, key_e, initializationVector)) + } + + private fun calculateMac(key_m: ByteArray): ByteArray { + val macData = ByteArrayOutputStream() + writeWithoutMAC(macData) + return cryptography().mac(key_m, macData.toByteArray()) + } + + private fun writeWithoutMAC(out: OutputStream) { + out.write(initializationVector) + Encode.int16(curveType.toLong(), out) + writeCoordinateComponent(out, Points.getX(R)) + writeCoordinateComponent(out, Points.getY(R)) + out.write(encrypted) + } + + private fun writeCoordinateComponent(out: OutputStream, x: ByteArray) { + val offset = Bytes.numberOfLeadingZeros(x) + val length = x.size - offset + Encode.int16(length.toLong(), out) + out.write(x, offset, length) + } + + private fun writeCoordinateComponent(buffer: ByteBuffer, x: ByteArray) { + val offset = Bytes.numberOfLeadingZeros(x) + val length = x.size - offset + Encode.int16(length.toLong(), buffer) + buffer.put(x, offset, length) + } + + override fun write(out: OutputStream) { + writeWithoutMAC(out) + out.write(mac) + } + + override fun write(buffer: ByteBuffer) { + buffer.put(initializationVector) + Encode.int16(curveType.toLong(), buffer) + writeCoordinateComponent(buffer, Points.getX(R)) + writeCoordinateComponent(buffer, Points.getY(R)) + buffer.put(encrypted) + buffer.put(mac) + } + + class Builder { + internal var initializationVector: ByteArray? = null + internal var curveType: Int = 0 + internal var xComponent: ByteArray? = null + internal var yComponent: ByteArray? = null + internal var encrypted: ByteArray? = null + internal var mac: ByteArray? = null + + fun IV(initializationVector: ByteArray): Builder { + this.initializationVector = initializationVector + return this + } + + fun curveType(curveType: Int): Builder { + if (curveType != 0x2CA) LOG.trace("Unexpected curve type " + curveType) + this.curveType = curveType + return this + } + + fun X(xComponent: ByteArray): Builder { + this.xComponent = xComponent + return this + } + + fun Y(yComponent: ByteArray): Builder { + this.yComponent = yComponent + return this + } + + fun encrypted(encrypted: ByteArray): Builder { + this.encrypted = encrypted + return this + } + + fun MAC(mac: ByteArray): Builder { + this.mac = mac + return this + } + + fun build(): CryptoBox { + return CryptoBox(this) + } + } + + companion object { + private val LOG = LoggerFactory.getLogger(CryptoBox::class.java) + + fun read(stream: InputStream, length: Int): CryptoBox { + val counter = AccessCounter() + return Builder() + .IV(Decode.bytes(stream, 16, counter)) + .curveType(Decode.uint16(stream, counter)) + .X(Decode.shortVarBytes(stream, counter)) + .Y(Decode.shortVarBytes(stream, counter)) + .encrypted(Decode.bytes(stream, length - counter.length() - 32)) + .MAC(Decode.bytes(stream, 32)) + .build() + } + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/GenericPayload.java b/core/src/main/java/ch/dissem/bitmessage/entity/payload/GenericPayload.java deleted file mode 100644 index 9312160..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/GenericPayload.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.entity.payload; - -import ch.dissem.bitmessage.utils.Decode; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.util.Arrays; - -/** - * In cases we don't know what to do with an object, we just store its bytes and send it again - we don't really - * have to know what it is. - */ -public class GenericPayload extends ObjectPayload { - private static final long serialVersionUID = -912314085064185940L; - - private long stream; - private byte[] data; - - public GenericPayload(long version, long stream, byte[] data) { - super(version); - this.stream = stream; - this.data = data; - } - - public static GenericPayload read(long version, long stream, InputStream is, int length) throws IOException { - return new GenericPayload(version, stream, Decode.bytes(is, length)); - } - - @Override - public ObjectType getType() { - return null; - } - - @Override - public long getStream() { - return stream; - } - - public byte[] getData() { - return data; - } - - @Override - public void write(OutputStream stream) throws IOException { - stream.write(data); - } - - @Override - public void write(ByteBuffer buffer) { - buffer.put(data); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - GenericPayload that = (GenericPayload) o; - - if (stream != that.stream) return false; - return Arrays.equals(data, that.data); - } - - @Override - public int hashCode() { - int result = (int) (stream ^ (stream >>> 32)); - result = 31 * result + Arrays.hashCode(data); - return result; - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/GenericPayload.kt b/core/src/main/java/ch/dissem/bitmessage/entity/payload/GenericPayload.kt new file mode 100644 index 0000000..336630a --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/GenericPayload.kt @@ -0,0 +1,60 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.entity.payload + +import ch.dissem.bitmessage.utils.Decode +import java.io.InputStream +import java.io.OutputStream +import java.nio.ByteBuffer +import java.util.* + +/** + * In cases we don't know what to do with an object, we just store its bytes and send it again - we don't really + * have to know what it is. + */ +class GenericPayload(version: Long, override val stream: Long, val data: ByteArray) : ObjectPayload(version) { + + override val type: ObjectType? = null + + override fun write(out: OutputStream) { + out.write(data) + } + + override fun write(buffer: ByteBuffer) { + buffer.put(data) + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is GenericPayload) return false + + if (stream != other.stream) return false + return Arrays.equals(data, other.data) + } + + override fun hashCode(): Int { + var result = (stream xor stream.ushr(32)).toInt() + result = 31 * result + Arrays.hashCode(data) + return result + } + + companion object { + fun read(version: Long, stream: Long, `is`: InputStream, length: Int): GenericPayload { + return GenericPayload(version, stream, Decode.bytes(`is`, length)) + } + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/GetPubkey.java b/core/src/main/java/ch/dissem/bitmessage/entity/payload/GetPubkey.java deleted file mode 100644 index d889489..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/GetPubkey.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.entity.payload; - -import ch.dissem.bitmessage.entity.BitmessageAddress; -import ch.dissem.bitmessage.utils.Decode; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.ByteBuffer; - -/** - * Request for a public key. - */ -public class GetPubkey extends ObjectPayload { - private static final long serialVersionUID = -3634516646972610180L; - - private long stream; - private byte[] ripeTag; - - public GetPubkey(BitmessageAddress address) { - super(address.getVersion()); - this.stream = address.getStream(); - if (address.getVersion() < 4) - this.ripeTag = address.getRipe(); - else - this.ripeTag = address.getTag(); - } - - private GetPubkey(long version, long stream, byte[] ripeOrTag) { - super(version); - this.stream = stream; - this.ripeTag = ripeOrTag; - } - - public static GetPubkey read(InputStream is, long stream, int length, long version) throws IOException { - return new GetPubkey(version, stream, Decode.bytes(is, length)); - } - - /** - * @return an array of bytes that represent either the ripe, or the tag of an address, depending on the - * address version. - */ - public byte[] getRipeTag() { - return ripeTag; - } - - @Override - public ObjectType getType() { - return ObjectType.GET_PUBKEY; - } - - @Override - public long getStream() { - return stream; - } - - @Override - public void write(OutputStream stream) throws IOException { - stream.write(ripeTag); - } - - @Override - public void write(ByteBuffer buffer) { - buffer.put(ripeTag); - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/GetPubkey.kt b/core/src/main/java/ch/dissem/bitmessage/entity/payload/GetPubkey.kt new file mode 100644 index 0000000..3ae9370 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/GetPubkey.kt @@ -0,0 +1,65 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.entity.payload + +import ch.dissem.bitmessage.entity.BitmessageAddress +import ch.dissem.bitmessage.utils.Decode +import java.io.InputStream +import java.io.OutputStream +import java.nio.ByteBuffer + +/** + * Request for a public key. + */ +class GetPubkey : ObjectPayload { + + override val type: ObjectType = ObjectType.GET_PUBKEY + + override var stream: Long = 0 + private set + + /** + * @return an array of bytes that represent either the ripe, or the tag of an address, depending on the + * * address version. + */ + val ripeTag: ByteArray + + constructor(address: BitmessageAddress) : super(address.version) { + this.stream = address.stream + this.ripeTag = if (address.version < 4) address.ripe else + address.tag ?: throw IllegalStateException("Address of version 4 without tag shouldn't exist!") + } + + private constructor(version: Long, stream: Long, ripeOrTag: ByteArray) : super(version) { + this.stream = stream + this.ripeTag = ripeOrTag + } + + override fun write(out: OutputStream) { + out.write(ripeTag) + } + + override fun write(buffer: ByteBuffer) { + buffer.put(ripeTag) + } + + companion object { + fun read(`is`: InputStream, stream: Long, length: Int, version: Long): GetPubkey { + return GetPubkey(version, stream, Decode.bytes(`is`, length)) + } + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/Msg.java b/core/src/main/java/ch/dissem/bitmessage/entity/payload/Msg.java deleted file mode 100644 index dc36bb1..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/Msg.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.entity.payload; - -import ch.dissem.bitmessage.entity.Encrypted; -import ch.dissem.bitmessage.entity.Plaintext; -import ch.dissem.bitmessage.entity.PlaintextHolder; -import ch.dissem.bitmessage.exception.DecryptionFailedException; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.util.Objects; - -import static ch.dissem.bitmessage.entity.Plaintext.Type.MSG; - -/** - * Used for person-to-person messages. - */ -public class Msg extends ObjectPayload implements Encrypted, PlaintextHolder { - private static final long serialVersionUID = 4327495048296365733L; - public static final int ACK_LENGTH = 32; - - private long stream; - private CryptoBox encrypted; - private Plaintext plaintext; - - private Msg(long stream, CryptoBox encrypted) { - super(1); - this.stream = stream; - this.encrypted = encrypted; - } - - public Msg(Plaintext plaintext) { - super(1); - this.stream = plaintext.getStream(); - this.plaintext = plaintext; - } - - public static Msg read(InputStream in, long stream, int length) throws IOException { - return new Msg(stream, CryptoBox.read(in, length)); - } - - @Override - public Plaintext getPlaintext() { - return plaintext; - } - - @Override - public ObjectType getType() { - return ObjectType.MSG; - } - - @Override - public long getStream() { - return stream; - } - - @Override - public boolean isSigned() { - return true; - } - - @Override - public void writeBytesToSign(OutputStream out) throws IOException { - plaintext.write(out, false); - } - - @Override - public byte[] getSignature() { - return plaintext.getSignature(); - } - - @Override - public void setSignature(byte[] signature) { - plaintext.setSignature(signature); - } - - @Override - public void encrypt(byte[] publicKey) throws IOException { - this.encrypted = new CryptoBox(plaintext, publicKey); - } - - @Override - public void decrypt(byte[] privateKey) throws IOException, DecryptionFailedException { - plaintext = Plaintext.read(MSG, encrypted.decrypt(privateKey)); - } - - @Override - public boolean isDecrypted() { - return plaintext != null; - } - - @Override - public void write(OutputStream out) throws IOException { - if (encrypted == null) throw new IllegalStateException("Msg must be signed and encrypted before writing it."); - encrypted.write(out); - } - - @Override - public void write(ByteBuffer buffer) { - if (encrypted == null) throw new IllegalStateException("Msg must be signed and encrypted before writing it."); - encrypted.write(buffer); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - if (!super.equals(o)) return false; - Msg msg = (Msg) o; - return stream == msg.stream && - (Objects.equals(encrypted, msg.encrypted) || Objects.equals(plaintext, msg.plaintext)); - } - - @Override - public int hashCode() { - return (int) stream; - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/Msg.kt b/core/src/main/java/ch/dissem/bitmessage/entity/payload/Msg.kt new file mode 100644 index 0000000..597220e --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/Msg.kt @@ -0,0 +1,103 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.entity.payload + +import ch.dissem.bitmessage.entity.Encrypted +import ch.dissem.bitmessage.entity.Plaintext +import ch.dissem.bitmessage.entity.Plaintext.Type.MSG +import ch.dissem.bitmessage.entity.PlaintextHolder +import ch.dissem.bitmessage.exception.DecryptionFailedException +import java.io.InputStream +import java.io.OutputStream +import java.nio.ByteBuffer + +/** + * Used for person-to-person messages. + */ +class Msg : ObjectPayload, Encrypted, PlaintextHolder { + + override val stream: Long + private var encrypted: CryptoBox? + override var plaintext: Plaintext? + private set + + private constructor(stream: Long, encrypted: CryptoBox) : super(1) { + this.stream = stream + this.encrypted = encrypted + this.plaintext = null + } + + constructor(plaintext: Plaintext) : super(1) { + this.stream = plaintext.stream + this.encrypted = null + this.plaintext = plaintext + } + + override val type: ObjectType = ObjectType.MSG + + override val isSigned: Boolean = true + + override fun writeBytesToSign(out: OutputStream) { + plaintext?.write(out, false) ?: throw IllegalStateException("no plaintext data available") + } + + override var signature: ByteArray? + get() = plaintext?.signature + set(signature) { + plaintext?.signature = signature ?: throw IllegalStateException("no plaintext data available") + } + + override fun encrypt(publicKey: ByteArray) { + this.encrypted = CryptoBox(plaintext ?: throw IllegalStateException("no plaintext data available"), publicKey) + } + + @Throws(DecryptionFailedException::class) + override fun decrypt(privateKey: ByteArray) { + plaintext = Plaintext.read(MSG, encrypted!!.decrypt(privateKey)) + } + + override val isDecrypted: Boolean + get() = plaintext != null + + override fun write(out: OutputStream) { + encrypted?.write(out) ?: throw IllegalStateException("Msg must be signed and encrypted before writing it.") + } + + override fun write(buffer: ByteBuffer) { + encrypted?.write(buffer) ?: throw IllegalStateException("Msg must be signed and encrypted before writing it.") + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is Msg) return false + if (!super.equals(other)) return false + + return stream == other.stream && (encrypted == other.encrypted || plaintext == other.plaintext) + } + + override fun hashCode(): Int { + return stream.toInt() + } + + companion object { + val ACK_LENGTH = 32 + + fun read(`in`: InputStream, stream: Long, length: Int): Msg { + return Msg(stream, CryptoBox.read(`in`, length)) + } + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectPayload.java b/core/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectPayload.java deleted file mode 100644 index 33da28d..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectPayload.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.entity.payload; - -import ch.dissem.bitmessage.entity.ObjectMessage; -import ch.dissem.bitmessage.entity.Streamable; - -import java.io.IOException; -import java.io.OutputStream; - -/** - * The payload of an 'object' command. This is shared by the network. - */ -public abstract class ObjectPayload implements Streamable { - private static final long serialVersionUID = -5034977402902364482L; - - private final long version; - - protected ObjectPayload(long version) { - this.version = version; - } - - - public abstract ObjectType getType(); - - public abstract long getStream(); - - public long getVersion() { - return version; - } - - public boolean isSigned() { - return false; - } - - public void writeBytesToSign(OutputStream out) throws IOException { - // nothing to do - } - - /** - * @return the ECDSA signature which, as of protocol v3, covers the object header starting with the time, - * appended with the data described in this table down to the extra_bytes. Therefore, this must - * be checked and set in the {@link ObjectMessage} object. - */ - public byte[] getSignature() { - return null; - } - - public void setSignature(byte[] signature) { - // nothing to do - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectPayload.kt b/core/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectPayload.kt new file mode 100644 index 0000000..5650652 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectPayload.kt @@ -0,0 +1,44 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.entity.payload + +import ch.dissem.bitmessage.entity.ObjectMessage +import ch.dissem.bitmessage.entity.Streamable +import java.io.OutputStream + +/** + * The payload of an 'object' command. This is shared by the network. + */ +abstract class ObjectPayload protected constructor(val version: Long) : Streamable { + + abstract val type: ObjectType? + + abstract val stream: Long + + open val isSigned: Boolean = false + + open fun writeBytesToSign(out: OutputStream) { + // nothing to do + } + + /** + * @return the ECDSA signature which, as of protocol v3, covers the object header starting with the time, + * * appended with the data described in this table down to the extra_bytes. Therefore, this must + * * be checked and set in the [ObjectMessage] object. + */ + open var signature: ByteArray? = null +} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectType.java b/core/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectType.kt similarity index 64% rename from core/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectType.java rename to core/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectType.kt index 06ea92f..967a3fd 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectType.java +++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectType.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015 Christian Basler + * Copyright 2017 Christian Basler * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,31 +14,20 @@ * limitations under the License. */ -package ch.dissem.bitmessage.entity.payload; +package ch.dissem.bitmessage.entity.payload /** * Known types for 'object' messages. Must not be used where an unknown type must be resent. */ -public enum ObjectType { +enum class ObjectType constructor(val number: Long) { GET_PUBKEY(0), PUBKEY(1), MSG(2), BROADCAST(3); - int number; - - ObjectType(int number) { - this.number = number; - } - - public static ObjectType fromNumber(long number) { - for (ObjectType type : values()) { - if (type.number == number) return type; + companion object { + fun fromNumber(number: Long): ObjectType? { + return values().firstOrNull { it.number == number } } - return null; - } - - public long getNumber() { - return number; } } diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/Pubkey.java b/core/src/main/java/ch/dissem/bitmessage/entity/payload/Pubkey.java deleted file mode 100644 index 27d2da9..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/Pubkey.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.entity.payload; - -import java.io.IOException; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.util.ArrayList; - -import static ch.dissem.bitmessage.utils.Singleton.cryptography; - -/** - * Public keys for signing and encryption, the answer to a 'getpubkey' request. - */ -public abstract class Pubkey extends ObjectPayload { - private static final long serialVersionUID = -6634533361454999619L; - - public final static long LATEST_VERSION = 4; - - protected Pubkey(long version) { - super(version); - } - - public static byte[] getRipe(byte[] publicSigningKey, byte[] publicEncryptionKey) { - return cryptography().ripemd160(cryptography().sha512(publicSigningKey, publicEncryptionKey)); - } - - public abstract byte[] getSigningKey(); - - public abstract byte[] getEncryptionKey(); - - public abstract int getBehaviorBitfield(); - - public byte[] getRipe() { - return cryptography().ripemd160(cryptography().sha512(getSigningKey(), getEncryptionKey())); - } - - public long getNonceTrialsPerByte() { - return 0; - } - - public long getExtraBytes() { - return 0; - } - - public void writeUnencrypted(OutputStream out) throws IOException { - write(out); - } - - public void writeUnencrypted(ByteBuffer buffer){ - write(buffer); - } - - protected byte[] add0x04(byte[] key) { - if (key.length == 65) return key; - byte[] result = new byte[65]; - result[0] = 4; - System.arraycopy(key, 0, result, 1, 64); - return result; - } - - /** - * Bits 0 through 29 are yet undefined - */ - public enum Feature { - /** - * Receiving node expects that the RIPE hash encoded in their address preceedes the encrypted message data of msg - * messages bound for them. - */ - INCLUDE_DESTINATION(30), - /** - * If true, the receiving node does send acknowledgements (rather than dropping them). - */ - DOES_ACK(31); - - private int bit; - - Feature(int bitNumber) { - // The Bitmessage Protocol Specification starts counting at the most significant bit, - // thus the slightly awkward calculation. - // https://bitmessage.org/wiki/Protocol_specification#Pubkey_bitfield_features - this.bit = 1 << (31 - bitNumber); - } - - public static int bitfield(Feature... features) { - int bits = 0; - for (Feature feature : features) { - bits |= feature.bit; - } - return bits; - } - - public static Feature[] features(int bitfield) { - ArrayList<Feature> features = new ArrayList<>(Feature.values().length); - for (Feature feature : Feature.values()) { - if ((bitfield & feature.bit) != 0) { - features.add(feature); - } - } - return features.toArray(new Feature[features.size()]); - } - - public boolean isActive(int bitfield) { - return (bitfield & bit) != 0; - } - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/Pubkey.kt b/core/src/main/java/ch/dissem/bitmessage/entity/payload/Pubkey.kt new file mode 100644 index 0000000..87e1355 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/Pubkey.kt @@ -0,0 +1,112 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.entity.payload + +import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_EXTRA_BYTES +import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_NONCE_TRIALS_PER_BYTE +import ch.dissem.bitmessage.utils.Singleton.cryptography +import java.io.OutputStream +import java.nio.ByteBuffer +import java.util.* + +/** + * Public keys for signing and encryption, the answer to a 'getpubkey' request. + */ +abstract class Pubkey protected constructor(version: Long) : ObjectPayload(version) { + + override val type: ObjectType = ObjectType.PUBKEY + + abstract val signingKey: ByteArray + + abstract val encryptionKey: ByteArray + + abstract val behaviorBitfield: Int + + val ripe: ByteArray by lazy { cryptography().ripemd160(cryptography().sha512(signingKey, encryptionKey)) } + + open val nonceTrialsPerByte: Long = NETWORK_NONCE_TRIALS_PER_BYTE + + open val extraBytes: Long = NETWORK_EXTRA_BYTES + + open fun writeUnencrypted(out: OutputStream) { + write(out) + } + + open fun writeUnencrypted(buffer: ByteBuffer) { + write(buffer) + } + + /** + * Bits 0 through 29 are yet undefined + */ + enum class Feature constructor(bitNumber: Int) { + /** + * Receiving node expects that the RIPE hash encoded in their address preceedes the encrypted message data of msg + * messages bound for them. + */ + INCLUDE_DESTINATION(30), + /** + * If true, the receiving node does send acknowledgements (rather than dropping them). + */ + DOES_ACK(31); + + // The Bitmessage Protocol Specification starts counting at the most significant bit, + // thus the slightly awkward calculation. + // https://bitmessage.org/wiki/Protocol_specification#Pubkey_bitfield_features + private val bit: Int = 1 shl 31 - bitNumber + + fun isActive(bitfield: Int): Boolean { + return bitfield and bit != 0 + } + + companion object { + @JvmStatic fun bitfield(vararg features: Feature): Int { + var bits = 0 + for (feature in features) { + bits = bits or feature.bit + } + return bits + } + + @JvmStatic fun features(bitfield: Int): Array<Feature> { + val features = ArrayList<Feature>(Feature.values().size) + for (feature in Feature.values()) { + if (bitfield and feature.bit != 0) { + features.add(feature) + } + } + return features.toTypedArray() + } + } + } + + companion object { + @JvmField val LATEST_VERSION: Long = 4 + + fun getRipe(publicSigningKey: ByteArray, publicEncryptionKey: ByteArray): ByteArray { + return cryptography().ripemd160(cryptography().sha512(publicSigningKey, publicEncryptionKey)) + } + + fun add0x04(key: ByteArray): ByteArray { + if (key.size == 65) return key + val result = ByteArray(65) + result[0] = 4 + System.arraycopy(key, 0, result, 1, 64) + return result + } + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V2Pubkey.java b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V2Pubkey.java deleted file mode 100644 index d2901c1..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V2Pubkey.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.entity.payload; - -import ch.dissem.bitmessage.utils.Decode; -import ch.dissem.bitmessage.utils.Encode; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.ByteBuffer; - -/** - * A version 2 public key. - */ -public class V2Pubkey extends Pubkey { - private static final long serialVersionUID = -257598690676510460L; - - protected long stream; - protected int behaviorBitfield; - protected byte[] publicSigningKey; // 64 Bytes - protected byte[] publicEncryptionKey; // 64 Bytes - - protected V2Pubkey(long version) { - super(version); - } - - private V2Pubkey(long version, Builder builder) { - super(version); - stream = builder.streamNumber; - behaviorBitfield = builder.behaviorBitfield; - publicSigningKey = add0x04(builder.publicSigningKey); - publicEncryptionKey = add0x04(builder.publicEncryptionKey); - } - - public static V2Pubkey read(InputStream is, long stream) throws IOException { - return new V2Pubkey.Builder() - .stream(stream) - .behaviorBitfield((int) Decode.uint32(is)) - .publicSigningKey(Decode.bytes(is, 64)) - .publicEncryptionKey(Decode.bytes(is, 64)) - .build(); - } - - @Override - public long getVersion() { - return 2; - } - - @Override - public ObjectType getType() { - return ObjectType.PUBKEY; - } - - @Override - public long getStream() { - return stream; - } - - @Override - public byte[] getSigningKey() { - return publicSigningKey; - } - - @Override - public byte[] getEncryptionKey() { - return publicEncryptionKey; - } - - @Override - public int getBehaviorBitfield() { - return behaviorBitfield; - } - - @Override - public void write(OutputStream out) throws IOException { - Encode.int32(behaviorBitfield, out); - out.write(publicSigningKey, 1, 64); - out.write(publicEncryptionKey, 1, 64); - } - - @Override - public void write(ByteBuffer buffer) { - Encode.int32(behaviorBitfield, buffer); - buffer.put(publicSigningKey, 1, 64); - buffer.put(publicEncryptionKey, 1, 64); - } - - public static class Builder { - private long streamNumber; - private int behaviorBitfield; - private byte[] publicSigningKey; - private byte[] publicEncryptionKey; - - public Builder stream(long streamNumber) { - this.streamNumber = streamNumber; - return this; - } - - public Builder behaviorBitfield(int behaviorBitfield) { - this.behaviorBitfield = behaviorBitfield; - return this; - } - - public Builder publicSigningKey(byte[] publicSigningKey) { - this.publicSigningKey = publicSigningKey; - return this; - } - - public Builder publicEncryptionKey(byte[] publicEncryptionKey) { - this.publicEncryptionKey = publicEncryptionKey; - return this; - } - - public V2Pubkey build() { - return new V2Pubkey(2, this); - } - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V2Pubkey.kt b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V2Pubkey.kt new file mode 100644 index 0000000..f599153 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V2Pubkey.kt @@ -0,0 +1,93 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.entity.payload + +import ch.dissem.bitmessage.utils.Decode +import ch.dissem.bitmessage.utils.Encode +import java.io.InputStream +import java.io.OutputStream +import java.nio.ByteBuffer + +/** + * A version 2 public key. + */ +open class V2Pubkey constructor(version: Long, override val stream: Long, override val behaviorBitfield: Int, signingKey: ByteArray, encryptionKey: ByteArray) : Pubkey(version) { + + override val signingKey: ByteArray = if (signingKey.size == 64) add0x04(signingKey) else signingKey + override val encryptionKey: ByteArray = if (encryptionKey.size == 64) add0x04(encryptionKey) else encryptionKey + + override fun write(out: OutputStream) { + Encode.int32(behaviorBitfield.toLong(), out) + out.write(signingKey, 1, 64) + out.write(encryptionKey, 1, 64) + } + + override fun write(buffer: ByteBuffer) { + Encode.int32(behaviorBitfield.toLong(), buffer) + buffer.put(signingKey, 1, 64) + buffer.put(encryptionKey, 1, 64) + } + + class Builder { + internal var streamNumber: Long = 0 + internal var behaviorBitfield: Int = 0 + internal var publicSigningKey: ByteArray? = null + internal var publicEncryptionKey: ByteArray? = null + + fun stream(streamNumber: Long): Builder { + this.streamNumber = streamNumber + return this + } + + fun behaviorBitfield(behaviorBitfield: Int): Builder { + this.behaviorBitfield = behaviorBitfield + return this + } + + fun publicSigningKey(publicSigningKey: ByteArray): Builder { + this.publicSigningKey = publicSigningKey + return this + } + + fun publicEncryptionKey(publicEncryptionKey: ByteArray): Builder { + this.publicEncryptionKey = publicEncryptionKey + return this + } + + fun build(): V2Pubkey { + return V2Pubkey( + version = 2, + stream = streamNumber, + behaviorBitfield = behaviorBitfield, + signingKey = add0x04(publicSigningKey!!), + encryptionKey = add0x04(publicEncryptionKey!!) + ) + } + } + + companion object { + fun read(`in`: InputStream, stream: Long): V2Pubkey { + return V2Pubkey( + version = 2, + stream = stream, + behaviorBitfield = Decode.uint32(`in`).toInt(), + signingKey = Decode.bytes(`in`, 64), + encryptionKey = Decode.bytes(`in`, 64) + ) + } + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V3Pubkey.java b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V3Pubkey.java deleted file mode 100644 index 5260060..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V3Pubkey.java +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.entity.payload; - -import ch.dissem.bitmessage.utils.Decode; -import ch.dissem.bitmessage.utils.Encode; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.util.Arrays; -import java.util.Objects; - -/** - * A version 3 public key. - */ -public class V3Pubkey extends V2Pubkey { - private static final long serialVersionUID = 6958853116648528319L; - - long nonceTrialsPerByte; - long extraBytes; - byte[] signature; - - protected V3Pubkey(long version, Builder builder) { - super(version); - stream = builder.streamNumber; - behaviorBitfield = builder.behaviorBitfield; - publicSigningKey = add0x04(builder.publicSigningKey); - publicEncryptionKey = add0x04(builder.publicEncryptionKey); - nonceTrialsPerByte = builder.nonceTrialsPerByte; - extraBytes = builder.extraBytes; - signature = builder.signature; - } - - public static V3Pubkey read(InputStream is, long stream) throws IOException { - return new V3Pubkey.Builder() - .stream(stream) - .behaviorBitfield(Decode.int32(is)) - .publicSigningKey(Decode.bytes(is, 64)) - .publicEncryptionKey(Decode.bytes(is, 64)) - .nonceTrialsPerByte(Decode.varInt(is)) - .extraBytes(Decode.varInt(is)) - .signature(Decode.varBytes(is)) - .build(); - } - - @Override - public void write(OutputStream out) throws IOException { - writeBytesToSign(out); - Encode.varBytes(signature, out); - } - - @Override - public void write(ByteBuffer buffer) { - super.write(buffer); - Encode.varInt(nonceTrialsPerByte, buffer); - Encode.varInt(extraBytes, buffer); - Encode.varBytes(signature, buffer); - } - - @Override - public long getVersion() { - return 3; - } - - public long getNonceTrialsPerByte() { - return nonceTrialsPerByte; - } - - public long getExtraBytes() { - return extraBytes; - } - - public boolean isSigned() { - return true; - } - - public void writeBytesToSign(OutputStream out) throws IOException { - super.write(out); - Encode.varInt(nonceTrialsPerByte, out); - Encode.varInt(extraBytes, out); - } - - @Override - public byte[] getSignature() { - return signature; - } - - @Override - public void setSignature(byte[] signature) { - this.signature = signature; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - V3Pubkey pubkey = (V3Pubkey) o; - return Objects.equals(nonceTrialsPerByte, pubkey.nonceTrialsPerByte) && - Objects.equals(extraBytes, pubkey.extraBytes) && - stream == pubkey.stream && - behaviorBitfield == pubkey.behaviorBitfield && - Arrays.equals(publicSigningKey, pubkey.publicSigningKey) && - Arrays.equals(publicEncryptionKey, pubkey.publicEncryptionKey); - } - - @Override - public int hashCode() { - return Objects.hash(nonceTrialsPerByte, extraBytes); - } - - public static class Builder { - private long streamNumber; - private int behaviorBitfield; - private byte[] publicSigningKey; - private byte[] publicEncryptionKey; - private long nonceTrialsPerByte; - private long extraBytes; - private byte[] signature = new byte[0]; - - public Builder stream(long streamNumber) { - this.streamNumber = streamNumber; - return this; - } - - public Builder behaviorBitfield(int behaviorBitfield) { - this.behaviorBitfield = behaviorBitfield; - return this; - } - - public Builder publicSigningKey(byte[] publicSigningKey) { - this.publicSigningKey = publicSigningKey; - return this; - } - - public Builder publicEncryptionKey(byte[] publicEncryptionKey) { - this.publicEncryptionKey = publicEncryptionKey; - return this; - } - - public Builder nonceTrialsPerByte(long nonceTrialsPerByte) { - this.nonceTrialsPerByte = nonceTrialsPerByte; - return this; - } - - public Builder extraBytes(long extraBytes) { - this.extraBytes = extraBytes; - return this; - } - - public Builder signature(byte[] signature) { - this.signature = signature; - return this; - } - - public V3Pubkey build() { - return new V3Pubkey(3, this); - } - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V3Pubkey.kt b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V3Pubkey.kt new file mode 100644 index 0000000..80dec2b --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V3Pubkey.kt @@ -0,0 +1,150 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.entity.payload + +import ch.dissem.bitmessage.utils.Decode +import ch.dissem.bitmessage.utils.Encode +import java.io.InputStream +import java.io.OutputStream +import java.nio.ByteBuffer +import java.util.* + +/** + * A version 3 public key. + */ +class V3Pubkey protected constructor( + version: Long, stream: Long, behaviorBitfield: Int, + signingKey: ByteArray, encryptionKey: ByteArray, + override val nonceTrialsPerByte: Long, + override val extraBytes: Long, + override var signature: ByteArray? = null +) : V2Pubkey(version, stream, behaviorBitfield, signingKey, encryptionKey) { + + override fun write(out: OutputStream) { + writeBytesToSign(out) + Encode.varBytes( + signature ?: throw IllegalStateException("signature not available"), + out + ) + } + + override fun write(buffer: ByteBuffer) { + super.write(buffer) + Encode.varInt(nonceTrialsPerByte, buffer) + Encode.varInt(extraBytes, buffer) + Encode.varBytes( + signature ?: throw IllegalStateException("signature not available"), + buffer + ) + } + + override val isSigned: Boolean = true + + override fun writeBytesToSign(out: OutputStream) { + super.write(out) + Encode.varInt(nonceTrialsPerByte, out) + Encode.varInt(extraBytes, out) + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is V3Pubkey) return false + return nonceTrialsPerByte == other.nonceTrialsPerByte && + extraBytes == other.extraBytes && + stream == other.stream && + behaviorBitfield == other.behaviorBitfield && + Arrays.equals(signingKey, other.signingKey) && + Arrays.equals(encryptionKey, other.encryptionKey) + } + + override fun hashCode(): Int { + return Objects.hash(nonceTrialsPerByte, extraBytes) + } + + class Builder { + private var streamNumber: Long = 0 + private var behaviorBitfield: Int = 0 + private var publicSigningKey: ByteArray? = null + private var publicEncryptionKey: ByteArray? = null + private var nonceTrialsPerByte: Long = 0 + private var extraBytes: Long = 0 + private var signature = ByteArray(0) + + fun stream(streamNumber: Long): Builder { + this.streamNumber = streamNumber + return this + } + + fun behaviorBitfield(behaviorBitfield: Int): Builder { + this.behaviorBitfield = behaviorBitfield + return this + } + + fun publicSigningKey(publicSigningKey: ByteArray): Builder { + this.publicSigningKey = publicSigningKey + return this + } + + fun publicEncryptionKey(publicEncryptionKey: ByteArray): Builder { + this.publicEncryptionKey = publicEncryptionKey + return this + } + + fun nonceTrialsPerByte(nonceTrialsPerByte: Long): Builder { + this.nonceTrialsPerByte = nonceTrialsPerByte + return this + } + + fun extraBytes(extraBytes: Long): Builder { + this.extraBytes = extraBytes + return this + } + + fun signature(signature: ByteArray): Builder { + this.signature = signature + return this + } + + fun build(): V3Pubkey { + return V3Pubkey( + version = 3, + stream = streamNumber, + behaviorBitfield = behaviorBitfield, + signingKey = publicSigningKey!!, + encryptionKey = publicEncryptionKey!!, + nonceTrialsPerByte = nonceTrialsPerByte, + extraBytes = extraBytes, + signature = signature + ) + } + } + + companion object { + fun read(`is`: InputStream, stream: Long): V3Pubkey { + return V3Pubkey( + version = 3, + stream = stream, + behaviorBitfield = Decode.int32(`is`), + signingKey = Decode.bytes(`is`, 64), + encryptionKey = Decode.bytes(`is`, 64), + nonceTrialsPerByte = Decode.varInt(`is`), + extraBytes = Decode.varInt(`is`), + signature = Decode.varBytes(`is`) + ) + } + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V4Broadcast.java b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V4Broadcast.java deleted file mode 100644 index 323da33..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V4Broadcast.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.entity.payload; - -import ch.dissem.bitmessage.entity.BitmessageAddress; -import ch.dissem.bitmessage.entity.Plaintext; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.ByteBuffer; - -/** - * Users who are subscribed to the sending address will see the message appear in their inbox. - * Broadcasts are version 4 or 5. - */ -public class V4Broadcast extends Broadcast { - private static final long serialVersionUID = 195663108282762711L; - - protected V4Broadcast(long version, long stream, CryptoBox encrypted, Plaintext plaintext) { - super(version, stream, encrypted, plaintext); - } - - public V4Broadcast(BitmessageAddress senderAddress, Plaintext plaintext) { - super(4, senderAddress.getStream(), null, plaintext); - if (senderAddress.getVersion() >= 4) - throw new IllegalArgumentException("Address version 3 or older expected, but was " + senderAddress.getVersion()); - } - - public static V4Broadcast read(InputStream in, long stream, int length) throws IOException { - return new V4Broadcast(4, stream, CryptoBox.read(in, length), null); - } - - @Override - public ObjectType getType() { - return ObjectType.BROADCAST; - } - - @Override - public void writeBytesToSign(OutputStream out) throws IOException { - plaintext.write(out, false); - } - - @Override - public void write(OutputStream out) throws IOException { - encrypted.write(out); - } - - @Override - public void write(ByteBuffer buffer) { - encrypted.write(buffer); - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V4Broadcast.kt b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V4Broadcast.kt new file mode 100644 index 0000000..b5ef096 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V4Broadcast.kt @@ -0,0 +1,59 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.entity.payload + +import ch.dissem.bitmessage.entity.BitmessageAddress +import ch.dissem.bitmessage.entity.Plaintext + +import java.io.InputStream +import java.io.OutputStream +import java.nio.ByteBuffer + +/** + * Users who are subscribed to the sending address will see the message appear in their inbox. + * Broadcasts are version 4 or 5. + */ +open class V4Broadcast : Broadcast { + + override val type: ObjectType = ObjectType.BROADCAST + + protected constructor(version: Long, stream: Long, encrypted: CryptoBox?, plaintext: Plaintext?) : super(version, stream, encrypted, plaintext) + + constructor(senderAddress: BitmessageAddress, plaintext: Plaintext) : super(4, senderAddress.stream, null, plaintext) { + if (senderAddress.version >= 4) + throw IllegalArgumentException("Address version 3 or older expected, but was " + senderAddress.version) + } + + + override fun writeBytesToSign(out: OutputStream) { + plaintext?.write(out, false) ?: throw IllegalStateException("no plaintext data available") + } + + override fun write(out: OutputStream) { + encrypted?.write(out) ?: throw IllegalStateException("broadcast not encrypted") + } + + override fun write(buffer: ByteBuffer) { + encrypted?.write(buffer) ?: throw IllegalStateException("broadcast not encrypted") + } + + companion object { + fun read(`in`: InputStream, stream: Long, length: Int): V4Broadcast { + return V4Broadcast(4, stream, CryptoBox.read(`in`, length), null) + } + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V4Pubkey.java b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V4Pubkey.java deleted file mode 100644 index 179a475..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V4Pubkey.java +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.entity.payload; - -import ch.dissem.bitmessage.entity.BitmessageAddress; -import ch.dissem.bitmessage.entity.Encrypted; -import ch.dissem.bitmessage.exception.DecryptionFailedException; -import ch.dissem.bitmessage.utils.Decode; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.util.Arrays; - -/** - * A version 4 public key. When version 4 pubkeys are created, most of the data in the pubkey is encrypted. This is - * done in such a way that only someone who has the Bitmessage address which corresponds to a pubkey can decrypt and - * use that pubkey. This prevents people from gathering pubkeys sent around the network and using the data from them - * to create messages to be used in spam or in flooding attacks. - */ -public class V4Pubkey extends Pubkey implements Encrypted { - private static final long serialVersionUID = 1556710353694033093L; - - private long stream; - private byte[] tag; - private CryptoBox encrypted; - private V3Pubkey decrypted; - - private V4Pubkey(long stream, byte[] tag, CryptoBox encrypted) { - super(4); - this.stream = stream; - this.tag = tag; - this.encrypted = encrypted; - } - - public V4Pubkey(V3Pubkey decrypted) { - super(4); - this.decrypted = decrypted; - this.stream = decrypted.stream; - this.tag = BitmessageAddress.calculateTag(4, decrypted.getStream(), decrypted.getRipe()); - } - - public static V4Pubkey read(InputStream in, long stream, int length, boolean encrypted) throws IOException { - if (encrypted) - return new V4Pubkey(stream, - Decode.bytes(in, 32), - CryptoBox.read(in, length - 32)); - else - return new V4Pubkey(V3Pubkey.read(in, stream)); - } - - @Override - public void encrypt(byte[] publicKey) throws IOException { - if (getSignature() == null) throw new IllegalStateException("Pubkey must be signed before encryption."); - this.encrypted = new CryptoBox(decrypted, publicKey); - } - - @Override - public void decrypt(byte[] privateKey) throws IOException, DecryptionFailedException { - decrypted = V3Pubkey.read(encrypted.decrypt(privateKey), stream); - } - - @Override - public boolean isDecrypted() { - return decrypted != null; - } - - @Override - public void write(OutputStream stream) throws IOException { - stream.write(tag); - encrypted.write(stream); - } - - @Override - public void write(ByteBuffer buffer) { - buffer.put(tag); - encrypted.write(buffer); - } - - @Override - public void writeUnencrypted(OutputStream out) throws IOException { - decrypted.write(out); - } - - @Override - public void writeUnencrypted(ByteBuffer buffer) { - decrypted.write(buffer); - } - - @Override - public void writeBytesToSign(OutputStream out) throws IOException { - out.write(tag); - decrypted.writeBytesToSign(out); - } - - @Override - public long getVersion() { - return 4; - } - - @Override - public ObjectType getType() { - return ObjectType.PUBKEY; - } - - @Override - public long getStream() { - return stream; - } - - public byte[] getTag() { - return tag; - } - - @Override - public byte[] getSigningKey() { - return decrypted.getSigningKey(); - } - - @Override - public byte[] getEncryptionKey() { - return decrypted.getEncryptionKey(); - } - - @Override - public int getBehaviorBitfield() { - return decrypted.getBehaviorBitfield(); - } - - @Override - public byte[] getSignature() { - if (decrypted != null) - return decrypted.getSignature(); - else - return null; - } - - @Override - public void setSignature(byte[] signature) { - decrypted.setSignature(signature); - } - - @Override - public boolean isSigned() { - return true; - } - - public long getNonceTrialsPerByte() { - return decrypted.getNonceTrialsPerByte(); - } - - public long getExtraBytes() { - return decrypted.getExtraBytes(); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - V4Pubkey v4Pubkey = (V4Pubkey) o; - - if (stream != v4Pubkey.stream) return false; - if (!Arrays.equals(tag, v4Pubkey.tag)) return false; - return !(decrypted != null ? !decrypted.equals(v4Pubkey.decrypted) : v4Pubkey.decrypted != null); - - } - - @Override - public int hashCode() { - int result = (int) (stream ^ (stream >>> 32)); - result = 31 * result + Arrays.hashCode(tag); - result = 31 * result + (decrypted != null ? decrypted.hashCode() : 0); - return result; - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V4Pubkey.kt b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V4Pubkey.kt new file mode 100644 index 0000000..d755864 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V4Pubkey.kt @@ -0,0 +1,139 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.entity.payload + +import ch.dissem.bitmessage.entity.BitmessageAddress +import ch.dissem.bitmessage.entity.Encrypted +import ch.dissem.bitmessage.exception.DecryptionFailedException +import ch.dissem.bitmessage.utils.Decode +import java.io.InputStream +import java.io.OutputStream +import java.nio.ByteBuffer +import java.util.* + +/** + * A version 4 public key. When version 4 pubkeys are created, most of the data in the pubkey is encrypted. This is + * done in such a way that only someone who has the Bitmessage address which corresponds to a pubkey can decrypt and + * use that pubkey. This prevents people from gathering pubkeys sent around the network and using the data from them + * to create messages to be used in spam or in flooding attacks. + */ +class V4Pubkey : Pubkey, Encrypted { + + override val stream: Long + val tag: ByteArray + private var encrypted: CryptoBox? = null + private var decrypted: V3Pubkey? = null + + private constructor(stream: Long, tag: ByteArray, encrypted: CryptoBox) : super(4) { + this.stream = stream + this.tag = tag + this.encrypted = encrypted + } + + constructor(decrypted: V3Pubkey) : super(4) { + this.stream = decrypted.stream + this.decrypted = decrypted + this.tag = BitmessageAddress.calculateTag(4, decrypted.stream, decrypted.ripe) + } + + override fun encrypt(publicKey: ByteArray) { + if (signature == null) throw IllegalStateException("Pubkey must be signed before encryption.") + this.encrypted = CryptoBox(decrypted ?: throw IllegalStateException("no plaintext pubkey data available"), publicKey) + } + + @Throws(DecryptionFailedException::class) + override fun decrypt(privateKey: ByteArray) { + decrypted = V3Pubkey.read(encrypted?.decrypt(privateKey) ?: throw IllegalStateException("no encrypted data available"), stream) + } + + override val isDecrypted: Boolean + get() = decrypted != null + + override fun write(out: OutputStream) { + out.write(tag) + encrypted?.write(out) ?: throw IllegalStateException("pubkey is encrypted") + } + + override fun write(buffer: ByteBuffer) { + buffer.put(tag) + encrypted?.write(buffer) ?: throw IllegalStateException("pubkey is encrypted") + } + + override fun writeUnencrypted(out: OutputStream) { + decrypted?.write(out) ?: throw IllegalStateException("pubkey is encrypted") + } + + override fun writeUnencrypted(buffer: ByteBuffer) { + decrypted?.write(buffer) ?: throw IllegalStateException("pubkey is encrypted") + } + + override fun writeBytesToSign(out: OutputStream) { + out.write(tag) + decrypted?.writeBytesToSign(out) ?: throw IllegalStateException("pubkey is encrypted") + } + + override val signingKey: ByteArray + get() = decrypted?.signingKey ?: throw IllegalStateException("pubkey is encrypted") + + override val encryptionKey: ByteArray + get() = decrypted?.encryptionKey ?: throw IllegalStateException("pubkey is encrypted") + + override val behaviorBitfield: Int + get() = decrypted?.behaviorBitfield ?: throw IllegalStateException("pubkey is encrypted") + + override var signature: ByteArray? + get() = decrypted?.signature + set(signature) { + decrypted?.signature = signature + } + + override val isSigned: Boolean = true + + override val nonceTrialsPerByte: Long + get() = decrypted?.nonceTrialsPerByte ?: throw IllegalStateException("pubkey is encrypted") + + override val extraBytes: Long + get() = decrypted?.extraBytes ?: throw IllegalStateException("pubkey is encrypted") + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is V4Pubkey) return false + + if (stream != other.stream) return false + if (!Arrays.equals(tag, other.tag)) return false + return !if (decrypted != null) decrypted != other.decrypted else other.decrypted != null + + } + + override fun hashCode(): Int { + var result = (stream xor stream.ushr(32)).toInt() + result = 31 * result + Arrays.hashCode(tag) + result = 31 * result + if (decrypted != null) decrypted!!.hashCode() else 0 + return result + } + + companion object { + fun read(`in`: InputStream, stream: Long, length: Int, encrypted: Boolean): V4Pubkey { + if (encrypted) + return V4Pubkey(stream, + Decode.bytes(`in`, 32), + CryptoBox.read(`in`, length - 32)) + else + return V4Pubkey(V3Pubkey.read(`in`, stream)) + } + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V5Broadcast.java b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V5Broadcast.java deleted file mode 100644 index 8f07a30..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V5Broadcast.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.entity.payload; - -import ch.dissem.bitmessage.entity.BitmessageAddress; -import ch.dissem.bitmessage.entity.Plaintext; -import ch.dissem.bitmessage.utils.Decode; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -/** - * Users who are subscribed to the sending address will see the message appear in their inbox. - */ -public class V5Broadcast extends V4Broadcast { - private static final long serialVersionUID = 920649721626968644L; - - private byte[] tag; - - private V5Broadcast(long stream, byte[] tag, CryptoBox encrypted) { - super(5, stream, encrypted, null); - this.tag = tag; - } - - public V5Broadcast(BitmessageAddress senderAddress, Plaintext plaintext) { - super(5, senderAddress.getStream(), null, plaintext); - if (senderAddress.getVersion() < 4) - throw new IllegalArgumentException("Address version 4 (or newer) expected, but was " + senderAddress.getVersion()); - this.tag = senderAddress.getTag(); - } - - public static V5Broadcast read(InputStream is, long stream, int length) throws IOException { - return new V5Broadcast(stream, Decode.bytes(is, 32), CryptoBox.read(is, length - 32)); - } - - public byte[] getTag() { - return tag; - } - - @Override - public void writeBytesToSign(OutputStream out) throws IOException { - out.write(tag); - super.writeBytesToSign(out); - } - - @Override - public void write(OutputStream out) throws IOException { - out.write(tag); - super.write(out); - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V5Broadcast.kt b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V5Broadcast.kt new file mode 100644 index 0000000..4ab9af9 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V5Broadcast.kt @@ -0,0 +1,58 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.entity.payload + +import ch.dissem.bitmessage.entity.BitmessageAddress +import ch.dissem.bitmessage.entity.Plaintext +import ch.dissem.bitmessage.utils.Decode + +import java.io.InputStream +import java.io.OutputStream + +/** + * Users who are subscribed to the sending address will see the message appear in their inbox. + */ +class V5Broadcast : V4Broadcast { + + val tag: ByteArray + + private constructor(stream: Long, tag: ByteArray, encrypted: CryptoBox) : super(5, stream, encrypted, null) { + this.tag = tag + } + + constructor(senderAddress: BitmessageAddress, plaintext: Plaintext) : super(5, senderAddress.stream, null, plaintext) { + if (senderAddress.version < 4) + throw IllegalArgumentException("Address version 4 (or newer) expected, but was " + senderAddress.version) + this.tag = senderAddress.tag ?: throw IllegalStateException("version 4 address without tag") + } + + override fun writeBytesToSign(out: OutputStream) { + out.write(tag) + super.writeBytesToSign(out) + } + + override fun write(out: OutputStream) { + out.write(tag) + super.write(out) + } + + companion object { + fun read(`is`: InputStream, stream: Long, length: Int): V5Broadcast { + return V5Broadcast(stream, Decode.bytes(`is`, 32), CryptoBox.read(`is`, length - 32)) + } + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/ExtendedEncoding.java b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/ExtendedEncoding.java deleted file mode 100644 index 3f3d1ec..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/ExtendedEncoding.java +++ /dev/null @@ -1,76 +0,0 @@ -package ch.dissem.bitmessage.entity.valueobject; - -import ch.dissem.bitmessage.exception.ApplicationException; -import ch.dissem.msgpack.types.MPMap; -import ch.dissem.msgpack.types.MPString; -import ch.dissem.msgpack.types.MPType; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.Serializable; -import java.util.Objects; -import java.util.zip.DeflaterOutputStream; - -/** - * Extended encoding message object. - */ -public class ExtendedEncoding implements Serializable { - private static final long serialVersionUID = 3876871488247305200L; - private static final Logger LOG = LoggerFactory.getLogger(ExtendedEncoding.class); - - private ExtendedType content; - - public ExtendedEncoding(ExtendedType content) { - this.content = content; - } - - public String getType() { - if (content == null) { - return null; - } else { - return content.getType(); - } - } - - public ExtendedType getContent() { - return content; - } - - public byte[] zip() { - try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { - try (DeflaterOutputStream zipper = new DeflaterOutputStream(out)) { - content.pack().pack(zipper); - } - return out.toByteArray(); - } catch (IOException e) { - throw new ApplicationException(e); - } - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - ExtendedEncoding that = (ExtendedEncoding) o; - return Objects.equals(content, that.content); - } - - @Override - public int hashCode() { - return Objects.hash(content); - } - - public interface Unpacker<T extends ExtendedType> { - String getType(); - - T unpack(MPMap<MPString, MPType<?>> map); - } - - public interface ExtendedType extends Serializable { - String getType(); - - MPMap<MPString, MPType<?>> pack() throws IOException; - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/ExtendedEncoding.kt b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/ExtendedEncoding.kt new file mode 100644 index 0000000..87d85b3 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/ExtendedEncoding.kt @@ -0,0 +1,51 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.entity.valueobject + +import ch.dissem.msgpack.types.MPMap +import ch.dissem.msgpack.types.MPString +import ch.dissem.msgpack.types.MPType +import java.io.ByteArrayOutputStream +import java.io.Serializable +import java.util.zip.DeflaterOutputStream + +/** + * Extended encoding message object. + */ +data class ExtendedEncoding(val content: ExtendedEncoding.ExtendedType) : Serializable { + + val type: String? = content.type + + fun zip(): ByteArray { + ByteArrayOutputStream().use { out -> + DeflaterOutputStream(out).use { zipper -> content.pack().pack(zipper) } + return out.toByteArray() + } + } + + interface Unpacker<out T : ExtendedType> { + val type: String + + fun unpack(map: MPMap<MPString, MPType<*>>): T + } + + interface ExtendedType : Serializable { + val type: String + + fun pack(): MPMap<MPString, MPType<*>> + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/InventoryVector.java b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/InventoryVector.java deleted file mode 100644 index e1f8f60..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/InventoryVector.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.entity.valueobject; - -import ch.dissem.bitmessage.entity.Streamable; -import ch.dissem.bitmessage.utils.Strings; - -import java.io.IOException; -import java.io.OutputStream; -import java.io.Serializable; -import java.nio.ByteBuffer; -import java.util.Arrays; - -public class InventoryVector implements Streamable, Serializable { - private static final long serialVersionUID = -7349009673063348719L; - - /** - * Hash of the object - */ - private final byte[] hash; - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof InventoryVector)) return false; - - InventoryVector that = (InventoryVector) o; - - return Arrays.equals(hash, that.hash); - } - - @Override - public int hashCode() { - return hash == null ? 0 : Arrays.hashCode(hash); - } - - public byte[] getHash() { - return hash; - } - - private InventoryVector(byte[] hash) { - if (hash == null) throw new IllegalArgumentException("hash must not be null"); - this.hash = hash; - } - - public static InventoryVector fromHash(byte[] hash) { - if (hash == null) { - return null; - } else { - return new InventoryVector(hash); - } - } - - @Override - public void write(OutputStream out) throws IOException { - out.write(hash); - } - - @Override - public void write(ByteBuffer buffer) { - buffer.put(hash); - } - - @Override - public String toString() { - return Strings.hex(hash).toString(); - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/InventoryVector.kt b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/InventoryVector.kt new file mode 100644 index 0000000..e46ea72 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/InventoryVector.kt @@ -0,0 +1,61 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.entity.valueobject + +import ch.dissem.bitmessage.entity.Streamable +import ch.dissem.bitmessage.utils.Strings +import java.io.OutputStream +import java.nio.ByteBuffer +import java.util.* + +data class InventoryVector constructor( + /** + * Hash of the object + */ + val hash: ByteArray) : Streamable { + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is InventoryVector) return false + + return Arrays.equals(hash, other.hash) + } + + override fun hashCode(): Int { + return Arrays.hashCode(hash) + } + + override fun write(out: OutputStream) { + out.write(hash) + } + + override fun write(buffer: ByteBuffer) { + buffer.put(hash) + } + + override fun toString(): String { + return Strings.hex(hash).toString() + } + + companion object { + @JvmStatic fun fromHash(hash: ByteArray?): InventoryVector? { + return InventoryVector( + hash ?: return null + ) + } + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/Label.java b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/Label.java deleted file mode 100644 index facbe55..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/Label.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.entity.valueobject; - -import java.io.Serializable; -import java.util.Objects; - -public class Label implements Serializable { - private static final long serialVersionUID = 831782893630994914L; - - private Object id; - private String label; - private Type type; - private int color; - - public Label(String label, Type type, int color) { - this.label = label; - this.type = type; - this.color = color; - } - - /** - * @return RGBA representation for the color. - */ - public int getColor() { - return color; - } - - /** - * @param color RGBA representation for the color. - */ - public void setColor(int color) { - this.color = color; - } - - @Override - public String toString() { - return label; - } - - public Object getId() { - return id; - } - - public void setId(Object id) { - this.id = id; - } - - public Type getType() { - return type; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Label label1 = (Label) o; - return Objects.equals(label, label1.label); - } - - @Override - public int hashCode() { - return Objects.hash(label); - } - - public enum Type { - INBOX, - BROADCAST, - DRAFT, - OUTBOX, - SENT, - UNREAD, - TRASH - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/Label.kt b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/Label.kt new file mode 100644 index 0000000..378aa35 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/Label.kt @@ -0,0 +1,53 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.entity.valueobject + +import java.io.Serializable +import java.util.* + +data class Label(private val label: String, val type: Label.Type, + /** + * RGBA representation for the color. + */ + var color: Int) : Serializable { + + var id: Any? = null + + override fun toString(): String { + return label + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is Label) return false + return label == other.label + } + + override fun hashCode(): Int { + return Objects.hash(label) + } + + enum class Type { + INBOX, + BROADCAST, + DRAFT, + OUTBOX, + SENT, + UNREAD, + TRASH + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/NetworkAddress.java b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/NetworkAddress.java deleted file mode 100644 index fe3bfcb..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/NetworkAddress.java +++ /dev/null @@ -1,246 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.entity.valueobject; - -import ch.dissem.bitmessage.entity.Streamable; -import ch.dissem.bitmessage.entity.Version; -import ch.dissem.bitmessage.exception.ApplicationException; -import ch.dissem.bitmessage.utils.Encode; -import ch.dissem.bitmessage.utils.UnixTime; - -import java.io.IOException; -import java.io.OutputStream; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.SocketAddress; -import java.net.UnknownHostException; -import java.nio.ByteBuffer; -import java.util.Arrays; - -/** - * A node's address. It's written in IPv6 format. - */ -public class NetworkAddress implements Streamable { - private static final long serialVersionUID = 2500120578167100300L; - - private long time; - - /** - * Stream number for this node - */ - private final long stream; - - /** - * same service(s) listed in version - */ - private final long services; - - /** - * IPv6 address. IPv4 addresses are written into the message as a 16 byte IPv4-mapped IPv6 address - * (12 bytes 00 00 00 00 00 00 00 00 00 00 FF FF, followed by the 4 bytes of the IPv4 address). - */ - private final byte[] ipv6; - private final int port; - - private NetworkAddress(Builder builder) { - time = builder.time; - stream = builder.stream; - services = builder.services; - ipv6 = builder.ipv6; - port = builder.port; - } - - public byte[] getIPv6() { - return ipv6; - } - - public int getPort() { - return port; - } - - public long getServices() { - return services; - } - - public boolean provides(Version.Service service) { - if (service == null) { - return false; - } - return service.isEnabled(services); - } - - public long getStream() { - return stream; - } - - public long getTime() { - return time; - } - - public void setTime(long time) { - this.time = time; - } - - public InetAddress toInetAddress() { - try { - return InetAddress.getByAddress(ipv6); - } catch (UnknownHostException e) { - throw new ApplicationException(e); - } - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - NetworkAddress that = (NetworkAddress) o; - - return port == that.port && Arrays.equals(ipv6, that.ipv6); - } - - @Override - public int hashCode() { - int result = ipv6 != null ? Arrays.hashCode(ipv6) : 0; - result = 31 * result + port; - return result; - } - - @Override - public String toString() { - return "[" + toInetAddress() + "]:" + port; - } - - @Override - public void write(OutputStream stream) throws IOException { - write(stream, false); - } - - public void write(OutputStream out, boolean light) throws IOException { - if (!light) { - Encode.int64(time, out); - Encode.int32(stream, out); - } - Encode.int64(services, out); - out.write(ipv6); - Encode.int16(port, out); - } - - @Override - public void write(ByteBuffer buffer) { - write(buffer, false); - } - - public void write(ByteBuffer buffer, boolean light) { - if (!light) { - Encode.int64(time, buffer); - Encode.int32(stream, buffer); - } - Encode.int64(services, buffer); - buffer.put(ipv6); - Encode.int16(port, buffer); - } - - public static final class Builder { - private long time; - private long stream; - private long services = 1; - private byte[] ipv6; - private int port; - - public Builder time(final long time) { - this.time = time; - return this; - } - - public Builder stream(final long stream) { - this.stream = stream; - return this; - } - - public Builder services(final long services) { - this.services = services; - return this; - } - - public Builder ip(InetAddress inetAddress) { - byte[] addr = inetAddress.getAddress(); - if (addr.length == 16) { - this.ipv6 = addr; - } else if (addr.length == 4) { - this.ipv6 = new byte[16]; - this.ipv6[10] = (byte) 0xff; - this.ipv6[11] = (byte) 0xff; - System.arraycopy(addr, 0, this.ipv6, 12, 4); - } else { - throw new IllegalArgumentException("Weird address " + inetAddress); - } - return this; - } - - public Builder ipv6(byte[] ipv6) { - this.ipv6 = ipv6; - return this; - } - - public Builder ipv6(int p00, int p01, int p02, int p03, - int p04, int p05, int p06, int p07, - int p08, int p09, int p10, int p11, - int p12, int p13, int p14, int p15) { - this.ipv6 = new byte[]{ - (byte) p00, (byte) p01, (byte) p02, (byte) p03, - (byte) p04, (byte) p05, (byte) p06, (byte) p07, - (byte) p08, (byte) p09, (byte) p10, (byte) p11, - (byte) p12, (byte) p13, (byte) p14, (byte) p15 - }; - return this; - } - - public Builder ipv4(int p00, int p01, int p02, int p03) { - this.ipv6 = new byte[]{ - (byte) 0, (byte) 0, (byte) 0x00, (byte) 0x00, - (byte) 0, (byte) 0, (byte) 0x00, (byte) 0x00, - (byte) 0, (byte) 0, (byte) 0xff, (byte) 0xff, - (byte) p00, (byte) p01, (byte) p02, (byte) p03 - }; - return this; - } - - public Builder port(final int port) { - this.port = port; - return this; - } - - public Builder address(SocketAddress address) { - if (address instanceof InetSocketAddress) { - InetSocketAddress inetAddress = (InetSocketAddress) address; - ip(inetAddress.getAddress()); - port(inetAddress.getPort()); - } else { - throw new IllegalArgumentException("Unknown type of address: " + address.getClass()); - } - return this; - } - - public NetworkAddress build() { - if (time == 0) { - time = UnixTime.now(); - } - return new NetworkAddress(this); - } - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/NetworkAddress.kt b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/NetworkAddress.kt new file mode 100644 index 0000000..e332a04 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/NetworkAddress.kt @@ -0,0 +1,183 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.entity.valueobject + +import ch.dissem.bitmessage.entity.Streamable +import ch.dissem.bitmessage.entity.Version +import ch.dissem.bitmessage.utils.Encode +import ch.dissem.bitmessage.utils.UnixTime +import java.io.OutputStream +import java.net.InetAddress +import java.net.InetSocketAddress +import java.net.SocketAddress +import java.nio.ByteBuffer +import java.util.* + +/** + * A node's address. It's written in IPv6 format. + */ +data class NetworkAddress constructor( + var time: Long, + + /** + * Stream number for this node + */ + val stream: Long, + + /** + * same service(s) listed in version + */ + val services: Long, + + /** + * IPv6 address. IPv4 addresses are written into the message as a 16 byte IPv4-mapped IPv6 address + * (12 bytes 00 00 00 00 00 00 00 00 00 00 FF FF, followed by the 4 bytes of the IPv4 address). + */ + val iPv6: ByteArray, + val port: Int +) : Streamable { + + fun provides(service: Version.Service?): Boolean = service?.isEnabled(services) ?: false + + fun toInetAddress(): InetAddress { + return InetAddress.getByAddress(iPv6) + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is NetworkAddress) return false + + return port == other.port && Arrays.equals(iPv6, other.iPv6) + } + + override fun hashCode(): Int { + var result = Arrays.hashCode(iPv6) + result = 31 * result + port + return result + } + + override fun toString(): String { + return "[" + toInetAddress() + "]:" + port + } + + override fun write(out: OutputStream) { + write(out, false) + } + + fun write(out: OutputStream, light: Boolean) { + if (!light) { + Encode.int64(time, out) + Encode.int32(stream, out) + } + Encode.int64(services, out) + out.write(iPv6) + Encode.int16(port.toLong(), out) + } + + override fun write(buffer: ByteBuffer) { + write(buffer, false) + } + + fun write(buffer: ByteBuffer, light: Boolean) { + if (!light) { + Encode.int64(time, buffer) + Encode.int32(stream, buffer) + } + Encode.int64(services, buffer) + buffer.put(iPv6) + Encode.int16(port.toLong(), buffer) + } + + class Builder { + internal var time: Long? = null + internal var stream: Long = 0 + internal var services: Long = 1 + internal var ipv6: ByteArray? = null + internal var port: Int = 0 + + fun time(time: Long): Builder { + this.time = time + return this + } + + fun stream(stream: Long): Builder { + this.stream = stream + return this + } + + fun services(services: Long): Builder { + this.services = services + return this + } + + fun ip(inetAddress: InetAddress): Builder { + val addr = inetAddress.address + if (addr.size == 16) { + this.ipv6 = addr + } else if (addr.size == 4) { + val ipv6 = ByteArray(16) + ipv6[10] = 0xff.toByte() + ipv6[11] = 0xff.toByte() + System.arraycopy(addr, 0, ipv6, 12, 4) + this.ipv6 = ipv6 + } else { + throw IllegalArgumentException("Weird address " + inetAddress) + } + return this + } + + fun ipv6(ipv6: ByteArray): Builder { + this.ipv6 = ipv6 + return this + } + + fun ipv6(p00: Int, p01: Int, p02: Int, p03: Int, + p04: Int, p05: Int, p06: Int, p07: Int, + p08: Int, p09: Int, p10: Int, p11: Int, + p12: Int, p13: Int, p14: Int, p15: Int): Builder { + this.ipv6 = byteArrayOf(p00.toByte(), p01.toByte(), p02.toByte(), p03.toByte(), p04.toByte(), p05.toByte(), p06.toByte(), p07.toByte(), p08.toByte(), p09.toByte(), p10.toByte(), p11.toByte(), p12.toByte(), p13.toByte(), p14.toByte(), p15.toByte()) + return this + } + + fun ipv4(p00: Int, p01: Int, p02: Int, p03: Int): Builder { + this.ipv6 = byteArrayOf(0.toByte(), 0.toByte(), 0x00.toByte(), 0x00.toByte(), 0.toByte(), 0.toByte(), 0x00.toByte(), 0x00.toByte(), 0.toByte(), 0.toByte(), 0xff.toByte(), 0xff.toByte(), p00.toByte(), p01.toByte(), p02.toByte(), p03.toByte()) + return this + } + + fun port(port: Int): Builder { + this.port = port + return this + } + + fun address(address: SocketAddress): Builder { + if (address is InetSocketAddress) { + val inetAddress = address + ip(inetAddress.address) + port(inetAddress.port) + } else { + throw IllegalArgumentException("Unknown type of address: " + address.javaClass) + } + return this + } + + fun build(): NetworkAddress { + return NetworkAddress( + time ?: UnixTime.now, stream, services, ipv6!!, port + ) + } + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/PrivateKey.java b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/PrivateKey.java deleted file mode 100644 index 7621ca5..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/PrivateKey.java +++ /dev/null @@ -1,198 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.entity.valueobject; - -import ch.dissem.bitmessage.InternalContext; -import ch.dissem.bitmessage.entity.BitmessageAddress; -import ch.dissem.bitmessage.entity.Streamable; -import ch.dissem.bitmessage.entity.payload.Pubkey; -import ch.dissem.bitmessage.exception.ApplicationException; -import ch.dissem.bitmessage.factory.Factory; -import ch.dissem.bitmessage.utils.Bytes; -import ch.dissem.bitmessage.utils.Decode; -import ch.dissem.bitmessage.utils.Encode; - -import java.io.*; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.List; - -import static ch.dissem.bitmessage.utils.Singleton.cryptography; - -/** - * Represents a private key. Additional information (stream, version, features, ...) is stored in the accompanying - * {@link Pubkey} object. - */ -public class PrivateKey implements Streamable { - private static final long serialVersionUID = 8562555470709110558L; - - public static final int PRIVATE_KEY_SIZE = 32; - - private final byte[] privateSigningKey; - private final byte[] privateEncryptionKey; - - private final Pubkey pubkey; - - public PrivateKey(boolean shorter, long stream, long nonceTrialsPerByte, long extraBytes, Pubkey.Feature... features) { - byte[] privSK; - byte[] pubSK; - byte[] privEK; - byte[] pubEK; - byte[] ripe; - do { - privSK = cryptography().randomBytes(PRIVATE_KEY_SIZE); - privEK = cryptography().randomBytes(PRIVATE_KEY_SIZE); - pubSK = cryptography().createPublicKey(privSK); - pubEK = cryptography().createPublicKey(privEK); - ripe = Pubkey.getRipe(pubSK, pubEK); - } while (ripe[0] != 0 || (shorter && ripe[1] != 0)); - this.privateSigningKey = privSK; - this.privateEncryptionKey = privEK; - this.pubkey = cryptography().createPubkey(Pubkey.LATEST_VERSION, stream, privateSigningKey, privateEncryptionKey, - nonceTrialsPerByte, extraBytes, features); - } - - public PrivateKey(byte[] privateSigningKey, byte[] privateEncryptionKey, Pubkey pubkey) { - this.privateSigningKey = privateSigningKey; - this.privateEncryptionKey = privateEncryptionKey; - this.pubkey = pubkey; - } - - public PrivateKey(BitmessageAddress address, String passphrase) { - this(address.getVersion(), address.getStream(), passphrase); - } - - public PrivateKey(long version, long stream, String passphrase) { - this(new Builder(version, stream, false).seed(passphrase).generate()); - } - - private PrivateKey(Builder builder) { - this.privateSigningKey = builder.privSK; - this.privateEncryptionKey = builder.privEK; - this.pubkey = Factory.createPubkey(builder.version, builder.stream, builder.pubSK, builder.pubEK, - InternalContext.NETWORK_NONCE_TRIALS_PER_BYTE, InternalContext.NETWORK_EXTRA_BYTES); - } - - private static class Builder { - final long version; - final long stream; - final boolean shorter; - - byte[] seed; - long nextNonce; - - byte[] privSK, privEK; - byte[] pubSK, pubEK; - - private Builder(long version, long stream, boolean shorter) { - this.version = version; - this.stream = stream; - this.shorter = shorter; - } - - Builder seed(String passphrase) { - try { - seed = passphrase.getBytes("UTF-8"); - } catch (UnsupportedEncodingException e) { - throw new ApplicationException(e); - } - return this; - } - - Builder generate() { - long signingKeyNonce = nextNonce; - long encryptionKeyNonce = nextNonce + 1; - byte[] ripe; - do { - privEK = Bytes.truncate(cryptography().sha512(seed, Encode.varInt(encryptionKeyNonce)), 32); - privSK = Bytes.truncate(cryptography().sha512(seed, Encode.varInt(signingKeyNonce)), 32); - pubSK = cryptography().createPublicKey(privSK); - pubEK = cryptography().createPublicKey(privEK); - ripe = cryptography().ripemd160(cryptography().sha512(pubSK, pubEK)); - - signingKeyNonce += 2; - encryptionKeyNonce += 2; - } while (ripe[0] != 0 || (shorter && ripe[1] != 0)); - nextNonce = signingKeyNonce; - return this; - } - } - - public static List<PrivateKey> deterministic(String passphrase, int numberOfAddresses, long version, long stream, boolean shorter) { - List<PrivateKey> result = new ArrayList<>(numberOfAddresses); - Builder builder = new Builder(version, stream, shorter).seed(passphrase); - for (int i = 0; i < numberOfAddresses; i++) { - builder.generate(); - result.add(new PrivateKey(builder)); - } - return result; - } - - public static PrivateKey read(InputStream is) throws IOException { - int version = (int) Decode.varInt(is); - long stream = Decode.varInt(is); - int len = (int) Decode.varInt(is); - Pubkey pubkey = Factory.readPubkey(version, stream, is, len, false); - len = (int) Decode.varInt(is); - byte[] signingKey = Decode.bytes(is, len); - len = (int) Decode.varInt(is); - byte[] encryptionKey = Decode.bytes(is, len); - return new PrivateKey(signingKey, encryptionKey, pubkey); - } - - public byte[] getPrivateSigningKey() { - return privateSigningKey; - } - - public byte[] getPrivateEncryptionKey() { - return privateEncryptionKey; - } - - public Pubkey getPubkey() { - return pubkey; - } - - @Override - public void write(OutputStream out) throws IOException { - Encode.varInt(pubkey.getVersion(), out); - Encode.varInt(pubkey.getStream(), out); - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - pubkey.writeUnencrypted(baos); - Encode.varInt(baos.size(), out); - out.write(baos.toByteArray()); - Encode.varInt(privateSigningKey.length, out); - out.write(privateSigningKey); - Encode.varInt(privateEncryptionKey.length, out); - out.write(privateEncryptionKey); - } - - - @Override - public void write(ByteBuffer buffer) { - Encode.varInt(pubkey.getVersion(), buffer); - Encode.varInt(pubkey.getStream(), buffer); - try { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - pubkey.writeUnencrypted(baos); - Encode.varBytes(baos.toByteArray(), buffer); - } catch (IOException e) { - throw new ApplicationException(e); - } - Encode.varBytes(privateSigningKey, buffer); - Encode.varBytes(privateEncryptionKey, buffer); - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/PrivateKey.kt b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/PrivateKey.kt new file mode 100644 index 0000000..1cfabda --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/PrivateKey.kt @@ -0,0 +1,171 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.entity.valueobject + +import ch.dissem.bitmessage.InternalContext +import ch.dissem.bitmessage.entity.BitmessageAddress +import ch.dissem.bitmessage.entity.Streamable +import ch.dissem.bitmessage.entity.payload.Pubkey +import ch.dissem.bitmessage.exception.ApplicationException +import ch.dissem.bitmessage.factory.Factory +import ch.dissem.bitmessage.utils.Bytes +import ch.dissem.bitmessage.utils.Decode +import ch.dissem.bitmessage.utils.Encode +import ch.dissem.bitmessage.utils.Singleton.cryptography +import java.io.* +import java.nio.ByteBuffer +import java.util.* + +/** + * Represents a private key. Additional information (stream, version, features, ...) is stored in the accompanying + * [Pubkey] object. + */ +class PrivateKey : Streamable { + + val privateSigningKey: ByteArray + val privateEncryptionKey: ByteArray + + val pubkey: Pubkey + + constructor(shorter: Boolean, stream: Long, nonceTrialsPerByte: Long, extraBytes: Long, vararg features: Pubkey.Feature) { + var privSK: ByteArray + var pubSK: ByteArray + var privEK: ByteArray + var pubEK: ByteArray + var ripe: ByteArray + do { + privSK = cryptography().randomBytes(PRIVATE_KEY_SIZE) + privEK = cryptography().randomBytes(PRIVATE_KEY_SIZE) + pubSK = cryptography().createPublicKey(privSK) + pubEK = cryptography().createPublicKey(privEK) + ripe = Pubkey.getRipe(pubSK, pubEK) + } while (ripe[0].toInt() != 0 || shorter && ripe[1].toInt() != 0) + this.privateSigningKey = privSK + this.privateEncryptionKey = privEK + this.pubkey = cryptography().createPubkey(Pubkey.LATEST_VERSION, stream, privateSigningKey, privateEncryptionKey, + nonceTrialsPerByte, extraBytes, *features) + } + + constructor(privateSigningKey: ByteArray, privateEncryptionKey: ByteArray, pubkey: Pubkey) { + this.privateSigningKey = privateSigningKey + this.privateEncryptionKey = privateEncryptionKey + this.pubkey = pubkey + } + + constructor(address: BitmessageAddress, passphrase: String) : this(address.version, address.stream, passphrase) + + constructor(version: Long, stream: Long, passphrase: String) : this(Builder(version, stream, false).seed(passphrase).generate()) + + private constructor(builder: Builder) { + this.privateSigningKey = builder.privSK!! + this.privateEncryptionKey = builder.privEK!! + this.pubkey = Factory.createPubkey(builder.version, builder.stream, builder.pubSK!!, builder.pubEK!!, + InternalContext.NETWORK_NONCE_TRIALS_PER_BYTE, InternalContext.NETWORK_EXTRA_BYTES) + } + + private class Builder internal constructor(internal val version: Long, internal val stream: Long, internal val shorter: Boolean) { + + internal var seed: ByteArray? = null + internal var nextNonce: Long = 0 + + internal var privSK: ByteArray? = null + internal var privEK: ByteArray? = null + internal var pubSK: ByteArray? = null + internal var pubEK: ByteArray? = null + + internal fun seed(passphrase: String): Builder { + try { + seed = passphrase.toByteArray(charset("UTF-8")) + } catch (e: UnsupportedEncodingException) { + throw ApplicationException(e) + } + + return this + } + + internal fun generate(): Builder { + var signingKeyNonce = nextNonce + var encryptionKeyNonce = nextNonce + 1 + var ripe: ByteArray + do { + privEK = Bytes.truncate(cryptography().sha512(seed!!, Encode.varInt(encryptionKeyNonce)), 32) + privSK = Bytes.truncate(cryptography().sha512(seed!!, Encode.varInt(signingKeyNonce)), 32) + pubSK = cryptography().createPublicKey(privSK!!) + pubEK = cryptography().createPublicKey(privEK!!) + ripe = cryptography().ripemd160(cryptography().sha512(pubSK!!, pubEK!!)) + + signingKeyNonce += 2 + encryptionKeyNonce += 2 + } while (ripe[0].toInt() != 0 || shorter && ripe[1].toInt() != 0) + nextNonce = signingKeyNonce + return this + } + } + + override fun write(out: OutputStream) { + Encode.varInt(pubkey.version, out) + Encode.varInt(pubkey.stream, out) + val baos = ByteArrayOutputStream() + pubkey.writeUnencrypted(baos) + Encode.varInt(baos.size().toLong(), out) + out.write(baos.toByteArray()) + Encode.varInt(privateSigningKey.size.toLong(), out) + out.write(privateSigningKey) + Encode.varInt(privateEncryptionKey.size.toLong(), out) + out.write(privateEncryptionKey) + } + + + override fun write(buffer: ByteBuffer) { + Encode.varInt(pubkey.version, buffer) + Encode.varInt(pubkey.stream, buffer) + try { + val baos = ByteArrayOutputStream() + pubkey.writeUnencrypted(baos) + Encode.varBytes(baos.toByteArray(), buffer) + } catch (e: IOException) { + throw ApplicationException(e) + } + + Encode.varBytes(privateSigningKey, buffer) + Encode.varBytes(privateEncryptionKey, buffer) + } + + companion object { + @JvmField val PRIVATE_KEY_SIZE = 32 + + @JvmStatic fun deterministic(passphrase: String, numberOfAddresses: Int, version: Long, stream: Long, shorter: Boolean): List<PrivateKey> { + val result = ArrayList<PrivateKey>(numberOfAddresses) + val builder = Builder(version, stream, shorter).seed(passphrase) + for (i in 0..numberOfAddresses - 1) { + builder.generate() + result.add(PrivateKey(builder)) + } + return result + } + + @JvmStatic fun read(`is`: InputStream): PrivateKey { + val version = Decode.varInt(`is`).toInt() + val stream = Decode.varInt(`is`) + val len = Decode.varInt(`is`).toInt() + val pubkey = Factory.readPubkey(version.toLong(), stream, `is`, len, false) ?: throw ApplicationException("Unknown pubkey version encountered") + val signingKey = Decode.varBytes(`is`) + val encryptionKey = Decode.varBytes(`is`) + return PrivateKey(signingKey, encryptionKey, pubkey) + } + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/extended/Attachment.java b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/extended/Attachment.java deleted file mode 100644 index 42afc4f..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/extended/Attachment.java +++ /dev/null @@ -1,100 +0,0 @@ -package ch.dissem.bitmessage.entity.valueobject.extended; - -import java.io.Serializable; -import java.util.Arrays; -import java.util.Objects; - -/** - * A "file" attachment as used by extended encoding type messages. Could either be an attachment, - * or used inline to be used by a HTML message, for example. - */ -public class Attachment implements Serializable { - private static final long serialVersionUID = 7319139427666943189L; - - private String name; - private byte[] data; - private String type; - private Disposition disposition; - - public String getName() { - return name; - } - - public byte[] getData() { - return data; - } - - public String getType() { - return type; - } - - public Disposition getDisposition() { - return disposition; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Attachment that = (Attachment) o; - return Objects.equals(name, that.name) && - Arrays.equals(data, that.data) && - Objects.equals(type, that.type) && - disposition == that.disposition; - } - - @Override - public int hashCode() { - return Objects.hash(name, data, type, disposition); - } - - public enum Disposition { - inline, attachment - } - - public static final class Builder { - private String name; - private byte[] data; - private String type; - private Disposition disposition; - - public Builder name(String name) { - this.name = name; - return this; - } - - public Builder data(byte[] data) { - this.data = data; - return this; - } - - public Builder type(String type) { - this.type = type; - return this; - } - - public Builder inline() { - this.disposition = Disposition.inline; - return this; - } - - public Builder attachment() { - this.disposition = Disposition.attachment; - return this; - } - - public Builder disposition(Disposition disposition) { - this.disposition = disposition; - return this; - } - - public Attachment build() { - Attachment attachment = new Attachment(); - attachment.type = this.type; - attachment.disposition = this.disposition; - attachment.data = this.data; - attachment.name = this.name; - return attachment; - } - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/extended/Attachment.kt b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/extended/Attachment.kt new file mode 100644 index 0000000..c410ba6 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/extended/Attachment.kt @@ -0,0 +1,90 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.entity.valueobject.extended + +import java.io.Serializable +import java.util.* + +/** + * A "file" attachment as used by extended encoding type messages. Could either be an attachment, + * or used inline to be used by a HTML message, for example. + */ +data class Attachment constructor( + val name: String, + val data: ByteArray, + val type: String, + val disposition: Disposition +) : Serializable { + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is Attachment) return false + return name == other.name && + Arrays.equals(data, other.data) && + type == other.type && + disposition == other.disposition + } + + override fun hashCode(): Int { + return Objects.hash(name, data, type, disposition) + } + + enum class Disposition { + inline, attachment + } + + class Builder { + private var name: String? = null + private var data: ByteArray? = null + private var type: String? = null + private var disposition: Disposition? = null + + fun name(name: String): Builder { + this.name = name + return this + } + + fun data(data: ByteArray): Builder { + this.data = data + return this + } + + fun type(type: String): Builder { + this.type = type + return this + } + + fun inline(): Builder { + this.disposition = Disposition.inline + return this + } + + fun attachment(): Builder { + this.disposition = Disposition.attachment + return this + } + + fun disposition(disposition: Disposition): Builder { + this.disposition = disposition + return this + } + + fun build(): Attachment { + return Attachment(name!!, data!!, type!!, disposition!!) + } + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/extended/Message.java b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/extended/Message.java deleted file mode 100644 index aea4368..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/extended/Message.java +++ /dev/null @@ -1,219 +0,0 @@ -package ch.dissem.bitmessage.entity.valueobject.extended; - -import ch.dissem.bitmessage.entity.Plaintext; -import ch.dissem.bitmessage.entity.valueobject.ExtendedEncoding; -import ch.dissem.bitmessage.entity.valueobject.InventoryVector; -import ch.dissem.msgpack.types.*; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.net.URLConnection; -import java.nio.file.Files; -import java.util.*; - -import static ch.dissem.bitmessage.entity.valueobject.extended.Attachment.Disposition.attachment; -import static ch.dissem.bitmessage.utils.Strings.str; -import static ch.dissem.msgpack.types.Utils.mp; - -/** - * Extended encoding type 'message'. Properties 'parents' and 'files' not yet supported by PyBitmessage, so they might not work - * properly with future PyBitmessage implementations. - */ -public class Message implements ExtendedEncoding.ExtendedType { - private static final long serialVersionUID = -2724977231484285467L; - private static final Logger LOG = LoggerFactory.getLogger(Message.class); - - public static final String TYPE = "message"; - - private String subject; - private String body; - private List<InventoryVector> parents; - private List<Attachment> files; - - private Message(Builder builder) { - subject = builder.subject; - body = builder.body; - parents = Collections.unmodifiableList(builder.parents); - files = Collections.unmodifiableList(builder.files); - } - - @Override - public String getType() { - return TYPE; - } - - public String getSubject() { - return subject; - } - - public String getBody() { - return body; - } - - public List<InventoryVector> getParents() { - return parents; - } - - public List<Attachment> getFiles() { - return files; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Message message = (Message) o; - return Objects.equals(subject, message.subject) && - Objects.equals(body, message.body) && - Objects.equals(parents, message.parents) && - Objects.equals(files, message.files); - } - - @Override - public int hashCode() { - return Objects.hash(subject, body, parents, files); - } - - @Override - public MPMap<MPString, MPType<?>> pack() throws IOException { - MPMap<MPString, MPType<?>> result = new MPMap<>(); - result.put(mp(""), mp(TYPE)); - result.put(mp("subject"), mp(subject)); - result.put(mp("body"), mp(body)); - - if (!files.isEmpty()) { - MPArray<MPMap<MPString, MPType<?>>> items = new MPArray<>(); - result.put(mp("files"), items); - for (Attachment file : files) { - MPMap<MPString, MPType<?>> item = new MPMap<>(); - item.put(mp("name"), mp(file.getName())); - item.put(mp("data"), mp(file.getData())); - item.put(mp("type"), mp(file.getType())); - item.put(mp("disposition"), mp(file.getDisposition().name())); - items.add(item); - } - } - if (!parents.isEmpty()) { - MPArray<MPBinary> items = new MPArray<>(); - result.put(mp("parents"), items); - for (InventoryVector parent : parents) { - items.add(mp(parent.getHash())); - } - } - return result; - } - - public static class Builder { - private String subject; - private String body; - private List<InventoryVector> parents = new LinkedList<>(); - private List<Attachment> files = new LinkedList<>(); - - public Builder subject(String subject) { - this.subject = subject; - return this; - } - - public Builder body(String body) { - this.body = body; - return this; - } - - public Builder addParent(Plaintext parent) { - if (parent != null) { - InventoryVector iv = parent.getInventoryVector(); - if (iv == null) { - LOG.debug("Ignored parent without IV"); - } else { - parents.add(iv); - } - } - return this; - } - - public Builder addParent(InventoryVector iv) { - if (iv != null) { - parents.add(iv); - } - return this; - } - - public Builder addFile(File file, Attachment.Disposition disposition) { - if (file != null) { - try { - files.add(new Attachment.Builder() - .name(file.getName()) - .disposition(disposition) - .type(URLConnection.guessContentTypeFromStream(new FileInputStream(file))) - .data(Files.readAllBytes(file.toPath())) - .build()); - } catch (IOException e) { - LOG.error(e.getMessage(), e); - } - } - return this; - } - - public Builder addFile(Attachment file) { - if (file != null) { - files.add(file); - } - return this; - } - - public ExtendedEncoding build() { - return new ExtendedEncoding(new Message(this)); - } - } - - public static class Unpacker implements ExtendedEncoding.Unpacker<Message> { - @Override - public String getType() { - return TYPE; - } - - @Override - public Message unpack(MPMap<MPString, MPType<?>> map) { - Message.Builder builder = new Message.Builder(); - builder.subject(str(map.get(mp("subject")))); - builder.body(str(map.get(mp("body")))); - @SuppressWarnings("unchecked") - MPArray<MPBinary> parents = (MPArray<MPBinary>) map.get(mp("parents")); - if (parents != null) { - for (MPBinary parent : parents) { - builder.addParent(InventoryVector.fromHash(parent.getValue())); - } - } - @SuppressWarnings("unchecked") - MPArray<MPMap<MPString, MPType<?>>> files = (MPArray<MPMap<MPString, MPType<?>>>) map.get(mp("files")); - if (files != null) { - for (MPMap<MPString, MPType<?>> item : files) { - Attachment.Builder b = new Attachment.Builder(); - b.name(str(item.get(mp("name")))); - b.data(bin(item.get(mp("data")))); - b.type(str(item.get(mp("type")))); - String disposition = str(item.get(mp("disposition"))); - if ("inline".equals(disposition)) { - b.inline(); - } else if ("attachment".equals(disposition)) { - b.attachment(); - } - builder.addFile(b.build()); - } - } - - return new Message(builder); - } - - private byte[] bin(MPType data) { - if (data instanceof MPBinary) { - return ((MPBinary) data).getValue(); - } else { - return null; - } - } - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/extended/Message.kt b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/extended/Message.kt new file mode 100644 index 0000000..c378394 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/extended/Message.kt @@ -0,0 +1,184 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.entity.valueobject.extended + +import ch.dissem.bitmessage.entity.Plaintext +import ch.dissem.bitmessage.entity.valueobject.ExtendedEncoding +import ch.dissem.bitmessage.entity.valueobject.InventoryVector +import ch.dissem.bitmessage.utils.Strings.str +import ch.dissem.msgpack.types.* +import ch.dissem.msgpack.types.Utils.mp +import org.slf4j.LoggerFactory +import java.io.File +import java.io.FileInputStream +import java.io.IOException +import java.net.URLConnection +import java.nio.file.Files +import java.util.* + +/** + * Extended encoding type 'message'. Properties 'parents' and 'files' not yet supported by PyBitmessage, so they might not work + * properly with future PyBitmessage implementations. + */ +data class Message constructor( + val subject: String, + val body: String, + val parents: List<InventoryVector>, + val files: List<Attachment> +) : ExtendedEncoding.ExtendedType { + + override val type: String = TYPE + + override fun pack(): MPMap<MPString, MPType<*>> { + val result = MPMap<MPString, MPType<*>>() + result.put(mp(""), mp(TYPE)) + result.put(mp("subject"), mp(subject)) + result.put(mp("body"), mp(body)) + + if (!files.isEmpty()) { + val items = MPArray<MPMap<MPString, MPType<*>>>() + result.put(mp("files"), items) + for (file in files) { + val item = MPMap<MPString, MPType<*>>() + item.put(mp("name"), mp(file.name)) + item.put(mp("data"), mp(*file.data)) + item.put(mp("type"), mp(file.type)) + item.put(mp("disposition"), mp(file.disposition.name)) + items.add(item) + } + } + if (!parents.isEmpty()) { + val items = MPArray<MPBinary>() + result.put(mp("parents"), items) + for ((hash) in parents) { + items.add(mp(*hash)) + } + } + return result + } + + class Builder { + private var subject: String? = null + private var body: String? = null + private val parents = LinkedList<InventoryVector>() + private val files = LinkedList<Attachment>() + + fun subject(subject: String): Builder { + this.subject = subject + return this + } + + fun body(body: String): Builder { + this.body = body + return this + } + + fun addParent(parent: Plaintext?): Builder { + if (parent != null) { + val iv = parent.inventoryVector + if (iv == null) { + LOG.debug("Ignored parent without IV") + } else { + parents.add(iv) + } + } + return this + } + + fun addParent(iv: InventoryVector?): Builder { + if (iv != null) { + parents.add(iv) + } + return this + } + + fun addFile(file: File?, disposition: Attachment.Disposition): Builder { + if (file != null) { + try { + files.add(Attachment.Builder() + .name(file.name) + .disposition(disposition) + .type(URLConnection.guessContentTypeFromStream(FileInputStream(file))) + .data(Files.readAllBytes(file.toPath())) + .build()) + } catch (e: IOException) { + LOG.error(e.message, e) + } + + } + return this + } + + fun addFile(file: Attachment?): Builder { + if (file != null) { + files.add(file) + } + return this + } + + fun build(): ExtendedEncoding { + return ExtendedEncoding(Message(subject!!, body!!, parents, files)) + } + } + + class Unpacker : ExtendedEncoding.Unpacker<Message> { + override val type: String = TYPE + + override fun unpack(map: MPMap<MPString, MPType<*>>): Message { + val subject = str(map[mp("subject")]) ?: "" + val body = str(map[mp("body")]) ?: "" + val parents = LinkedList<InventoryVector>() + val files = LinkedList<Attachment>() + val mpParents = map[mp("parents")] as? MPArray<*> + for (parent in mpParents ?: emptyList<MPArray<MPBinary>>()) { + parents.add(InventoryVector.fromHash( + (parent as? MPBinary)?.value ?: continue + ) ?: continue) + } + val mpFiles = map[mp("files")] as? MPArray<*> + for (item in mpFiles ?: emptyList<Any>()) { + if (item is MPMap<*, *>) { + val b = Attachment.Builder() + b.name(str(item[mp("name")])!!) + b.data( + bin(item[mp("data")] ?: continue) ?: continue + ) + b.type(str(item[mp("type")])!!) + val disposition = str(item[mp("disposition")]) + if ("inline" == disposition) { + b.inline() + } else if ("attachment" == disposition) { + b.attachment() + } + files.add(b.build()) + } + } + + return Message(subject, body, parents, files) + } + + private fun bin(data: MPType<*>): ByteArray? { + return (data as? MPBinary)?.value + } + } + + companion object { + private val LOG = LoggerFactory.getLogger(Message::class.java) + + val TYPE = "message" + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/extended/Vote.java b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/extended/Vote.java deleted file mode 100644 index 1c13440..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/extended/Vote.java +++ /dev/null @@ -1,114 +0,0 @@ -package ch.dissem.bitmessage.entity.valueobject.extended; - -import ch.dissem.bitmessage.entity.Plaintext; -import ch.dissem.bitmessage.entity.valueobject.ExtendedEncoding; -import ch.dissem.bitmessage.entity.valueobject.InventoryVector; -import ch.dissem.msgpack.types.*; - -import java.io.IOException; -import java.util.Objects; - -import static ch.dissem.bitmessage.utils.Strings.str; -import static ch.dissem.msgpack.types.Utils.mp; - -/** - * Extended encoding type 'vote'. Specification still outstanding, so this will need some work. - */ -public class Vote implements ExtendedEncoding.ExtendedType { - private static final long serialVersionUID = -8427038604209964837L; - - public static final String TYPE = "vote"; - - private InventoryVector msgId; - private String vote; - - private Vote(Builder builder) { - msgId = builder.msgId; - vote = builder.vote; - } - - @Override - public String getType() { - return TYPE; - } - - public InventoryVector getMsgId() { - return msgId; - } - - public String getVote() { - return vote; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Vote vote1 = (Vote) o; - return Objects.equals(msgId, vote1.msgId) && - Objects.equals(vote, vote1.vote); - } - - @Override - public int hashCode() { - return Objects.hash(msgId, vote); - } - - @Override - public MPMap<MPString, MPType<?>> pack() throws IOException { - MPMap<MPString, MPType<?>> result = new MPMap<>(); - result.put(mp(""), mp(TYPE)); - result.put(mp("msgId"), mp(msgId.getHash())); - result.put(mp("vote"), mp(vote)); - return result; - } - - public static class Builder { - private InventoryVector msgId; - private String vote; - - public ExtendedEncoding up(Plaintext message) { - msgId = message.getInventoryVector(); - vote = "1"; - return new ExtendedEncoding(new Vote(this)); - } - - public ExtendedEncoding down(Plaintext message) { - msgId = message.getInventoryVector(); - vote = "1"; - return new ExtendedEncoding(new Vote(this)); - } - - public Builder msgId(InventoryVector iv) { - this.msgId = iv; - return this; - } - - public Builder vote(String vote) { - this.vote = vote; - return this; - } - - public ExtendedEncoding build() { - return new ExtendedEncoding(new Vote(this)); - } - } - - public static class Unpacker implements ExtendedEncoding.Unpacker<Vote> { - @Override - public String getType() { - return TYPE; - } - - @Override - public Vote unpack(MPMap<MPString, MPType<?>> map) { - Vote.Builder builder = new Vote.Builder(); - MPType<?> msgId = map.get(mp("msgId")); - if (msgId instanceof MPBinary) { - builder.msgId(InventoryVector.fromHash(((MPBinary) msgId).getValue())); - } - builder.vote(str(map.get(mp("vote")))); - return new Vote(builder); - } - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/extended/Vote.kt b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/extended/Vote.kt new file mode 100644 index 0000000..d26d38a --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/extended/Vote.kt @@ -0,0 +1,89 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.entity.valueobject.extended + +import ch.dissem.bitmessage.entity.Plaintext +import ch.dissem.bitmessage.entity.valueobject.ExtendedEncoding +import ch.dissem.bitmessage.entity.valueobject.InventoryVector +import ch.dissem.bitmessage.utils.Strings.str +import ch.dissem.msgpack.types.MPBinary +import ch.dissem.msgpack.types.MPMap +import ch.dissem.msgpack.types.MPString +import ch.dissem.msgpack.types.MPType +import ch.dissem.msgpack.types.Utils.mp + +/** + * Extended encoding type 'vote'. Specification still outstanding, so this will need some work. + */ +data class Vote constructor(val msgId: InventoryVector, val vote: String) : ExtendedEncoding.ExtendedType { + + override val type: String = TYPE + + override fun pack(): MPMap<MPString, MPType<*>> { + val result = MPMap<MPString, MPType<*>>() + result.put(mp(""), mp(TYPE)) + result.put(mp("msgId"), mp(*msgId.hash)) + result.put(mp("vote"), mp(vote)) + return result + } + + class Builder { + private var msgId: InventoryVector? = null + private var vote: String? = null + + fun up(message: Plaintext): ExtendedEncoding { + msgId = message.inventoryVector + vote = "1" + return ExtendedEncoding(Vote(msgId!!, vote!!)) + } + + fun down(message: Plaintext): ExtendedEncoding { + msgId = message.inventoryVector + vote = "-1" + return ExtendedEncoding(Vote(msgId!!, vote!!)) + } + + fun msgId(iv: InventoryVector): Builder { + this.msgId = iv + return this + } + + fun vote(vote: String): Builder { + this.vote = vote + return this + } + + fun build(): ExtendedEncoding { + return ExtendedEncoding(Vote(msgId!!, vote!!)) + } + } + + class Unpacker : ExtendedEncoding.Unpacker<Vote> { + override val type: String + get() = TYPE + + override fun unpack(map: MPMap<MPString, MPType<*>>): Vote { + val msgId = InventoryVector.fromHash((map[mp("msgId")] as? MPBinary)?.value) ?: throw IllegalArgumentException("data doesn't contain proper msgId") + val vote = str(map[mp("vote")]) ?: throw IllegalArgumentException("no vote given") + return Vote(msgId, vote) + } + } + + companion object { + @JvmField val TYPE = "vote" + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/exception/AddressFormatException.java b/core/src/main/java/ch/dissem/bitmessage/exception/AddressFormatException.kt similarity index 67% rename from core/src/main/java/ch/dissem/bitmessage/exception/AddressFormatException.java rename to core/src/main/java/ch/dissem/bitmessage/exception/AddressFormatException.kt index 9f6674d..d838ed8 100644 --- a/core/src/main/java/ch/dissem/bitmessage/exception/AddressFormatException.java +++ b/core/src/main/java/ch/dissem/bitmessage/exception/AddressFormatException.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015 Christian Basler + * Copyright 2017 Christian Basler * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,15 +14,9 @@ * limitations under the License. */ -package ch.dissem.bitmessage.exception; +package ch.dissem.bitmessage.exception /** * Indicates an illegal Bitmessage address */ -public class AddressFormatException extends RuntimeException { - private static final long serialVersionUID = 6943764578672021573L; - - public AddressFormatException(String message) { - super(message); - } -} +class AddressFormatException(message: String) : RuntimeException(message) diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/CustomCommandHandler.java b/core/src/main/java/ch/dissem/bitmessage/exception/ApplicationException.kt similarity index 69% rename from core/src/main/java/ch/dissem/bitmessage/ports/CustomCommandHandler.java rename to core/src/main/java/ch/dissem/bitmessage/exception/ApplicationException.kt index 8e49586..b520c11 100644 --- a/core/src/main/java/ch/dissem/bitmessage/ports/CustomCommandHandler.java +++ b/core/src/main/java/ch/dissem/bitmessage/exception/ApplicationException.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015 Christian Basler + * Copyright 2017 Christian Basler * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,14 +14,15 @@ * limitations under the License. */ -package ch.dissem.bitmessage.ports; - -import ch.dissem.bitmessage.entity.CustomMessage; -import ch.dissem.bitmessage.entity.MessagePayload; +package ch.dissem.bitmessage.exception /** * @author Christian Basler */ -public interface CustomCommandHandler { - MessagePayload handle(CustomMessage request); +class ApplicationException : RuntimeException { + + constructor(cause: Throwable) : super(cause) + + constructor(message: String) : super(message) + } diff --git a/core/src/main/java/ch/dissem/bitmessage/exception/DecryptionFailedException.kt b/core/src/main/java/ch/dissem/bitmessage/exception/DecryptionFailedException.kt new file mode 100644 index 0000000..4d898f8 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/exception/DecryptionFailedException.kt @@ -0,0 +1,19 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.exception + +class DecryptionFailedException : Exception() diff --git a/core/src/main/java/ch/dissem/bitmessage/exception/InsufficientProofOfWorkException.java b/core/src/main/java/ch/dissem/bitmessage/exception/InsufficientProofOfWorkException.java deleted file mode 100644 index 94a5d84..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/exception/InsufficientProofOfWorkException.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.exception; - -import ch.dissem.bitmessage.utils.Strings; - -import java.io.IOException; -import java.util.Arrays; - -public class InsufficientProofOfWorkException extends IOException { - private static final long serialVersionUID = 9105580366564571318L; - - public InsufficientProofOfWorkException(byte[] target, byte[] hash) { - super("Insufficient proof of work: " + Strings.hex(target) + " required, " + Strings.hex(Arrays.copyOfRange(hash, 0, 8)) + " achieved."); - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/exception/InsufficientProofOfWorkException.kt b/core/src/main/java/ch/dissem/bitmessage/exception/InsufficientProofOfWorkException.kt new file mode 100644 index 0000000..a878d88 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/exception/InsufficientProofOfWorkException.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.exception + +import ch.dissem.bitmessage.utils.Strings +import java.io.IOException +import java.util.* + +class InsufficientProofOfWorkException(target: ByteArray, hash: ByteArray) : IOException( + "Insufficient proof of work: " + Strings.hex(target) + " required, " + + Strings.hex(Arrays.copyOfRange(hash, 0, 8)) + " achieved.") diff --git a/core/src/main/java/ch/dissem/bitmessage/exception/NodeException.java b/core/src/main/java/ch/dissem/bitmessage/exception/NodeException.kt similarity index 64% rename from core/src/main/java/ch/dissem/bitmessage/exception/NodeException.java rename to core/src/main/java/ch/dissem/bitmessage/exception/NodeException.kt index cecd950..0536991 100644 --- a/core/src/main/java/ch/dissem/bitmessage/exception/NodeException.java +++ b/core/src/main/java/ch/dissem/bitmessage/exception/NodeException.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015 Christian Basler + * Copyright 2017 Christian Basler * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,21 +14,13 @@ * limitations under the License. */ -package ch.dissem.bitmessage.exception; +package ch.dissem.bitmessage.exception /** * An exception on the node that's severe enough to cause the client to disconnect this node. - * + * @author Ch. Basler */ -public class NodeException extends RuntimeException { - private static final long serialVersionUID = 2965325796118227802L; - - public NodeException(String message) { - super(message); - } - - public NodeException(String message, Throwable cause) { - super(message, cause); - } +class NodeException(message: String?, cause: Throwable? = null) : RuntimeException(message ?: cause?.message, cause) { + constructor(message: String) : this(message, null) } diff --git a/core/src/main/java/ch/dissem/bitmessage/factory/BufferPool.java b/core/src/main/java/ch/dissem/bitmessage/factory/BufferPool.java deleted file mode 100644 index 65c1d34..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/factory/BufferPool.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2016 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.factory; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.nio.ByteBuffer; -import java.util.Map; -import java.util.Stack; -import java.util.TreeMap; - -import static ch.dissem.bitmessage.ports.NetworkHandler.HEADER_SIZE; -import static ch.dissem.bitmessage.ports.NetworkHandler.MAX_PAYLOAD_SIZE; - -/** - * A pool for {@link ByteBuffer}s. As they may use up a lot of memory, - * they should be reused as efficiently as possible. - */ -class BufferPool { - private static final Logger LOG = LoggerFactory.getLogger(BufferPool.class); - - public static final BufferPool bufferPool = new BufferPool(); - - private final Map<Integer, Stack<ByteBuffer>> pools = new TreeMap<>(); - - private BufferPool() { - pools.put(HEADER_SIZE, new Stack<ByteBuffer>()); - pools.put(54, new Stack<ByteBuffer>()); - pools.put(1000, new Stack<ByteBuffer>()); - pools.put(60000, new Stack<ByteBuffer>()); - pools.put(MAX_PAYLOAD_SIZE, new Stack<ByteBuffer>()); - } - - public synchronized ByteBuffer allocate(int capacity) { - Integer targetSize = getTargetSize(capacity); - Stack<ByteBuffer> pool = pools.get(targetSize); - if (pool.isEmpty()) { - LOG.trace("Creating new buffer of size " + targetSize); - return ByteBuffer.allocate(targetSize); - } else { - return pool.pop(); - } - } - - /** - * Returns a buffer that has the size of the Bitmessage network message header, 24 bytes. - * - * @return a buffer of size 24 - */ - public synchronized ByteBuffer allocateHeaderBuffer() { - Stack<ByteBuffer> pool = pools.get(HEADER_SIZE); - if (pool.isEmpty()) { - return ByteBuffer.allocate(HEADER_SIZE); - } else { - return pool.pop(); - } - } - - public synchronized void deallocate(ByteBuffer buffer) { - buffer.clear(); - Stack<ByteBuffer> pool = pools.get(buffer.capacity()); - if (pool == null) { - throw new IllegalArgumentException("Illegal buffer capacity " + buffer.capacity() + - " one of " + pools.keySet() + " expected."); - } else { - pool.push(buffer); - } - } - - private Integer getTargetSize(int capacity) { - for (Integer size : pools.keySet()) { - if (size >= capacity) return size; - } - throw new IllegalArgumentException("Requested capacity too large: " + - "requested=" + capacity + "; max=" + MAX_PAYLOAD_SIZE); - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/factory/BufferPool.kt b/core/src/main/java/ch/dissem/bitmessage/factory/BufferPool.kt new file mode 100644 index 0000000..a301d37 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/factory/BufferPool.kt @@ -0,0 +1,79 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.factory + +import ch.dissem.bitmessage.constants.Network.HEADER_SIZE +import ch.dissem.bitmessage.constants.Network.MAX_PAYLOAD_SIZE +import org.slf4j.LoggerFactory +import java.nio.ByteBuffer +import java.util.* + +/** + * A pool for [ByteBuffer]s. As they may use up a lot of memory, + * they should be reused as efficiently as possible. + */ +object BufferPool { + private val LOG = LoggerFactory.getLogger(BufferPool::class.java) + + private val pools = mapOf( + HEADER_SIZE to Stack<ByteBuffer>(), + 54 to Stack<ByteBuffer>(), + 1000 to Stack<ByteBuffer>(), + 60000 to Stack<ByteBuffer>(), + MAX_PAYLOAD_SIZE to Stack<ByteBuffer>() + ) + + @Synchronized fun allocate(capacity: Int): ByteBuffer { + val targetSize = getTargetSize(capacity) + val pool = pools[targetSize] + if (pool == null || pool.isEmpty()) { + LOG.trace("Creating new buffer of size " + targetSize!!) + return ByteBuffer.allocate(targetSize) + } else { + return pool.pop() + } + } + + /** + * Returns a buffer that has the size of the Bitmessage network message header, 24 bytes. + + * @return a buffer of size 24 + */ + @Synchronized fun allocateHeaderBuffer(): ByteBuffer { + val pool = pools[HEADER_SIZE] + if (pool == null || pool.isEmpty()) { + return ByteBuffer.allocate(HEADER_SIZE) + } else { + return pool.pop() + } + } + + @Synchronized fun deallocate(buffer: ByteBuffer) { + buffer.clear() + val pool = pools[buffer.capacity()] + pool?.push(buffer) ?: throw IllegalArgumentException("Illegal buffer capacity " + buffer.capacity() + + " one of " + pools.keys + " expected.") + } + + private fun getTargetSize(capacity: Int): Int? { + for (size in pools.keys) { + if (size >= capacity) return size + } + throw IllegalArgumentException("Requested capacity too large: " + + "requested=" + capacity + "; max=" + MAX_PAYLOAD_SIZE) + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/factory/ExtendedEncodingFactory.java b/core/src/main/java/ch/dissem/bitmessage/factory/ExtendedEncodingFactory.java deleted file mode 100644 index b94b5f9..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/factory/ExtendedEncodingFactory.java +++ /dev/null @@ -1,62 +0,0 @@ -package ch.dissem.bitmessage.factory; - -import ch.dissem.bitmessage.entity.valueobject.ExtendedEncoding; -import ch.dissem.bitmessage.entity.valueobject.extended.Message; -import ch.dissem.bitmessage.entity.valueobject.extended.Vote; -import ch.dissem.bitmessage.exception.ApplicationException; -import ch.dissem.msgpack.Reader; -import ch.dissem.msgpack.types.MPMap; -import ch.dissem.msgpack.types.MPString; -import ch.dissem.msgpack.types.MPType; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; -import java.util.zip.InflaterInputStream; - -import static ch.dissem.bitmessage.utils.Strings.str; - -/** - * Factory that creates {@link ExtendedEncoding} objects from byte arrays. You can register your own types by adding a - * {@link ExtendedEncoding.Unpacker} using {@link #registerFactory(ExtendedEncoding.Unpacker)}. - */ -public class ExtendedEncodingFactory { - private static final Logger LOG = LoggerFactory.getLogger(ExtendedEncodingFactory.class); - private static final ExtendedEncodingFactory INSTANCE = new ExtendedEncodingFactory(); - private static final MPString KEY_MESSAGE_TYPE = new MPString(""); - private Map<String, ExtendedEncoding.Unpacker<?>> factories = new HashMap<>(); - - private ExtendedEncodingFactory() { - registerFactory(new Message.Unpacker()); - registerFactory(new Vote.Unpacker()); - } - - public void registerFactory(ExtendedEncoding.Unpacker<?> factory) { - factories.put(factory.getType(), factory); - } - - - public ExtendedEncoding unzip(byte[] zippedData) { - try (InflaterInputStream unzipper = new InflaterInputStream(new ByteArrayInputStream(zippedData))) { - Reader reader = Reader.getInstance(); - @SuppressWarnings("unchecked") - MPMap<MPString, MPType<?>> map = (MPMap<MPString, MPType<?>>) reader.read(unzipper); - MPType<?> messageType = map.get(KEY_MESSAGE_TYPE); - if (messageType == null) { - LOG.error("Missing message type"); - return null; - } - ExtendedEncoding.Unpacker<?> factory = factories.get(str(messageType)); - return new ExtendedEncoding(factory.unpack(map)); - } catch (ClassCastException | IOException e) { - throw new ApplicationException(e); - } - } - - public static ExtendedEncodingFactory getInstance() { - return INSTANCE; - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/factory/ExtendedEncodingFactory.kt b/core/src/main/java/ch/dissem/bitmessage/factory/ExtendedEncodingFactory.kt new file mode 100644 index 0000000..d8833c7 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/factory/ExtendedEncodingFactory.kt @@ -0,0 +1,74 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.factory + +import ch.dissem.bitmessage.entity.valueobject.ExtendedEncoding +import ch.dissem.bitmessage.entity.valueobject.extended.Message +import ch.dissem.bitmessage.entity.valueobject.extended.Vote +import ch.dissem.bitmessage.exception.ApplicationException +import ch.dissem.bitmessage.utils.Strings.str +import ch.dissem.msgpack.Reader +import ch.dissem.msgpack.types.MPMap +import ch.dissem.msgpack.types.MPString +import ch.dissem.msgpack.types.MPType +import org.slf4j.LoggerFactory +import java.io.ByteArrayInputStream +import java.util.* +import java.util.zip.InflaterInputStream + +/** + * Factory that creates [ExtendedEncoding] objects from byte arrays. You can register your own types by adding a + * [ExtendedEncoding.Unpacker] using [.registerFactory]. + */ +object ExtendedEncodingFactory { + + private val LOG = LoggerFactory.getLogger(ExtendedEncodingFactory::class.java) + private val KEY_MESSAGE_TYPE = MPString("") + + private val factories = HashMap<String, ExtendedEncoding.Unpacker<*>>() + + init { + registerFactory(Message.Unpacker()) + registerFactory(Vote.Unpacker()) + } + + fun registerFactory(factory: ExtendedEncoding.Unpacker<*>) { + factories.put(factory.type, factory) + } + + + fun unzip(zippedData: ByteArray): ExtendedEncoding? { + try { + InflaterInputStream(ByteArrayInputStream(zippedData)).use { unzipper -> + val reader = Reader.getInstance() + @Suppress("UNCHECKED_CAST") + val map = reader.read(unzipper) as MPMap<MPString, MPType<*>> + val messageType = map[KEY_MESSAGE_TYPE] + if (messageType == null) { + LOG.error("Missing message type") + return null + } + val factory = factories[str(messageType)] + return ExtendedEncoding( + factory?.unpack(map) ?: return null + ) + } + } catch (e: ClassCastException) { + throw ApplicationException(e) + } + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/factory/Factory.java b/core/src/main/java/ch/dissem/bitmessage/factory/Factory.java deleted file mode 100644 index 07b3161..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/factory/Factory.java +++ /dev/null @@ -1,217 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.factory; - -import ch.dissem.bitmessage.entity.BitmessageAddress; -import ch.dissem.bitmessage.entity.NetworkMessage; -import ch.dissem.bitmessage.entity.ObjectMessage; -import ch.dissem.bitmessage.entity.Plaintext; -import ch.dissem.bitmessage.entity.payload.*; -import ch.dissem.bitmessage.entity.valueobject.PrivateKey; -import ch.dissem.bitmessage.exception.NodeException; -import ch.dissem.bitmessage.utils.TTL; -import ch.dissem.bitmessage.utils.UnixTime; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.io.InputStream; -import java.net.SocketException; -import java.net.SocketTimeoutException; - -import static ch.dissem.bitmessage.entity.payload.ObjectType.MSG; -import static ch.dissem.bitmessage.utils.Singleton.cryptography; - -/** - * Creates {@link NetworkMessage} objects from {@link InputStream InputStreams} - */ -public class Factory { - private static final Logger LOG = LoggerFactory.getLogger(Factory.class); - - public static NetworkMessage getNetworkMessage(int version, InputStream stream) throws SocketTimeoutException { - try { - return V3MessageFactory.read(stream); - } catch (SocketTimeoutException | NodeException e) { - throw e; - } catch (SocketException e) { - throw new NodeException(e.getMessage(), e); - } catch (Exception e) { - LOG.error(e.getMessage(), e); - return null; - } - } - - public static ObjectMessage getObjectMessage(int version, InputStream stream, int length) { - try { - return V3MessageFactory.readObject(stream, length); - } catch (IOException e) { - LOG.error(e.getMessage(), e); - return null; - } - } - - public static Pubkey createPubkey(long version, long stream, byte[] publicSigningKey, byte[] publicEncryptionKey, - long nonceTrialsPerByte, long extraBytes, Pubkey.Feature... features) { - return createPubkey(version, stream, publicSigningKey, publicEncryptionKey, nonceTrialsPerByte, extraBytes, - Pubkey.Feature.bitfield(features)); - } - - public static Pubkey createPubkey(long version, long stream, byte[] publicSigningKey, byte[] publicEncryptionKey, - long nonceTrialsPerByte, long extraBytes, int behaviourBitfield) { - if (publicSigningKey.length != 64 && publicSigningKey.length != 65) - throw new IllegalArgumentException("64 bytes signing key expected, but it was " - + publicSigningKey.length + " bytes long."); - if (publicEncryptionKey.length != 64 && publicEncryptionKey.length != 65) - throw new IllegalArgumentException("64 bytes encryption key expected, but it was " - + publicEncryptionKey.length + " bytes long."); - - switch ((int) version) { - case 2: - return new V2Pubkey.Builder() - .stream(stream) - .publicSigningKey(publicSigningKey) - .publicEncryptionKey(publicEncryptionKey) - .behaviorBitfield(behaviourBitfield) - .build(); - case 3: - return new V3Pubkey.Builder() - .stream(stream) - .publicSigningKey(publicSigningKey) - .publicEncryptionKey(publicEncryptionKey) - .behaviorBitfield(behaviourBitfield) - .nonceTrialsPerByte(nonceTrialsPerByte) - .extraBytes(extraBytes) - .build(); - case 4: - return new V4Pubkey( - new V3Pubkey.Builder() - .stream(stream) - .publicSigningKey(publicSigningKey) - .publicEncryptionKey(publicEncryptionKey) - .behaviorBitfield(behaviourBitfield) - .nonceTrialsPerByte(nonceTrialsPerByte) - .extraBytes(extraBytes) - .build() - ); - default: - throw new IllegalArgumentException("Unexpected pubkey version " + version); - } - } - - public static BitmessageAddress createIdentityFromPrivateKey(String address, - byte[] privateSigningKey, byte[] privateEncryptionKey, - long nonceTrialsPerByte, long extraBytes, - int behaviourBitfield) { - BitmessageAddress temp = new BitmessageAddress(address); - PrivateKey privateKey = new PrivateKey(privateSigningKey, privateEncryptionKey, - createPubkey(temp.getVersion(), temp.getStream(), - cryptography().createPublicKey(privateSigningKey), - cryptography().createPublicKey(privateEncryptionKey), - nonceTrialsPerByte, extraBytes, behaviourBitfield)); - BitmessageAddress result = new BitmessageAddress(privateKey); - if (!result.getAddress().equals(address)) { - throw new IllegalArgumentException("Address not matching private key. Address: " + address - + "; Address derived from private key: " + result.getAddress()); - } - return result; - } - - public static BitmessageAddress generatePrivateAddress(boolean shorter, - long stream, - Pubkey.Feature... features) { - return new BitmessageAddress(new PrivateKey(shorter, stream, 1000, 1000, features)); - } - - static ObjectPayload getObjectPayload(long objectType, - long version, - long streamNumber, - InputStream stream, - int length) throws IOException { - ObjectType type = ObjectType.fromNumber(objectType); - if (type != null) { - switch (type) { - case GET_PUBKEY: - return parseGetPubkey(version, streamNumber, stream, length); - case PUBKEY: - return parsePubkey(version, streamNumber, stream, length); - case MSG: - return parseMsg(version, streamNumber, stream, length); - case BROADCAST: - return parseBroadcast(version, streamNumber, stream, length); - default: - LOG.error("This should not happen, someone broke something in the code!"); - } - } - // fallback: just store the message - we don't really care what it is - LOG.trace("Unexpected object type: " + objectType); - return GenericPayload.read(version, streamNumber, stream, length); - } - - private static ObjectPayload parseGetPubkey(long version, long streamNumber, InputStream stream, int length) throws IOException { - return GetPubkey.read(stream, streamNumber, length, version); - } - - public static Pubkey readPubkey(long version, long stream, InputStream is, int length, boolean encrypted) throws IOException { - switch ((int) version) { - case 2: - return V2Pubkey.read(is, stream); - case 3: - return V3Pubkey.read(is, stream); - case 4: - return V4Pubkey.read(is, stream, length, encrypted); - } - LOG.debug("Unexpected pubkey version " + version + ", handling as generic payload object"); - return null; - } - - private static ObjectPayload parsePubkey(long version, long streamNumber, InputStream stream, int length) throws IOException { - Pubkey pubkey = readPubkey(version, streamNumber, stream, length, true); - return pubkey != null ? pubkey : GenericPayload.read(version, streamNumber, stream, length); - } - - private static ObjectPayload parseMsg(long version, long streamNumber, InputStream stream, int length) throws IOException { - return Msg.read(stream, streamNumber, length); - } - - private static ObjectPayload parseBroadcast(long version, long streamNumber, InputStream stream, int length) throws IOException { - switch ((int) version) { - case 4: - return V4Broadcast.read(stream, streamNumber, length); - case 5: - return V5Broadcast.read(stream, streamNumber, length); - default: - LOG.debug("Encountered unknown broadcast version " + version); - return GenericPayload.read(version, streamNumber, stream, length); - } - } - - public static Broadcast getBroadcast(Plaintext plaintext) { - BitmessageAddress sendingAddress = plaintext.getFrom(); - if (sendingAddress.getVersion() < 4) { - return new V4Broadcast(sendingAddress, plaintext); - } else { - return new V5Broadcast(sendingAddress, plaintext); - } - } - - public static ObjectMessage createAck(Plaintext plaintext) { - if (plaintext == null || plaintext.getAckData() == null) - return null; - GenericPayload ack = new GenericPayload(3, plaintext.getFrom().getStream(), plaintext.getAckData()); - return new ObjectMessage.Builder().objectType(MSG).payload(ack).expiresTime(UnixTime.now(plaintext.getTTL())).build(); - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/factory/Factory.kt b/core/src/main/java/ch/dissem/bitmessage/factory/Factory.kt new file mode 100644 index 0000000..767e66b --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/factory/Factory.kt @@ -0,0 +1,206 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.factory + +import ch.dissem.bitmessage.entity.BitmessageAddress +import ch.dissem.bitmessage.entity.NetworkMessage +import ch.dissem.bitmessage.entity.ObjectMessage +import ch.dissem.bitmessage.entity.Plaintext +import ch.dissem.bitmessage.entity.payload.* +import ch.dissem.bitmessage.entity.payload.ObjectType.MSG +import ch.dissem.bitmessage.entity.valueobject.PrivateKey +import ch.dissem.bitmessage.exception.NodeException +import ch.dissem.bitmessage.utils.Singleton.cryptography +import ch.dissem.bitmessage.utils.UnixTime +import org.slf4j.LoggerFactory +import java.io.IOException +import java.io.InputStream +import java.net.SocketException +import java.net.SocketTimeoutException + +/** + * Creates [NetworkMessage] objects from [InputStreams][InputStream] + */ +object Factory { + private val LOG = LoggerFactory.getLogger(Factory::class.java) + + @Throws(SocketTimeoutException::class) + @JvmStatic fun getNetworkMessage(@Suppress("UNUSED_PARAMETER") version: Int, stream: InputStream): NetworkMessage? { + try { + return V3MessageFactory.read(stream) + } catch (e: Exception) { + when (e) { + is SocketTimeoutException, + is NodeException -> throw e + is SocketException -> throw NodeException(e.message, e) + else -> { + LOG.error(e.message, e) + return null + } + } + } + + } + + @JvmStatic fun getObjectMessage(version: Int, stream: InputStream, length: Int): ObjectMessage? { + try { + return V3MessageFactory.readObject(stream, length) + } catch (e: IOException) { + LOG.error(e.message, e) + return null + } + } + + @JvmStatic fun createPubkey(version: Long, stream: Long, publicSigningKey: ByteArray, publicEncryptionKey: ByteArray, + nonceTrialsPerByte: Long, extraBytes: Long, vararg features: Pubkey.Feature): Pubkey { + return createPubkey(version, stream, publicSigningKey, publicEncryptionKey, nonceTrialsPerByte, extraBytes, + Pubkey.Feature.bitfield(*features)) + } + + @JvmStatic fun createPubkey(version: Long, stream: Long, publicSigningKey: ByteArray, publicEncryptionKey: ByteArray, + nonceTrialsPerByte: Long, extraBytes: Long, behaviourBitfield: Int): Pubkey { + if (publicSigningKey.size != 64 && publicSigningKey.size != 65) + throw IllegalArgumentException("64 bytes signing key expected, but it was " + + publicSigningKey.size + " bytes long.") + if (publicEncryptionKey.size != 64 && publicEncryptionKey.size != 65) + throw IllegalArgumentException("64 bytes encryption key expected, but it was " + + publicEncryptionKey.size + " bytes long.") + + when (version.toInt()) { + 2 -> return V2Pubkey.Builder() + .stream(stream) + .publicSigningKey(publicSigningKey) + .publicEncryptionKey(publicEncryptionKey) + .behaviorBitfield(behaviourBitfield) + .build() + 3 -> return V3Pubkey.Builder() + .stream(stream) + .publicSigningKey(publicSigningKey) + .publicEncryptionKey(publicEncryptionKey) + .behaviorBitfield(behaviourBitfield) + .nonceTrialsPerByte(nonceTrialsPerByte) + .extraBytes(extraBytes) + .build() + 4 -> return V4Pubkey( + V3Pubkey.Builder() + .stream(stream) + .publicSigningKey(publicSigningKey) + .publicEncryptionKey(publicEncryptionKey) + .behaviorBitfield(behaviourBitfield) + .nonceTrialsPerByte(nonceTrialsPerByte) + .extraBytes(extraBytes) + .build() + ) + else -> throw IllegalArgumentException("Unexpected pubkey version " + version) + } + } + + @JvmStatic fun createIdentityFromPrivateKey(address: String, + privateSigningKey: ByteArray, privateEncryptionKey: ByteArray, + nonceTrialsPerByte: Long, extraBytes: Long, + behaviourBitfield: Int): BitmessageAddress { + val temp = BitmessageAddress(address) + val privateKey = PrivateKey(privateSigningKey, privateEncryptionKey, + createPubkey(temp.version, temp.stream, + cryptography().createPublicKey(privateSigningKey), + cryptography().createPublicKey(privateEncryptionKey), + nonceTrialsPerByte, extraBytes, behaviourBitfield)) + val result = BitmessageAddress(privateKey) + if (result.address != address) { + throw IllegalArgumentException("Address not matching private key. Address: " + address + + "; Address derived from private key: " + result.address) + } + return result + } + + @JvmStatic fun generatePrivateAddress(shorter: Boolean, + stream: Long, + vararg features: Pubkey.Feature): BitmessageAddress { + return BitmessageAddress(PrivateKey(shorter, stream, 1000, 1000, *features)) + } + + @JvmStatic fun getObjectPayload(objectType: Long, + version: Long, + streamNumber: Long, + stream: InputStream, + length: Int): ObjectPayload { + val type = ObjectType.fromNumber(objectType) + if (type != null) { + when (type) { + ObjectType.GET_PUBKEY -> return parseGetPubkey(version, streamNumber, stream, length) + ObjectType.PUBKEY -> return parsePubkey(version, streamNumber, stream, length) + MSG -> return parseMsg(version, streamNumber, stream, length) + ObjectType.BROADCAST -> return parseBroadcast(version, streamNumber, stream, length) + else -> LOG.error("This should not happen, someone broke something in the code!") + } + } + // fallback: just store the message - we don't really care what it is + LOG.trace("Unexpected object type: " + objectType) + return GenericPayload.read(version, streamNumber, stream, length) + } + + @JvmStatic private fun parseGetPubkey(version: Long, streamNumber: Long, stream: InputStream, length: Int): ObjectPayload { + return GetPubkey.read(stream, streamNumber, length, version) + } + + @JvmStatic fun readPubkey(version: Long, stream: Long, `is`: InputStream, length: Int, encrypted: Boolean): Pubkey? { + when (version.toInt()) { + 2 -> return V2Pubkey.read(`is`, stream) + 3 -> return V3Pubkey.read(`is`, stream) + 4 -> return V4Pubkey.read(`is`, stream, length, encrypted) + } + LOG.debug("Unexpected pubkey version $version, handling as generic payload object") + return null + } + + @JvmStatic private fun parsePubkey(version: Long, streamNumber: Long, stream: InputStream, length: Int): ObjectPayload { + val pubkey = readPubkey(version, streamNumber, stream, length, true) + return pubkey ?: GenericPayload.read(version, streamNumber, stream, length) + } + + @JvmStatic private fun parseMsg(version: Long, streamNumber: Long, stream: InputStream, length: Int): ObjectPayload { + return Msg.read(stream, streamNumber, length) + } + + @JvmStatic private fun parseBroadcast(version: Long, streamNumber: Long, stream: InputStream, length: Int): ObjectPayload { + when (version.toInt()) { + 4 -> return V4Broadcast.read(stream, streamNumber, length) + 5 -> return V5Broadcast.read(stream, streamNumber, length) + else -> { + LOG.debug("Encountered unknown broadcast version " + version) + return GenericPayload.read(version, streamNumber, stream, length) + } + } + } + + @JvmStatic fun getBroadcast(plaintext: Plaintext): Broadcast { + val sendingAddress = plaintext.from + if (sendingAddress.version < 4) { + return V4Broadcast(sendingAddress, plaintext) + } else { + return V5Broadcast(sendingAddress, plaintext) + } + } + + @JvmStatic fun createAck(from: BitmessageAddress, ackData: ByteArray?, ttl: Long): ObjectMessage? { + val ack = GenericPayload( + 3, from.stream, + ackData ?: return null + ) + return ObjectMessage.Builder().objectType(MSG).payload(ack).expiresTime(UnixTime.now + ttl).build() + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/factory/V3MessageFactory.java b/core/src/main/java/ch/dissem/bitmessage/factory/V3MessageFactory.java deleted file mode 100644 index 378f658..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/factory/V3MessageFactory.java +++ /dev/null @@ -1,236 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.factory; - -import ch.dissem.bitmessage.entity.*; -import ch.dissem.bitmessage.entity.payload.GenericPayload; -import ch.dissem.bitmessage.entity.payload.ObjectPayload; -import ch.dissem.bitmessage.entity.valueobject.InventoryVector; -import ch.dissem.bitmessage.entity.valueobject.NetworkAddress; -import ch.dissem.bitmessage.exception.NodeException; -import ch.dissem.bitmessage.utils.AccessCounter; -import ch.dissem.bitmessage.utils.Decode; -import ch.dissem.bitmessage.utils.Strings; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; - -import static ch.dissem.bitmessage.entity.NetworkMessage.MAGIC_BYTES; -import static ch.dissem.bitmessage.utils.Singleton.cryptography; - -/** - * Creates protocol v3 network messages from {@link InputStream InputStreams} - */ -class V3MessageFactory { - private static Logger LOG = LoggerFactory.getLogger(V3MessageFactory.class); - - public static NetworkMessage read(InputStream in) throws IOException { - findMagic(in); - String command = getCommand(in); - int length = (int) Decode.uint32(in); - if (length > 1600003) { - throw new NodeException("Payload of " + length + " bytes received, no more than 1600003 was expected."); - } - byte[] checksum = Decode.bytes(in, 4); - - byte[] payloadBytes = Decode.bytes(in, length); - - if (testChecksum(checksum, payloadBytes)) { - MessagePayload payload = getPayload(command, new ByteArrayInputStream(payloadBytes), length); - if (payload != null) - return new NetworkMessage(payload); - else - return null; - } else { - throw new IOException("Checksum failed for message '" + command + "'"); - } - } - - static MessagePayload getPayload(String command, InputStream stream, int length) throws IOException { - switch (command) { - case "version": - return parseVersion(stream); - case "verack": - return new VerAck(); - case "addr": - return parseAddr(stream); - case "inv": - return parseInv(stream); - case "getdata": - return parseGetData(stream); - case "object": - return readObject(stream, length); - case "custom": - return readCustom(stream, length); - default: - LOG.debug("Unknown command: " + command); - return null; - } - } - - private static MessagePayload readCustom(InputStream in, int length) throws IOException { - return CustomMessage.read(in, length); - } - - public static ObjectMessage readObject(InputStream in, int length) throws IOException { - AccessCounter counter = new AccessCounter(); - byte nonce[] = Decode.bytes(in, 8, counter); - long expiresTime = Decode.int64(in, counter); - long objectType = Decode.uint32(in, counter); - long version = Decode.varInt(in, counter); - long stream = Decode.varInt(in, counter); - - byte[] data = Decode.bytes(in, length - counter.length()); - ObjectPayload payload; - try { - ByteArrayInputStream dataStream = new ByteArrayInputStream(data); - payload = Factory.getObjectPayload(objectType, version, stream, dataStream, data.length); - } catch (Exception e) { - if (LOG.isTraceEnabled()) { - LOG.trace("Could not parse object payload - using generic payload instead", e); - LOG.trace(Strings.hex(data).toString()); - } - payload = new GenericPayload(version, stream, data); - } - - return new ObjectMessage.Builder() - .nonce(nonce) - .expiresTime(expiresTime) - .objectType(objectType) - .stream(stream) - .payload(payload) - .build(); - } - - private static GetData parseGetData(InputStream stream) throws IOException { - long count = Decode.varInt(stream); - GetData.Builder builder = new GetData.Builder(); - for (int i = 0; i < count; i++) { - builder.addInventoryVector(parseInventoryVector(stream)); - } - return builder.build(); - } - - private static Inv parseInv(InputStream stream) throws IOException { - long count = Decode.varInt(stream); - Inv.Builder builder = new Inv.Builder(); - for (int i = 0; i < count; i++) { - builder.addInventoryVector(parseInventoryVector(stream)); - } - return builder.build(); - } - - private static Addr parseAddr(InputStream stream) throws IOException { - long count = Decode.varInt(stream); - Addr.Builder builder = new Addr.Builder(); - for (int i = 0; i < count; i++) { - builder.addAddress(parseAddress(stream, false)); - } - return builder.build(); - } - - private static Version parseVersion(InputStream stream) throws IOException { - int version = Decode.int32(stream); - long services = Decode.int64(stream); - long timestamp = Decode.int64(stream); - NetworkAddress addrRecv = parseAddress(stream, true); - NetworkAddress addrFrom = parseAddress(stream, true); - long nonce = Decode.int64(stream); - String userAgent = Decode.varString(stream); - long[] streamNumbers = Decode.varIntList(stream); - - return new Version.Builder() - .version(version) - .services(services) - .timestamp(timestamp) - .addrRecv(addrRecv).addrFrom(addrFrom) - .nonce(nonce) - .userAgent(userAgent) - .streams(streamNumbers).build(); - } - - private static InventoryVector parseInventoryVector(InputStream stream) throws IOException { - return InventoryVector.fromHash(Decode.bytes(stream, 32)); - } - - private static NetworkAddress parseAddress(InputStream stream, boolean light) throws IOException { - long time; - long streamNumber; - if (!light) { - time = Decode.int64(stream); - streamNumber = Decode.uint32(stream); // This isn't consistent, not sure if this is correct - } else { - time = 0; - streamNumber = 0; - } - long services = Decode.int64(stream); - byte[] ipv6 = Decode.bytes(stream, 16); - int port = Decode.uint16(stream); - return new NetworkAddress.Builder() - .time(time) - .stream(streamNumber) - .services(services) - .ipv6(ipv6) - .port(port) - .build(); - } - - private static boolean testChecksum(byte[] checksum, byte[] payload) { - byte[] payloadChecksum = cryptography().sha512(payload); - for (int i = 0; i < checksum.length; i++) { - if (checksum[i] != payloadChecksum[i]) { - return false; - } - } - return true; - } - - private static String getCommand(InputStream stream) throws IOException { - byte[] bytes = new byte[12]; - int end = bytes.length; - for (int i = 0; i < bytes.length; i++) { - bytes[i] = (byte) stream.read(); - if (end == bytes.length) { - if (bytes[i] == 0) end = i; - } else { - if (bytes[i] != 0) throw new IOException("'\\0' padding expected for command"); - } - } - return new String(bytes, 0, end, "ASCII"); - } - - private static void findMagic(InputStream in) throws IOException { - int pos = 0; - for (int i = 0; i < 1620000; i++) { - byte b = (byte) in.read(); - if (b == MAGIC_BYTES[pos]) { - if (pos + 1 == MAGIC_BYTES.length) { - return; - } - } else if (pos > 0 && b == MAGIC_BYTES[0]) { - pos = 1; - } else { - pos = 0; - } - pos++; - } - throw new NodeException("Failed to find MAGIC bytes in stream"); - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/factory/V3MessageFactory.kt b/core/src/main/java/ch/dissem/bitmessage/factory/V3MessageFactory.kt new file mode 100644 index 0000000..bc62662 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/factory/V3MessageFactory.kt @@ -0,0 +1,230 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.factory + +import ch.dissem.bitmessage.entity.* +import ch.dissem.bitmessage.entity.payload.GenericPayload +import ch.dissem.bitmessage.entity.payload.ObjectPayload +import ch.dissem.bitmessage.entity.valueobject.InventoryVector +import ch.dissem.bitmessage.entity.valueobject.NetworkAddress +import ch.dissem.bitmessage.exception.NodeException +import ch.dissem.bitmessage.utils.AccessCounter +import ch.dissem.bitmessage.utils.Decode +import ch.dissem.bitmessage.utils.Singleton.cryptography +import ch.dissem.bitmessage.utils.Strings +import org.slf4j.LoggerFactory +import java.io.ByteArrayInputStream +import java.io.IOException +import java.io.InputStream +import java.util.* + +/** + * Creates protocol v3 network messages from [InputStreams][InputStream] + */ +object V3MessageFactory { + private val LOG = LoggerFactory.getLogger(V3MessageFactory::class.java) + + @JvmStatic + fun read(`in`: InputStream): NetworkMessage? { + findMagic(`in`) + val command = getCommand(`in`) + val length = Decode.uint32(`in`).toInt() + if (length > 1600003) { + throw NodeException("Payload of $length bytes received, no more than 1600003 was expected.") + } + val checksum = Decode.bytes(`in`, 4) + + val payloadBytes = Decode.bytes(`in`, length) + + if (testChecksum(checksum, payloadBytes)) { + val payload = getPayload(command, ByteArrayInputStream(payloadBytes), length) + if (payload != null) + return NetworkMessage(payload) + else + return null + } else { + throw IOException("Checksum failed for message '$command'") + } + } + + @JvmStatic + fun getPayload(command: String, stream: InputStream, length: Int): MessagePayload? { + when (command) { + "version" -> return parseVersion(stream) + "verack" -> return VerAck() + "addr" -> return parseAddr(stream) + "inv" -> return parseInv(stream) + "getdata" -> return parseGetData(stream) + "object" -> return readObject(stream, length) + "custom" -> return readCustom(stream, length) + else -> { + LOG.debug("Unknown command: " + command) + return null + } + } + } + + private fun readCustom(`in`: InputStream, length: Int): MessagePayload { + return CustomMessage.read(`in`, length) + } + + @JvmStatic + fun readObject(`in`: InputStream, length: Int): ObjectMessage { + val counter = AccessCounter() + val nonce = Decode.bytes(`in`, 8, counter) + val expiresTime = Decode.int64(`in`, counter) + val objectType = Decode.uint32(`in`, counter) + val version = Decode.varInt(`in`, counter) + val stream = Decode.varInt(`in`, counter) + + val data = Decode.bytes(`in`, length - counter.length()) + var payload: ObjectPayload + try { + val dataStream = ByteArrayInputStream(data) + payload = Factory.getObjectPayload(objectType, version, stream, dataStream, data.size) + } catch (e: Exception) { + if (LOG.isTraceEnabled) { + LOG.trace("Could not parse object payload - using generic payload instead", e) + LOG.trace(Strings.hex(data).toString()) + } + payload = GenericPayload(version, stream, data) + } + + return ObjectMessage.Builder() + .nonce(nonce) + .expiresTime(expiresTime) + .objectType(objectType) + .stream(stream) + .payload(payload) + .build() + } + + private fun parseGetData(stream: InputStream): GetData { + val count = Decode.varInt(stream) + val inventoryVectors = LinkedList<InventoryVector>() + for (i in 0..count - 1) { + inventoryVectors.add(parseInventoryVector(stream)) + } + return GetData(inventoryVectors) + } + + private fun parseInv(stream: InputStream): Inv { + val count = Decode.varInt(stream) + val inventoryVectors = LinkedList<InventoryVector>() + for (i in 0..count - 1) { + inventoryVectors.add(parseInventoryVector(stream)) + } + return Inv(inventoryVectors) + } + + private fun parseAddr(stream: InputStream): Addr { + val count = Decode.varInt(stream) + val networkAddresses = LinkedList<NetworkAddress>() + for (i in 0..count - 1) { + networkAddresses.add(parseAddress(stream, false)) + } + return Addr(networkAddresses) + } + + private fun parseVersion(stream: InputStream): Version { + val version = Decode.int32(stream) + val services = Decode.int64(stream) + val timestamp = Decode.int64(stream) + val addrRecv = parseAddress(stream, true) + val addrFrom = parseAddress(stream, true) + val nonce = Decode.int64(stream) + val userAgent = Decode.varString(stream) + val streamNumbers = Decode.varIntList(stream) + + return Version.Builder() + .version(version) + .services(services) + .timestamp(timestamp) + .addrRecv(addrRecv).addrFrom(addrFrom) + .nonce(nonce) + .userAgent(userAgent) + .streams(*streamNumbers).build() + } + + private fun parseInventoryVector(stream: InputStream): InventoryVector { + return InventoryVector(Decode.bytes(stream, 32)) + } + + private fun parseAddress(stream: InputStream, light: Boolean): NetworkAddress { + val time: Long + val streamNumber: Long + if (!light) { + time = Decode.int64(stream) + streamNumber = Decode.uint32(stream) // This isn't consistent, not sure if this is correct + } else { + time = 0 + streamNumber = 0 + } + val services = Decode.int64(stream) + val ipv6 = Decode.bytes(stream, 16) + val port = Decode.uint16(stream) + return NetworkAddress.Builder() + .time(time) + .stream(streamNumber) + .services(services) + .ipv6(ipv6) + .port(port) + .build() + } + + private fun testChecksum(checksum: ByteArray, payload: ByteArray): Boolean { + val payloadChecksum = cryptography().sha512(payload) + for (i in checksum.indices) { + if (checksum[i] != payloadChecksum[i]) { + return false + } + } + return true + } + + private fun getCommand(stream: InputStream): String { + val bytes = ByteArray(12) + var end = bytes.size + for (i in bytes.indices) { + bytes[i] = stream.read().toByte() + if (end == bytes.size) { + if (bytes[i].toInt() == 0) end = i + } else { + if (bytes[i].toInt() != 0) throw IOException("'\\u0000' padding expected for command") + } + } + return String(bytes, 0, end, Charsets.US_ASCII) + } + + private fun findMagic(`in`: InputStream) { + var pos = 0 + for (i in 0..1619999) { + val b = `in`.read().toByte() + if (b == NetworkMessage.MAGIC_BYTES[pos]) { + if (pos + 1 == NetworkMessage.MAGIC_BYTES.size) { + return + } + } else if (pos > 0 && b == NetworkMessage.MAGIC_BYTES[0]) { + pos = 1 + } else { + pos = 0 + } + pos++ + } + throw NodeException("Failed to find MAGIC bytes in stream") + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/factory/V3MessageReader.java b/core/src/main/java/ch/dissem/bitmessage/factory/V3MessageReader.java deleted file mode 100644 index 89677cd..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/factory/V3MessageReader.java +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Copyright 2016 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.factory; - -import ch.dissem.bitmessage.entity.MessagePayload; -import ch.dissem.bitmessage.entity.NetworkMessage; -import ch.dissem.bitmessage.exception.ApplicationException; -import ch.dissem.bitmessage.exception.NodeException; -import ch.dissem.bitmessage.utils.Decode; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.nio.ByteBuffer; -import java.util.LinkedList; -import java.util.List; - -import static ch.dissem.bitmessage.entity.NetworkMessage.MAGIC_BYTES; -import static ch.dissem.bitmessage.factory.BufferPool.bufferPool; -import static ch.dissem.bitmessage.ports.NetworkHandler.MAX_PAYLOAD_SIZE; -import static ch.dissem.bitmessage.utils.Singleton.cryptography; - -/** - * Similar to the {@link V3MessageFactory}, but used for NIO buffers which may or may not contain a whole message. - */ -public class V3MessageReader { - private ByteBuffer headerBuffer; - private ByteBuffer dataBuffer; - - private ReaderState state = ReaderState.MAGIC; - private String command; - private int length; - private byte[] checksum; - - private List<NetworkMessage> messages = new LinkedList<>(); - - public ByteBuffer getActiveBuffer() { - if (state != null && state != ReaderState.DATA) { - if (headerBuffer == null) { - headerBuffer = bufferPool.allocateHeaderBuffer(); - } - } - return state == ReaderState.DATA ? dataBuffer : headerBuffer; - } - - public void update() { - if (state != ReaderState.DATA) { - getActiveBuffer(); - headerBuffer.flip(); - } - switch (state) { - case MAGIC: - if (!findMagicBytes(headerBuffer)) { - headerBuffer.compact(); - return; - } - state = ReaderState.HEADER; - case HEADER: - if (headerBuffer.remaining() < 20) { - headerBuffer.compact(); - headerBuffer.limit(20); - return; - } - command = getCommand(headerBuffer); - length = (int) Decode.uint32(headerBuffer); - if (length > MAX_PAYLOAD_SIZE) { - throw new NodeException("Payload of " + length + " bytes received, no more than " + - MAX_PAYLOAD_SIZE + " was expected."); - } - checksum = new byte[4]; - headerBuffer.get(checksum); - state = ReaderState.DATA; - bufferPool.deallocate(headerBuffer); - headerBuffer = null; - dataBuffer = bufferPool.allocate(length); - dataBuffer.clear(); - dataBuffer.limit(length); - case DATA: - if (dataBuffer.position() < length) { - return; - } else { - dataBuffer.flip(); - } - if (!testChecksum(dataBuffer)) { - state = ReaderState.MAGIC; - throw new NodeException("Checksum failed for message '" + command + "'"); - } - try { - MessagePayload payload = V3MessageFactory.getPayload( - command, - new ByteArrayInputStream(dataBuffer.array(), - dataBuffer.arrayOffset() + dataBuffer.position(), length), - length); - if (payload != null) { - messages.add(new NetworkMessage(payload)); - } - } catch (IOException e) { - throw new NodeException(e.getMessage()); - } finally { - state = ReaderState.MAGIC; - bufferPool.deallocate(dataBuffer); - dataBuffer = null; - dataBuffer = null; - } - } - } - - public List<NetworkMessage> getMessages() { - return messages; - } - - private boolean findMagicBytes(ByteBuffer buffer) { - int i = 0; - while (buffer.hasRemaining()) { - if (i == 0) { - buffer.mark(); - } - if (buffer.get() == MAGIC_BYTES[i]) { - i++; - if (i == MAGIC_BYTES.length) { - return true; - } - } else { - i = 0; - } - } - if (i > 0) { - buffer.reset(); - } - return false; - } - - private static String getCommand(ByteBuffer buffer) { - int start = buffer.position(); - int l = 0; - while (l < 12 && buffer.get() != 0) l++; - int i = l + 1; - while (i < 12) { - if (buffer.get() != 0) throw new NodeException("'\\0' padding expected for command"); - i++; - } - try { - return new String(buffer.array(), start, l, "ASCII"); - } catch (UnsupportedEncodingException e) { - throw new ApplicationException(e); - } - } - - private boolean testChecksum(ByteBuffer buffer) { - byte[] payloadChecksum = cryptography().sha512(buffer.array(), - buffer.arrayOffset() + buffer.position(), length); - for (int i = 0; i < checksum.length; i++) { - if (checksum[i] != payloadChecksum[i]) { - return false; - } - } - return true; - } - - /** - * De-allocates all buffers. This method should be called iff the reader isn't used anymore, i.e. when its - * connection is severed. - */ - public void cleanup() { - state = null; - if (headerBuffer != null) { - bufferPool.deallocate(headerBuffer); - } - if (dataBuffer != null) { - bufferPool.deallocate(dataBuffer); - } - } - - private enum ReaderState {MAGIC, HEADER, DATA} -} diff --git a/core/src/main/java/ch/dissem/bitmessage/factory/V3MessageReader.kt b/core/src/main/java/ch/dissem/bitmessage/factory/V3MessageReader.kt new file mode 100644 index 0000000..694e8c0 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/factory/V3MessageReader.kt @@ -0,0 +1,190 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.factory + +import ch.dissem.bitmessage.constants.Network.MAX_PAYLOAD_SIZE +import ch.dissem.bitmessage.entity.NetworkMessage +import ch.dissem.bitmessage.exception.NodeException +import ch.dissem.bitmessage.utils.Decode +import ch.dissem.bitmessage.utils.Singleton.cryptography +import java.io.ByteArrayInputStream +import java.io.IOException +import java.nio.ByteBuffer +import java.util.* + +/** + * Similar to the [V3MessageFactory], but used for NIO buffers which may or may not contain a whole message. + */ +class V3MessageReader { + private var headerBuffer: ByteBuffer? = null + private var dataBuffer: ByteBuffer? = null + + private var state: ReaderState? = ReaderState.MAGIC + private var command: String? = null + private var length: Int = 0 + private val checksum = ByteArray(4) + + private val messages = LinkedList<NetworkMessage>() + + val activeBuffer: ByteBuffer + get() { + if (state != null && state != ReaderState.DATA) { + if (headerBuffer == null) { + headerBuffer = BufferPool.allocateHeaderBuffer() + } + } + return if (state == ReaderState.DATA) + dataBuffer ?: throw IllegalStateException("data buffer is null") + else + headerBuffer ?: throw IllegalStateException("header buffer is null") + } + + fun update() { + if (state != ReaderState.DATA) { + activeBuffer + headerBuffer!!.flip() + } + when (state) { + V3MessageReader.ReaderState.MAGIC -> magic(headerBuffer ?: throw IllegalStateException("header buffer is null")) + V3MessageReader.ReaderState.HEADER -> header(headerBuffer ?: throw IllegalStateException("header buffer is null")) + V3MessageReader.ReaderState.DATA -> data(dataBuffer ?: throw IllegalStateException("data buffer is null")) + } + } + + private fun magic(headerBuffer: ByteBuffer) { + if (!findMagicBytes(headerBuffer)) { + headerBuffer.compact() + return + } else { + state = ReaderState.HEADER + header(headerBuffer) + } + } + + private fun header(headerBuffer: ByteBuffer) { + if (headerBuffer.remaining() < 20) { + headerBuffer.compact() + headerBuffer.limit(20) + return + } + command = getCommand(headerBuffer) + length = Decode.uint32(headerBuffer).toInt() + if (length > MAX_PAYLOAD_SIZE) { + throw NodeException("Payload of " + length + " bytes received, no more than " + + MAX_PAYLOAD_SIZE + " was expected.") + } + headerBuffer.get(checksum) + state = ReaderState.DATA + this.headerBuffer = null + BufferPool.deallocate(headerBuffer) + val dataBuffer = BufferPool.allocate(length) + this.dataBuffer = dataBuffer + data(dataBuffer) + } + + private fun data(dataBuffer: ByteBuffer) { + dataBuffer.clear() + dataBuffer.limit(length) + if (dataBuffer.position() < length) { + return + } else { + dataBuffer.flip() + } + if (!testChecksum(dataBuffer)) { + state = ReaderState.MAGIC + this.dataBuffer = null + BufferPool.deallocate(dataBuffer) + throw NodeException("Checksum failed for message '$command'") + } + try { + V3MessageFactory.getPayload( + command ?: throw IllegalStateException("command is null"), + ByteArrayInputStream(dataBuffer.array(), + dataBuffer.arrayOffset() + dataBuffer.position(), length), + length + )?.let { messages.add(NetworkMessage(it)) } + } catch (e: IOException) { + throw NodeException(e.message) + } finally { + state = ReaderState.MAGIC + this.dataBuffer = null + BufferPool.deallocate(dataBuffer) + } + } + + fun getMessages(): List<NetworkMessage> { + return messages + } + + private fun findMagicBytes(buffer: ByteBuffer): Boolean { + var i = 0 + while (buffer.hasRemaining()) { + if (i == 0) { + buffer.mark() + } + if (buffer.get() == NetworkMessage.MAGIC_BYTES[i]) { + i++ + if (i == NetworkMessage.MAGIC_BYTES.size) { + return true + } + } else { + i = 0 + } + } + if (i > 0) { + buffer.reset() + } + return false + } + + private fun getCommand(buffer: ByteBuffer): String { + val start = buffer.position() + var l = 0 + while (l < 12 && buffer.get().toInt() != 0) l++ + var i = l + 1 + while (i < 12) { + if (buffer.get().toInt() != 0) throw NodeException("'\\u0000' padding expected for command") + i++ + } + return String(buffer.array(), start, l, Charsets.US_ASCII) + } + + private fun testChecksum(buffer: ByteBuffer): Boolean { + val payloadChecksum = cryptography().sha512(buffer.array(), + buffer.arrayOffset() + buffer.position(), length) + for (i in checksum.indices) { + if (checksum[i] != payloadChecksum[i]) { + return false + } + } + return true + } + + /** + * De-allocates all buffers. This method should be called iff the reader isn't used anymore, i.e. when its + * connection is severed. + */ + fun cleanup() { + state = null + headerBuffer?.let { BufferPool.deallocate(it) } + dataBuffer?.let { BufferPool.deallocate(it) } + } + + private enum class ReaderState { + MAGIC, HEADER, DATA + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/AbstractCryptography.java b/core/src/main/java/ch/dissem/bitmessage/ports/AbstractCryptography.java deleted file mode 100644 index 304c20d..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/ports/AbstractCryptography.java +++ /dev/null @@ -1,214 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.ports; - -import ch.dissem.bitmessage.InternalContext; -import ch.dissem.bitmessage.entity.ObjectMessage; -import ch.dissem.bitmessage.entity.payload.Pubkey; -import ch.dissem.bitmessage.exception.ApplicationException; -import ch.dissem.bitmessage.exception.InsufficientProofOfWorkException; -import ch.dissem.bitmessage.factory.Factory; -import ch.dissem.bitmessage.utils.Bytes; -import ch.dissem.bitmessage.utils.UnixTime; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; -import java.io.IOException; -import java.math.BigInteger; -import java.security.*; - -import static ch.dissem.bitmessage.InternalContext.NETWORK_EXTRA_BYTES; -import static ch.dissem.bitmessage.InternalContext.NETWORK_NONCE_TRIALS_PER_BYTE; -import static ch.dissem.bitmessage.utils.Numbers.max; - -/** - * Implements everything that isn't directly dependent on either Spongy- or Bouncycastle. - */ -public abstract class AbstractCryptography implements Cryptography, InternalContext.ContextHolder { - protected static final Logger LOG = LoggerFactory.getLogger(Cryptography.class); - private static final SecureRandom RANDOM = new SecureRandom(); - private static final BigInteger TWO = BigInteger.valueOf(2); - private static final BigInteger TWO_POW_64 = TWO.pow(64); - private static final BigInteger TWO_POW_16 = TWO.pow(16); - - protected static final String ALGORITHM_ECDSA = "ECDSA"; - protected static final String ALGORITHM_ECDSA_SHA1 = "SHA1withECDSA"; - protected static final String ALGORITHM_EVP_SHA256 = "SHA256withECDSA"; - - protected final Provider provider; - private InternalContext context; - - protected AbstractCryptography(Provider provider) { - this.provider = provider; - } - - @Override - public void setContext(InternalContext context) { - this.context = context; - } - - public byte[] sha512(byte[] data, int offset, int length) { - MessageDigest mda = md("SHA-512"); - mda.update(data, offset, length); - return mda.digest(); - } - - public byte[] sha512(byte[]... data) { - return hash("SHA-512", data); - } - - public byte[] doubleSha512(byte[]... data) { - MessageDigest mda = md("SHA-512"); - for (byte[] d : data) { - mda.update(d); - } - return mda.digest(mda.digest()); - } - - public byte[] doubleSha512(byte[] data, int length) { - MessageDigest mda = md("SHA-512"); - mda.update(data, 0, length); - return mda.digest(mda.digest()); - } - - public byte[] ripemd160(byte[]... data) { - return hash("RIPEMD160", data); - } - - public byte[] doubleSha256(byte[] data, int length) { - MessageDigest mda = md("SHA-256"); - mda.update(data, 0, length); - return mda.digest(mda.digest()); - } - - public byte[] sha1(byte[]... data) { - return hash("SHA-1", data); - } - - public byte[] randomBytes(int length) { - byte[] result = new byte[length]; - RANDOM.nextBytes(result); - return result; - } - - public void doProofOfWork(ObjectMessage object, long nonceTrialsPerByte, - long extraBytes, ProofOfWorkEngine.Callback callback) { - nonceTrialsPerByte = max(nonceTrialsPerByte, NETWORK_NONCE_TRIALS_PER_BYTE); - extraBytes = max(extraBytes, NETWORK_EXTRA_BYTES); - - byte[] initialHash = getInitialHash(object); - - byte[] target = getProofOfWorkTarget(object, nonceTrialsPerByte, extraBytes); - - context.getProofOfWorkEngine().calculateNonce(initialHash, target, callback); - } - - public void checkProofOfWork(ObjectMessage object, long nonceTrialsPerByte, long extraBytes) - throws IOException { - byte[] target = getProofOfWorkTarget(object, nonceTrialsPerByte, extraBytes); - byte[] value = doubleSha512(object.getNonce(), getInitialHash(object)); - if (Bytes.lt(target, value, 8)) { - throw new InsufficientProofOfWorkException(target, value); - } - } - - protected byte[] doSign(byte[] data, java.security.PrivateKey privKey) throws GeneralSecurityException { - // TODO: change this to ALGORITHM_EVP_SHA256 once it's generally used in the network - Signature sig = Signature.getInstance(ALGORITHM_ECDSA_SHA1, provider); - sig.initSign(privKey); - sig.update(data); - return sig.sign(); - } - - - protected boolean doCheckSignature(byte[] data, byte[] signature, PublicKey publicKey) throws GeneralSecurityException { - for (String algorithm : new String[]{ALGORITHM_ECDSA_SHA1, ALGORITHM_EVP_SHA256}) { - Signature sig = Signature.getInstance(algorithm, provider); - sig.initVerify(publicKey); - sig.update(data); - if (sig.verify(signature)) { - return true; - } - } - return false; - } - - @Override - public byte[] getInitialHash(ObjectMessage object) { - return sha512(object.getPayloadBytesWithoutNonce()); - } - - @Override - public byte[] getProofOfWorkTarget(ObjectMessage object, long nonceTrialsPerByte, long extraBytes) { - if (nonceTrialsPerByte == 0) nonceTrialsPerByte = NETWORK_NONCE_TRIALS_PER_BYTE; - if (extraBytes == 0) extraBytes = NETWORK_EXTRA_BYTES; - - BigInteger TTL = BigInteger.valueOf(object.getExpiresTime() - UnixTime.now()); - BigInteger powLength = BigInteger.valueOf(object.getPayloadBytesWithoutNonce().length + extraBytes); - BigInteger denominator = BigInteger.valueOf(nonceTrialsPerByte) - .multiply( - powLength.add( - powLength.multiply(TTL).divide(TWO_POW_16) - ) - ); - return Bytes.expand(TWO_POW_64.divide(denominator).toByteArray(), 8); - } - - private byte[] hash(String algorithm, byte[]... data) { - MessageDigest mda = md(algorithm); - for (byte[] d : data) { - mda.update(d); - } - return mda.digest(); - } - - private MessageDigest md(String algorithm) { - try { - return MessageDigest.getInstance(algorithm, provider); - } catch (GeneralSecurityException e) { - throw new ApplicationException(e); - } - } - - public byte[] mac(byte[] key_m, byte[] data) { - try { - Mac mac = Mac.getInstance("HmacSHA256", provider); - mac.init(new SecretKeySpec(key_m, "HmacSHA256")); - return mac.doFinal(data); - } catch (GeneralSecurityException e) { - throw new ApplicationException(e); - } - } - - public Pubkey createPubkey(long version, long stream, byte[] privateSigningKey, byte[] privateEncryptionKey, - long nonceTrialsPerByte, long extraBytes, Pubkey.Feature... features) { - return Factory.createPubkey(version, stream, - createPublicKey(privateSigningKey), - createPublicKey(privateEncryptionKey), - nonceTrialsPerByte, extraBytes, features); - } - - public BigInteger keyToBigInt(byte[] privateKey) { - return new BigInteger(1, privateKey); - } - - public long randomNonce() { - return RANDOM.nextLong(); - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/AbstractCryptography.kt b/core/src/main/java/ch/dissem/bitmessage/ports/AbstractCryptography.kt new file mode 100644 index 0000000..56b3522 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/ports/AbstractCryptography.kt @@ -0,0 +1,205 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.ports + +import ch.dissem.bitmessage.InternalContext +import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_EXTRA_BYTES +import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_NONCE_TRIALS_PER_BYTE +import ch.dissem.bitmessage.entity.ObjectMessage +import ch.dissem.bitmessage.entity.payload.Pubkey +import ch.dissem.bitmessage.exception.ApplicationException +import ch.dissem.bitmessage.exception.InsufficientProofOfWorkException +import ch.dissem.bitmessage.factory.Factory +import ch.dissem.bitmessage.utils.Bytes +import ch.dissem.bitmessage.utils.UnixTime +import ch.dissem.bitmessage.utils.max +import org.slf4j.LoggerFactory +import java.math.BigInteger +import java.security.* +import javax.crypto.Mac +import javax.crypto.spec.SecretKeySpec + +/** + * Implements everything that isn't directly dependent on either Spongy- or Bouncycastle. + */ +abstract class AbstractCryptography protected constructor(@JvmField protected val provider: Provider) : Cryptography { + private val context by InternalContext + + @JvmField protected val ALGORITHM_ECDSA = "ECDSA" + @JvmField protected val ALGORITHM_ECDSA_SHA1 = "SHA1withECDSA" + @JvmField protected val ALGORITHM_EVP_SHA256 = "SHA256withECDSA" + + override fun sha512(data: ByteArray, offset: Int, length: Int): ByteArray { + val mda = md("SHA-512") + mda.update(data, offset, length) + return mda.digest() + } + + override fun sha512(vararg data: ByteArray): ByteArray { + return hash("SHA-512", *data) + } + + override fun doubleSha512(vararg data: ByteArray): ByteArray { + val mda = md("SHA-512") + for (d in data) { + mda.update(d) + } + return mda.digest(mda.digest()) + } + + override fun doubleSha512(data: ByteArray, length: Int): ByteArray { + val mda = md("SHA-512") + mda.update(data, 0, length) + return mda.digest(mda.digest()) + } + + override fun ripemd160(vararg data: ByteArray): ByteArray { + return hash("RIPEMD160", *data) + } + + override fun doubleSha256(data: ByteArray, length: Int): ByteArray { + val mda = md("SHA-256") + mda.update(data, 0, length) + return mda.digest(mda.digest()) + } + + override fun sha1(vararg data: ByteArray): ByteArray { + return hash("SHA-1", *data) + } + + override fun randomBytes(length: Int): ByteArray { + val result = ByteArray(length) + RANDOM.nextBytes(result) + return result + } + + override fun doProofOfWork(`object`: ObjectMessage, nonceTrialsPerByte: Long, + extraBytes: Long, callback: ProofOfWorkEngine.Callback) { + + val initialHash = getInitialHash(`object`) + + val target = getProofOfWorkTarget(`object`, + max(nonceTrialsPerByte, NETWORK_NONCE_TRIALS_PER_BYTE), max(extraBytes, NETWORK_EXTRA_BYTES)) + + context.proofOfWorkEngine.calculateNonce(initialHash, target, callback) + } + + @Throws(InsufficientProofOfWorkException::class) + override fun checkProofOfWork(`object`: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long) { + val target = getProofOfWorkTarget(`object`, nonceTrialsPerByte, extraBytes) + val value = doubleSha512(`object`.nonce ?: throw ApplicationException("Object without nonce"), getInitialHash(`object`)) + if (Bytes.lt(target, value, 8)) { + throw InsufficientProofOfWorkException(target, value) + } + } + + @Throws(GeneralSecurityException::class) + protected fun doSign(data: ByteArray, privKey: java.security.PrivateKey): ByteArray { + // TODO: change this to ALGORITHM_EVP_SHA256 once it's generally used in the network + val sig = Signature.getInstance(ALGORITHM_ECDSA_SHA1, provider) + sig.initSign(privKey) + sig.update(data) + return sig.sign() + } + + + @Throws(GeneralSecurityException::class) + protected fun doCheckSignature(data: ByteArray, signature: ByteArray, publicKey: PublicKey): Boolean { + for (algorithm in arrayOf(ALGORITHM_ECDSA_SHA1, ALGORITHM_EVP_SHA256)) { + val sig = Signature.getInstance(algorithm, provider) + sig.initVerify(publicKey) + sig.update(data) + if (sig.verify(signature)) { + return true + } + } + return false + } + + override fun getInitialHash(`object`: ObjectMessage): ByteArray { + return sha512(`object`.payloadBytesWithoutNonce) + } + + override fun getProofOfWorkTarget(`object`: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long): ByteArray { + @Suppress("NAME_SHADOWING") + val nonceTrialsPerByte = if (nonceTrialsPerByte == 0L) NETWORK_NONCE_TRIALS_PER_BYTE else nonceTrialsPerByte + @Suppress("NAME_SHADOWING") + val extraBytes = if (extraBytes == 0L) NETWORK_EXTRA_BYTES else extraBytes + + val TTL = BigInteger.valueOf(`object`.expiresTime - UnixTime.now) + val powLength = BigInteger.valueOf(`object`.payloadBytesWithoutNonce.size + extraBytes) + val denominator = BigInteger.valueOf(nonceTrialsPerByte) + .multiply( + powLength.add( + powLength.multiply(TTL).divide(TWO_POW_16) + ) + ) + return Bytes.expand(TWO_POW_64.divide(denominator).toByteArray(), 8) + } + + private fun hash(algorithm: String, vararg data: ByteArray): ByteArray { + val mda = md(algorithm) + for (d in data) { + mda.update(d) + } + return mda.digest() + } + + private fun md(algorithm: String): MessageDigest { + try { + return MessageDigest.getInstance(algorithm, provider) + } catch (e: GeneralSecurityException) { + throw ApplicationException(e) + } + + } + + override fun mac(key_m: ByteArray, data: ByteArray): ByteArray { + try { + val mac = Mac.getInstance("HmacSHA256", provider) + mac.init(SecretKeySpec(key_m, "HmacSHA256")) + return mac.doFinal(data) + } catch (e: GeneralSecurityException) { + throw ApplicationException(e) + } + + } + + override fun createPubkey(version: Long, stream: Long, privateSigningKey: ByteArray, privateEncryptionKey: ByteArray, + nonceTrialsPerByte: Long, extraBytes: Long, vararg features: Pubkey.Feature): Pubkey { + return Factory.createPubkey(version, stream, + createPublicKey(privateSigningKey), + createPublicKey(privateEncryptionKey), + nonceTrialsPerByte, extraBytes, *features) + } + + override fun keyToBigInt(privateKey: ByteArray): BigInteger { + return BigInteger(1, privateKey) + } + + override fun randomNonce(): Long { + return RANDOM.nextLong() + } + + companion object { + protected val LOG = LoggerFactory.getLogger(Cryptography::class.java) + private val RANDOM = SecureRandom() + private val TWO = BigInteger.valueOf(2) + private val TWO_POW_64 = TWO.pow(64) + private val TWO_POW_16 = TWO.pow(16) + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/AbstractMessageRepository.java b/core/src/main/java/ch/dissem/bitmessage/ports/AbstractMessageRepository.java deleted file mode 100644 index 6184ff9..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/ports/AbstractMessageRepository.java +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Copyright 2016 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.ports; - -import ch.dissem.bitmessage.InternalContext; -import ch.dissem.bitmessage.entity.BitmessageAddress; -import ch.dissem.bitmessage.entity.Plaintext; -import ch.dissem.bitmessage.entity.valueobject.InventoryVector; -import ch.dissem.bitmessage.entity.valueobject.Label; -import ch.dissem.bitmessage.exception.ApplicationException; -import ch.dissem.bitmessage.utils.Strings; -import ch.dissem.bitmessage.utils.UnixTime; - -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.UUID; - -import static ch.dissem.bitmessage.utils.SqlStrings.join; - -public abstract class AbstractMessageRepository implements MessageRepository, InternalContext.ContextHolder { - protected InternalContext ctx; - - @Override - public void setContext(InternalContext context) { - this.ctx = context; - } - - /** - * @deprecated use {@link #saveContactIfNecessary(BitmessageAddress)} instead. - */ - @Deprecated - protected void safeSenderIfNecessary(Plaintext message) { - if (message.getId() == null) { - saveContactIfNecessary(message.getFrom()); - } - } - - protected void saveContactIfNecessary(BitmessageAddress contact) { - if (contact != null) { - BitmessageAddress savedAddress = ctx.getAddressRepository().getAddress(contact.getAddress()); - if (savedAddress == null) { - ctx.getAddressRepository().save(contact); - } else if (savedAddress.getPubkey() == null && contact.getPubkey() != null) { - savedAddress.setPubkey(contact.getPubkey()); - ctx.getAddressRepository().save(savedAddress); - } - if (savedAddress != null) { - contact.setAlias(savedAddress.getAlias()); - } - } - } - - @Override - public Plaintext getMessage(Object id) { - if (id instanceof Long) { - return single(find("id=" + id)); - } else { - throw new IllegalArgumentException("Long expected for ID"); - } - } - - @Override - public Plaintext getMessage(InventoryVector iv) { - return single(find("iv=X'" + Strings.hex(iv.getHash()) + "'")); - } - - @Override - public Plaintext getMessage(byte[] initialHash) { - return single(find("initial_hash=X'" + Strings.hex(initialHash) + "'")); - } - - @Override - public Plaintext getMessageForAck(byte[] ackData) { - return single(find("ack_data=X'" + Strings.hex(ackData) + "' AND status='" + Plaintext.Status.SENT + "'")); - } - - @Override - public List<Plaintext> findMessages(Label label) { - if (label == null) { - return find("id NOT IN (SELECT message_id FROM Message_Label)"); - } else { - return find("id IN (SELECT message_id FROM Message_Label WHERE label_id=" + label.getId() + ")"); - } - } - - @Override - public List<Plaintext> findMessages(Plaintext.Status status, BitmessageAddress recipient) { - return find("status='" + status.name() + "' AND recipient='" + recipient.getAddress() + "'"); - } - - @Override - public List<Plaintext> findMessages(Plaintext.Status status) { - return find("status='" + status.name() + "'"); - } - - @Override - public List<Plaintext> findMessages(BitmessageAddress sender) { - return find("sender='" + sender.getAddress() + "'"); - } - - @Override - public List<Plaintext> findMessagesToResend() { - return find("status='" + Plaintext.Status.SENT.name() + "'" + - " AND next_try < " + UnixTime.now()); - } - - @Override - public List<Plaintext> findResponses(Plaintext parent) { - if (parent.getInventoryVector() == null) { - return Collections.emptyList(); - } - return find("iv IN (SELECT child FROM Message_Parent" - + " WHERE parent=X'" + Strings.hex(parent.getInventoryVector().getHash()) + "')"); - } - - @Override - public List<Plaintext> getConversation(UUID conversationId) { - return find("conversation=X'" + conversationId.toString().replace("-", "") + "'"); - } - - @Override - public List<Label> getLabels() { - return findLabels("1=1"); - } - - @Override - public List<Label> getLabels(Label.Type... types) { - return findLabels("type IN (" + join(types) + ")"); - } - - protected abstract List<Label> findLabels(String where); - - - protected <T> T single(Collection<T> collection) { - switch (collection.size()) { - case 0: - return null; - case 1: - return collection.iterator().next(); - default: - throw new ApplicationException("This shouldn't happen, found " + collection.size() + - " items, one or none was expected"); - } - } - - protected abstract List<Plaintext> find(String where); -} diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/AbstractMessageRepository.kt b/core/src/main/java/ch/dissem/bitmessage/ports/AbstractMessageRepository.kt new file mode 100644 index 0000000..b7329eb --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/ports/AbstractMessageRepository.kt @@ -0,0 +1,126 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.ports + +import ch.dissem.bitmessage.InternalContext +import ch.dissem.bitmessage.entity.BitmessageAddress +import ch.dissem.bitmessage.entity.Plaintext +import ch.dissem.bitmessage.entity.valueobject.InventoryVector +import ch.dissem.bitmessage.entity.valueobject.Label +import ch.dissem.bitmessage.exception.ApplicationException +import ch.dissem.bitmessage.utils.SqlStrings.join +import ch.dissem.bitmessage.utils.Strings +import ch.dissem.bitmessage.utils.UnixTime +import java.util.* + +abstract class AbstractMessageRepository : MessageRepository { + protected var ctx by InternalContext + + protected fun saveContactIfNecessary(contact: BitmessageAddress?) { + contact?.let { + val savedAddress = ctx.addressRepository.getAddress(contact.address) + if (savedAddress == null) { + ctx.addressRepository.save(contact) + } else if (savedAddress.pubkey == null && contact.pubkey != null) { + savedAddress.pubkey = contact.pubkey + ctx.addressRepository.save(savedAddress) + } + if (savedAddress != null) { + contact.alias = savedAddress.alias + } + } + } + + override fun getMessage(id: Any): Plaintext { + if (id is Long) { + return single(find("id=" + id)) ?: throw IllegalArgumentException("There is no message with id $id") + } else { + throw IllegalArgumentException("Long expected for ID") + } + } + + override fun getMessage(iv: InventoryVector): Plaintext? { + return single(find("iv=X'" + Strings.hex(iv.hash) + "'")) + } + + override fun getMessage(initialHash: ByteArray): Plaintext? { + return single(find("initial_hash=X'" + Strings.hex(initialHash) + "'")) + } + + override fun getMessageForAck(ackData: ByteArray): Plaintext? { + return single(find("ack_data=X'" + Strings.hex(ackData) + "' AND status='" + Plaintext.Status.SENT + "'")) + } + + override fun findMessages(label: Label?): List<Plaintext> { + if (label == null) { + return find("id NOT IN (SELECT message_id FROM Message_Label)") + } else { + return find("id IN (SELECT message_id FROM Message_Label WHERE label_id=" + label.id + ")") + } + } + + override fun findMessages(status: Plaintext.Status, recipient: BitmessageAddress): List<Plaintext> { + return find("status='" + status.name + "' AND recipient='" + recipient.address + "'") + } + + override fun findMessages(status: Plaintext.Status): List<Plaintext> { + return find("status='" + status.name + "'") + } + + override fun findMessages(sender: BitmessageAddress): List<Plaintext> { + return find("sender='" + sender.address + "'") + } + + override fun findMessagesToResend(): List<Plaintext> { + return find("status='" + Plaintext.Status.SENT.name + "'" + + " AND next_try < " + UnixTime.now) + } + + override fun findResponses(parent: Plaintext): List<Plaintext> { + if (parent.inventoryVector == null) { + return emptyList() + } + return find("iv IN (SELECT child FROM Message_Parent" + + " WHERE parent=X'" + Strings.hex(parent.inventoryVector!!.hash) + "')") + } + + override fun getConversation(conversationId: UUID): List<Plaintext> { + return find("conversation=X'" + conversationId.toString().replace("-", "") + "'") + } + + override fun getLabels(): List<Label> { + return findLabels("1=1") + } + + override fun getLabels(vararg types: Label.Type): List<Label> { + return findLabels("type IN (" + join(*types) + ")") + } + + protected abstract fun findLabels(where: String): List<Label> + + + protected fun <T> single(collection: Collection<T>): T? { + when (collection.size) { + 0 -> return null + 1 -> return collection.iterator().next() + else -> throw ApplicationException("This shouldn't happen, found " + collection.size + + " items, one or none was expected") + } + } + + protected abstract fun find(where: String): List<Plaintext> +} diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/AddressRepository.java b/core/src/main/java/ch/dissem/bitmessage/ports/AddressRepository.kt similarity index 63% rename from core/src/main/java/ch/dissem/bitmessage/ports/AddressRepository.java rename to core/src/main/java/ch/dissem/bitmessage/ports/AddressRepository.kt index 5247730..811785a 100644 --- a/core/src/main/java/ch/dissem/bitmessage/ports/AddressRepository.java +++ b/core/src/main/java/ch/dissem/bitmessage/ports/AddressRepository.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015 Christian Basler + * Copyright 2017 Christian Basler * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,52 +14,51 @@ * limitations under the License. */ -package ch.dissem.bitmessage.ports; +package ch.dissem.bitmessage.ports -import ch.dissem.bitmessage.entity.BitmessageAddress; +import ch.dissem.bitmessage.entity.BitmessageAddress -import java.util.List; - -public interface AddressRepository { +interface AddressRepository { /** * Returns a matching BitmessageAddress if there is one with the given ripe or tag, that * has no public key yet. If it doesn't exist or already has a public key, null is returned. - * + * @param ripeOrTag Either ripe or tag (depending of address version) of an address with - * missing public key. + * * missing public key. + * * * @return the matching address if there is one without public key, or null otherwise. */ - BitmessageAddress findContact(byte[] ripeOrTag); + fun findContact(ripeOrTag: ByteArray): BitmessageAddress? - BitmessageAddress findIdentity(byte[] ripeOrTag); + fun findIdentity(ripeOrTag: ByteArray): BitmessageAddress? /** * @return all Bitmessage addresses that belong to this user, i.e. have a private key. */ - List<BitmessageAddress> getIdentities(); + fun getIdentities(): List<BitmessageAddress> /** * @return all subscribed chans. */ - List<BitmessageAddress> getChans(); + fun getChans(): List<BitmessageAddress> - List<BitmessageAddress> getSubscriptions(); + fun getSubscriptions(): List<BitmessageAddress> - List<BitmessageAddress> getSubscriptions(long broadcastVersion); + fun getSubscriptions(broadcastVersion: Long): List<BitmessageAddress> /** * @return all Bitmessage addresses that have no private key or are chans. */ - List<BitmessageAddress> getContacts(); + fun getContacts(): List<BitmessageAddress> /** - * Implementations must not delete cryptographic keys if they're not provided by <code>address</code>. - * + * Implementations must not delete cryptographic keys if they're not provided by `address`. + * @param address to save or update */ - void save(BitmessageAddress address); + fun save(address: BitmessageAddress) - void remove(BitmessageAddress address); + fun remove(address: BitmessageAddress) - BitmessageAddress getAddress(String address); + fun getAddress(address: String): BitmessageAddress? } diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/Cryptography.java b/core/src/main/java/ch/dissem/bitmessage/ports/Cryptography.kt similarity index 65% rename from core/src/main/java/ch/dissem/bitmessage/ports/Cryptography.java rename to core/src/main/java/ch/dissem/bitmessage/ports/Cryptography.kt index 9ea6a9d..22176bb 100644 --- a/core/src/main/java/ch/dissem/bitmessage/ports/Cryptography.java +++ b/core/src/main/java/ch/dissem/bitmessage/ports/Cryptography.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015 Christian Basler + * Copyright 2017 Christian Basler * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -11,212 +11,253 @@ * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and - * limitations under the License. + * limitations under the License. */ -package ch.dissem.bitmessage.ports; +package ch.dissem.bitmessage.ports -import ch.dissem.bitmessage.entity.ObjectMessage; -import ch.dissem.bitmessage.entity.payload.Pubkey; -import ch.dissem.bitmessage.exception.InsufficientProofOfWorkException; - -import java.io.IOException; -import java.math.BigInteger; -import java.security.MessageDigest; -import java.security.SecureRandom; +import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_EXTRA_BYTES +import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_NONCE_TRIALS_PER_BYTE +import ch.dissem.bitmessage.entity.ObjectMessage +import ch.dissem.bitmessage.entity.payload.Pubkey +import ch.dissem.bitmessage.exception.InsufficientProofOfWorkException +import java.math.BigInteger +import java.security.MessageDigest +import java.security.SecureRandom /** - * Provides some methods to help with hashing and encryption. All randoms are created using {@link SecureRandom}, + * Provides some methods to help with hashing and encryption. All randoms are created using [SecureRandom], * which should be secure enough. */ -public interface Cryptography { +interface Cryptography { /** - * A helper method to calculate SHA-512 hashes. Please note that a new {@link MessageDigest} object is created at + * A helper method to calculate SHA-512 hashes. Please note that a new [MessageDigest] object is created at * each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in * success on the same thread. - * + * @param data to get hashed + * * * @param offset of the data to be hashed + * * * @param length of the data to be hashed + * * * @return SHA-512 hash of data within the given range */ - byte[] sha512(byte[] data, int offset, int length); + fun sha512(data: ByteArray, offset: Int, length: Int): ByteArray /** - * A helper method to calculate SHA-512 hashes. Please note that a new {@link MessageDigest} object is created at + * A helper method to calculate SHA-512 hashes. Please note that a new [MessageDigest] object is created at * each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in * success on the same thread. - * + * @param data to get hashed + * * * @return SHA-512 hash of data */ - byte[] sha512(byte[]... data); + fun sha512(vararg data: ByteArray): ByteArray /** - * A helper method to calculate doubleSHA-512 hashes. Please note that a new {@link MessageDigest} object is created + * A helper method to calculate doubleSHA-512 hashes. Please note that a new [MessageDigest] object is created * at each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in * success on the same thread. - * + * @param data to get hashed + * * * @return SHA-512 hash of data */ - byte[] doubleSha512(byte[]... data); + fun doubleSha512(vararg data: ByteArray): ByteArray /** * A helper method to calculate double SHA-512 hashes. This method allows to only use a part of the available bytes * to use for the hash calculation. - * <p> - * Please note that a new {@link MessageDigest} object is created at each call (to ensure thread safety), so you - * shouldn't use this if you need to do many hash calculations in short order on the same thread. - * </p> * + * + * Please note that a new [MessageDigest] object is created at each call (to ensure thread safety), so you + * shouldn't use this if you need to do many hash calculations in short order on the same thread. + * + * @param data to get hashed + * * * @param length number of bytes to be taken into account + * * * @return SHA-512 hash of data */ - byte[] doubleSha512(byte[] data, int length); + fun doubleSha512(data: ByteArray, length: Int): ByteArray /** * A helper method to calculate RIPEMD-160 hashes. Supplying multiple byte arrays has the same result as a * concatenation of all arrays, but might perform better. - * <p> - * Please note that a new {@link MessageDigest} object is created at + * + * + * Please note that a new [MessageDigest] object is created at * each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in short * order on the same thread. - * </p> * + * @param data to get hashed + * * * @return RIPEMD-160 hash of data */ - byte[] ripemd160(byte[]... data); + fun ripemd160(vararg data: ByteArray): ByteArray /** * A helper method to calculate double SHA-256 hashes. This method allows to only use a part of the available bytes * to use for the hash calculation. - * <p> - * Please note that a new {@link MessageDigest} object is created at + * + * + * Please note that a new [MessageDigest] object is created at * each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in short * order on the same thread. - * </p> * + * @param data to get hashed + * * * @param length number of bytes to be taken into account + * * * @return SHA-256 hash of data */ - byte[] doubleSha256(byte[] data, int length); + fun doubleSha256(data: ByteArray, length: Int): ByteArray /** * A helper method to calculate SHA-1 hashes. Supplying multiple byte arrays has the same result as a * concatenation of all arrays, but might perform better. - * <p> - * Please note that a new {@link MessageDigest} object is created at + * + * + * Please note that a new [MessageDigest] object is created at * each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in short * order on the same thread. - * </p> * + * @param data to get hashed + * * * @return SHA hash of data */ - byte[] sha1(byte[]... data); + fun sha1(vararg data: ByteArray): ByteArray /** * @param length number of bytes to return + * * * @return an array of the given size containing random bytes */ - byte[] randomBytes(int length); + fun randomBytes(length: Int): ByteArray /** * Calculates the proof of work. This might take a long time, depending on the hardware, message size and time to * live. - * + * @param object to do the proof of work for + * * * @param nonceTrialsPerByte difficulty + * * * @param extraBytes bytes to add to the object size (makes it more difficult to send small messages) + * * * @param callback to handle nonce once it's calculated */ - void doProofOfWork(ObjectMessage object, long nonceTrialsPerByte, - long extraBytes, ProofOfWorkEngine.Callback callback); + fun doProofOfWork(`object`: ObjectMessage, nonceTrialsPerByte: Long, + extraBytes: Long, callback: ProofOfWorkEngine.Callback) /** * @param object to be checked + * * * @param nonceTrialsPerByte difficulty + * * * @param extraBytes bytes to add to the object size + * * * @throws InsufficientProofOfWorkException if proof of work doesn't check out (makes it more difficult to send small messages) */ - void checkProofOfWork(ObjectMessage object, long nonceTrialsPerByte, long extraBytes) - throws IOException; + @Throws(InsufficientProofOfWorkException::class) + fun checkProofOfWork(`object`: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long) - byte[] getInitialHash(ObjectMessage object); + fun getInitialHash(`object`: ObjectMessage): ByteArray - byte[] getProofOfWorkTarget(ObjectMessage object, long nonceTrialsPerByte, long extraBytes); + fun getProofOfWorkTarget(`object`: ObjectMessage, nonceTrialsPerByte: Long = NETWORK_NONCE_TRIALS_PER_BYTE, extraBytes: Long = NETWORK_EXTRA_BYTES): ByteArray /** * Calculates the MAC for a message (data) - * + * @param key_m the symmetric key used + * * * @param data the message data to calculate the MAC for + * * * @return the MAC */ - byte[] mac(byte[] key_m, byte[] data); + fun mac(key_m: ByteArray, data: ByteArray): ByteArray /** * @param encrypt if true, encrypts data, otherwise tries to decrypt it. + * * * @param data + * * * @param key_e + * * * @return */ - byte[] crypt(boolean encrypt, byte[] data, byte[] key_e, byte[] initializationVector); + fun crypt(encrypt: Boolean, data: ByteArray, key_e: ByteArray, initializationVector: ByteArray): ByteArray /** * Create a new public key fom given private keys. - * + * @param version of the public key / address + * * * @param stream of the address + * * * @param privateSigningKey private key used for signing + * * * @param privateEncryptionKey private key used for encryption + * * * @param nonceTrialsPerByte proof of work difficulty + * * * @param extraBytes bytes to add for the proof of work (make it harder for small messages) + * * * @param features of the address + * * * @return a public key object */ - Pubkey createPubkey(long version, long stream, byte[] privateSigningKey, byte[] privateEncryptionKey, - long nonceTrialsPerByte, long extraBytes, Pubkey.Feature... features); + fun createPubkey(version: Long, stream: Long, privateSigningKey: ByteArray, privateEncryptionKey: ByteArray, + nonceTrialsPerByte: Long, extraBytes: Long, vararg features: Pubkey.Feature): Pubkey /** * @param privateKey private key as byte array + * * * @return a public key corresponding to the given private key */ - byte[] createPublicKey(byte[] privateKey); + fun createPublicKey(privateKey: ByteArray): ByteArray /** * @param privateKey private key as byte array + * * * @return a big integer representation (unsigned) of the given bytes */ - BigInteger keyToBigInt(byte[] privateKey); + fun keyToBigInt(privateKey: ByteArray): BigInteger /** * @param data to check + * * * @param signature the signature of the message + * * * @param pubkey the sender's public key + * * * @return true if the signature is valid, false otherwise */ - boolean isSignatureValid(byte[] data, byte[] signature, Pubkey pubkey); + fun isSignatureValid(data: ByteArray, signature: ByteArray, pubkey: Pubkey): Boolean /** * Calculate the signature of data, using the given private key. - * + * @param data to be signed + * * * @param privateKey to be used for signing + * * * @return the signature */ - byte[] getSignature(byte[] data, ch.dissem.bitmessage.entity.valueobject.PrivateKey privateKey); + fun getSignature(data: ByteArray, privateKey: ch.dissem.bitmessage.entity.valueobject.PrivateKey): ByteArray /** * @return a random number of type long */ - long randomNonce(); + fun randomNonce(): Long - byte[] multiply(byte[] k, byte[] r); + fun multiply(k: ByteArray, r: ByteArray): ByteArray - byte[] createPoint(byte[] x, byte[] y); + fun createPoint(x: ByteArray, y: ByteArray): ByteArray } diff --git a/core/src/test/java/ch/dissem/bitmessage/utils/TestBase.java b/core/src/main/java/ch/dissem/bitmessage/ports/CustomCommandHandler.kt similarity index 66% rename from core/src/test/java/ch/dissem/bitmessage/utils/TestBase.java rename to core/src/main/java/ch/dissem/bitmessage/ports/CustomCommandHandler.kt index 4c73fe3..3f8b63b 100644 --- a/core/src/test/java/ch/dissem/bitmessage/utils/TestBase.java +++ b/core/src/main/java/ch/dissem/bitmessage/ports/CustomCommandHandler.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015 Christian Basler + * Copyright 2017 Christian Basler * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,17 +14,14 @@ * limitations under the License. */ -package ch.dissem.bitmessage.utils; +package ch.dissem.bitmessage.ports -import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography; -import org.junit.BeforeClass; +import ch.dissem.bitmessage.entity.CustomMessage +import ch.dissem.bitmessage.entity.MessagePayload /** * @author Christian Basler */ -public class TestBase { - @BeforeClass - public static void setUpClass() { - Singleton.initialize(new BouncyCryptography()); - } +interface CustomCommandHandler { + fun handle(request: CustomMessage): MessagePayload? } diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/DefaultLabeler.java b/core/src/main/java/ch/dissem/bitmessage/ports/DefaultLabeler.java deleted file mode 100644 index e4c2105..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/ports/DefaultLabeler.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2016 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.ports; - -import ch.dissem.bitmessage.InternalContext; -import ch.dissem.bitmessage.entity.Plaintext; -import ch.dissem.bitmessage.entity.valueobject.Label; - -import static ch.dissem.bitmessage.entity.Plaintext.Status.*; - -public class DefaultLabeler implements Labeler, InternalContext.ContextHolder { - private InternalContext ctx; - - @Override - public void setLabels(Plaintext msg) { - msg.setStatus(RECEIVED); - if (msg.getType() == Plaintext.Type.BROADCAST) { - msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.INBOX, Label.Type.BROADCAST, Label.Type.UNREAD)); - } else { - msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.INBOX, Label.Type.UNREAD)); - } - } - - @Override - public void markAsDraft(Plaintext msg) { - msg.setStatus(DRAFT); - msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.DRAFT)); - } - - @Override - public void markAsSending(Plaintext msg) { - if (msg.getTo() != null && msg.getTo().getPubkey() == null) { - msg.setStatus(PUBKEY_REQUESTED); - } else { - msg.setStatus(DOING_PROOF_OF_WORK); - } - msg.removeLabel(Label.Type.DRAFT); - msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.OUTBOX)); - } - - @Override - public void markAsSent(Plaintext msg) { - msg.setStatus(SENT); - msg.removeLabel(Label.Type.OUTBOX); - msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.SENT)); - } - - @Override - public void markAsAcknowledged(Plaintext msg) { - msg.setStatus(SENT_ACKNOWLEDGED); - } - - @Override - public void markAsRead(Plaintext msg) { - msg.removeLabel(Label.Type.UNREAD); - } - - @Override - public void markAsUnread(Plaintext msg) { - msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.UNREAD)); - } - - @Override - public void delete(Plaintext msg) { - msg.getLabels().clear(); - msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.TRASH)); - } - - @Override - public void archive(Plaintext msg) { - msg.getLabels().clear(); - } - - @Override - public void setContext(InternalContext ctx) { - this.ctx = ctx; - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/DefaultLabeler.kt b/core/src/main/java/ch/dissem/bitmessage/ports/DefaultLabeler.kt new file mode 100644 index 0000000..63148b7 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/ports/DefaultLabeler.kt @@ -0,0 +1,77 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.ports + +import ch.dissem.bitmessage.InternalContext +import ch.dissem.bitmessage.entity.Plaintext +import ch.dissem.bitmessage.entity.Plaintext.Status.* +import ch.dissem.bitmessage.entity.valueobject.Label + +open class DefaultLabeler : Labeler { + private var ctx by InternalContext + + override fun setLabels(msg: Plaintext) { + msg.status = RECEIVED + if (msg.type === Plaintext.Type.BROADCAST) { + msg.addLabels(ctx.messageRepository.getLabels(Label.Type.INBOX, Label.Type.BROADCAST, Label.Type.UNREAD)) + } else { + msg.addLabels(ctx.messageRepository.getLabels(Label.Type.INBOX, Label.Type.UNREAD)) + } + } + + override fun markAsDraft(msg: Plaintext) { + msg.status = DRAFT + msg.addLabels(ctx.messageRepository.getLabels(Label.Type.DRAFT)) + } + + override fun markAsSending(msg: Plaintext) { + if (msg.to != null && msg.to!!.pubkey == null) { + msg.status = PUBKEY_REQUESTED + } else { + msg.status = DOING_PROOF_OF_WORK + } + msg.removeLabel(Label.Type.DRAFT) + msg.addLabels(ctx.messageRepository.getLabels(Label.Type.OUTBOX)) + } + + override fun markAsSent(msg: Plaintext) { + msg.status = SENT + msg.removeLabel(Label.Type.OUTBOX) + msg.addLabels(ctx.messageRepository.getLabels(Label.Type.SENT)) + } + + override fun markAsAcknowledged(msg: Plaintext) { + msg.status = SENT_ACKNOWLEDGED + } + + override fun markAsRead(msg: Plaintext) { + msg.removeLabel(Label.Type.UNREAD) + } + + override fun markAsUnread(msg: Plaintext) { + msg.addLabels(ctx.messageRepository.getLabels(Label.Type.UNREAD)) + } + + override fun delete(msg: Plaintext) { + msg.labels.clear() + msg.addLabels(ctx.messageRepository.getLabels(Label.Type.TRASH)) + } + + override fun archive(msg: Plaintext) { + msg.labels.clear() + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/Inventory.java b/core/src/main/java/ch/dissem/bitmessage/ports/Inventory.kt similarity index 65% rename from core/src/main/java/ch/dissem/bitmessage/ports/Inventory.java rename to core/src/main/java/ch/dissem/bitmessage/ports/Inventory.kt index 6af65c1..2087354 100644 --- a/core/src/main/java/ch/dissem/bitmessage/ports/Inventory.java +++ b/core/src/main/java/ch/dissem/bitmessage/ports/Inventory.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015 Christian Basler + * Copyright 2017 Christian Basler * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,44 +14,42 @@ * limitations under the License. */ -package ch.dissem.bitmessage.ports; +package ch.dissem.bitmessage.ports -import ch.dissem.bitmessage.entity.ObjectMessage; -import ch.dissem.bitmessage.entity.payload.ObjectType; -import ch.dissem.bitmessage.entity.valueobject.InventoryVector; - -import java.util.List; +import ch.dissem.bitmessage.entity.ObjectMessage +import ch.dissem.bitmessage.entity.payload.ObjectType +import ch.dissem.bitmessage.entity.valueobject.InventoryVector /** * The Inventory stores and retrieves objects, cleans up outdated objects and can tell which objects are still missing. */ -public interface Inventory { +interface Inventory { /** * Returns the IVs of all valid objects we have for the given streams */ - List<InventoryVector> getInventory(long... streams); + fun getInventory(vararg streams: Long): List<InventoryVector> /** * Returns the IVs of all objects in the offer that we don't have already. Implementations are allowed to * ignore the streams parameter, but it must be set when calling this method. */ - List<InventoryVector> getMissing(List<InventoryVector> offer, long... streams); + fun getMissing(offer: List<InventoryVector>, vararg streams: Long): List<InventoryVector> - ObjectMessage getObject(InventoryVector vector); + fun getObject(vector: InventoryVector): ObjectMessage? /** * This method is mainly used to search for public keys to newly added addresses or broadcasts from new * subscriptions. */ - List<ObjectMessage> getObjects(long stream, long version, ObjectType... types); + fun getObjects(stream: Long, version: Long, vararg types: ObjectType): List<ObjectMessage> - void storeObject(ObjectMessage object); + fun storeObject(`object`: ObjectMessage) - boolean contains(ObjectMessage object); + operator fun contains(`object`: ObjectMessage): Boolean /** * Deletes all objects that expired 5 minutes ago or earlier * (so we don't accidentally request objects we just deleted) */ - void cleanup(); + fun cleanup() } diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/Labeler.java b/core/src/main/java/ch/dissem/bitmessage/ports/Labeler.kt similarity index 56% rename from core/src/main/java/ch/dissem/bitmessage/ports/Labeler.java rename to core/src/main/java/ch/dissem/bitmessage/ports/Labeler.kt index e79bee2..35ddf16 100644 --- a/core/src/main/java/ch/dissem/bitmessage/ports/Labeler.java +++ b/core/src/main/java/ch/dissem/bitmessage/ports/Labeler.kt @@ -1,5 +1,5 @@ /* - * Copyright 2016 Christian Basler + * Copyright 2017 Christian Basler * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,44 +14,43 @@ * limitations under the License. */ -package ch.dissem.bitmessage.ports; +package ch.dissem.bitmessage.ports -import ch.dissem.bitmessage.entity.Plaintext; +import ch.dissem.bitmessage.entity.Plaintext /** * Defines and sets labels. Note that it should also update the status field of a message. - * Generally it's highly advised to override the {@link DefaultLabeler} whenever possible, + * Generally it's highly advised to override the [DefaultLabeler] whenever possible, * instead of directly implementing the interface. - * <p> + * * As the labeler gets called whenever the state of a message changes, it can also be used * as a listener. - * </p> */ -public interface Labeler { +interface Labeler { /** * Sets the labels of a newly received message. * * @param msg an unlabeled message or broadcast */ - void setLabels(Plaintext msg); + fun setLabels(msg: Plaintext) - void markAsDraft(Plaintext msg); + fun markAsDraft(msg: Plaintext) /** - * It is paramount that this methods marks the {@link Plaintext} object with status - * {@link Plaintext.Status#PUBKEY_REQUESTED} (see {@link DefaultLabeler}) + * It is paramount that this methods marks the [Plaintext] object with status + * [Plaintext.Status.PUBKEY_REQUESTED] (see [DefaultLabeler]) */ - void markAsSending(Plaintext msg); + fun markAsSending(msg: Plaintext) - void markAsSent(Plaintext msg); + fun markAsSent(msg: Plaintext) - void markAsAcknowledged(Plaintext msg); + fun markAsAcknowledged(msg: Plaintext) - void markAsRead(Plaintext msg); + fun markAsRead(msg: Plaintext) - void markAsUnread(Plaintext msg); + fun markAsUnread(msg: Plaintext) - void delete(Plaintext msg); + fun delete(msg: Plaintext) - void archive(Plaintext msg); + fun archive(msg: Plaintext) } diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/MessageRepository.java b/core/src/main/java/ch/dissem/bitmessage/ports/MessageRepository.java deleted file mode 100644 index ba34cfe..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/ports/MessageRepository.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.ports; - -import ch.dissem.bitmessage.entity.BitmessageAddress; -import ch.dissem.bitmessage.entity.Plaintext; -import ch.dissem.bitmessage.entity.Plaintext.Status; -import ch.dissem.bitmessage.entity.valueobject.InventoryVector; -import ch.dissem.bitmessage.entity.valueobject.Label; - -import java.util.Collection; -import java.util.List; -import java.util.UUID; - -public interface MessageRepository { - List<Label> getLabels(); - - List<Label> getLabels(Label.Type... types); - - int countUnread(Label label); - - Plaintext getMessage(Object id); - - Plaintext getMessage(InventoryVector iv); - - Plaintext getMessage(byte[] initialHash); - - Plaintext getMessageForAck(byte[] ackData); - - /** - * @param label to search for - * @return a distinct list of all conversations that have at least one message with the given label. - */ - List<UUID> findConversations(Label label); - - List<Plaintext> findMessages(Label label); - - List<Plaintext> findMessages(Status status); - - List<Plaintext> findMessages(Status status, BitmessageAddress recipient); - - List<Plaintext> findMessages(BitmessageAddress sender); - - List<Plaintext> findResponses(Plaintext parent); - - List<Plaintext> findMessagesToResend(); - - void save(Plaintext message); - - void remove(Plaintext message); - - /** - * Returns all messages with this conversation ID. The returned messages aren't sorted in any way, - * so you may prefer to use {@link ch.dissem.bitmessage.utils.ConversationService#getConversation(UUID)} - * instead. - * - * @param conversationId ID of the requested conversation - * @return all messages with the given conversation ID - */ - Collection<Plaintext> getConversation(UUID conversationId); -} diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/MessageRepository.kt b/core/src/main/java/ch/dissem/bitmessage/ports/MessageRepository.kt new file mode 100644 index 0000000..c1fd59a --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/ports/MessageRepository.kt @@ -0,0 +1,74 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.ports + +import ch.dissem.bitmessage.entity.BitmessageAddress +import ch.dissem.bitmessage.entity.Plaintext +import ch.dissem.bitmessage.entity.Plaintext.Status +import ch.dissem.bitmessage.entity.valueobject.InventoryVector +import ch.dissem.bitmessage.entity.valueobject.Label +import java.util.* + +interface MessageRepository { + fun getLabels(): List<Label> + + fun getLabels(vararg types: Label.Type): List<Label> + + fun countUnread(label: Label): Int + + fun getMessage(id: Any): Plaintext + + fun getMessage(iv: InventoryVector): Plaintext? + + fun getMessage(initialHash: ByteArray): Plaintext? + + fun getMessageForAck(ackData: ByteArray): Plaintext? + + /** + * @param label to search for + * * + * @return a distinct list of all conversations that have at least one message with the given label. + */ + fun findConversations(label: Label): List<UUID> + + fun findMessages(label: Label?): List<Plaintext> + + fun findMessages(status: Status): List<Plaintext> + + fun findMessages(status: Status, recipient: BitmessageAddress): List<Plaintext> + + fun findMessages(sender: BitmessageAddress): List<Plaintext> + + fun findResponses(parent: Plaintext): List<Plaintext> + + fun findMessagesToResend(): List<Plaintext> + + fun save(message: Plaintext) + + fun remove(message: Plaintext) + + /** + * Returns all messages with this conversation ID. The returned messages aren't sorted in any way, + * so you may prefer to use [ch.dissem.bitmessage.utils.ConversationService.getConversation] + * instead. + + * @param conversationId ID of the requested conversation + * * + * @return all messages with the given conversation ID + */ + fun getConversation(conversationId: UUID): Collection<Plaintext> +} diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/MultiThreadedPOWEngine.java b/core/src/main/java/ch/dissem/bitmessage/ports/MultiThreadedPOWEngine.java deleted file mode 100644 index fb45e50..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/ports/MultiThreadedPOWEngine.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.ports; - -import ch.dissem.bitmessage.exception.ApplicationException; -import ch.dissem.bitmessage.utils.Bytes; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.*; - -import static ch.dissem.bitmessage.utils.Bytes.inc; -import static ch.dissem.bitmessage.utils.ThreadFactoryBuilder.pool; - -/** - * A POW engine using all available CPU cores. - */ -public class MultiThreadedPOWEngine implements ProofOfWorkEngine { - private static final Logger LOG = LoggerFactory.getLogger(MultiThreadedPOWEngine.class); - private final ExecutorService waiterPool = Executors.newSingleThreadExecutor(pool("POW-waiter").daemon().build()); - private final ExecutorService workerPool = Executors.newCachedThreadPool(pool("POW-worker").daemon().build()); - - /** - * This method will block until all pending nonce calculations are done, but not wait for its own calculation - * to finish. - * (This implementation becomes very inefficient if multiple nonce are calculated at the same time.) - * - * @param initialHash the SHA-512 hash of the object to send, sans nonce - * @param target the target, representing an unsigned long - * @param callback called with the calculated nonce as argument. The ProofOfWorkEngine implementation must make - */ - @Override - public void calculateNonce(final byte[] initialHash, final byte[] target, final Callback callback) { - waiterPool.execute(new Runnable() { - @Override - public void run() { - long startTime = System.currentTimeMillis(); - - int cores = Runtime.getRuntime().availableProcessors(); - if (cores > 255) cores = 255; - LOG.info("Doing POW using " + cores + " cores"); - List<Worker> workers = new ArrayList<>(cores); - for (int i = 0; i < cores; i++) { - Worker w = new Worker((byte) cores, i, initialHash, target); - workers.add(w); - } - List<Future<byte[]>> futures = new ArrayList<>(cores); - for (Worker w : workers) { - // Doing this in the previous loop might cause a ConcurrentModificationException in the worker - // if a worker finds a nonce while new ones are still being added. - futures.add(workerPool.submit(w)); - } - try { - while (!Thread.interrupted()) { - for (Future<byte[]> future : futures) { - if (future.isDone()) { - callback.onNonceCalculated(initialHash, future.get()); - LOG.info("Nonce calculated in " + ((System.currentTimeMillis() - startTime) / 1000) + " seconds"); - for (Future<byte[]> f : futures) { - f.cancel(true); - } - return; - } - } - Thread.sleep(100); - } - LOG.error("POW waiter thread interrupted - this should not happen!"); - } catch (ExecutionException e) { - LOG.error(e.getMessage(), e); - } catch (InterruptedException e) { - LOG.error("POW waiter thread interrupted - this should not happen!", e); - } - } - }); - } - - private class Worker implements Callable<byte[]> { - private final byte numberOfCores; - private final byte[] initialHash; - private final byte[] target; - private final MessageDigest mda; - private final byte[] nonce = new byte[8]; - - Worker(byte numberOfCores, int core, byte[] initialHash, byte[] target) { - this.numberOfCores = numberOfCores; - this.initialHash = initialHash; - this.target = target; - this.nonce[7] = (byte) core; - try { - mda = MessageDigest.getInstance("SHA-512"); - } catch (NoSuchAlgorithmException e) { - LOG.error(e.getMessage(), e); - throw new ApplicationException(e); - } - } - - @Override - public byte[] call() throws Exception { - do { - inc(nonce, numberOfCores); - mda.update(nonce); - mda.update(initialHash); - if (!Bytes.lt(target, mda.digest(mda.digest()), 8)) { - return nonce; - } - } while (!Thread.interrupted()); - return null; - } - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/MultiThreadedPOWEngine.kt b/core/src/main/java/ch/dissem/bitmessage/ports/MultiThreadedPOWEngine.kt new file mode 100644 index 0000000..2f2c8b8 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/ports/MultiThreadedPOWEngine.kt @@ -0,0 +1,119 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.ports + +import ch.dissem.bitmessage.exception.ApplicationException +import ch.dissem.bitmessage.utils.Bytes +import ch.dissem.bitmessage.utils.Bytes.inc +import ch.dissem.bitmessage.utils.ThreadFactoryBuilder.Companion.pool +import org.slf4j.LoggerFactory +import java.security.MessageDigest +import java.security.NoSuchAlgorithmException +import java.util.* +import java.util.concurrent.Callable +import java.util.concurrent.ExecutionException +import java.util.concurrent.Executors +import java.util.concurrent.Future + +/** + * A POW engine using all available CPU cores. + */ +class MultiThreadedPOWEngine : ProofOfWorkEngine { + private val waiterPool = Executors.newSingleThreadExecutor(pool("POW-waiter").daemon().build()) + private val workerPool = Executors.newCachedThreadPool(pool("POW-worker").daemon().build()) + + /** + * This method will block until all pending nonce calculations are done, but not wait for its own calculation + * to finish. + * (This implementation becomes very inefficient if multiple nonce are calculated at the same time.) + + * @param initialHash the SHA-512 hash of the object to send, sans nonce + * * + * @param target the target, representing an unsigned long + * * + * @param callback called with the calculated nonce as argument. The ProofOfWorkEngine implementation must make + */ + override fun calculateNonce(initialHash: ByteArray, target: ByteArray, callback: ProofOfWorkEngine.Callback) { + waiterPool.execute({ + val startTime = System.currentTimeMillis() + + var cores = Runtime.getRuntime().availableProcessors() + if (cores > 255) cores = 255 + LOG.info("Doing POW using $cores cores") + val workers = ArrayList<Worker>(cores) + for (i in 0..cores - 1) { + val w = Worker(cores.toByte(), i, initialHash, target) + workers.add(w) + } + val futures = ArrayList<Future<ByteArray>>(cores) + // Doing this in the previous loop might cause a ConcurrentModificationException in the worker + // if a worker finds a nonce while new ones are still being added. + workers.mapTo(futures) { workerPool.submit(it) } + try { + while (!Thread.interrupted()) { + futures.firstOrNull { it.isDone }?.let { + callback.onNonceCalculated(initialHash, it.get()) + LOG.info("Nonce calculated in " + (System.currentTimeMillis() - startTime) / 1000 + " seconds") + futures.forEach { it.cancel(true) } + return@execute + } + } + LOG.error("POW waiter thread interrupted - this should not happen!") + } catch (e: ExecutionException) { + LOG.error(e.message, e) + } catch (e: InterruptedException) { + LOG.error("POW waiter thread interrupted - this should not happen!", e) + } + }) + } + + private inner class Worker internal constructor( + private val numberOfCores: Byte, core: Int, + private val initialHash: ByteArray, + private val target: ByteArray + ) : Callable<ByteArray> { + private val mda: MessageDigest + private val nonce = ByteArray(8) + + init { + this.nonce[7] = core.toByte() + try { + mda = MessageDigest.getInstance("SHA-512") + } catch (e: NoSuchAlgorithmException) { + LOG.error(e.message, e) + throw ApplicationException(e) + } + + } + + override fun call(): ByteArray? { + do { + inc(nonce, numberOfCores) + mda.update(nonce) + mda.update(initialHash) + if (!Bytes.lt(target, mda.digest(mda.digest()), 8)) { + return nonce + } + } while (!Thread.interrupted()) + return null + } + } + + companion object { + private val LOG = LoggerFactory.getLogger(MultiThreadedPOWEngine::class.java) + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.java b/core/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.kt similarity index 58% rename from core/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.java rename to core/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.kt index e9c164e..72fee7b 100644 --- a/core/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.java +++ b/core/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015 Christian Basler + * Copyright 2017 Christian Basler * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,73 +14,73 @@ * limitations under the License. */ -package ch.dissem.bitmessage.ports; +package ch.dissem.bitmessage.ports -import ch.dissem.bitmessage.entity.CustomMessage; -import ch.dissem.bitmessage.entity.ObjectMessage; -import ch.dissem.bitmessage.entity.valueobject.InventoryVector; -import ch.dissem.bitmessage.utils.Property; +import ch.dissem.bitmessage.entity.CustomMessage +import ch.dissem.bitmessage.entity.ObjectMessage +import ch.dissem.bitmessage.entity.valueobject.InventoryVector +import ch.dissem.bitmessage.utils.Property -import java.io.IOException; -import java.net.InetAddress; -import java.util.Collection; -import java.util.concurrent.Future; +import java.io.IOException +import java.net.InetAddress +import java.util.concurrent.Future /** * Handles incoming messages */ -public interface NetworkHandler { - int NETWORK_MAGIC_NUMBER = 8; - int HEADER_SIZE = 24; - int MAX_PAYLOAD_SIZE = 1600003; - int MAX_MESSAGE_SIZE = HEADER_SIZE + MAX_PAYLOAD_SIZE; +interface NetworkHandler { /** * Connects to the trusted host, fetches and offers new messages and disconnects afterwards. - * <p> + * + * * An implementation should disconnect if either the timeout is reached or the returned thread is interrupted. - * </p> + * */ - Future<?> synchronize(InetAddress server, int port, long timeoutInSeconds); + fun synchronize(server: InetAddress, port: Int, timeoutInSeconds: Long): Future<*> /** * Send a custom message to a specific node (that should implement handling for this message type) and returns - * the response, which in turn is expected to be a {@link CustomMessage}. - * + * the response, which in turn is expected to be a [CustomMessage]. + * @param server the node's address + * * * @param port the node's port + * * * @param request the request + * * * @return the response */ - CustomMessage send(InetAddress server, int port, CustomMessage request); + fun send(server: InetAddress, port: Int, request: CustomMessage): CustomMessage /** * Start a full network node, accepting incoming connections and relaying objects. */ - void start(); + fun start() /** * Stop the full network node. */ - void stop(); + fun stop() /** * Offer new objects to up to 8 random nodes. */ - void offer(InventoryVector iv); + fun offer(iv: InventoryVector) /** * Request each of those objects from a node that knows of the requested object. - * + * @param inventoryVectors of the objects to be requested */ - void request(Collection<InventoryVector> inventoryVectors); + fun request(inventoryVectors: Collection<InventoryVector>) - Property getNetworkStatus(); + val networkStatus: Property - boolean isRunning(); + val isRunning: Boolean interface MessageListener { - void receive(ObjectMessage object) throws IOException; + @Throws(IOException::class) + fun receive(`object`: ObjectMessage) } } diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/NodeRegistry.java b/core/src/main/java/ch/dissem/bitmessage/ports/NodeRegistry.kt similarity index 69% rename from core/src/main/java/ch/dissem/bitmessage/ports/NodeRegistry.java rename to core/src/main/java/ch/dissem/bitmessage/ports/NodeRegistry.kt index ad5219b..59e3567 100644 --- a/core/src/main/java/ch/dissem/bitmessage/ports/NodeRegistry.java +++ b/core/src/main/java/ch/dissem/bitmessage/ports/NodeRegistry.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015 Christian Basler + * Copyright 2017 Christian Basler * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,23 +14,21 @@ * limitations under the License. */ -package ch.dissem.bitmessage.ports; +package ch.dissem.bitmessage.ports -import ch.dissem.bitmessage.entity.valueobject.NetworkAddress; - -import java.util.List; +import ch.dissem.bitmessage.entity.valueobject.NetworkAddress /** * Stores and provides known peers. */ -public interface NodeRegistry { +interface NodeRegistry { /** * Removes all known nodes from registry. This should work around connection issues * when there are many invalid nodes in the registry. */ - void clear(); + fun clear() - List<NetworkAddress> getKnownAddresses(int limit, long... streams); + fun getKnownAddresses(limit: Int, vararg streams: Long): List<NetworkAddress> - void offerAddresses(List<NetworkAddress> addresses); + fun offerAddresses(addresses: List<NetworkAddress>) } diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/NodeRegistryHelper.java b/core/src/main/java/ch/dissem/bitmessage/ports/NodeRegistryHelper.java deleted file mode 100644 index 63d70eb..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/ports/NodeRegistryHelper.java +++ /dev/null @@ -1,54 +0,0 @@ -package ch.dissem.bitmessage.ports; - -import ch.dissem.bitmessage.entity.valueobject.NetworkAddress; -import ch.dissem.bitmessage.exception.ApplicationException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.io.InputStream; -import java.net.InetAddress; -import java.util.*; - -/** - * Helper class to kick start node registries. - */ -public class NodeRegistryHelper { - private static final Logger LOG = LoggerFactory.getLogger(NodeRegistryHelper.class); - - public static Map<Long, Set<NetworkAddress>> loadStableNodes() { - try (InputStream in = NodeRegistryHelper.class.getClassLoader().getResourceAsStream("nodes.txt")) { - Scanner scanner = new Scanner(in); - long stream = 0; - Map<Long, Set<NetworkAddress>> result = new HashMap<>(); - Set<NetworkAddress> streamSet = null; - while (scanner.hasNext()) { - try { - String line = scanner.nextLine().trim(); - if (line.startsWith("[stream")) { - stream = Long.parseLong(line.substring(8, line.lastIndexOf(']'))); - streamSet = new HashSet<>(); - result.put(stream, streamSet); - } else if (streamSet != null && !line.isEmpty() && !line.startsWith("#")) { - int portIndex = line.lastIndexOf(':'); - InetAddress[] inetAddresses = InetAddress.getAllByName(line.substring(0, portIndex)); - int port = Integer.valueOf(line.substring(portIndex + 1)); - for (InetAddress inetAddress : inetAddresses) { - streamSet.add(new NetworkAddress.Builder().ip(inetAddress).port(port).stream(stream).build()); - } - } - } catch (IOException e) { - LOG.warn(e.getMessage(), e); - } - } - if (LOG.isDebugEnabled()) { - for (Map.Entry<Long, Set<NetworkAddress>> e : result.entrySet()) { - LOG.debug("Stream " + e.getKey() + ": loaded " + e.getValue().size() + " bootstrap nodes."); - } - } - return result; - } catch (IOException e) { - throw new ApplicationException(e); - } - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/NodeRegistryHelper.kt b/core/src/main/java/ch/dissem/bitmessage/ports/NodeRegistryHelper.kt new file mode 100644 index 0000000..1b947dd --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/ports/NodeRegistryHelper.kt @@ -0,0 +1,65 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.ports + +import ch.dissem.bitmessage.entity.valueobject.NetworkAddress +import org.slf4j.LoggerFactory +import java.io.IOException +import java.net.InetAddress +import java.util.* + +/** + * Helper class to kick start node registries. + */ +object NodeRegistryHelper { + private val LOG = LoggerFactory.getLogger(NodeRegistryHelper::class.java) + + @JvmStatic + fun loadStableNodes(): Map<Long, Set<NetworkAddress>> { + javaClass.classLoader.getResourceAsStream("nodes.txt").use { `in` -> + val scanner = Scanner(`in`) + var stream: Long = 0 + val result = HashMap<Long, Set<NetworkAddress>>() + var streamSet: MutableSet<NetworkAddress>? = null + while (scanner.hasNext()) { + try { + val line = scanner.nextLine().trim { it <= ' ' } + if (line.startsWith("[stream")) { + stream = java.lang.Long.parseLong(line.substring(8, line.lastIndexOf(']'))) + streamSet = HashSet<NetworkAddress>() + result.put(stream, streamSet) + } else if (streamSet != null && !line.isEmpty() && !line.startsWith("#")) { + val portIndex = line.lastIndexOf(':') + val inetAddresses = InetAddress.getAllByName(line.substring(0, portIndex)) + val port = Integer.valueOf(line.substring(portIndex + 1))!! + for (inetAddress in inetAddresses) { + streamSet.add(NetworkAddress.Builder().ip(inetAddress).port(port).stream(stream).build()) + } + } + } catch (e: IOException) { + LOG.warn(e.message, e) + } + } + if (LOG.isDebugEnabled) { + for ((key, value) in result) { + LOG.debug("Stream " + key + ": loaded " + value.size + " bootstrap nodes.") + } + } + return result + } + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/ProofOfWorkEngine.java b/core/src/main/java/ch/dissem/bitmessage/ports/ProofOfWorkEngine.kt similarity index 77% rename from core/src/main/java/ch/dissem/bitmessage/ports/ProofOfWorkEngine.java rename to core/src/main/java/ch/dissem/bitmessage/ports/ProofOfWorkEngine.kt index fc7b4c2..ce76694 100644 --- a/core/src/main/java/ch/dissem/bitmessage/ports/ProofOfWorkEngine.java +++ b/core/src/main/java/ch/dissem/bitmessage/ports/ProofOfWorkEngine.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015 Christian Basler + * Copyright 2017 Christian Basler * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,27 +14,29 @@ * limitations under the License. */ -package ch.dissem.bitmessage.ports; +package ch.dissem.bitmessage.ports /** * Does the proof of work necessary to send an object. */ -public interface ProofOfWorkEngine { +interface ProofOfWorkEngine { /** * Returns a nonce, such that the first 8 bytes from sha512(sha512(nonce||initialHash)) represent a unsigned long * smaller than target. - * + * @param initialHash the SHA-512 hash of the object to send, sans nonce + * * * @param target the target, representing an unsigned long + * * * @param callback called with the calculated nonce as argument. The ProofOfWorkEngine implementation must make - * sure this is only called once. + * * sure this is only called once. */ - void calculateNonce(byte[] initialHash, byte[] target, Callback callback); + fun calculateNonce(initialHash: ByteArray, target: ByteArray, callback: Callback) interface Callback { /** * @param nonce 8 bytes nonce */ - void onNonceCalculated(byte[] initialHash, byte[] nonce); + fun onNonceCalculated(initialHash: ByteArray, nonce: ByteArray) } } diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/ProofOfWorkRepository.java b/core/src/main/java/ch/dissem/bitmessage/ports/ProofOfWorkRepository.java deleted file mode 100644 index b27a05d..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/ports/ProofOfWorkRepository.java +++ /dev/null @@ -1,45 +0,0 @@ -package ch.dissem.bitmessage.ports; - -import ch.dissem.bitmessage.entity.ObjectMessage; -import ch.dissem.bitmessage.entity.Plaintext; - -import java.util.List; - -/** - * Objects that proof of work is currently being done for. - * - * @author Christian Basler - */ -public interface ProofOfWorkRepository { - Item getItem(byte[] initialHash); - - List<byte[]> getItems(); - - void putObject(ObjectMessage object, long nonceTrialsPerByte, long extraBytes); - - void putObject(Item item); - - void removeObject(byte[] initialHash); - - class Item { - public final ObjectMessage object; - public final long nonceTrialsPerByte; - public final long extraBytes; - - // Needed for ACK POW calculation - public final Long expirationTime; - public final Plaintext message; - - public Item(ObjectMessage object, long nonceTrialsPerByte, long extraBytes) { - this(object, nonceTrialsPerByte, extraBytes, 0, null); - } - - public Item(ObjectMessage object, long nonceTrialsPerByte, long extraBytes, long expirationTime, Plaintext message) { - this.object = object; - this.nonceTrialsPerByte = nonceTrialsPerByte; - this.extraBytes = extraBytes; - this.expirationTime = expirationTime; - this.message = message; - } - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/ProofOfWorkRepository.kt b/core/src/main/java/ch/dissem/bitmessage/ports/ProofOfWorkRepository.kt new file mode 100644 index 0000000..fbf5f60 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/ports/ProofOfWorkRepository.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.ports + +import ch.dissem.bitmessage.entity.ObjectMessage +import ch.dissem.bitmessage.entity.Plaintext + +/** + * Objects that proof of work is currently being done for. + + * @author Christian Basler + */ +interface ProofOfWorkRepository { + fun getItem(initialHash: ByteArray): Item + + fun getItems(): List<ByteArray> + + fun putObject(`object`: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long) + + fun putObject(item: Item) + + fun removeObject(initialHash: ByteArray) + + data class Item @JvmOverloads constructor( + val `object`: ObjectMessage, + val nonceTrialsPerByte: Long, + val extraBytes: Long, + // Needed for ACK POW calculation + val expirationTime: Long? = 0, + val message: Plaintext? = null + ) +} diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/SimplePOWEngine.java b/core/src/main/java/ch/dissem/bitmessage/ports/SimplePOWEngine.java deleted file mode 100644 index a7d0d57..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/ports/SimplePOWEngine.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.ports; - -import ch.dissem.bitmessage.exception.ApplicationException; -import ch.dissem.bitmessage.utils.Bytes; - -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; - -import static ch.dissem.bitmessage.utils.Bytes.inc; - -/** - * You should really use the MultiThreadedPOWEngine, but this one might help you grok the other one. - * <p> - * <strong>Warning:</strong> implementations probably depend on POW being asynchronous, that's - * another reason not to use this one. - * </p> - */ -public class SimplePOWEngine implements ProofOfWorkEngine { - @Override - public void calculateNonce(byte[] initialHash, byte[] target, Callback callback) { - try { - MessageDigest mda = MessageDigest.getInstance("SHA-512"); - byte[] nonce = new byte[8]; - do { - inc(nonce); - mda.update(nonce); - mda.update(initialHash); - } while (Bytes.lt(target, mda.digest(mda.digest()), 8)); - callback.onNonceCalculated(initialHash, nonce); - } catch (NoSuchAlgorithmException e) { - throw new ApplicationException(e); - } - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/SimplePOWEngine.kt b/core/src/main/java/ch/dissem/bitmessage/ports/SimplePOWEngine.kt new file mode 100644 index 0000000..3d7c6d3 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/ports/SimplePOWEngine.kt @@ -0,0 +1,39 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.ports + +import ch.dissem.bitmessage.utils.Bytes +import java.security.MessageDigest + +/** + * You should really use the MultiThreadedPOWEngine, but this one might help you grok the other one. + * + * **Warning:** implementations probably depend on POW being asynchronous, that's + * another reason not to use this one. + */ +class SimplePOWEngine : ProofOfWorkEngine { + override fun calculateNonce(initialHash: ByteArray, target: ByteArray, callback: ProofOfWorkEngine.Callback) { + val mda = MessageDigest.getInstance("SHA-512") + val nonce = ByteArray(8) + do { + Bytes.inc(nonce) + mda.update(nonce) + mda.update(initialHash) + } while (Bytes.lt(target, mda.digest(mda.digest()), 8)) + callback.onNonceCalculated(initialHash, nonce) + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/AccessCounter.java b/core/src/main/java/ch/dissem/bitmessage/utils/AccessCounter.kt similarity index 52% rename from core/src/main/java/ch/dissem/bitmessage/utils/AccessCounter.java rename to core/src/main/java/ch/dissem/bitmessage/utils/AccessCounter.kt index 55c23c3..9da06e8 100644 --- a/core/src/main/java/ch/dissem/bitmessage/utils/AccessCounter.java +++ b/core/src/main/java/ch/dissem/bitmessage/utils/AccessCounter.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015 Christian Basler + * Copyright 2017 Christian Basler * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,48 +14,50 @@ * limitations under the License. */ -package ch.dissem.bitmessage.utils; +package ch.dissem.bitmessage.utils /** * Intended to count the bytes read or written during (de-)serialization. */ -public class AccessCounter { - private int count; - - /** - * Increases the counter by one, if not null. - */ - public static void inc(AccessCounter counter) { - if (counter != null) counter.inc(); - } - - /** - * Increases the counter by length, if not null. - */ - public static void inc(AccessCounter counter, int length) { - if (counter != null) counter.inc(length); - } +class AccessCounter { + private var count: Int = 0 /** * Increases the counter by one. */ - private void inc() { - count++; + private fun inc() { + count++ } /** * Increases the counter by length. */ - private void inc(int length) { - count += length; + private fun inc(length: Int) { + count += length } - public int length() { - return count; + fun length(): Int { + return count } - @Override - public String toString() { - return String.valueOf(count); + override fun toString(): String { + return count.toString() + } + + companion object { + + /** + * Increases the counter by one, if not null. + */ + @JvmStatic fun inc(counter: AccessCounter?) { + counter?.inc() + } + + /** + * Increases the counter by length, if not null. + */ + @JvmStatic fun inc(counter: AccessCounter?, length: Int) { + counter?.inc(length) + } } } diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/Base58.java b/core/src/main/java/ch/dissem/bitmessage/utils/Base58.java deleted file mode 100644 index a67e344..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/utils/Base58.java +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright 2011 Google Inc. - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.utils; - -import ch.dissem.bitmessage.exception.AddressFormatException; -import ch.dissem.bitmessage.exception.ApplicationException; - -import java.io.UnsupportedEncodingException; - -import static java.util.Arrays.copyOfRange; - -/** - * Base58 encoder and decoder. - * - * @author Christian Basler: I removed some dependencies to the BitcoinJ code so it can be used here more easily. - */ -public class Base58 { - private static final int[] INDEXES = new int[128]; - private static final char[] ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray(); - - static { - for (int i = 0; i < INDEXES.length; i++) { - INDEXES[i] = -1; - } - for (int i = 0; i < ALPHABET.length; i++) { - INDEXES[ALPHABET[i]] = i; - } - } - - /** - * Encodes the given bytes in base58. No checksum is appended. - * - * @param data to encode - * @return base58 encoded input - */ - public static String encode(byte[] data) { - if (data.length == 0) { - return ""; - } - final byte[] bytes = copyOfRange(data, 0, data.length); - // Count leading zeroes. - int zeroCount = 0; - while (zeroCount < bytes.length && bytes[zeroCount] == 0) { - ++zeroCount; - } - // The actual encoding. - byte[] temp = new byte[bytes.length * 2]; - int j = temp.length; - - int startAt = zeroCount; - while (startAt < bytes.length) { - byte mod = divmod58(bytes, startAt); - if (bytes[startAt] == 0) { - ++startAt; - } - temp[--j] = (byte) ALPHABET[mod]; - } - - // Strip extra '1' if there are some after decoding. - while (j < temp.length && temp[j] == ALPHABET[0]) { - ++j; - } - // Add as many leading '1' as there were leading zeros. - while (--zeroCount >= 0) { - temp[--j] = (byte) ALPHABET[0]; - } - - byte[] output = copyOfRange(temp, j, temp.length); - try { - return new String(output, "US-ASCII"); - } catch (UnsupportedEncodingException e) { - throw new ApplicationException(e); // Cannot happen. - } - } - - public static byte[] decode(String input) throws AddressFormatException { - if (input.length() == 0) { - return new byte[0]; - } - byte[] input58 = new byte[input.length()]; - // Transform the String to a base58 byte sequence - for (int i = 0; i < input.length(); ++i) { - char c = input.charAt(i); - - int digit58 = -1; - if (c < 128) { - digit58 = INDEXES[c]; - } - if (digit58 < 0) { - throw new AddressFormatException("Illegal character " + c + " at " + i); - } - - input58[i] = (byte) digit58; - } - // Count leading zeroes - int zeroCount = 0; - while (zeroCount < input58.length && input58[zeroCount] == 0) { - ++zeroCount; - } - // The encoding - byte[] temp = new byte[input.length()]; - int j = temp.length; - - int startAt = zeroCount; - while (startAt < input58.length) { - byte mod = divmod256(input58, startAt); - if (input58[startAt] == 0) { - ++startAt; - } - - temp[--j] = mod; - } - // Do no add extra leading zeroes, move j to first non null byte. - while (j < temp.length && temp[j] == 0) { - ++j; - } - return copyOfRange(temp, j - zeroCount, temp.length); - } - - // - // number -> number / 58, returns number % 58 - // - private static byte divmod58(byte[] number, int startAt) { - int remainder = 0; - for (int i = startAt; i < number.length; i++) { - int digit256 = (int) number[i] & 0xFF; - int temp = remainder * 256 + digit256; - - number[i] = (byte) (temp / 58); - - remainder = temp % 58; - } - - return (byte) remainder; - } - - // - // number -> number / 256, returns number % 256 - // - private static byte divmod256(byte[] number58, int startAt) { - int remainder = 0; - for (int i = startAt; i < number58.length; i++) { - int digit58 = (int) number58[i] & 0xFF; - int temp = remainder * 58 + digit58; - - number58[i] = (byte) (temp / 256); - - remainder = temp % 256; - } - - return (byte) remainder; - } -} \ No newline at end of file diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/Base58.kt b/core/src/main/java/ch/dissem/bitmessage/utils/Base58.kt new file mode 100644 index 0000000..7f98f93 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/utils/Base58.kt @@ -0,0 +1,161 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.utils + +import ch.dissem.bitmessage.exception.AddressFormatException +import java.util.Arrays.copyOfRange + +/** + * Base58 encoder and decoder. + + * @author Christian Basler: I removed some dependencies to the BitcoinJ code so it can be used here more easily. + */ +object Base58 { + private val INDEXES = IntArray(128) + private val ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray() + + init { + for (i in INDEXES.indices) { + INDEXES[i] = -1 + } + for (i in ALPHABET.indices) { + INDEXES[ALPHABET[i].toInt()] = i + } + } + + /** + * Encodes the given bytes in base58. No checksum is appended. + + * @param data to encode + * * + * @return base58 encoded input + */ + @JvmStatic fun encode(data: ByteArray): String { + if (data.isEmpty()) { + return "" + } + val bytes = copyOfRange(data, 0, data.size) + // Count leading zeroes. + var zeroCount = 0 + while (zeroCount < bytes.size && bytes[zeroCount].toInt() == 0) { + ++zeroCount + } + // The actual encoding. + val temp = ByteArray(bytes.size * 2) + var j = temp.size + + var startAt = zeroCount + while (startAt < bytes.size) { + val mod = divmod58(bytes, startAt) + if (bytes[startAt].toInt() == 0) { + ++startAt + } + temp[--j] = ALPHABET[mod.toInt()].toByte() + } + + // Strip extra '1' if there are some after decoding. + while (j < temp.size && temp[j] == ALPHABET[0].toByte()) { + ++j + } + // Add as many leading '1' as there were leading zeros. + while (--zeroCount >= 0) { + temp[--j] = ALPHABET[0].toByte() + } + + val output = copyOfRange(temp, j, temp.size) + return String(output, Charsets.US_ASCII) + } + + @Throws(AddressFormatException::class) + @JvmStatic fun decode(input: String): ByteArray { + if (input.isEmpty()) { + return ByteArray(0) + } + val input58 = ByteArray(input.length) + // Transform the String to a base58 byte sequence + for (i in 0..input.length - 1) { + val c = input[i] + + var digit58 = -1 + if (c.toInt() < 128) { + digit58 = INDEXES[c.toInt()] + } + if (digit58 < 0) { + throw AddressFormatException("Illegal character $c at $i") + } + + input58[i] = digit58.toByte() + } + // Count leading zeroes + var zeroCount = 0 + while (zeroCount < input58.size && input58[zeroCount].toInt() == 0) { + ++zeroCount + } + // The encoding + val temp = ByteArray(input.length) + var j = temp.size + + var startAt = zeroCount + while (startAt < input58.size) { + val mod = divmod256(input58, startAt) + if (input58[startAt].toInt() == 0) { + ++startAt + } + + temp[--j] = mod + } + // Do no add extra leading zeroes, move j to first non null byte. + while (j < temp.size && temp[j].toInt() == 0) { + ++j + } + return copyOfRange(temp, j - zeroCount, temp.size) + } + + // + // number -> number / 58, returns number % 58 + // + private fun divmod58(number: ByteArray, startAt: Int): Byte { + var remainder = 0 + for (i in startAt..number.size - 1) { + val digit256 = number[i].toInt() and 0xFF + val temp = remainder * 256 + digit256 + + number[i] = (temp / 58).toByte() + + remainder = temp % 58 + } + + return remainder.toByte() + } + + // + // number -> number / 256, returns number % 256 + // + private fun divmod256(number58: ByteArray, startAt: Int): Byte { + var remainder = 0 + for (i in startAt..number58.size - 1) { + val digit58 = number58[i].toInt() and 0xFF + val temp = remainder * 58 + digit58 + + number58[i] = (temp / 256).toByte() + + remainder = temp % 256 + } + + return remainder.toByte() + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/Bytes.java b/core/src/main/java/ch/dissem/bitmessage/utils/Bytes.java index 986c288..15e174c 100644 --- a/core/src/main/java/ch/dissem/bitmessage/utils/Bytes.java +++ b/core/src/main/java/ch/dissem/bitmessage/utils/Bytes.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 Christian Basler + * Copyright 2017 Christian Basler * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,10 @@ package ch.dissem.bitmessage.utils; * This is one part due to the fact that Java doesn't support unsigned numbers, and another * part so we don't have to convert between byte arrays and numbers in time critical * situations. + * <p> + * Note: This class can't yet be ported to Kotlin, as with Kotlin byte + byte = int, which + * would be rather inefficient in our case. + * </p> */ public class Bytes { public static final byte BYTE_0x80 = (byte) 0x80; diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/CallbackWaiter.java b/core/src/main/java/ch/dissem/bitmessage/utils/CallbackWaiter.kt similarity index 52% rename from core/src/main/java/ch/dissem/bitmessage/utils/CallbackWaiter.java rename to core/src/main/java/ch/dissem/bitmessage/utils/CallbackWaiter.kt index 775a5f3..14dea31 100644 --- a/core/src/main/java/ch/dissem/bitmessage/utils/CallbackWaiter.java +++ b/core/src/main/java/ch/dissem/bitmessage/utils/CallbackWaiter.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015 Christian Basler + * Copyright 2017 Christian Basler * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,35 +14,32 @@ * limitations under the License. */ -package ch.dissem.bitmessage.utils; +package ch.dissem.bitmessage.utils /** * Waits for a value within a callback method to be set. */ -public class CallbackWaiter<T> { - private final long startTime = System.currentTimeMillis(); - private volatile boolean isSet; - private T value; - private long time; +class CallbackWaiter<T> { + private val startTime = System.currentTimeMillis() + @Volatile private var isSet: Boolean = false + private var _value: T? = null + var time: Long = 0 + private set - public void setValue(T value) { - synchronized (this) { - this.time = System.currentTimeMillis() - startTime; - this.value = value; - this.isSet = true; + fun setValue(value: T?) { + synchronized(this) { + this.time = System.currentTimeMillis() - startTime + this._value = value + this.isSet = true } } - public T waitForValue() throws InterruptedException { + fun waitForValue(): T? { while (!isSet) { - Thread.sleep(100); + Thread.sleep(100) } - synchronized (this) { - return value; + synchronized(this) { + return _value } } - - public long getTime() { - return time; - } } diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/Collections.java b/core/src/main/java/ch/dissem/bitmessage/utils/Collections.java deleted file mode 100644 index 7eda028..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/utils/Collections.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.utils; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Random; - -public class Collections { - private final static Random RANDOM = new Random(); - - /** - * @param count the number of elements to return (if possible) - * @param collection the collection to take samples from - * @return a random subset of the given collection, or a copy of the collection if it's not larger than count. The - * result is by no means securely random, but should be random enough so not the same objects get selected over - * and over again. - */ - public static <T> List<T> selectRandom(int count, Collection<T> collection) { - ArrayList<T> result = new ArrayList<>(count); - if (collection.size() <= count) { - result.addAll(collection); - } else { - double collectionRest = collection.size(); - double resultRest = count; - int skipMax = (int) Math.ceil(collectionRest / resultRest); - int skip = RANDOM.nextInt(skipMax); - for (T item : collection) { - collectionRest--; - if (skip > 0) { - skip--; - } else { - result.add(item); - resultRest--; - if (resultRest == 0) { - break; - } - skipMax = (int) Math.ceil(collectionRest / resultRest); - skip = RANDOM.nextInt(skipMax); - } - } - } - return result; - } - - public static <T> T selectRandom(Collection<T> collection) { - int index = RANDOM.nextInt(collection.size()); - for (T item : collection) { - if (index == 0) { - return item; - } - index--; - } - throw new IllegalArgumentException("Empty collection? Size: " + collection.size()); - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/Collections.kt b/core/src/main/java/ch/dissem/bitmessage/utils/Collections.kt new file mode 100644 index 0000000..47626d2 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/utils/Collections.kt @@ -0,0 +1,70 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.utils + +import java.util.* + +object Collections { + private val RANDOM = Random() + + /** + * @param count the number of elements to return (if possible) + * * + * @param collection the collection to take samples from + * * + * @return a random subset of the given collection, or a copy of the collection if it's not larger than count. The + * * result is by no means securely random, but should be random enough so not the same objects get selected over + * * and over again. + */ + @JvmStatic fun <T> selectRandom(count: Int, collection: Collection<T>): List<T> { + val result = ArrayList<T>(count) + if (collection.size <= count) { + result.addAll(collection) + } else { + var collectionRest = collection.size.toDouble() + var resultRest = count.toDouble() + var skipMax = Math.ceil(collectionRest / resultRest).toInt() + var skip = RANDOM.nextInt(skipMax) + for (item in collection) { + collectionRest-- + if (skip > 0) { + skip-- + } else { + result.add(item) + resultRest-- + if (resultRest == 0.0) { + break + } + skipMax = Math.ceil(collectionRest / resultRest).toInt() + skip = RANDOM.nextInt(skipMax) + } + } + } + return result + } + + @JvmStatic fun <T> selectRandom(collection: Collection<T>): T { + var index = RANDOM.nextInt(collection.size) + for (item in collection) { + if (index == 0) { + return item + } + index-- + } + throw IllegalArgumentException("Empty collection? Size: " + collection.size) + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/ConversationService.java b/core/src/main/java/ch/dissem/bitmessage/utils/ConversationService.java deleted file mode 100644 index f261a08..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/utils/ConversationService.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright 2017 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.utils; - -import ch.dissem.bitmessage.entity.Plaintext; -import ch.dissem.bitmessage.entity.valueobject.InventoryVector; -import ch.dissem.bitmessage.ports.MessageRepository; - -import java.util.*; -import java.util.Collections; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import static java.util.regex.Pattern.CASE_INSENSITIVE; - -/** - * Service that helps with conversations. - */ -public class ConversationService { - private final MessageRepository messageRepository; - - private final Pattern SUBJECT_PREFIX = Pattern.compile("^(re|fwd?):", CASE_INSENSITIVE); - - public ConversationService(MessageRepository messageRepository) { - this.messageRepository = messageRepository; - } - - /** - * Retrieve the whole conversation from one single message. If the message isn't part - * of a conversation, a singleton list containing the given message is returned. Otherwise - * it's the same as {@link #getConversation(UUID)} - * - * @param message - * @return a list of messages that belong to the same conversation. - */ - public List<Plaintext> getConversation(Plaintext message) { - if (message.getConversationId() == null) { - return Collections.singletonList(message); - } - return getConversation(message.getConversationId()); - } - - private LinkedList<Plaintext> sorted(Collection<Plaintext> collection) { - LinkedList<Plaintext> result = new LinkedList<>(collection); - Collections.sort(result, new Comparator<Plaintext>() { - @Override - public int compare(Plaintext o1, Plaintext o2) { - //noinspection NumberEquality - if both are null (if both are the same, it's a bonus) - if (o1.getReceived() == o2.getReceived()) { - return 0; - } - if (o1.getReceived() == null) { - return -1; - } - if (o2.getReceived() == null) { - return 1; - } - return -o1.getReceived().compareTo(o2.getReceived()); - } - }); - return result; - } - - public List<Plaintext> getConversation(UUID conversationId) { - LinkedList<Plaintext> messages = sorted(messageRepository.getConversation(conversationId)); - Map<InventoryVector, Plaintext> map = new HashMap<>(messages.size()); - for (Plaintext message : messages) { - if (message.getInventoryVector() != null) { - map.put(message.getInventoryVector(), message); - } - } - - LinkedList<Plaintext> result = new LinkedList<>(); - while (!messages.isEmpty()) { - Plaintext last = messages.poll(); - int pos = lastParentPosition(last, result); - result.add(pos, last); - addAncestors(last, result, messages, map); - } - return result; - } - - public String getSubject(List<Plaintext> conversation) { - if (conversation.isEmpty()) { - return null; - } - // TODO: this has room for improvement - String subject = conversation.get(0).getSubject(); - Matcher matcher = SUBJECT_PREFIX.matcher(subject); - if (matcher.find()) { - return subject.substring(matcher.end()).trim(); - } - return subject.trim(); - } - - private int lastParentPosition(Plaintext child, LinkedList<Plaintext> messages) { - Iterator<Plaintext> plaintextIterator = messages.descendingIterator(); - int i = 0; - while (plaintextIterator.hasNext()) { - Plaintext next = plaintextIterator.next(); - if (isParent(next, child)) { - break; - } - i++; - } - return messages.size() - i; - } - - private boolean isParent(Plaintext item, Plaintext child) { - for (InventoryVector parentId : child.getParents()) { - if (parentId.equals(item.getInventoryVector())) { - return true; - } - } - return false; - } - - private void addAncestors(Plaintext message, LinkedList<Plaintext> result, LinkedList<Plaintext> messages, Map<InventoryVector, Plaintext> map) { - for (InventoryVector parentKey : message.getParents()) { - Plaintext parent = map.remove(parentKey); - if (parent != null) { - messages.remove(parent); - result.addFirst(parent); - addAncestors(parent, result, messages, map); - } - } - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/ConversationService.kt b/core/src/main/java/ch/dissem/bitmessage/utils/ConversationService.kt new file mode 100644 index 0000000..392a1cf --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/utils/ConversationService.kt @@ -0,0 +1,120 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.utils + +import ch.dissem.bitmessage.entity.Plaintext +import ch.dissem.bitmessage.entity.valueobject.InventoryVector +import ch.dissem.bitmessage.ports.MessageRepository +import java.util.* +import java.util.Collections +import java.util.regex.Pattern +import java.util.regex.Pattern.CASE_INSENSITIVE + +/** + * Service that helps with conversations. + */ +class ConversationService(private val messageRepository: MessageRepository) { + + private val SUBJECT_PREFIX = Pattern.compile("^(re|fwd?):", CASE_INSENSITIVE) + + /** + * Retrieve the whole conversation from one single message. If the message isn't part + * of a conversation, a singleton list containing the given message is returned. Otherwise + * it's the same as [.getConversation] + + * @param message + * * + * @return a list of messages that belong to the same conversation. + */ + fun getConversation(message: Plaintext): List<Plaintext> { + return getConversation(message.conversationId) + } + + private fun sorted(collection: Collection<Plaintext>): LinkedList<Plaintext> { + val result = LinkedList(collection) + Collections.sort(result, Comparator<Plaintext> { o1, o2 -> + return@Comparator when { + o1.received === o2.received -> 0 + o1.received == null -> -1 + o2.received == null -> 1 + else -> -o1.received.compareTo(o2.received) + } + }) + return result + } + + fun getConversation(conversationId: UUID): List<Plaintext> { + val messages = sorted(messageRepository.getConversation(conversationId)) + val map = HashMap<InventoryVector, Plaintext>(messages.size) + for (message in messages) { + message.inventoryVector?.let { + map.put(it, message) + } + } + + val result = LinkedList<Plaintext>() + while (!messages.isEmpty()) { + val last = messages.poll() + val pos = lastParentPosition(last, result) + result.add(pos, last) + addAncestors(last, result, messages, map) + } + return result + } + + fun getSubject(conversation: List<Plaintext>): String? { + if (conversation.isEmpty()) { + return null + } + // TODO: this has room for improvement + val subject = conversation[0].subject + val matcher = SUBJECT_PREFIX.matcher(subject!!) + + return if (matcher.find()) { + subject.substring(matcher.end()) + } else { + subject + }.trim { it <= ' ' } + } + + private fun lastParentPosition(child: Plaintext, messages: LinkedList<Plaintext>): Int { + val plaintextIterator = messages.descendingIterator() + var i = 0 + while (plaintextIterator.hasNext()) { + val next = plaintextIterator.next() + if (isParent(next, child)) { + break + } + i++ + } + return messages.size - i + } + + private fun isParent(item: Plaintext, child: Plaintext): Boolean { + return child.parents.firstOrNull { it == item.inventoryVector } != null + } + + private fun addAncestors(message: Plaintext, result: LinkedList<Plaintext>, messages: LinkedList<Plaintext>, map: MutableMap<InventoryVector, Plaintext>) { + for (parentKey in message.parents) { + map.remove(parentKey)?.let { + messages.remove(it) + result.addFirst(it) + addAncestors(it, result, messages, map) + } + } + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/DebugUtils.java b/core/src/main/java/ch/dissem/bitmessage/utils/DebugUtils.java deleted file mode 100644 index e2090b9..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/utils/DebugUtils.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.utils; - -import ch.dissem.bitmessage.entity.ObjectMessage; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.util.Map; - -public class DebugUtils { - private final static Logger LOG = LoggerFactory.getLogger(DebugUtils.class); - - public static void saveToFile(ObjectMessage objectMessage) { - try { - File f = new File(System.getProperty("user.home") + "/jabit.error/" + objectMessage.getInventoryVector() + ".inv"); - f.createNewFile(); - objectMessage.write(new FileOutputStream(f)); - } catch (IOException e) { - LOG.debug(e.getMessage(), e); - } - } - - public static <K> void inc(Map<K, Integer> map, K key) { - if (map.containsKey(key)) { - map.put(key, map.get(key) + 1); - } else { - map.put(key, 1); - } - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/DebugUtils.kt b/core/src/main/java/ch/dissem/bitmessage/utils/DebugUtils.kt new file mode 100644 index 0000000..31b2e65 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/utils/DebugUtils.kt @@ -0,0 +1,47 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.utils + +import ch.dissem.bitmessage.entity.ObjectMessage +import org.slf4j.LoggerFactory +import java.io.File +import java.io.FileOutputStream +import java.io.IOException + +object DebugUtils { + private val LOG = LoggerFactory.getLogger(DebugUtils::class.java) + + @JvmStatic fun saveToFile(objectMessage: ObjectMessage) { + try { + val f = File(System.getProperty("user.home") + "/jabit.error/" + objectMessage.inventoryVector + ".inv") + f.createNewFile() + objectMessage.write(FileOutputStream(f)) + } catch (e: IOException) { + LOG.debug(e.message, e) + } + + } + + @JvmStatic fun <K> inc(map: MutableMap<K, Int>, key: K) { + val value = map.get(key); + if (value == null) { + map.put(key, 1) + } else { + map.put(key, value + 1) + } + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/Decode.java b/core/src/main/java/ch/dissem/bitmessage/utils/Decode.java deleted file mode 100644 index 8d05e7e..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/utils/Decode.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.utils; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.ByteBuffer; - -import static ch.dissem.bitmessage.utils.AccessCounter.inc; - -/** - * This class handles decoding simple types from byte stream, according to - * https://bitmessage.org/wiki/Protocol_specification#Common_structures - */ -public class Decode { - public static byte[] shortVarBytes(InputStream in, AccessCounter counter) throws IOException { - int length = uint16(in, counter); - return bytes(in, length, counter); - } - - public static byte[] varBytes(InputStream in) throws IOException { - return varBytes(in, null); - } - - public static byte[] varBytes(InputStream in, AccessCounter counter) throws IOException { - int length = (int) varInt(in, counter); - return bytes(in, length, counter); - } - - public static byte[] bytes(InputStream in, int count) throws IOException { - return bytes(in, count, null); - } - - public static byte[] bytes(InputStream in, int count, AccessCounter counter) throws IOException { - byte[] result = new byte[count]; - int off = 0; - while (off < count) { - int read = in.read(result, off, count - off); - if (read < 0) { - throw new IOException("Unexpected end of stream, wanted to read " + count + " bytes but only got " + off); - } - off += read; - } - inc(counter, count); - return result; - } - - public static long[] varIntList(InputStream in) throws IOException { - int length = (int) varInt(in); - long[] result = new long[length]; - - for (int i = 0; i < length; i++) { - result[i] = varInt(in); - } - return result; - } - - public static long varInt(InputStream in) throws IOException { - return varInt(in, null); - } - - public static long varInt(InputStream in, AccessCounter counter) throws IOException { - int first = in.read(); - inc(counter); - switch (first) { - case 0xfd: - return uint16(in, counter); - case 0xfe: - return uint32(in, counter); - case 0xff: - return int64(in, counter); - default: - return first; - } - } - - public static int uint8(InputStream in) throws IOException { - return in.read(); - } - - public static int uint16(InputStream in) throws IOException { - return uint16(in, null); - } - - public static int uint16(InputStream in, AccessCounter counter) throws IOException { - inc(counter, 2); - return in.read() << 8 | in.read(); - } - - public static long uint32(InputStream in) throws IOException { - return uint32(in, null); - } - - public static long uint32(InputStream in, AccessCounter counter) throws IOException { - inc(counter, 4); - return in.read() << 24 | in.read() << 16 | in.read() << 8 | in.read(); - } - - public static long uint32(ByteBuffer in) { - return u(in.get()) << 24 | u(in.get()) << 16 | u(in.get()) << 8 | u(in.get()); - } - - public static int int32(InputStream in) throws IOException { - return int32(in, null); - } - - public static int int32(InputStream in, AccessCounter counter) throws IOException { - inc(counter, 4); - return ByteBuffer.wrap(bytes(in, 4)).getInt(); - } - - public static long int64(InputStream in) throws IOException { - return int64(in, null); - } - - public static long int64(InputStream in, AccessCounter counter) throws IOException { - inc(counter, 8); - return ByteBuffer.wrap(bytes(in, 8)).getLong(); - } - - public static String varString(InputStream in) throws IOException { - return varString(in, null); - } - - public static String varString(InputStream in, AccessCounter counter) throws IOException { - int length = (int) varInt(in, counter); - // technically, it says the length in characters, but I think this one might be correct - // otherwise it will get complicated, as we'll need to read UTF-8 char by char... - return new String(bytes(in, length, counter), "utf-8"); - } - - /** - * Returns the given byte as if it were unsigned. - */ - private static int u(byte b) { - return b & 0xFF; - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/Decode.kt b/core/src/main/java/ch/dissem/bitmessage/utils/Decode.kt new file mode 100644 index 0000000..3eb192e --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/utils/Decode.kt @@ -0,0 +1,114 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.utils + +import java.io.IOException +import java.io.InputStream +import java.nio.ByteBuffer + +/** + * This class handles decoding simple types from byte stream, according to + * https://bitmessage.org/wiki/Protocol_specification#Common_structures + */ +object Decode { + @JvmStatic fun shortVarBytes(`in`: InputStream, counter: AccessCounter): ByteArray { + val length = uint16(`in`, counter) + return bytes(`in`, length, counter) + } + + @JvmStatic @JvmOverloads fun varBytes(`in`: InputStream, counter: AccessCounter? = null): ByteArray { + val length = varInt(`in`, counter).toInt() + return bytes(`in`, length, counter) + } + + @JvmStatic @JvmOverloads fun bytes(`in`: InputStream, count: Int, counter: AccessCounter? = null): ByteArray { + val result = ByteArray(count) + var off = 0 + while (off < count) { + val read = `in`.read(result, off, count - off) + if (read < 0) { + throw IOException("Unexpected end of stream, wanted to read $count bytes but only got $off") + } + off += read + } + AccessCounter.inc(counter, count) + return result + } + + @JvmStatic fun varIntList(`in`: InputStream): LongArray { + val length = varInt(`in`).toInt() + val result = LongArray(length) + + for (i in 0..length - 1) { + result[i] = varInt(`in`) + } + return result + } + + @JvmStatic @JvmOverloads fun varInt(`in`: InputStream, counter: AccessCounter? = null): Long { + val first = `in`.read() + AccessCounter.inc(counter) + when (first) { + 0xfd -> return uint16(`in`, counter).toLong() + 0xfe -> return uint32(`in`, counter) + 0xff -> return int64(`in`, counter) + else -> return first.toLong() + } + } + + @JvmStatic fun uint8(`in`: InputStream): Int { + return `in`.read() + } + + @JvmStatic @JvmOverloads fun uint16(`in`: InputStream, counter: AccessCounter? = null): Int { + AccessCounter.inc(counter, 2) + return `in`.read() shl 8 or `in`.read() + } + + @JvmStatic @JvmOverloads fun uint32(`in`: InputStream, counter: AccessCounter? = null): Long { + AccessCounter.inc(counter, 4) + return (`in`.read() shl 24 or (`in`.read() shl 16) or (`in`.read() shl 8) or `in`.read()).toLong() + } + + @JvmStatic fun uint32(`in`: ByteBuffer): Long { + return (u(`in`.get()) shl 24 or (u(`in`.get()) shl 16) or (u(`in`.get()) shl 8) or u(`in`.get())).toLong() + } + + @JvmStatic @JvmOverloads fun int32(`in`: InputStream, counter: AccessCounter? = null): Int { + AccessCounter.inc(counter, 4) + return ByteBuffer.wrap(bytes(`in`, 4)).int + } + + @JvmStatic @JvmOverloads fun int64(`in`: InputStream, counter: AccessCounter? = null): Long { + AccessCounter.inc(counter, 8) + return ByteBuffer.wrap(bytes(`in`, 8)).long + } + + @JvmStatic @JvmOverloads fun varString(`in`: InputStream, counter: AccessCounter? = null): String { + val length = varInt(`in`, counter).toInt() + // technically, it says the length in characters, but I think this one might be correct + // otherwise it will get complicated, as we'll need to read UTF-8 char by char... + return String(bytes(`in`, length, counter)) + } + + /** + * Returns the given byte as if it were unsigned. + */ + @JvmStatic private fun u(b: Byte): Int { + return b.toInt() and 0xFF + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/Encode.java b/core/src/main/java/ch/dissem/bitmessage/utils/Encode.java deleted file mode 100644 index a60c027..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/utils/Encode.java +++ /dev/null @@ -1,207 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.utils; - -import ch.dissem.bitmessage.entity.Streamable; -import ch.dissem.bitmessage.exception.ApplicationException; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.io.UnsupportedEncodingException; -import java.nio.Buffer; -import java.nio.ByteBuffer; - -import static ch.dissem.bitmessage.utils.AccessCounter.inc; - -/** - * This class handles encoding simple types from byte stream, according to - * https://bitmessage.org/wiki/Protocol_specification#Common_structures - */ -public class Encode { - public static void varIntList(long[] values, OutputStream stream) throws IOException { - varInt(values.length, stream); - for (long value : values) { - varInt(value, stream); - } - } - - public static void varIntList(long[] values, ByteBuffer buffer) { - varInt(values.length, buffer); - for (long value : values) { - varInt(value, buffer); - } - } - - public static void varInt(long value, OutputStream stream) throws IOException { - varInt(value, stream, null); - } - - public static void varInt(long value, ByteBuffer buffer) { - if (value < 0) { - // This is due to the fact that Java doesn't really support unsigned values. - // Please be aware that this might be an error due to a smaller negative value being cast to long. - // Normally, negative values shouldn't occur within the protocol, and longs large enough for being - // recognized as negatives aren't realistic. - buffer.put((byte) 0xff); - buffer.putLong(value); - } else if (value < 0xfd) { - buffer.put((byte) value); - } else if (value <= 0xffffL) { - buffer.put((byte) 0xfd); - buffer.putShort((short) value); - } else if (value <= 0xffffffffL) { - buffer.put((byte) 0xfe); - buffer.putInt((int) value); - } else { - buffer.put((byte) 0xff); - buffer.putLong(value); - } - } - - public static byte[] varInt(long value) { - ByteBuffer buffer = ByteBuffer.allocate(9); - varInt(value, buffer); - buffer.flip(); - return Bytes.truncate(buffer.array(), buffer.limit()); - } - - public static void varInt(long value, OutputStream stream, AccessCounter counter) throws IOException { - ByteBuffer buffer = ByteBuffer.allocate(9); - varInt(value, buffer); - buffer.flip(); - stream.write(buffer.array(), 0, buffer.limit()); - inc(counter, buffer.limit()); - } - - public static void int8(long value, OutputStream stream) throws IOException { - int8(value, stream, null); - } - - public static void int8(long value, OutputStream stream, AccessCounter counter) throws IOException { - stream.write((int) value); - inc(counter); - } - - public static void int16(long value, OutputStream stream) throws IOException { - int16(value, stream, null); - } - - public static void int16(long value, OutputStream stream, AccessCounter counter) throws IOException { - stream.write(ByteBuffer.allocate(2).putShort((short) value).array()); - inc(counter, 2); - } - - public static void int16(long value, ByteBuffer buffer) { - buffer.putShort((short) value); - } - - public static void int32(long value, OutputStream stream) throws IOException { - int32(value, stream, null); - } - - public static void int32(long value, OutputStream stream, AccessCounter counter) throws IOException { - stream.write(ByteBuffer.allocate(4).putInt((int) value).array()); - inc(counter, 4); - } - - public static void int32(long value, ByteBuffer buffer) { - buffer.putInt((int) value); - } - - public static void int64(long value, OutputStream stream) throws IOException { - int64(value, stream, null); - } - - public static void int64(long value, OutputStream stream, AccessCounter counter) throws IOException { - stream.write(ByteBuffer.allocate(8).putLong(value).array()); - inc(counter, 8); - } - - public static void int64(long value, ByteBuffer buffer) { - buffer.putLong(value); - } - - public static void varString(String value, OutputStream out) throws IOException { - byte[] bytes = value.getBytes("utf-8"); - // Technically, it says the length in characters, but I think this one might be correct. - // It doesn't really matter, as only ASCII characters are being used. - // see also Decode#varString() - varInt(bytes.length, out); - out.write(bytes); - } - - public static void varString(String value, ByteBuffer buffer) { - try { - byte[] bytes = value.getBytes("utf-8"); - // Technically, it says the length in characters, but I think this one might be correct. - // It doesn't really matter, as only ASCII characters are being used. - // see also Decode#varString() - buffer.put(varInt(bytes.length)); - buffer.put(bytes); - } catch (UnsupportedEncodingException e) { - throw new ApplicationException(e); - } - } - - public static void varBytes(byte[] data, OutputStream out) throws IOException { - varInt(data.length, out); - out.write(data); - } - - public static void varBytes(byte[] data, ByteBuffer buffer) { - varInt(data.length, buffer); - buffer.put(data); - } - - /** - * Serializes a {@link Streamable} object and returns the byte array. - * - * @param streamable the object to be serialized - * @return an array of bytes representing the given streamable object. - */ - public static byte[] bytes(Streamable streamable) { - if (streamable == null) return null; - - ByteArrayOutputStream stream = new ByteArrayOutputStream(); - try { - streamable.write(stream); - } catch (IOException e) { - throw new ApplicationException(e); - } - return stream.toByteArray(); - } - - /** - * @param streamable the object to be serialized - * @param padding the result will be padded such that its length is a multiple of <em>padding</em> - * @return the bytes of the given {@link Streamable} object, 0-padded such that the final length is x*padding. - */ - public static byte[] bytes(Streamable streamable, int padding) { - ByteArrayOutputStream stream = new ByteArrayOutputStream(); - try { - streamable.write(stream); - } catch (IOException e) { - throw new ApplicationException(e); - } - int offset = padding - stream.size() % padding; - int length = stream.size() + offset; - byte[] result = new byte[length]; - stream.write(result, offset, stream.size()); - return result; - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/Encode.kt b/core/src/main/java/ch/dissem/bitmessage/utils/Encode.kt new file mode 100644 index 0000000..1904c66 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/utils/Encode.kt @@ -0,0 +1,165 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.utils + +import ch.dissem.bitmessage.entity.Streamable +import java.io.ByteArrayOutputStream +import java.io.OutputStream +import java.nio.ByteBuffer + +/** + * This class handles encoding simple types from byte stream, according to + * https://bitmessage.org/wiki/Protocol_specification#Common_structures + */ +object Encode { + @JvmStatic fun varIntList(values: LongArray, stream: OutputStream) { + varInt(values.size.toLong(), stream) + for (value in values) { + varInt(value, stream) + } + } + + @JvmStatic fun varIntList(values: LongArray, buffer: ByteBuffer) { + varInt(values.size.toLong(), buffer) + for (value in values) { + varInt(value, buffer) + } + } + + @JvmStatic fun varInt(value: Long, buffer: ByteBuffer) { + if (value < 0) { + // This is due to the fact that Java doesn't really support unsigned values. + // Please be aware that this might be an error due to a smaller negative value being cast to long. + // Normally, negative values shouldn't occur within the protocol, and longs large enough for being + // recognized as negatives aren't realistic. + buffer.put(0xff.toByte()) + buffer.putLong(value) + } else if (value < 0xfd) { + buffer.put(value.toByte()) + } else if (value <= 0xffffL) { + buffer.put(0xfd.toByte()) + buffer.putShort(value.toShort()) + } else if (value <= 0xffffffffL) { + buffer.put(0xfe.toByte()) + buffer.putInt(value.toInt()) + } else { + buffer.put(0xff.toByte()) + buffer.putLong(value) + } + } + + @JvmStatic fun varInt(value: Long): ByteArray { + val buffer = ByteBuffer.allocate(9) + varInt(value, buffer) + buffer.flip() + return Bytes.truncate(buffer.array(), buffer.limit()) + } + + @JvmStatic @JvmOverloads fun varInt(value: Long, stream: OutputStream, counter: AccessCounter? = null) { + val buffer = ByteBuffer.allocate(9) + varInt(value, buffer) + buffer.flip() + stream.write(buffer.array(), 0, buffer.limit()) + AccessCounter.inc(counter, buffer.limit()) + } + + @JvmStatic @JvmOverloads fun int8(value: Long, stream: OutputStream, counter: AccessCounter? = null) { + stream.write(value.toInt()) + AccessCounter.inc(counter) + } + + @JvmStatic @JvmOverloads fun int16(value: Long, stream: OutputStream, counter: AccessCounter? = null) { + stream.write(ByteBuffer.allocate(2).putShort(value.toShort()).array()) + AccessCounter.inc(counter, 2) + } + + @JvmStatic fun int16(value: Long, buffer: ByteBuffer) { + buffer.putShort(value.toShort()) + } + + @JvmStatic @JvmOverloads fun int32(value: Long, stream: OutputStream, counter: AccessCounter? = null) { + stream.write(ByteBuffer.allocate(4).putInt(value.toInt()).array()) + AccessCounter.inc(counter, 4) + } + + @JvmStatic fun int32(value: Long, buffer: ByteBuffer) { + buffer.putInt(value.toInt()) + } + + @JvmStatic @JvmOverloads fun int64(value: Long, stream: OutputStream, counter: AccessCounter? = null) { + stream.write(ByteBuffer.allocate(8).putLong(value).array()) + AccessCounter.inc(counter, 8) + } + + @JvmStatic fun int64(value: Long, buffer: ByteBuffer) { + buffer.putLong(value) + } + + @JvmStatic fun varString(value: String, out: OutputStream) { + val bytes = value.toByteArray(charset("utf-8")) + // Technically, it says the length in characters, but I think this one might be correct. + // It doesn't really matter, as only ASCII characters are being used. + // see also Decode#varString() + varInt(bytes.size.toLong(), out) + out.write(bytes) + } + + @JvmStatic fun varString(value: String, buffer: ByteBuffer) { + val bytes = value.toByteArray() + // Technically, it says the length in characters, but I think this one might be correct. + // It doesn't really matter, as only ASCII characters are being used. + // see also Decode#varString() + buffer.put(varInt(bytes.size.toLong())) + buffer.put(bytes) + } + + @JvmStatic fun varBytes(data: ByteArray, out: OutputStream) { + varInt(data.size.toLong(), out) + out.write(data) + } + + @JvmStatic fun varBytes(data: ByteArray, buffer: ByteBuffer) { + varInt(data.size.toLong(), buffer) + buffer.put(data) + } + + /** + * Serializes a [Streamable] object and returns the byte array. + * @param streamable the object to be serialized + * @return an array of bytes representing the given streamable object. + */ + @JvmStatic fun bytes(streamable: Streamable): ByteArray { + val stream = ByteArrayOutputStream() + streamable.write(stream) + return stream.toByteArray() + } + + /** + * @param streamable the object to be serialized + * @param padding the result will be padded such that its length is a multiple of *padding* + * @return the bytes of the given [Streamable] object, 0-padded such that the final length is x*padding. + */ + @JvmStatic fun bytes(streamable: Streamable, padding: Int): ByteArray { + val stream = ByteArrayOutputStream() + streamable.write(stream) + val offset = padding - stream.size() % padding + val length = stream.size() + offset + val result = ByteArray(length) + stream.write(result, offset, stream.size()) + return result + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/Numbers.java b/core/src/main/java/ch/dissem/bitmessage/utils/Numbers.java deleted file mode 100644 index 9d0c078..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/utils/Numbers.java +++ /dev/null @@ -1,10 +0,0 @@ -package ch.dissem.bitmessage.utils; - -/** - * @author Christian Basler - */ -public class Numbers { - public static long max(long a, long b) { - return a > b ? a : b; - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/exception/DecryptionFailedException.java b/core/src/main/java/ch/dissem/bitmessage/utils/Numbers.kt similarity index 73% rename from core/src/main/java/ch/dissem/bitmessage/exception/DecryptionFailedException.java rename to core/src/main/java/ch/dissem/bitmessage/utils/Numbers.kt index c9e3efd..445cbd1 100644 --- a/core/src/main/java/ch/dissem/bitmessage/exception/DecryptionFailedException.java +++ b/core/src/main/java/ch/dissem/bitmessage/utils/Numbers.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015 Christian Basler + * Copyright 2017 Christian Basler * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,8 +14,10 @@ * limitations under the License. */ -package ch.dissem.bitmessage.exception; +@file:JvmName("Numbers") -public class DecryptionFailedException extends Exception { - private static final long serialVersionUID = 3241116253113872731L; +package ch.dissem.bitmessage.utils + +fun max(a: Long, b: Long): Long { + return if (a > b) a else b } diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/Points.java b/core/src/main/java/ch/dissem/bitmessage/utils/Points.kt similarity index 52% rename from core/src/main/java/ch/dissem/bitmessage/utils/Points.java rename to core/src/main/java/ch/dissem/bitmessage/utils/Points.kt index 937fe20..7f07188 100644 --- a/core/src/main/java/ch/dissem/bitmessage/utils/Points.java +++ b/core/src/main/java/ch/dissem/bitmessage/utils/Points.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015 Christian Basler + * Copyright 2017 Christian Basler * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,19 +14,23 @@ * limitations under the License. */ -package ch.dissem.bitmessage.utils; - -import java.util.Arrays; +package ch.dissem.bitmessage.utils /** - * Created by chris on 20.07.15. + * Helper object to get a point from a public key on a elliptic curve. */ -public class Points { - public static byte[] getX(byte[] P) { - return Arrays.copyOfRange(P, 1, ((P.length - 1) / 2) + 1); +object Points { + /** + * returns X component of the point represented by public key P + */ + @JvmStatic fun getX(P: ByteArray): ByteArray { + return P.sliceArray(1..(P.size - 1) / 2) } - public static byte[] getY(byte[] P) { - return Arrays.copyOfRange(P, ((P.length - 1) / 2) + 1, P.length); + /** + * returns Y component of the point represented by public key P + */ + @JvmStatic fun getY(P: ByteArray): ByteArray { + return P.sliceArray((P.size - 1) / 2 + 1..P.size - 1) } } diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/Property.java b/core/src/main/java/ch/dissem/bitmessage/utils/Property.java deleted file mode 100644 index b823eb5..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/utils/Property.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.utils; - -import java.util.Arrays; -import java.util.Objects; - -/** - * Some property that has a name, a value and/or other properties. This can be used for any purpose, but is for now - * used to contain different status information. It is by default displayed in some JSON inspired human readable - * notation, but you might only want to rely on the 'human readable' part. - * <p> - * If you need a real JSON representation, please add a method <code>toJson()</code>. - * </p> - */ -public class Property { - private String name; - private Object value; - private Property[] properties; - - public Property(String name, Object value, Property... properties) { - this.name = name; - this.value = value; - this.properties = properties; - } - - public String getName() { - return name; - } - - public Object getValue() { - return value; - } - - /** - * Returns the property if available or <code>null</code> otherwise. - * Subproperties can be requested by submitting the sequence of properties. - */ - public Property getProperty(String... name) { - if (name == null || name.length == 0) return null; - - for (Property p : properties) { - if (Objects.equals(name[0], p.name)) { - if (name.length == 1) - return p; - else - return p.getProperty(Arrays.copyOfRange(name, 1, name.length)); - } - } - return null; - } - - public Property[] getProperties() { - return properties; - } - - @Override - public String toString() { - return toString(""); - } - - private String toString(String indentation) { - StringBuilder result = new StringBuilder(); - result.append(indentation).append(name).append(": "); - if (value != null || properties.length == 0) { - result.append(value); - } - if (properties.length > 0) { - result.append("{\n"); - for (Property property : properties) { - result.append(property.toString(indentation + " ")).append('\n'); - } - result.append(indentation).append("}"); - } - return result.toString(); - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/Property.kt b/core/src/main/java/ch/dissem/bitmessage/utils/Property.kt new file mode 100644 index 0000000..bea1621 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/utils/Property.kt @@ -0,0 +1,68 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.utils + +/** + * Some property that has a name, a value and/or other properties. This can be used for any purpose, but is for now + * used to contain different status information. It is by default displayed in some JSON inspired human readable + * notation, but you might only want to rely on the 'human readable' part. + * + * + * If you need a real JSON representation, please add a method `toJson()`. + * + */ +class Property private constructor(val name: String, val value: Any? = null, val properties: Array<Property> = emptyArray()) { + + constructor(name: String, value: Any) : this(name = name, value = value, properties = emptyArray()) + constructor(name: String, vararg properties: Property) : this(name, null, Array(properties.size, { i -> properties[i] })) + + /** + * Returns the property if available or `null` otherwise. + * Subproperties can be requested by submitting the sequence of properties. + */ + fun getProperty(vararg name: String): Property? { + properties + .filter { name[0] == it.name } + .forEach { + if (name.size == 1) + return it + else + return it.getProperty(*name.sliceArray(1..name.size - 1)) + } + return null + } + + override fun toString(): String { + return toString("") + } + + private fun toString(indentation: String): String { + val result = StringBuilder() + result.append(indentation).append(name).append(": ") + if (value != null || properties.isEmpty()) { + result.append(value) + } + if (properties.isNotEmpty()) { + result.append("{\n") + for (property in properties) { + result.append(property.toString(indentation + " ")).append('\n') + } + result.append(indentation).append("}") + } + return result.toString() + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/Singleton.java b/core/src/main/java/ch/dissem/bitmessage/utils/Singleton.kt similarity index 58% rename from core/src/main/java/ch/dissem/bitmessage/utils/Singleton.java rename to core/src/main/java/ch/dissem/bitmessage/utils/Singleton.kt index 2eeaa97..bd7e4d7 100644 --- a/core/src/main/java/ch/dissem/bitmessage/utils/Singleton.java +++ b/core/src/main/java/ch/dissem/bitmessage/utils/Singleton.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015 Christian Basler + * Copyright 2017 Christian Basler * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,23 +14,23 @@ * limitations under the License. */ -package ch.dissem.bitmessage.utils; +package ch.dissem.bitmessage.utils -import ch.dissem.bitmessage.ports.Cryptography; +import ch.dissem.bitmessage.ports.Cryptography +import kotlin.properties.Delegates /** * @author Christian Basler */ -public class Singleton { - private static Cryptography cryptography; +object Singleton { + private var cryptography by Delegates.notNull<Cryptography>() - public static void initialize(Cryptography cryptography) { - synchronized (Singleton.class) { - Singleton.cryptography = cryptography; - } + @Synchronized + @JvmStatic fun initialize(cryptography: Cryptography) { + Singleton.cryptography = cryptography } - public static Cryptography cryptography() { - return cryptography; + @JvmStatic fun cryptography(): Cryptography { + return cryptography } } diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/SqlStrings.java b/core/src/main/java/ch/dissem/bitmessage/utils/SqlStrings.java deleted file mode 100644 index 82d06e3..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/utils/SqlStrings.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2016 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.utils; - -import ch.dissem.bitmessage.entity.payload.ObjectType; - -import static ch.dissem.bitmessage.utils.Strings.hex; - -public class SqlStrings { - public static StringBuilder join(long... objects) { - StringBuilder streamList = new StringBuilder(); - for (int i = 0; i < objects.length; i++) { - if (i > 0) streamList.append(", "); - streamList.append(objects[i]); - } - return streamList; - } - - public static StringBuilder join(byte[]... objects) { - StringBuilder streamList = new StringBuilder(); - for (int i = 0; i < objects.length; i++) { - if (i > 0) streamList.append(", "); - streamList.append(hex(objects[i])); - } - return streamList; - } - - public static StringBuilder join(ObjectType... types) { - StringBuilder streamList = new StringBuilder(); - for (int i = 0; i < types.length; i++) { - if (i > 0) streamList.append(", "); - streamList.append(types[i].getNumber()); - } - return streamList; - } - - public static StringBuilder join(Enum... types) { - StringBuilder streamList = new StringBuilder(); - for (int i = 0; i < types.length; i++) { - if (i > 0) streamList.append(", "); - streamList.append('\'').append(types[i].name()).append('\''); - } - return streamList; - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/SqlStrings.kt b/core/src/main/java/ch/dissem/bitmessage/utils/SqlStrings.kt new file mode 100644 index 0000000..d0efdf0 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/utils/SqlStrings.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.utils + +import ch.dissem.bitmessage.entity.payload.ObjectType + +object SqlStrings { + @JvmStatic fun join(vararg objects: Long): String { + return objects.joinToString() + } + + @JvmStatic fun join(vararg objects: ByteArray): String { + return objects.map { Strings.hex(it) }.joinToString() + } + + @JvmStatic fun join(vararg types: ObjectType): String { + return types.map { it.number }.joinToString() + } + + @JvmStatic fun join(vararg types: Enum<*>): String { + return types.map { '\'' + it.name + '\'' }.joinToString() + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/Strings.java b/core/src/main/java/ch/dissem/bitmessage/utils/Strings.kt similarity index 50% rename from core/src/main/java/ch/dissem/bitmessage/utils/Strings.java rename to core/src/main/java/ch/dissem/bitmessage/utils/Strings.kt index 96383f7..98fd16e 100644 --- a/core/src/main/java/ch/dissem/bitmessage/utils/Strings.java +++ b/core/src/main/java/ch/dissem/bitmessage/utils/Strings.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015 Christian Basler + * Copyright 2017 Christian Basler * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,31 +14,27 @@ * limitations under the License. */ -package ch.dissem.bitmessage.utils; +package ch.dissem.bitmessage.utils /** * Some utilities to handle strings. * TODO: Probably this should be split in a GUI related and an SQL related utility class. */ -public class Strings { - public static StringBuilder join(Object... objects) { - StringBuilder streamList = new StringBuilder(); - for (int i = 0; i < objects.length; i++) { - if (i > 0) streamList.append(", "); - streamList.append(objects[i]); - } - return streamList; +object Strings { + @JvmStatic fun join(vararg objects: Any): String { + return objects.joinToString() } - public static StringBuilder hex(byte[] bytes) { - StringBuilder hex = new StringBuilder(bytes.length + 2); - for (byte b : bytes) { - hex.append(String.format("%02x", b)); - } - return hex; + @JvmStatic fun hex(bytes: ByteArray): String { + return bytes.map { String.format("%02x", it) }.joinToString(separator = "") } - public static String str(Object o) { - return o == null ? null : o.toString(); + @JvmStatic fun str(o: Any?): String? { + return o?.toString() + } + + @JvmName("strNonNull") + @JvmStatic fun str(o: Any): String { + return o.toString() } } diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/TTL.java b/core/src/main/java/ch/dissem/bitmessage/utils/TTL.java deleted file mode 100644 index c4fab9c..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/utils/TTL.java +++ /dev/null @@ -1,44 +0,0 @@ -package ch.dissem.bitmessage.utils; - -import static ch.dissem.bitmessage.utils.UnixTime.DAY; - -/** - * Stores times to live in seconds for different object types. Usually this shouldn't be messed with, but for tests - * it might be a good idea to reduce it to a minimum, and on mobile clients you might want to optimize it as well. - * - * @author Christian Basler - */ -public class TTL { - private static long msg = 2 * DAY; - private static long getpubkey = 2 * DAY; - private static long pubkey = 28 * DAY; - - public static long msg() { - return msg; - } - - public static void msg(long msg) { - TTL.msg = validate(msg); - } - - public static long getpubkey() { - return getpubkey; - } - - public static void getpubkey(long getpubkey) { - TTL.getpubkey = validate(getpubkey); - } - - public static long pubkey() { - return pubkey; - } - - public static void pubkey(long pubkey) { - TTL.pubkey = validate(pubkey); - } - - private static long validate(long ttl) { - if (ttl < 0 || ttl > 28 * DAY) throw new IllegalArgumentException("TTL must be between 0 seconds and 28 days"); - return ttl; - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/TTL.kt b/core/src/main/java/ch/dissem/bitmessage/utils/TTL.kt new file mode 100644 index 0000000..6fe883a --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/utils/TTL.kt @@ -0,0 +1,49 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.utils + +/** + * Stores times to live in seconds for different object types. Usually this shouldn't be messed with, but for tests + * it might be a good idea to reduce it to a minimum, and on mobile clients you might want to optimize it as well. + + * @author Christian Basler + */ +object TTL { + @JvmStatic var msg = 2 * UnixTime.DAY + @JvmName("msg") get + @JvmName("msg") set(msg) { + field = validate(msg) + } + + @JvmStatic var getpubkey = 2 * UnixTime.DAY + @JvmName("getpubkey") get + @JvmName("getpubkey") set(getpubkey) { + field = validate(getpubkey) + } + + @JvmStatic var pubkey = 28 * UnixTime.DAY + @JvmName("pubkey") get + @JvmName("pubkey") set(pubkey) { + field = validate(pubkey) + } + + private fun validate(ttl: Long): Long { + if (ttl < 0 || ttl > 28 * UnixTime.DAY) + throw IllegalArgumentException("TTL must be between 0 seconds and 28 days") + return ttl + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/ThreadFactoryBuilder.java b/core/src/main/java/ch/dissem/bitmessage/utils/ThreadFactoryBuilder.java deleted file mode 100644 index 36cf5b9..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/utils/ThreadFactoryBuilder.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2016 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.utils; - -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.atomic.AtomicInteger; - -public class ThreadFactoryBuilder { - private final String namePrefix; - private int prio = Thread.NORM_PRIORITY; - private boolean daemon = false; - - private ThreadFactoryBuilder(String pool) { - this.namePrefix = pool + "-thread-"; - } - - - public static ThreadFactoryBuilder pool(String name) { - return new ThreadFactoryBuilder(name); - } - - public ThreadFactoryBuilder lowPrio() { - prio = Thread.MIN_PRIORITY; - return this; - } - - public ThreadFactoryBuilder daemon() { - daemon = true; - return this; - } - - public ThreadFactory build() { - SecurityManager s = System.getSecurityManager(); - final ThreadGroup group = (s != null) ? s.getThreadGroup() : - Thread.currentThread().getThreadGroup(); - - return new ThreadFactory() { - private final AtomicInteger threadNumber = new AtomicInteger(1); - - @Override - public Thread newThread(Runnable r) { - Thread t = new Thread(group, r, - namePrefix + threadNumber.getAndIncrement(), - 0); - t.setPriority(prio); - t.setDaemon(daemon); - return t; - } - }; - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/ThreadFactoryBuilder.kt b/core/src/main/java/ch/dissem/bitmessage/utils/ThreadFactoryBuilder.kt new file mode 100644 index 0000000..98bfa55 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/utils/ThreadFactoryBuilder.kt @@ -0,0 +1,63 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.utils + +import java.util.concurrent.ThreadFactory +import java.util.concurrent.atomic.AtomicInteger + +class ThreadFactoryBuilder private constructor(pool: String) { + private val namePrefix: String = pool + "-thread-" + private var prio = Thread.NORM_PRIORITY + private var daemon = false + + fun lowPrio(): ThreadFactoryBuilder { + prio = Thread.MIN_PRIORITY + return this + } + + fun daemon(): ThreadFactoryBuilder { + daemon = true + return this + } + + fun build(): ThreadFactory { + val s = System.getSecurityManager() + val group = if (s != null) + s.threadGroup + else + Thread.currentThread().threadGroup + + return object : ThreadFactory { + private val threadNumber = AtomicInteger(1) + + override fun newThread(r: Runnable): Thread { + val t = Thread(group, r, + namePrefix + threadNumber.getAndIncrement(), + 0) + t.priority = prio + t.isDaemon = daemon + return t + } + } + } + + companion object { + @JvmStatic fun pool(name: String): ThreadFactoryBuilder { + return ThreadFactoryBuilder(name) + } + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/UnixTime.java b/core/src/main/java/ch/dissem/bitmessage/utils/UnixTime.java deleted file mode 100644 index 0d0d991..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/utils/UnixTime.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.utils; - -/** - * A simple utility class that simplifies using the second based time used in Bitmessage. - */ -public class UnixTime { - /** - * Length of a minute in seconds, intended for use with {@link #now(long)}. - */ - public static final int MINUTE = 60; - /** - * Length of an hour in seconds, intended for use with {@link #now(long)}. - */ - public static final long HOUR = 60 * MINUTE; - /** - * Length of a day in seconds, intended for use with {@link #now(long)}. - */ - public static final long DAY = 24 * HOUR; - - /** - * @return the time in second based Unix time ({@link System#currentTimeMillis()}/1000) - */ - public static long now() { - return System.currentTimeMillis() / 1000; - } - - /** - * Same as {@link #now()} + shiftSeconds, but might be more readable. - * - * @param shiftSeconds number of seconds from now we're interested in - * @return the Unix time in shiftSeconds seconds / shiftSeconds seconds ago - */ - public static long now(long shiftSeconds) { - return (System.currentTimeMillis() / 1000) + shiftSeconds; - } -} diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/UnixTime.kt b/core/src/main/java/ch/dissem/bitmessage/utils/UnixTime.kt new file mode 100644 index 0000000..d21bc2e --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/utils/UnixTime.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.utils + +/** + * A simple utility class that simplifies using the second based time used in Bitmessage. + */ +object UnixTime { + /** + * Length of a minute in seconds, intended for use with [.now]. + */ + @JvmField val MINUTE = 60L + /** + * Length of an hour in seconds, intended for use with [.now]. + */ + @JvmField val HOUR = 60L * MINUTE + /** + * Length of a day in seconds, intended for use with [.now]. + */ + @JvmField val DAY = 24L * HOUR + + /** + * @return the time in second based Unix time ([System.currentTimeMillis]/1000) + */ + @JvmStatic val now: Long + @JvmName("now") get() { + return System.currentTimeMillis() / 1000L + } +} diff --git a/core/src/test/java/ch/dissem/bitmessage/BitmessageContextTest.java b/core/src/test/java/ch/dissem/bitmessage/BitmessageContextTest.java deleted file mode 100644 index 0702866..0000000 --- a/core/src/test/java/ch/dissem/bitmessage/BitmessageContextTest.java +++ /dev/null @@ -1,321 +0,0 @@ -/* - * Copyright 2016 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage; - -import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography; -import ch.dissem.bitmessage.entity.BitmessageAddress; -import ch.dissem.bitmessage.entity.ObjectMessage; -import ch.dissem.bitmessage.entity.Plaintext; -import ch.dissem.bitmessage.entity.Plaintext.Type; -import ch.dissem.bitmessage.entity.payload.ObjectType; -import ch.dissem.bitmessage.entity.payload.Pubkey; -import ch.dissem.bitmessage.entity.valueobject.InventoryVector; -import ch.dissem.bitmessage.ports.*; -import ch.dissem.bitmessage.testutils.TestInventory; -import ch.dissem.bitmessage.utils.MessageMatchers; -import ch.dissem.bitmessage.utils.Singleton; -import ch.dissem.bitmessage.utils.TTL; -import ch.dissem.bitmessage.utils.TestUtils; -import org.junit.Before; -import org.junit.Test; - -import java.util.*; - -import static ch.dissem.bitmessage.entity.payload.ObjectType.*; -import static ch.dissem.bitmessage.utils.MessageMatchers.object; -import static ch.dissem.bitmessage.utils.Singleton.cryptography; -import static ch.dissem.bitmessage.utils.UnixTime.MINUTE; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.notNullValue; -import static org.junit.Assert.*; -import static org.mockito.Mockito.*; - -/** - * @author Christian Basler - */ -public class BitmessageContextTest { - private BitmessageContext ctx; - private BitmessageContext.Listener listener; - private TestInventory testInventory; - - @Before - public void setUp() throws Exception { - Singleton.initialize(null); - listener = mock(BitmessageContext.Listener.class); - Singleton.initialize(new BouncyCryptography()); - testInventory = new TestInventory(); - ctx = new BitmessageContext.Builder() - .addressRepo(mock(AddressRepository.class)) - .cryptography(cryptography()) - .inventory(spy(testInventory)) - .listener(listener) - .messageRepo(mock(MessageRepository.class)) - .networkHandler(mock(NetworkHandler.class)) - .nodeRegistry(mock(NodeRegistry.class)) - .labeler(spy(new DefaultLabeler())) - .powRepo(spy(new ProofOfWorkRepository() { - Map<InventoryVector, Item> items = new HashMap<>(); - - @Override - public Item getItem(byte[] initialHash) { - return items.get(InventoryVector.fromHash(initialHash)); - } - - @Override - public List<byte[]> getItems() { - List<byte[]> result = new LinkedList<>(); - for (InventoryVector iv : items.keySet()) { - result.add(iv.getHash()); - } - return result; - } - - @Override - public void putObject(Item item) { - items.put(InventoryVector.fromHash(cryptography().getInitialHash(item.object)), item); - } - - @Override - public void putObject(ObjectMessage object, long nonceTrialsPerByte, long extraBytes) { - items.put(InventoryVector.fromHash(cryptography().getInitialHash(object)), new Item(object, nonceTrialsPerByte, extraBytes)); - } - - @Override - public void removeObject(byte[] initialHash) { - items.remove(initialHash); - } - })) - .proofOfWorkEngine(spy(new ProofOfWorkEngine() { - @Override - public void calculateNonce(byte[] initialHash, byte[] target, Callback callback) { - callback.onNonceCalculated(initialHash, new byte[8]); - } - })) - .build(); - TTL.msg(2 * MINUTE); - } - - @Test - public void ensureContactIsSavedAndPubkeyRequested() { - BitmessageAddress contact = new BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT"); - when(ctx.addresses().getAddress(contact.getAddress())).thenReturn(contact); - ctx.addContact(contact); - - verify(ctx.addresses(), timeout(1000).atLeastOnce()).save(contact); - verify(ctx.internals().getProofOfWorkEngine(), timeout(1000)) - .calculateNonce(any(byte[].class), any(byte[].class), any(ProofOfWorkEngine.Callback.class)); - } - - @Test - public void ensurePubkeyIsNotRequestedIfItExists() throws Exception { - ObjectMessage object = TestUtils.loadObjectMessage(2, "V2Pubkey.payload"); - Pubkey pubkey = (Pubkey) object.getPayload(); - BitmessageAddress contact = new BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT"); - contact.setPubkey(pubkey); - - ctx.addContact(contact); - - verify(ctx.addresses(), times(1)).save(contact); - verify(ctx.internals().getProofOfWorkEngine(), never()) - .calculateNonce(any(byte[].class), any(byte[].class), any(ProofOfWorkEngine.Callback.class)); - } - - @Test - public void ensureV2PubkeyIsNotRequestedIfItExistsInInventory() throws Exception { - testInventory.init( - "V1Msg.payload", - "V2GetPubkey.payload", - "V2Pubkey.payload", - "V3GetPubkey.payload", - "V3Pubkey.payload", - "V4Broadcast.payload", - "V4GetPubkey.payload", - "V4Pubkey.payload", - "V5Broadcast.payload" - ); - BitmessageAddress contact = new BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT"); - - when(ctx.addresses().getAddress(contact.getAddress())).thenReturn(contact); - - ctx.addContact(contact); - - verify(ctx.addresses(), atLeastOnce()).save(contact); - verify(ctx.internals().getProofOfWorkEngine(), never()) - .calculateNonce(any(byte[].class), any(byte[].class), any(ProofOfWorkEngine.Callback.class)); - } - - @Test - public void ensureV4PubkeyIsNotRequestedIfItExistsInInventory() throws Exception { - testInventory.init( - "V1Msg.payload", - "V2GetPubkey.payload", - "V2Pubkey.payload", - "V3GetPubkey.payload", - "V3Pubkey.payload", - "V4Broadcast.payload", - "V4GetPubkey.payload", - "V4Pubkey.payload", - "V5Broadcast.payload" - ); - BitmessageAddress contact = new BitmessageAddress("BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h"); - final BitmessageAddress stored = new BitmessageAddress(contact.getAddress()); - stored.setAlias("Test"); - when(ctx.addresses().getAddress(contact.getAddress())).thenReturn(stored); - - ctx.addContact(contact); - - verify(ctx.addresses(), atLeastOnce()).save(any(BitmessageAddress.class)); - verify(ctx.internals().getProofOfWorkEngine(), never()) - .calculateNonce(any(byte[].class), any(byte[].class), any(ProofOfWorkEngine.Callback.class)); - } - - @Test - public void ensureSubscriptionIsAddedAndExistingBroadcastsRetrieved() throws Exception { - BitmessageAddress address = new BitmessageAddress("BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ"); - - testInventory.init( - "V4Broadcast.payload", - "V5Broadcast.payload" - ); - - when(ctx.addresses().getSubscriptions(anyLong())).thenReturn(Collections.singletonList(address)); - ctx.addSubscribtion(address); - - verify(ctx.addresses(), atLeastOnce()).save(address); - assertThat(address.isSubscribed(), is(true)); - verify(ctx.internals().getInventory()).getObjects(eq(address.getStream()), anyLong(), any(ObjectType.class)); - verify(listener).receive(any(Plaintext.class)); - } - - @Test - public void ensureIdentityIsCreated() { - assertThat(ctx.createIdentity(false), notNullValue()); - } - - @Test - public void ensureMessageIsSent() throws Exception { - ctx.send(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"), TestUtils.loadContact(), - "Subject", "Message"); - assertEquals(2, ctx.internals().getProofOfWorkRepository().getItems().size()); - verify(ctx.internals().getProofOfWorkRepository(), timeout(10000).atLeastOnce()) - .putObject(object(MSG), eq(1000L), eq(1000L)); - verify(ctx.messages(), timeout(10000).atLeastOnce()).save(MessageMatchers.plaintext(Type.MSG)); - } - - @Test - public void ensurePubkeyIsRequestedIfItIsMissing() throws Exception { - ctx.send(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"), - new BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT"), - "Subject", "Message"); - verify(ctx.internals().getProofOfWorkRepository(), timeout(10000).atLeastOnce()) - .putObject(object(GET_PUBKEY), eq(1000L), eq(1000L)); - verify(ctx.messages(), timeout(10000).atLeastOnce()).save(MessageMatchers.plaintext(Type.MSG)); - } - - @Test(expected = IllegalArgumentException.class) - public void ensureSenderMustBeIdentity() { - ctx.send(new BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT"), - new BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT"), - "Subject", "Message"); - } - - @Test - public void ensureBroadcastIsSent() throws Exception { - ctx.broadcast(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"), - "Subject", "Message"); - verify(ctx.internals().getProofOfWorkRepository(), timeout(10000).atLeastOnce()) - .putObject(object(BROADCAST), eq(1000L), eq(1000L)); - verify(ctx.internals().getProofOfWorkEngine()) - .calculateNonce(any(byte[].class), any(byte[].class), any(ProofOfWorkEngine.Callback.class)); - verify(ctx.messages(), timeout(10000).atLeastOnce()) - .save(MessageMatchers.plaintext(Type.BROADCAST)); - } - - @Test(expected = IllegalArgumentException.class) - public void ensureSenderWithoutPrivateKeyThrowsException() { - Plaintext msg = new Plaintext.Builder(Type.BROADCAST) - .from(new BitmessageAddress("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8")) - .message("Subject", "Message") - .build(); - ctx.send(msg); - } - - @Test - public void ensureChanIsJoined() { - String chanAddress = "BM-2cW67GEKkHGonXKZLCzouLLxnLym3azS8r"; - BitmessageAddress chan = ctx.joinChan("general", chanAddress); - assertNotNull(chan); - assertEquals(chan.getAddress(), chanAddress); - assertTrue(chan.isChan()); - } - - @Test - public void ensureDeterministicAddressesAreCreated() { - final int expected_size = 8; - List<BitmessageAddress> addresses = ctx.createDeterministicAddresses("test", expected_size, 4, 1, false); - assertEquals(expected_size, addresses.size()); - Set<String> expected = new HashSet<>(expected_size); - expected.add("BM-2cWFkyuXXFw6d393RGnin2RpSXj8wxtt6F"); - expected.add("BM-2cX8TF9vuQZEWvT7UrEeq1HN9dgiSUPLEN"); - expected.add("BM-2cUzX8f9CKUU7L8NeB8GExZvf54PrcXq1S"); - expected.add("BM-2cU7MAoQd7KE8SPF7AKFPpoEZKjk86KRqE"); - expected.add("BM-2cVm8ByVBacc2DVhdTNs6rmy5ZQK6DUsrt"); - expected.add("BM-2cW2af1vB6kWon2WkygDHqGwfcpfAFm2Jk"); - expected.add("BM-2cWdWD7UtUN4gWChgNX9pvyvNPjUZvU8BT"); - expected.add("BM-2cXkYgYcUrv4fGxSHzyEScW955Cc8sDteo"); - for (BitmessageAddress a : addresses) { - assertTrue(expected.contains(a.getAddress())); - expected.remove(a.getAddress()); - } - } - - @Test - public void ensureShortDeterministicAddressesAreCreated() { - final int expected_size = 1; - List<BitmessageAddress> addresses = ctx.createDeterministicAddresses("test", expected_size, 4, 1, true); - assertEquals(expected_size, addresses.size()); - Set<String> expected = new HashSet<>(expected_size); - expected.add("BM-NBGyBAEp6VnBkFWKpzUSgxuTqVdWPi78"); - for (BitmessageAddress a : addresses) { - assertTrue(expected.contains(a.getAddress())); - expected.remove(a.getAddress()); - } - } - - @Test - public void ensureChanIsCreated() { - BitmessageAddress chan = ctx.createChan("test"); - assertNotNull(chan); - assertEquals(chan.getVersion(), Pubkey.LATEST_VERSION); - assertTrue(chan.isChan()); - } - - @Test - public void ensureUnacknowledgedMessageIsResent() throws Exception { - Plaintext plaintext = new Plaintext.Builder(Type.MSG) - .ttl(1) - .message("subject", "message") - .from(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8")) - .to(TestUtils.loadContact()) - .build(); - assertTrue(plaintext.getTo().has(Pubkey.Feature.DOES_ACK)); - when(ctx.messages().findMessagesToResend()).thenReturn(Collections.singletonList(plaintext)); - when(ctx.messages().getMessage(any(byte[].class))).thenReturn(plaintext); - ctx.resendUnacknowledgedMessages(); - verify(ctx.labeler(), timeout(1000).times(1)).markAsSent(eq(plaintext)); - } -} diff --git a/core/src/test/java/ch/dissem/bitmessage/BitmessageContextTest.kt b/core/src/test/java/ch/dissem/bitmessage/BitmessageContextTest.kt new file mode 100644 index 0000000..024b66a --- /dev/null +++ b/core/src/test/java/ch/dissem/bitmessage/BitmessageContextTest.kt @@ -0,0 +1,319 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage + +import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography +import ch.dissem.bitmessage.entity.BitmessageAddress +import ch.dissem.bitmessage.entity.ObjectMessage +import ch.dissem.bitmessage.entity.Plaintext +import ch.dissem.bitmessage.entity.Plaintext.Type +import ch.dissem.bitmessage.entity.payload.ObjectType +import ch.dissem.bitmessage.entity.payload.Pubkey +import ch.dissem.bitmessage.entity.valueobject.InventoryVector +import ch.dissem.bitmessage.ports.DefaultLabeler +import ch.dissem.bitmessage.ports.ProofOfWorkEngine +import ch.dissem.bitmessage.ports.ProofOfWorkRepository +import ch.dissem.bitmessage.testutils.TestInventory +import ch.dissem.bitmessage.utils.Singleton +import ch.dissem.bitmessage.utils.Singleton.cryptography +import ch.dissem.bitmessage.utils.TTL +import ch.dissem.bitmessage.utils.TestUtils +import ch.dissem.bitmessage.utils.UnixTime.MINUTE +import com.nhaarman.mockito_kotlin.* +import org.hamcrest.CoreMatchers.`is` +import org.hamcrest.CoreMatchers.notNullValue +import org.junit.Assert.* +import org.junit.Before +import org.junit.Test +import java.util.* + +/** + * @author Christian Basler + */ +class BitmessageContextTest { + private lateinit var ctx: BitmessageContext + private var listener: BitmessageContext.Listener = mock() + private val inventory = spy(TestInventory()) + private val testPowRepo = spy(object : ProofOfWorkRepository { + internal var items: MutableMap<InventoryVector, ProofOfWorkRepository.Item> = HashMap() + internal var added = 0 + internal var removed = 0 + + override fun getItem(initialHash: ByteArray): ProofOfWorkRepository.Item { + return items[InventoryVector(initialHash)]!! + } + + override fun getItems(): List<ByteArray> { + val result = LinkedList<ByteArray>() + for ((hash) in items.keys) { + result.add(hash) + } + return result + } + + override fun putObject(item: ProofOfWorkRepository.Item) { + items.put(InventoryVector(cryptography().getInitialHash(item.`object`)), item) + added++ + } + + override fun putObject(`object`: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long) { + items.put(InventoryVector(cryptography().getInitialHash(`object`)), ProofOfWorkRepository.Item(`object`, nonceTrialsPerByte, extraBytes)) + added++ + } + + override fun removeObject(initialHash: ByteArray) { + if (items.remove(InventoryVector(initialHash)) != null) { + removed++ + } + } + + fun reset() { + items.clear() + added = 0 + removed = 0 + } + }) + + @Before + fun setUp() { + Singleton.initialize(BouncyCryptography()) + ctx = BitmessageContext.Builder() + .addressRepo(mock()) + .cryptography(cryptography()) + .inventory(inventory) + .listener(listener) + .messageRepo(mock()) + .networkHandler(mock()) + .nodeRegistry(mock()) + .labeler(spy(DefaultLabeler())) + .powRepo(testPowRepo) + .proofOfWorkEngine(spy(object : ProofOfWorkEngine { + override fun calculateNonce(initialHash: ByteArray, target: ByteArray, callback: ProofOfWorkEngine.Callback) { + callback.onNonceCalculated(initialHash, ByteArray(8)) + } + })) + .build() + TTL.msg = 2 * MINUTE + testPowRepo.reset() + } + + @Test + fun `ensure contact is saved and pubkey requested`() { + val contact = BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT") + whenever(ctx.addresses().getAddress(contact.address)).thenReturn(contact) + + ctx.addContact(contact) + + verify(ctx.addresses(), timeout(1000).atLeastOnce()).save(contact) + verify(ctx.internals().proofOfWorkEngine, timeout(1000)).calculateNonce(any(), any(), any()) + } + + @Test + fun `ensure pubkey is not requested if it exists`() { + val (_, _, payload) = TestUtils.loadObjectMessage(2, "V2Pubkey.payload") + val pubkey = payload as Pubkey + val contact = BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT") + contact.pubkey = pubkey + + ctx.addContact(contact) + + verify(ctx.addresses(), times(1)).save(contact) + verify(ctx.internals().proofOfWorkEngine, never()).calculateNonce(any(), any(), any()) + } + + @Test + fun `ensure V2Pubkey is not requested if it exists in inventory`() { + inventory.init( + "V1Msg.payload", + "V2GetPubkey.payload", + "V2Pubkey.payload", + "V3GetPubkey.payload", + "V3Pubkey.payload", + "V4Broadcast.payload", + "V4GetPubkey.payload", + "V4Pubkey.payload", + "V5Broadcast.payload" + ) + val contact = BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT") + + whenever(ctx.addresses().getAddress(contact.address)).thenReturn(contact) + + ctx.addContact(contact) + + verify(ctx.addresses(), atLeastOnce()).save(contact) + verify(ctx.internals().proofOfWorkEngine, never()).calculateNonce(any(), any(), any()) + } + + @Test + fun `ensure V4Pubkey is not requested if it exists in inventory`() { + inventory.init( + "V1Msg.payload", + "V2GetPubkey.payload", + "V2Pubkey.payload", + "V3GetPubkey.payload", + "V3Pubkey.payload", + "V4Broadcast.payload", + "V4GetPubkey.payload", + "V4Pubkey.payload", + "V5Broadcast.payload" + ) + val contact = BitmessageAddress("BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h") + val stored = BitmessageAddress(contact.address) + stored.alias = "Test" + whenever(ctx.addresses().getAddress(contact.address)).thenReturn(stored) + + ctx.addContact(contact) + + verify(ctx.addresses(), atLeastOnce()).save(any()) + verify(ctx.internals().proofOfWorkEngine, never()).calculateNonce(any(), any(), any()) + } + + @Test + fun `ensure subscription is added and existing broadcasts retrieved`() { + val address = BitmessageAddress("BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ") + + inventory.init( + "V4Broadcast.payload", + "V5Broadcast.payload" + ) + + whenever(ctx.addresses().getSubscriptions(any())).thenReturn(listOf(address)) + ctx.addSubscribtion(address) + + verify(ctx.addresses(), atLeastOnce()).save(address) + assertThat(address.isSubscribed, `is`(true)) + verify(ctx.internals().inventory).getObjects(eq(address.stream), any(), any()) + verify(listener).receive(any()) + } + + @Test + fun `ensure identity is created`() { + assertThat(ctx.createIdentity(false), notNullValue()) + } + + @Test + fun `ensure message is sent`() { + ctx.send(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"), TestUtils.loadContact(), + "Subject", "Message") + assertEquals(2, testPowRepo.added) + verify(ctx.internals().proofOfWorkRepository, timeout(10000).atLeastOnce()) + .putObject(argThat { payload.type == ObjectType.MSG }, eq(1000L), eq(1000L)) + verify(ctx.messages(), timeout(10000).atLeastOnce()).save(argThat { type == Type.MSG }) + } + + @Test + fun `ensure pubkey is requested if it is missing`() { + ctx.send(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"), + BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT"), + "Subject", "Message") + verify(testPowRepo, timeout(10000).atLeastOnce()) + .putObject(argThat { payload.type == ObjectType.GET_PUBKEY }, eq(1000L), eq(1000L)) + verify(ctx.messages(), timeout(10000).atLeastOnce()).save(argThat { type == Type.MSG }) + } + + @Test(expected = IllegalArgumentException::class) + fun `ensure sender must be identity`() { + ctx.send(BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT"), + BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT"), + "Subject", "Message") + } + + @Test + fun `ensure broadcast is sent`() { + ctx.broadcast(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"), + "Subject", "Message") + verify(ctx.internals().proofOfWorkRepository, timeout(10000).atLeastOnce()) + .putObject(argThat { payload.type == ObjectType.BROADCAST }, eq(1000L), eq(1000L)) + verify(ctx.internals().proofOfWorkEngine) + .calculateNonce(any(), any(), any()) + verify(ctx.messages(), timeout(10000).atLeastOnce()).save(argThat { type == Type.BROADCAST }) + } + + @Test(expected = IllegalArgumentException::class) + fun `ensure sender without private key throws exception`() { + val msg = Plaintext.Builder(Type.BROADCAST) + .from(BitmessageAddress("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8")) + .message("Subject", "Message") + .build() + ctx.send(msg) + } + + @Test + fun `ensure chan is joined`() { + val chanAddress = "BM-2cW67GEKkHGonXKZLCzouLLxnLym3azS8r" + val chan = ctx.joinChan("general", chanAddress) + assertNotNull(chan) + assertEquals(chan.address, chanAddress) + assertTrue(chan.isChan) + } + + @Test + fun `ensure deterministic addresses are created`() { + val expected_size = 8 + val addresses = ctx.createDeterministicAddresses("test", expected_size, 4, 1, false) + assertEquals(expected_size.toLong(), addresses.size.toLong()) + val expected = HashSet<String>(expected_size) + expected.add("BM-2cWFkyuXXFw6d393RGnin2RpSXj8wxtt6F") + expected.add("BM-2cX8TF9vuQZEWvT7UrEeq1HN9dgiSUPLEN") + expected.add("BM-2cUzX8f9CKUU7L8NeB8GExZvf54PrcXq1S") + expected.add("BM-2cU7MAoQd7KE8SPF7AKFPpoEZKjk86KRqE") + expected.add("BM-2cVm8ByVBacc2DVhdTNs6rmy5ZQK6DUsrt") + expected.add("BM-2cW2af1vB6kWon2WkygDHqGwfcpfAFm2Jk") + expected.add("BM-2cWdWD7UtUN4gWChgNX9pvyvNPjUZvU8BT") + expected.add("BM-2cXkYgYcUrv4fGxSHzyEScW955Cc8sDteo") + for (a in addresses) { + assertTrue(expected.contains(a.address)) + expected.remove(a.address) + } + } + + @Test + fun `ensure short deterministic addresses are created`() { + val expected_size = 1 + val addresses = ctx.createDeterministicAddresses("test", expected_size, 4, 1, true) + assertEquals(expected_size.toLong(), addresses.size.toLong()) + val expected = HashSet<String>(expected_size) + expected.add("BM-NBGyBAEp6VnBkFWKpzUSgxuTqVdWPi78") + for (a in addresses) { + assertTrue(expected.contains(a.address)) + expected.remove(a.address) + } + } + + @Test + fun `ensure chan is created`() { + val chan = ctx.createChan("test") + assertNotNull(chan) + assertEquals(chan.version, Pubkey.LATEST_VERSION) + assertTrue(chan.isChan) + } + + @Test + fun `ensure unacknowledged message is resent`() { + val plaintext = Plaintext.Builder(Type.MSG) + .ttl(1) + .message("subject", "message") + .from(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8")) + .to(TestUtils.loadContact()) + .build() + assertTrue(plaintext.to!!.has(Pubkey.Feature.DOES_ACK)) + whenever(ctx.messages().findMessagesToResend()).thenReturn(listOf(plaintext)) + whenever(ctx.messages().getMessage(any<ByteArray>())).thenReturn(plaintext) + ctx.resendUnacknowledgedMessages() + verify(ctx.labeler(), timeout(1000).times(1)).markAsSent(eq(plaintext)) + } +} diff --git a/core/src/test/java/ch/dissem/bitmessage/DecryptionTest.java b/core/src/test/java/ch/dissem/bitmessage/DecryptionTest.java deleted file mode 100644 index 21f9506..0000000 --- a/core/src/test/java/ch/dissem/bitmessage/DecryptionTest.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage; - -import ch.dissem.bitmessage.entity.BitmessageAddress; -import ch.dissem.bitmessage.entity.ObjectMessage; -import ch.dissem.bitmessage.entity.payload.V4Broadcast; -import ch.dissem.bitmessage.entity.payload.V5Broadcast; -import ch.dissem.bitmessage.exception.DecryptionFailedException; -import ch.dissem.bitmessage.utils.TestBase; -import ch.dissem.bitmessage.utils.TestUtils; -import org.junit.Test; - -import java.io.IOException; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -public class DecryptionTest extends TestBase { - @Test - public void ensureV4BroadcastIsDecryptedCorrectly() throws IOException, DecryptionFailedException { - BitmessageAddress address = new BitmessageAddress("BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ"); - TestUtils.loadPubkey(address); - ObjectMessage objectMessage = TestUtils.loadObjectMessage(5, "V4Broadcast.payload"); - V4Broadcast broadcast = (V4Broadcast) objectMessage.getPayload(); - broadcast.decrypt(address); - assertEquals("Test-Broadcast", broadcast.getPlaintext().getSubject()); - assertTrue(objectMessage.isSignatureValid(address.getPubkey())); - } - - @Test - public void ensureV5BroadcastIsDecryptedCorrectly() throws IOException, DecryptionFailedException { - BitmessageAddress address = new BitmessageAddress("BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h"); - TestUtils.loadPubkey(address); - ObjectMessage objectMessage = TestUtils.loadObjectMessage(5, "V5Broadcast.payload"); - V5Broadcast broadcast = (V5Broadcast) objectMessage.getPayload(); - broadcast.decrypt(address); - assertEquals("Test-Broadcast", broadcast.getPlaintext().getSubject()); - assertTrue(objectMessage.isSignatureValid(address.getPubkey())); - } -} diff --git a/core/src/test/java/ch/dissem/bitmessage/DecryptionTest.kt b/core/src/test/java/ch/dissem/bitmessage/DecryptionTest.kt new file mode 100644 index 0000000..dd9bc95 --- /dev/null +++ b/core/src/test/java/ch/dissem/bitmessage/DecryptionTest.kt @@ -0,0 +1,50 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage + +import ch.dissem.bitmessage.entity.BitmessageAddress +import ch.dissem.bitmessage.entity.payload.V4Broadcast +import ch.dissem.bitmessage.entity.payload.V5Broadcast +import ch.dissem.bitmessage.utils.TestBase +import ch.dissem.bitmessage.utils.TestUtils +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Test + +class DecryptionTest : TestBase() { + @Test + fun `ensure V4Broadcast is decrypted correctly`() { + val address = BitmessageAddress("BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ") + TestUtils.loadPubkey(address) + val objectMessage = TestUtils.loadObjectMessage(5, "V4Broadcast.payload") + val broadcast = objectMessage.payload as V4Broadcast + broadcast.decrypt(address) + assertEquals("Test-Broadcast", broadcast.plaintext?.subject) + assertTrue(objectMessage.isSignatureValid(address.pubkey!!)) + } + + @Test + fun `ensure V5Broadcast is decrypted correctly`() { + val address = BitmessageAddress("BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h") + TestUtils.loadPubkey(address) + val objectMessage = TestUtils.loadObjectMessage(5, "V5Broadcast.payload") + val broadcast = objectMessage.payload as V5Broadcast + broadcast.decrypt(address) + assertEquals("Test-Broadcast", broadcast.plaintext?.subject) + assertTrue(objectMessage.isSignatureValid(address.pubkey!!)) + } +} diff --git a/core/src/test/java/ch/dissem/bitmessage/DefaultMessageListenerTest.java b/core/src/test/java/ch/dissem/bitmessage/DefaultMessageListenerTest.java deleted file mode 100644 index 32a20e4..0000000 --- a/core/src/test/java/ch/dissem/bitmessage/DefaultMessageListenerTest.java +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright 2016 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage; - -import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography; -import ch.dissem.bitmessage.entity.BitmessageAddress; -import ch.dissem.bitmessage.entity.ObjectMessage; -import ch.dissem.bitmessage.entity.Plaintext; -import ch.dissem.bitmessage.entity.payload.Broadcast; -import ch.dissem.bitmessage.entity.payload.GetPubkey; -import ch.dissem.bitmessage.entity.payload.Msg; -import ch.dissem.bitmessage.factory.Factory; -import ch.dissem.bitmessage.ports.*; -import ch.dissem.bitmessage.utils.Singleton; -import ch.dissem.bitmessage.utils.TestBase; -import ch.dissem.bitmessage.utils.TestUtils; -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.util.Collections; - -import static ch.dissem.bitmessage.entity.Plaintext.Status.PUBKEY_REQUESTED; -import static ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST; -import static ch.dissem.bitmessage.entity.Plaintext.Type.MSG; -import static ch.dissem.bitmessage.utils.MessageMatchers.plaintext; -import static org.mockito.Mockito.*; - -/** - * @author Christian Basler - */ -public class DefaultMessageListenerTest extends TestBase { - @Mock - private AddressRepository addressRepo; - @Mock - private MessageRepository messageRepo; - @Mock - private Inventory inventory; - @Mock - private NetworkHandler networkHandler; - - private InternalContext ctx; - private DefaultMessageListener listener; - - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - ctx = mock(InternalContext.class); - Singleton.initialize(new BouncyCryptography()); - when(ctx.getAddressRepository()).thenReturn(addressRepo); - when(ctx.getMessageRepository()).thenReturn(messageRepo); - when(ctx.getInventory()).thenReturn(inventory); - when(ctx.getNetworkHandler()).thenReturn(networkHandler); - when(ctx.getLabeler()).thenReturn(mock(Labeler.class)); - - listener = new DefaultMessageListener(mock(Labeler.class), mock(BitmessageContext.Listener.class)); - when(ctx.getNetworkListener()).thenReturn(listener); - listener.setContext(ctx); - } - - @Test - public void ensurePubkeyIsSentOnRequest() throws Exception { - BitmessageAddress identity = TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"); - when(addressRepo.findIdentity(any(byte[].class))) - .thenReturn(identity); - listener.receive(new ObjectMessage.Builder() - .stream(2) - .payload(new GetPubkey(new BitmessageAddress("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"))) - .build()); - verify(ctx).sendPubkey(eq(identity), eq(2L)); - } - - @Test - public void ensureIncomingPubkeyIsAddedToContact() throws Exception { - BitmessageAddress identity = TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"); - BitmessageAddress contact = new BitmessageAddress(identity.getAddress()); - when(addressRepo.findContact(any(byte[].class))) - .thenReturn(contact); - when(messageRepo.findMessages(eq(PUBKEY_REQUESTED), eq(contact))) - .thenReturn(Collections.singletonList( - new Plaintext.Builder(MSG).from(identity).to(contact).message("S", "T").build() - )); - - ObjectMessage objectMessage = new ObjectMessage.Builder() - .stream(2) - .payload(identity.getPubkey()) - .build(); - objectMessage.sign(identity.getPrivateKey()); - objectMessage.encrypt(Singleton.cryptography().createPublicKey(identity.getPublicDecryptionKey())); - listener.receive(objectMessage); - - verify(addressRepo).save(any(BitmessageAddress.class)); - } - - @Test - public void ensureIncomingMessageIsSaved() throws Exception { - BitmessageAddress identity = TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"); - BitmessageAddress contact = new BitmessageAddress(identity.getAddress()); - contact.setPubkey(identity.getPubkey()); - - when(addressRepo.getIdentities()).thenReturn(Collections.singletonList(identity)); - - ObjectMessage objectMessage = new ObjectMessage.Builder() - .stream(2) - .payload(new Msg(new Plaintext.Builder(MSG) - .from(identity) - .to(contact) - .message("S", "T") - .build())) - .nonce(new byte[8]) - .build(); - objectMessage.sign(identity.getPrivateKey()); - objectMessage.encrypt(identity.getPubkey()); - - listener.receive(objectMessage); - - verify(messageRepo, atLeastOnce()).save(plaintext(MSG)); - } - - @Test - public void ensureIncomingBroadcastIsSaved() throws Exception { - BitmessageAddress identity = TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"); - - when(addressRepo.getSubscriptions(anyLong())).thenReturn(Collections.singletonList(identity)); - - Broadcast broadcast = Factory.getBroadcast(new Plaintext.Builder(BROADCAST) - .from(identity) - .message("S", "T") - .build()); - ObjectMessage objectMessage = new ObjectMessage.Builder() - .stream(2) - .payload(broadcast) - .nonce(new byte[8]) - .build(); - objectMessage.sign(identity.getPrivateKey()); - broadcast.encrypt(); - - listener.receive(objectMessage); - - verify(messageRepo, atLeastOnce()).save(plaintext(BROADCAST)); - } -} diff --git a/core/src/test/java/ch/dissem/bitmessage/DefaultMessageListenerTest.kt b/core/src/test/java/ch/dissem/bitmessage/DefaultMessageListenerTest.kt new file mode 100644 index 0000000..dc30105 --- /dev/null +++ b/core/src/test/java/ch/dissem/bitmessage/DefaultMessageListenerTest.kt @@ -0,0 +1,128 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage + +import ch.dissem.bitmessage.entity.BitmessageAddress +import ch.dissem.bitmessage.entity.ObjectMessage +import ch.dissem.bitmessage.entity.Plaintext +import ch.dissem.bitmessage.entity.Plaintext.Status.PUBKEY_REQUESTED +import ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST +import ch.dissem.bitmessage.entity.Plaintext.Type.MSG +import ch.dissem.bitmessage.entity.payload.GetPubkey +import ch.dissem.bitmessage.entity.payload.Msg +import ch.dissem.bitmessage.entity.payload.ObjectType +import ch.dissem.bitmessage.factory.Factory +import ch.dissem.bitmessage.utils.Singleton +import ch.dissem.bitmessage.utils.TestBase +import ch.dissem.bitmessage.utils.TestUtils +import com.nhaarman.mockito_kotlin.* +import org.junit.Before +import org.junit.Test + +/** + * @author Christian Basler + */ +class DefaultMessageListenerTest : TestBase() { + private lateinit var listener: DefaultMessageListener + + private val ctx = TestUtils.mockedInternalContext( + cryptography = Singleton.cryptography() + ) + + @Before + fun setUp() { + listener = ctx.networkListener as DefaultMessageListener + } + + @Test + fun `ensure pubkey is sent on request`() { + val identity = TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8") + whenever(ctx.addressRepository.findIdentity(any())).thenReturn(identity) + listener.receive(ObjectMessage.Builder() + .stream(2) + .payload(GetPubkey(BitmessageAddress("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"))) + .build()) + verify(ctx.proofOfWorkRepository).putObject(argThat { type == ObjectType.PUBKEY.number }, any(), any()) + } + + @Test + fun `ensure incoming pubkey is added to contact`() { + val identity = TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8") + val contact = BitmessageAddress(identity.address) + whenever(ctx.addressRepository.findContact(isA())).thenReturn(contact) + whenever(ctx.messageRepository.findMessages(eq(PUBKEY_REQUESTED), eq(contact))) + .thenReturn(listOf(Plaintext.Builder(MSG).from(identity).to(contact).message("S", "T").build())) + + val objectMessage = ObjectMessage.Builder() + .stream(2) + .payload(identity.pubkey!!) + .build() + objectMessage.sign(identity.privateKey!!) + objectMessage.encrypt(Singleton.cryptography().createPublicKey(identity.publicDecryptionKey)) + listener.receive(objectMessage) + + verify(ctx.addressRepository).save(eq(contact)) + } + + @Test + fun `ensure incoming message is saved`() { + val identity = TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8") + val contact = BitmessageAddress(identity.address) + contact.pubkey = identity.pubkey + + whenever(ctx.addressRepository.getIdentities()).thenReturn(listOf(identity)) + + val objectMessage = ObjectMessage.Builder() + .stream(2) + .payload(Msg(Plaintext.Builder(MSG) + .from(identity) + .to(contact) + .message("S", "T") + .build())) + .nonce(ByteArray(8)) + .build() + objectMessage.sign(identity.privateKey!!) + objectMessage.encrypt(identity.pubkey!!) + + listener.receive(objectMessage) + + verify(ctx.messageRepository, atLeastOnce()).save(argThat { type == MSG }) + } + + @Test + fun `ensure incoming broadcast is saved`() { + val identity = TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8") + + whenever(ctx.addressRepository.getSubscriptions(any())).thenReturn(listOf(identity)) + + val broadcast = Factory.getBroadcast(Plaintext.Builder(BROADCAST) + .from(identity) + .message("S", "T") + .build()) + val objectMessage = ObjectMessage.Builder() + .stream(2) + .payload(broadcast) + .nonce(ByteArray(8)) + .build() + objectMessage.sign(identity.privateKey!!) + broadcast.encrypt() + + listener.receive(objectMessage) + + verify(ctx.messageRepository, atLeastOnce()).save(argThat { type == BROADCAST }) + } +} diff --git a/core/src/test/java/ch/dissem/bitmessage/EncryptionTest.java b/core/src/test/java/ch/dissem/bitmessage/EncryptionTest.java deleted file mode 100644 index 0a6ee25..0000000 --- a/core/src/test/java/ch/dissem/bitmessage/EncryptionTest.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage; - -import ch.dissem.bitmessage.entity.BitmessageAddress; -import ch.dissem.bitmessage.entity.ObjectMessage; -import ch.dissem.bitmessage.entity.Plaintext; -import ch.dissem.bitmessage.entity.payload.CryptoBox; -import ch.dissem.bitmessage.entity.payload.GenericPayload; -import ch.dissem.bitmessage.entity.payload.Msg; -import ch.dissem.bitmessage.entity.valueobject.PrivateKey; -import ch.dissem.bitmessage.exception.DecryptionFailedException; -import ch.dissem.bitmessage.utils.TestBase; -import ch.dissem.bitmessage.utils.TestUtils; -import org.junit.Test; - -import java.io.IOException; - -import static ch.dissem.bitmessage.utils.Singleton.cryptography; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; - -public class EncryptionTest extends TestBase { - @Test - public void ensureDecryptedDataIsSameAsBeforeEncryption() throws IOException, DecryptionFailedException { - GenericPayload before = new GenericPayload(0, 1, cryptography().randomBytes(100)); - - PrivateKey privateKey = new PrivateKey(false, 1, 1000, 1000); - CryptoBox cryptoBox = new CryptoBox(before, privateKey.getPubkey().getEncryptionKey()); - - GenericPayload after = GenericPayload.read(0, 1, cryptoBox.decrypt(privateKey.getPrivateEncryptionKey()), 100); - - assertEquals(before, after); - } - - @Test - public void ensureMessageCanBeDecrypted() throws IOException, DecryptionFailedException { - PrivateKey privateKey = PrivateKey.read(TestUtils.getResource("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8.privkey")); - BitmessageAddress identity = new BitmessageAddress(privateKey); - assertEquals("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8", identity.getAddress()); - - ObjectMessage object = TestUtils.loadObjectMessage(3, "V1Msg.payload"); - Msg msg = (Msg) object.getPayload(); - msg.decrypt(privateKey.getPrivateEncryptionKey()); - Plaintext plaintext = msg.getPlaintext(); - assertNotNull(plaintext); - assertEquals("Test", plaintext.getSubject()); - assertEquals("Hallo, das ist ein Test von der v4-Adresse", plaintext.getText()); - } -} diff --git a/core/src/test/java/ch/dissem/bitmessage/EncryptionTest.kt b/core/src/test/java/ch/dissem/bitmessage/EncryptionTest.kt new file mode 100644 index 0000000..657b468 --- /dev/null +++ b/core/src/test/java/ch/dissem/bitmessage/EncryptionTest.kt @@ -0,0 +1,57 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage + +import ch.dissem.bitmessage.entity.BitmessageAddress +import ch.dissem.bitmessage.entity.payload.CryptoBox +import ch.dissem.bitmessage.entity.payload.GenericPayload +import ch.dissem.bitmessage.entity.payload.Msg +import ch.dissem.bitmessage.entity.valueobject.PrivateKey +import ch.dissem.bitmessage.utils.Singleton.cryptography +import ch.dissem.bitmessage.utils.TestBase +import ch.dissem.bitmessage.utils.TestUtils +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull +import org.junit.Test + +class EncryptionTest : TestBase() { + @Test + fun `ensure decrypted data is same as before encryption`() { + val before = GenericPayload(0, 1, cryptography().randomBytes(100)) + + val privateKey = PrivateKey(false, 1, 1000, 1000) + val cryptoBox = CryptoBox(before, privateKey.pubkey.encryptionKey) + + val after = GenericPayload.read(0, 1, cryptoBox.decrypt(privateKey.privateEncryptionKey), 100) + + assertEquals(before, after) + } + + @Test + fun `ensure message can be decrypted`() { + val privateKey = PrivateKey.read(TestUtils.getResource("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8.privkey")) + val identity = BitmessageAddress(privateKey) + assertEquals("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8", identity.address) + + val (_, _, payload) = TestUtils.loadObjectMessage(3, "V1Msg.payload") + val msg = payload as Msg + msg.decrypt(privateKey.privateEncryptionKey) + assertNotNull(msg.plaintext) + assertEquals("Test", msg.plaintext?.subject) + assertEquals("Hallo, das ist ein Test von der v4-Adresse", msg.plaintext?.text) + } +} diff --git a/core/src/test/java/ch/dissem/bitmessage/ProofOfWorkServiceTest.java b/core/src/test/java/ch/dissem/bitmessage/ProofOfWorkServiceTest.java deleted file mode 100644 index 039e7ad..0000000 --- a/core/src/test/java/ch/dissem/bitmessage/ProofOfWorkServiceTest.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright 2016 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage; - -import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography; -import ch.dissem.bitmessage.entity.BitmessageAddress; -import ch.dissem.bitmessage.entity.ObjectMessage; -import ch.dissem.bitmessage.entity.Plaintext; -import ch.dissem.bitmessage.entity.payload.Msg; -import ch.dissem.bitmessage.ports.*; -import ch.dissem.bitmessage.utils.Singleton; -import ch.dissem.bitmessage.utils.TestUtils; -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.util.Arrays; - -import static ch.dissem.bitmessage.entity.Plaintext.Type.MSG; -import static org.hamcrest.CoreMatchers.equalTo; -import static org.junit.Assert.assertThat; -import static org.mockito.Mockito.*; - -/** - * @author Christian Basler - */ -public class ProofOfWorkServiceTest { - private ProofOfWorkService proofOfWorkService; - - private Cryptography cryptography; - @Mock - private InternalContext ctx; - @Mock - private ProofOfWorkRepository proofOfWorkRepo; - @Mock - private Inventory inventory; - @Mock - private NetworkHandler networkHandler; - @Mock - private MessageRepository messageRepo; - - @Before - public void setUp() throws Exception { - MockitoAnnotations.initMocks(this); - cryptography = spy(new BouncyCryptography()); - Singleton.initialize(cryptography); - - ctx = mock(InternalContext.class); - when(ctx.getProofOfWorkRepository()).thenReturn(proofOfWorkRepo); - when(ctx.getInventory()).thenReturn(inventory); - when(ctx.getNetworkHandler()).thenReturn(networkHandler); - when(ctx.getMessageRepository()).thenReturn(messageRepo); - when(ctx.getLabeler()).thenReturn(mock(Labeler.class)); - when(ctx.getNetworkListener()).thenReturn(mock(NetworkHandler.MessageListener.class)); - - proofOfWorkService = new ProofOfWorkService(); - proofOfWorkService.setContext(ctx); - } - - @Test - public void ensureMissingProofOfWorkIsDone() { - when(proofOfWorkRepo.getItems()).thenReturn(Arrays.asList(new byte[64])); - when(proofOfWorkRepo.getItem(any(byte[].class))).thenReturn(new ProofOfWorkRepository.Item(null, 1001, 1002)); - doNothing().when(cryptography).doProofOfWork(any(ObjectMessage.class), anyLong(), anyLong(), any(ProofOfWorkEngine.Callback.class)); - - proofOfWorkService.doMissingProofOfWork(10); - - verify(cryptography, timeout(1000)).doProofOfWork((ObjectMessage) isNull(), eq(1001L), eq(1002L), - any(ProofOfWorkEngine.Callback.class)); - } - - @Test - public void ensureCalculatedNonceIsStored() throws Exception { - BitmessageAddress identity = TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"); - BitmessageAddress address = TestUtils.loadContact(); - Plaintext plaintext = new Plaintext.Builder(MSG).from(identity).to(address).message("", "").build(); - ObjectMessage object = new ObjectMessage.Builder() - .payload(new Msg(plaintext)) - .build(); - object.sign(identity.getPrivateKey()); - object.encrypt(address.getPubkey()); - byte[] initialHash = new byte[64]; - byte[] nonce = new byte[]{1, 2, 3, 4, 5, 6, 7, 8}; - - when(proofOfWorkRepo.getItem(initialHash)).thenReturn(new ProofOfWorkRepository.Item(object, 1001, 1002)); - when(messageRepo.getMessage(initialHash)).thenReturn(plaintext); - - proofOfWorkService.onNonceCalculated(initialHash, nonce); - - verify(proofOfWorkRepo).removeObject(eq(initialHash)); - verify(inventory).storeObject(eq(object)); - verify(networkHandler).offer(eq(object.getInventoryVector())); - assertThat(plaintext.getInventoryVector(), equalTo(object.getInventoryVector())); - } -} diff --git a/core/src/test/java/ch/dissem/bitmessage/ProofOfWorkServiceTest.kt b/core/src/test/java/ch/dissem/bitmessage/ProofOfWorkServiceTest.kt new file mode 100644 index 0000000..23a31a9 --- /dev/null +++ b/core/src/test/java/ch/dissem/bitmessage/ProofOfWorkServiceTest.kt @@ -0,0 +1,100 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage + +import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography +import ch.dissem.bitmessage.entity.ObjectMessage +import ch.dissem.bitmessage.entity.Plaintext +import ch.dissem.bitmessage.entity.Plaintext.Type.MSG +import ch.dissem.bitmessage.entity.payload.GenericPayload +import ch.dissem.bitmessage.entity.payload.Msg +import ch.dissem.bitmessage.ports.Cryptography +import ch.dissem.bitmessage.ports.ProofOfWorkRepository +import ch.dissem.bitmessage.utils.Singleton +import ch.dissem.bitmessage.utils.TestUtils +import com.nhaarman.mockito_kotlin.* +import org.hamcrest.CoreMatchers.equalTo +import org.junit.Assert.assertThat +import org.junit.Before +import org.junit.Test +import java.util.* +import kotlin.properties.Delegates + +/** + * @author Christian Basler + */ +class ProofOfWorkServiceTest { + private var cryptography by Delegates.notNull<Cryptography>() + private var ctx by Delegates.notNull<InternalContext>() + + private var obj by Delegates.notNull<ObjectMessage>() + + @Before + fun setUp() { + cryptography = spy(BouncyCryptography()) + Singleton.initialize(cryptography) + + ctx = TestUtils.mockedInternalContext( + cryptography = cryptography + ) + + obj = ObjectMessage( + expiresTime = 0, + stream = 1, + payload = GenericPayload(1, 1, kotlin.ByteArray(0)), + type = 42, + version = 42 + ) + } + + @Test + fun `ensure missing proof of work is done`() { + whenever(ctx.proofOfWorkRepository.getItems()).thenReturn(Arrays.asList<ByteArray>(ByteArray(64))) + whenever(ctx.proofOfWorkRepository.getItem(any())).thenReturn(ProofOfWorkRepository.Item(obj, 1001, 1002)) + doNothing().whenever(cryptography).doProofOfWork(any(), any(), any(), any()) + + ctx.proofOfWorkService.doMissingProofOfWork(10) + + verify(cryptography, timeout(1000)).doProofOfWork(eq(obj), eq(1001L), eq(1002L), any()) + } + + @Test + fun `ensure calculated nonce is stored`() { + val identity = TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8") + val address = TestUtils.loadContact() + val plaintext = Plaintext.Builder(MSG).from(identity).to(address).message("", "").build() + val `object` = ObjectMessage( + expiresTime = 0, + stream = 1, + payload = Msg(plaintext) + ) + `object`.sign(identity.privateKey!!) + `object`.encrypt(address.pubkey!!) + val initialHash = ByteArray(64) + val nonce = byteArrayOf(1, 2, 3, 4, 5, 6, 7, 8) + + whenever(ctx.proofOfWorkRepository.getItem(initialHash)).thenReturn(ProofOfWorkRepository.Item(`object`, 1001, 1002)) + whenever(ctx.messageRepository.getMessage(initialHash)).thenReturn(plaintext) + + ctx.proofOfWorkService.onNonceCalculated(initialHash, nonce) + + verify(ctx.proofOfWorkRepository).removeObject(eq(initialHash)) + verify(ctx.inventory).storeObject(eq(`object`)) + verify(ctx.networkHandler).offer(eq(`object`.inventoryVector)) + assertThat(plaintext.inventoryVector, equalTo(`object`.inventoryVector)) + } +} diff --git a/core/src/test/java/ch/dissem/bitmessage/SignatureTest.java b/core/src/test/java/ch/dissem/bitmessage/SignatureTest.java deleted file mode 100644 index 3566a5f..0000000 --- a/core/src/test/java/ch/dissem/bitmessage/SignatureTest.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage; - -import ch.dissem.bitmessage.entity.BitmessageAddress; -import ch.dissem.bitmessage.entity.ObjectMessage; -import ch.dissem.bitmessage.entity.Plaintext; -import ch.dissem.bitmessage.entity.payload.Msg; -import ch.dissem.bitmessage.entity.payload.ObjectType; -import ch.dissem.bitmessage.entity.payload.Pubkey; -import ch.dissem.bitmessage.entity.valueobject.PrivateKey; -import ch.dissem.bitmessage.exception.DecryptionFailedException; -import ch.dissem.bitmessage.utils.TestBase; -import ch.dissem.bitmessage.utils.TestUtils; -import org.junit.Test; - -import java.io.IOException; - -import static org.junit.Assert.*; - -public class SignatureTest extends TestBase { - @Test - public void ensureValidationWorks() throws IOException { - ObjectMessage object = TestUtils.loadObjectMessage(3, "V3Pubkey.payload"); - Pubkey pubkey = (Pubkey) object.getPayload(); - assertTrue(object.isSignatureValid(pubkey)); - } - - @Test - public void ensureSigningWorks() throws IOException { - PrivateKey privateKey = new PrivateKey(false, 1, 1000, 1000); - - ObjectMessage objectMessage = new ObjectMessage.Builder() - .objectType(ObjectType.PUBKEY) - .stream(1) - .payload(privateKey.getPubkey()) - .build(); - objectMessage.sign(privateKey); - - assertTrue(objectMessage.isSignatureValid(privateKey.getPubkey())); - } - - @Test - public void ensureMessageIsProperlySigned() throws IOException, DecryptionFailedException { - BitmessageAddress identity = TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"); - - ObjectMessage object = TestUtils.loadObjectMessage(3, "V1Msg.payload"); - Msg msg = (Msg) object.getPayload(); - msg.decrypt(identity.getPrivateKey().getPrivateEncryptionKey()); - Plaintext plaintext = msg.getPlaintext(); - assertEquals(TestUtils.loadContact().getPubkey(), plaintext.getFrom().getPubkey()); - assertNotNull(plaintext); - assertTrue(object.isSignatureValid(plaintext.getFrom().getPubkey())); - } -} diff --git a/core/src/test/java/ch/dissem/bitmessage/SignatureTest.kt b/core/src/test/java/ch/dissem/bitmessage/SignatureTest.kt new file mode 100644 index 0000000..5fe9f7a --- /dev/null +++ b/core/src/test/java/ch/dissem/bitmessage/SignatureTest.kt @@ -0,0 +1,62 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage + +import ch.dissem.bitmessage.entity.ObjectMessage +import ch.dissem.bitmessage.entity.payload.Msg +import ch.dissem.bitmessage.entity.payload.ObjectType +import ch.dissem.bitmessage.entity.payload.Pubkey +import ch.dissem.bitmessage.entity.valueobject.PrivateKey +import ch.dissem.bitmessage.utils.TestBase +import ch.dissem.bitmessage.utils.TestUtils +import org.junit.Assert.* +import org.junit.Test + +class SignatureTest : TestBase() { + @Test + fun `ensure validation works`() { + val `object` = TestUtils.loadObjectMessage(3, "V3Pubkey.payload") + val pubkey = `object`.payload as Pubkey + assertTrue(`object`.isSignatureValid(pubkey)) + } + + @Test + fun `ensure signing works`() { + val privateKey = PrivateKey(false, 1, 1000, 1000) + + val objectMessage = ObjectMessage.Builder() + .objectType(ObjectType.PUBKEY) + .stream(1) + .payload(privateKey.pubkey) + .build() + objectMessage.sign(privateKey) + + assertTrue(objectMessage.isSignatureValid(privateKey.pubkey)) + } + + @Test + fun `ensure message is properly signed`() { + val identity = TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8") + + val `object` = TestUtils.loadObjectMessage(3, "V1Msg.payload") + val msg = `object`.payload as Msg + msg.decrypt(identity.privateKey!!.privateEncryptionKey) + assertNotNull(msg.plaintext) + assertEquals(TestUtils.loadContact().pubkey, msg.plaintext!!.from.pubkey) + assertTrue(`object`.isSignatureValid(msg.plaintext!!.from.pubkey!!)) + } +} diff --git a/core/src/test/java/ch/dissem/bitmessage/entity/BitmessageAddressTest.java b/core/src/test/java/ch/dissem/bitmessage/entity/BitmessageAddressTest.java deleted file mode 100644 index fced84f..0000000 --- a/core/src/test/java/ch/dissem/bitmessage/entity/BitmessageAddressTest.java +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.entity; - -import ch.dissem.bitmessage.entity.payload.Pubkey; -import ch.dissem.bitmessage.entity.payload.V4Pubkey; -import ch.dissem.bitmessage.entity.valueobject.PrivateKey; -import ch.dissem.bitmessage.exception.DecryptionFailedException; -import ch.dissem.bitmessage.utils.*; -import org.junit.Test; - -import java.io.IOException; -import java.util.Arrays; - -import static ch.dissem.bitmessage.entity.payload.Pubkey.Feature.DOES_ACK; -import static ch.dissem.bitmessage.entity.payload.Pubkey.Feature.INCLUDE_DESTINATION; -import static ch.dissem.bitmessage.utils.Singleton.cryptography; -import static org.junit.Assert.*; - -public class BitmessageAddressTest extends TestBase { - @Test - public void ensureFeatureFlagIsCalculatedCorrectly() { - assertEquals(1, Pubkey.Feature.bitfield(DOES_ACK)); - assertEquals(2, Pubkey.Feature.bitfield(INCLUDE_DESTINATION)); - assertEquals(3, Pubkey.Feature.bitfield(DOES_ACK, INCLUDE_DESTINATION)); - } - - @Test - public void ensureBase58DecodesCorrectly() { - assertHexEquals("800C28FCA386C7A227600B2FE50B7CAE11EC86D3BF1FBE471BE89827E19D72AA1D507A5B8D", - Base58.decode("5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ")); - } - - @Test - public void ensureAddressStaysSame() { - String address = "BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ"; - assertEquals(address, new BitmessageAddress(address).toString()); - } - - @Test - public void ensureStreamAndVersionAreParsed() { - BitmessageAddress address = new BitmessageAddress("BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ"); - assertEquals(1, address.getStream()); - assertEquals(3, address.getVersion()); - - address = new BitmessageAddress("BM-87hJ99tPAXxtetvnje7Z491YSvbEtBJVc5e"); - assertEquals(1, address.getStream()); - assertEquals(4, address.getVersion()); - } - - @Test - public void ensureIdentityCanBeCreated() { - BitmessageAddress address = new BitmessageAddress(new PrivateKey(false, 1, 1000, 1000, DOES_ACK)); - assertNotNull(address.getPubkey()); - assertTrue(address.has(DOES_ACK)); - } - - @Test - public void ensureV2PubkeyCanBeImported() throws IOException { - ObjectMessage object = TestUtils.loadObjectMessage(2, "V2Pubkey.payload"); - Pubkey pubkey = (Pubkey) object.getPayload(); - BitmessageAddress address = new BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT"); - try { - address.setPubkey(pubkey); - } catch (Exception e) { - fail(e.getMessage()); - } - } - - @Test - public void ensureV3PubkeyCanBeImported() throws IOException { - BitmessageAddress address = new BitmessageAddress("BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ"); - assertArrayEquals(Bytes.fromHex("007402be6e76c3cb87caa946d0c003a3d4d8e1d5"), address.getRipe()); - - ObjectMessage object = TestUtils.loadObjectMessage(3, "V3Pubkey.payload"); - Pubkey pubkey = (Pubkey) object.getPayload(); - assertTrue(object.isSignatureValid(pubkey)); - try { - address.setPubkey(pubkey); - } catch (Exception e) { - fail(e.getMessage()); - } - - assertArrayEquals(Bytes.fromHex("007402be6e76c3cb87caa946d0c003a3d4d8e1d5"), pubkey.getRipe()); - assertTrue(address.has(DOES_ACK)); - } - - @Test - public void ensureV4PubkeyCanBeImported() throws IOException, DecryptionFailedException { - BitmessageAddress address = new BitmessageAddress("BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h"); - ObjectMessage object = TestUtils.loadObjectMessage(4, "V4Pubkey.payload"); - object.decrypt(address.getPublicDecryptionKey()); - V4Pubkey pubkey = (V4Pubkey) object.getPayload(); - assertTrue(object.isSignatureValid(pubkey)); - try { - address.setPubkey(pubkey); - } catch (Exception e) { - fail(e.getMessage()); - } - assertTrue(address.has(DOES_ACK)); - } - - @Test - public void ensureV3IdentityCanBeImported() throws IOException { - String address_string = "BM-2DAjcCFrqFrp88FUxExhJ9kPqHdunQmiyn"; - assertEquals(3, new BitmessageAddress(address_string).getVersion()); - assertEquals(1, new BitmessageAddress(address_string).getStream()); - - byte[] privsigningkey = getSecret("5KU2gbe9u4rKJ8PHYb1rvwMnZnAJj4gtV5GLwoYckeYzygWUzB9"); - byte[] privencryptionkey = getSecret("5KHd4c6cavd8xv4kzo3PwnVaYuBgEfg7voPQ5V97aZKgpYBXGck"); - - System.out.println("\n\n" + Strings.hex(privsigningkey) + "\n\n"); - - BitmessageAddress address = new BitmessageAddress(new PrivateKey(privsigningkey, privencryptionkey, - cryptography().createPubkey(3, 1, privsigningkey, privencryptionkey, 320, 14000))); - assertEquals(address_string, address.getAddress()); - } - - @Test - public void ensureV4IdentityCanBeImported() throws IOException { - assertEquals(4, new BitmessageAddress("BM-2cV5f9EpzaYARxtoruSpa6pDoucSf9ZNke").getVersion()); - byte[] privsigningkey = getSecret("5KMWqfCyJZGFgW6QrnPJ6L9Gatz25B51y7ErgqNr1nXUVbtZbdU"); - byte[] privencryptionkey = getSecret("5JXXWEuhHQEPk414SzEZk1PHDRi8kCuZd895J7EnKeQSahJPxGz"); - BitmessageAddress address = new BitmessageAddress(new PrivateKey(privsigningkey, privencryptionkey, - cryptography().createPubkey(4, 1, privsigningkey, privencryptionkey, 320, 14000))); - assertEquals("BM-2cV5f9EpzaYARxtoruSpa6pDoucSf9ZNke", address.getAddress()); - } - - private void assertHexEquals(String hex, byte[] bytes) { - assertEquals(hex.toLowerCase(), Strings.hex(bytes).toString().toLowerCase()); - } - - private byte[] getSecret(String walletImportFormat) throws IOException { - byte[] bytes = Base58.decode(walletImportFormat); - if (bytes[0] != (byte) 0x80) - throw new IOException("Unknown format: 0x80 expected as first byte, but secret " + walletImportFormat + " was " + bytes[0]); - if (bytes.length != 37) - throw new IOException("Unknown format: 37 bytes expected, but secret " + walletImportFormat + " was " + bytes.length + " long"); - - byte[] hash = cryptography().doubleSha256(bytes, 33); - for (int i = 0; i < 4; i++) { - if (hash[i] != bytes[33 + i]) throw new IOException("Hash check failed for secret " + walletImportFormat); - } - return Arrays.copyOfRange(bytes, 1, 33); - } -} diff --git a/core/src/test/java/ch/dissem/bitmessage/entity/BitmessageAddressTest.kt b/core/src/test/java/ch/dissem/bitmessage/entity/BitmessageAddressTest.kt new file mode 100644 index 0000000..1177ea2 --- /dev/null +++ b/core/src/test/java/ch/dissem/bitmessage/entity/BitmessageAddressTest.kt @@ -0,0 +1,159 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.entity + +import ch.dissem.bitmessage.entity.payload.Pubkey +import ch.dissem.bitmessage.entity.payload.Pubkey.Feature.DOES_ACK +import ch.dissem.bitmessage.entity.payload.Pubkey.Feature.INCLUDE_DESTINATION +import ch.dissem.bitmessage.entity.payload.V4Pubkey +import ch.dissem.bitmessage.entity.valueobject.PrivateKey +import ch.dissem.bitmessage.utils.* +import org.junit.Assert +import org.junit.Assert.* +import org.junit.Test +import java.io.IOException +import java.util.* + +class BitmessageAddressTest : TestBase() { + @Test + fun `ensure feature flag is calculated correctly`() { + Assert.assertEquals(1, Pubkey.Feature.bitfield(DOES_ACK).toLong()) + assertEquals(2, Pubkey.Feature.bitfield(INCLUDE_DESTINATION).toLong()) + assertEquals(3, Pubkey.Feature.bitfield(DOES_ACK, INCLUDE_DESTINATION).toLong()) + } + + @Test + fun `ensure base58 decodes correctly`() { + assertHexEquals("800C28FCA386C7A227600B2FE50B7CAE11EC86D3BF1FBE471BE89827E19D72AA1D507A5B8D", + Base58.decode("5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ")) + } + + @Test + fun `ensure address stays same`() { + val address = "BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ" + assertEquals(address, BitmessageAddress(address).toString()) + } + + @Test + fun `ensure stream and version are parsed`() { + var address = BitmessageAddress("BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ") + assertEquals(1, address.stream) + assertEquals(3, address.version) + + address = BitmessageAddress("BM-87hJ99tPAXxtetvnje7Z491YSvbEtBJVc5e") + assertEquals(1, address.stream) + assertEquals(4, address.version) + } + + @Test + fun `ensure identity can be created`() { + val address = BitmessageAddress(PrivateKey(false, 1, 1000, 1000, DOES_ACK)) + assertNotNull(address.pubkey) + assertTrue(address.has(DOES_ACK)) + } + + @Test + fun `ensure V2Pubkey can be imported`() { + val (_, _, payload) = TestUtils.loadObjectMessage(2, "V2Pubkey.payload") + val pubkey = payload as Pubkey + val address = BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT") + try { + address.pubkey = pubkey + } catch (e: Exception) { + fail(e.message) + } + + } + + @Test + fun `ensure V3Pubkey can be imported`() { + val address = BitmessageAddress("BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ") + Assert.assertArrayEquals(Bytes.fromHex("007402be6e76c3cb87caa946d0c003a3d4d8e1d5"), address.ripe) + + val `object` = TestUtils.loadObjectMessage(3, "V3Pubkey.payload") + val pubkey = `object`.payload as Pubkey + assertTrue(`object`.isSignatureValid(pubkey)) + try { + address.pubkey = pubkey + } catch (e: Exception) { + fail(e.message) + } + + assertArrayEquals(Bytes.fromHex("007402be6e76c3cb87caa946d0c003a3d4d8e1d5"), pubkey.ripe) + assertTrue(address.has(DOES_ACK)) + } + + @Test + fun `ensure V4Pubkey can be imported`() { + val address = BitmessageAddress("BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h") + val `object` = TestUtils.loadObjectMessage(4, "V4Pubkey.payload") + `object`.decrypt(address.publicDecryptionKey) + val pubkey = `object`.payload as V4Pubkey + assertTrue(`object`.isSignatureValid(pubkey)) + try { + address.pubkey = pubkey + } catch (e: Exception) { + fail(e.message) + } + + assertTrue(address.has(DOES_ACK)) + } + + @Test + fun `ensure V3 identity can be imported`() { + val address_string = "BM-2DAjcCFrqFrp88FUxExhJ9kPqHdunQmiyn" + assertEquals(3, BitmessageAddress(address_string).version) + assertEquals(1, BitmessageAddress(address_string).stream) + + val privsigningkey = getSecret("5KU2gbe9u4rKJ8PHYb1rvwMnZnAJj4gtV5GLwoYckeYzygWUzB9") + val privencryptionkey = getSecret("5KHd4c6cavd8xv4kzo3PwnVaYuBgEfg7voPQ5V97aZKgpYBXGck") + + println("\n\n" + Strings.hex(privsigningkey) + "\n\n") + + val address = BitmessageAddress(PrivateKey(privsigningkey, privencryptionkey, + Singleton.cryptography().createPubkey(3, 1, privsigningkey, privencryptionkey, 320, 14000))) + assertEquals(address_string, address.address) + } + + @Test + fun `ensure V4 identity can be imported`() { + assertEquals(4, BitmessageAddress("BM-2cV5f9EpzaYARxtoruSpa6pDoucSf9ZNke").version) + val privsigningkey = getSecret("5KMWqfCyJZGFgW6QrnPJ6L9Gatz25B51y7ErgqNr1nXUVbtZbdU") + val privencryptionkey = getSecret("5JXXWEuhHQEPk414SzEZk1PHDRi8kCuZd895J7EnKeQSahJPxGz") + val address = BitmessageAddress(PrivateKey(privsigningkey, privencryptionkey, + Singleton.cryptography().createPubkey(4, 1, privsigningkey, privencryptionkey, 320, 14000))) + assertEquals("BM-2cV5f9EpzaYARxtoruSpa6pDoucSf9ZNke", address.address) + } + + private fun assertHexEquals(hex: String, bytes: ByteArray) { + assertEquals(hex.toLowerCase(), Strings.hex(bytes).toLowerCase()) + } + + private fun getSecret(walletImportFormat: String): ByteArray { + val bytes = Base58.decode(walletImportFormat) + if (bytes[0] != 0x80.toByte()) + throw IOException("Unknown format: 0x80 expected as first byte, but secret " + walletImportFormat + " was " + bytes[0]) + if (bytes.size != 37) + throw IOException("Unknown format: 37 bytes expected, but secret " + walletImportFormat + " was " + bytes.size + " long") + + val hash = Singleton.cryptography().doubleSha256(bytes, 33) + for (i in 0..3) { + if (hash[i] != bytes[33 + i]) throw IOException("Hash check failed for secret " + walletImportFormat) + } + return Arrays.copyOfRange(bytes, 1, 33) + } +} diff --git a/core/src/test/java/ch/dissem/bitmessage/entity/ExtendedEncodingTest.java b/core/src/test/java/ch/dissem/bitmessage/entity/ExtendedEncodingTest.java deleted file mode 100644 index c417df1..0000000 --- a/core/src/test/java/ch/dissem/bitmessage/entity/ExtendedEncodingTest.java +++ /dev/null @@ -1,82 +0,0 @@ -package ch.dissem.bitmessage.entity; - -import ch.dissem.bitmessage.entity.valueobject.ExtendedEncoding; -import ch.dissem.bitmessage.entity.valueobject.extended.Attachment; -import ch.dissem.bitmessage.entity.valueobject.extended.Message; -import ch.dissem.bitmessage.entity.valueobject.extended.Vote; -import ch.dissem.bitmessage.factory.ExtendedEncodingFactory; -import ch.dissem.bitmessage.utils.Bytes; -import ch.dissem.bitmessage.utils.TestUtils; -import org.junit.Test; - -import java.io.UnsupportedEncodingException; - -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.assertThat; - -/** - * @author Christian Basler - */ -public class ExtendedEncodingTest { - private ExtendedEncodingFactory extendedEncodingFactory = ExtendedEncodingFactory.getInstance(); - - @Test - public void ensureSimpleMessageIsDecoded() { - ExtendedEncoding extended = extendedEncodingFactory.unzip(Bytes.fromHex("78da9d59dd8e1bb715ee359fa097c4b65b498876babb76d0760bd970368eedb68e8daed3c0700c81d25012b333c3e99063ad1c0430d0a728d002bd289077caa59fa4df39244723693705b2f0da339c730ebff3c3f343ffe33f3f94da39b5d4ff9dd97cf3e32f9d10524afe4b3a2b57762dfd4acb453bbf967355c967b2755a1a2f1f0afe791eb85f2a7c374e2ab950ce8fe5dc96b59a7b3933956a36d2e9c6a8c2bc57ded84a2e6c532a10b9d678352b342d48674a53a846e6ca2be9adfcd3d58b2f33f96a05a190748d3d64ddd87726d74e5ebedcf81504417a6eaaa563018d56f4225595cb75633c3df7d191e04c3cab9c574521c4af656d6a69c2ab2cdd927639a959b0102f372f3742ecae6ef7afdb0668c2a229eb4297baf2ac5b2689932d4576f2809f89af01d3ae61d9af57ba921bdbd2f78f1ffee999269a2837ce3766d692943113555ae7648984f16fc6b5aa9057becd8d95d038ca95579fff5902497ccdf00064ad97fac6ebcab1bcd69139eec64dfcc9aa4d5b39e90abb2e3699105f60a3f8e13cfbdd583e37f3c63abbf009cfe5279fc84bb8db14ba61476ca929221a8d58c03e399471b660fd76c5decb3ebd456c54f3fcf4ec53125fb695f11bf93837016e231fdfd40ddcdb2d91cd679a4cca569bb5a6c8b72690a5cddb4267e22945b42532f1a2d2276e653d0798fc8d6c2b7a10e22b78859e66ac0e3da5b80a14613d3ce34b96a2671b20795bd68e390aab723ce14f615408543e1bdecc4c411aade12cf9ad034222afcdfc9a500a96472b248a313882bd80954f0a73ada59d7dabe73e8b30babde23b766a6c79482ec483070fc8f7b6f109362fc5e78cf51bbe391bcbf3b1bcf7762406dfdcfce1de3737a767f83dc7efbdc10e7db4c8703a121d93101154c2128911f2b043d3a80da912bccf6b85a19c81d80f07273083c4b788d10b71eb86b3436014e67a4ac2265fa8c2e99118464823215ee344c1d92d8242156bb5a1b482e8a1e496b8e4b5deac6d038266d9d2d1c8e4954628e88633563547e273aed514d485e20413222970db9ae370a6717460e8bf221fb1f8dccee968839abc6fb1d4445ae4862b8fbc55d2c72e9e1080fc0832cea81f3ffccbed51e9e6e3877f67f299df9ab82dbc81b5a2a3a3ff6da565e09543eccd6bb38da7141642951036b65dae90d2c14319a7d4f04b3e82067b61c2dc389271f93392f3ec8510b3762127e9753812a4a541d6928daa967a78767a3abaa07a02ba8c12b31eee065b2033a3d18865654eebebe1e9284511cc30e9bc9f2c330461d82912e5b46162e0eda0a2a97cf71d393d98f7b79da1a55dc879eb3cd40a5567536b216054327b81ea575be70c1528b8997822eb011372ee53dd68e6ab907314e556f63648b437a5ceba87ceac69e5c0ce0211b5688b696e504027f23b56e6c8e44717f26c1c5ee670a9d7b472b84165d7c3d1587c2f44ae173247facdf5347d1d223c8237a459c8d960da7d994e0764427c0f9fe9072f0070b80542aa4ed2de1c2937c5c2d1dbb13c3a7e7d5c1ee7af8e9f5e1c3fbf38beca8e17472396d668df362c3dc0d2d54fc0328e4b1ece1bad8f0f018cb610a3e0ef7635b990af9a568fe52060c33b0411ec05ef762bceef0f808a1039c911bb51db731210ea85c2019ceca93512d40030cd542d15cc3b39c863bd3dc6f1f04e57d65e4ff63c37daa605ce993d5264cda2985160a2d86af34e53d620817fe4fc13296b651ab7475f221593ad2955f56a6714032921b12d28339ebc53454b85116232f1980a2be70f8aff9f776a6257421851a779edee021597b87cf042886eb67b0c9f2ccbe41d21c46c19ff3de25a49f1401b928de5642207f9602ba01708c95dd0f715c887f7cfc781d7869c3e44d64a6c8d32e81c88ec71d3d86678f455758dd358b16617f2b83992c792f104a64e0d68cf8e19121a8e77b5ab4e4279ff7c17a44240f5541b428bd12e4146593b4265b9b729a9b62a8485a46d0fcf1630f9ef60dbb17c739651a9cdeea36920ba9896f7cf0db16f0f4cfc377074a9fcae5332ee2c35490fa31ea6c936d9d3f117e251fe8e4220dfd65658b2f28d45ffff8873b52abc6e2ad4f2771cae284e8d0a1d78578353418d1533cac2f4726dea314f1d53b6c2748547d05374f16aa9eab4166aaaa37986eb428935bfb64cc725a3f206f5238e62a14ed3b108b59b7671085542596052d0719c72a92d0d5e4b6709c430e9987a64b3ac2c771ec687bdd1b6f8b877c4945c4e9c555bce48634464980d1c95046e916a74adc97e4864ba19d378e8570a63869aaf1203d19359c9208c603771f067641d182eb5ebc9e73402c2a23557f2dcc0d498027802794cf28109389cee6053ea0a5d1491f1c4a7196a94c7218139954cccadb3daa6bd455bcd8913ade55d4d0d67973492e961ea2cc672a9fd144d229e6315aa487527bbe06bb23def0f4309dc6b8b025baf92fd0acd660127036890cf1ca4d1127eafc8921d2d9efb1b8687b80ffd4401931eda2178466267b72af4a7c13d34a224dba53884d50273c7c6cd636828fb2d5ffadc2122570eb7c4a1efdbc7874eafcafb5440cb6028a98a2f2dd645ec9c29cae3a01cbb34d4e882bcbc71e36efecaad7661b026d781af356e256f17415307e6a238839e85e3411fe265020d9714d07771a3da55aa84b11a45dd7e824061fdff37e73d501a32f919861ec3b5b3c220da87749e754f52e5396731c38520c80cbc69df467768c86d20e13d03e3cc841ac41303b69ae9b9a2ab876badebdbc6513e3bb1c784cbd9d28599217f91f8b572a153e0f8a4ca5dda86ce9eaaa28eb0c765db00922f9005d62b4ca17b372cd2b5351d38a8add71dbeae2e48a73dcf575b8fc66b271e89b96a3b06032c61a0496d0c1245b2d390ac00d1e99e81669ad7e1f645ce57740af98286340c9d088d72c032252c132a2169a62321a11ad0fedce5817e8221acf58b93dfe3df1e65aa1c77f7327794c637b381ab554973ec402f9783b7e34344a35b87e1ae3c266403c68566e0cd9e482152ab25c42b1e5ebbde6b1c26e2bd8e27d6bfd4ccfd0cb56e6ba166839bcdfbf79bc1e8a7f419897e1f32213e8acc49e2dd7a33064708de1842eca9d82584413e4ee694583876f742f2343bcf6e4854837e96b2c1c1f77b72a5d028385b62bb2a1d1c04778826b4c5cc971084c44a0179d705031d468e332e43d82093bd7b8aee82a223d7377561e606c78a15a062b65690b77386b3eda4d05506b606e38ecd782aa34842a16f75f245eb5f2c3ee7b2b93d4b57ded6cf525794edbe12fa2e1184d689eecb1aebeddc16d25654bed9da6a46f791bd8b14f1e41239a27705f4e412394b514258d325e9b2b16bce9b4561e79c86d27d17df08975a55aed7d5513310521a7e0bba157c72194c99a26339cf9096e9b619c6e02db6dc05ecdb355f99d8bbd011e22ff462822bb609eaefada61b214ee73054d0252465beb7228ec22c57dc6f516e2499bba076afab62a2dce26298bbb74ff1d8a98aaef27ae6e311b57fabcc0a203952a740beefdf849351d7e9266e119a3452012d4bcd85650f57a4df02732d3ab2642ff9a80a775aeb70afb74716d38749d3dec1349a891f5ccb8b3ffee2ac1b2b5326eb5d6c73d7e0a43c917c5548ff35f1f0e1ff00b5629026")); - assertThat(extended, instanceOf(ExtendedEncoding.class)); - assertThat(extended.getContent(), instanceOf(Message.class)); - assertThat(((Message) extended.getContent()).getSubject(), is("Extended encoding on Windows works - but how ??")); - assertThat(((Message) extended.getContent()).getBody(), notNullValue()); - assertThat(((Message) extended.getContent()).getBody().length(), is(6233)); - } - - - @Test - public void ensureSimpleMessageIsEncoded() { - ExtendedEncoding in = new Message.Builder() - .subject("Test sübject") - .body("test bödy") - .build(); - - assertThat(in.zip(), notNullValue()); - - ExtendedEncoding out = extendedEncodingFactory.unzip(in.zip()); - assertThat(out, is(in)); - } - - @Test - public void ensureCompleteMessageIsEncodedAndDecoded() throws UnsupportedEncodingException { - ExtendedEncoding in = new Message.Builder() - .addParent(TestUtils.randomInventoryVector()) - .addParent(TestUtils.randomInventoryVector()) - .subject("Test sübject") - .body("test bödy") - .addFile( - new Attachment.Builder() - .name("test.txt") - .type("text/plain") - .data("test".getBytes("UTF-8")) - .attachment() - .build() - ) - .build(); - - assertThat(in.zip(), notNullValue()); - - ExtendedEncoding out = extendedEncodingFactory.unzip(in.zip()); - assertThat(out, is(in)); - } - - @Test - public void ensureVoteIsEncodedAndDecoded() { - ExtendedEncoding in = new Vote.Builder() - .msgId(TestUtils.randomInventoryVector()) - .vote("+1") - .build(); - - assertThat(in.zip(), notNullValue()); - - ExtendedEncoding out = extendedEncodingFactory.unzip(in.zip()); - assertThat(out, is(in)); - } -} diff --git a/core/src/test/java/ch/dissem/bitmessage/entity/ExtendedEncodingTest.kt b/core/src/test/java/ch/dissem/bitmessage/entity/ExtendedEncodingTest.kt new file mode 100644 index 0000000..bc4e8d6 --- /dev/null +++ b/core/src/test/java/ch/dissem/bitmessage/entity/ExtendedEncodingTest.kt @@ -0,0 +1,93 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.entity + +import ch.dissem.bitmessage.entity.valueobject.ExtendedEncoding +import ch.dissem.bitmessage.entity.valueobject.extended.Attachment +import ch.dissem.bitmessage.entity.valueobject.extended.Message +import ch.dissem.bitmessage.entity.valueobject.extended.Vote +import ch.dissem.bitmessage.factory.ExtendedEncodingFactory +import ch.dissem.bitmessage.utils.Bytes +import ch.dissem.bitmessage.utils.TestUtils +import org.hamcrest.Matchers.* +import org.junit.Assert.assertThat +import org.junit.Test + +/** + * @author Christian Basler + */ +class ExtendedEncodingTest { + @Test + fun `ensure simple message is decoded`() { + val extended = ExtendedEncodingFactory.unzip(Bytes.fromHex("78da9d59dd8e1bb715ee359fa097c4b65b498876babb76d0760bd970368eedb68e8daed3c0700c81d25012b333c3e99063ad1c0430d0a728d002bd289077caa59fa4df39244723693705b2f0da339c730ebff3c3f343ffe33f3f94da39b5d4ff9dd97cf3e32f9d10524afe4b3a2b57762dfd4acb453bbf967355c967b2755a1a2f1f0afe791eb85f2a7c374e2ab950ce8fe5dc96b59a7b3933956a36d2e9c6a8c2bc57ded84a2e6c532a10b9d678352b342d48674a53a846e6ca2be9adfcd3d58b2f33f96a05a190748d3d64ddd87726d74e5ebedcf81504417a6eaaa563018d56f4225595cb75633c3df7d191e04c3cab9c574521c4af656d6a69c2ab2cdd927639a959b0102f372f3742ecae6ef7afdb0668c2a229eb4297baf2ac5b2689932d4576f2809f89af01d3ae61d9af57ba921bdbd2f78f1ffee999269a2837ce3766d692943113555ae7648984f16fc6b5aa9057becd8d95d038ca95579fff5902497ccdf00064ad97fac6ebcab1bcd69139eec64dfcc9aa4d5b39e90abb2e3699105f60a3f8e13cfbdd583e37f3c63abbf009cfe5279fc84bb8db14ba61476ca929221a8d58c03e399471b660fd76c5decb3ebd456c54f3fcf4ec53125fb695f11bf93837016e231fdfd40ddcdb2d91cd679a4cca569bb5a6c8b72690a5cddb4267e22945b42532f1a2d2276e653d0798fc8d6c2b7a10e22b78859e66ac0e3da5b80a14613d3ce34b96a2671b20795bd68e390aab723ce14f615408543e1bdecc4c411aade12cf9ad034222afcdfc9a500a96472b248a313882bd80954f0a73ada59d7dabe73e8b30babde23b766a6c79482ec483070fc8f7b6f109362fc5e78cf51bbe391bcbf3b1bcf7762406dfdcfce1de3737a767f83dc7efbdc10e7db4c8703a121d93101154c2128911f2b043d3a80da912bccf6b85a19c81d80f07273083c4b788d10b71eb86b3436014e67a4ac2265fa8c2e99118464823215ee344c1d92d8242156bb5a1b482e8a1e496b8e4b5deac6d038266d9d2d1c8e4954628e88633563547e273aed514d485e20413222970db9ae370a6717460e8bf221fb1f8dccee968839abc6fb1d4445ae4862b8fbc55d2c72e9e1080fc0832cea81f3ffccbed51e9e6e3877f67f299df9ab82dbc81b5a2a3a3ff6da565e09543eccd6bb38da7141642951036b65dae90d2c14319a7d4f04b3e82067b61c2dc389271f93392f3ec8510b3762127e9753812a4a541d6928daa967a78767a3abaa07a02ba8c12b31eee065b2033a3d18865654eebebe1e9284511cc30e9bc9f2c330461d82912e5b46162e0eda0a2a97cf71d393d98f7b79da1a55dc879eb3cd40a5567536b216054327b81ea575be70c1528b8997822eb011372ee53dd68e6ab907314e556f63648b437a5ceba87ceac69e5c0ce0211b5688b696e504027f23b56e6c8e44717f26c1c5ee670a9d7b472b84165d7c3d1587c2f44ae173247facdf5347d1d223c8237a459c8d960da7d994e0764427c0f9fe9072f0070b80542aa4ed2de1c2937c5c2d1dbb13c3a7e7d5c1ee7af8e9f5e1c3fbf38beca8e17472396d668df362c3dc0d2d54fc0328e4b1ece1bad8f0f018cb610a3e0ef7635b990af9a568fe52060c33b0411ec05ef762bceef0f808a1039c911bb51db731210ea85c2019ceca93512d40030cd542d15cc3b39c863bd3dc6f1f04e57d65e4ff63c37daa605ce993d5264cda2985160a2d86af34e53d620817fe4fc13296b651ab7475f221593ad2955f56a6714032921b12d28339ebc53454b85116232f1980a2be70f8aff9f776a6257421851a779edee021597b87cf042886eb67b0c9f2ccbe41d21c46c19ff3de25a49f1401b928de5642207f9602ba01708c95dd0f715c887f7cfc781d7869c3e44d64a6c8d32e81c88ec71d3d86678f455758dd358b16617f2b83992c792f104a64e0d68cf8e19121a8e77b5ab4e4279ff7c17a44240f5541b428bd12e4146593b4265b9b729a9b62a8485a46d0fcf1630f9ef60dbb17c739651a9cdeea36920ba9896f7cf0db16f0f4cfc377074a9fcae5332ee2c35490fa31ea6c936d9d3f117e251fe8e4220dfd65658b2f28d45ffff8873b52abc6e2ad4f2771cae284e8d0a1d78578353418d1533cac2f4726dea314f1d53b6c2748547d05374f16aa9eab4166aaaa37986eb428935bfb64cc725a3f206f5238e62a14ed3b108b59b7671085542596052d0719c72a92d0d5e4b6709c430e9987a64b3ac2c771ec687bdd1b6f8b877c4945c4e9c555bce48634464980d1c95046e916a74adc97e4864ba19d378e8570a63869aaf1203d19359c9208c603771f067641d182eb5ebc9e73402c2a23557f2dcc0d498027802794cf28109389cee6053ea0a5d1491f1c4a7196a94c7218139954cccadb3daa6bd455bcd8913ade55d4d0d67973492e961ea2cc672a9fd144d229e6315aa487527bbe06bb23def0f4309dc6b8b025baf92fd0acd660127036890cf1ca4d1127eafc8921d2d9efb1b8687b80ffd4401931eda2178466267b72af4a7c13d34a224dba53884d50273c7c6cd636828fb2d5ffadc2122570eb7c4a1efdbc7874eafcafb5440cb6028a98a2f2dd645ec9c29cae3a01cbb34d4e882bcbc71e36efecaad7661b026d781af356e256f17415307e6a238839e85e3411fe265020d9714d07771a3da55aa84b11a45dd7e824061fdff37e73d501a32f919861ec3b5b3c220da87749e754f52e5396731c38520c80cbc69df467768c86d20e13d03e3cc841ac41303b69ae9b9a2ab876badebdbc6513e3bb1c784cbd9d28599217f91f8b572a153e0f8a4ca5dda86ce9eaaa28eb0c765db00922f9005d62b4ca17b372cd2b5351d38a8add71dbeae2e48a73dcf575b8fc66b271e89b96a3b06032c61a0496d0c1245b2d390ac00d1e99e81669ad7e1f645ce57740af98286340c9d088d72c032252c132a2169a62321a11ad0fedce5817e8221acf58b93dfe3df1e65aa1c77f7327794c637b381ab554973ec402f9783b7e34344a35b87e1ae3c266403c68566e0cd9e482152ab25c42b1e5ebbde6b1c26e2bd8e27d6bfd4ccfd0cb56e6ba166839bcdfbf79bc1e8a7f419897e1f32213e8acc49e2dd7a33064708de1842eca9d82584413e4ee694583876f742f2343bcf6e4854837e96b2c1c1f77b72a5d028385b62bb2a1d1c04778826b4c5cc971084c44a0179d705031d468e332e43d82093bd7b8aee82a223d7377561e606c78a15a062b65690b77386b3eda4d05506b606e38ecd782aa34842a16f75f245eb5f2c3ee7b2b93d4b57ded6cf525794edbe12fa2e1184d689eecb1aebeddc16d25654bed9da6a46f791bd8b14f1e41239a27705f4e412394b514258d325e9b2b16bce9b4561e79c86d27d17df08975a55aed7d5513310521a7e0bba157c72194c99a26339cf9096e9b619c6e02db6dc05ecdb355f99d8bbd011e22ff462822bb609eaefada61b214ee73054d0252465beb7228ec22c57dc6f516e2499bba076afab62a2dce26298bbb74ff1d8a98aaef27ae6e311b57fabcc0a203952a740beefdf849351d7e9266e119a3452012d4bcd85650f57a4df02732d3ab2642ff9a80a775aeb70afb74716d38749d3dec1349a891f5ccb8b3ffee2ac1b2b5326eb5d6c73d7e0a43c917c5548ff35f1f0e1ff00b5629026")) + assertThat<ExtendedEncoding>(extended, instanceOf(ExtendedEncoding::class.java)) + assertThat(extended!!.content, instanceOf<Any>(Message::class.java)) + assertThat((extended.content as Message).subject, `is`("Extended encoding on Windows works - but how ??")) + assertThat((extended.content as Message).body, notNullValue()) + assertThat((extended.content as Message).body.length, `is`(6233)) + } + + + @Test + fun `ensure simple message is encoded`() { + val `in` = Message.Builder() + .subject("Test sübject") + .body("test bödy") + .build() + + assertThat(`in`.zip(), notNullValue()) + + val out = ExtendedEncodingFactory.unzip(`in`.zip()) + assertThat<ExtendedEncoding>(out, `is`(`in`)) + } + + @Test + fun `ensure complete message is encoded and decoded`() { + val `in` = Message.Builder() + .addParent(TestUtils.randomInventoryVector()) + .addParent(TestUtils.randomInventoryVector()) + .subject("Test sübject") + .body("test bödy") + .addFile( + Attachment.Builder() + .name("test.txt") + .type("text/plain") + .data("test".toByteArray(charset("UTF-8"))) + .attachment() + .build() + ) + .build() + + assertThat(`in`.zip(), notNullValue()) + + val out = ExtendedEncodingFactory.unzip(`in`.zip()) + assertThat<ExtendedEncoding>(out, `is`(`in`)) + } + + @Test + fun `ensure vote is encoded and decoded`() { + val `in` = Vote.Builder() + .msgId(TestUtils.randomInventoryVector()) + .vote("+1") + .build() + + assertThat(`in`.zip(), notNullValue()) + + val out = ExtendedEncodingFactory.unzip(`in`.zip()) + assertThat<ExtendedEncoding>(out, `is`(`in`)) + } +} diff --git a/core/src/test/java/ch/dissem/bitmessage/entity/SerializationTest.java b/core/src/test/java/ch/dissem/bitmessage/entity/SerializationTest.java deleted file mode 100644 index 3e96b77..0000000 --- a/core/src/test/java/ch/dissem/bitmessage/entity/SerializationTest.java +++ /dev/null @@ -1,198 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.entity; - -import ch.dissem.bitmessage.entity.payload.*; -import ch.dissem.bitmessage.entity.valueobject.InventoryVector; -import ch.dissem.bitmessage.entity.valueobject.Label; -import ch.dissem.bitmessage.entity.valueobject.extended.Message; -import ch.dissem.bitmessage.factory.Factory; -import ch.dissem.bitmessage.utils.TestBase; -import ch.dissem.bitmessage.utils.TestUtils; -import org.junit.Test; - -import java.io.*; -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.Collections; - -import static ch.dissem.bitmessage.entity.Plaintext.Type.MSG; -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.*; - -public class SerializationTest extends TestBase { - @Test - public void ensureGetPubkeyIsDeserializedAndSerializedCorrectly() throws IOException { - doTest("V2GetPubkey.payload", 2, GetPubkey.class); - doTest("V3GetPubkey.payload", 2, GetPubkey.class); - doTest("V4GetPubkey.payload", 2, GetPubkey.class); - } - - @Test - public void ensureV2PubkeyIsDeserializedAndSerializedCorrectly() throws IOException { - doTest("V2Pubkey.payload", 2, V2Pubkey.class); - } - - @Test - public void ensureV3PubkeyIsDeserializedAndSerializedCorrectly() throws IOException { - doTest("V3Pubkey.payload", 3, V3Pubkey.class); - } - - @Test - public void ensureV4PubkeyIsDeserializedAndSerializedCorrectly() throws IOException { - doTest("V4Pubkey.payload", 4, V4Pubkey.class); - } - - @Test - public void ensureV1MsgIsDeserializedAndSerializedCorrectly() throws IOException { - doTest("V1Msg.payload", 1, Msg.class); - } - - @Test - public void ensureV4BroadcastIsDeserializedAndSerializedCorrectly() throws IOException { - doTest("V4Broadcast.payload", 4, V4Broadcast.class); - } - - @Test - public void ensureV5BroadcastIsDeserializedAndSerializedCorrectly() throws IOException { - doTest("V5Broadcast.payload", 5, V5Broadcast.class); - } - - @Test - public void ensureUnknownDataIsDeserializedAndSerializedCorrectly() throws IOException { - doTest("V1MsgStrangeData.payload", 1, GenericPayload.class); - } - - @Test - public void ensurePlaintextIsSerializedAndDeserializedCorrectly() throws Exception { - Plaintext expected = new Plaintext.Builder(MSG) - .from(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8")) - .to(TestUtils.loadContact()) - .message("Subject", "Message") - .ackData("ackMessage".getBytes()) - .signature(new byte[0]) - .build(); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - expected.write(out); - ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); - Plaintext actual = Plaintext.read(MSG, in); - - // Received is automatically set on deserialization, so we'll need to set it to null - Field received = Plaintext.class.getDeclaredField("received"); - received.setAccessible(true); - received.set(actual, null); - - assertThat(expected, is(actual)); - } - - @Test - public void ensurePlaintextWithExtendedEncodingIsSerializedAndDeserializedCorrectly() throws Exception { - Plaintext expected = new Plaintext.Builder(MSG) - .from(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8")) - .to(TestUtils.loadContact()) - .message(new Message.Builder() - .subject("Subject") - .body("Message") - .build()) - .ackData("ackMessage".getBytes()) - .signature(new byte[0]) - .build(); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - expected.write(out); - ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); - Plaintext actual = Plaintext.read(MSG, in); - - // Received is automatically set on deserialization, so we'll need to set it to null - Field received = Plaintext.class.getDeclaredField("received"); - received.setAccessible(true); - received.set(actual, null); - - assertEquals(expected, actual); - } - - @Test - public void ensurePlaintextWithAckMessageIsSerializedAndDeserializedCorrectly() throws Exception { - Plaintext expected = new Plaintext.Builder(MSG) - .from(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8")) - .to(TestUtils.loadContact()) - .message("Subject", "Message") - .ackData("ackMessage".getBytes()) - .signature(new byte[0]) - .build(); - ObjectMessage ackMessage1 = expected.getAckMessage(); - assertNotNull(ackMessage1); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - expected.write(out); - ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); - Plaintext actual = Plaintext.read(MSG, in); - - // Received is automatically set on deserialization, so we'll need to set it to null - Field received = Plaintext.class.getDeclaredField("received"); - received.setAccessible(true); - received.set(actual, null); - - assertEquals(expected, actual); - assertEquals(ackMessage1, actual.getAckMessage()); - } - - @Test - public void ensureNetworkMessageIsSerializedAndDeserializedCorrectly() throws Exception { - ArrayList<InventoryVector> ivs = new ArrayList<>(50000); - for (int i = 0; i < 50000; i++) { - ivs.add(TestUtils.randomInventoryVector()); - } - - Inv inv = new Inv.Builder().inventory(ivs).build(); - NetworkMessage before = new NetworkMessage(inv); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - before.write(out); - - NetworkMessage after = Factory.getNetworkMessage(3, new ByteArrayInputStream(out.toByteArray())); - assertNotNull(after); - Inv invAfter = (Inv) after.getPayload(); - assertEquals(ivs, invAfter.getInventory()); - } - - private void doTest(String resourceName, int version, Class<?> expectedPayloadType) throws IOException { - byte[] data = TestUtils.getBytes(resourceName); - InputStream in = new ByteArrayInputStream(data); - ObjectMessage object = Factory.getObjectMessage(version, in, data.length); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - assertNotNull(object); - object.write(out); - assertArrayEquals(data, out.toByteArray()); - assertEquals(expectedPayloadType.getCanonicalName(), object.getPayload().getClass().getCanonicalName()); - } - - @Test - public void ensureSystemSerializationWorks() throws Exception { - Plaintext plaintext = new Plaintext.Builder(MSG) - .from(TestUtils.loadContact()) - .to(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8")) - .labels(Collections.singletonList(new Label("Test", Label.Type.INBOX, 0))) - .message("Test", "Test Test.\nTest") - .build(); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - ObjectOutputStream oos = new ObjectOutputStream(out); - oos.writeObject(plaintext); - - ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); - ObjectInputStream ois = new ObjectInputStream(in); - assertEquals(plaintext, ois.readObject()); - } -} diff --git a/core/src/test/java/ch/dissem/bitmessage/entity/SerializationTest.kt b/core/src/test/java/ch/dissem/bitmessage/entity/SerializationTest.kt new file mode 100644 index 0000000..a9fbfea --- /dev/null +++ b/core/src/test/java/ch/dissem/bitmessage/entity/SerializationTest.kt @@ -0,0 +1,197 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.entity + +import ch.dissem.bitmessage.entity.Plaintext.Type.MSG +import ch.dissem.bitmessage.entity.payload.* +import ch.dissem.bitmessage.entity.valueobject.InventoryVector +import ch.dissem.bitmessage.entity.valueobject.Label +import ch.dissem.bitmessage.entity.valueobject.extended.Message +import ch.dissem.bitmessage.factory.Factory +import ch.dissem.bitmessage.utils.TestBase +import ch.dissem.bitmessage.utils.TestUtils +import org.hamcrest.Matchers.`is` +import org.junit.Assert.* +import org.junit.Test +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.io.ObjectInputStream +import java.io.ObjectOutputStream +import java.util.* + +class SerializationTest : TestBase() { + @Test + fun `ensure GetPubkey is deserialized and serialized correctly`() { + doTest("V2GetPubkey.payload", 2, GetPubkey::class.java) + doTest("V3GetPubkey.payload", 2, GetPubkey::class.java) + doTest("V4GetPubkey.payload", 2, GetPubkey::class.java) + } + + @Test + fun `ensure V2Pubkey is deserialized and serialized correctly`() { + doTest("V2Pubkey.payload", 2, V2Pubkey::class.java) + } + + @Test + fun `ensure V3Pubkey is deserialized and serialized correctly`() { + doTest("V3Pubkey.payload", 3, V3Pubkey::class.java) + } + + @Test + fun `ensure V4Pubkey is deserialized and serialized correctly`() { + doTest("V4Pubkey.payload", 4, V4Pubkey::class.java) + } + + @Test + fun `ensure V1 msg is deserialized and serialized correctly`() { + doTest("V1Msg.payload", 1, Msg::class.java) + } + + @Test + fun `ensure V4Broadcast is deserialized and serialized correctly`() { + doTest("V4Broadcast.payload", 4, V4Broadcast::class.java) + } + + @Test + fun `ensure V5Broadcast is deserialized and serialized correctly`() { + doTest("V5Broadcast.payload", 5, V5Broadcast::class.java) + } + + @Test + fun `ensure unknown data is deserialized and serialized correctly`() { + doTest("V1MsgStrangeData.payload", 1, GenericPayload::class.java) + } + + @Test + fun `ensure plaintext is serialized and deserialized correctly`() { + val expected = Plaintext.Builder(MSG) + .from(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8")) + .to(TestUtils.loadContact()) + .message("Subject", "Message") + .ackData("ackMessage".toByteArray()) + .signature(ByteArray(0)) + .build() + val out = ByteArrayOutputStream() + expected.write(out) + val `in` = ByteArrayInputStream(out.toByteArray()) + val actual = Plaintext.read(MSG, `in`) + + // Received is automatically set on deserialization, so we'll need to set it to null + val received = Plaintext::class.java.getDeclaredField("received") + received.isAccessible = true + received.set(actual, null) + + assertThat(expected, `is`(actual)) + } + + @Test + fun `ensure plaintext with extended encoding is serialized and deserialized correctly`() { + val expected = Plaintext.Builder(MSG) + .from(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8")) + .to(TestUtils.loadContact()) + .message(Message.Builder() + .subject("Subject") + .body("Message") + .build()) + .ackData("ackMessage".toByteArray()) + .signature(ByteArray(0)) + .build() + val out = ByteArrayOutputStream() + expected.write(out) + val `in` = ByteArrayInputStream(out.toByteArray()) + val actual = Plaintext.read(MSG, `in`) + + // Received is automatically set on deserialization, so we'll need to set it to null + val received = Plaintext::class.java.getDeclaredField("received") + received.isAccessible = true + received.set(actual, null) + + assertEquals(expected, actual) + } + + @Test + fun `ensure plaintext with ack message is serialized and deserialized correctly`() { + val expected = Plaintext.Builder(MSG) + .from(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8")) + .to(TestUtils.loadContact()) + .message("Subject", "Message") + .ackData("ackMessage".toByteArray()) + .signature(ByteArray(0)) + .build() + val ackMessage1 = expected.ackMessage + assertNotNull(ackMessage1) + + val out = ByteArrayOutputStream() + expected.write(out) + val `in` = ByteArrayInputStream(out.toByteArray()) + val actual = Plaintext.read(MSG, `in`) + + // Received is automatically set on deserialization, so we'll need to set it to null + val received = Plaintext::class.java.getDeclaredField("received") + received.isAccessible = true + received.set(actual, null) + + assertEquals(expected, actual) + assertEquals(ackMessage1, actual.ackMessage) + } + + @Test + fun `ensure network message is serialized and deserialized correctly`() { + val ivs = ArrayList<InventoryVector>(50000) + for (i in 0..49999) { + ivs.add(TestUtils.randomInventoryVector()) + } + + val inv = Inv(ivs) + val before = NetworkMessage(inv) + val out = ByteArrayOutputStream() + before.write(out) + + val after = Factory.getNetworkMessage(3, ByteArrayInputStream(out.toByteArray())) + assertNotNull(after) + val invAfter = after!!.payload as Inv + assertEquals(ivs, invAfter.inventory) + } + + private fun doTest(resourceName: String, version: Int, expectedPayloadType: Class<*>) { + val data = TestUtils.getBytes(resourceName) + val `in` = ByteArrayInputStream(data) + val `object` = Factory.getObjectMessage(version, `in`, data.size) + val out = ByteArrayOutputStream() + assertNotNull(`object`) + `object`!!.write(out) + assertArrayEquals(data, out.toByteArray()) + assertEquals(expectedPayloadType.canonicalName, `object`.payload.javaClass.canonicalName) + } + + @Test + fun `ensure system serialization works`() { + val plaintext = Plaintext.Builder(MSG) + .from(TestUtils.loadContact()) + .to(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8")) + .labels(listOf(Label("Test", Label.Type.INBOX, 0))) + .message("Test", "Test Test.\nTest") + .build() + val out = ByteArrayOutputStream() + val oos = ObjectOutputStream(out) + oos.writeObject(plaintext) + + val `in` = ByteArrayInputStream(out.toByteArray()) + val ois = ObjectInputStream(`in`) + assertEquals(plaintext, ois.readObject()) + } +} diff --git a/core/src/test/java/ch/dissem/bitmessage/ports/ProofOfWorkEngineTest.java b/core/src/test/java/ch/dissem/bitmessage/ports/ProofOfWorkEngineTest.java deleted file mode 100644 index c2efb1f..0000000 --- a/core/src/test/java/ch/dissem/bitmessage/ports/ProofOfWorkEngineTest.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.ports; - -import ch.dissem.bitmessage.utils.Bytes; -import ch.dissem.bitmessage.utils.CallbackWaiter; -import ch.dissem.bitmessage.utils.TestBase; -import org.junit.Test; - -import static ch.dissem.bitmessage.utils.Singleton.cryptography; -import static org.junit.Assert.assertTrue; - -public class ProofOfWorkEngineTest extends TestBase { - @Test(timeout = 90_000) - public void testSimplePOWEngine() throws InterruptedException { - testPOW(new SimplePOWEngine()); - } - - @Test(timeout = 90_000) - public void testThreadedPOWEngine() throws InterruptedException { - testPOW(new MultiThreadedPOWEngine()); - } - - private void testPOW(ProofOfWorkEngine engine) throws InterruptedException { - byte[] initialHash = cryptography().sha512(new byte[]{1, 3, 6, 4}); - byte[] target = {0, 0, 0, -1, -1, -1, -1, -1}; - - final CallbackWaiter<byte[]> waiter1 = new CallbackWaiter<>(); - engine.calculateNonce(initialHash, target, - new ProofOfWorkEngine.Callback() { - @Override - public void onNonceCalculated(byte[] initialHash, byte[] nonce) { - waiter1.setValue(nonce); - } - }); - byte[] nonce = waiter1.waitForValue(); - System.out.println("Calculating nonce took " + waiter1.getTime() + "ms"); - assertTrue(Bytes.lt(cryptography().doubleSha512(nonce, initialHash), target, 8)); - - // Let's add a second (shorter) run to find possible multi threading issues - byte[] initialHash2 = cryptography().sha512(new byte[]{1, 3, 6, 5}); - byte[] target2 = {0, 0, -1, -1, -1, -1, -1, -1}; - - final CallbackWaiter<byte[]> waiter2 = new CallbackWaiter<>(); - engine.calculateNonce(initialHash2, target2, - new ProofOfWorkEngine.Callback() { - @Override - public void onNonceCalculated(byte[] initialHash, byte[] nonce) { - waiter2.setValue(nonce); - } - }); - byte[] nonce2 = waiter2.waitForValue(); - System.out.println("Calculating nonce took " + waiter2.getTime() + "ms"); - assertTrue(Bytes.lt(cryptography().doubleSha512(nonce2, initialHash2), target2, 8)); - assertTrue("Second nonce must be quicker to find", waiter1.getTime() > waiter2.getTime()); - } - -} diff --git a/core/src/test/java/ch/dissem/bitmessage/ports/ProofOfWorkEngineTest.kt b/core/src/test/java/ch/dissem/bitmessage/ports/ProofOfWorkEngineTest.kt new file mode 100644 index 0000000..4ce5ec6 --- /dev/null +++ b/core/src/test/java/ch/dissem/bitmessage/ports/ProofOfWorkEngineTest.kt @@ -0,0 +1,68 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.ports + +import ch.dissem.bitmessage.utils.Bytes +import ch.dissem.bitmessage.utils.CallbackWaiter +import ch.dissem.bitmessage.utils.Singleton.cryptography +import ch.dissem.bitmessage.utils.TestBase +import org.junit.Assert.assertTrue +import org.junit.Test + +class ProofOfWorkEngineTest : TestBase() { + @Test(timeout = 90000) + fun `test SimplePOWEngine`() { + testPOW(SimplePOWEngine()) + } + + @Test(timeout = 90000) + fun `test MultiThreadedPOWEngine`() { + testPOW(MultiThreadedPOWEngine()) + } + + private fun testPOW(engine: ProofOfWorkEngine) { + val initialHash = cryptography().sha512(byteArrayOf(1, 3, 6, 4)) + val target = byteArrayOf(0, 0, 0, -1, -1, -1, -1, -1) + + val waiter1 = CallbackWaiter<ByteArray>() + engine.calculateNonce(initialHash, target, + object : ProofOfWorkEngine.Callback { + override fun onNonceCalculated(initialHash: ByteArray, nonce: ByteArray) { + waiter1.setValue(nonce) + } + }) + val nonce = waiter1.waitForValue()!! + println("Calculating nonce took " + waiter1.time + "ms") + assertTrue(Bytes.lt(cryptography().doubleSha512(nonce, initialHash), target, 8)) + + // Let's add a second (shorter) run to find possible multi threading issues + val initialHash2 = cryptography().sha512(byteArrayOf(1, 3, 6, 5)) + val target2 = byteArrayOf(0, 0, -1, -1, -1, -1, -1, -1) + + val waiter2 = CallbackWaiter<ByteArray>() + engine.calculateNonce(initialHash2, target2, + object : ProofOfWorkEngine.Callback { + override fun onNonceCalculated(initialHash: ByteArray, nonce: ByteArray) { + waiter2.setValue(nonce) + } + }) + val nonce2 = waiter2.waitForValue()!! + println("Calculating nonce took " + waiter2.time + "ms") + assertTrue(Bytes.lt(cryptography().doubleSha512(nonce2, initialHash2), target2, 8)) + assertTrue("Second nonce must be quicker to find", waiter1.time > waiter2.time) + } +} diff --git a/core/src/test/java/ch/dissem/bitmessage/testutils/TestInventory.java b/core/src/test/java/ch/dissem/bitmessage/testutils/TestInventory.java deleted file mode 100644 index 1f8cfd3..0000000 --- a/core/src/test/java/ch/dissem/bitmessage/testutils/TestInventory.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.testutils; - -import ch.dissem.bitmessage.entity.ObjectMessage; -import ch.dissem.bitmessage.entity.payload.ObjectType; -import ch.dissem.bitmessage.entity.valueobject.InventoryVector; -import ch.dissem.bitmessage.ports.Inventory; -import ch.dissem.bitmessage.utils.TestUtils; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class TestInventory implements Inventory { - private final Map<InventoryVector, ObjectMessage> inventory; - - public TestInventory() { - this.inventory = new HashMap<>(); - } - - @Override - public List<InventoryVector> getInventory(long... streams) { - return new ArrayList<>(inventory.keySet()); - } - - @Override - public List<InventoryVector> getMissing(List<InventoryVector> offer, long... streams) { - return offer; - } - - @Override - public ObjectMessage getObject(InventoryVector vector) { - return inventory.get(vector); - } - - @Override - public List<ObjectMessage> getObjects(long stream, long version, ObjectType... types) { - return new ArrayList<>(inventory.values()); - } - - @Override - public void storeObject(ObjectMessage object) { - inventory.put(object.getInventoryVector(), object); - } - - @Override - public boolean contains(ObjectMessage object) { - return inventory.containsKey(object.getInventoryVector()); - } - - @Override - public void cleanup() { - - } - - public void init(String... resources) throws IOException { - inventory.clear(); - for (String resource : resources) { - int version = Integer.parseInt(resource.substring(1, 2)); - ObjectMessage obj = TestUtils.loadObjectMessage(version, resource); - inventory.put(obj.getInventoryVector(), obj); - } - } -} diff --git a/core/src/test/java/ch/dissem/bitmessage/testutils/TestInventory.kt b/core/src/test/java/ch/dissem/bitmessage/testutils/TestInventory.kt new file mode 100644 index 0000000..f147151 --- /dev/null +++ b/core/src/test/java/ch/dissem/bitmessage/testutils/TestInventory.kt @@ -0,0 +1,65 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.testutils + +import ch.dissem.bitmessage.entity.ObjectMessage +import ch.dissem.bitmessage.entity.payload.ObjectType +import ch.dissem.bitmessage.entity.valueobject.InventoryVector +import ch.dissem.bitmessage.ports.Inventory +import ch.dissem.bitmessage.utils.TestUtils +import java.util.* + +class TestInventory : Inventory { + private val inventory = HashMap<InventoryVector, ObjectMessage>() + + override fun getInventory(vararg streams: Long): List<InventoryVector> { + return ArrayList(inventory.keys) + } + + override fun getMissing(offer: List<InventoryVector>, vararg streams: Long): List<InventoryVector> { + return offer + } + + override fun getObject(vector: InventoryVector): ObjectMessage? { + return inventory[vector] + } + + override fun getObjects(stream: Long, version: Long, vararg types: ObjectType): List<ObjectMessage> { + return ArrayList(inventory.values) + } + + override fun storeObject(`object`: ObjectMessage) { + inventory.put(`object`.inventoryVector, `object`) + } + + override fun contains(`object`: ObjectMessage): Boolean { + return inventory.containsKey(`object`.inventoryVector) + } + + override fun cleanup() { + + } + + fun init(vararg resources: String) { + inventory.clear() + for (resource in resources) { + val version = Integer.parseInt(resource.substring(1, 2)) + val obj = TestUtils.loadObjectMessage(version, resource) + inventory.put(obj.inventoryVector, obj) + } + } +} diff --git a/core/src/test/java/ch/dissem/bitmessage/utils/BytesTest.java b/core/src/test/java/ch/dissem/bitmessage/utils/BytesTest.java deleted file mode 100644 index 1af8d37..0000000 --- a/core/src/test/java/ch/dissem/bitmessage/utils/BytesTest.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.utils; - -import org.junit.Ignore; -import org.junit.Test; - -import java.io.IOException; -import java.math.BigInteger; -import java.util.Random; - -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; - -public class BytesTest { - public static final Random rnd = new Random(); - - @Test - public void ensureExpandsCorrectly() { - byte[] source = {1}; - byte[] expected = {0, 1}; - assertArrayEquals(expected, Bytes.expand(source, 2)); - } - - @Test - public void ensureIncrementCarryWorks() throws IOException { - byte[] bytes = {0, -1}; - Bytes.inc(bytes); - assertArrayEquals(TestUtils.int16(256), bytes); - } - - @Test - public void testIncrementByValue() throws IOException { - for (int v = 0; v < 256; v++) { - for (int i = 1; i < 256; i++) { - byte[] bytes = {0, (byte) v}; - Bytes.inc(bytes, (byte) i); - assertArrayEquals("value = " + v + "; inc = " + i + "; expected = " + (v + i), TestUtils.int16(v + i), bytes); - } - } - } - - /** - * This test is used to compare different implementations of the single byte lt comparison. It an safely be ignored. - */ - @Test - @Ignore - public void testLowerThanSingleByte() { - byte[] a = new byte[1]; - byte[] b = new byte[1]; - for (int i = 0; i < 255; i++) { - for (int j = 0; j < 255; j++) { - System.out.println("a = " + i + "\tb = " + j); - a[0] = (byte) i; - b[0] = (byte) j; - assertEquals(i < j, Bytes.lt(a, b)); - } - } - } - - @Test - public void testLowerThan() { - for (int i = 0; i < 1000; i++) { - BigInteger a = BigInteger.valueOf(rnd.nextLong()).pow((rnd.nextInt(5) + 1)).abs(); - BigInteger b = BigInteger.valueOf(rnd.nextLong()).pow((rnd.nextInt(5) + 1)).abs(); - System.out.println("a = " + a.toString(16) + "\tb = " + b.toString(16)); - assertEquals(a.compareTo(b) == -1, Bytes.lt(a.toByteArray(), b.toByteArray())); - } - } - - @Test - public void testLowerThanBounded() { - for (int i = 0; i < 1000; i++) { - BigInteger a = BigInteger.valueOf(rnd.nextLong()).pow((rnd.nextInt(5) + 1)).abs(); - BigInteger b = BigInteger.valueOf(rnd.nextLong()).pow((rnd.nextInt(5) + 1)).abs(); - System.out.println("a = " + a.toString(16) + "\tb = " + b.toString(16)); - assertEquals(a.compareTo(b) == -1, Bytes.lt( - Bytes.expand(a.toByteArray(), 100), - Bytes.expand(b.toByteArray(), 100), - 100)); - } - } -} diff --git a/core/src/test/java/ch/dissem/bitmessage/utils/BytesTest.kt b/core/src/test/java/ch/dissem/bitmessage/utils/BytesTest.kt new file mode 100644 index 0000000..cabd0b9 --- /dev/null +++ b/core/src/test/java/ch/dissem/bitmessage/utils/BytesTest.kt @@ -0,0 +1,94 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.utils + +import org.junit.Assert.assertArrayEquals +import org.junit.Assert.assertEquals +import org.junit.Ignore +import org.junit.Test +import java.math.BigInteger +import java.util.* + +class BytesTest { + val rnd = Random() + + @Test + fun `ensure expands correctly`() { + val source = byteArrayOf(1) + val expected = byteArrayOf(0, 1) + assertArrayEquals(expected, Bytes.expand(source, 2)) + } + + @Test + fun `ensure increment carry works`() { + val bytes = byteArrayOf(0, -1) + Bytes.inc(bytes) + assertArrayEquals(TestUtils.int16(256), bytes) + } + + @Test + fun `test increment by value`() { + for (v in 0..255) { + for (i in 1..255) { + val bytes = byteArrayOf(0, v.toByte()) + Bytes.inc(bytes, i.toByte()) + assertArrayEquals("value = " + v + "; inc = " + i + "; expected = " + (v + i), TestUtils.int16(v + i), bytes) + } + } + } + + /** + * This test is used to compare different implementations of the single byte lt comparison. It an safely be ignored. + */ + @Test + @Ignore + fun `test lower than single byte`() { + val a = ByteArray(1) + val b = ByteArray(1) + for (i in 0..254) { + for (j in 0..254) { + println("a = $i\tb = $j") + a[0] = i.toByte() + b[0] = j.toByte() + assertEquals(i < j, Bytes.lt(a, b)) + } + } + } + + @Test + fun `test lower than`() { + for (i in 0..999) { + val a = BigInteger.valueOf(rnd.nextLong()).pow(rnd.nextInt(5) + 1).abs() + val b = BigInteger.valueOf(rnd.nextLong()).pow(rnd.nextInt(5) + 1).abs() + println("a = " + a.toString(16) + "\tb = " + b.toString(16)) + assertEquals(a.compareTo(b) == -1, Bytes.lt(a.toByteArray(), b.toByteArray())) + } + } + + @Test + fun `test lower than bounded`() { + for (i in 0..999) { + val a = BigInteger.valueOf(rnd.nextLong()).pow(rnd.nextInt(5) + 1).abs() + val b = BigInteger.valueOf(rnd.nextLong()).pow(rnd.nextInt(5) + 1).abs() + println("a = " + a.toString(16) + "\tb = " + b.toString(16)) + assertEquals(a.compareTo(b) == -1, Bytes.lt( + Bytes.expand(a.toByteArray(), 100), + Bytes.expand(b.toByteArray(), 100), + 100)) + } + } +} diff --git a/core/src/test/java/ch/dissem/bitmessage/utils/CollectionsTest.java b/core/src/test/java/ch/dissem/bitmessage/utils/CollectionsTest.java deleted file mode 100644 index 91e42b5..0000000 --- a/core/src/test/java/ch/dissem/bitmessage/utils/CollectionsTest.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.utils; - -import org.junit.Test; - -import java.util.LinkedList; -import java.util.List; - -import static org.junit.Assert.assertEquals; - -public class CollectionsTest { - @Test - public void ensureSelectRandomReturnsMaximumPossibleItems() throws Exception { - List<Integer> list = new LinkedList<>(); - for (int i = 0; i < 10; i++) { - list.add(i); - } - assertEquals(9, Collections.selectRandom(9, list).size()); - } -} \ No newline at end of file diff --git a/core/src/test/java/ch/dissem/bitmessage/utils/StringsTest.java b/core/src/test/java/ch/dissem/bitmessage/utils/CollectionsTest.kt similarity index 60% rename from core/src/test/java/ch/dissem/bitmessage/utils/StringsTest.java rename to core/src/test/java/ch/dissem/bitmessage/utils/CollectionsTest.kt index ef54165..dcfad1f 100644 --- a/core/src/test/java/ch/dissem/bitmessage/utils/StringsTest.java +++ b/core/src/test/java/ch/dissem/bitmessage/utils/CollectionsTest.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015 Christian Basler + * Copyright 2017 Christian Basler * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,16 +14,19 @@ * limitations under the License. */ -package ch.dissem.bitmessage.utils; +package ch.dissem.bitmessage.utils -import org.junit.Test; +import org.junit.Test -import static org.junit.Assert.assertEquals; +import java.util.LinkedList -public class StringsTest { +import org.junit.Assert.assertEquals + +class CollectionsTest { @Test - public void testHexString() { - assertEquals("48656c6c6f21", Strings.hex("Hello!".getBytes()).toString()); - assertEquals("0001", Strings.hex(new byte[]{0, 1}).toString()); + fun `ensure select random returns maximum possible items`() { + val list = LinkedList<Int>() + list += 0..9 + assertEquals(9, Collections.selectRandom(9, list).size.toLong()) } } diff --git a/core/src/test/java/ch/dissem/bitmessage/utils/ConversationServiceTest.java b/core/src/test/java/ch/dissem/bitmessage/utils/ConversationServiceTest.java deleted file mode 100644 index 6543232..0000000 --- a/core/src/test/java/ch/dissem/bitmessage/utils/ConversationServiceTest.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright 2017 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.utils; - -import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography; -import ch.dissem.bitmessage.entity.BitmessageAddress; -import ch.dissem.bitmessage.entity.Plaintext; -import ch.dissem.bitmessage.entity.valueobject.ExtendedEncoding; -import ch.dissem.bitmessage.entity.valueobject.extended.Message; -import ch.dissem.bitmessage.ports.MessageRepository; -import org.junit.Test; - -import java.util.LinkedList; -import java.util.List; -import java.util.UUID; - -import static ch.dissem.bitmessage.entity.Plaintext.Type.MSG; -import static ch.dissem.bitmessage.utils.TestUtils.RANDOM; -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class ConversationServiceTest { - private BitmessageAddress alice = new BitmessageAddress("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"); - private BitmessageAddress bob = new BitmessageAddress("BM-2cTtkBnb4BUYDndTKun6D9PjtueP2h1bQj"); - - private MessageRepository messageRepository = mock(MessageRepository.class); - private ConversationService conversationService = new ConversationService(messageRepository); - - static { - Singleton.initialize(new BouncyCryptography()); - } - - @Test - public void ensureConversationIsSortedProperly() { - List<Plaintext> expected = getConversation(); - - when(conversationService.getConversation(any(UUID.class))).thenReturn(expected); - List<Plaintext> actual = conversationService.getConversation(UUID.randomUUID()); - assertThat(actual, is(expected)); - } - - private List<Plaintext> getConversation() { - List<Plaintext> result = new LinkedList<>(); - - Plaintext older = plaintext(alice, bob, - new Message.Builder() - .subject("hey there") - .body("does it work?") - .build(), - Plaintext.Status.SENT); - result.add(older); - - Plaintext root = plaintext(alice, bob, - new Message.Builder() - .subject("new test") - .body("There's a new test in town!") - .build(), - Plaintext.Status.SENT); - result.add(root); - - result.add( - plaintext(bob, alice, - new Message.Builder() - .subject("Re: new test (1a)") - .body("Nice!") - .addParent(root) - .build(), - Plaintext.Status.RECEIVED) - ); - - Plaintext latest = plaintext(bob, alice, - new Message.Builder() - .subject("Re: new test (2b)") - .body("PS: it did work!") - .addParent(root) - .addParent(older) - .build(), - Plaintext.Status.RECEIVED); - result.add(latest); - - result.add( - plaintext(alice, bob, - new Message.Builder() - .subject("Re: new test (2)") - .body("") - .addParent(latest) - .build(), - Plaintext.Status.DRAFT) - ); - - return result; - } - - private int timer = 2; - - private Plaintext plaintext(BitmessageAddress from, BitmessageAddress to, - ExtendedEncoding content, Plaintext.Status status) { - Plaintext.Builder builder = new Plaintext.Builder(MSG) - .IV(TestUtils.randomInventoryVector()) - .from(from) - .to(to) - .message(content) - .status(status); - if (status != Plaintext.Status.DRAFT && status != Plaintext.Status.DOING_PROOF_OF_WORK) { - builder.received(5L * ++timer - RANDOM.nextInt(10)); - } - return builder.build(); - } -} diff --git a/core/src/test/java/ch/dissem/bitmessage/utils/ConversationServiceTest.kt b/core/src/test/java/ch/dissem/bitmessage/utils/ConversationServiceTest.kt new file mode 100644 index 0000000..97e1a2d --- /dev/null +++ b/core/src/test/java/ch/dissem/bitmessage/utils/ConversationServiceTest.kt @@ -0,0 +1,124 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.utils + +import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography +import ch.dissem.bitmessage.entity.BitmessageAddress +import ch.dissem.bitmessage.entity.Plaintext +import ch.dissem.bitmessage.entity.Plaintext.Type.MSG +import ch.dissem.bitmessage.entity.valueobject.ExtendedEncoding +import ch.dissem.bitmessage.entity.valueobject.extended.Message +import ch.dissem.bitmessage.ports.MessageRepository +import ch.dissem.bitmessage.utils.TestUtils.RANDOM +import com.nhaarman.mockito_kotlin.* +import org.hamcrest.Matchers.`is` +import org.junit.Assert.assertThat +import org.junit.Test +import java.util.* + +class ConversationServiceTest { + private val alice = BitmessageAddress("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8") + private val bob = BitmessageAddress("BM-2cTtkBnb4BUYDndTKun6D9PjtueP2h1bQj") + + private val messageRepository = mock<MessageRepository>() + private val conversationService = spy(ConversationService(messageRepository)) + + companion object { + init { + Singleton.initialize(BouncyCryptography()) + } + } + + @Test + fun `ensure conversation is sorted properly`() { + MockitoKotlin.registerInstanceCreator { UUID.randomUUID() } + val expected = conversation + + doReturn(expected).whenever(conversationService).getConversation(any<UUID>()) + val actual = conversationService.getConversation(UUID.randomUUID()) + assertThat(actual, `is`(expected)) + } + + private val conversation: List<Plaintext> + get() { + val result = LinkedList<Plaintext>() + + val older = plaintext(alice, bob, + Message.Builder() + .subject("hey there") + .body("does it work?") + .build(), + Plaintext.Status.SENT) + result.add(older) + + val root = plaintext(alice, bob, + Message.Builder() + .subject("new test") + .body("There's a new test in town!") + .build(), + Plaintext.Status.SENT) + result.add(root) + + result.add( + plaintext(bob, alice, + Message.Builder() + .subject("Re: new test (1a)") + .body("Nice!") + .addParent(root) + .build(), + Plaintext.Status.RECEIVED) + ) + + val latest = plaintext(bob, alice, + Message.Builder() + .subject("Re: new test (2b)") + .body("PS: it did work!") + .addParent(root) + .addParent(older) + .build(), + Plaintext.Status.RECEIVED) + result.add(latest) + + result.add( + plaintext(alice, bob, + Message.Builder() + .subject("Re: new test (2)") + .body("") + .addParent(latest) + .build(), + Plaintext.Status.DRAFT) + ) + + return result + } + + private var timer = 2 + + private fun plaintext(from: BitmessageAddress, to: BitmessageAddress, + content: ExtendedEncoding, status: Plaintext.Status): Plaintext { + val builder = Plaintext.Builder(MSG) + .IV(TestUtils.randomInventoryVector()) + .from(from) + .to(to) + .message(content) + .status(status) + if (status !== Plaintext.Status.DRAFT && status !== Plaintext.Status.DOING_PROOF_OF_WORK) { + builder.received(5L * ++timer - RANDOM.nextInt(10)) + } + return builder.build() + } +} diff --git a/core/src/test/java/ch/dissem/bitmessage/utils/DecodeTest.java b/core/src/test/java/ch/dissem/bitmessage/utils/DecodeTest.kt similarity index 54% rename from core/src/test/java/ch/dissem/bitmessage/utils/DecodeTest.java rename to core/src/test/java/ch/dissem/bitmessage/utils/DecodeTest.kt index 60d882f..f1cf244 100644 --- a/core/src/test/java/ch/dissem/bitmessage/utils/DecodeTest.java +++ b/core/src/test/java/ch/dissem/bitmessage/utils/DecodeTest.kt @@ -1,5 +1,5 @@ /* - * Copyright 2015 Christian Basler + * Copyright 2017 Christian Basler * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,27 +14,28 @@ * limitations under the License. */ -package ch.dissem.bitmessage.utils; +package ch.dissem.bitmessage.utils -import org.junit.Test; +import org.junit.Assert.assertEquals +import org.junit.Test +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream -import java.io.*; - -import static org.junit.Assert.assertEquals; - -public class DecodeTest { +class DecodeTest { @Test - public void ensureDecodingWorks() throws Exception { + fun `ensure decoding works`() { // This should test all relevant cases for var_int and therefore also uint_16, uint_32 and int_64 - testCodec(0); - for (long i = 1; i > 0; i = 3 * i + 7) { - testCodec(i); + testCodec(0) + var i: Long = 1 + while (i > 0) { + testCodec(i) + i = 3 * i + 7 } } - private void testCodec(long number) throws IOException { - ByteArrayOutputStream is = new ByteArrayOutputStream(); - Encode.varInt(number, is); - assertEquals(number, Decode.varInt(new ByteArrayInputStream(is.toByteArray()))); + private fun testCodec(number: Long) { + val `is` = ByteArrayOutputStream() + Encode.varInt(number, `is`) + assertEquals(number, Decode.varInt(ByteArrayInputStream(`is`.toByteArray()))) } } diff --git a/core/src/test/java/ch/dissem/bitmessage/utils/EncodeTest.java b/core/src/test/java/ch/dissem/bitmessage/utils/EncodeTest.java deleted file mode 100644 index 23d602c..0000000 --- a/core/src/test/java/ch/dissem/bitmessage/utils/EncodeTest.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.utils; - -import org.junit.Test; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; - -import static org.junit.Assert.assertEquals; - -public class EncodeTest { - @Test - public void testUint8() throws IOException { - ByteArrayOutputStream stream = new ByteArrayOutputStream(); - Encode.int8(0, stream); - checkBytes(stream, 0); - - stream = new ByteArrayOutputStream(); - Encode.int8(255, stream); - checkBytes(stream, 255); - } - - @Test - public void testUint16() throws IOException { - ByteArrayOutputStream stream = new ByteArrayOutputStream(); - Encode.int16(0, stream); - checkBytes(stream, 0, 0); - - stream = new ByteArrayOutputStream(); - Encode.int16(513, stream); - checkBytes(stream, 2, 1); - } - - @Test - public void testUint32() throws IOException { - ByteArrayOutputStream stream = new ByteArrayOutputStream(); - Encode.int32(0, stream); - checkBytes(stream, 0, 0, 0, 0); - - stream = new ByteArrayOutputStream(); - Encode.int32(67305985, stream); - checkBytes(stream, 4, 3, 2, 1); - - stream = new ByteArrayOutputStream(); - Encode.int32(3355443201L, stream); - checkBytes(stream, 200, 0, 0, 1); - } - - @Test - public void testUint64() throws IOException { - ByteArrayOutputStream stream = new ByteArrayOutputStream(); - Encode.int64(0, stream); - checkBytes(stream, 0, 0, 0, 0, 0, 0, 0, 0); - - stream = new ByteArrayOutputStream(); - Encode.int64(578437695752307201L, stream); - checkBytes(stream, 8, 7, 6, 5, 4, 3, 2, 1); - - stream = new ByteArrayOutputStream(); - // 200 * 72057594037927936L + 1 - Encode.int64(0xc800000000000001L, stream); - checkBytes(stream, 200, 0, 0, 0, 0, 0, 0, 1); - } - - @Test - public void testVarInt() throws IOException { - ByteArrayOutputStream stream = new ByteArrayOutputStream(); - Encode.varInt(0, stream); - checkBytes(stream, 0); - - stream = new ByteArrayOutputStream(); - Encode.varInt(252, stream); - checkBytes(stream, 252); - - stream = new ByteArrayOutputStream(); - Encode.varInt(253, stream); - checkBytes(stream, 253, 0, 253); - - stream = new ByteArrayOutputStream(); - Encode.varInt(65535, stream); - checkBytes(stream, 253, 255, 255); - - stream = new ByteArrayOutputStream(); - Encode.varInt(65536, stream); - checkBytes(stream, 254, 0, 1, 0, 0); - - stream = new ByteArrayOutputStream(); - Encode.varInt(4294967295L, stream); - checkBytes(stream, 254, 255, 255, 255, 255); - - stream = new ByteArrayOutputStream(); - Encode.varInt(4294967296L, stream); - checkBytes(stream, 255, 0, 0, 0, 1, 0, 0, 0, 0); - - stream = new ByteArrayOutputStream(); - Encode.varInt(-1L, stream); - checkBytes(stream, 255, 255, 255, 255, 255, 255, 255, 255, 255); - } - - - public void checkBytes(ByteArrayOutputStream stream, int... bytes) { - assertEquals(bytes.length, stream.size()); - byte[] streamBytes = stream.toByteArray(); - - for (int i = 0; i < bytes.length; i++) { - assertEquals((byte) bytes[i], streamBytes[i]); - } - } -} diff --git a/core/src/test/java/ch/dissem/bitmessage/utils/EncodeTest.kt b/core/src/test/java/ch/dissem/bitmessage/utils/EncodeTest.kt new file mode 100644 index 0000000..9027162 --- /dev/null +++ b/core/src/test/java/ch/dissem/bitmessage/utils/EncodeTest.kt @@ -0,0 +1,121 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.utils + +import org.junit.Assert.assertEquals +import org.junit.Test +import java.io.ByteArrayOutputStream + +class EncodeTest { + @Test + fun `test uint8`() { + var stream = ByteArrayOutputStream() + Encode.int8(0, stream) + checkBytes(stream, 0) + + stream = ByteArrayOutputStream() + Encode.int8(255, stream) + checkBytes(stream, 255) + } + + @Test + fun `test uint16`() { + var stream = ByteArrayOutputStream() + Encode.int16(0, stream) + checkBytes(stream, 0, 0) + + stream = ByteArrayOutputStream() + Encode.int16(513, stream) + checkBytes(stream, 2, 1) + } + + @Test + fun `test uint32`() { + var stream = ByteArrayOutputStream() + Encode.int32(0, stream) + checkBytes(stream, 0, 0, 0, 0) + + stream = ByteArrayOutputStream() + Encode.int32(67305985, stream) + checkBytes(stream, 4, 3, 2, 1) + + stream = ByteArrayOutputStream() + Encode.int32(3355443201L, stream) + checkBytes(stream, 200, 0, 0, 1) + } + + @Test + fun `test uint64`() { + var stream = ByteArrayOutputStream() + Encode.int64(0, stream) + checkBytes(stream, 0, 0, 0, 0, 0, 0, 0, 0) + + stream = ByteArrayOutputStream() + Encode.int64(578437695752307201L, stream) + checkBytes(stream, 8, 7, 6, 5, 4, 3, 2, 1) + + stream = ByteArrayOutputStream() + @Suppress("INTEGER_OVERFLOW") // 0xc800000000000001L + Encode.int64(200 * 72057594037927936L + 1, stream) + checkBytes(stream, 200, 0, 0, 0, 0, 0, 0, 1) + } + + @Test + fun `test varInt`() { + var stream = ByteArrayOutputStream() + Encode.varInt(0, stream) + checkBytes(stream, 0) + + stream = ByteArrayOutputStream() + Encode.varInt(252, stream) + checkBytes(stream, 252) + + stream = ByteArrayOutputStream() + Encode.varInt(253, stream) + checkBytes(stream, 253, 0, 253) + + stream = ByteArrayOutputStream() + Encode.varInt(65535, stream) + checkBytes(stream, 253, 255, 255) + + stream = ByteArrayOutputStream() + Encode.varInt(65536, stream) + checkBytes(stream, 254, 0, 1, 0, 0) + + stream = ByteArrayOutputStream() + Encode.varInt(4294967295L, stream) + checkBytes(stream, 254, 255, 255, 255, 255) + + stream = ByteArrayOutputStream() + Encode.varInt(4294967296L, stream) + checkBytes(stream, 255, 0, 0, 0, 1, 0, 0, 0, 0) + + stream = ByteArrayOutputStream() + Encode.varInt(-1L, stream) + checkBytes(stream, 255, 255, 255, 255, 255, 255, 255, 255, 255) + } + + + fun checkBytes(stream: ByteArrayOutputStream, vararg bytes: Int) { + assertEquals(bytes.size.toLong(), stream.size().toLong()) + val streamBytes = stream.toByteArray() + + for (i in bytes.indices) { + assertEquals(bytes[i].toByte().toLong(), streamBytes[i].toLong()) + } + } +} diff --git a/core/src/test/java/ch/dissem/bitmessage/utils/MessageMatchers.java b/core/src/test/java/ch/dissem/bitmessage/utils/MessageMatchers.java deleted file mode 100644 index 5be73ff..0000000 --- a/core/src/test/java/ch/dissem/bitmessage/utils/MessageMatchers.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2016 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.utils; - -import ch.dissem.bitmessage.entity.ObjectMessage; -import ch.dissem.bitmessage.entity.Plaintext; -import ch.dissem.bitmessage.entity.payload.ObjectType; -import org.mockito.ArgumentMatcher; -import org.mockito.ArgumentMatchers; - -/** - * @author Christian Basler - */ -public class MessageMatchers { - public static Plaintext plaintext(final Plaintext.Type type) { - return ArgumentMatchers.argThat(new ArgumentMatcher<Plaintext>() { - @Override - public boolean matches(Plaintext item) { - return item != null && item.getType() == type; - } - }); - } - - public static ObjectMessage object(final ObjectType type) { - return ArgumentMatchers.argThat(new ArgumentMatcher<ObjectMessage>() { - @Override - public boolean matches(ObjectMessage item) { - return item != null && item.getPayload().getType() == type; - } - }); - } -} diff --git a/core/src/test/java/ch/dissem/bitmessage/utils/SqlStringsTest.java b/core/src/test/java/ch/dissem/bitmessage/utils/SqlStringsTest.kt similarity index 65% rename from core/src/test/java/ch/dissem/bitmessage/utils/SqlStringsTest.java rename to core/src/test/java/ch/dissem/bitmessage/utils/SqlStringsTest.kt index 2aa1113..1097e01 100644 --- a/core/src/test/java/ch/dissem/bitmessage/utils/SqlStringsTest.java +++ b/core/src/test/java/ch/dissem/bitmessage/utils/SqlStringsTest.kt @@ -1,5 +1,5 @@ /* - * Copyright 2016 Christian Basler + * Copyright 2017 Christian Basler * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,16 +14,16 @@ * limitations under the License. */ -package ch.dissem.bitmessage.utils; +package ch.dissem.bitmessage.utils -import org.junit.Test; +import org.junit.Test -import static org.junit.Assert.assertEquals; +import org.junit.Assert.assertEquals -public class SqlStringsTest { +class SqlStringsTest { @Test - public void ensureJoinWorksWithLongArray() { - long[] test = {1L, 2L}; - assertEquals("1, 2", SqlStrings.join(test).toString()); + fun `ensure join works with long array`() { + val test = longArrayOf(1L, 2L) + assertEquals("1, 2", SqlStrings.join(*test)) } } diff --git a/core/src/test/java/ch/dissem/bitmessage/utils/StringsTest.kt b/core/src/test/java/ch/dissem/bitmessage/utils/StringsTest.kt new file mode 100644 index 0000000..815d8e8 --- /dev/null +++ b/core/src/test/java/ch/dissem/bitmessage/utils/StringsTest.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.utils + +import org.junit.Test + +import org.junit.Assert.assertEquals + +class StringsTest { + @Test + fun `test hex string`() { + assertEquals("48656c6c6f21", Strings.hex("Hello!".toByteArray())) + assertEquals("0001", Strings.hex(byteArrayOf(0, 1))) + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/exception/ApplicationException.java b/core/src/test/java/ch/dissem/bitmessage/utils/TestBase.kt similarity index 62% rename from core/src/main/java/ch/dissem/bitmessage/exception/ApplicationException.java rename to core/src/test/java/ch/dissem/bitmessage/utils/TestBase.kt index e688cb1..90e2b2d 100644 --- a/core/src/main/java/ch/dissem/bitmessage/exception/ApplicationException.java +++ b/core/src/test/java/ch/dissem/bitmessage/utils/TestBase.kt @@ -1,5 +1,5 @@ /* - * Copyright 2016 Christian Basler + * Copyright 2017 Christian Basler * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,19 +14,19 @@ * limitations under the License. */ -package ch.dissem.bitmessage.exception; +package ch.dissem.bitmessage.utils + +import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography +import org.junit.BeforeClass /** * @author Christian Basler */ -public class ApplicationException extends RuntimeException { - private static final long serialVersionUID = 1796776684126759324L; - - public ApplicationException(Throwable cause) { - super(cause); - } - - public ApplicationException(String message) { - super(message); +open class TestBase { + companion object { + @BeforeClass + @JvmStatic fun setUpClass() { + Singleton.initialize(BouncyCryptography()) + } } } diff --git a/core/src/test/java/ch/dissem/bitmessage/utils/TestUtils.java b/core/src/test/java/ch/dissem/bitmessage/utils/TestUtils.java deleted file mode 100644 index 6fadce4..0000000 --- a/core/src/test/java/ch/dissem/bitmessage/utils/TestUtils.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.utils; - -import ch.dissem.bitmessage.entity.BitmessageAddress; -import ch.dissem.bitmessage.entity.ObjectMessage; -import ch.dissem.bitmessage.entity.payload.Pubkey; -import ch.dissem.bitmessage.entity.payload.V4Pubkey; -import ch.dissem.bitmessage.entity.valueobject.InventoryVector; -import ch.dissem.bitmessage.entity.valueobject.PrivateKey; -import ch.dissem.bitmessage.exception.DecryptionFailedException; -import ch.dissem.bitmessage.factory.Factory; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.Random; - -import static org.junit.Assert.assertEquals; - -/** - * If there's ever a need for this in production code, it should be rewritten to be more efficient. - */ -public class TestUtils { - public static final Random RANDOM = new Random(); - - public static byte[] int16(int number) throws IOException { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - Encode.int16(number, out); - return out.toByteArray(); - } - - public static ObjectMessage loadObjectMessage(int version, String resourceName) throws IOException { - byte[] data = getBytes(resourceName); - InputStream in = new ByteArrayInputStream(data); - return Factory.getObjectMessage(version, in, data.length); - } - - public static byte[] getBytes(String resourceName) throws IOException { - InputStream in = TestUtils.class.getClassLoader().getResourceAsStream(resourceName); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - byte[] buffer = new byte[1024]; - int len = in.read(buffer); - while (len != -1) { - out.write(buffer, 0, len); - len = in.read(buffer); - } - return out.toByteArray(); - } - - public static InventoryVector randomInventoryVector() { - byte[] bytes = new byte[32]; - RANDOM.nextBytes(bytes); - return InventoryVector.fromHash(bytes); - } - - public static InputStream getResource(String resourceName) { - return TestUtils.class.getClassLoader().getResourceAsStream(resourceName); - } - - public static BitmessageAddress loadIdentity(String address) throws IOException { - PrivateKey privateKey = PrivateKey.read(TestUtils.getResource(address + ".privkey")); - BitmessageAddress identity = new BitmessageAddress(privateKey); - assertEquals(address, identity.getAddress()); - return identity; - } - - public static BitmessageAddress loadContact() throws IOException, DecryptionFailedException { - BitmessageAddress address = new BitmessageAddress("BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h"); - ObjectMessage object = TestUtils.loadObjectMessage(3, "V4Pubkey.payload"); - object.decrypt(address.getPublicDecryptionKey()); - address.setPubkey((V4Pubkey) object.getPayload()); - return address; - } - - public static void loadPubkey(BitmessageAddress address) throws IOException { - byte[] bytes = getBytes(address.getAddress() + ".pubkey"); - Pubkey pubkey = Factory.readPubkey(address.getVersion(), address.getStream(), new ByteArrayInputStream(bytes), bytes.length, false); - address.setPubkey(pubkey); - } -} diff --git a/core/src/test/java/ch/dissem/bitmessage/utils/TestUtils.kt b/core/src/test/java/ch/dissem/bitmessage/utils/TestUtils.kt new file mode 100644 index 0000000..397f926 --- /dev/null +++ b/core/src/test/java/ch/dissem/bitmessage/utils/TestUtils.kt @@ -0,0 +1,133 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.utils + +import ch.dissem.bitmessage.BitmessageContext +import ch.dissem.bitmessage.InternalContext +import ch.dissem.bitmessage.entity.BitmessageAddress +import ch.dissem.bitmessage.entity.ObjectMessage +import ch.dissem.bitmessage.entity.payload.V4Pubkey +import ch.dissem.bitmessage.entity.valueobject.InventoryVector +import ch.dissem.bitmessage.entity.valueobject.PrivateKey +import ch.dissem.bitmessage.exception.DecryptionFailedException +import ch.dissem.bitmessage.factory.Factory +import ch.dissem.bitmessage.ports.* +import com.nhaarman.mockito_kotlin.mock +import com.nhaarman.mockito_kotlin.spy +import org.junit.Assert.assertEquals +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.io.InputStream +import java.util.Random +import kotlin.NoSuchElementException + +/** + * If there's ever a need for this in production code, it should be rewritten to be more efficient. + */ +object TestUtils { + @JvmField val RANDOM = Random() + + @JvmStatic fun int16(number: Int): ByteArray { + val out = ByteArrayOutputStream() + Encode.int16(number.toLong(), out) + return out.toByteArray() + } + + @JvmStatic fun loadObjectMessage(version: Int, resourceName: String): ObjectMessage { + val data = getBytes(resourceName) + val `in` = ByteArrayInputStream(data) + return Factory.getObjectMessage(version, `in`, data.size) ?: throw NoSuchElementException("error loading object message") + } + + @JvmStatic fun getBytes(resourceName: String): ByteArray { + val `in` = javaClass.classLoader.getResourceAsStream(resourceName) + val out = ByteArrayOutputStream() + val buffer = ByteArray(1024) + var len = `in`.read(buffer) + while (len != -1) { + out.write(buffer, 0, len) + len = `in`.read(buffer) + } + return out.toByteArray() + } + + @JvmStatic fun randomInventoryVector(): InventoryVector { + val bytes = ByteArray(32) + RANDOM.nextBytes(bytes) + return InventoryVector(bytes) + } + + @JvmStatic fun getResource(resourceName: String): InputStream { + return javaClass.classLoader.getResourceAsStream(resourceName) + } + + @JvmStatic fun loadIdentity(address: String): BitmessageAddress { + val privateKey = PrivateKey.read(TestUtils.getResource(address + ".privkey")) + val identity = BitmessageAddress(privateKey) + assertEquals(address, identity.address) + return identity + } + + @Throws(DecryptionFailedException::class) + @JvmStatic fun loadContact(): BitmessageAddress { + val address = BitmessageAddress("BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h") + val `object` = TestUtils.loadObjectMessage(3, "V4Pubkey.payload") + `object`.decrypt(address.publicDecryptionKey) + address.pubkey = `object`.payload as V4Pubkey + return address + } + + @JvmStatic fun loadPubkey(address: BitmessageAddress) { + val bytes = getBytes(address.address + ".pubkey") + val pubkey = Factory.readPubkey(address.version, address.stream, ByteArrayInputStream(bytes), bytes.size, false) + address.pubkey = pubkey + } + + @JvmStatic fun mockedInternalContext( + cryptography: Cryptography = mock {}, + inventory: Inventory = mock {}, + nodeRegistry: NodeRegistry = mock {}, + networkHandler: NetworkHandler = mock {}, + addressRepository: AddressRepository = mock {}, + messageRepository: MessageRepository = mock {}, + proofOfWorkRepository: ProofOfWorkRepository = mock {}, + proofOfWorkEngine: ProofOfWorkEngine = mock {}, + customCommandHandler: CustomCommandHandler = mock {}, + listener: BitmessageContext.Listener = mock {}, + labeler: Labeler = mock {}, + port: Int = 0, + connectionTTL: Long = 0, + connectionLimit: Int = 0 + ): InternalContext { + return spy(InternalContext( + cryptography, + inventory, + nodeRegistry, + networkHandler, + addressRepository, + messageRepository, + proofOfWorkRepository, + proofOfWorkEngine, + customCommandHandler, + listener, + labeler, + port, + connectionTTL, + connectionLimit + )) + } +} diff --git a/core/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/core/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker new file mode 100644 index 0000000..1f0955d --- /dev/null +++ b/core/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker @@ -0,0 +1 @@ +mock-maker-inline diff --git a/cryptography-bc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.java b/cryptography-bc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.java index b0937d3..f19be27 100644 --- a/cryptography-bc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.java +++ b/cryptography-bc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.java @@ -1,3 +1,19 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package ch.dissem.bitmessage.security; import ch.dissem.bitmessage.InternalContext; @@ -31,12 +47,12 @@ import static org.mockito.Mockito.when; public class CryptographyTest { public static final byte[] TEST_VALUE = "teststring".getBytes(); public static final byte[] TEST_SHA1 = DatatypeConverter.parseHexBinary("" - + "b8473b86d4c2072ca9b08bd28e373e8253e865c4"); + + "b8473b86d4c2072ca9b08bd28e373e8253e865c4"); public static final byte[] TEST_SHA512 = DatatypeConverter.parseHexBinary("" - + "6253b39071e5df8b5098f59202d414c37a17d6a38a875ef5f8c7d89b0212b028" - + "692d3d2090ce03ae1de66c862fa8a561e57ed9eb7935ce627344f742c0931d72"); + + "6253b39071e5df8b5098f59202d414c37a17d6a38a875ef5f8c7d89b0212b028" + + "692d3d2090ce03ae1de66c862fa8a561e57ed9eb7935ce627344f742c0931d72"); public static final byte[] TEST_RIPEMD160 = DatatypeConverter.parseHexBinary("" - + "cd566972b5e50104011a92b59fa8e0b1234851ae"); + + "cd566972b5e50104011a92b59fa8e0b1234851ae"); private static BouncyCryptography crypto; @@ -46,7 +62,6 @@ public class CryptographyTest { Singleton.initialize(crypto); InternalContext ctx = mock(InternalContext.class); when(ctx.getProofOfWorkEngine()).thenReturn(new MultiThreadedPOWEngine()); - crypto.setContext(ctx); } @Test @@ -77,30 +92,30 @@ public class CryptographyTest { @Test(expected = IOException.class) public void ensureExceptionForInsufficientProofOfWork() throws IOException { ObjectMessage objectMessage = new ObjectMessage.Builder() - .nonce(new byte[8]) - .expiresTime(UnixTime.now(+28 * DAY)) - .objectType(0) - .payload(GenericPayload.read(0, 1, new ByteArrayInputStream(new byte[0]), 0)) - .build(); + .nonce(new byte[8]) + .expiresTime(UnixTime.now() + 28 * DAY) + .objectType(0) + .payload(GenericPayload.Companion.read(0, 1, new ByteArrayInputStream(new byte[0]), 0)) + .build(); crypto.checkProofOfWork(objectMessage, 1000, 1000); } @Test public void testDoProofOfWork() throws Exception { ObjectMessage objectMessage = new ObjectMessage.Builder() - .nonce(new byte[8]) - .expiresTime(UnixTime.now(+2 * MINUTE)) - .objectType(0) - .payload(GenericPayload.read(0, 1, new ByteArrayInputStream(new byte[0]), 0)) - .build(); + .nonce(new byte[8]) + .expiresTime(UnixTime.now() + 2 * MINUTE) + .objectType(0) + .payload(GenericPayload.Companion.read(0, 1, new ByteArrayInputStream(new byte[0]), 0)) + .build(); final CallbackWaiter<byte[]> waiter = new CallbackWaiter<>(); crypto.doProofOfWork(objectMessage, 1000, 1000, - new ProofOfWorkEngine.Callback() { - @Override - public void onNonceCalculated(byte[] initialHash, byte[] nonce) { - waiter.setValue(nonce); - } - }); + new ProofOfWorkEngine.Callback() { + @Override + public void onNonceCalculated(byte[] initialHash, byte[] nonce) { + waiter.setValue(nonce); + } + }); objectMessage.setNonce(waiter.waitForValue()); try { crypto.checkProofOfWork(objectMessage, 1000, 1000); diff --git a/cryptography-sc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.java b/cryptography-sc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.java index dc3cdea..1589fcf 100644 --- a/cryptography-sc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.java +++ b/cryptography-sc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.java @@ -1,3 +1,19 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package ch.dissem.bitmessage.security; import ch.dissem.bitmessage.InternalContext; @@ -11,6 +27,7 @@ import ch.dissem.bitmessage.ports.ProofOfWorkEngine; import ch.dissem.bitmessage.utils.CallbackWaiter; import ch.dissem.bitmessage.utils.Singleton; import ch.dissem.bitmessage.utils.UnixTime; +import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; @@ -31,12 +48,12 @@ import static org.mockito.Mockito.when; public class CryptographyTest { public static final byte[] TEST_VALUE = "teststring".getBytes(); public static final byte[] TEST_SHA1 = DatatypeConverter.parseHexBinary("" - + "b8473b86d4c2072ca9b08bd28e373e8253e865c4"); + + "b8473b86d4c2072ca9b08bd28e373e8253e865c4"); public static final byte[] TEST_SHA512 = DatatypeConverter.parseHexBinary("" - + "6253b39071e5df8b5098f59202d414c37a17d6a38a875ef5f8c7d89b0212b028" - + "692d3d2090ce03ae1de66c862fa8a561e57ed9eb7935ce627344f742c0931d72"); + + "6253b39071e5df8b5098f59202d414c37a17d6a38a875ef5f8c7d89b0212b028" + + "692d3d2090ce03ae1de66c862fa8a561e57ed9eb7935ce627344f742c0931d72"); public static final byte[] TEST_RIPEMD160 = DatatypeConverter.parseHexBinary("" - + "cd566972b5e50104011a92b59fa8e0b1234851ae"); + + "cd566972b5e50104011a92b59fa8e0b1234851ae"); private static SpongyCryptography crypto; @@ -45,62 +62,62 @@ public class CryptographyTest { crypto = new SpongyCryptography(); Singleton.initialize(crypto); InternalContext ctx = mock(InternalContext.class); + InternalContext.Companion.setValue(null, null, ctx); when(ctx.getProofOfWorkEngine()).thenReturn(new MultiThreadedPOWEngine()); - crypto.setContext(ctx); } @Test public void testRipemd160() { - assertArrayEquals(TEST_RIPEMD160, crypto.ripemd160(TEST_VALUE)); + Assert.assertArrayEquals(TEST_RIPEMD160, crypto.ripemd160(TEST_VALUE)); } @Test public void testSha1() { - assertArrayEquals(TEST_SHA1, crypto.sha1(TEST_VALUE)); + Assert.assertArrayEquals(TEST_SHA1, crypto.sha1(TEST_VALUE)); } @Test public void testSha512() { - assertArrayEquals(TEST_SHA512, crypto.sha512(TEST_VALUE)); + Assert.assertArrayEquals(TEST_SHA512, crypto.sha512(TEST_VALUE)); } @Test public void testChaining() { - assertArrayEquals(TEST_SHA512, crypto.sha512("test".getBytes(), "string".getBytes())); + Assert.assertArrayEquals(TEST_SHA512, crypto.sha512("test".getBytes(), "string".getBytes())); } @Test public void ensureDoubleHashYieldsSameResultAsHashOfHash() { - assertArrayEquals(crypto.sha512(TEST_SHA512), crypto.doubleSha512(TEST_VALUE)); + Assert.assertArrayEquals(crypto.sha512(TEST_SHA512), crypto.doubleSha512(TEST_VALUE)); } @Test(expected = IOException.class) public void ensureExceptionForInsufficientProofOfWork() throws IOException { ObjectMessage objectMessage = new ObjectMessage.Builder() - .nonce(new byte[8]) - .expiresTime(UnixTime.now(+28 * DAY)) - .objectType(0) - .payload(GenericPayload.read(0, 1, new ByteArrayInputStream(new byte[0]), 0)) - .build(); + .nonce(new byte[8]) + .expiresTime(UnixTime.now() + 28 * DAY) + .objectType(0) + .payload(GenericPayload.Companion.read(0, 1, new ByteArrayInputStream(new byte[0]), 0)) + .build(); crypto.checkProofOfWork(objectMessage, 1000, 1000); } @Test public void testDoProofOfWork() throws Exception { ObjectMessage objectMessage = new ObjectMessage.Builder() - .nonce(new byte[8]) - .expiresTime(UnixTime.now(+2 * MINUTE)) - .objectType(0) - .payload(GenericPayload.read(0, 1, new ByteArrayInputStream(new byte[0]), 0)) - .build(); + .nonce(new byte[8]) + .expiresTime(UnixTime.now() + 2 * MINUTE) + .objectType(0) + .payload(GenericPayload.Companion.read(0, 1, new ByteArrayInputStream(new byte[0]), 0)) + .build(); final CallbackWaiter<byte[]> waiter = new CallbackWaiter<>(); crypto.doProofOfWork(objectMessage, 1000, 1000, - new ProofOfWorkEngine.Callback() { - @Override - public void onNonceCalculated(byte[] initialHash, byte[] nonce) { - waiter.setValue(nonce); - } - }); + new ProofOfWorkEngine.Callback() { + @Override + public void onNonceCalculated(byte[] initialHash, byte[] nonce) { + waiter.setValue(nonce); + } + }); objectMessage.setNonce(waiter.waitForValue()); try { crypto.checkProofOfWork(objectMessage, 1000, 1000); diff --git a/demo/src/test/java/ch/dissem/bitmessage/SystemTest.java b/demo/src/test/java/ch/dissem/bitmessage/SystemTest.java index 9f97432..fe3343d 100644 --- a/demo/src/test/java/ch/dissem/bitmessage/SystemTest.java +++ b/demo/src/test/java/ch/dissem/bitmessage/SystemTest.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package ch.dissem.bitmessage; import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography; diff --git a/demo/src/test/java/ch/dissem/bitmessage/TestListener.java b/demo/src/test/java/ch/dissem/bitmessage/TestListener.java index 9c00776..d2b1cd5 100644 --- a/demo/src/test/java/ch/dissem/bitmessage/TestListener.java +++ b/demo/src/test/java/ch/dissem/bitmessage/TestListener.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package ch.dissem.bitmessage; import ch.dissem.bitmessage.entity.Plaintext; diff --git a/demo/src/test/java/ch/dissem/bitmessage/TestNodeRegistry.java b/demo/src/test/java/ch/dissem/bitmessage/TestNodeRegistry.java index 2f9f070..3ac7348 100644 --- a/demo/src/test/java/ch/dissem/bitmessage/TestNodeRegistry.java +++ b/demo/src/test/java/ch/dissem/bitmessage/TestNodeRegistry.java @@ -31,10 +31,10 @@ class TestNodeRegistry implements NodeRegistry { public TestNodeRegistry(int... ports) { for (int port : ports) { nodes.add( - new NetworkAddress.Builder() - .ipv4(127, 0, 0, 1) - .port(port) - .build() + new NetworkAddress.Builder() + .ipv4(127, 0, 0, 1) + .port(port) + .build() ); } } diff --git a/extensions/src/main/java/ch/dissem/bitmessage/extensions/CryptoCustomMessage.java b/extensions/src/main/java/ch/dissem/bitmessage/extensions/CryptoCustomMessage.java deleted file mode 100644 index 7955e5b..0000000 --- a/extensions/src/main/java/ch/dissem/bitmessage/extensions/CryptoCustomMessage.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.extensions; - -import ch.dissem.bitmessage.entity.BitmessageAddress; -import ch.dissem.bitmessage.entity.CustomMessage; -import ch.dissem.bitmessage.entity.Streamable; -import ch.dissem.bitmessage.entity.payload.CryptoBox; -import ch.dissem.bitmessage.entity.payload.Pubkey; -import ch.dissem.bitmessage.exception.DecryptionFailedException; -import ch.dissem.bitmessage.factory.Factory; -import ch.dissem.bitmessage.utils.Encode; - -import java.io.*; - -import static ch.dissem.bitmessage.utils.Decode.*; -import static ch.dissem.bitmessage.utils.Singleton.cryptography; - -/** - * A {@link CustomMessage} implementation that contains signed and encrypted data. - * - * @author Christian Basler - */ -public class CryptoCustomMessage<T extends Streamable> extends CustomMessage { - private static final long serialVersionUID = 7395193565986284426L; - - public static final String COMMAND = "ENCRYPTED"; - - private final Reader<T> dataReader; - private CryptoBox container; - private BitmessageAddress sender; - private T data; - - public CryptoCustomMessage(T data) throws IOException { - super(COMMAND); - this.data = data; - this.dataReader = null; - } - - private CryptoCustomMessage(CryptoBox container, Reader<T> dataReader) { - super(COMMAND); - this.container = container; - this.dataReader = dataReader; - } - - public static <T extends Streamable> CryptoCustomMessage<T> read(CustomMessage data, Reader<T> dataReader) throws IOException { - CryptoBox cryptoBox = CryptoBox.read(new ByteArrayInputStream(data.getData()), data.getData().length); - return new CryptoCustomMessage<>(cryptoBox, dataReader); - } - - public BitmessageAddress getSender() { - return sender; - } - - public void signAndEncrypt(BitmessageAddress identity, byte[] publicKey) throws IOException { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - - Encode.varInt(identity.getVersion(), out); - Encode.varInt(identity.getStream(), out); - Encode.int32(identity.getPubkey().getBehaviorBitfield(), out); - out.write(identity.getPubkey().getSigningKey(), 1, 64); - out.write(identity.getPubkey().getEncryptionKey(), 1, 64); - if (identity.getVersion() >= 3) { - Encode.varInt(identity.getPubkey().getNonceTrialsPerByte(), out); - Encode.varInt(identity.getPubkey().getExtraBytes(), out); - } - - data.write(out); - Encode.varBytes(cryptography().getSignature(out.toByteArray(), identity.getPrivateKey()), out); - container = new CryptoBox(out.toByteArray(), publicKey); - } - - public T decrypt(byte[] privateKey) throws IOException, DecryptionFailedException { - SignatureCheckingInputStream in = new SignatureCheckingInputStream(container.decrypt(privateKey)); - - long addressVersion = varInt(in); - long stream = varInt(in); - int behaviorBitfield = int32(in); - byte[] publicSigningKey = bytes(in, 64); - byte[] publicEncryptionKey = bytes(in, 64); - long nonceTrialsPerByte = addressVersion >= 3 ? varInt(in) : 0; - long extraBytes = addressVersion >= 3 ? varInt(in) : 0; - - sender = new BitmessageAddress(Factory.createPubkey( - addressVersion, - stream, - publicSigningKey, - publicEncryptionKey, - nonceTrialsPerByte, - extraBytes, - behaviorBitfield - )); - - data = dataReader.read(sender, in); - - in.checkSignature(sender.getPubkey()); - - return data; - } - - @Override - public void write(OutputStream out) throws IOException { - Encode.varString(COMMAND, out); - container.write(out); - } - - public interface Reader<T> { - T read(BitmessageAddress sender, InputStream in) throws IOException; - } - - private class SignatureCheckingInputStream extends InputStream { - private final ByteArrayOutputStream out = new ByteArrayOutputStream(); - private final InputStream wrapped; - - private SignatureCheckingInputStream(InputStream wrapped) { - this.wrapped = wrapped; - } - - @Override - public int read() throws IOException { - int read = wrapped.read(); - if (read >= 0) out.write(read); - return read; - } - - public void checkSignature(Pubkey pubkey) throws IOException, IllegalStateException { - if (!cryptography().isSignatureValid(out.toByteArray(), varBytes(wrapped), pubkey)) { - throw new IllegalStateException("Signature check failed"); - } - } - } -} diff --git a/extensions/src/main/java/ch/dissem/bitmessage/extensions/pow/ProofOfWorkRequest.java b/extensions/src/main/java/ch/dissem/bitmessage/extensions/pow/ProofOfWorkRequest.java deleted file mode 100644 index e5ba4f8..0000000 --- a/extensions/src/main/java/ch/dissem/bitmessage/extensions/pow/ProofOfWorkRequest.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.extensions.pow; - -import ch.dissem.bitmessage.entity.BitmessageAddress; -import ch.dissem.bitmessage.entity.Streamable; -import ch.dissem.bitmessage.extensions.CryptoCustomMessage; -import ch.dissem.bitmessage.utils.Encode; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.util.Arrays; - -import static ch.dissem.bitmessage.utils.Decode.*; - -/** - * @author Christian Basler - */ -public class ProofOfWorkRequest implements Streamable { - private static final long serialVersionUID = 4729003751499662713L; - - private final BitmessageAddress sender; - private final byte[] initialHash; - private final Request request; - - private final byte[] data; - - public ProofOfWorkRequest(BitmessageAddress sender, byte[] initialHash, Request request) { - this(sender, initialHash, request, new byte[0]); - } - - public ProofOfWorkRequest(BitmessageAddress sender, byte[] initialHash, Request request, byte[] data) { - this.sender = sender; - this.initialHash = initialHash; - this.request = request; - this.data = data; - } - - public static ProofOfWorkRequest read(BitmessageAddress client, InputStream in) throws IOException { - return new ProofOfWorkRequest( - client, - bytes(in, 64), - Request.valueOf(varString(in)), - varBytes(in) - ); - } - - public BitmessageAddress getSender() { - return sender; - } - - public byte[] getInitialHash() { - return initialHash; - } - - public Request getRequest() { - return request; - } - - public byte[] getData() { - return data; - } - - @Override - public void write(OutputStream out) throws IOException { - out.write(initialHash); - Encode.varString(request.name(), out); - Encode.varBytes(data, out); - } - - @Override - public void write(ByteBuffer buffer) { - buffer.put(initialHash); - Encode.varString(request.name(), buffer); - Encode.varBytes(data, buffer); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - ProofOfWorkRequest other = (ProofOfWorkRequest) o; - - if (!sender.equals(other.sender)) return false; - if (!Arrays.equals(initialHash, other.initialHash)) return false; - if (request != other.request) return false; - return Arrays.equals(data, other.data); - } - - @Override - public int hashCode() { - int result = sender.hashCode(); - result = 31 * result + Arrays.hashCode(initialHash); - result = 31 * result + request.hashCode(); - result = 31 * result + Arrays.hashCode(data); - return result; - } - - public static class Reader implements CryptoCustomMessage.Reader<ProofOfWorkRequest> { - private final BitmessageAddress identity; - - public Reader(BitmessageAddress identity) { - this.identity = identity; - } - - @Override - public ProofOfWorkRequest read(BitmessageAddress sender, InputStream in) throws IOException { - return ProofOfWorkRequest.read(identity, in); - } - } - - public enum Request { - CALCULATE, - CALCULATING, - COMPLETE - } -} diff --git a/extensions/src/main/kotlin/ch/dissem/bitmessage/extensions/CryptoCustomMessage.kt b/extensions/src/main/kotlin/ch/dissem/bitmessage/extensions/CryptoCustomMessage.kt new file mode 100644 index 0000000..1c76bdb --- /dev/null +++ b/extensions/src/main/kotlin/ch/dissem/bitmessage/extensions/CryptoCustomMessage.kt @@ -0,0 +1,146 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.extensions + +import ch.dissem.bitmessage.entity.BitmessageAddress +import ch.dissem.bitmessage.entity.CustomMessage +import ch.dissem.bitmessage.entity.Streamable +import ch.dissem.bitmessage.entity.payload.CryptoBox +import ch.dissem.bitmessage.entity.payload.Pubkey +import ch.dissem.bitmessage.exception.DecryptionFailedException +import ch.dissem.bitmessage.factory.Factory +import ch.dissem.bitmessage.utils.Decode.bytes +import ch.dissem.bitmessage.utils.Decode.int32 +import ch.dissem.bitmessage.utils.Decode.varBytes +import ch.dissem.bitmessage.utils.Decode.varInt +import ch.dissem.bitmessage.utils.Encode +import ch.dissem.bitmessage.utils.Singleton.cryptography +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.io.InputStream +import java.io.OutputStream + +/** + * A [CustomMessage] implementation that contains signed and encrypted data. + + * @author Christian Basler + */ +class CryptoCustomMessage<T : Streamable> : CustomMessage { + + private val dataReader: Reader<T>? + private var container: CryptoBox? = null + var sender: BitmessageAddress? = null + private set + private var data: T? = null + private set + + constructor(data: T) : super(COMMAND, null) { + this.data = data + this.dataReader = null + } + + private constructor(container: CryptoBox, dataReader: Reader<T>) : super(COMMAND, null) { + this.container = container + this.dataReader = dataReader + } + + fun signAndEncrypt(identity: BitmessageAddress, publicKey: ByteArray) { + val out = ByteArrayOutputStream() + + val privateKey = identity.privateKey ?: throw IllegalStateException("signing identity must have a private key") + + Encode.varInt(identity.version, out) + Encode.varInt(identity.stream, out) + Encode.int32(privateKey.pubkey.behaviorBitfield.toLong(), out) + out.write(privateKey.pubkey.signingKey, 1, 64) + out.write(privateKey.pubkey.encryptionKey, 1, 64) + if (identity.version >= 3) { + Encode.varInt(privateKey.pubkey.nonceTrialsPerByte, out) + Encode.varInt(privateKey.pubkey.extraBytes, out) + } + + data?.write(out) ?: throw IllegalStateException("no unencrypted data available") + Encode.varBytes(cryptography().getSignature(out.toByteArray(), privateKey), out) + container = CryptoBox(out.toByteArray(), publicKey) + } + + @Throws(DecryptionFailedException::class) + fun decrypt(privateKey: ByteArray): T { + val `in` = SignatureCheckingInputStream(container?.decrypt(privateKey) ?: throw IllegalStateException("no encrypted data available")) + if (dataReader == null) throw IllegalStateException("no data reader available") + + val addressVersion = varInt(`in`) + val stream = varInt(`in`) + val behaviorBitfield = int32(`in`) + val publicSigningKey = bytes(`in`, 64) + val publicEncryptionKey = bytes(`in`, 64) + val nonceTrialsPerByte = if (addressVersion >= 3) varInt(`in`) else 0 + val extraBytes = if (addressVersion >= 3) varInt(`in`) else 0 + + val sender = BitmessageAddress(Factory.createPubkey( + addressVersion, + stream, + publicSigningKey, + publicEncryptionKey, + nonceTrialsPerByte, + extraBytes, + behaviorBitfield + )) + this.sender = sender + + data = dataReader.read(sender, `in`) + + `in`.checkSignature(sender.pubkey!!) + + return data!! + } + + override fun write(out: OutputStream) { + Encode.varString(COMMAND, out) + container?.write(out) ?: throw IllegalStateException("not encrypted yet") + } + + interface Reader<T> { + fun read(sender: BitmessageAddress, `in`: InputStream): T + } + + private inner class SignatureCheckingInputStream internal constructor(private val wrapped: InputStream) : InputStream() { + private val out = ByteArrayOutputStream() + + override fun read(): Int { + val read = wrapped.read() + if (read >= 0) out.write(read) + return read + } + + @Throws(IllegalStateException::class) + fun checkSignature(pubkey: Pubkey) { + if (!cryptography().isSignatureValid(out.toByteArray(), varBytes(wrapped), pubkey)) { + throw IllegalStateException("Signature check failed") + } + } + } + + companion object { + @JvmField val COMMAND = "ENCRYPTED" + + @JvmStatic fun <T : Streamable> read(data: CustomMessage, dataReader: Reader<T>): CryptoCustomMessage<T> { + val cryptoBox = CryptoBox.read(ByteArrayInputStream(data.getData()), data.getData().size) + return CryptoCustomMessage(cryptoBox, dataReader) + } + } +} diff --git a/extensions/src/main/kotlin/ch/dissem/bitmessage/extensions/pow/ProofOfWorkRequest.kt b/extensions/src/main/kotlin/ch/dissem/bitmessage/extensions/pow/ProofOfWorkRequest.kt new file mode 100644 index 0000000..ef987c3 --- /dev/null +++ b/extensions/src/main/kotlin/ch/dissem/bitmessage/extensions/pow/ProofOfWorkRequest.kt @@ -0,0 +1,89 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.extensions.pow + +import ch.dissem.bitmessage.entity.BitmessageAddress +import ch.dissem.bitmessage.entity.Streamable +import ch.dissem.bitmessage.extensions.CryptoCustomMessage +import ch.dissem.bitmessage.utils.Decode.bytes +import ch.dissem.bitmessage.utils.Decode.varBytes +import ch.dissem.bitmessage.utils.Decode.varString +import ch.dissem.bitmessage.utils.Encode +import java.io.InputStream +import java.io.OutputStream +import java.nio.ByteBuffer +import java.util.* + +/** + * @author Christian Basler + */ +data class ProofOfWorkRequest @JvmOverloads constructor(val sender: BitmessageAddress, val initialHash: ByteArray, val request: ProofOfWorkRequest.Request, val data: ByteArray = ByteArray(0)) : Streamable { + + override fun write(out: OutputStream) { + out.write(initialHash) + Encode.varString(request.name, out) + Encode.varBytes(data, out) + } + + override fun write(buffer: ByteBuffer) { + buffer.put(initialHash) + Encode.varString(request.name, buffer) + Encode.varBytes(data, buffer) + } + + class Reader(private val identity: BitmessageAddress) : CryptoCustomMessage.Reader<ProofOfWorkRequest> { + + override fun read(sender: BitmessageAddress, `in`: InputStream): ProofOfWorkRequest { + return ProofOfWorkRequest.read(identity, `in`) + } + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is ProofOfWorkRequest) return false + + if (sender != other.sender) return false + if (!Arrays.equals(initialHash, other.initialHash)) return false + if (request != other.request) return false + return Arrays.equals(data, other.data) + } + + override fun hashCode(): Int { + var result = sender.hashCode() + result = 31 * result + Arrays.hashCode(initialHash) + result = 31 * result + request.hashCode() + result = 31 * result + Arrays.hashCode(data) + return result + } + + enum class Request { + CALCULATE, + CALCULATING, + COMPLETE + } + + companion object { + fun read(client: BitmessageAddress, `in`: InputStream): ProofOfWorkRequest { + return ProofOfWorkRequest( + client, + bytes(`in`, 64), + Request.valueOf(varString(`in`)), + varBytes(`in`) + ) + } + } +} diff --git a/extensions/src/test/java/ch/dissem/bitmessage/extensions/CryptoCustomMessageTest.java b/extensions/src/test/java/ch/dissem/bitmessage/extensions/CryptoCustomMessageTest.java deleted file mode 100644 index bf0fa6d..0000000 --- a/extensions/src/test/java/ch/dissem/bitmessage/extensions/CryptoCustomMessageTest.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.extensions; - -import ch.dissem.bitmessage.entity.BitmessageAddress; -import ch.dissem.bitmessage.entity.CustomMessage; -import ch.dissem.bitmessage.entity.payload.GenericPayload; -import ch.dissem.bitmessage.entity.valueobject.PrivateKey; -import ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest; -import ch.dissem.bitmessage.utils.TestBase; -import ch.dissem.bitmessage.utils.TestUtils; -import org.junit.Test; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; - -import static ch.dissem.bitmessage.utils.Singleton.cryptography; -import static org.junit.Assert.assertEquals; - -public class CryptoCustomMessageTest extends TestBase { - @Test - public void ensureEncryptThenDecryptYieldsSameObject() throws Exception { - PrivateKey privateKey = PrivateKey.read(TestUtils.getResource("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8.privkey")); - BitmessageAddress sendingIdentity = new BitmessageAddress(privateKey); - - GenericPayload payloadBefore = new GenericPayload(0, 1, cryptography().randomBytes(100)); - CryptoCustomMessage<GenericPayload> messageBefore = new CryptoCustomMessage<>(payloadBefore); - messageBefore.signAndEncrypt(sendingIdentity, cryptography().createPublicKey(sendingIdentity.getPublicDecryptionKey())); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - messageBefore.write(out); - ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); - - CustomMessage customMessage = CustomMessage.read(in, out.size()); - CryptoCustomMessage<GenericPayload> messageAfter = CryptoCustomMessage.read(customMessage, - new CryptoCustomMessage.Reader<GenericPayload>() { - @Override - public GenericPayload read(BitmessageAddress ignore, InputStream in) throws IOException { - return GenericPayload.read(0, 1, in, 100); - } - }); - GenericPayload payloadAfter = messageAfter.decrypt(sendingIdentity.getPublicDecryptionKey()); - - assertEquals(payloadBefore, payloadAfter); - } - - @Test - public void testWithActualRequest() throws Exception { - PrivateKey privateKey = PrivateKey.read(TestUtils.getResource("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8.privkey")); - final BitmessageAddress sendingIdentity = new BitmessageAddress(privateKey); - - ProofOfWorkRequest requestBefore = new ProofOfWorkRequest(sendingIdentity, cryptography().randomBytes(64), - ProofOfWorkRequest.Request.CALCULATE); - - CryptoCustomMessage<ProofOfWorkRequest> messageBefore = new CryptoCustomMessage<>(requestBefore); - messageBefore.signAndEncrypt(sendingIdentity, cryptography().createPublicKey(sendingIdentity.getPublicDecryptionKey())); - - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - messageBefore.write(out); - ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); - - CustomMessage customMessage = CustomMessage.read(in, out.size()); - CryptoCustomMessage<ProofOfWorkRequest> messageAfter = CryptoCustomMessage.read(customMessage, - new ProofOfWorkRequest.Reader(sendingIdentity)); - ProofOfWorkRequest requestAfter = messageAfter.decrypt(sendingIdentity.getPublicDecryptionKey()); - - assertEquals(requestBefore, requestAfter); - } -} diff --git a/extensions/src/test/kotlin/ch/dissem/bitmessage/extensions/CryptoCustomMessageTest.kt b/extensions/src/test/kotlin/ch/dissem/bitmessage/extensions/CryptoCustomMessageTest.kt new file mode 100644 index 0000000..f722fa0 --- /dev/null +++ b/extensions/src/test/kotlin/ch/dissem/bitmessage/extensions/CryptoCustomMessageTest.kt @@ -0,0 +1,84 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.extensions + +import ch.dissem.bitmessage.entity.BitmessageAddress +import ch.dissem.bitmessage.entity.CustomMessage +import ch.dissem.bitmessage.entity.payload.GenericPayload +import ch.dissem.bitmessage.entity.valueobject.PrivateKey +import ch.dissem.bitmessage.extensions.pow.ProofOfWorkRequest +import ch.dissem.bitmessage.utils.TestBase +import ch.dissem.bitmessage.utils.TestUtils +import org.junit.Test + +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream +import java.io.InputStream + +import ch.dissem.bitmessage.utils.Singleton.cryptography +import org.junit.Assert.assertEquals + +class CryptoCustomMessageTest : TestBase() { + @Test + fun `ensure encrypt then decrypt yields same object`() { + val privateKey = PrivateKey.read(TestUtils.getResource("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8.privkey")) + val sendingIdentity = BitmessageAddress(privateKey) + + val payloadBefore = GenericPayload(0, 1, cryptography().randomBytes(100)) + val messageBefore = CryptoCustomMessage(payloadBefore) + messageBefore.signAndEncrypt(sendingIdentity, cryptography().createPublicKey(sendingIdentity.publicDecryptionKey)) + + val out = ByteArrayOutputStream() + messageBefore.write(out) + val `in` = ByteArrayInputStream(out.toByteArray()) + + val customMessage = CustomMessage.read(`in`, out.size()) + val messageAfter = CryptoCustomMessage.read(customMessage, + object : CryptoCustomMessage.Reader<GenericPayload> { + override fun read(sender: BitmessageAddress, `in`: InputStream): GenericPayload { + return GenericPayload.read(0, 1, `in`, 100) + } + }) + val payloadAfter = messageAfter.decrypt(sendingIdentity.publicDecryptionKey) + + assertEquals(payloadBefore, payloadAfter) + } + + @Test + fun `test with actual request`() { + val privateKey = PrivateKey.read(TestUtils.getResource("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8.privkey")) + val sendingIdentity = BitmessageAddress(privateKey) + + val requestBefore = ProofOfWorkRequest(sendingIdentity, cryptography().randomBytes(64), + ProofOfWorkRequest.Request.CALCULATE) + + val messageBefore = CryptoCustomMessage(requestBefore) + messageBefore.signAndEncrypt(sendingIdentity, cryptography().createPublicKey(sendingIdentity.publicDecryptionKey)) + + + val out = ByteArrayOutputStream() + messageBefore.write(out) + val `in` = ByteArrayInputStream(out.toByteArray()) + + val customMessage = CustomMessage.read(`in`, out.size()) + val messageAfter = CryptoCustomMessage.read(customMessage, + ProofOfWorkRequest.Reader(sendingIdentity)) + val requestAfter = messageAfter.decrypt(sendingIdentity.publicDecryptionKey) + + assertEquals(requestBefore, requestAfter) + } +} diff --git a/gradle.properties b/gradle.properties index bb8beb4..ad2d684 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,11 +1,8 @@ # Don't change this file - override those properties in the # gradle.properties file in your home directory instead - signing.keyId= signing.password= #signing.secretKeyRingFile= - ossrhUsername= ossrhPassword= - -systemTestsEnabled=false \ No newline at end of file +systemTestsEnabled=false diff --git a/networking/src/main/java/ch/dissem/bitmessage/networking/AbstractConnection.java b/networking/src/main/java/ch/dissem/bitmessage/networking/AbstractConnection.java index 4ad759a..8bcd2b0 100644 --- a/networking/src/main/java/ch/dissem/bitmessage/networking/AbstractConnection.java +++ b/networking/src/main/java/ch/dissem/bitmessage/networking/AbstractConnection.java @@ -78,7 +78,7 @@ public abstract class AbstractConnection { this.host = new NetworkAddress.Builder().ipv6(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0).port(0).build(); this.node = node; this.listener = context.getNetworkListener(); - this.syncTimeout = (syncTimeout > 0 ? UnixTime.now(+syncTimeout) : 0); + this.syncTimeout = (syncTimeout > 0 ? UnixTime.now() + syncTimeout : 0); this.requestedObjects = Collections.newSetFromMap(new ConcurrentHashMap<InventoryVector, Boolean>(10_000)); this.ivCache = new ConcurrentHashMap<>(); this.sendingQueue = new ConcurrentLinkedDeque<>(); @@ -146,7 +146,7 @@ public abstract class AbstractConnection { missing.removeAll(commonRequestedObjects.keySet()); LOG.trace("Received inventory with " + originalSize + " elements, of which are " + missing.size() + " missing."); - send(new GetData.Builder().inventory(missing).build()); + send(new GetData(missing)); } private void receiveMessage(GetData getData) { @@ -195,9 +195,7 @@ public abstract class AbstractConnection { } public void offer(InventoryVector iv) { - sendingQueue.offer(new Inv.Builder() - .addInventoryVector(iv) - .build()); + sendingQueue.offer(new Inv(Collections.singletonList(iv))); updateIvCache(Collections.singletonList(iv)); } @@ -210,7 +208,7 @@ public abstract class AbstractConnection { } private void cleanupIvCache() { - Long fiveMinutesAgo = UnixTime.now(-5 * MINUTE); + long fiveMinutesAgo = UnixTime.now() - 5 * MINUTE; for (Map.Entry<InventoryVector, Long> entry : ivCache.entrySet()) { if (entry.getValue() < fiveMinutesAgo) { ivCache.remove(entry.getKey()); @@ -256,15 +254,13 @@ public abstract class AbstractConnection { private void sendAddresses() { List<NetworkAddress> addresses = ctx.getNodeRegistry().getKnownAddresses(1000, streams); - sendingQueue.offer(new Addr.Builder().addresses(addresses).build()); + sendingQueue.offer(new Addr(addresses)); } private void sendInventory() { List<InventoryVector> inventory = ctx.getInventory().getInventory(streams); for (int i = 0; i < inventory.size(); i += 50000) { - sendingQueue.offer(new Inv.Builder() - .inventory(inventory.subList(i, Math.min(inventory.size(), i + 50000))) - .build()); + sendingQueue.offer(new Inv(inventory.subList(i, Math.min(inventory.size(), i + 50000)))); } } @@ -328,10 +324,6 @@ public abstract class AbstractConnection { protected abstract void send(MessagePayload payload); - public enum Mode {SERVER, CLIENT, SYNC} - - public enum State {CONNECTING, ACTIVE, DISCONNECTED} - @Override public boolean equals(Object o) { if (this == o) return true; @@ -344,4 +336,8 @@ public abstract class AbstractConnection { public int hashCode() { return Objects.hash(node); } + + public enum Mode {SERVER, CLIENT, SYNC} + + public enum State {CONNECTING, ACTIVE, DISCONNECTED} } diff --git a/networking/src/main/java/ch/dissem/bitmessage/networking/ConnectionOrganizer.java b/networking/src/main/java/ch/dissem/bitmessage/networking/ConnectionOrganizer.java index 6d7ae04..7e0c96e 100644 --- a/networking/src/main/java/ch/dissem/bitmessage/networking/ConnectionOrganizer.java +++ b/networking/src/main/java/ch/dissem/bitmessage/networking/ConnectionOrganizer.java @@ -26,7 +26,7 @@ import java.util.Iterator; import java.util.List; import static ch.dissem.bitmessage.networking.AbstractConnection.Mode.CLIENT; -import static ch.dissem.bitmessage.networking.DefaultNetworkHandler.NETWORK_MAGIC_NUMBER; +import static ch.dissem.bitmessage.constants.Network.NETWORK_MAGIC_NUMBER; /** * @author Christian Basler diff --git a/networking/src/main/java/ch/dissem/bitmessage/networking/DefaultNetworkHandler.java b/networking/src/main/java/ch/dissem/bitmessage/networking/DefaultNetworkHandler.java index b0966d4..b3e51e8 100644 --- a/networking/src/main/java/ch/dissem/bitmessage/networking/DefaultNetworkHandler.java +++ b/networking/src/main/java/ch/dissem/bitmessage/networking/DefaultNetworkHandler.java @@ -35,6 +35,7 @@ import java.net.Socket; import java.util.*; import java.util.concurrent.*; +import static ch.dissem.bitmessage.constants.Network.NETWORK_MAGIC_NUMBER; import static ch.dissem.bitmessage.networking.AbstractConnection.Mode.SERVER; import static ch.dissem.bitmessage.networking.AbstractConnection.State.ACTIVE; import static ch.dissem.bitmessage.utils.DebugUtils.inc; @@ -193,7 +194,7 @@ public class DefaultNetworkHandler implements NetworkHandler, ContextHolder { } return new Property("network", null, new Property("connectionManager", running ? "running" : "stopped"), - new Property("connections", null, streamProperties), + new Property("connections", streamProperties), new Property("requestedObjects", requestedObjects.size()) ); } @@ -222,7 +223,7 @@ public class DefaultNetworkHandler implements NetworkHandler, ContextHolder { if (connection.knowsOf(next)) { List<InventoryVector> ivs = distribution.get(connection); if (ivs.size() == GetData.MAX_INVENTORY_SIZE) { - connection.send(new GetData.Builder().inventory(ivs).build()); + connection.send(new GetData(ivs)); ivs.clear(); } ivs.add(next); @@ -241,7 +242,7 @@ public class DefaultNetworkHandler implements NetworkHandler, ContextHolder { for (Connection connection : distribution.keySet()) { List<InventoryVector> ivs = distribution.get(connection); if (!ivs.isEmpty()) { - connection.send(new GetData.Builder().inventory(ivs).build()); + connection.send(new GetData(ivs)); } } } diff --git a/networking/src/main/java/ch/dissem/bitmessage/networking/ServerRunnable.java b/networking/src/main/java/ch/dissem/bitmessage/networking/ServerRunnable.java index 0a4e7a2..eca4d5d 100644 --- a/networking/src/main/java/ch/dissem/bitmessage/networking/ServerRunnable.java +++ b/networking/src/main/java/ch/dissem/bitmessage/networking/ServerRunnable.java @@ -37,12 +37,10 @@ public class ServerRunnable implements Runnable, Closeable { private final InternalContext ctx; private final ServerSocket serverSocket; private final DefaultNetworkHandler networkHandler; - private final NetworkHandler.MessageListener listener; public ServerRunnable(InternalContext ctx, DefaultNetworkHandler networkHandler) throws IOException { this.ctx = ctx; this.networkHandler = networkHandler; - this.listener = ctx.getNetworkListener(); this.serverSocket = new ServerSocket(ctx.getPort()); } diff --git a/networking/src/main/java/ch/dissem/bitmessage/networking/nio/NioNetworkHandler.java b/networking/src/main/java/ch/dissem/bitmessage/networking/nio/NioNetworkHandler.java index 04e6d4d..7f6dc5d 100644 --- a/networking/src/main/java/ch/dissem/bitmessage/networking/nio/NioNetworkHandler.java +++ b/networking/src/main/java/ch/dissem/bitmessage/networking/nio/NioNetworkHandler.java @@ -25,7 +25,9 @@ import ch.dissem.bitmessage.entity.valueobject.NetworkAddress; import ch.dissem.bitmessage.exception.ApplicationException; import ch.dissem.bitmessage.exception.NodeException; import ch.dissem.bitmessage.factory.V3MessageReader; +import ch.dissem.bitmessage.networking.AbstractConnection; import ch.dissem.bitmessage.ports.NetworkHandler; +import ch.dissem.bitmessage.utils.DebugUtils; import ch.dissem.bitmessage.utils.Property; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -39,11 +41,14 @@ import java.nio.channels.*; import java.util.*; import java.util.concurrent.*; -import static ch.dissem.bitmessage.networking.AbstractConnection.Mode.*; +import static ch.dissem.bitmessage.constants.Network.HEADER_SIZE; +import static ch.dissem.bitmessage.constants.Network.NETWORK_MAGIC_NUMBER; +import static ch.dissem.bitmessage.networking.AbstractConnection.Mode.CLIENT; +import static ch.dissem.bitmessage.networking.AbstractConnection.Mode.SERVER; +import static ch.dissem.bitmessage.networking.AbstractConnection.Mode.SYNC; import static ch.dissem.bitmessage.networking.AbstractConnection.State.ACTIVE; import static ch.dissem.bitmessage.networking.AbstractConnection.State.DISCONNECTED; import static ch.dissem.bitmessage.utils.Collections.selectRandom; -import static ch.dissem.bitmessage.utils.DebugUtils.inc; import static ch.dissem.bitmessage.utils.ThreadFactoryBuilder.pool; import static java.nio.channels.SelectionKey.*; @@ -434,7 +439,7 @@ public class NioNetworkHandler implements NetworkHandler, InternalContext.Contex if (connection.knowsOf(next) && !connection.requested(next)) { List<InventoryVector> ivs = distribution.get(connection); if (ivs.size() == GetData.MAX_INVENTORY_SIZE) { - connection.send(new GetData.Builder().inventory(ivs).build()); + connection.send(new GetData(ivs)); ivs.clear(); } ivs.add(next); @@ -458,7 +463,7 @@ public class NioNetworkHandler implements NetworkHandler, InternalContext.Contex for (ConnectionInfo connection : distribution.keySet()) { List<InventoryVector> ivs = distribution.get(connection); if (!ivs.isEmpty()) { - connection.send(new GetData.Builder().inventory(ivs).build()); + connection.send(new GetData(ivs)); } } } @@ -474,9 +479,9 @@ public class NioNetworkHandler implements NetworkHandler, InternalContext.Contex for (long stream : connection.getStreams()) { streams.add(stream); if (connection.getMode() == SERVER) { - inc(incomingConnections, stream); + DebugUtils.inc(incomingConnections, stream); } else { - inc(outgoingConnections, stream); + DebugUtils.inc(outgoingConnections, stream); } } } @@ -495,7 +500,7 @@ public class NioNetworkHandler implements NetworkHandler, InternalContext.Contex } return new Property("network", null, new Property("connectionManager", isRunning() ? "running" : "stopped"), - new Property("connections", null, streamProperties), + new Property("connections", streamProperties), new Property("requestedObjects", requestedObjects.size()) ); } diff --git a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcAddressRepository.java b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcAddressRepository.java index 5422997..b296bc3 100644 --- a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcAddressRepository.java +++ b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcAddressRepository.java @@ -97,10 +97,10 @@ public class JdbcAddressRepository extends JdbcHelper implements AddressReposito private List<BitmessageAddress> find(String where) { List<BitmessageAddress> result = new LinkedList<>(); try ( - Connection connection = config.getConnection(); - Statement stmt = connection.createStatement(); - ResultSet rs = stmt.executeQuery("SELECT address, alias, public_key, private_key, subscribed, chan " + - "FROM Address WHERE " + where) + Connection connection = config.getConnection(); + Statement stmt = connection.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT address, alias, public_key, private_key, subscribed, chan " + + "FROM Address WHERE " + where) ) { while (rs.next()) { BitmessageAddress address; @@ -111,7 +111,7 @@ public class JdbcAddressRepository extends JdbcHelper implements AddressReposito Blob publicKeyBlob = rs.getBlob("public_key"); if (publicKeyBlob != null) { Pubkey pubkey = Factory.readPubkey(address.getVersion(), address.getStream(), - publicKeyBlob.getBinaryStream(), (int) publicKeyBlob.length(), false); + publicKeyBlob.getBinaryStream(), (int) publicKeyBlob.length(), false); if (address.getVersion() == 4 && pubkey instanceof V3Pubkey) { pubkey = new V4Pubkey((V3Pubkey) pubkey); } @@ -127,7 +127,7 @@ public class JdbcAddressRepository extends JdbcHelper implements AddressReposito result.add(address); } - } catch (IOException | SQLException e) { + } catch (SQLException e) { LOG.error(e.getMessage(), e); } return result; @@ -135,10 +135,10 @@ public class JdbcAddressRepository extends JdbcHelper implements AddressReposito private boolean exists(BitmessageAddress address) { try ( - Connection connection = config.getConnection(); - Statement stmt = connection.createStatement(); - ResultSet rs = stmt.executeQuery("SELECT COUNT(*) FROM Address " + - "WHERE address='" + address.getAddress() + "'") + Connection connection = config.getConnection(); + Statement stmt = connection.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT COUNT(*) FROM Address " + + "WHERE address='" + address.getAddress() + "'") ) { if (rs.next()) { return rs.getInt(1) > 0; @@ -172,8 +172,8 @@ public class JdbcAddressRepository extends JdbcHelper implements AddressReposito } statement.append(", subscribed=?, chan=? WHERE address=?"); try ( - Connection connection = config.getConnection(); - PreparedStatement ps = connection.prepareStatement(statement.toString()) + Connection connection = config.getConnection(); + PreparedStatement ps = connection.prepareStatement(statement.toString()) ) { int i = 0; ps.setString(++i, address.getAlias()); @@ -192,10 +192,10 @@ public class JdbcAddressRepository extends JdbcHelper implements AddressReposito private void insert(BitmessageAddress address) throws IOException, SQLException { try ( - Connection connection = config.getConnection(); - PreparedStatement ps = connection.prepareStatement( - "INSERT INTO Address (address, version, alias, public_key, private_key, subscribed, chan) " + - "VALUES (?, ?, ?, ?, ?, ?, ?)") + Connection connection = config.getConnection(); + PreparedStatement ps = connection.prepareStatement( + "INSERT INTO Address (address, version, alias, public_key, private_key, subscribed, chan) " + + "VALUES (?, ?, ?, ?, ?, ?, ?)") ) { ps.setString(1, address.getAddress()); ps.setLong(2, address.getVersion()); @@ -221,8 +221,8 @@ public class JdbcAddressRepository extends JdbcHelper implements AddressReposito @Override public void remove(BitmessageAddress address) { try ( - Connection connection = config.getConnection(); - Statement stmt = connection.createStatement() + Connection connection = config.getConnection(); + Statement stmt = connection.createStatement() ) { stmt.executeUpdate("DELETE FROM Address WHERE address = '" + address.getAddress() + "'"); } catch (SQLException e) { diff --git a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcHelper.java b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcHelper.java index b0be9c1..8548267 100644 --- a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcHelper.java +++ b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcHelper.java @@ -17,24 +17,16 @@ package ch.dissem.bitmessage.repository; import ch.dissem.bitmessage.entity.Streamable; -import ch.dissem.bitmessage.entity.payload.ObjectType; -import ch.dissem.bitmessage.exception.ApplicationException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.sql.PreparedStatement; import java.sql.SQLException; -import java.util.Collection; - -import static ch.dissem.bitmessage.utils.Strings.hex; /** * Helper class that does Flyway migration, provides JDBC connections and some helper methods. */ public abstract class JdbcHelper { - private static final Logger LOG = LoggerFactory.getLogger(JdbcHelper.class); protected final JdbcConfig config; diff --git a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcInventory.java b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcInventory.java index 33b7290..65ea15f 100644 --- a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcInventory.java +++ b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcInventory.java @@ -49,8 +49,8 @@ public class JdbcInventory extends JdbcHelper implements Inventory { List<InventoryVector> result = new LinkedList<>(); for (long stream : streams) { getCache(stream).entrySet().stream() - .filter(e -> e.getValue() > now()) - .forEach(e -> result.add(e.getKey())); + .filter(e -> e.getValue() > now()) + .forEach(e -> result.add(e.getKey())); } return result; } @@ -63,10 +63,10 @@ public class JdbcInventory extends JdbcHelper implements Inventory { result = new ConcurrentHashMap<>(); cache.put(stream, result); try ( - Connection connection = config.getConnection(); - Statement stmt = connection.createStatement(); - ResultSet rs = stmt.executeQuery("SELECT hash, expires FROM Inventory " + - "WHERE expires > " + now(-5 * MINUTE) + " AND stream = " + stream) + Connection connection = config.getConnection(); + Statement stmt = connection.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT hash, expires FROM Inventory " + + "WHERE expires > " + (now() - 5 * MINUTE) + " AND stream = " + stream) ) { while (rs.next()) { result.put(InventoryVector.fromHash(rs.getBytes("hash")), rs.getLong("expires")); @@ -91,9 +91,9 @@ public class JdbcInventory extends JdbcHelper implements Inventory { @Override public ObjectMessage getObject(InventoryVector vector) { try ( - Connection connection = config.getConnection(); - Statement stmt = connection.createStatement(); - ResultSet rs = stmt.executeQuery("SELECT data, version FROM Inventory WHERE hash = X'" + vector + "'") + Connection connection = config.getConnection(); + Statement stmt = connection.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT data, version FROM Inventory WHERE hash = X'" + vector + "'") ) { if (rs.next()) { Blob data = rs.getBlob("data"); @@ -121,9 +121,9 @@ public class JdbcInventory extends JdbcHelper implements Inventory { query.append(" AND type IN (").append(join(types)).append(')'); } try ( - Connection connection = config.getConnection(); - Statement stmt = connection.createStatement(); - ResultSet rs = stmt.executeQuery(query.toString()) + Connection connection = config.getConnection(); + Statement stmt = connection.createStatement(); + ResultSet rs = stmt.executeQuery(query.toString()) ) { List<ObjectMessage> result = new LinkedList<>(); while (rs.next()) { @@ -143,9 +143,9 @@ public class JdbcInventory extends JdbcHelper implements Inventory { return; try ( - Connection connection = config.getConnection(); - PreparedStatement ps = connection.prepareStatement("INSERT INTO Inventory " + - "(hash, stream, expires, data, type, version) VALUES (?, ?, ?, ?, ?, ?)") + Connection connection = config.getConnection(); + PreparedStatement ps = connection.prepareStatement("INSERT INTO Inventory " + + "(hash, stream, expires, data, type, version) VALUES (?, ?, ?, ?, ?, ?)") ) { InventoryVector iv = object.getInventoryVector(); LOG.trace("Storing object " + iv); @@ -167,21 +167,21 @@ public class JdbcInventory extends JdbcHelper implements Inventory { @Override public boolean contains(ObjectMessage object) { return getCache(object.getStream()).entrySet().stream() - .anyMatch(x -> x.getKey().equals(object.getInventoryVector())); + .anyMatch(x -> x.getKey().equals(object.getInventoryVector())); } @Override public void cleanup() { try ( - Connection connection = config.getConnection(); - Statement stmt = connection.createStatement() + Connection connection = config.getConnection(); + Statement stmt = connection.createStatement() ) { - stmt.executeUpdate("DELETE FROM Inventory WHERE expires < " + now(-5 * MINUTE)); + stmt.executeUpdate("DELETE FROM Inventory WHERE expires < " + (now() - 5 * MINUTE)); } catch (SQLException e) { LOG.debug(e.getMessage(), e); } for (Map<InventoryVector, Long> c : cache.values()) { - c.entrySet().removeIf(e -> e.getValue() < now(-5 * MINUTE)); + c.entrySet().removeIf(e -> e.getValue() < (now() - 5 * MINUTE)); } } } diff --git a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcMessageRepository.java b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcMessageRepository.java index 11c7028..e9fffb6 100644 --- a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcMessageRepository.java +++ b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcMessageRepository.java @@ -111,8 +111,8 @@ public class JdbcMessageRepository extends AbstractMessageRepository implements long id = rs.getLong("id"); builder.id(id); builder.IV(InventoryVector.fromHash(iv)); - builder.from(ctx.getAddressRepository().getAddress(rs.getString("sender"))); - builder.to(ctx.getAddressRepository().getAddress(rs.getString("recipient"))); + builder.from(getCtx().getAddressRepository().getAddress(rs.getString("sender"))); + builder.to(getCtx().getAddressRepository().getAddress(rs.getString("recipient"))); builder.ackData(rs.getBytes("ack_data")); builder.sent(rs.getObject("sent", Long.class)); builder.received(rs.getObject("received", Long.class)); @@ -127,7 +127,7 @@ public class JdbcMessageRepository extends AbstractMessageRepository implements message.setInitialHash(rs.getBytes("initial_hash")); result.add(message); } - } catch (IOException | SQLException e) { + } catch (SQLException e) { LOG.error(e.getMessage(), e); } return result; diff --git a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcNodeRegistry.java b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcNodeRegistry.java index a889db6..acecd33 100644 --- a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcNodeRegistry.java +++ b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcNodeRegistry.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package ch.dissem.bitmessage.repository; import ch.dissem.bitmessage.entity.valueobject.NetworkAddress; @@ -33,7 +49,7 @@ public class JdbcNodeRegistry extends JdbcHelper implements NodeRegistry { PreparedStatement ps = connection.prepareStatement( "DELETE FROM Node WHERE time<?") ) { - ps.setLong(1, now(-28 * DAY)); + ps.setLong(1, now() - 28 * DAY); ps.executeUpdate(); } catch (SQLException e) { LOG.error(e.getMessage(), e); @@ -135,7 +151,7 @@ public class JdbcNodeRegistry extends JdbcHelper implements NodeRegistry { public void offerAddresses(List<NetworkAddress> nodes) { cleanUp(); nodes.stream() - .filter(node -> node.getTime() < now(+2 * MINUTE) && node.getTime() > now(-28 * DAY)) + .filter(node -> node.getTime() < now() + 2 * MINUTE && node.getTime() > now() - 28 * DAY) .forEach(node -> { synchronized (this) { NetworkAddress existing = loadExisting(node); diff --git a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepository.java b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepository.java index 0fda3fa..2af1450 100644 --- a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepository.java +++ b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepository.java @@ -1,3 +1,19 @@ +/* + * Copyright 2016 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package ch.dissem.bitmessage.repository; import ch.dissem.bitmessage.InternalContext; @@ -30,9 +46,9 @@ public class JdbcProofOfWorkRepository extends JdbcHelper implements ProofOfWork @Override public Item getItem(byte[] initialHash) { try ( - Connection connection = config.getConnection(); - PreparedStatement ps = connection.prepareStatement("SELECT data, version, nonce_trials_per_byte, " + - "extra_bytes, expiration_time, message_id FROM POW WHERE initial_hash=?") + Connection connection = config.getConnection(); + PreparedStatement ps = connection.prepareStatement("SELECT data, version, nonce_trials_per_byte, " + + "extra_bytes, expiration_time, message_id FROM POW WHERE initial_hash=?") ) { ps.setBytes(1, initialHash); try (ResultSet rs = ps.executeQuery()) { @@ -40,17 +56,17 @@ public class JdbcProofOfWorkRepository extends JdbcHelper implements ProofOfWork Blob data = rs.getBlob("data"); if (rs.getObject("message_id") == null) { return new Item( - Factory.getObjectMessage(rs.getInt("version"), data.getBinaryStream(), (int) data.length()), - rs.getLong("nonce_trials_per_byte"), - rs.getLong("extra_bytes") + Factory.getObjectMessage(rs.getInt("version"), data.getBinaryStream(), (int) data.length()), + rs.getLong("nonce_trials_per_byte"), + rs.getLong("extra_bytes") ); } else { return new Item( - Factory.getObjectMessage(rs.getInt("version"), data.getBinaryStream(), (int) data.length()), - rs.getLong("nonce_trials_per_byte"), - rs.getLong("extra_bytes"), - rs.getLong("expiration_time"), - ctx.getMessageRepository().getMessage(rs.getLong("message_id")) + Factory.getObjectMessage(rs.getInt("version"), data.getBinaryStream(), (int) data.length()), + rs.getLong("nonce_trials_per_byte"), + rs.getLong("extra_bytes"), + rs.getLong("expiration_time"), + ctx.getMessageRepository().getMessage(rs.getLong("message_id")) ); } } else { @@ -66,9 +82,9 @@ public class JdbcProofOfWorkRepository extends JdbcHelper implements ProofOfWork @Override public List<byte[]> getItems() { try ( - Connection connection = config.getConnection(); - Statement stmt = connection.createStatement(); - ResultSet rs = stmt.executeQuery("SELECT initial_hash FROM POW") + Connection connection = config.getConnection(); + Statement stmt = connection.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT initial_hash FROM POW") ) { List<byte[]> result = new LinkedList<>(); while (rs.next()) { @@ -84,27 +100,27 @@ public class JdbcProofOfWorkRepository extends JdbcHelper implements ProofOfWork @Override public void putObject(Item item) { try ( - Connection connection = config.getConnection(); - PreparedStatement ps = connection.prepareStatement("INSERT INTO POW (initial_hash, data, version, " + - "nonce_trials_per_byte, extra_bytes, expiration_time, message_id) " + - "VALUES (?, ?, ?, ?, ?, ?, ?)") + Connection connection = config.getConnection(); + PreparedStatement ps = connection.prepareStatement("INSERT INTO POW (initial_hash, data, version, " + + "nonce_trials_per_byte, extra_bytes, expiration_time, message_id) " + + "VALUES (?, ?, ?, ?, ?, ?, ?)") ) { - ps.setBytes(1, cryptography().getInitialHash(item.object)); - writeBlob(ps, 2, item.object); - ps.setLong(3, item.object.getVersion()); - ps.setLong(4, item.nonceTrialsPerByte); - ps.setLong(5, item.extraBytes); + ps.setBytes(1, cryptography().getInitialHash(item.getObject())); + writeBlob(ps, 2, item.getObject()); + ps.setLong(3, item.getObject().getVersion()); + ps.setLong(4, item.getNonceTrialsPerByte()); + ps.setLong(5, item.getExtraBytes()); - if (item.message == null) { + if (item.getMessage() == null) { ps.setObject(6, null); ps.setObject(7, null); } else { - ps.setLong(6, item.expirationTime); - ps.setLong(7, (Long) item.message.getId()); + ps.setLong(6, item.getExpirationTime()); + ps.setLong(7, (Long) item.getMessage().getId()); } ps.executeUpdate(); } catch (IOException | SQLException e) { - LOG.debug("Error storing object of type " + item.object.getPayload().getClass().getSimpleName(), e); + LOG.debug("Error storing object of type " + item.getObject().getPayload().getClass().getSimpleName(), e); throw new ApplicationException(e); } } @@ -117,8 +133,8 @@ public class JdbcProofOfWorkRepository extends JdbcHelper implements ProofOfWork @Override public void removeObject(byte[] initialHash) { try ( - Connection connection = config.getConnection(); - PreparedStatement ps = connection.prepareStatement("DELETE FROM POW WHERE initial_hash=?") + Connection connection = config.getConnection(); + PreparedStatement ps = connection.prepareStatement("DELETE FROM POW WHERE initial_hash=?") ) { ps.setBytes(1, initialHash); ps.executeUpdate(); diff --git a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcAddressRepositoryTest.java b/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcAddressRepositoryTest.java index 7f3098b..c028837 100644 --- a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcAddressRepositoryTest.java +++ b/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcAddressRepositoryTest.java @@ -17,7 +17,6 @@ package ch.dissem.bitmessage.repository; import ch.dissem.bitmessage.entity.BitmessageAddress; -import ch.dissem.bitmessage.entity.payload.Pubkey; import ch.dissem.bitmessage.entity.valueobject.PrivateKey; import org.junit.Before; import org.junit.Test; @@ -72,7 +71,7 @@ public class JdbcAddressRepositoryTest extends TestBase { BitmessageAddress storedIdentity = repo.findIdentity(identity.getTag()); assertEquals(identity, storedIdentity); - assertTrue(storedIdentity.has(Pubkey.Feature.DOES_ACK)); + assertTrue(storedIdentity.has(DOES_ACK)); } @Test @@ -181,4 +180,4 @@ public class JdbcAddressRepositoryTest extends TestBase { subscription.setSubscribed(true); repo.save(subscription); } -} \ No newline at end of file +} diff --git a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcMessageRepositoryTest.java b/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcMessageRepositoryTest.java index 43be79f..dd0bd07 100644 --- a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcMessageRepositoryTest.java +++ b/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcMessageRepositoryTest.java @@ -25,8 +25,7 @@ import ch.dissem.bitmessage.entity.valueobject.ExtendedEncoding; import ch.dissem.bitmessage.entity.valueobject.Label; import ch.dissem.bitmessage.entity.valueobject.PrivateKey; import ch.dissem.bitmessage.entity.valueobject.extended.Message; -import ch.dissem.bitmessage.ports.AddressRepository; -import ch.dissem.bitmessage.ports.MessageRepository; +import ch.dissem.bitmessage.ports.*; import ch.dissem.bitmessage.utils.TestUtils; import ch.dissem.bitmessage.utils.UnixTime; import org.hamcrest.BaseMatcher; @@ -45,6 +44,7 @@ import static ch.dissem.bitmessage.utils.Singleton.cryptography; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; +import static org.mockito.Mockito.mock; public class JdbcMessageRepositoryTest extends TestBase { private BitmessageAddress contactA; @@ -64,12 +64,21 @@ public class JdbcMessageRepositoryTest extends TestBase { config.reset(); AddressRepository addressRepo = new JdbcAddressRepository(config); repo = new JdbcMessageRepository(config); - new InternalContext(new BitmessageContext.Builder() - .cryptography(cryptography()) - .addressRepo(addressRepo) - .messageRepo(repo) + new InternalContext( + cryptography(), + mock(Inventory.class), + mock(NodeRegistry.class), + mock(NetworkHandler.class), + addressRepo, + repo, + mock(ProofOfWorkRepository.class), + mock(ProofOfWorkEngine.class), + mock(CustomCommandHandler.class), + mock(BitmessageContext.Listener.class), + mock(Labeler.class), + 12345, + 10, 10 ); - BitmessageAddress tmp = new BitmessageAddress(new PrivateKey(false, 1, 1000, 1000, DOES_ACK)); contactA = new BitmessageAddress(tmp.getAddress()); contactA.setPubkey(tmp.getPubkey()); @@ -225,7 +234,7 @@ public class JdbcMessageRepositoryTest extends TestBase { message.updateNextTry(); assertThat(message.getRetries(), is(1)); assertThat(message.getNextTry(), greaterThan(UnixTime.now())); - assertThat(message.getNextTry(), lessThanOrEqualTo(UnixTime.now(+2))); + assertThat(message.getNextTry(), lessThanOrEqualTo(UnixTime.now() + 2)); repo.save(message); Thread.sleep(4100); // somewhat longer than 2*TTL List<Plaintext> messagesToResend = repo.findMessagesToResend(); diff --git a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcNodeRegistryTest.java b/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcNodeRegistryTest.java index 48ae664..1078e24 100644 --- a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcNodeRegistryTest.java +++ b/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcNodeRegistryTest.java @@ -18,7 +18,6 @@ package ch.dissem.bitmessage.repository; import ch.dissem.bitmessage.entity.valueobject.NetworkAddress; import ch.dissem.bitmessage.ports.NodeRegistry; -import ch.dissem.bitmessage.utils.UnixTime; import org.junit.Before; import org.junit.Test; @@ -28,7 +27,6 @@ import java.util.List; import static ch.dissem.bitmessage.utils.UnixTime.now; import static org.hamcrest.Matchers.empty; -import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; diff --git a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepositoryTest.java b/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepositoryTest.java deleted file mode 100644 index 4396eb5..0000000 --- a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepositoryTest.java +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright 2016 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.repository; - -import ch.dissem.bitmessage.BitmessageContext; -import ch.dissem.bitmessage.InternalContext; -import ch.dissem.bitmessage.entity.BitmessageAddress; -import ch.dissem.bitmessage.entity.ObjectMessage; -import ch.dissem.bitmessage.entity.Plaintext; -import ch.dissem.bitmessage.entity.payload.GenericPayload; -import ch.dissem.bitmessage.entity.payload.GetPubkey; -import ch.dissem.bitmessage.ports.AddressRepository; -import ch.dissem.bitmessage.ports.MessageRepository; -import ch.dissem.bitmessage.ports.ProofOfWorkRepository.Item; -import ch.dissem.bitmessage.utils.TestUtils; -import ch.dissem.bitmessage.utils.UnixTime; -import org.junit.Before; -import org.junit.Test; - -import static ch.dissem.bitmessage.entity.Plaintext.Type.MSG; -import static ch.dissem.bitmessage.utils.Singleton.cryptography; -import static ch.dissem.bitmessage.utils.UnixTime.MINUTE; -import static org.hamcrest.CoreMatchers.*; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; - -/** - * @author Christian Basler - */ -public class JdbcProofOfWorkRepositoryTest extends TestBase { - private TestJdbcConfig config; - private JdbcProofOfWorkRepository repo; - private AddressRepository addressRepo; - private MessageRepository messageRepo; - - private byte[] initialHash1; - private byte[] initialHash2; - - @Before - public void setUp() throws Exception { - config = new TestJdbcConfig(); - config.reset(); - - addressRepo = new JdbcAddressRepository(config); - messageRepo = new JdbcMessageRepository(config); - repo = new JdbcProofOfWorkRepository(config); - InternalContext ctx = new InternalContext(new BitmessageContext.Builder() - .addressRepo(addressRepo) - .messageRepo(messageRepo) - .powRepo(repo) - .cryptography(cryptography()) - ); - - repo.putObject(new ObjectMessage.Builder() - .payload(new GetPubkey(new BitmessageAddress("BM-2DAjcCFrqFrp88FUxExhJ9kPqHdunQmiyn"))).build(), - 1000, 1000); - initialHash1 = repo.getItems().get(0); - - BitmessageAddress sender = TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"); - BitmessageAddress recipient = TestUtils.loadContact(); - addressRepo.save(sender); - addressRepo.save(recipient); - Plaintext plaintext = new Plaintext.Builder(MSG) - .ackData(cryptography().randomBytes(32)) - .from(sender) - .to(recipient) - .message("Subject", "Message") - .status(Plaintext.Status.DOING_PROOF_OF_WORK) - .build(); - messageRepo.save(plaintext); - initialHash2 = cryptography().getInitialHash(plaintext.getAckMessage()); - repo.putObject(new Item( - plaintext.getAckMessage(), - 1000, 1000, - UnixTime.now(+10 * MINUTE), - plaintext - )); - } - - @Test - public void ensureObjectIsStored() throws Exception { - int sizeBefore = repo.getItems().size(); - repo.putObject(new ObjectMessage.Builder() - .payload(new GetPubkey(new BitmessageAddress("BM-2D9U2hv3YBMHM1zERP32anKfVKohyPN9x2"))).build(), - 1000, 1000); - assertThat(repo.getItems().size(), is(sizeBefore + 1)); - } - - @Test - public void ensureAckObjectsAreStored() throws Exception { - int sizeBefore = repo.getItems().size(); - BitmessageAddress sender = TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"); - BitmessageAddress recipient = TestUtils.loadContact(); - addressRepo.save(sender); - addressRepo.save(recipient); - Plaintext plaintext = new Plaintext.Builder(MSG) - .ackData(cryptography().randomBytes(32)) - .from(sender) - .to(recipient) - .message("Subject", "Message") - .status(Plaintext.Status.DOING_PROOF_OF_WORK) - .build(); - messageRepo.save(plaintext); - repo.putObject(new Item( - plaintext.getAckMessage(), - 1000, 1000, - UnixTime.now(+10 * MINUTE), - plaintext - )); - assertThat(repo.getItems().size(), is(sizeBefore + 1)); - } - - @Test - public void ensureItemCanBeRetrieved() { - Item item = repo.getItem(initialHash1); - assertThat(item, notNullValue()); - assertThat(item.object.getPayload(), instanceOf(GetPubkey.class)); - assertThat(item.nonceTrialsPerByte, is(1000L)); - assertThat(item.extraBytes, is(1000L)); - } - - @Test - public void ensureAckItemCanBeRetrieved() { - Item item = repo.getItem(initialHash2); - assertThat(item, notNullValue()); - assertThat(item.object.getPayload(), instanceOf(GenericPayload.class)); - assertThat(item.nonceTrialsPerByte, is(1000L)); - assertThat(item.extraBytes, is(1000L)); - assertThat(item.expirationTime, not(0)); - assertThat(item.message, notNullValue()); - assertThat(item.message.getFrom().getPrivateKey(), notNullValue()); - assertThat(item.message.getTo().getPubkey(), notNullValue()); - } - - @Test(expected = RuntimeException.class) - public void ensureRetrievingNonexistingItemThrowsException() { - repo.getItem(new byte[0]); - } - - @Test - public void ensureItemCanBeDeleted() { - repo.removeObject(initialHash1); - repo.removeObject(initialHash2); - assertTrue(repo.getItems().isEmpty()); - } - - @Test - public void ensureDeletionOfNonexistingItemIsHandledSilently() { - repo.removeObject(new byte[0]); - } -} diff --git a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepositoryTest.kt b/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepositoryTest.kt new file mode 100644 index 0000000..76031e9 --- /dev/null +++ b/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepositoryTest.kt @@ -0,0 +1,166 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.repository + +import ch.dissem.bitmessage.entity.BitmessageAddress +import ch.dissem.bitmessage.entity.ObjectMessage +import ch.dissem.bitmessage.entity.Plaintext +import ch.dissem.bitmessage.entity.Plaintext.Type.MSG +import ch.dissem.bitmessage.entity.payload.GenericPayload +import ch.dissem.bitmessage.entity.payload.GetPubkey +import ch.dissem.bitmessage.entity.payload.ObjectPayload +import ch.dissem.bitmessage.entity.payload.Pubkey +import ch.dissem.bitmessage.entity.valueobject.PrivateKey +import ch.dissem.bitmessage.ports.AddressRepository +import ch.dissem.bitmessage.ports.MessageRepository +import ch.dissem.bitmessage.ports.ProofOfWorkRepository.Item +import ch.dissem.bitmessage.utils.Singleton.cryptography +import ch.dissem.bitmessage.utils.TestUtils +import ch.dissem.bitmessage.utils.UnixTime +import ch.dissem.bitmessage.utils.UnixTime.MINUTE +import org.hamcrest.CoreMatchers.* +import org.junit.Assert.assertThat +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import kotlin.properties.Delegates + +/** + * @author Christian Basler + */ +class JdbcProofOfWorkRepositoryTest : TestBase() { + private var config: TestJdbcConfig by Delegates.notNull<TestJdbcConfig>() + private var repo: JdbcProofOfWorkRepository by Delegates.notNull<JdbcProofOfWorkRepository>() + private var addressRepo: AddressRepository by Delegates.notNull<AddressRepository>() + private var messageRepo: MessageRepository by Delegates.notNull<MessageRepository>() + + private var initialHash1: ByteArray by Delegates.notNull<ByteArray>() + private var initialHash2: ByteArray by Delegates.notNull<ByteArray>() + + @Before + fun setUp() { + config = TestJdbcConfig() + config.reset() + + addressRepo = JdbcAddressRepository(config) + messageRepo = JdbcMessageRepository(config) + repo = JdbcProofOfWorkRepository(config) + TestUtils.mockedInternalContext( + addressRepository = addressRepo, + messageRepository = messageRepo, + proofOfWorkRepository = repo, + cryptography = cryptography() + ) + + repo.putObject(ObjectMessage.Builder() + .payload(GetPubkey(BitmessageAddress("BM-2DAjcCFrqFrp88FUxExhJ9kPqHdunQmiyn"))).build(), + 1000, 1000) + initialHash1 = repo.getItems()[0] + + val sender = TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8") + val recipient = TestUtils.loadContact() + addressRepo.save(sender) + addressRepo.save(recipient) + val plaintext = Plaintext.Builder(MSG) + .ackData(cryptography().randomBytes(32)) + .from(sender) + .to(recipient) + .message("Subject", "Message") + .status(Plaintext.Status.DOING_PROOF_OF_WORK) + .build() + messageRepo.save(plaintext) + initialHash2 = cryptography().getInitialHash(plaintext.ackMessage!!) + repo.putObject(Item( + plaintext.ackMessage!!, + 1000, 1000, + UnixTime.now + 10 * MINUTE, + plaintext + )) + } + + @Test + fun `ensure object is stored`() { + val sizeBefore = repo.getItems().size + repo.putObject(ObjectMessage.Builder() + .payload(GetPubkey(BitmessageAddress("BM-2D9U2hv3YBMHM1zERP32anKfVKohyPN9x2"))).build(), + 1000, 1000) + assertThat(repo.getItems().size, `is`(sizeBefore + 1)) + } + + @Test + fun `ensure ack objects are stored`() { + val sizeBefore = repo.getItems().size + val sender = TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8") + val recipient = TestUtils.loadContact() + addressRepo.save(sender) + addressRepo.save(recipient) + val plaintext = Plaintext.Builder(MSG) + .ackData(cryptography().randomBytes(32)) + .from(sender) + .to(recipient) + .message("Subject", "Message") + .status(Plaintext.Status.DOING_PROOF_OF_WORK) + .build() + messageRepo.save(plaintext) + repo.putObject(Item( + plaintext.ackMessage!!, + 1000, 1000, + UnixTime.now + 10 * MINUTE, + plaintext + )) + assertThat(repo.getItems().size, `is`(sizeBefore + 1)) + } + + @Test + fun `ensure item can be retrieved`() { + val item = repo.getItem(initialHash1) + assertThat(item, notNullValue()) + assertThat<ObjectPayload>(item.`object`.payload, instanceOf<ObjectPayload>(GetPubkey::class.java)) + assertThat(item.nonceTrialsPerByte, `is`(1000L)) + assertThat(item.extraBytes, `is`(1000L)) + } + + @Test + fun `ensure ack item can be retrieved`() { + val item = repo.getItem(initialHash2) + assertThat(item, notNullValue()) + assertThat<ObjectPayload>(item.`object`.payload, instanceOf<ObjectPayload>(GenericPayload::class.java)) + assertThat(item.nonceTrialsPerByte, `is`(1000L)) + assertThat(item.extraBytes, `is`(1000L)) + assertThat(item.expirationTime, not<Number>(0)) + assertThat(item.message, notNullValue()) + assertThat<PrivateKey>(item.message?.from?.privateKey, notNullValue()) + assertThat<Pubkey>(item.message?.to?.pubkey, notNullValue()) + } + + @Test(expected = RuntimeException::class) + fun `ensure retrieving nonexisting item causes exception`() { + repo.getItem(ByteArray(0)) + } + + @Test + fun `ensure item can be deleted`() { + repo.removeObject(initialHash1) + repo.removeObject(initialHash2) + assertTrue(repo.getItems().isEmpty()) + } + + @Test + fun `ensure deletion of nonexisting item is handled silently`() { + repo.removeObject(ByteArray(0)) + } +} diff --git a/repositories/src/test/java/ch/dissem/bitmessage/repository/TestBase.java b/repositories/src/test/java/ch/dissem/bitmessage/repository/TestBase.java deleted file mode 100644 index be386cd..0000000 --- a/repositories/src/test/java/ch/dissem/bitmessage/repository/TestBase.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.repository; - -import ch.dissem.bitmessage.InternalContext; -import ch.dissem.bitmessage.ports.MultiThreadedPOWEngine; -import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography; -import ch.dissem.bitmessage.utils.Singleton; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -/** - * Created by chris on 20.07.15. - */ -public class TestBase { - static { - BouncyCryptography security = new BouncyCryptography(); - Singleton.initialize(security); - InternalContext ctx = mock(InternalContext.class); - when(ctx.getProofOfWorkEngine()).thenReturn(new MultiThreadedPOWEngine()); - security.setContext(ctx); - } -} diff --git a/repositories/src/test/java/ch/dissem/bitmessage/repository/TestBase.kt b/repositories/src/test/java/ch/dissem/bitmessage/repository/TestBase.kt new file mode 100644 index 0000000..107e462 --- /dev/null +++ b/repositories/src/test/java/ch/dissem/bitmessage/repository/TestBase.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.repository + +import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography +import ch.dissem.bitmessage.ports.MultiThreadedPOWEngine +import ch.dissem.bitmessage.utils.Singleton +import ch.dissem.bitmessage.utils.TestUtils.mockedInternalContext + +/** + * Created by chris on 20.07.15. + */ +open class TestBase { + companion object { + init { + val security = BouncyCryptography() + Singleton.initialize(security) + mockedInternalContext( + cryptography = security, + proofOfWorkEngine = MultiThreadedPOWEngine() + ) + } + } +} From 83e50e1ad1e710a30de631dde3f3554a88965a24 Mon Sep 17 00:00:00 2001 From: Christian Basler <chrigu.meyer@gmail.com> Date: Thu, 8 Jun 2017 16:59:24 +0200 Subject: [PATCH 02/10] Migrated BitmessageContext and fixed some tests --- .../dissem/bitmessage/BitmessageContext.java | 452 ------------------ .../ch/dissem/bitmessage/BitmessageContext.kt | 451 +++++++++++++++++ .../ch/dissem/bitmessage/entity/Plaintext.kt | 42 +- .../dissem/bitmessage/ports/DefaultLabeler.kt | 3 +- .../bitmessage/BitmessageContextTest.kt | 46 +- cryptography-bc/build.gradle | 1 + .../bitmessage/security/CryptographyTest.java | 172 ------- .../bitmessage/security/CryptographyTest.kt | 172 +++++++ cryptography-sc/build.gradle | 1 + .../bitmessage/security/CryptographyTest.java | 174 ------- .../bitmessage/security/CryptographyTest.kt | 177 +++++++ .../repository/JdbcInventoryTest.java | 12 +- 12 files changed, 872 insertions(+), 831 deletions(-) delete mode 100644 core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java create mode 100644 core/src/main/java/ch/dissem/bitmessage/BitmessageContext.kt delete mode 100644 cryptography-bc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.java create mode 100644 cryptography-bc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.kt delete mode 100644 cryptography-sc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.java create mode 100644 cryptography-sc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java b/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java deleted file mode 100644 index 4bf0adc..0000000 --- a/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.java +++ /dev/null @@ -1,452 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage; - -import ch.dissem.bitmessage.entity.*; -import ch.dissem.bitmessage.entity.payload.Broadcast; -import ch.dissem.bitmessage.entity.payload.ObjectType; -import ch.dissem.bitmessage.entity.payload.Pubkey.Feature; -import ch.dissem.bitmessage.entity.valueobject.PrivateKey; -import ch.dissem.bitmessage.exception.DecryptionFailedException; -import ch.dissem.bitmessage.factory.Factory; -import ch.dissem.bitmessage.ports.*; -import ch.dissem.bitmessage.utils.Property; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.net.InetAddress; -import java.util.List; -import java.util.concurrent.CancellationException; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; - -import static ch.dissem.bitmessage.InternalContext.NETWORK_EXTRA_BYTES; -import static ch.dissem.bitmessage.InternalContext.NETWORK_NONCE_TRIALS_PER_BYTE; -import static ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST; -import static ch.dissem.bitmessage.entity.Plaintext.Type.MSG; -import static ch.dissem.bitmessage.utils.UnixTime.HOUR; -import static ch.dissem.bitmessage.utils.UnixTime.MINUTE; - -/** - * <p>Use this class if you want to create a Bitmessage client.</p> - * You'll need the Builder to create a BitmessageContext, and set the following properties: - * <ul> - * <li>addressRepo</li> - * <li>inventory</li> - * <li>nodeRegistry</li> - * <li>networkHandler</li> - * <li>messageRepo</li> - * <li>streams</li> - * </ul> - * <p>The default implementations in the different module builds can be used.</p> - * <p>The port defaults to 8444 (the default Bitmessage port)</p> - */ -public class BitmessageContext { - public static final int CURRENT_VERSION = 3; - private final static Logger LOG = LoggerFactory.getLogger(BitmessageContext.class); - - private final InternalContext ctx; - - private final Labeler labeler; - - private final boolean sendPubkeyOnIdentityCreation; - - private BitmessageContext(Builder builder) { - ctx = new InternalContext( - builder.cryptography, - builder.inventory, - builder.nodeRegistry, - builder.networkHandler, - builder.addressRepo, - builder.messageRepo, - builder.proofOfWorkRepository, - builder.proofOfWorkEngine, - builder.customCommandHandler, - builder.listener, - builder.labeler, - builder.port, - builder.connectionTTL, - builder.connectionLimit - ); - labeler = builder.labeler; - ctx.getProofOfWorkService().doMissingProofOfWork(30_000); // TODO: this should be configurable - sendPubkeyOnIdentityCreation = builder.sendPubkeyOnIdentityCreation; - if (builder.listener instanceof Listener.WithContext) { - ((Listener.WithContext) builder.listener).setContext(this); - } - } - - public AddressRepository addresses() { - return ctx.getAddressRepository(); - } - - public MessageRepository messages() { - return ctx.getMessageRepository(); - } - - public Labeler labeler() { - return labeler; - } - - public BitmessageAddress createIdentity(boolean shorter, Feature... features) { - final BitmessageAddress identity = new BitmessageAddress(new PrivateKey( - shorter, - ctx.getStreams()[0], - NETWORK_NONCE_TRIALS_PER_BYTE, - NETWORK_EXTRA_BYTES, - features - )); - ctx.getAddressRepository().save(identity); - if (sendPubkeyOnIdentityCreation) { - ctx.sendPubkey(identity, identity.getStream()); - } - return identity; - } - - public BitmessageAddress joinChan(String passphrase, String address) { - BitmessageAddress chan = BitmessageAddress.Companion.chan(address, passphrase); - chan.setAlias(passphrase); - ctx.getAddressRepository().save(chan); - return chan; - } - - public BitmessageAddress createChan(String passphrase) { - // FIXME: hardcoded stream number - BitmessageAddress chan = BitmessageAddress.Companion.chan(1, passphrase); - ctx.getAddressRepository().save(chan); - return chan; - } - - public List<BitmessageAddress> createDeterministicAddresses( - String passphrase, int numberOfAddresses, long version, long stream, boolean shorter) { - List<BitmessageAddress> result = BitmessageAddress.Companion.deterministic( - passphrase, numberOfAddresses, version, stream, shorter); - for (int i = 0; i < result.size(); i++) { - BitmessageAddress address = result.get(i); - address.setAlias("deterministic (" + (i + 1) + ")"); - ctx.getAddressRepository().save(address); - } - return result; - } - - public void broadcast(final BitmessageAddress from, final String subject, final String message) { - Plaintext msg = new Plaintext.Builder(BROADCAST) - .from(from) - .message(subject, message) - .build(); - send(msg); - } - - public void send(final BitmessageAddress from, final BitmessageAddress to, final String subject, final String message) { - if (from.getPrivateKey() == null) { - throw new IllegalArgumentException("'From' must be an identity, i.e. have a private key."); - } - Plaintext msg = new Plaintext.Builder(MSG) - .from(from) - .to(to) - .message(subject, message) - .build(); - send(msg); - } - - public void send(final Plaintext msg) { - if (msg.getFrom().getPrivateKey() == null) { - throw new IllegalArgumentException("'From' must be an identity, i.e. have a private key."); - } - labeler().markAsSending(msg); - BitmessageAddress to = msg.getTo(); - if (to != null) { - if (to.getPubkey() == null) { - LOG.info("Public key is missing from recipient. Requesting."); - ctx.requestPubkey(to); - } - if (to.getPubkey() == null) { - ctx.getMessageRepository().save(msg); - } - } - if (to == null || to.getPubkey() != null) { - LOG.info("Sending message."); - ctx.getMessageRepository().save(msg); - if (msg.getType() == MSG) { - ctx.send(msg); - } else { - ctx.send( - msg.getFrom(), - to, - Factory.getBroadcast(msg), - msg.getTTL() - ); - } - } - } - - public void startup() { - ctx.getNetworkHandler().start(); - } - - public void shutdown() { - ctx.getNetworkHandler().stop(); - } - - /** - * @param host a trusted node that must be reliable (it's used for every synchronization) - * @param port of the trusted host, default is 8444 - * @param timeoutInSeconds synchronization should end no later than about 5 seconds after the timeout elapsed, even - * if not all objects were fetched - * @param wait waits for the synchronization thread to finish - */ - public void synchronize(InetAddress host, int port, long timeoutInSeconds, boolean wait) { - Future<?> future = ctx.getNetworkHandler().synchronize(host, port, timeoutInSeconds); - if (wait) { - try { - future.get(); - } catch (InterruptedException e) { - LOG.info("Thread was interrupted. Trying to shut down synchronization and returning."); - future.cancel(true); - } catch (CancellationException | ExecutionException e) { - LOG.debug(e.getMessage(), e); - } - } - } - - /** - * Send a custom message to a specific node (that should implement handling for this message type) and returns - * the response, which in turn is expected to be a {@link CustomMessage}. - * - * @param server the node's address - * @param port the node's port - * @param request the request - * @return the response - */ - public CustomMessage send(InetAddress server, int port, CustomMessage request) { - return ctx.getNetworkHandler().send(server, port, request); - } - - /** - * Removes expired objects from the inventory. You should call this method regularly, - * e.g. daily and on each shutdown. - */ - public void cleanup() { - ctx.getInventory().cleanup(); - } - - /** - * Sends messages again whose time to live expired without being acknowledged. (And whose - * recipient is expected to send acknowledgements. - * <p> - * You should call this method regularly, but be aware of the following: - * <ul> - * <li>As messages might be sent, POW will be done. It is therefore not advised to - * call it on shutdown.</li> - * <li>It shouldn't be called right after startup, as it's possible the missing - * acknowledgement was sent while the client was offline.</li> - * <li>Other than that, the call isn't expensive as long as there is no message - * to send, so it might be a good idea to just call it every few minutes.</li> - * </ul> - */ - public void resendUnacknowledgedMessages() { - ctx.resendUnacknowledged(); - } - - public boolean isRunning() { - return ctx.getNetworkHandler().isRunning(); - } - - public void addContact(BitmessageAddress contact) { - ctx.getAddressRepository().save(contact); - if (contact.getPubkey() == null) { - BitmessageAddress stored = ctx.getAddressRepository().getAddress(contact.getAddress()); - if (stored.getPubkey() == null) { - ctx.requestPubkey(contact); - } - } - } - - public void addSubscribtion(BitmessageAddress address) { - address.setSubscribed(true); - ctx.getAddressRepository().save(address); - tryToFindBroadcastsForAddress(address); - } - - private void tryToFindBroadcastsForAddress(BitmessageAddress address) { - for (ObjectMessage object : ctx.getInventory().getObjects(address.getStream(), Broadcast.Companion.getVersion(address), ObjectType.BROADCAST)) { - try { - Broadcast broadcast = (Broadcast) object.getPayload(); - broadcast.decrypt(address); - // This decrypts it twice, but on the other hand it doesn't try to decrypt the objects with - // other subscriptions and the interface stays as simple as possible. - ctx.getNetworkListener().receive(object); - } catch (DecryptionFailedException ignore) { - } catch (Exception e) { - LOG.debug(e.getMessage(), e); - } - } - } - - public Property status() { - return new Property("status", null, - ctx.getNetworkHandler().getNetworkStatus(), - new Property("unacknowledged", ctx.getMessageRepository().findMessagesToResend().size()) - ); - } - - /** - * Returns the {@link InternalContext} - normally you wouldn't need it, - * unless you are doing something crazy with the protocol. - */ - public InternalContext internals() { - return ctx; - } - - public interface Listener { - void receive(Plaintext plaintext); - - /** - * A message listener that needs a {@link BitmessageContext}, i.e. for implementing some sort of chat bot. - */ - interface WithContext extends Listener { - void setContext(BitmessageContext ctx); - } - } - - public static final class Builder { - int port = 8444; - Inventory inventory; - NodeRegistry nodeRegistry; - NetworkHandler networkHandler; - AddressRepository addressRepo; - MessageRepository messageRepo; - ProofOfWorkRepository proofOfWorkRepository; - ProofOfWorkEngine proofOfWorkEngine; - Cryptography cryptography; - CustomCommandHandler customCommandHandler; - Labeler labeler; - Listener listener; - int connectionLimit = 150; - long connectionTTL = 30 * MINUTE; - boolean sendPubkeyOnIdentityCreation = true; - - public Builder port(int port) { - this.port = port; - return this; - } - - public Builder inventory(Inventory inventory) { - this.inventory = inventory; - return this; - } - - public Builder nodeRegistry(NodeRegistry nodeRegistry) { - this.nodeRegistry = nodeRegistry; - return this; - } - - public Builder networkHandler(NetworkHandler networkHandler) { - this.networkHandler = networkHandler; - return this; - } - - public Builder addressRepo(AddressRepository addressRepo) { - this.addressRepo = addressRepo; - return this; - } - - public Builder messageRepo(MessageRepository messageRepo) { - this.messageRepo = messageRepo; - return this; - } - - public Builder powRepo(ProofOfWorkRepository proofOfWorkRepository) { - this.proofOfWorkRepository = proofOfWorkRepository; - return this; - } - - public Builder cryptography(Cryptography cryptography) { - this.cryptography = cryptography; - return this; - } - - public Builder customCommandHandler(CustomCommandHandler handler) { - this.customCommandHandler = handler; - return this; - } - - public Builder proofOfWorkEngine(ProofOfWorkEngine proofOfWorkEngine) { - this.proofOfWorkEngine = proofOfWorkEngine; - return this; - } - - public Builder labeler(Labeler labeler) { - this.labeler = labeler; - return this; - } - - public Builder listener(Listener listener) { - this.listener = listener; - return this; - } - - public Builder connectionLimit(int connectionLimit) { - this.connectionLimit = connectionLimit; - return this; - } - - public Builder connectionTTL(int hours) { - this.connectionTTL = hours * HOUR; - return this; - } - - /** - * By default a client will send the public key when an identity is being created. On weaker devices - * this behaviour might not be desirable. - */ - public Builder doNotSendPubkeyOnIdentityCreation() { - this.sendPubkeyOnIdentityCreation = false; - return this; - } - - public BitmessageContext build() { - nonNull("inventory", inventory); - nonNull("nodeRegistry", nodeRegistry); - nonNull("networkHandler", networkHandler); - nonNull("addressRepo", addressRepo); - nonNull("messageRepo", messageRepo); - nonNull("proofOfWorkRepo", proofOfWorkRepository); - if (proofOfWorkEngine == null) { - proofOfWorkEngine = new MultiThreadedPOWEngine(); - } - if (labeler == null) { - labeler = new DefaultLabeler(); - } - if (customCommandHandler == null) { - customCommandHandler = new CustomCommandHandler() { - @Override - public MessagePayload handle(CustomMessage request) { - LOG.debug("Received custom request, but no custom command handler configured."); - return null; - } - }; - } - return new BitmessageContext(this); - } - - private void nonNull(String name, Object o) { - if (o == null) throw new IllegalStateException(name + " must not be null"); - } - } - -} diff --git a/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.kt b/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.kt new file mode 100644 index 0000000..ef4b1d6 --- /dev/null +++ b/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.kt @@ -0,0 +1,451 @@ +/* + * Copyright 2015 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage + +import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_EXTRA_BYTES +import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_NONCE_TRIALS_PER_BYTE +import ch.dissem.bitmessage.entity.BitmessageAddress +import ch.dissem.bitmessage.entity.CustomMessage +import ch.dissem.bitmessage.entity.MessagePayload +import ch.dissem.bitmessage.entity.Plaintext +import ch.dissem.bitmessage.entity.Plaintext.Status.DRAFT +import ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST +import ch.dissem.bitmessage.entity.Plaintext.Type.MSG +import ch.dissem.bitmessage.entity.payload.Broadcast +import ch.dissem.bitmessage.entity.payload.ObjectType +import ch.dissem.bitmessage.entity.payload.Pubkey.Feature +import ch.dissem.bitmessage.entity.valueobject.PrivateKey +import ch.dissem.bitmessage.exception.DecryptionFailedException +import ch.dissem.bitmessage.factory.Factory +import ch.dissem.bitmessage.ports.* +import ch.dissem.bitmessage.utils.Property +import ch.dissem.bitmessage.utils.UnixTime.HOUR +import ch.dissem.bitmessage.utils.UnixTime.MINUTE +import org.slf4j.LoggerFactory +import java.net.InetAddress +import java.util.concurrent.CancellationException +import java.util.concurrent.ExecutionException +import kotlin.properties.Delegates + +/** + * + * Use this class if you want to create a Bitmessage client. + * You'll need the Builder to create a BitmessageContext, and set the following properties: + * + * * addressRepo + * * inventory + * * nodeRegistry + * * networkHandler + * * messageRepo + * * streams + * + * + * The default implementations in the different module builds can be used. + * + * The port defaults to 8444 (the default Bitmessage port) + */ +class BitmessageContext private constructor(builder: BitmessageContext.Builder) { + + private val sendPubkeyOnIdentityCreation: Boolean + + /** + * The [InternalContext] - normally you wouldn't need it, + * unless you are doing something crazy with the protocol. + */ + val internals: InternalContext + @JvmName("internals") get + + val labeler: Labeler + @JvmName("labeler") get + + init { + labeler = builder.labeler ?: DefaultLabeler() + internals = InternalContext( + builder.cryptography, + builder.inventory, + builder.nodeRegistry, + builder.networkHandler, + builder.addressRepo, + builder.messageRepo, + builder.proofOfWorkRepository, + builder.proofOfWorkEngine ?: MultiThreadedPOWEngine(), + builder.customCommandHandler ?: object : CustomCommandHandler { + override fun handle(request: CustomMessage): MessagePayload? { + LOG.debug("Received custom request, but no custom command handler configured.") + return null + } + }, + builder.listener, + labeler, + builder.port, + builder.connectionTTL, + builder.connectionLimit + ) + internals.proofOfWorkService.doMissingProofOfWork(30000) // TODO: this should be configurable + sendPubkeyOnIdentityCreation = builder.sendPubkeyOnIdentityCreation + (builder.listener as? Listener.WithContext)?.setContext(this) + } + + val addresses: AddressRepository = internals.addressRepository + @JvmName("addresses") get + + val messages: MessageRepository = internals.messageRepository + @JvmName("messages") get + + fun createIdentity(shorter: Boolean, vararg features: Feature): BitmessageAddress { + val identity = BitmessageAddress(PrivateKey( + shorter, + internals.streams[0], + NETWORK_NONCE_TRIALS_PER_BYTE, + NETWORK_EXTRA_BYTES, + *features + )) + internals.addressRepository.save(identity) + if (sendPubkeyOnIdentityCreation) { + internals.sendPubkey(identity, identity.stream) + } + return identity + } + + fun joinChan(passphrase: String, address: String): BitmessageAddress { + val chan = BitmessageAddress.chan(address, passphrase) + chan.alias = passphrase + internals.addressRepository.save(chan) + return chan + } + + fun createChan(passphrase: String): BitmessageAddress { + // FIXME: hardcoded stream number + val chan = BitmessageAddress.chan(1, passphrase) + internals.addressRepository.save(chan) + return chan + } + + fun createDeterministicAddresses( + passphrase: String, numberOfAddresses: Int, version: Long, stream: Long, shorter: Boolean): List<BitmessageAddress> { + val result = BitmessageAddress.deterministic( + passphrase, numberOfAddresses, version, stream, shorter) + for (i in result.indices) { + val address = result[i] + address.alias = "deterministic (" + (i + 1) + ")" + internals.addressRepository.save(address) + } + return result + } + + fun broadcast(from: BitmessageAddress, subject: String, message: String) { + send(Plaintext( + type = BROADCAST, + from = from, + subject = subject, + body = message, + status = DRAFT + )) + } + + fun send(from: BitmessageAddress, to: BitmessageAddress, subject: String, message: String) { + if (from.privateKey == null) { + throw IllegalArgumentException("'From' must be an identity, i.e. have a private key.") + } + send(Plaintext( + type = MSG, + from = from, + to = to, + subject = subject, + body = message + )) + } + + fun send(msg: Plaintext) { + if (msg.from.privateKey == null) { + throw IllegalArgumentException("'From' must be an identity, i.e. have a private key.") + } + labeler.markAsSending(msg) + val to = msg.to + if (to != null) { + if (to.pubkey == null) { + LOG.info("Public key is missing from recipient. Requesting.") + internals.requestPubkey(to) + } + if (to.pubkey == null) { + internals.messageRepository.save(msg) + } + } + if (to == null || to.pubkey != null) { + LOG.info("Sending message.") + internals.messageRepository.save(msg) + if (msg.type == MSG) { + internals.send(msg) + } else { + internals.send( + msg.from, + to, + Factory.getBroadcast(msg), + msg.ttl + ) + } + } + } + + fun startup() { + internals.networkHandler.start() + } + + fun shutdown() { + internals.networkHandler.stop() + } + + /** + * @param host a trusted node that must be reliable (it's used for every synchronization) + * * + * @param port of the trusted host, default is 8444 + * * + * @param timeoutInSeconds synchronization should end no later than about 5 seconds after the timeout elapsed, even + * * if not all objects were fetched + * * + * @param wait waits for the synchronization thread to finish + */ + fun synchronize(host: InetAddress, port: Int, timeoutInSeconds: Long, wait: Boolean) { + val future = internals.networkHandler.synchronize(host, port, timeoutInSeconds) + if (wait) { + try { + future.get() + } catch (e: InterruptedException) { + LOG.info("Thread was interrupted. Trying to shut down synchronization and returning.") + future.cancel(true) + } catch (e: CancellationException) { + LOG.debug(e.message, e) + } catch (e: ExecutionException) { + LOG.debug(e.message, e) + } + + } + } + + /** + * Send a custom message to a specific node (that should implement handling for this message type) and returns + * the response, which in turn is expected to be a [CustomMessage]. + + * @param server the node's address + * * + * @param port the node's port + * * + * @param request the request + * * + * @return the response + */ + fun send(server: InetAddress, port: Int, request: CustomMessage): CustomMessage { + return internals.networkHandler.send(server, port, request) + } + + /** + * Removes expired objects from the inventory. You should call this method regularly, + * e.g. daily and on each shutdown. + */ + fun cleanup() { + internals.inventory.cleanup() + } + + /** + * Sends messages again whose time to live expired without being acknowledged. (And whose + * recipient is expected to send acknowledgements. + * + * + * You should call this method regularly, but be aware of the following: + * + * * As messages might be sent, POW will be done. It is therefore not advised to + * call it on shutdown. + * * It shouldn't be called right after startup, as it's possible the missing + * acknowledgement was sent while the client was offline. + * * Other than that, the call isn't expensive as long as there is no message + * to send, so it might be a good idea to just call it every few minutes. + * + */ + fun resendUnacknowledgedMessages() { + internals.resendUnacknowledged() + } + + val isRunning: Boolean + get() = internals.networkHandler.isRunning + + fun addContact(contact: BitmessageAddress) { + internals.addressRepository.save(contact) + if (contact.pubkey == null) { + internals.addressRepository.getAddress(contact.address)?.let { + if (it.pubkey == null) { + internals.requestPubkey(contact) + } + } + } + } + + fun addSubscribtion(address: BitmessageAddress) { + address.isSubscribed = true + internals.addressRepository.save(address) + tryToFindBroadcastsForAddress(address) + } + + private fun tryToFindBroadcastsForAddress(address: BitmessageAddress) { + for (`object` in internals.inventory.getObjects(address.stream, Broadcast.getVersion(address), ObjectType.BROADCAST)) { + try { + val broadcast = `object`.payload as Broadcast + broadcast.decrypt(address) + // This decrypts it twice, but on the other hand it doesn't try to decrypt the objects with + // other subscriptions and the interface stays as simple as possible. + internals.networkListener.receive(`object`) + } catch (ignore: DecryptionFailedException) { + } catch (e: Exception) { + LOG.debug(e.message, e) + } + + } + } + + fun status(): Property { + return Property("status", + internals.networkHandler.networkStatus, + Property("unacknowledged", internals.messageRepository.findMessagesToResend().size) + ) + } + + interface Listener { + fun receive(plaintext: Plaintext) + + /** + * A message listener that needs a [BitmessageContext], i.e. for implementing some sort of chat bot. + */ + interface WithContext : Listener { + fun setContext(ctx: BitmessageContext) + } + } + + class Builder { + internal var port = 8444 + internal var inventory by Delegates.notNull<Inventory>() + internal var nodeRegistry by Delegates.notNull<NodeRegistry>() + internal var networkHandler by Delegates.notNull<NetworkHandler>() + internal var addressRepo by Delegates.notNull<AddressRepository>() + internal var messageRepo by Delegates.notNull<MessageRepository>() + internal var proofOfWorkRepository by Delegates.notNull<ProofOfWorkRepository>() + internal var proofOfWorkEngine: ProofOfWorkEngine? = null + internal var cryptography by Delegates.notNull<Cryptography>() + internal var customCommandHandler: CustomCommandHandler? = null + internal var labeler: Labeler? = null + internal var listener by Delegates.notNull<Listener>() + internal var connectionLimit = 150 + internal var connectionTTL = 30 * MINUTE + internal var sendPubkeyOnIdentityCreation = true + + fun port(port: Int): Builder { + this.port = port + return this + } + + fun inventory(inventory: Inventory): Builder { + this.inventory = inventory + return this + } + + fun nodeRegistry(nodeRegistry: NodeRegistry): Builder { + this.nodeRegistry = nodeRegistry + return this + } + + fun networkHandler(networkHandler: NetworkHandler): Builder { + this.networkHandler = networkHandler + return this + } + + fun addressRepo(addressRepo: AddressRepository): Builder { + this.addressRepo = addressRepo + return this + } + + fun messageRepo(messageRepo: MessageRepository): Builder { + this.messageRepo = messageRepo + return this + } + + fun powRepo(proofOfWorkRepository: ProofOfWorkRepository): Builder { + this.proofOfWorkRepository = proofOfWorkRepository + return this + } + + fun cryptography(cryptography: Cryptography): Builder { + this.cryptography = cryptography + return this + } + + fun customCommandHandler(handler: CustomCommandHandler): Builder { + this.customCommandHandler = handler + return this + } + + fun proofOfWorkEngine(proofOfWorkEngine: ProofOfWorkEngine): Builder { + this.proofOfWorkEngine = proofOfWorkEngine + return this + } + + fun labeler(labeler: Labeler): Builder { + this.labeler = labeler + return this + } + + fun listener(listener: Listener): Builder { + this.listener = listener + return this + } + + fun connectionLimit(connectionLimit: Int): Builder { + this.connectionLimit = connectionLimit + return this + } + + fun connectionTTL(hours: Int): Builder { + this.connectionTTL = hours * HOUR + return this + } + + /** + * By default a client will send the public key when an identity is being created. On weaker devices + * this behaviour might not be desirable. + */ + fun doNotSendPubkeyOnIdentityCreation(): Builder { + this.sendPubkeyOnIdentityCreation = false + return this + } + + fun build(): BitmessageContext { + nonNull("inventory", inventory) + nonNull("nodeRegistry", nodeRegistry) + nonNull("networkHandler", networkHandler) + nonNull("addressRepo", addressRepo) + nonNull("messageRepo", messageRepo) + nonNull("proofOfWorkRepo", proofOfWorkRepository) + return BitmessageContext(this) + } + + private fun nonNull(name: String, o: Any?) { + if (o == null) throw IllegalStateException(name + " must not be null") + } + } + + companion object { + @JvmField val CURRENT_VERSION = 3 + private val LOG = LoggerFactory.getLogger(BitmessageContext::class.java) + } +} diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.kt b/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.kt index 8946d73..fc4ee0a 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.kt +++ b/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.kt @@ -16,8 +16,7 @@ package ch.dissem.bitmessage.entity -import ch.dissem.bitmessage.entity.Plaintext.Encoding.EXTENDED -import ch.dissem.bitmessage.entity.Plaintext.Encoding.SIMPLE +import ch.dissem.bitmessage.entity.Plaintext.Encoding.* import ch.dissem.bitmessage.entity.payload.Msg import ch.dissem.bitmessage.entity.payload.Pubkey.Feature import ch.dissem.bitmessage.entity.valueobject.ExtendedEncoding @@ -36,6 +35,13 @@ import java.util.* import java.util.Collections import kotlin.collections.HashSet +fun message(encoding: Plaintext.Encoding, subject: String, body: String): ByteArray = when (encoding) { + SIMPLE -> "Subject:$subject\nBody:$body".toByteArray() + EXTENDED -> Message.Builder().subject(subject).body(body).build().zip() + TRIVIAL -> (subject+body).toByteArray() + IGNORE -> ByteArray(0) +} + /** * The unencrypted message to be sent by 'msg' or 'broadcast'. */ @@ -182,9 +188,39 @@ class Plaintext private constructor( status ) + constructor( + type: Type, + from: BitmessageAddress, + to: BitmessageAddress? = null, + encoding: Encoding = SIMPLE, + subject: String, + body: String, + ackData: ByteArray = cryptography().randomBytes(Msg.ACK_LENGTH), + conversationId: UUID = UUID.randomUUID(), + ttl: Long = TTL.msg, + labels: MutableSet<Label> = HashSet(), + status: Status = Status.DRAFT + ) : this( + type, + from, + to, + encoding.code, + message(encoding, subject, body), + ackData, + lazy { Factory.createAck(from, ackData, ttl) }, + conversationId, + null, + null, + null, + null, + ttl, + labels, + status + ) + constructor(builder: Builder) : this( builder.type, - builder.from!!, + builder.from ?: throw IllegalStateException("sender identity not set"), builder.to, builder.encoding, builder.message, diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/DefaultLabeler.kt b/core/src/main/java/ch/dissem/bitmessage/ports/DefaultLabeler.kt index 63148b7..43a4f6e 100644 --- a/core/src/main/java/ch/dissem/bitmessage/ports/DefaultLabeler.kt +++ b/core/src/main/java/ch/dissem/bitmessage/ports/DefaultLabeler.kt @@ -19,6 +19,7 @@ package ch.dissem.bitmessage.ports import ch.dissem.bitmessage.InternalContext import ch.dissem.bitmessage.entity.Plaintext import ch.dissem.bitmessage.entity.Plaintext.Status.* +import ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST import ch.dissem.bitmessage.entity.valueobject.Label open class DefaultLabeler : Labeler { @@ -26,7 +27,7 @@ open class DefaultLabeler : Labeler { override fun setLabels(msg: Plaintext) { msg.status = RECEIVED - if (msg.type === Plaintext.Type.BROADCAST) { + if (msg.type == BROADCAST) { msg.addLabels(ctx.messageRepository.getLabels(Label.Type.INBOX, Label.Type.BROADCAST, Label.Type.UNREAD)) } else { msg.addLabels(ctx.messageRepository.getLabels(Label.Type.INBOX, Label.Type.UNREAD)) diff --git a/core/src/test/java/ch/dissem/bitmessage/BitmessageContextTest.kt b/core/src/test/java/ch/dissem/bitmessage/BitmessageContextTest.kt index 024b66a..b51cb7d 100644 --- a/core/src/test/java/ch/dissem/bitmessage/BitmessageContextTest.kt +++ b/core/src/test/java/ch/dissem/bitmessage/BitmessageContextTest.kt @@ -114,12 +114,12 @@ class BitmessageContextTest { @Test fun `ensure contact is saved and pubkey requested`() { val contact = BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT") - whenever(ctx.addresses().getAddress(contact.address)).thenReturn(contact) + whenever(ctx.addresses.getAddress(contact.address)).thenReturn(contact) ctx.addContact(contact) - verify(ctx.addresses(), timeout(1000).atLeastOnce()).save(contact) - verify(ctx.internals().proofOfWorkEngine, timeout(1000)).calculateNonce(any(), any(), any()) + verify(ctx.addresses, timeout(1000).atLeastOnce()).save(contact) + verify(ctx.internals.proofOfWorkEngine, timeout(1000)).calculateNonce(any(), any(), any()) } @Test @@ -131,8 +131,8 @@ class BitmessageContextTest { ctx.addContact(contact) - verify(ctx.addresses(), times(1)).save(contact) - verify(ctx.internals().proofOfWorkEngine, never()).calculateNonce(any(), any(), any()) + verify(ctx.addresses, times(1)).save(contact) + verify(ctx.internals.proofOfWorkEngine, never()).calculateNonce(any(), any(), any()) } @Test @@ -150,12 +150,12 @@ class BitmessageContextTest { ) val contact = BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT") - whenever(ctx.addresses().getAddress(contact.address)).thenReturn(contact) + whenever(ctx.addresses.getAddress(contact.address)).thenReturn(contact) ctx.addContact(contact) - verify(ctx.addresses(), atLeastOnce()).save(contact) - verify(ctx.internals().proofOfWorkEngine, never()).calculateNonce(any(), any(), any()) + verify(ctx.addresses, atLeastOnce()).save(contact) + verify(ctx.internals.proofOfWorkEngine, never()).calculateNonce(any(), any(), any()) } @Test @@ -174,12 +174,12 @@ class BitmessageContextTest { val contact = BitmessageAddress("BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h") val stored = BitmessageAddress(contact.address) stored.alias = "Test" - whenever(ctx.addresses().getAddress(contact.address)).thenReturn(stored) + whenever(ctx.addresses.getAddress(contact.address)).thenReturn(stored) ctx.addContact(contact) - verify(ctx.addresses(), atLeastOnce()).save(any()) - verify(ctx.internals().proofOfWorkEngine, never()).calculateNonce(any(), any(), any()) + verify(ctx.addresses, atLeastOnce()).save(any()) + verify(ctx.internals.proofOfWorkEngine, never()).calculateNonce(any(), any(), any()) } @Test @@ -191,12 +191,12 @@ class BitmessageContextTest { "V5Broadcast.payload" ) - whenever(ctx.addresses().getSubscriptions(any())).thenReturn(listOf(address)) + whenever(ctx.addresses.getSubscriptions(any())).thenReturn(listOf(address)) ctx.addSubscribtion(address) - verify(ctx.addresses(), atLeastOnce()).save(address) + verify(ctx.addresses, atLeastOnce()).save(address) assertThat(address.isSubscribed, `is`(true)) - verify(ctx.internals().inventory).getObjects(eq(address.stream), any(), any()) + verify(ctx.internals.inventory).getObjects(eq(address.stream), any(), any()) verify(listener).receive(any()) } @@ -210,9 +210,9 @@ class BitmessageContextTest { ctx.send(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"), TestUtils.loadContact(), "Subject", "Message") assertEquals(2, testPowRepo.added) - verify(ctx.internals().proofOfWorkRepository, timeout(10000).atLeastOnce()) + verify(ctx.internals.proofOfWorkRepository, timeout(10000).atLeastOnce()) .putObject(argThat { payload.type == ObjectType.MSG }, eq(1000L), eq(1000L)) - verify(ctx.messages(), timeout(10000).atLeastOnce()).save(argThat { type == Type.MSG }) + verify(ctx.messages, timeout(10000).atLeastOnce()).save(argThat { type == Type.MSG }) } @Test @@ -222,7 +222,7 @@ class BitmessageContextTest { "Subject", "Message") verify(testPowRepo, timeout(10000).atLeastOnce()) .putObject(argThat { payload.type == ObjectType.GET_PUBKEY }, eq(1000L), eq(1000L)) - verify(ctx.messages(), timeout(10000).atLeastOnce()).save(argThat { type == Type.MSG }) + verify(ctx.messages, timeout(10000).atLeastOnce()).save(argThat { type == Type.MSG }) } @Test(expected = IllegalArgumentException::class) @@ -236,11 +236,11 @@ class BitmessageContextTest { fun `ensure broadcast is sent`() { ctx.broadcast(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"), "Subject", "Message") - verify(ctx.internals().proofOfWorkRepository, timeout(10000).atLeastOnce()) + verify(ctx.internals.proofOfWorkRepository, timeout(10000).atLeastOnce()) .putObject(argThat { payload.type == ObjectType.BROADCAST }, eq(1000L), eq(1000L)) - verify(ctx.internals().proofOfWorkEngine) + verify(ctx.internals.proofOfWorkEngine) .calculateNonce(any(), any(), any()) - verify(ctx.messages(), timeout(10000).atLeastOnce()).save(argThat { type == Type.BROADCAST }) + verify(ctx.messages, timeout(10000).atLeastOnce()).save(argThat { type == Type.BROADCAST }) } @Test(expected = IllegalArgumentException::class) @@ -311,9 +311,9 @@ class BitmessageContextTest { .to(TestUtils.loadContact()) .build() assertTrue(plaintext.to!!.has(Pubkey.Feature.DOES_ACK)) - whenever(ctx.messages().findMessagesToResend()).thenReturn(listOf(plaintext)) - whenever(ctx.messages().getMessage(any<ByteArray>())).thenReturn(plaintext) + whenever(ctx.messages.findMessagesToResend()).thenReturn(listOf(plaintext)) + whenever(ctx.messages.getMessage(any<ByteArray>())).thenReturn(plaintext) ctx.resendUnacknowledgedMessages() - verify(ctx.labeler(), timeout(1000).times(1)).markAsSent(eq(plaintext)) + verify(ctx.labeler, timeout(1000).times(1)).markAsSent(eq(plaintext)) } } diff --git a/cryptography-bc/build.gradle b/cryptography-bc/build.gradle index 7dde574..9adb0b5 100644 --- a/cryptography-bc/build.gradle +++ b/cryptography-bc/build.gradle @@ -15,4 +15,5 @@ dependencies { compile 'org.bouncycastle:bcprov-jdk15on:1.56' testCompile 'junit:junit:4.12' testCompile 'org.mockito:mockito-core:2.7.21' + testCompile project(path: ':core', configuration: 'testArtifacts') } diff --git a/cryptography-bc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.java b/cryptography-bc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.java deleted file mode 100644 index f19be27..0000000 --- a/cryptography-bc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.java +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright 2017 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.security; - -import ch.dissem.bitmessage.InternalContext; -import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography; -import ch.dissem.bitmessage.entity.ObjectMessage; -import ch.dissem.bitmessage.entity.payload.GenericPayload; -import ch.dissem.bitmessage.entity.valueobject.PrivateKey; -import ch.dissem.bitmessage.exception.InsufficientProofOfWorkException; -import ch.dissem.bitmessage.ports.MultiThreadedPOWEngine; -import ch.dissem.bitmessage.ports.ProofOfWorkEngine; -import ch.dissem.bitmessage.utils.CallbackWaiter; -import ch.dissem.bitmessage.utils.Singleton; -import ch.dissem.bitmessage.utils.UnixTime; -import org.junit.BeforeClass; -import org.junit.Test; - -import javax.xml.bind.DatatypeConverter; -import java.io.ByteArrayInputStream; -import java.io.IOException; - -import static ch.dissem.bitmessage.utils.UnixTime.DAY; -import static ch.dissem.bitmessage.utils.UnixTime.MINUTE; -import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.*; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -/** - * @author Christian Basler - */ -public class CryptographyTest { - public static final byte[] TEST_VALUE = "teststring".getBytes(); - public static final byte[] TEST_SHA1 = DatatypeConverter.parseHexBinary("" - + "b8473b86d4c2072ca9b08bd28e373e8253e865c4"); - public static final byte[] TEST_SHA512 = DatatypeConverter.parseHexBinary("" - + "6253b39071e5df8b5098f59202d414c37a17d6a38a875ef5f8c7d89b0212b028" - + "692d3d2090ce03ae1de66c862fa8a561e57ed9eb7935ce627344f742c0931d72"); - public static final byte[] TEST_RIPEMD160 = DatatypeConverter.parseHexBinary("" - + "cd566972b5e50104011a92b59fa8e0b1234851ae"); - - private static BouncyCryptography crypto; - - @BeforeClass - public static void setUp() { - crypto = new BouncyCryptography(); - Singleton.initialize(crypto); - InternalContext ctx = mock(InternalContext.class); - when(ctx.getProofOfWorkEngine()).thenReturn(new MultiThreadedPOWEngine()); - } - - @Test - public void testRipemd160() { - assertArrayEquals(TEST_RIPEMD160, crypto.ripemd160(TEST_VALUE)); - } - - @Test - public void testSha1() { - assertArrayEquals(TEST_SHA1, crypto.sha1(TEST_VALUE)); - } - - @Test - public void testSha512() { - assertArrayEquals(TEST_SHA512, crypto.sha512(TEST_VALUE)); - } - - @Test - public void testChaining() { - assertArrayEquals(TEST_SHA512, crypto.sha512("test".getBytes(), "string".getBytes())); - } - - @Test - public void ensureDoubleHashYieldsSameResultAsHashOfHash() { - assertArrayEquals(crypto.sha512(TEST_SHA512), crypto.doubleSha512(TEST_VALUE)); - } - - @Test(expected = IOException.class) - public void ensureExceptionForInsufficientProofOfWork() throws IOException { - ObjectMessage objectMessage = new ObjectMessage.Builder() - .nonce(new byte[8]) - .expiresTime(UnixTime.now() + 28 * DAY) - .objectType(0) - .payload(GenericPayload.Companion.read(0, 1, new ByteArrayInputStream(new byte[0]), 0)) - .build(); - crypto.checkProofOfWork(objectMessage, 1000, 1000); - } - - @Test - public void testDoProofOfWork() throws Exception { - ObjectMessage objectMessage = new ObjectMessage.Builder() - .nonce(new byte[8]) - .expiresTime(UnixTime.now() + 2 * MINUTE) - .objectType(0) - .payload(GenericPayload.Companion.read(0, 1, new ByteArrayInputStream(new byte[0]), 0)) - .build(); - final CallbackWaiter<byte[]> waiter = new CallbackWaiter<>(); - crypto.doProofOfWork(objectMessage, 1000, 1000, - new ProofOfWorkEngine.Callback() { - @Override - public void onNonceCalculated(byte[] initialHash, byte[] nonce) { - waiter.setValue(nonce); - } - }); - objectMessage.setNonce(waiter.waitForValue()); - try { - crypto.checkProofOfWork(objectMessage, 1000, 1000); - } catch (InsufficientProofOfWorkException e) { - fail(e.getMessage()); - } - } - - @Test - public void ensureEncryptionAndDecryptionWorks() { - byte[] data = crypto.randomBytes(100); - byte[] key_e = crypto.randomBytes(32); - byte[] iv = crypto.randomBytes(16); - byte[] encrypted = crypto.crypt(true, data, key_e, iv); - byte[] decrypted = crypto.crypt(false, encrypted, key_e, iv); - assertArrayEquals(data, decrypted); - } - - @Test(expected = IllegalArgumentException.class) - public void ensureDecryptionFailsWithInvalidCypherText() { - byte[] data = crypto.randomBytes(128); - byte[] key_e = crypto.randomBytes(32); - byte[] iv = crypto.randomBytes(16); - crypto.crypt(false, data, key_e, iv); - } - - @Test - public void testMultiplication() { - byte[] a = crypto.randomBytes(PrivateKey.PRIVATE_KEY_SIZE); - byte[] A = crypto.createPublicKey(a); - - byte[] b = crypto.randomBytes(PrivateKey.PRIVATE_KEY_SIZE); - byte[] B = crypto.createPublicKey(b); - - assertArrayEquals(crypto.multiply(A, b), crypto.multiply(B, a)); - } - - @Test - public void ensureSignatureIsValid() { - byte[] data = crypto.randomBytes(100); - PrivateKey privateKey = new PrivateKey(false, 1, 1000, 1000); - byte[] signature = crypto.getSignature(data, privateKey); - assertThat(crypto.isSignatureValid(data, signature, privateKey.getPubkey()), is(true)); - } - - @Test - public void ensureSignatureIsInvalidForTemperedData() { - byte[] data = crypto.randomBytes(100); - PrivateKey privateKey = new PrivateKey(false, 1, 1000, 1000); - byte[] signature = crypto.getSignature(data, privateKey); - data[0]++; - assertThat(crypto.isSignatureValid(data, signature, privateKey.getPubkey()), is(false)); - } -} diff --git a/cryptography-bc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.kt b/cryptography-bc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.kt new file mode 100644 index 0000000..1897512 --- /dev/null +++ b/cryptography-bc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.kt @@ -0,0 +1,172 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.security + +import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography +import ch.dissem.bitmessage.entity.ObjectMessage +import ch.dissem.bitmessage.entity.payload.GenericPayload +import ch.dissem.bitmessage.entity.valueobject.PrivateKey +import ch.dissem.bitmessage.exception.InsufficientProofOfWorkException +import ch.dissem.bitmessage.ports.MultiThreadedPOWEngine +import ch.dissem.bitmessage.ports.ProofOfWorkEngine +import ch.dissem.bitmessage.utils.CallbackWaiter +import ch.dissem.bitmessage.utils.Singleton +import ch.dissem.bitmessage.utils.TestUtils +import ch.dissem.bitmessage.utils.UnixTime +import ch.dissem.bitmessage.utils.UnixTime.DAY +import ch.dissem.bitmessage.utils.UnixTime.MINUTE +import ch.dissem.bitmessage.utils.UnixTime.now +import org.hamcrest.CoreMatchers.`is` +import org.junit.Assert.* +import org.junit.Test +import java.io.ByteArrayInputStream +import java.io.IOException +import javax.xml.bind.DatatypeConverter + +/** + * @author Christian Basler + */ +class CryptographyTest { + + @Test + fun testRipemd160() { + assertArrayEquals(TEST_RIPEMD160, crypto.ripemd160(TEST_VALUE)) + } + + @Test + fun testSha1() { + assertArrayEquals(TEST_SHA1, crypto.sha1(TEST_VALUE)) + } + + @Test + fun testSha512() { + assertArrayEquals(TEST_SHA512, crypto.sha512(TEST_VALUE)) + } + + @Test + fun testChaining() { + assertArrayEquals(TEST_SHA512, crypto.sha512("test".toByteArray(), "string".toByteArray())) + } + + @Test + fun ensureDoubleHashYieldsSameResultAsHashOfHash() { + assertArrayEquals(crypto.sha512(TEST_SHA512), crypto.doubleSha512(TEST_VALUE)) + } + + @Test(expected = IOException::class) + fun ensureExceptionForInsufficientProofOfWork() { + val objectMessage = ObjectMessage.Builder() + .nonce(ByteArray(8)) + .expiresTime(UnixTime.now + 28 * DAY) + .objectType(0) + .payload(GenericPayload.read(0, 1, ByteArrayInputStream(ByteArray(0)), 0)) + .build() + crypto.checkProofOfWork(objectMessage, 1000, 1000) + } + + @Test + fun testDoProofOfWork() { + TestUtils.mockedInternalContext( + cryptography = crypto, + proofOfWorkEngine = MultiThreadedPOWEngine() + ) + val objectMessage = ObjectMessage( + nonce = ByteArray(8), + expiresTime = now + 2 * MINUTE, + type = 0, + payload = GenericPayload.read(0, 1, ByteArrayInputStream(ByteArray(0)), 0), + version = 0, + stream = 1 + ) + val waiter = CallbackWaiter<ByteArray>() + crypto.doProofOfWork(objectMessage, 1000, 1000, + object : ProofOfWorkEngine.Callback { + override fun onNonceCalculated(initialHash: ByteArray, nonce: ByteArray) { + waiter.setValue(nonce) + } + }) + objectMessage.nonce = waiter.waitForValue() + try { + crypto.checkProofOfWork(objectMessage, 1000, 1000) + } catch (e: InsufficientProofOfWorkException) { + fail(e.message) + } + } + + @Test + fun ensureEncryptionAndDecryptionWorks() { + val data = crypto.randomBytes(100) + val key_e = crypto.randomBytes(32) + val iv = crypto.randomBytes(16) + val encrypted = crypto.crypt(true, data, key_e, iv) + val decrypted = crypto.crypt(false, encrypted, key_e, iv) + assertArrayEquals(data, decrypted) + } + + @Test(expected = IllegalArgumentException::class) + fun ensureDecryptionFailsWithInvalidCypherText() { + val data = crypto.randomBytes(128) + val key_e = crypto.randomBytes(32) + val iv = crypto.randomBytes(16) + crypto.crypt(false, data, key_e, iv) + } + + @Test + fun testMultiplication() { + val a = crypto.randomBytes(PrivateKey.PRIVATE_KEY_SIZE) + val A = crypto.createPublicKey(a) + + val b = crypto.randomBytes(PrivateKey.PRIVATE_KEY_SIZE) + val B = crypto.createPublicKey(b) + + assertArrayEquals(crypto.multiply(A, b), crypto.multiply(B, a)) + } + + @Test + fun ensureSignatureIsValid() { + val data = crypto.randomBytes(100) + val privateKey = PrivateKey(false, 1, 1000, 1000) + val signature = crypto.getSignature(data, privateKey) + assertThat(crypto.isSignatureValid(data, signature, privateKey.pubkey), `is`(true)) + } + + @Test + fun ensureSignatureIsInvalidForTemperedData() { + val data = crypto.randomBytes(100) + val privateKey = PrivateKey(false, 1, 1000, 1000) + val signature = crypto.getSignature(data, privateKey) + data[0]++ + assertThat(crypto.isSignatureValid(data, signature, privateKey.pubkey), `is`(false)) + } + + companion object { + val TEST_VALUE = "teststring".toByteArray() + val TEST_SHA1 = DatatypeConverter.parseHexBinary("" + + "b8473b86d4c2072ca9b08bd28e373e8253e865c4") + val TEST_SHA512 = DatatypeConverter.parseHexBinary("" + + "6253b39071e5df8b5098f59202d414c37a17d6a38a875ef5f8c7d89b0212b028" + + "692d3d2090ce03ae1de66c862fa8a561e57ed9eb7935ce627344f742c0931d72") + val TEST_RIPEMD160 = DatatypeConverter.parseHexBinary("" + + "cd566972b5e50104011a92b59fa8e0b1234851ae") + + private val crypto = BouncyCryptography() + + init { + Singleton.initialize(crypto) + } + } +} diff --git a/cryptography-sc/build.gradle b/cryptography-sc/build.gradle index c18f68d..d69b841 100644 --- a/cryptography-sc/build.gradle +++ b/cryptography-sc/build.gradle @@ -15,4 +15,5 @@ dependencies { compile 'com.madgag.spongycastle:prov:1.54.0.0' testCompile 'junit:junit:4.12' testCompile 'org.mockito:mockito-core:2.7.21' + testCompile project(path: ':core', configuration: 'testArtifacts') } diff --git a/cryptography-sc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.java b/cryptography-sc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.java deleted file mode 100644 index 1589fcf..0000000 --- a/cryptography-sc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright 2017 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.security; - -import ch.dissem.bitmessage.InternalContext; -import ch.dissem.bitmessage.cryptography.sc.SpongyCryptography; -import ch.dissem.bitmessage.entity.ObjectMessage; -import ch.dissem.bitmessage.entity.payload.GenericPayload; -import ch.dissem.bitmessage.entity.valueobject.PrivateKey; -import ch.dissem.bitmessage.exception.InsufficientProofOfWorkException; -import ch.dissem.bitmessage.ports.MultiThreadedPOWEngine; -import ch.dissem.bitmessage.ports.ProofOfWorkEngine; -import ch.dissem.bitmessage.utils.CallbackWaiter; -import ch.dissem.bitmessage.utils.Singleton; -import ch.dissem.bitmessage.utils.UnixTime; -import org.junit.Assert; -import org.junit.BeforeClass; -import org.junit.Test; - -import javax.xml.bind.DatatypeConverter; -import java.io.ByteArrayInputStream; -import java.io.IOException; - -import static ch.dissem.bitmessage.utils.UnixTime.DAY; -import static ch.dissem.bitmessage.utils.UnixTime.MINUTE; -import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.*; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -/** - * @author Christian Basler - */ -public class CryptographyTest { - public static final byte[] TEST_VALUE = "teststring".getBytes(); - public static final byte[] TEST_SHA1 = DatatypeConverter.parseHexBinary("" - + "b8473b86d4c2072ca9b08bd28e373e8253e865c4"); - public static final byte[] TEST_SHA512 = DatatypeConverter.parseHexBinary("" - + "6253b39071e5df8b5098f59202d414c37a17d6a38a875ef5f8c7d89b0212b028" - + "692d3d2090ce03ae1de66c862fa8a561e57ed9eb7935ce627344f742c0931d72"); - public static final byte[] TEST_RIPEMD160 = DatatypeConverter.parseHexBinary("" - + "cd566972b5e50104011a92b59fa8e0b1234851ae"); - - private static SpongyCryptography crypto; - - @BeforeClass - public static void setUp() { - crypto = new SpongyCryptography(); - Singleton.initialize(crypto); - InternalContext ctx = mock(InternalContext.class); - InternalContext.Companion.setValue(null, null, ctx); - when(ctx.getProofOfWorkEngine()).thenReturn(new MultiThreadedPOWEngine()); - } - - @Test - public void testRipemd160() { - Assert.assertArrayEquals(TEST_RIPEMD160, crypto.ripemd160(TEST_VALUE)); - } - - @Test - public void testSha1() { - Assert.assertArrayEquals(TEST_SHA1, crypto.sha1(TEST_VALUE)); - } - - @Test - public void testSha512() { - Assert.assertArrayEquals(TEST_SHA512, crypto.sha512(TEST_VALUE)); - } - - @Test - public void testChaining() { - Assert.assertArrayEquals(TEST_SHA512, crypto.sha512("test".getBytes(), "string".getBytes())); - } - - @Test - public void ensureDoubleHashYieldsSameResultAsHashOfHash() { - Assert.assertArrayEquals(crypto.sha512(TEST_SHA512), crypto.doubleSha512(TEST_VALUE)); - } - - @Test(expected = IOException.class) - public void ensureExceptionForInsufficientProofOfWork() throws IOException { - ObjectMessage objectMessage = new ObjectMessage.Builder() - .nonce(new byte[8]) - .expiresTime(UnixTime.now() + 28 * DAY) - .objectType(0) - .payload(GenericPayload.Companion.read(0, 1, new ByteArrayInputStream(new byte[0]), 0)) - .build(); - crypto.checkProofOfWork(objectMessage, 1000, 1000); - } - - @Test - public void testDoProofOfWork() throws Exception { - ObjectMessage objectMessage = new ObjectMessage.Builder() - .nonce(new byte[8]) - .expiresTime(UnixTime.now() + 2 * MINUTE) - .objectType(0) - .payload(GenericPayload.Companion.read(0, 1, new ByteArrayInputStream(new byte[0]), 0)) - .build(); - final CallbackWaiter<byte[]> waiter = new CallbackWaiter<>(); - crypto.doProofOfWork(objectMessage, 1000, 1000, - new ProofOfWorkEngine.Callback() { - @Override - public void onNonceCalculated(byte[] initialHash, byte[] nonce) { - waiter.setValue(nonce); - } - }); - objectMessage.setNonce(waiter.waitForValue()); - try { - crypto.checkProofOfWork(objectMessage, 1000, 1000); - } catch (InsufficientProofOfWorkException e) { - fail(e.getMessage()); - } - } - - @Test - public void ensureEncryptionAndDecryptionWorks() { - byte[] data = crypto.randomBytes(100); - byte[] key_e = crypto.randomBytes(32); - byte[] iv = crypto.randomBytes(16); - byte[] encrypted = crypto.crypt(true, data, key_e, iv); - byte[] decrypted = crypto.crypt(false, encrypted, key_e, iv); - assertArrayEquals(data, decrypted); - } - - @Test(expected = IllegalArgumentException.class) - public void ensureDecryptionFailsWithInvalidCypherText() { - byte[] data = crypto.randomBytes(128); - byte[] key_e = crypto.randomBytes(32); - byte[] iv = crypto.randomBytes(16); - crypto.crypt(false, data, key_e, iv); - } - - @Test - public void testMultiplication() { - byte[] a = crypto.randomBytes(PrivateKey.PRIVATE_KEY_SIZE); - byte[] A = crypto.createPublicKey(a); - - byte[] b = crypto.randomBytes(PrivateKey.PRIVATE_KEY_SIZE); - byte[] B = crypto.createPublicKey(b); - - assertArrayEquals(crypto.multiply(A, b), crypto.multiply(B, a)); - } - - @Test - public void ensureSignatureIsValid() { - byte[] data = crypto.randomBytes(100); - PrivateKey privateKey = new PrivateKey(false, 1, 1000, 1000); - byte[] signature = crypto.getSignature(data, privateKey); - assertThat(crypto.isSignatureValid(data, signature, privateKey.getPubkey()), is(true)); - } - - @Test - public void ensureSignatureIsInvalidForTemperedData() { - byte[] data = crypto.randomBytes(100); - PrivateKey privateKey = new PrivateKey(false, 1, 1000, 1000); - byte[] signature = crypto.getSignature(data, privateKey); - data[0]++; - assertThat(crypto.isSignatureValid(data, signature, privateKey.getPubkey()), is(false)); - } -} diff --git a/cryptography-sc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.kt b/cryptography-sc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.kt new file mode 100644 index 0000000..e456159 --- /dev/null +++ b/cryptography-sc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.kt @@ -0,0 +1,177 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.security + +import ch.dissem.bitmessage.InternalContext +import ch.dissem.bitmessage.cryptography.sc.SpongyCryptography +import ch.dissem.bitmessage.entity.ObjectMessage +import ch.dissem.bitmessage.entity.payload.GenericPayload +import ch.dissem.bitmessage.entity.valueobject.PrivateKey +import ch.dissem.bitmessage.exception.InsufficientProofOfWorkException +import ch.dissem.bitmessage.ports.MultiThreadedPOWEngine +import ch.dissem.bitmessage.ports.ProofOfWorkEngine +import ch.dissem.bitmessage.utils.CallbackWaiter +import ch.dissem.bitmessage.utils.Singleton +import ch.dissem.bitmessage.utils.UnixTime +import org.junit.Assert +import org.junit.BeforeClass +import org.junit.Test + +import javax.xml.bind.DatatypeConverter +import java.io.ByteArrayInputStream +import java.io.IOException + +import ch.dissem.bitmessage.utils.UnixTime.DAY +import ch.dissem.bitmessage.utils.UnixTime.MINUTE +import org.hamcrest.CoreMatchers.`is` +import org.junit.Assert.* +import org.mockito.Mockito.mock +import org.mockito.Mockito.`when` + +/** + * @author Christian Basler + */ +class CryptographyTest { + + @Test + fun testRipemd160() { + assertArrayEquals(TEST_RIPEMD160, crypto.ripemd160(TEST_VALUE)) + } + + @Test + fun testSha1() { + assertArrayEquals(TEST_SHA1, crypto.sha1(TEST_VALUE)) + } + + @Test + fun testSha512() { + assertArrayEquals(TEST_SHA512, crypto.sha512(TEST_VALUE)) + } + + @Test + fun testChaining() { + assertArrayEquals(TEST_SHA512, crypto.sha512("test".toByteArray(), "string".toByteArray())) + } + + @Test + fun ensureDoubleHashYieldsSameResultAsHashOfHash() { + assertArrayEquals(crypto.sha512(TEST_SHA512), crypto.doubleSha512(TEST_VALUE)) + } + + @Test(expected = IOException::class) + fun ensureExceptionForInsufficientProofOfWork() { + val objectMessage = ObjectMessage.Builder() + .nonce(ByteArray(8)) + .expiresTime(UnixTime.now + 28 * DAY) + .objectType(0) + .payload(GenericPayload.read(0, 1, ByteArrayInputStream(ByteArray(0)), 0)) + .build() + crypto.checkProofOfWork(objectMessage, 1000, 1000) + } + + @Test + fun testDoProofOfWork() { + TestUtils.mockedInternalContext( + cryptography = crypto, + proofOfWorkEngine = MultiThreadedPOWEngine() + ) + val objectMessage = ObjectMessage( + nonce = ByteArray(8), + expiresTime = UnixTime.now + 2 * MINUTE, + type = 0, + payload = GenericPayload.read(0, 1, ByteArrayInputStream(ByteArray(0)), 0), + version = 0, + stream = 1 + ) + val waiter = CallbackWaiter<ByteArray>() + crypto.doProofOfWork(objectMessage, 1000, 1000, + object : ProofOfWorkEngine.Callback { + override fun onNonceCalculated(initialHash: ByteArray, nonce: ByteArray) { + waiter.setValue(nonce) + } + }) + objectMessage.nonce = waiter.waitForValue() + try { + crypto.checkProofOfWork(objectMessage, 1000, 1000) + } catch (e: InsufficientProofOfWorkException) { + fail(e.message) + } + } + + @Test + fun ensureEncryptionAndDecryptionWorks() { + val data = crypto.randomBytes(100) + val key_e = crypto.randomBytes(32) + val iv = crypto.randomBytes(16) + val encrypted = crypto.crypt(true, data, key_e, iv) + val decrypted = crypto.crypt(false, encrypted, key_e, iv) + assertArrayEquals(data, decrypted) + } + + @Test(expected = IllegalArgumentException::class) + fun ensureDecryptionFailsWithInvalidCypherText() { + val data = crypto.randomBytes(128) + val key_e = crypto.randomBytes(32) + val iv = crypto.randomBytes(16) + crypto.crypt(false, data, key_e, iv) + } + + @Test + fun testMultiplication() { + val a = crypto.randomBytes(PrivateKey.PRIVATE_KEY_SIZE) + val A = crypto.createPublicKey(a) + + val b = crypto.randomBytes(PrivateKey.PRIVATE_KEY_SIZE) + val B = crypto.createPublicKey(b) + + assertArrayEquals(crypto.multiply(A, b), crypto.multiply(B, a)) + } + + @Test + fun ensureSignatureIsValid() { + val data = crypto.randomBytes(100) + val privateKey = PrivateKey(false, 1, 1000, 1000) + val signature = crypto.getSignature(data, privateKey) + assertThat(crypto.isSignatureValid(data, signature, privateKey.pubkey), `is`(true)) + } + + @Test + fun ensureSignatureIsInvalidForTemperedData() { + val data = crypto.randomBytes(100) + val privateKey = PrivateKey(false, 1, 1000, 1000) + val signature = crypto.getSignature(data, privateKey) + data[0]++ + assertThat(crypto.isSignatureValid(data, signature, privateKey.pubkey), `is`(false)) + } + + companion object { + val TEST_VALUE = "teststring".toByteArray() + val TEST_SHA1 = DatatypeConverter.parseHexBinary("" + + "b8473b86d4c2072ca9b08bd28e373e8253e865c4") + val TEST_SHA512 = DatatypeConverter.parseHexBinary("" + + "6253b39071e5df8b5098f59202d414c37a17d6a38a875ef5f8c7d89b0212b028" + + "692d3d2090ce03ae1de66c862fa8a561e57ed9eb7935ce627344f742c0931d72") + val TEST_RIPEMD160 = DatatypeConverter.parseHexBinary("" + + "cd566972b5e50104011a92b59fa8e0b1234851ae") + + private val crypto = SpongyCryptography() + + init { + Singleton.initialize(crypto) + } + } +} diff --git a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcInventoryTest.java b/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcInventoryTest.java index 6f31299..0a5c387 100644 --- a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcInventoryTest.java +++ b/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcInventoryTest.java @@ -130,14 +130,14 @@ public class JdbcInventoryTest extends TestBase { private ObjectMessage getObjectMessage(long stream, long TTL, ObjectPayload payload) { return new ObjectMessage.Builder() - .nonce(new byte[8]) - .expiresTime(now(+TTL)) - .stream(stream) - .payload(payload) - .build(); + .nonce(new byte[8]) + .expiresTime(now() + TTL) + .stream(stream) + .payload(payload) + .build(); } private GetPubkey getGetPubkey() { return new GetPubkey(new BitmessageAddress("BM-2cW7cD5cDQJDNkE7ibmyTxfvGAmnPqa9Vt")); } -} \ No newline at end of file +} From 1d3340a547dd61cc3839e28244c48a1e3a82b224 Mon Sep 17 00:00:00 2001 From: Christian Basler <chrigu.meyer@gmail.com> Date: Thu, 8 Jun 2017 21:56:31 +0200 Subject: [PATCH 03/10] Migrated cryptography and some improvements --- README.md | 2 +- .../ch/dissem/bitmessage/BitmessageContext.kt | 10 +- .../cryptography/bc/BouncyCryptography.java | 145 ----------------- .../cryptography/bc/BouncyCryptography.kt | 122 +++++++++++++++ .../bitmessage/security/CryptographyTest.kt | 2 +- .../cryptography/sc/SpongyCryptography.java | 147 ------------------ .../cryptography/sc/SpongyCryptography.kt | 122 +++++++++++++++ .../bitmessage/security/CryptographyTest.kt | 18 +-- .../java/ch/dissem/bitmessage/demo/Main.java | 2 +- .../java/ch/dissem/bitmessage/SystemTest.java | 4 +- .../networking/NetworkHandlerTest.java | 4 +- .../bitmessage/wif/WifExporterTest.java | 4 +- .../bitmessage/wif/WifImporterTest.java | 4 +- 13 files changed, 270 insertions(+), 316 deletions(-) delete mode 100644 cryptography-bc/src/main/java/ch/dissem/bitmessage/cryptography/bc/BouncyCryptography.java create mode 100644 cryptography-bc/src/main/java/ch/dissem/bitmessage/cryptography/bc/BouncyCryptography.kt delete mode 100644 cryptography-sc/src/main/java/ch/dissem/bitmessage/cryptography/sc/SpongyCryptography.java create mode 100644 cryptography-sc/src/main/java/ch/dissem/bitmessage/cryptography/sc/SpongyCryptography.kt diff --git a/README.md b/README.md index 75f2b28..dcbffa4 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ BitmessageContext ctx = new BitmessageContext.Builder() .powRepo(new JdbcProofOfWorkRepository(jdbcConfig)) .nodeRegistry(new JdbcNodeRegistry(jdbcConfig)) .networkHandler(new NioNetworkHandler()) - .cryptography(new BouncyCryptography()) + .cryptography(BouncyCryptography.INSTANCE) .listener(System.out::println) .build(); ``` diff --git a/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.kt b/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.kt index ef4b1d6..175e378 100644 --- a/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.kt +++ b/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.kt @@ -311,7 +311,6 @@ class BitmessageContext private constructor(builder: BitmessageContext.Builder) } catch (e: Exception) { LOG.debug(e.message, e) } - } } @@ -410,6 +409,15 @@ class BitmessageContext private constructor(builder: BitmessageContext.Builder) return this } + fun listener(listener: (Plaintext) -> Unit): Builder { + this.listener = object : Listener { + override fun receive(plaintext: Plaintext) { + listener.invoke(plaintext) + } + } + return this + } + fun connectionLimit(connectionLimit: Int): Builder { this.connectionLimit = connectionLimit return this diff --git a/cryptography-bc/src/main/java/ch/dissem/bitmessage/cryptography/bc/BouncyCryptography.java b/cryptography-bc/src/main/java/ch/dissem/bitmessage/cryptography/bc/BouncyCryptography.java deleted file mode 100644 index bc8ec93..0000000 --- a/cryptography-bc/src/main/java/ch/dissem/bitmessage/cryptography/bc/BouncyCryptography.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.cryptography.bc; - -import ch.dissem.bitmessage.entity.payload.Pubkey; -import ch.dissem.bitmessage.entity.valueobject.PrivateKey; -import ch.dissem.bitmessage.exception.ApplicationException; -import ch.dissem.bitmessage.ports.AbstractCryptography; -import org.bouncycastle.asn1.x9.X9ECParameters; -import org.bouncycastle.crypto.BufferedBlockCipher; -import org.bouncycastle.crypto.CipherParameters; -import org.bouncycastle.crypto.InvalidCipherTextException; -import org.bouncycastle.crypto.ec.CustomNamedCurves; -import org.bouncycastle.crypto.engines.AESEngine; -import org.bouncycastle.crypto.modes.CBCBlockCipher; -import org.bouncycastle.crypto.paddings.PKCS7Padding; -import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher; -import org.bouncycastle.crypto.params.KeyParameter; -import org.bouncycastle.crypto.params.ParametersWithIV; -import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.bouncycastle.jce.spec.ECParameterSpec; -import org.bouncycastle.jce.spec.ECPrivateKeySpec; -import org.bouncycastle.jce.spec.ECPublicKeySpec; -import org.bouncycastle.math.ec.ECPoint; - -import java.math.BigInteger; -import java.security.*; -import java.security.spec.KeySpec; -import java.util.Arrays; - -/** - * As Spongycastle can't be used on the Oracle JVM, and Bouncycastle doesn't work properly on Android (thanks, Google), - * this is the Bouncycastle implementation. - */ -public class BouncyCryptography extends AbstractCryptography { - private static final X9ECParameters EC_CURVE_PARAMETERS = CustomNamedCurves.getByName("secp256k1"); - - public BouncyCryptography() { - super(new BouncyCastleProvider()); - } - - @Override - public byte[] crypt(boolean encrypt, byte[] data, byte[] key_e, byte[] initializationVector) { - BufferedBlockCipher cipher = new PaddedBufferedBlockCipher( - new CBCBlockCipher(new AESEngine()), - new PKCS7Padding() - ); - CipherParameters params = new ParametersWithIV(new KeyParameter(key_e), initializationVector); - - cipher.init(encrypt, params); - - byte[] buffer = new byte[cipher.getOutputSize(data.length)]; - int length = cipher.processBytes(data, 0, data.length, buffer, 0); - try { - length += cipher.doFinal(buffer, length); - } catch (InvalidCipherTextException e) { - throw new IllegalArgumentException(e); - } - if (length < buffer.length) { - return Arrays.copyOfRange(buffer, 0, length); - } - return buffer; - } - - @Override - public byte[] createPublicKey(byte[] privateKey) { - return EC_CURVE_PARAMETERS.getG().multiply(keyToBigInt(privateKey)).normalize().getEncoded(false); - } - - private ECPoint keyToPoint(byte[] publicKey) { - BigInteger x = new BigInteger(1, Arrays.copyOfRange(publicKey, 1, 33)); - BigInteger y = new BigInteger(1, Arrays.copyOfRange(publicKey, 33, 65)); - return EC_CURVE_PARAMETERS.getCurve().createPoint(x, y); - } - - @Override - public boolean isSignatureValid(byte[] data, byte[] signature, Pubkey pubkey) { - try { - ECParameterSpec spec = new ECParameterSpec( - EC_CURVE_PARAMETERS.getCurve(), - EC_CURVE_PARAMETERS.getG(), - EC_CURVE_PARAMETERS.getN(), - EC_CURVE_PARAMETERS.getH(), - EC_CURVE_PARAMETERS.getSeed() - ); - - ECPoint Q = keyToPoint(pubkey.getSigningKey()); - KeySpec keySpec = new ECPublicKeySpec(Q, spec); - PublicKey publicKey = KeyFactory.getInstance(ALGORITHM_ECDSA, provider).generatePublic(keySpec); - - return doCheckSignature(data, signature, publicKey); - } catch (GeneralSecurityException e) { - throw new ApplicationException(e); - } - } - - @Override - public byte[] getSignature(byte[] data, PrivateKey privateKey) { - try { - ECParameterSpec spec = new ECParameterSpec( - EC_CURVE_PARAMETERS.getCurve(), - EC_CURVE_PARAMETERS.getG(), - EC_CURVE_PARAMETERS.getN(), - EC_CURVE_PARAMETERS.getH(), - EC_CURVE_PARAMETERS.getSeed() - ); - - BigInteger d = keyToBigInt(privateKey.getPrivateSigningKey()); - KeySpec keySpec = new ECPrivateKeySpec(d, spec); - java.security.PrivateKey privKey = KeyFactory.getInstance(ALGORITHM_ECDSA, provider) - .generatePrivate(keySpec); - - return doSign(data, privKey); - } catch (GeneralSecurityException e) { - throw new ApplicationException(e); - } - } - - @Override - public byte[] multiply(byte[] K, byte[] r) { - return keyToPoint(K).multiply(keyToBigInt(r)).normalize().getEncoded(false); - } - - @Override - public byte[] createPoint(byte[] x, byte[] y) { - return EC_CURVE_PARAMETERS.getCurve().createPoint( - new BigInteger(1, x), - new BigInteger(1, y) - ).getEncoded(false); - } -} diff --git a/cryptography-bc/src/main/java/ch/dissem/bitmessage/cryptography/bc/BouncyCryptography.kt b/cryptography-bc/src/main/java/ch/dissem/bitmessage/cryptography/bc/BouncyCryptography.kt new file mode 100644 index 0000000..89e38e2 --- /dev/null +++ b/cryptography-bc/src/main/java/ch/dissem/bitmessage/cryptography/bc/BouncyCryptography.kt @@ -0,0 +1,122 @@ +/* + * Copyright 2015 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.cryptography.bc + +import ch.dissem.bitmessage.entity.payload.Pubkey +import ch.dissem.bitmessage.entity.valueobject.PrivateKey +import ch.dissem.bitmessage.ports.AbstractCryptography +import org.bouncycastle.crypto.InvalidCipherTextException +import org.bouncycastle.crypto.ec.CustomNamedCurves +import org.bouncycastle.crypto.engines.AESEngine +import org.bouncycastle.crypto.modes.CBCBlockCipher +import org.bouncycastle.crypto.paddings.PKCS7Padding +import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher +import org.bouncycastle.crypto.params.KeyParameter +import org.bouncycastle.crypto.params.ParametersWithIV +import org.bouncycastle.jce.provider.BouncyCastleProvider +import org.bouncycastle.jce.spec.ECParameterSpec +import org.bouncycastle.jce.spec.ECPrivateKeySpec +import org.bouncycastle.jce.spec.ECPublicKeySpec +import org.bouncycastle.math.ec.ECPoint +import java.math.BigInteger +import java.security.KeyFactory +import java.util.* + +/** + * As Spongycastle can't be used on the Oracle JVM, and Bouncycastle doesn't work properly on Android (thanks, Google), + * this is the Bouncycastle implementation. + */ +object BouncyCryptography : AbstractCryptography(BouncyCastleProvider()) { + private val EC_CURVE_PARAMETERS = CustomNamedCurves.getByName("secp256k1") + + override fun crypt(encrypt: Boolean, data: ByteArray, key_e: ByteArray, initializationVector: ByteArray): ByteArray { + val cipher = PaddedBufferedBlockCipher( + CBCBlockCipher(AESEngine()), + PKCS7Padding() + ) + val params = ParametersWithIV(KeyParameter(key_e), initializationVector) + + cipher.init(encrypt, params) + + val buffer = ByteArray(cipher.getOutputSize(data.size)) + var length = cipher.processBytes(data, 0, data.size, buffer, 0) + try { + length += cipher.doFinal(buffer, length) + } catch (e: InvalidCipherTextException) { + throw IllegalArgumentException(e) + } + + if (length < buffer.size) { + return Arrays.copyOfRange(buffer, 0, length) + } + return buffer + } + + override fun createPublicKey(privateKey: ByteArray): ByteArray { + return EC_CURVE_PARAMETERS.g.multiply(keyToBigInt(privateKey)).normalize().getEncoded(false) + } + + private fun keyToPoint(publicKey: ByteArray): ECPoint { + val x = BigInteger(1, Arrays.copyOfRange(publicKey, 1, 33)) + val y = BigInteger(1, Arrays.copyOfRange(publicKey, 33, 65)) + return EC_CURVE_PARAMETERS.curve.createPoint(x, y) + } + + override fun isSignatureValid(data: ByteArray, signature: ByteArray, pubkey: Pubkey): Boolean { + val spec = ECParameterSpec( + EC_CURVE_PARAMETERS.curve, + EC_CURVE_PARAMETERS.g, + EC_CURVE_PARAMETERS.n, + EC_CURVE_PARAMETERS.h, + EC_CURVE_PARAMETERS.seed + ) + + val Q = keyToPoint(pubkey.signingKey) + val keySpec = ECPublicKeySpec(Q, spec) + val publicKey = KeyFactory.getInstance(ALGORITHM_ECDSA, provider).generatePublic(keySpec) + + return doCheckSignature(data, signature, publicKey) + } + + override fun getSignature(data: ByteArray, privateKey: PrivateKey): ByteArray { + val spec = ECParameterSpec( + EC_CURVE_PARAMETERS.curve, + EC_CURVE_PARAMETERS.g, + EC_CURVE_PARAMETERS.n, + EC_CURVE_PARAMETERS.h, + EC_CURVE_PARAMETERS.seed + ) + + val d = keyToBigInt(privateKey.privateSigningKey) + val keySpec = ECPrivateKeySpec(d, spec) + val privKey = KeyFactory.getInstance(ALGORITHM_ECDSA, provider) + .generatePrivate(keySpec) + + return doSign(data, privKey) + } + + override fun multiply(k: ByteArray, r: ByteArray): ByteArray { + return keyToPoint(k).multiply(keyToBigInt(r)).normalize().getEncoded(false) + } + + override fun createPoint(x: ByteArray, y: ByteArray): ByteArray { + return EC_CURVE_PARAMETERS.curve.createPoint( + BigInteger(1, x), + BigInteger(1, y) + ).getEncoded(false) + } +} diff --git a/cryptography-bc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.kt b/cryptography-bc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.kt index 1897512..c51c296 100644 --- a/cryptography-bc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.kt +++ b/cryptography-bc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.kt @@ -163,7 +163,7 @@ class CryptographyTest { val TEST_RIPEMD160 = DatatypeConverter.parseHexBinary("" + "cd566972b5e50104011a92b59fa8e0b1234851ae") - private val crypto = BouncyCryptography() + private val crypto = BouncyCryptography init { Singleton.initialize(crypto) diff --git a/cryptography-sc/src/main/java/ch/dissem/bitmessage/cryptography/sc/SpongyCryptography.java b/cryptography-sc/src/main/java/ch/dissem/bitmessage/cryptography/sc/SpongyCryptography.java deleted file mode 100644 index f86e6c2..0000000 --- a/cryptography-sc/src/main/java/ch/dissem/bitmessage/cryptography/sc/SpongyCryptography.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.cryptography.sc; - -import ch.dissem.bitmessage.entity.payload.Pubkey; -import ch.dissem.bitmessage.entity.valueobject.PrivateKey; -import ch.dissem.bitmessage.exception.ApplicationException; -import ch.dissem.bitmessage.ports.AbstractCryptography; -import org.spongycastle.asn1.x9.X9ECParameters; -import org.spongycastle.crypto.BufferedBlockCipher; -import org.spongycastle.crypto.CipherParameters; -import org.spongycastle.crypto.InvalidCipherTextException; -import org.spongycastle.crypto.ec.CustomNamedCurves; -import org.spongycastle.crypto.engines.AESEngine; -import org.spongycastle.crypto.modes.CBCBlockCipher; -import org.spongycastle.crypto.paddings.PKCS7Padding; -import org.spongycastle.crypto.paddings.PaddedBufferedBlockCipher; -import org.spongycastle.crypto.params.KeyParameter; -import org.spongycastle.crypto.params.ParametersWithIV; -import org.spongycastle.jce.provider.BouncyCastleProvider; -import org.spongycastle.jce.spec.ECParameterSpec; -import org.spongycastle.jce.spec.ECPrivateKeySpec; -import org.spongycastle.jce.spec.ECPublicKeySpec; -import org.spongycastle.math.ec.ECPoint; - -import java.math.BigInteger; -import java.security.GeneralSecurityException; -import java.security.KeyFactory; -import java.security.PublicKey; -import java.security.spec.KeySpec; -import java.util.Arrays; - -/** - * As Spongycastle can't be used on the Oracle JVM, and Bouncycastle doesn't work properly on Android (thanks, Google), - * this is the Spongycastle implementation. - */ -public class SpongyCryptography extends AbstractCryptography { - private static final X9ECParameters EC_CURVE_PARAMETERS = CustomNamedCurves.getByName("secp256k1"); - - public SpongyCryptography() { - super(new BouncyCastleProvider()); - } - - @Override - public byte[] crypt(boolean encrypt, byte[] data, byte[] key_e, byte[] initializationVector) { - BufferedBlockCipher cipher = new PaddedBufferedBlockCipher( - new CBCBlockCipher(new AESEngine()), - new PKCS7Padding() - ); - CipherParameters params = new ParametersWithIV(new KeyParameter(key_e), initializationVector); - - cipher.init(encrypt, params); - - byte[] buffer = new byte[cipher.getOutputSize(data.length)]; - int length = cipher.processBytes(data, 0, data.length, buffer, 0); - try { - length += cipher.doFinal(buffer, length); - } catch (InvalidCipherTextException e) { - throw new IllegalArgumentException(e); - } - if (length < buffer.length) { - return Arrays.copyOfRange(buffer, 0, length); - } - return buffer; - } - - @Override - public byte[] createPublicKey(byte[] privateKey) { - return EC_CURVE_PARAMETERS.getG().multiply(keyToBigInt(privateKey)).normalize().getEncoded(false); - } - - private ECPoint keyToPoint(byte[] publicKey) { - BigInteger x = new BigInteger(1, Arrays.copyOfRange(publicKey, 1, 33)); - BigInteger y = new BigInteger(1, Arrays.copyOfRange(publicKey, 33, 65)); - return EC_CURVE_PARAMETERS.getCurve().createPoint(x, y); - } - - @Override - public boolean isSignatureValid(byte[] data, byte[] signature, Pubkey pubkey) { - try { - ECParameterSpec spec = new ECParameterSpec( - EC_CURVE_PARAMETERS.getCurve(), - EC_CURVE_PARAMETERS.getG(), - EC_CURVE_PARAMETERS.getN(), - EC_CURVE_PARAMETERS.getH(), - EC_CURVE_PARAMETERS.getSeed() - ); - - ECPoint Q = keyToPoint(pubkey.getSigningKey()); - KeySpec keySpec = new ECPublicKeySpec(Q, spec); - PublicKey publicKey = KeyFactory.getInstance(ALGORITHM_ECDSA, provider).generatePublic(keySpec); - - return doCheckSignature(data, signature, publicKey); - } catch (GeneralSecurityException e) { - throw new ApplicationException(e); - } - } - - @Override - public byte[] getSignature(byte[] data, PrivateKey privateKey) { - try { - ECParameterSpec spec = new ECParameterSpec( - EC_CURVE_PARAMETERS.getCurve(), - EC_CURVE_PARAMETERS.getG(), - EC_CURVE_PARAMETERS.getN(), - EC_CURVE_PARAMETERS.getH(), - EC_CURVE_PARAMETERS.getSeed() - ); - - BigInteger d = keyToBigInt(privateKey.getPrivateSigningKey()); - KeySpec keySpec = new ECPrivateKeySpec(d, spec); - java.security.PrivateKey privKey = KeyFactory.getInstance(ALGORITHM_ECDSA, provider) - .generatePrivate(keySpec); - - return doSign(data, privKey); - } catch (GeneralSecurityException e) { - throw new ApplicationException(e); - } - } - - @Override - public byte[] multiply(byte[] K, byte[] r) { - return keyToPoint(K).multiply(keyToBigInt(r)).normalize().getEncoded(false); - } - - @Override - public byte[] createPoint(byte[] x, byte[] y) { - return EC_CURVE_PARAMETERS.getCurve().createPoint( - new BigInteger(1, x), - new BigInteger(1, y) - ).getEncoded(false); - } -} diff --git a/cryptography-sc/src/main/java/ch/dissem/bitmessage/cryptography/sc/SpongyCryptography.kt b/cryptography-sc/src/main/java/ch/dissem/bitmessage/cryptography/sc/SpongyCryptography.kt new file mode 100644 index 0000000..f2e2320 --- /dev/null +++ b/cryptography-sc/src/main/java/ch/dissem/bitmessage/cryptography/sc/SpongyCryptography.kt @@ -0,0 +1,122 @@ +/* + * Copyright 2015 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.cryptography.sc + +import ch.dissem.bitmessage.entity.payload.Pubkey +import ch.dissem.bitmessage.entity.valueobject.PrivateKey +import ch.dissem.bitmessage.ports.AbstractCryptography +import org.spongycastle.crypto.InvalidCipherTextException +import org.spongycastle.crypto.ec.CustomNamedCurves +import org.spongycastle.crypto.engines.AESEngine +import org.spongycastle.crypto.modes.CBCBlockCipher +import org.spongycastle.crypto.paddings.PKCS7Padding +import org.spongycastle.crypto.paddings.PaddedBufferedBlockCipher +import org.spongycastle.crypto.params.KeyParameter +import org.spongycastle.crypto.params.ParametersWithIV +import org.spongycastle.jce.provider.BouncyCastleProvider +import org.spongycastle.jce.spec.ECParameterSpec +import org.spongycastle.jce.spec.ECPrivateKeySpec +import org.spongycastle.jce.spec.ECPublicKeySpec +import org.spongycastle.math.ec.ECPoint +import java.math.BigInteger +import java.security.KeyFactory +import java.util.* + +/** + * As Spongycastle can't be used on the Oracle JVM, and Bouncycastle doesn't work properly on Android (thanks, Google), + * this is the Spongycastle implementation. + */ +object SpongyCryptography : AbstractCryptography(BouncyCastleProvider()) { + private val EC_CURVE_PARAMETERS = CustomNamedCurves.getByName("secp256k1") + + override fun crypt(encrypt: Boolean, data: ByteArray, key_e: ByteArray, initializationVector: ByteArray): ByteArray { + val cipher = PaddedBufferedBlockCipher( + CBCBlockCipher(AESEngine()), + PKCS7Padding() + ) + val params = ParametersWithIV(KeyParameter(key_e), initializationVector) + + cipher.init(encrypt, params) + + val buffer = ByteArray(cipher.getOutputSize(data.size)) + var length = cipher.processBytes(data, 0, data.size, buffer, 0) + try { + length += cipher.doFinal(buffer, length) + } catch (e: InvalidCipherTextException) { + throw IllegalArgumentException(e) + } + + if (length < buffer.size) { + return Arrays.copyOfRange(buffer, 0, length) + } + return buffer + } + + override fun createPublicKey(privateKey: ByteArray): ByteArray { + return EC_CURVE_PARAMETERS.g.multiply(keyToBigInt(privateKey)).normalize().getEncoded(false) + } + + private fun keyToPoint(publicKey: ByteArray): ECPoint { + val x = BigInteger(1, Arrays.copyOfRange(publicKey, 1, 33)) + val y = BigInteger(1, Arrays.copyOfRange(publicKey, 33, 65)) + return EC_CURVE_PARAMETERS.curve.createPoint(x, y) + } + + override fun isSignatureValid(data: ByteArray, signature: ByteArray, pubkey: Pubkey): Boolean { + val spec = ECParameterSpec( + EC_CURVE_PARAMETERS.curve, + EC_CURVE_PARAMETERS.g, + EC_CURVE_PARAMETERS.n, + EC_CURVE_PARAMETERS.h, + EC_CURVE_PARAMETERS.seed + ) + + val Q = keyToPoint(pubkey.signingKey) + val keySpec = ECPublicKeySpec(Q, spec) + val publicKey = KeyFactory.getInstance(ALGORITHM_ECDSA, provider).generatePublic(keySpec) + + return doCheckSignature(data, signature, publicKey) + } + + override fun getSignature(data: ByteArray, privateKey: PrivateKey): ByteArray { + val spec = ECParameterSpec( + EC_CURVE_PARAMETERS.curve, + EC_CURVE_PARAMETERS.g, + EC_CURVE_PARAMETERS.n, + EC_CURVE_PARAMETERS.h, + EC_CURVE_PARAMETERS.seed + ) + + val d = keyToBigInt(privateKey.privateSigningKey) + val keySpec = ECPrivateKeySpec(d, spec) + val privKey = KeyFactory.getInstance(ALGORITHM_ECDSA, provider) + .generatePrivate(keySpec) + + return doSign(data, privKey) + } + + override fun multiply(k: ByteArray, r: ByteArray): ByteArray { + return keyToPoint(k).multiply(keyToBigInt(r)).normalize().getEncoded(false) + } + + override fun createPoint(x: ByteArray, y: ByteArray): ByteArray { + return EC_CURVE_PARAMETERS.curve.createPoint( + BigInteger(1, x), + BigInteger(1, y) + ).getEncoded(false) + } +} diff --git a/cryptography-sc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.kt b/cryptography-sc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.kt index e456159..c8eafe0 100644 --- a/cryptography-sc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.kt +++ b/cryptography-sc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.kt @@ -16,7 +16,6 @@ package ch.dissem.bitmessage.security -import ch.dissem.bitmessage.InternalContext import ch.dissem.bitmessage.cryptography.sc.SpongyCryptography import ch.dissem.bitmessage.entity.ObjectMessage import ch.dissem.bitmessage.entity.payload.GenericPayload @@ -26,21 +25,16 @@ import ch.dissem.bitmessage.ports.MultiThreadedPOWEngine import ch.dissem.bitmessage.ports.ProofOfWorkEngine import ch.dissem.bitmessage.utils.CallbackWaiter import ch.dissem.bitmessage.utils.Singleton +import ch.dissem.bitmessage.utils.TestUtils import ch.dissem.bitmessage.utils.UnixTime -import org.junit.Assert -import org.junit.BeforeClass -import org.junit.Test - -import javax.xml.bind.DatatypeConverter -import java.io.ByteArrayInputStream -import java.io.IOException - import ch.dissem.bitmessage.utils.UnixTime.DAY import ch.dissem.bitmessage.utils.UnixTime.MINUTE import org.hamcrest.CoreMatchers.`is` import org.junit.Assert.* -import org.mockito.Mockito.mock -import org.mockito.Mockito.`when` +import org.junit.Test +import java.io.ByteArrayInputStream +import java.io.IOException +import javax.xml.bind.DatatypeConverter /** * @author Christian Basler @@ -168,7 +162,7 @@ class CryptographyTest { val TEST_RIPEMD160 = DatatypeConverter.parseHexBinary("" + "cd566972b5e50104011a92b59fa8e0b1234851ae") - private val crypto = SpongyCryptography() + private val crypto = SpongyCryptography init { Singleton.initialize(crypto) diff --git a/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java b/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java index 1da2910..2c9ca8e 100644 --- a/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java +++ b/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java @@ -61,7 +61,7 @@ public class Main { .messageRepo(new JdbcMessageRepository(jdbcConfig)) .powRepo(new JdbcProofOfWorkRepository(jdbcConfig)) .networkHandler(new NioNetworkHandler()) - .cryptography(new BouncyCryptography()) + .cryptography(BouncyCryptography.INSTANCE) .port(48444); if (options.localPort != null) { ctxBuilder.nodeRegistry(new NodeRegistry() { diff --git a/demo/src/test/java/ch/dissem/bitmessage/SystemTest.java b/demo/src/test/java/ch/dissem/bitmessage/SystemTest.java index fe3343d..293741f 100644 --- a/demo/src/test/java/ch/dissem/bitmessage/SystemTest.java +++ b/demo/src/test/java/ch/dissem/bitmessage/SystemTest.java @@ -94,7 +94,7 @@ public class SystemTest { .port(alicePort) .nodeRegistry(new TestNodeRegistry(bobPort)) .networkHandler(aliceNetworkHandler) - .cryptography(new BouncyCryptography()) + .cryptography(BouncyCryptography.INSTANCE) .listener(aliceListener) .labeler(aliceLabeler) .build(); @@ -110,7 +110,7 @@ public class SystemTest { .port(bobPort) .nodeRegistry(new TestNodeRegistry(alicePort)) .networkHandler(bobNetworkHandler) - .cryptography(new BouncyCryptography()) + .cryptography(BouncyCryptography.INSTANCE) .listener(bobListener) .labeler(new DebugLabeler("Bob")) .build(); diff --git a/networking/src/test/java/ch/dissem/bitmessage/networking/NetworkHandlerTest.java b/networking/src/test/java/ch/dissem/bitmessage/networking/NetworkHandlerTest.java index f079ede..a4e7d54 100644 --- a/networking/src/test/java/ch/dissem/bitmessage/networking/NetworkHandlerTest.java +++ b/networking/src/test/java/ch/dissem/bitmessage/networking/NetworkHandlerTest.java @@ -98,7 +98,7 @@ public class NetworkHandlerTest { .port(peerAddress.getPort()) .nodeRegistry(new TestNodeRegistry()) .networkHandler(peerNetworkHandler) - .cryptography(new BouncyCryptography()) + .cryptography(BouncyCryptography.INSTANCE) .listener(mock(BitmessageContext.Listener.class)) .customCommandHandler(new CustomCommandHandler() { @Override @@ -133,7 +133,7 @@ public class NetworkHandlerTest { .port(6002) .nodeRegistry(new TestNodeRegistry(peerAddress)) .networkHandler(nodeNetworkHandler) - .cryptography(new BouncyCryptography()) + .cryptography(BouncyCryptography.INSTANCE) .listener(mock(BitmessageContext.Listener.class)) .build(); } diff --git a/wif/src/test/java/ch/dissem/bitmessage/wif/WifExporterTest.java b/wif/src/test/java/ch/dissem/bitmessage/wif/WifExporterTest.java index 225f2f9..d5e8bde 100644 --- a/wif/src/test/java/ch/dissem/bitmessage/wif/WifExporterTest.java +++ b/wif/src/test/java/ch/dissem/bitmessage/wif/WifExporterTest.java @@ -36,7 +36,7 @@ public class WifExporterTest { @Before public void setUp() throws Exception { ctx = new BitmessageContext.Builder() - .cryptography(new BouncyCryptography()) + .cryptography(BouncyCryptography.INSTANCE) .networkHandler(mock(NetworkHandler.class)) .inventory(mock(Inventory.class)) .messageRepo(mock(MessageRepository.class)) @@ -104,4 +104,4 @@ public class WifExporterTest { exporter.addIdentity(chan); assertEquals(expected, exporter.toString()); } -} \ No newline at end of file +} diff --git a/wif/src/test/java/ch/dissem/bitmessage/wif/WifImporterTest.java b/wif/src/test/java/ch/dissem/bitmessage/wif/WifImporterTest.java index 398ff20..2116b3a 100644 --- a/wif/src/test/java/ch/dissem/bitmessage/wif/WifImporterTest.java +++ b/wif/src/test/java/ch/dissem/bitmessage/wif/WifImporterTest.java @@ -36,7 +36,7 @@ public class WifImporterTest { @Before public void setUp() throws Exception { ctx = new BitmessageContext.Builder() - .cryptography(new BouncyCryptography()) + .cryptography(BouncyCryptography.INSTANCE) .networkHandler(mock(NetworkHandler.class)) .inventory(mock(Inventory.class)) .messageRepo(mock(MessageRepository.class)) @@ -113,4 +113,4 @@ public class WifImporterTest { BitmessageAddress chan = importer.getIdentities().get(0); assertTrue(chan.isChan()); } -} \ No newline at end of file +} From 894e0ff72409e06a5911b8736d6dd5cb925dd51d Mon Sep 17 00:00:00 2001 From: Christian Basler <chrigu.meyer@gmail.com> Date: Fri, 16 Jun 2017 07:03:12 +0200 Subject: [PATCH 04/10] Fixes and improvements, SystemTest still broken --- README.md | 2 +- build.gradle | 1 + .../ch/dissem/bitmessage/BitmessageContext.kt | 133 ++++--- .../bitmessage/DefaultMessageListener.kt | 68 ++-- .../ch/dissem/bitmessage/InternalContext.kt | 39 +- .../dissem/bitmessage/ProofOfWorkService.kt | 57 ++- .../java/ch/dissem/bitmessage/entity/Addr.kt | 4 +- .../ch/dissem/bitmessage/entity/GetData.kt | 4 +- .../java/ch/dissem/bitmessage/entity/Inv.kt | 4 +- .../bitmessage/entity/NetworkMessage.kt | 8 +- .../ch/dissem/bitmessage/entity/Plaintext.kt | 189 +++++----- .../ch/dissem/bitmessage/entity/Version.kt | 4 +- .../bitmessage/entity/payload/CryptoBox.kt | 8 +- .../bitmessage/entity/payload/V2Pubkey.kt | 4 +- .../bitmessage/entity/valueobject/Label.kt | 13 +- .../entity/valueobject/NetworkAddress.kt | 18 +- .../entity/valueobject/PrivateKey.kt | 8 +- .../bitmessage/ports/AbstractCryptography.kt | 24 +- .../ports/AbstractMessageRepository.kt | 18 +- .../dissem/bitmessage/ports/Cryptography.kt | 12 +- .../dissem/bitmessage/ports/DefaultLabeler.kt | 2 +- .../ch/dissem/bitmessage/ports/Inventory.kt | 4 +- .../bitmessage/ports/MessageRepository.kt | 4 +- .../dissem/bitmessage/ports/NetworkHandler.kt | 2 +- .../bitmessage/ports/ProofOfWorkRepository.kt | 4 +- .../bitmessage/utils/ConversationService.kt | 1 + .../java/ch/dissem/bitmessage/utils/Encode.kt | 34 +- .../bitmessage/BitmessageContextTest.kt | 76 ++-- .../bitmessage/DefaultMessageListenerTest.kt | 18 +- .../bitmessage/ProofOfWorkServiceTest.kt | 14 +- .../ch/dissem/bitmessage/SignatureTest.kt | 12 +- .../entity/BitmessageAddressTest.kt | 20 +- .../bitmessage/entity/SerializationTest.kt | 8 +- .../bitmessage/testutils/TestInventory.kt | 8 +- .../bitmessage/utils/CollectionsTest.kt | 2 +- .../utils/ConversationServiceTest.kt | 9 +- .../ch/dissem/bitmessage/utils/EncodeTest.kt | 4 +- .../ch/dissem/bitmessage/utils/TestUtils.kt | 8 +- .../cryptography/bc/BouncyCryptography.kt | 7 +- .../bitmessage/security/CryptographyTest.kt | 2 +- .../cryptography/sc/SpongyCryptography.kt | 7 +- .../bitmessage/security/CryptographyTest.kt | 2 +- .../java/ch/dissem/bitmessage/demo/Main.java | 2 +- .../java/ch/dissem/bitmessage/SystemTest.java | 99 +++-- .../extensions/CryptoCustomMessage.kt | 4 +- .../networking/nio/NioNetworkHandler.java | 17 +- .../networking/NetworkHandlerTest.java | 4 +- .../repository/JdbcAddressRepository.java | 9 +- .../repository/JdbcMessageRepository.java | 344 ------------------ .../repository/JdbcMessageRepository.kt | 344 ++++++++++++++++++ .../repository/JdbcProofOfWorkRepository.java | 8 +- .../JdbcProofOfWorkRepositoryTest.kt | 4 +- .../dissem/bitmessage/repository/TestBase.kt | 7 +- wif/build.gradle | 1 + .../ch/dissem/bitmessage/wif/WifExporter.java | 106 ------ .../ch/dissem/bitmessage/wif/WifExporter.kt | 103 ++++++ .../ch/dissem/bitmessage/wif/WifImporter.java | 119 ------ .../ch/dissem/bitmessage/wif/WifImporter.kt | 126 +++++++ .../bitmessage/wif/WifExporterTest.java | 107 ------ .../dissem/bitmessage/wif/WifExporterTest.kt | 122 +++++++ .../bitmessage/wif/WifImporterTest.java | 116 ------ .../dissem/bitmessage/wif/WifImporterTest.kt | 132 +++++++ 62 files changed, 1364 insertions(+), 1276 deletions(-) delete mode 100644 repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcMessageRepository.java create mode 100644 repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcMessageRepository.kt delete mode 100644 wif/src/main/java/ch/dissem/bitmessage/wif/WifExporter.java create mode 100644 wif/src/main/java/ch/dissem/bitmessage/wif/WifExporter.kt delete mode 100644 wif/src/main/java/ch/dissem/bitmessage/wif/WifImporter.java create mode 100644 wif/src/main/java/ch/dissem/bitmessage/wif/WifImporter.kt delete mode 100644 wif/src/test/java/ch/dissem/bitmessage/wif/WifExporterTest.java create mode 100644 wif/src/test/java/ch/dissem/bitmessage/wif/WifExporterTest.kt delete mode 100644 wif/src/test/java/ch/dissem/bitmessage/wif/WifImporterTest.java create mode 100644 wif/src/test/java/ch/dissem/bitmessage/wif/WifImporterTest.kt diff --git a/README.md b/README.md index dcbffa4..75f2b28 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ BitmessageContext ctx = new BitmessageContext.Builder() .powRepo(new JdbcProofOfWorkRepository(jdbcConfig)) .nodeRegistry(new JdbcNodeRegistry(jdbcConfig)) .networkHandler(new NioNetworkHandler()) - .cryptography(BouncyCryptography.INSTANCE) + .cryptography(new BouncyCryptography()) .listener(System.out::println) .build(); ``` diff --git a/build.gradle b/build.gradle index 26aa243..02fa2b4 100644 --- a/build.gradle +++ b/build.gradle @@ -29,6 +29,7 @@ subprojects { } dependencies { compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version" + compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" } test { diff --git a/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.kt b/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.kt index 175e378..f3ffa6c 100644 --- a/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.kt +++ b/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.kt @@ -58,7 +58,53 @@ import kotlin.properties.Delegates * * The port defaults to 8444 (the default Bitmessage port) */ -class BitmessageContext private constructor(builder: BitmessageContext.Builder) { +class BitmessageContext( + cryptography: Cryptography, + inventory: Inventory, + nodeRegistry: NodeRegistry, + networkHandler: NetworkHandler, + addressRepository: AddressRepository, + messageRepository: MessageRepository, + proofOfWorkRepository: ProofOfWorkRepository, + proofOfWorkEngine: ProofOfWorkEngine = MultiThreadedPOWEngine(), + customCommandHandler: CustomCommandHandler = object : CustomCommandHandler { + override fun handle(request: CustomMessage): MessagePayload? { + BitmessageContext.LOG.debug("Received custom request, but no custom command handler configured.") + return null + } + }, + listener: Listener, + labeler: Labeler = DefaultLabeler(), + port: Int = 8444, + connectionTTL: Long = 30 * MINUTE, + connectionLimit: Int = 150, + sendPubkeyOnIdentityCreation: Boolean, + doMissingProofOfWorkDelayInSeconds: Int = 30 +) { + + private constructor(builder: BitmessageContext.Builder) : this( + builder.cryptography, + builder.inventory, + builder.nodeRegistry, + builder.networkHandler, + builder.addressRepo, + builder.messageRepo, + builder.proofOfWorkRepository, + builder.proofOfWorkEngine ?: MultiThreadedPOWEngine(), + builder.customCommandHandler ?: object : CustomCommandHandler { + override fun handle(request: CustomMessage): MessagePayload? { + BitmessageContext.LOG.debug("Received custom request, but no custom command handler configured.") + return null + } + }, + builder.listener, + builder.labeler ?: DefaultLabeler(), + builder.port, + builder.connectionTTL, + builder.connectionLimit, + builder.sendPubkeyOnIdentityCreation, + builder.doMissingProofOfWorkDelay + ) private val sendPubkeyOnIdentityCreation: Boolean @@ -72,38 +118,10 @@ class BitmessageContext private constructor(builder: BitmessageContext.Builder) val labeler: Labeler @JvmName("labeler") get - init { - labeler = builder.labeler ?: DefaultLabeler() - internals = InternalContext( - builder.cryptography, - builder.inventory, - builder.nodeRegistry, - builder.networkHandler, - builder.addressRepo, - builder.messageRepo, - builder.proofOfWorkRepository, - builder.proofOfWorkEngine ?: MultiThreadedPOWEngine(), - builder.customCommandHandler ?: object : CustomCommandHandler { - override fun handle(request: CustomMessage): MessagePayload? { - LOG.debug("Received custom request, but no custom command handler configured.") - return null - } - }, - builder.listener, - labeler, - builder.port, - builder.connectionTTL, - builder.connectionLimit - ) - internals.proofOfWorkService.doMissingProofOfWork(30000) // TODO: this should be configurable - sendPubkeyOnIdentityCreation = builder.sendPubkeyOnIdentityCreation - (builder.listener as? Listener.WithContext)?.setContext(this) - } - - val addresses: AddressRepository = internals.addressRepository + val addresses: AddressRepository @JvmName("addresses") get - val messages: MessageRepository = internals.messageRepository + val messages: MessageRepository @JvmName("messages") get fun createIdentity(shorter: Boolean, vararg features: Feature): BitmessageAddress { @@ -285,10 +303,9 @@ class BitmessageContext private constructor(builder: BitmessageContext.Builder) fun addContact(contact: BitmessageAddress) { internals.addressRepository.save(contact) if (contact.pubkey == null) { - internals.addressRepository.getAddress(contact.address)?.let { - if (it.pubkey == null) { - internals.requestPubkey(contact) - } + // If it already existed, the saved contact might have the public key + if (internals.addressRepository.getAddress(contact.address)!!.pubkey == null) { + internals.requestPubkey(contact) } } } @@ -300,13 +317,13 @@ class BitmessageContext private constructor(builder: BitmessageContext.Builder) } private fun tryToFindBroadcastsForAddress(address: BitmessageAddress) { - for (`object` in internals.inventory.getObjects(address.stream, Broadcast.getVersion(address), ObjectType.BROADCAST)) { + for (objectMessage in internals.inventory.getObjects(address.stream, Broadcast.getVersion(address), ObjectType.BROADCAST)) { try { - val broadcast = `object`.payload as Broadcast + val broadcast = objectMessage.payload as Broadcast broadcast.decrypt(address) // This decrypts it twice, but on the other hand it doesn't try to decrypt the objects with // other subscriptions and the interface stays as simple as possible. - internals.networkListener.receive(`object`) + internals.networkListener.receive(objectMessage) } catch (ignore: DecryptionFailedException) { } catch (e: Exception) { LOG.debug(e.message, e) @@ -348,6 +365,7 @@ class BitmessageContext private constructor(builder: BitmessageContext.Builder) internal var connectionLimit = 150 internal var connectionTTL = 30 * MINUTE internal var sendPubkeyOnIdentityCreation = true + internal var doMissingProofOfWorkDelay = 30 fun port(port: Int): Builder { this.port = port @@ -409,6 +427,7 @@ class BitmessageContext private constructor(builder: BitmessageContext.Builder) return this } + @JvmName("kotlinListener") fun listener(listener: (Plaintext) -> Unit): Builder { this.listener = object : Listener { override fun receive(plaintext: Plaintext) { @@ -428,6 +447,10 @@ class BitmessageContext private constructor(builder: BitmessageContext.Builder) return this } + fun doMissingProofOfWorkDelay(seconds: Int) { + this.doMissingProofOfWorkDelay = seconds + } + /** * By default a client will send the public key when an identity is being created. On weaker devices * this behaviour might not be desirable. @@ -438,18 +461,34 @@ class BitmessageContext private constructor(builder: BitmessageContext.Builder) } fun build(): BitmessageContext { - nonNull("inventory", inventory) - nonNull("nodeRegistry", nodeRegistry) - nonNull("networkHandler", networkHandler) - nonNull("addressRepo", addressRepo) - nonNull("messageRepo", messageRepo) - nonNull("proofOfWorkRepo", proofOfWorkRepository) return BitmessageContext(this) } + } - private fun nonNull(name: String, o: Any?) { - if (o == null) throw IllegalStateException(name + " must not be null") - } + + init { + this.labeler = labeler + this.internals = InternalContext( + cryptography, + inventory, + nodeRegistry, + networkHandler, + addressRepository, + messageRepository, + proofOfWorkRepository, + proofOfWorkEngine, + customCommandHandler, + listener, + labeler, + port, + connectionTTL, + connectionLimit + ) + this.addresses = addressRepository + this.messages = messageRepository + this.sendPubkeyOnIdentityCreation = sendPubkeyOnIdentityCreation + (listener as? Listener.WithContext)?.setContext(this) + internals.proofOfWorkService.doMissingProofOfWork(doMissingProofOfWorkDelayInSeconds * 1000L) } companion object { diff --git a/core/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.kt b/core/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.kt index 35a7628..37c2a75 100644 --- a/core/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.kt +++ b/core/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.kt @@ -25,6 +25,7 @@ import ch.dissem.bitmessage.entity.valueobject.InventoryVector import ch.dissem.bitmessage.exception.DecryptionFailedException import ch.dissem.bitmessage.ports.Labeler import ch.dissem.bitmessage.ports.NetworkHandler +import ch.dissem.bitmessage.utils.Strings.hex import org.slf4j.LoggerFactory import java.util.* @@ -32,23 +33,23 @@ internal open class DefaultMessageListener( private val labeler: Labeler, private val listener: BitmessageContext.Listener ) : NetworkHandler.MessageListener { - private var ctx by InternalContext + private var ctx by InternalContext.lateinit - override fun receive(`object`: ObjectMessage) { - val payload = `object`.payload + override fun receive(objectMessage: ObjectMessage) { + val payload = objectMessage.payload when (payload.type) { ObjectType.GET_PUBKEY -> { - receive(`object`, payload as GetPubkey) + receive(objectMessage, payload as GetPubkey) } ObjectType.PUBKEY -> { - receive(`object`, payload as Pubkey) + receive(objectMessage, payload as Pubkey) } ObjectType.MSG -> { - receive(`object`, payload as Msg) + receive(objectMessage, payload as Msg) } ObjectType.BROADCAST -> { - receive(`object`, payload as Broadcast) + receive(objectMessage, payload as Broadcast) } null -> { if (payload is GenericPayload) { @@ -61,30 +62,33 @@ internal open class DefaultMessageListener( } } - protected fun receive(`object`: ObjectMessage, getPubkey: GetPubkey) { + protected fun receive(objectMessage: ObjectMessage, getPubkey: GetPubkey) { val identity = ctx.addressRepository.findIdentity(getPubkey.ripeTag) if (identity != null && identity.privateKey != null && !identity.isChan) { LOG.info("Got pubkey request for identity " + identity) // FIXME: only send pubkey if it wasn't sent in the last TTL.pubkey() days - ctx.sendPubkey(identity, `object`.stream) + ctx.sendPubkey(identity, objectMessage.stream) } } - protected fun receive(`object`: ObjectMessage, pubkey: Pubkey) { - val address: BitmessageAddress? + protected fun receive(objectMessage: ObjectMessage, pubkey: Pubkey) { try { if (pubkey is V4Pubkey) { - address = ctx.addressRepository.findContact(pubkey.tag) - if (address != null) { - pubkey.decrypt(address.publicDecryptionKey) + ctx.addressRepository.findContact(pubkey.tag)?.let { + if (it.pubkey == null) { + pubkey.decrypt(it.publicDecryptionKey) + updatePubkey(it, pubkey) + } } } else { - address = ctx.addressRepository.findContact(pubkey.ripe) + ctx.addressRepository.findContact(pubkey.ripe)?.let { + if (it.pubkey == null) { + updatePubkey(it, pubkey) + } + } } - if (address != null && address.pubkey == null) { - updatePubkey(address, pubkey) - } - } catch (_: DecryptionFailedException) {} + } catch (_: DecryptionFailedException) { + } } @@ -101,19 +105,20 @@ internal open class DefaultMessageListener( } } - protected fun receive(`object`: ObjectMessage, msg: Msg) { + protected fun receive(objectMessage: ObjectMessage, msg: Msg) { for (identity in ctx.addressRepository.getIdentities()) { try { msg.decrypt(identity.privateKey!!.privateEncryptionKey) val plaintext = msg.plaintext!! plaintext.to = identity - if (!`object`.isSignatureValid(plaintext.from.pubkey!!)) { - LOG.warn("Msg with IV " + `object`.inventoryVector + " was successfully decrypted, but signature check failed. Ignoring.") + if (!objectMessage.isSignatureValid(plaintext.from.pubkey!!)) { + LOG.warn("Msg with IV " + objectMessage.inventoryVector + " was successfully decrypted, but signature check failed. Ignoring.") } else { - receive(`object`.inventoryVector, plaintext) + receive(objectMessage.inventoryVector, plaintext) } break - } catch (_: DecryptionFailedException) {} + } catch (_: DecryptionFailedException) { + } } } @@ -122,11 +127,11 @@ internal open class DefaultMessageListener( ctx.messageRepository.getMessageForAck(ack.data)?.let { ctx.labeler.markAsAcknowledged(it) ctx.messageRepository.save(it) - } + } ?: LOG.debug("Message not found for ack ${hex(ack.data)}") } } - protected fun receive(`object`: ObjectMessage, broadcast: Broadcast) { + protected fun receive(objectMessage: ObjectMessage, broadcast: Broadcast) { val tag = if (broadcast is V5Broadcast) broadcast.tag else null for (subscription in ctx.addressRepository.getSubscriptions(broadcast.version)) { if (tag != null && !Arrays.equals(tag, subscription.tag)) { @@ -134,12 +139,13 @@ internal open class DefaultMessageListener( } try { broadcast.decrypt(subscription.publicDecryptionKey) - if (!`object`.isSignatureValid(broadcast.plaintext!!.from.pubkey!!)) { - LOG.warn("Broadcast with IV " + `object`.inventoryVector + " was successfully decrypted, but signature check failed. Ignoring.") + if (!objectMessage.isSignatureValid(broadcast.plaintext!!.from.pubkey!!)) { + LOG.warn("Broadcast with IV " + objectMessage.inventoryVector + " was successfully decrypted, but signature check failed. Ignoring.") } else { - receive(`object`.inventoryVector, broadcast.plaintext!!) + receive(objectMessage.inventoryVector, broadcast.plaintext!!) } - } catch (_: DecryptionFailedException) {} + } catch (_: DecryptionFailedException) { + } } } @@ -158,7 +164,7 @@ internal open class DefaultMessageListener( msg.ackMessage?.let { ctx.inventory.storeObject(it) ctx.networkHandler.offer(it.inventoryVector) - } + } ?: LOG.debug("ack message expected") } } diff --git a/core/src/main/java/ch/dissem/bitmessage/InternalContext.kt b/core/src/main/java/ch/dissem/bitmessage/InternalContext.kt index e36a8ac..62bba56 100644 --- a/core/src/main/java/ch/dissem/bitmessage/InternalContext.kt +++ b/core/src/main/java/ch/dissem/bitmessage/InternalContext.kt @@ -28,7 +28,6 @@ import ch.dissem.bitmessage.utils.UnixTime import org.slf4j.LoggerFactory import java.util.* import java.util.concurrent.Executors -import kotlin.properties.Delegates import kotlin.reflect.KProperty /** @@ -68,7 +67,8 @@ class InternalContext( get() = _streams.toLongArray() init { - instance = this + lateinit.instance = this + lateinit = ContextDelegate() Singleton.initialize(cryptography) // TODO: streams of new identities and subscriptions should also be added. This works only after a restart. @@ -102,24 +102,24 @@ class InternalContext( val recipient = to ?: from val expires = UnixTime.now + timeToLive LOG.info("Expires at " + expires) - val `object` = ObjectMessage( + val objectMessage = ObjectMessage( stream = recipient.stream, expiresTime = expires, payload = payload ) - if (`object`.isSigned) { - `object`.sign( + if (objectMessage.isSigned) { + objectMessage.sign( from.privateKey ?: throw IllegalArgumentException("The given sending address is no identity") ) } if (payload is Broadcast) { payload.encrypt() } else if (payload is Encrypted) { - `object`.encrypt( + objectMessage.encrypt( recipient.pubkey ?: throw IllegalArgumentException("The public key for the recipient isn't available") ) } - proofOfWorkService.doProofOfWork(to, `object`) + proofOfWorkService.doProofOfWork(to, objectMessage) } fun sendPubkey(identity: BitmessageAddress, targetStream: Long) { @@ -163,12 +163,11 @@ class InternalContext( } val expires = UnixTime.now + TTL.getpubkey - LOG.info("Expires at " + expires) - val payload = GetPubkey(contact) + LOG.info("Expires at $expires") val request = ObjectMessage( stream = contact.stream, expiresTime = expires, - payload = payload + payload = GetPubkey(contact) ) proofOfWorkService.doProofOfWork(request) } @@ -179,14 +178,14 @@ class InternalContext( address.alias = it.alias address.isSubscribed = it.isSubscribed } - for (`object` in inventory.getObjects(address.stream, address.version, ObjectType.PUBKEY)) { + for (objectMessage in inventory.getObjects(address.stream, address.version, ObjectType.PUBKEY)) { try { - val pubkey = `object`.payload as Pubkey + val pubkey = objectMessage.payload as Pubkey if (address.version == 4L) { val v4Pubkey = pubkey as V4Pubkey if (Arrays.equals(address.tag, v4Pubkey.tag)) { v4Pubkey.decrypt(address.publicDecryptionKey) - if (`object`.isSignatureValid(v4Pubkey)) { + if (objectMessage.isSignatureValid(v4Pubkey)) { address.pubkey = v4Pubkey addressRepository.save(address) break @@ -219,17 +218,19 @@ class InternalContext( fun setContext(context: InternalContext) } + class ContextDelegate { + internal lateinit var instance: InternalContext + operator fun getValue(thisRef: Any?, property: KProperty<*>) = instance + operator fun setValue(thisRef: Any?, property: KProperty<*>, value: InternalContext) {} + } + companion object { private val LOG = LoggerFactory.getLogger(InternalContext::class.java) @JvmField val NETWORK_NONCE_TRIALS_PER_BYTE: Long = 1000 @JvmField val NETWORK_EXTRA_BYTES: Long = 1000 - private var instance: InternalContext by Delegates.notNull<InternalContext>() - - operator fun getValue(thisRef: Any?, property: KProperty<*>) = instance - operator fun setValue(thisRef: Any?, property: KProperty<*>, value: InternalContext) { - instance = value - } + var lateinit = ContextDelegate() + private set } } diff --git a/core/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.kt b/core/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.kt index 2472d67..67d6637 100644 --- a/core/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.kt +++ b/core/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.kt @@ -22,6 +22,7 @@ import ch.dissem.bitmessage.entity.* import ch.dissem.bitmessage.entity.payload.Msg import ch.dissem.bitmessage.ports.ProofOfWorkEngine import ch.dissem.bitmessage.ports.ProofOfWorkRepository.Item +import ch.dissem.bitmessage.utils.Strings import org.slf4j.LoggerFactory import java.io.IOException import java.util.* @@ -31,7 +32,7 @@ import java.util.* */ class ProofOfWorkService : ProofOfWorkEngine.Callback { - private val ctx by InternalContext + private val ctx by InternalContext.lateinit private val cryptography by lazy { ctx.cryptography } private val powRepo by lazy { ctx.proofOfWorkRepository } private val messageRepo by lazy { ctx.messageRepository } @@ -45,75 +46,69 @@ class ProofOfWorkService : ProofOfWorkEngine.Callback { override fun run() { LOG.info("Doing POW for " + items.size + " tasks.") for (initialHash in items) { - val (`object`, nonceTrialsPerByte, extraBytes) = powRepo.getItem(initialHash) - cryptography.doProofOfWork(`object`, nonceTrialsPerByte, extraBytes, + val (objectMessage, nonceTrialsPerByte, extraBytes) = powRepo.getItem(initialHash) + cryptography.doProofOfWork(objectMessage, nonceTrialsPerByte, extraBytes, this@ProofOfWorkService) } } }, delayInMilliseconds) } - fun doProofOfWork(`object`: ObjectMessage) { - doProofOfWork(null, `object`) + fun doProofOfWork(objectMessage: ObjectMessage) { + doProofOfWork(null, objectMessage) } - fun doProofOfWork(recipient: BitmessageAddress?, `object`: ObjectMessage) { + fun doProofOfWork(recipient: BitmessageAddress?, objectMessage: ObjectMessage) { val pubkey = recipient?.pubkey val nonceTrialsPerByte = pubkey?.nonceTrialsPerByte ?: NETWORK_NONCE_TRIALS_PER_BYTE val extraBytes = pubkey?.extraBytes ?: NETWORK_EXTRA_BYTES - powRepo.putObject(`object`, nonceTrialsPerByte, extraBytes) - if (`object`.payload is PlaintextHolder) { - `object`.payload.plaintext?.let { - it.initialHash = cryptography.getInitialHash(`object`) + powRepo.putObject(objectMessage, nonceTrialsPerByte, extraBytes) + if (objectMessage.payload is PlaintextHolder) { + objectMessage.payload.plaintext?.let { + it.initialHash = cryptography.getInitialHash(objectMessage) messageRepo.save(it) - } + } ?: LOG.error("PlaintextHolder without Plaintext shouldn't make it to the POW") } - cryptography.doProofOfWork(`object`, nonceTrialsPerByte, extraBytes, this) + cryptography.doProofOfWork(objectMessage, nonceTrialsPerByte, extraBytes, this) } fun doProofOfWorkWithAck(plaintext: Plaintext, expirationTime: Long) { - val ack = plaintext.ackMessage + val ack = plaintext.ackMessage!! messageRepo.save(plaintext) - val item = Item(ack!!, NETWORK_NONCE_TRIALS_PER_BYTE, NETWORK_EXTRA_BYTES, + val item = Item(ack, NETWORK_NONCE_TRIALS_PER_BYTE, NETWORK_EXTRA_BYTES, expirationTime, plaintext) powRepo.putObject(item) cryptography.doProofOfWork(ack, NETWORK_NONCE_TRIALS_PER_BYTE, NETWORK_EXTRA_BYTES, this) } override fun onNonceCalculated(initialHash: ByteArray, nonce: ByteArray) { - val (`object`, _, _, expirationTime, message) = powRepo.getItem(initialHash) + val (objectMessage, _, _, expirationTime, message) = powRepo.getItem(initialHash) if (message == null) { - `object`.nonce = nonce + objectMessage.nonce = nonce messageRepo.getMessage(initialHash)?.let { - it.inventoryVector = `object`.inventoryVector + it.inventoryVector = objectMessage.inventoryVector it.updateNextTry() ctx.labeler.markAsSent(it) messageRepo.save(it) } - try { - ctx.networkListener.receive(`object`) - } catch (e: IOException) { - LOG.debug(e.message, e) - } - - ctx.inventory.storeObject(`object`) - ctx.networkHandler.offer(`object`.inventoryVector) + ctx.inventory.storeObject(objectMessage) + ctx.networkHandler.offer(objectMessage.inventoryVector) } else { message.ackMessage!!.nonce = nonce - val `object` = ObjectMessage.Builder() + val newObjectMessage = ObjectMessage.Builder() .stream(message.stream) .expiresTime(expirationTime!!) .payload(Msg(message)) .build() - if (`object`.isSigned) { - `object`.sign(message.from.privateKey!!) + if (newObjectMessage.isSigned) { + newObjectMessage.sign(message.from.privateKey!!) } - if (`object`.payload is Encrypted) { - `object`.encrypt(message.to!!.pubkey!!) + if (newObjectMessage.payload is Encrypted) { + newObjectMessage.encrypt(message.to!!.pubkey!!) } - doProofOfWork(message.to, `object`) + doProofOfWork(message.to, newObjectMessage) } powRepo.removeObject(initialHash) } diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/Addr.kt b/core/src/main/java/ch/dissem/bitmessage/entity/Addr.kt index 0e7e8f6..056dd63 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/Addr.kt +++ b/core/src/main/java/ch/dissem/bitmessage/entity/Addr.kt @@ -28,14 +28,14 @@ data class Addr constructor(val addresses: List<NetworkAddress>) : MessagePayloa override val command: MessagePayload.Command = MessagePayload.Command.ADDR override fun write(out: OutputStream) { - Encode.varInt(addresses.size.toLong(), out) + Encode.varInt(addresses.size, out) for (address in addresses) { address.write(out) } } override fun write(buffer: ByteBuffer) { - Encode.varInt(addresses.size.toLong(), buffer) + Encode.varInt(addresses.size, buffer) for (address in addresses) { address.write(buffer) } diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/GetData.kt b/core/src/main/java/ch/dissem/bitmessage/entity/GetData.kt index 62fab2b..feebea1 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/GetData.kt +++ b/core/src/main/java/ch/dissem/bitmessage/entity/GetData.kt @@ -29,14 +29,14 @@ class GetData constructor(var inventory: List<InventoryVector>) : MessagePayload override val command: MessagePayload.Command = MessagePayload.Command.GETDATA override fun write(out: OutputStream) { - Encode.varInt(inventory.size.toLong(), out) + Encode.varInt(inventory.size, out) for (iv in inventory) { iv.write(out) } } override fun write(buffer: ByteBuffer) { - Encode.varInt(inventory.size.toLong(), buffer) + Encode.varInt(inventory.size, buffer) for (iv in inventory) { iv.write(buffer) } diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/Inv.kt b/core/src/main/java/ch/dissem/bitmessage/entity/Inv.kt index 42f963f..b0c5af9 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/Inv.kt +++ b/core/src/main/java/ch/dissem/bitmessage/entity/Inv.kt @@ -29,14 +29,14 @@ class Inv constructor(val inventory: List<InventoryVector>) : MessagePayload { override val command: MessagePayload.Command = MessagePayload.Command.INV override fun write(out: OutputStream) { - Encode.varInt(inventory.size.toLong(), out) + Encode.varInt(inventory.size, out) for (iv in inventory) { iv.write(out) } } override fun write(buffer: ByteBuffer) { - Encode.varInt(inventory.size.toLong(), buffer) + Encode.varInt(inventory.size, buffer) for (iv in inventory) { iv.write(buffer) } diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/NetworkMessage.kt b/core/src/main/java/ch/dissem/bitmessage/entity/NetworkMessage.kt index 3b5369e..d0c427a 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/NetworkMessage.kt +++ b/core/src/main/java/ch/dissem/bitmessage/entity/NetworkMessage.kt @@ -43,7 +43,7 @@ data class NetworkMessage( @Throws(IOException::class) override fun write(out: OutputStream) { // magic - Encode.int32(MAGIC.toLong(), out) + Encode.int32(MAGIC, out) // ASCII string identifying the packet content, NULL padded (non-NULL padding results in packet rejected) val command = payload.command.name.toLowerCase() @@ -57,7 +57,7 @@ data class NetworkMessage( // Length of payload in number of bytes. Because of other restrictions, there is no reason why this length would // ever be larger than 1600003 bytes. Some clients include a sanity-check to avoid processing messages which are // larger than this. - Encode.int32(payloadBytes.size.toLong(), out) + Encode.int32(payloadBytes.size, out) // checksum out.write(getChecksum(payloadBytes)) @@ -92,7 +92,7 @@ data class NetworkMessage( private fun writeHeader(out: ByteBuffer): ByteArray { // magic - Encode.int32(MAGIC.toLong(), out) + Encode.int32(MAGIC, out) // ASCII string identifying the packet content, NULL padded (non-NULL padding results in packet rejected) val command = payload.command.name.toLowerCase() @@ -107,7 +107,7 @@ data class NetworkMessage( // Length of payload in number of bytes. Because of other restrictions, there is no reason why this length would // ever be larger than 1600003 bytes. Some clients include a sanity-check to avoid processing messages which are // larger than this. - Encode.int32(payloadBytes.size.toLong(), out) + Encode.int32(payloadBytes.size, out) // checksum out.put(getChecksum(payloadBytes)) diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.kt b/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.kt index fc4ee0a..462ae83 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.kt +++ b/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.kt @@ -17,6 +17,7 @@ package ch.dissem.bitmessage.entity import ch.dissem.bitmessage.entity.Plaintext.Encoding.* +import ch.dissem.bitmessage.entity.Plaintext.Type.MSG import ch.dissem.bitmessage.entity.payload.Msg import ch.dissem.bitmessage.entity.payload.Pubkey.Feature import ch.dissem.bitmessage.entity.valueobject.ExtendedEncoding @@ -38,10 +39,20 @@ import kotlin.collections.HashSet fun message(encoding: Plaintext.Encoding, subject: String, body: String): ByteArray = when (encoding) { SIMPLE -> "Subject:$subject\nBody:$body".toByteArray() EXTENDED -> Message.Builder().subject(subject).body(body).build().zip() - TRIVIAL -> (subject+body).toByteArray() + TRIVIAL -> (subject + body).toByteArray() IGNORE -> ByteArray(0) } +fun ackData(type: Plaintext.Type, ackData: ByteArray?): ByteArray? { + if (ackData != null) { + return ackData + } else if (type == MSG) { + return cryptography().randomBytes(Msg.ACK_LENGTH) + } else { + return null + } +} + /** * The unencrypted message to be sent by 'msg' or 'broadcast'. */ @@ -52,7 +63,7 @@ class Plaintext private constructor( val encodingCode: Long, val message: ByteArray, val ackData: ByteArray?, - ackMessage: Lazy<ObjectMessage?>, + ackMessage: Lazy<ObjectMessage?> = lazy { Factory.createAck(from, ackData, ttl) }, val conversationId: UUID = UUID.randomUUID(), var inventoryVector: InventoryVector? = null, var signature: ByteArray? = null, @@ -121,7 +132,7 @@ class Plaintext private constructor( to: BitmessageAddress?, encoding: Encoding, message: ByteArray, - ackData: ByteArray = cryptography().randomBytes(Msg.ACK_LENGTH), + ackData: ByteArray? = null, conversationId: UUID = UUID.randomUUID(), inventoryVector: InventoryVector? = null, signature: ByteArray? = null, @@ -131,21 +142,20 @@ class Plaintext private constructor( labels: MutableSet<Label> = HashSet(), status: Status ) : this( - type, - from, - to, - encoding.code, - message, - ackData, - lazy { Factory.createAck(from, ackData, ttl) }, - conversationId, - inventoryVector, - signature, - received, - initialHash, - ttl, - labels, - status + type = type, + from = from, + to = to, + encoding = encoding.code, + message = message, + ackMessage = ackData(type, ackData), + conversationId = conversationId, + inventoryVector = inventoryVector, + signature = signature, + received = received, + initialHash = initialHash, + ttl = ttl, + labels = labels, + status = status ) constructor( @@ -164,13 +174,13 @@ class Plaintext private constructor( labels: MutableSet<Label> = HashSet(), status: Status ) : this( - type, - from, - to, - encoding, - message, - null, - lazy { + type = type, + from = from, + to = to, + encodingCode = encoding, + message = message, + ackData = null, + ackMessage = lazy { if (ackMessage != null && ackMessage.isNotEmpty()) { Factory.getObjectMessage( 3, @@ -178,14 +188,14 @@ class Plaintext private constructor( ackMessage.size) } else null }, - conversationId, - inventoryVector, - signature, - received, - initialHash, - ttl, - labels, - status + conversationId = conversationId, + inventoryVector = inventoryVector, + signature = signature, + received = received, + initialHash = initialHash, + ttl = ttl, + labels = labels, + status = status ) constructor( @@ -195,37 +205,36 @@ class Plaintext private constructor( encoding: Encoding = SIMPLE, subject: String, body: String, - ackData: ByteArray = cryptography().randomBytes(Msg.ACK_LENGTH), + ackData: ByteArray? = null, conversationId: UUID = UUID.randomUUID(), ttl: Long = TTL.msg, labels: MutableSet<Label> = HashSet(), status: Status = Status.DRAFT ) : this( - type, - from, - to, - encoding.code, - message(encoding, subject, body), - ackData, - lazy { Factory.createAck(from, ackData, ttl) }, - conversationId, - null, - null, - null, - null, - ttl, - labels, - status + type = type, + from = from, + to = to, + encoding = encoding.code, + message = message(encoding, subject, body), + ackMessage = ackData(type, ackData), + conversationId = conversationId, + inventoryVector = null, + signature = null, + received = null, + initialHash = null, + ttl = ttl, + labels = labels, + status = status ) constructor(builder: Builder) : this( - builder.type, - builder.from ?: throw IllegalStateException("sender identity not set"), - builder.to, - builder.encoding, - builder.message, - builder.ackData, - lazy { + type = builder.type, + from = builder.from ?: throw IllegalStateException("sender identity not set"), + to = builder.to, + encodingCode = builder.encoding, + message = builder.message, + ackData = builder.ackData, + ackMessage = lazy { val ackMsg = builder.ackMessage if (ackMsg != null && ackMsg.isNotEmpty()) { Factory.getObjectMessage( @@ -236,15 +245,17 @@ class Plaintext private constructor( Factory.createAck(builder.from!!, builder.ackData, builder.ttl) } }, - builder.conversation ?: UUID.randomUUID(), - builder.inventoryVector, - builder.signature, - builder.received, - null, - builder.ttl, - builder.labels, - builder.status ?: Status.RECEIVED - ) + conversationId = builder.conversation ?: UUID.randomUUID(), + inventoryVector = builder.inventoryVector, + signature = builder.signature, + received = builder.received, + initialHash = null, + ttl = builder.ttl, + labels = builder.labels, + status = builder.status ?: Status.RECEIVED + ) { + id = builder.id + } fun write(out: OutputStream, includeSignature: Boolean) { Encode.varInt(from.version, out) @@ -259,7 +270,7 @@ class Plaintext private constructor( Encode.varInt(0, out) } } else { - Encode.int32(from.pubkey!!.behaviorBitfield.toLong(), out) + Encode.int32(from.pubkey!!.behaviorBitfield, out) out.write(from.pubkey!!.signingKey, 1, 64) out.write(from.pubkey!!.encryptionKey, 1, 64) if (from.version >= 3) { @@ -267,13 +278,13 @@ class Plaintext private constructor( Encode.varInt(from.pubkey!!.extraBytes, out) } } - if (type == Type.MSG) { + if (type == MSG) { out.write(to!!.ripe) } Encode.varInt(encodingCode, out) - Encode.varInt(message.size.toLong(), out) + Encode.varInt(message.size, out) out.write(message) - if (type == Type.MSG) { + if (type == MSG) { if (to?.has(Feature.DOES_ACK) ?: false) { val ack = ByteArrayOutputStream() ackMessage?.write(ack) @@ -286,8 +297,7 @@ class Plaintext private constructor( if (signature == null) { Encode.varInt(0, out) } else { - Encode.varInt(signature!!.size.toLong(), out) - out.write(signature!!) + Encode.varBytes(signature!!, out) } } } @@ -305,7 +315,7 @@ class Plaintext private constructor( Encode.varInt(0, buffer) } } else { - Encode.int32(from.pubkey!!.behaviorBitfield.toLong(), buffer) + Encode.int32(from.pubkey!!.behaviorBitfield, buffer) buffer.put(from.pubkey!!.signingKey, 1, 64) buffer.put(from.pubkey!!.encryptionKey, 1, 64) if (from.version >= 3) { @@ -313,13 +323,12 @@ class Plaintext private constructor( Encode.varInt(from.pubkey!!.extraBytes, buffer) } } - if (type == Type.MSG) { + if (type == MSG) { buffer.put(to!!.ripe) } Encode.varInt(encodingCode, buffer) - Encode.varInt(message.size.toLong(), buffer) - buffer.put(message) - if (type == Type.MSG) { + Encode.varBytes(message, buffer) + if (type == MSG) { if (to!!.has(Feature.DOES_ACK) && ackMessage != null) { Encode.varBytes(Encode.bytes(ackMessage!!), buffer) } else { @@ -331,8 +340,7 @@ class Plaintext private constructor( if (sig == null) { Encode.varInt(0, buffer) } else { - Encode.varInt(sig.size.toLong(), buffer) - buffer.put(sig) + Encode.varBytes(sig, buffer) } } } @@ -365,7 +373,7 @@ class Plaintext private constructor( val firstLine = s.nextLine() if (encodingCode == EXTENDED.code) { if (Message.TYPE == extendedData?.type) { - return (extendedData!!.content as Message?)?.subject + return (extendedData!!.content as? Message)?.subject } else { return null } @@ -551,10 +559,12 @@ class Plaintext private constructor( return this } - fun to(address: BitmessageAddress): Builder { - if (type != Type.MSG && to != null) - throw IllegalArgumentException("recipient address only allowed for msg") - to = address + fun to(address: BitmessageAddress?): Builder { + if (address != null) { + if (type != MSG && to != null) + throw IllegalArgumentException("recipient address only allowed for msg") + to = address + } return this } @@ -594,7 +604,7 @@ class Plaintext private constructor( } fun destinationRipe(ripe: ByteArray?): Builder { - if (type != Type.MSG && ripe != null) throw IllegalArgumentException("ripe only allowed for msg") + if (type != MSG && ripe != null) throw IllegalArgumentException("ripe only allowed for msg") this.destinationRipe = ripe return this } @@ -622,7 +632,6 @@ class Plaintext private constructor( } catch (e: UnsupportedEncodingException) { throw ApplicationException(e) } - return this } @@ -632,13 +641,13 @@ class Plaintext private constructor( } fun ackMessage(ack: ByteArray?): Builder { - if (type != Type.MSG && ack != null) throw IllegalArgumentException("ackMessage only allowed for msg") + if (type != MSG && ack != null) throw IllegalArgumentException("ackMessage only allowed for msg") this.ackMessage = ack return this } fun ackData(ackData: ByteArray?): Builder { - if (type != Type.MSG && ackData != null) + if (type != MSG && ackData != null) throw IllegalArgumentException("ackMessage only allowed for msg") this.ackData = ackData return this @@ -704,7 +713,7 @@ class Plaintext private constructor( if (to == null && type != Type.BROADCAST && destinationRipe != null) { to = BitmessageAddress(0, 0, destinationRipe!!) } - if (type == Type.MSG && ackMessage == null && ackData == null) { + if (type == MSG && ackMessage == null && ackData == null) { ackData = cryptography().randomBytes(Msg.ACK_LENGTH) } if (ttl <= 0) { @@ -733,10 +742,10 @@ class Plaintext private constructor( .publicEncryptionKey(Decode.bytes(`in`, 64)) .nonceTrialsPerByte(if (version >= 3) Decode.varInt(`in`) else 0) .extraBytes(if (version >= 3) Decode.varInt(`in`) else 0) - .destinationRipe(if (type == Type.MSG) Decode.bytes(`in`, 20) else null) + .destinationRipe(if (type == MSG) Decode.bytes(`in`, 20) else null) .encoding(Decode.varInt(`in`)) .message(Decode.varBytes(`in`)) - .ackMessage(if (type == Type.MSG) Decode.varBytes(`in`) else null) + .ackMessage(if (type == MSG) Decode.varBytes(`in`) else null) } } } diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/Version.kt b/core/src/main/java/ch/dissem/bitmessage/entity/Version.kt index 3a1c3f7..07d6d47 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/Version.kt +++ b/core/src/main/java/ch/dissem/bitmessage/entity/Version.kt @@ -78,7 +78,7 @@ class Version constructor( override val command: MessagePayload.Command = MessagePayload.Command.VERSION override fun write(out: OutputStream) { - Encode.int32(version.toLong(), out) + Encode.int32(version, out) Encode.int64(services, out) Encode.int64(timestamp, out) addrRecv.write(out, true) @@ -89,7 +89,7 @@ class Version constructor( } override fun write(buffer: ByteBuffer) { - Encode.int32(version.toLong(), buffer) + Encode.int32(version, buffer) Encode.int64(services, buffer) Encode.int64(timestamp, buffer) addrRecv.write(buffer, true) diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.kt b/core/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.kt index 15f8bef..d6df388 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.kt +++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.kt @@ -114,7 +114,7 @@ class CryptoBox : Streamable { private fun writeWithoutMAC(out: OutputStream) { out.write(initializationVector) - Encode.int16(curveType.toLong(), out) + Encode.int16(curveType, out) writeCoordinateComponent(out, Points.getX(R)) writeCoordinateComponent(out, Points.getY(R)) out.write(encrypted) @@ -123,14 +123,14 @@ class CryptoBox : Streamable { private fun writeCoordinateComponent(out: OutputStream, x: ByteArray) { val offset = Bytes.numberOfLeadingZeros(x) val length = x.size - offset - Encode.int16(length.toLong(), out) + Encode.int16(length, out) out.write(x, offset, length) } private fun writeCoordinateComponent(buffer: ByteBuffer, x: ByteArray) { val offset = Bytes.numberOfLeadingZeros(x) val length = x.size - offset - Encode.int16(length.toLong(), buffer) + Encode.int16(length, buffer) buffer.put(x, offset, length) } @@ -141,7 +141,7 @@ class CryptoBox : Streamable { override fun write(buffer: ByteBuffer) { buffer.put(initializationVector) - Encode.int16(curveType.toLong(), buffer) + Encode.int16(curveType, buffer) writeCoordinateComponent(buffer, Points.getX(R)) writeCoordinateComponent(buffer, Points.getY(R)) buffer.put(encrypted) diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V2Pubkey.kt b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V2Pubkey.kt index f599153..28acf6f 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V2Pubkey.kt +++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V2Pubkey.kt @@ -31,13 +31,13 @@ open class V2Pubkey constructor(version: Long, override val stream: Long, overri override val encryptionKey: ByteArray = if (encryptionKey.size == 64) add0x04(encryptionKey) else encryptionKey override fun write(out: OutputStream) { - Encode.int32(behaviorBitfield.toLong(), out) + Encode.int32(behaviorBitfield, out) out.write(signingKey, 1, 64) out.write(encryptionKey, 1, 64) } override fun write(buffer: ByteBuffer) { - Encode.int32(behaviorBitfield.toLong(), buffer) + Encode.int32(behaviorBitfield, buffer) buffer.put(signingKey, 1, 64) buffer.put(encryptionKey, 1, 64) } diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/Label.kt b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/Label.kt index 378aa35..7b221df 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/Label.kt +++ b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/Label.kt @@ -19,11 +19,14 @@ package ch.dissem.bitmessage.entity.valueobject import java.io.Serializable import java.util.* -data class Label(private val label: String, val type: Label.Type, - /** - * RGBA representation for the color. - */ - var color: Int) : Serializable { +data class Label( + private val label: String, + val type: Label.Type?, + /** + * RGBA representation for the color. + */ + var color: Int +) : Serializable { var id: Any? = null diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/NetworkAddress.kt b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/NetworkAddress.kt index e332a04..01be7f6 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/NetworkAddress.kt +++ b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/NetworkAddress.kt @@ -30,7 +30,7 @@ import java.util.* /** * A node's address. It's written in IPv6 format. */ -data class NetworkAddress constructor( +data class NetworkAddress( var time: Long, /** @@ -47,25 +47,25 @@ data class NetworkAddress constructor( * IPv6 address. IPv4 addresses are written into the message as a 16 byte IPv4-mapped IPv6 address * (12 bytes 00 00 00 00 00 00 00 00 00 00 FF FF, followed by the 4 bytes of the IPv4 address). */ - val iPv6: ByteArray, + val IPv6: ByteArray, val port: Int ) : Streamable { fun provides(service: Version.Service?): Boolean = service?.isEnabled(services) ?: false fun toInetAddress(): InetAddress { - return InetAddress.getByAddress(iPv6) + return InetAddress.getByAddress(IPv6) } override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is NetworkAddress) return false - return port == other.port && Arrays.equals(iPv6, other.iPv6) + return port == other.port && Arrays.equals(IPv6, other.IPv6) } override fun hashCode(): Int { - var result = Arrays.hashCode(iPv6) + var result = Arrays.hashCode(IPv6) result = 31 * result + port return result } @@ -84,8 +84,8 @@ data class NetworkAddress constructor( Encode.int32(stream, out) } Encode.int64(services, out) - out.write(iPv6) - Encode.int16(port.toLong(), out) + out.write(IPv6) + Encode.int16(port, out) } override fun write(buffer: ByteBuffer) { @@ -98,8 +98,8 @@ data class NetworkAddress constructor( Encode.int32(stream, buffer) } Encode.int64(services, buffer) - buffer.put(iPv6) - Encode.int16(port.toLong(), buffer) + buffer.put(IPv6) + Encode.int16(port, buffer) } class Builder { diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/PrivateKey.kt b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/PrivateKey.kt index 1cfabda..cfc618a 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/PrivateKey.kt +++ b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/PrivateKey.kt @@ -121,12 +121,10 @@ class PrivateKey : Streamable { Encode.varInt(pubkey.stream, out) val baos = ByteArrayOutputStream() pubkey.writeUnencrypted(baos) - Encode.varInt(baos.size().toLong(), out) + Encode.varInt(baos.size(), out) out.write(baos.toByteArray()) - Encode.varInt(privateSigningKey.size.toLong(), out) - out.write(privateSigningKey) - Encode.varInt(privateEncryptionKey.size.toLong(), out) - out.write(privateEncryptionKey) + Encode.varBytes(privateSigningKey, out) + Encode.varBytes(privateEncryptionKey, out) } diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/AbstractCryptography.kt b/core/src/main/java/ch/dissem/bitmessage/ports/AbstractCryptography.kt index 56b3522..e30f943 100644 --- a/core/src/main/java/ch/dissem/bitmessage/ports/AbstractCryptography.kt +++ b/core/src/main/java/ch/dissem/bitmessage/ports/AbstractCryptography.kt @@ -37,7 +37,7 @@ import javax.crypto.spec.SecretKeySpec * Implements everything that isn't directly dependent on either Spongy- or Bouncycastle. */ abstract class AbstractCryptography protected constructor(@JvmField protected val provider: Provider) : Cryptography { - private val context by InternalContext + private val context by InternalContext.lateinit @JvmField protected val ALGORITHM_ECDSA = "ECDSA" @JvmField protected val ALGORITHM_ECDSA_SHA1 = "SHA1withECDSA" @@ -87,21 +87,21 @@ abstract class AbstractCryptography protected constructor(@JvmField protected va return result } - override fun doProofOfWork(`object`: ObjectMessage, nonceTrialsPerByte: Long, + override fun doProofOfWork(objectMessage: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long, callback: ProofOfWorkEngine.Callback) { - val initialHash = getInitialHash(`object`) + val initialHash = getInitialHash(objectMessage) - val target = getProofOfWorkTarget(`object`, + val target = getProofOfWorkTarget(objectMessage, max(nonceTrialsPerByte, NETWORK_NONCE_TRIALS_PER_BYTE), max(extraBytes, NETWORK_EXTRA_BYTES)) context.proofOfWorkEngine.calculateNonce(initialHash, target, callback) } @Throws(InsufficientProofOfWorkException::class) - override fun checkProofOfWork(`object`: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long) { - val target = getProofOfWorkTarget(`object`, nonceTrialsPerByte, extraBytes) - val value = doubleSha512(`object`.nonce ?: throw ApplicationException("Object without nonce"), getInitialHash(`object`)) + override fun checkProofOfWork(objectMessage: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long) { + val target = getProofOfWorkTarget(objectMessage, nonceTrialsPerByte, extraBytes) + val value = doubleSha512(objectMessage.nonce ?: throw ApplicationException("Object without nonce"), getInitialHash(objectMessage)) if (Bytes.lt(target, value, 8)) { throw InsufficientProofOfWorkException(target, value) } @@ -130,18 +130,18 @@ abstract class AbstractCryptography protected constructor(@JvmField protected va return false } - override fun getInitialHash(`object`: ObjectMessage): ByteArray { - return sha512(`object`.payloadBytesWithoutNonce) + override fun getInitialHash(objectMessage: ObjectMessage): ByteArray { + return sha512(objectMessage.payloadBytesWithoutNonce) } - override fun getProofOfWorkTarget(`object`: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long): ByteArray { + override fun getProofOfWorkTarget(objectMessage: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long): ByteArray { @Suppress("NAME_SHADOWING") val nonceTrialsPerByte = if (nonceTrialsPerByte == 0L) NETWORK_NONCE_TRIALS_PER_BYTE else nonceTrialsPerByte @Suppress("NAME_SHADOWING") val extraBytes = if (extraBytes == 0L) NETWORK_EXTRA_BYTES else extraBytes - val TTL = BigInteger.valueOf(`object`.expiresTime - UnixTime.now) - val powLength = BigInteger.valueOf(`object`.payloadBytesWithoutNonce.size + extraBytes) + val TTL = BigInteger.valueOf(objectMessage.expiresTime - UnixTime.now) + val powLength = BigInteger.valueOf(objectMessage.payloadBytesWithoutNonce.size + extraBytes) val denominator = BigInteger.valueOf(nonceTrialsPerByte) .multiply( powLength.add( diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/AbstractMessageRepository.kt b/core/src/main/java/ch/dissem/bitmessage/ports/AbstractMessageRepository.kt index b7329eb..c951ad2 100644 --- a/core/src/main/java/ch/dissem/bitmessage/ports/AbstractMessageRepository.kt +++ b/core/src/main/java/ch/dissem/bitmessage/ports/AbstractMessageRepository.kt @@ -28,19 +28,19 @@ import ch.dissem.bitmessage.utils.UnixTime import java.util.* abstract class AbstractMessageRepository : MessageRepository { - protected var ctx by InternalContext + protected var ctx by InternalContext.lateinit protected fun saveContactIfNecessary(contact: BitmessageAddress?) { contact?.let { - val savedAddress = ctx.addressRepository.getAddress(contact.address) + val savedAddress = ctx.addressRepository.getAddress(it.address) if (savedAddress == null) { - ctx.addressRepository.save(contact) - } else if (savedAddress.pubkey == null && contact.pubkey != null) { - savedAddress.pubkey = contact.pubkey - ctx.addressRepository.save(savedAddress) - } - if (savedAddress != null) { - contact.alias = savedAddress.alias + ctx.addressRepository.save(it) + } else { + if (savedAddress.pubkey == null && it.pubkey != null) { + savedAddress.pubkey = it.pubkey + ctx.addressRepository.save(savedAddress) + } + it.alias = savedAddress.alias } } } diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/Cryptography.kt b/core/src/main/java/ch/dissem/bitmessage/ports/Cryptography.kt index 22176bb..32f7dee 100644 --- a/core/src/main/java/ch/dissem/bitmessage/ports/Cryptography.kt +++ b/core/src/main/java/ch/dissem/bitmessage/ports/Cryptography.kt @@ -145,7 +145,7 @@ interface Cryptography { * Calculates the proof of work. This might take a long time, depending on the hardware, message size and time to * live. - * @param object to do the proof of work for + * @param objectMessage to do the proof of work for * * * @param nonceTrialsPerByte difficulty * * @@ -153,11 +153,11 @@ interface Cryptography { * * * @param callback to handle nonce once it's calculated */ - fun doProofOfWork(`object`: ObjectMessage, nonceTrialsPerByte: Long, + fun doProofOfWork(objectMessage: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long, callback: ProofOfWorkEngine.Callback) /** - * @param object to be checked + * @param objectMessage to be checked * * * @param nonceTrialsPerByte difficulty * * @@ -166,11 +166,11 @@ interface Cryptography { * @throws InsufficientProofOfWorkException if proof of work doesn't check out (makes it more difficult to send small messages) */ @Throws(InsufficientProofOfWorkException::class) - fun checkProofOfWork(`object`: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long) + fun checkProofOfWork(objectMessage: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long) - fun getInitialHash(`object`: ObjectMessage): ByteArray + fun getInitialHash(objectMessage: ObjectMessage): ByteArray - fun getProofOfWorkTarget(`object`: ObjectMessage, nonceTrialsPerByte: Long = NETWORK_NONCE_TRIALS_PER_BYTE, extraBytes: Long = NETWORK_EXTRA_BYTES): ByteArray + fun getProofOfWorkTarget(objectMessage: ObjectMessage, nonceTrialsPerByte: Long = NETWORK_NONCE_TRIALS_PER_BYTE, extraBytes: Long = NETWORK_EXTRA_BYTES): ByteArray /** * Calculates the MAC for a message (data) diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/DefaultLabeler.kt b/core/src/main/java/ch/dissem/bitmessage/ports/DefaultLabeler.kt index 43a4f6e..4df9048 100644 --- a/core/src/main/java/ch/dissem/bitmessage/ports/DefaultLabeler.kt +++ b/core/src/main/java/ch/dissem/bitmessage/ports/DefaultLabeler.kt @@ -23,7 +23,7 @@ import ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST import ch.dissem.bitmessage.entity.valueobject.Label open class DefaultLabeler : Labeler { - private var ctx by InternalContext + private var ctx by InternalContext.lateinit override fun setLabels(msg: Plaintext) { msg.status = RECEIVED diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/Inventory.kt b/core/src/main/java/ch/dissem/bitmessage/ports/Inventory.kt index 2087354..9c4885d 100644 --- a/core/src/main/java/ch/dissem/bitmessage/ports/Inventory.kt +++ b/core/src/main/java/ch/dissem/bitmessage/ports/Inventory.kt @@ -43,9 +43,9 @@ interface Inventory { */ fun getObjects(stream: Long, version: Long, vararg types: ObjectType): List<ObjectMessage> - fun storeObject(`object`: ObjectMessage) + fun storeObject(objectMessage: ObjectMessage) - operator fun contains(`object`: ObjectMessage): Boolean + operator fun contains(objectMessage: ObjectMessage): Boolean /** * Deletes all objects that expired 5 minutes ago or earlier diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/MessageRepository.kt b/core/src/main/java/ch/dissem/bitmessage/ports/MessageRepository.kt index c1fd59a..51e0d02 100644 --- a/core/src/main/java/ch/dissem/bitmessage/ports/MessageRepository.kt +++ b/core/src/main/java/ch/dissem/bitmessage/ports/MessageRepository.kt @@ -28,7 +28,7 @@ interface MessageRepository { fun getLabels(vararg types: Label.Type): List<Label> - fun countUnread(label: Label): Int + fun countUnread(label: Label?): Int fun getMessage(id: Any): Plaintext @@ -43,7 +43,7 @@ interface MessageRepository { * * * @return a distinct list of all conversations that have at least one message with the given label. */ - fun findConversations(label: Label): List<UUID> + fun findConversations(label: Label?): List<UUID> fun findMessages(label: Label?): List<Plaintext> diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.kt b/core/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.kt index 72fee7b..de7f24c 100644 --- a/core/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.kt +++ b/core/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.kt @@ -81,6 +81,6 @@ interface NetworkHandler { interface MessageListener { @Throws(IOException::class) - fun receive(`object`: ObjectMessage) + fun receive(objectMessage: ObjectMessage) } } diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/ProofOfWorkRepository.kt b/core/src/main/java/ch/dissem/bitmessage/ports/ProofOfWorkRepository.kt index fbf5f60..01693eb 100644 --- a/core/src/main/java/ch/dissem/bitmessage/ports/ProofOfWorkRepository.kt +++ b/core/src/main/java/ch/dissem/bitmessage/ports/ProofOfWorkRepository.kt @@ -29,14 +29,14 @@ interface ProofOfWorkRepository { fun getItems(): List<ByteArray> - fun putObject(`object`: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long) + fun putObject(objectMessage: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long) fun putObject(item: Item) fun removeObject(initialHash: ByteArray) data class Item @JvmOverloads constructor( - val `object`: ObjectMessage, + val objectMessage: ObjectMessage, val nonceTrialsPerByte: Long, val extraBytes: Long, // Needed for ACK POW calculation diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/ConversationService.kt b/core/src/main/java/ch/dissem/bitmessage/utils/ConversationService.kt index 392a1cf..f06a6ed 100644 --- a/core/src/main/java/ch/dissem/bitmessage/utils/ConversationService.kt +++ b/core/src/main/java/ch/dissem/bitmessage/utils/ConversationService.kt @@ -19,6 +19,7 @@ package ch.dissem.bitmessage.utils import ch.dissem.bitmessage.entity.Plaintext import ch.dissem.bitmessage.entity.valueobject.InventoryVector import ch.dissem.bitmessage.ports.MessageRepository +import org.slf4j.LoggerFactory import java.util.* import java.util.Collections import java.util.regex.Pattern diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/Encode.kt b/core/src/main/java/ch/dissem/bitmessage/utils/Encode.kt index 1904c66..1b96935 100644 --- a/core/src/main/java/ch/dissem/bitmessage/utils/Encode.kt +++ b/core/src/main/java/ch/dissem/bitmessage/utils/Encode.kt @@ -27,19 +27,20 @@ import java.nio.ByteBuffer */ object Encode { @JvmStatic fun varIntList(values: LongArray, stream: OutputStream) { - varInt(values.size.toLong(), stream) + varInt(values.size, stream) for (value in values) { varInt(value, stream) } } @JvmStatic fun varIntList(values: LongArray, buffer: ByteBuffer) { - varInt(values.size.toLong(), buffer) + varInt(values.size, buffer) for (value in values) { varInt(value, buffer) } } + @JvmStatic fun varInt(value: Int, buffer: ByteBuffer) = varInt(value.toLong(), buffer) @JvmStatic fun varInt(value: Long, buffer: ByteBuffer) { if (value < 0) { // This is due to the fact that Java doesn't really support unsigned values. @@ -62,6 +63,7 @@ object Encode { } } + @JvmStatic fun varInt(value: Int) = varInt(value.toLong()) @JvmStatic fun varInt(value: Long): ByteArray { val buffer = ByteBuffer.allocate(9) varInt(value, buffer) @@ -69,6 +71,7 @@ object Encode { return Bytes.truncate(buffer.array(), buffer.limit()) } + @JvmStatic @JvmOverloads fun varInt(value: Int, stream: OutputStream, counter: AccessCounter? = null) = varInt(value.toLong(), stream, counter) @JvmStatic @JvmOverloads fun varInt(value: Long, stream: OutputStream, counter: AccessCounter? = null) { val buffer = ByteBuffer.allocate(9) varInt(value, buffer) @@ -77,27 +80,34 @@ object Encode { AccessCounter.inc(counter, buffer.limit()) } - @JvmStatic @JvmOverloads fun int8(value: Long, stream: OutputStream, counter: AccessCounter? = null) { - stream.write(value.toInt()) + @JvmStatic @JvmOverloads fun int8(value: Long, stream: OutputStream, counter: AccessCounter? = null) = int8(value.toInt(), stream, counter) + @JvmStatic @JvmOverloads fun int8(value: Int, stream: OutputStream, counter: AccessCounter? = null) { + stream.write(value) AccessCounter.inc(counter) } - @JvmStatic @JvmOverloads fun int16(value: Long, stream: OutputStream, counter: AccessCounter? = null) { - stream.write(ByteBuffer.allocate(2).putShort(value.toShort()).array()) + @JvmStatic @JvmOverloads fun int16(value: Long, stream: OutputStream, counter: AccessCounter? = null) = int16(value.toShort(), stream, counter) + @JvmStatic @JvmOverloads fun int16(value: Int, stream: OutputStream, counter: AccessCounter? = null) = int16(value.toShort(), stream, counter) + @JvmStatic @JvmOverloads fun int16(value: Short, stream: OutputStream, counter: AccessCounter? = null) { + stream.write(ByteBuffer.allocate(2).putShort(value).array()) AccessCounter.inc(counter, 2) } - @JvmStatic fun int16(value: Long, buffer: ByteBuffer) { - buffer.putShort(value.toShort()) + @JvmStatic fun int16(value: Long, buffer: ByteBuffer) = int16(value.toShort(), buffer) + @JvmStatic fun int16(value: Int, buffer: ByteBuffer) = int16(value.toShort(), buffer) + @JvmStatic fun int16(value: Short, buffer: ByteBuffer) { + buffer.putShort(value) } - @JvmStatic @JvmOverloads fun int32(value: Long, stream: OutputStream, counter: AccessCounter? = null) { - stream.write(ByteBuffer.allocate(4).putInt(value.toInt()).array()) + @JvmStatic @JvmOverloads fun int32(value: Long, stream: OutputStream, counter: AccessCounter? = null) = int32(value.toInt(), stream, counter) + @JvmStatic @JvmOverloads fun int32(value: Int, stream: OutputStream, counter: AccessCounter? = null) { + stream.write(ByteBuffer.allocate(4).putInt(value).array()) AccessCounter.inc(counter, 4) } - @JvmStatic fun int32(value: Long, buffer: ByteBuffer) { - buffer.putInt(value.toInt()) + @JvmStatic fun int32(value: Long, buffer: ByteBuffer) = int32(value.toInt(), buffer) + @JvmStatic fun int32(value: Int, buffer: ByteBuffer) { + buffer.putInt(value) } @JvmStatic @JvmOverloads fun int64(value: Long, stream: OutputStream, counter: AccessCounter? = null) { diff --git a/core/src/test/java/ch/dissem/bitmessage/BitmessageContextTest.kt b/core/src/test/java/ch/dissem/bitmessage/BitmessageContextTest.kt index b51cb7d..4814ac7 100644 --- a/core/src/test/java/ch/dissem/bitmessage/BitmessageContextTest.kt +++ b/core/src/test/java/ch/dissem/bitmessage/BitmessageContextTest.kt @@ -28,8 +28,8 @@ import ch.dissem.bitmessage.ports.DefaultLabeler import ch.dissem.bitmessage.ports.ProofOfWorkEngine import ch.dissem.bitmessage.ports.ProofOfWorkRepository import ch.dissem.bitmessage.testutils.TestInventory -import ch.dissem.bitmessage.utils.Singleton import ch.dissem.bitmessage.utils.Singleton.cryptography +import ch.dissem.bitmessage.utils.Strings.hex import ch.dissem.bitmessage.utils.TTL import ch.dissem.bitmessage.utils.TestUtils import ch.dissem.bitmessage.utils.UnixTime.MINUTE @@ -40,12 +40,12 @@ import org.junit.Assert.* import org.junit.Before import org.junit.Test import java.util.* +import kotlin.concurrent.thread /** * @author Christian Basler */ class BitmessageContextTest { - private lateinit var ctx: BitmessageContext private var listener: BitmessageContext.Listener = mock() private val inventory = spy(TestInventory()) private val testPowRepo = spy(object : ProofOfWorkRepository { @@ -54,7 +54,7 @@ class BitmessageContextTest { internal var removed = 0 override fun getItem(initialHash: ByteArray): ProofOfWorkRepository.Item { - return items[InventoryVector(initialHash)]!! + return items[InventoryVector(initialHash)] ?: throw IllegalArgumentException("${hex(initialHash)} not found in $items") } override fun getItems(): List<ByteArray> { @@ -66,12 +66,12 @@ class BitmessageContextTest { } override fun putObject(item: ProofOfWorkRepository.Item) { - items.put(InventoryVector(cryptography().getInitialHash(item.`object`)), item) + items.put(InventoryVector(cryptography().getInitialHash(item.objectMessage)), item) added++ } - override fun putObject(`object`: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long) { - items.put(InventoryVector(cryptography().getInitialHash(`object`)), ProofOfWorkRepository.Item(`object`, nonceTrialsPerByte, extraBytes)) + override fun putObject(objectMessage: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long) { + items.put(InventoryVector(cryptography().getInitialHash(objectMessage)), ProofOfWorkRepository.Item(objectMessage, nonceTrialsPerByte, extraBytes)) added++ } @@ -87,39 +87,42 @@ class BitmessageContextTest { removed = 0 } }) + private val testPowEngine = spy(object : ProofOfWorkEngine { + override fun calculateNonce(initialHash: ByteArray, target: ByteArray, callback: ProofOfWorkEngine.Callback) { + thread { callback.onNonceCalculated(initialHash, ByteArray(8)) } + } + }) + private var ctx = BitmessageContext.Builder() + .addressRepo(mock()) + .cryptography(BouncyCryptography()) + .inventory(inventory) + .listener(listener) + .messageRepo(mock()) + .networkHandler(mock()) + .nodeRegistry(mock()) + .labeler(spy(DefaultLabeler())) + .powRepo(testPowRepo) + .proofOfWorkEngine(testPowEngine) + .build() + + init { + TTL.msg = 2 * MINUTE + } @Before fun setUp() { - Singleton.initialize(BouncyCryptography()) - ctx = BitmessageContext.Builder() - .addressRepo(mock()) - .cryptography(cryptography()) - .inventory(inventory) - .listener(listener) - .messageRepo(mock()) - .networkHandler(mock()) - .nodeRegistry(mock()) - .labeler(spy(DefaultLabeler())) - .powRepo(testPowRepo) - .proofOfWorkEngine(spy(object : ProofOfWorkEngine { - override fun calculateNonce(initialHash: ByteArray, target: ByteArray, callback: ProofOfWorkEngine.Callback) { - callback.onNonceCalculated(initialHash, ByteArray(8)) - } - })) - .build() - TTL.msg = 2 * MINUTE testPowRepo.reset() } @Test fun `ensure contact is saved and pubkey requested`() { val contact = BitmessageAddress("BM-opWQhvk9xtMFvQA2Kvetedpk8LkbraWHT") - whenever(ctx.addresses.getAddress(contact.address)).thenReturn(contact) + doReturn(contact).whenever(ctx.addresses).getAddress(eq(contact.address)) ctx.addContact(contact) - verify(ctx.addresses, timeout(1000).atLeastOnce()).save(contact) - verify(ctx.internals.proofOfWorkEngine, timeout(1000)).calculateNonce(any(), any(), any()) + verify(ctx.addresses, timeout(1000).atLeastOnce()).save(eq(contact)) + verify(testPowEngine, timeout(1000)).calculateNonce(any(), any(), any()) } @Test @@ -132,7 +135,7 @@ class BitmessageContextTest { ctx.addContact(contact) verify(ctx.addresses, times(1)).save(contact) - verify(ctx.internals.proofOfWorkEngine, never()).calculateNonce(any(), any(), any()) + verify(testPowEngine, never()).calculateNonce(any(), any(), any()) } @Test @@ -155,7 +158,7 @@ class BitmessageContextTest { ctx.addContact(contact) verify(ctx.addresses, atLeastOnce()).save(contact) - verify(ctx.internals.proofOfWorkEngine, never()).calculateNonce(any(), any(), any()) + verify(testPowEngine, never()).calculateNonce(any(), any(), any()) } @Test @@ -179,7 +182,7 @@ class BitmessageContextTest { ctx.addContact(contact) verify(ctx.addresses, atLeastOnce()).save(any()) - verify(ctx.internals.proofOfWorkEngine, never()).calculateNonce(any(), any(), any()) + verify(testPowEngine, never()).calculateNonce(any(), any(), any()) } @Test @@ -209,9 +212,9 @@ class BitmessageContextTest { fun `ensure message is sent`() { ctx.send(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"), TestUtils.loadContact(), "Subject", "Message") + verify(ctx.internals.proofOfWorkRepository, timeout(10000)).putObject( + argThat { payload.type == ObjectType.MSG }, eq(1000L), eq(1000L)) assertEquals(2, testPowRepo.added) - verify(ctx.internals.proofOfWorkRepository, timeout(10000).atLeastOnce()) - .putObject(argThat { payload.type == ObjectType.MSG }, eq(1000L), eq(1000L)) verify(ctx.messages, timeout(10000).atLeastOnce()).save(argThat { type == Type.MSG }) } @@ -236,10 +239,9 @@ class BitmessageContextTest { fun `ensure broadcast is sent`() { ctx.broadcast(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"), "Subject", "Message") - verify(ctx.internals.proofOfWorkRepository, timeout(10000).atLeastOnce()) + verify(ctx.internals.proofOfWorkRepository, timeout(1000).atLeastOnce()) .putObject(argThat { payload.type == ObjectType.BROADCAST }, eq(1000L), eq(1000L)) - verify(ctx.internals.proofOfWorkEngine) - .calculateNonce(any(), any(), any()) + verify(testPowEngine).calculateNonce(any(), any(), any()) verify(ctx.messages, timeout(10000).atLeastOnce()).save(argThat { type == Type.BROADCAST }) } @@ -265,7 +267,7 @@ class BitmessageContextTest { fun `ensure deterministic addresses are created`() { val expected_size = 8 val addresses = ctx.createDeterministicAddresses("test", expected_size, 4, 1, false) - assertEquals(expected_size.toLong(), addresses.size.toLong()) + assertEquals(expected_size, addresses.size) val expected = HashSet<String>(expected_size) expected.add("BM-2cWFkyuXXFw6d393RGnin2RpSXj8wxtt6F") expected.add("BM-2cX8TF9vuQZEWvT7UrEeq1HN9dgiSUPLEN") @@ -285,7 +287,7 @@ class BitmessageContextTest { fun `ensure short deterministic addresses are created`() { val expected_size = 1 val addresses = ctx.createDeterministicAddresses("test", expected_size, 4, 1, true) - assertEquals(expected_size.toLong(), addresses.size.toLong()) + assertEquals(expected_size, addresses.size) val expected = HashSet<String>(expected_size) expected.add("BM-NBGyBAEp6VnBkFWKpzUSgxuTqVdWPi78") for (a in addresses) { diff --git a/core/src/test/java/ch/dissem/bitmessage/DefaultMessageListenerTest.kt b/core/src/test/java/ch/dissem/bitmessage/DefaultMessageListenerTest.kt index dc30105..a96430f 100644 --- a/core/src/test/java/ch/dissem/bitmessage/DefaultMessageListenerTest.kt +++ b/core/src/test/java/ch/dissem/bitmessage/DefaultMessageListenerTest.kt @@ -16,6 +16,7 @@ package ch.dissem.bitmessage +import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography import ch.dissem.bitmessage.entity.BitmessageAddress import ch.dissem.bitmessage.entity.ObjectMessage import ch.dissem.bitmessage.entity.Plaintext @@ -26,9 +27,12 @@ import ch.dissem.bitmessage.entity.payload.GetPubkey import ch.dissem.bitmessage.entity.payload.Msg import ch.dissem.bitmessage.entity.payload.ObjectType import ch.dissem.bitmessage.factory.Factory +import ch.dissem.bitmessage.ports.ProofOfWorkRepository import ch.dissem.bitmessage.utils.Singleton import ch.dissem.bitmessage.utils.TestBase import ch.dissem.bitmessage.utils.TestUtils +import ch.dissem.bitmessage.utils.UnixTime.MINUTE +import ch.dissem.bitmessage.utils.UnixTime.now import com.nhaarman.mockito_kotlin.* import org.junit.Before import org.junit.Test @@ -40,7 +44,7 @@ class DefaultMessageListenerTest : TestBase() { private lateinit var listener: DefaultMessageListener private val ctx = TestUtils.mockedInternalContext( - cryptography = Singleton.cryptography() + cryptography = BouncyCryptography() ) @Before @@ -52,10 +56,13 @@ class DefaultMessageListenerTest : TestBase() { fun `ensure pubkey is sent on request`() { val identity = TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8") whenever(ctx.addressRepository.findIdentity(any())).thenReturn(identity) - listener.receive(ObjectMessage.Builder() - .stream(2) - .payload(GetPubkey(BitmessageAddress("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"))) - .build()) + val objectMessage = ObjectMessage( + stream = 2, + payload = GetPubkey(BitmessageAddress("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8")), + expiresTime = now + MINUTE + ) + whenever(ctx.proofOfWorkRepository.getItem(any())).thenReturn(ProofOfWorkRepository.Item(objectMessage, 1000L, 1000L)) + listener.receive(objectMessage) verify(ctx.proofOfWorkRepository).putObject(argThat { type == ObjectType.PUBKEY.number }, any(), any()) } @@ -73,6 +80,7 @@ class DefaultMessageListenerTest : TestBase() { .build() objectMessage.sign(identity.privateKey!!) objectMessage.encrypt(Singleton.cryptography().createPublicKey(identity.publicDecryptionKey)) + whenever(ctx.proofOfWorkRepository.getItem(any())).thenReturn(ProofOfWorkRepository.Item(objectMessage, 1000L, 1000L)) listener.receive(objectMessage) verify(ctx.addressRepository).save(eq(contact)) diff --git a/core/src/test/java/ch/dissem/bitmessage/ProofOfWorkServiceTest.kt b/core/src/test/java/ch/dissem/bitmessage/ProofOfWorkServiceTest.kt index 23a31a9..c244ce0 100644 --- a/core/src/test/java/ch/dissem/bitmessage/ProofOfWorkServiceTest.kt +++ b/core/src/test/java/ch/dissem/bitmessage/ProofOfWorkServiceTest.kt @@ -77,24 +77,24 @@ class ProofOfWorkServiceTest { val identity = TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8") val address = TestUtils.loadContact() val plaintext = Plaintext.Builder(MSG).from(identity).to(address).message("", "").build() - val `object` = ObjectMessage( + val objectMessage = ObjectMessage( expiresTime = 0, stream = 1, payload = Msg(plaintext) ) - `object`.sign(identity.privateKey!!) - `object`.encrypt(address.pubkey!!) + objectMessage.sign(identity.privateKey!!) + objectMessage.encrypt(address.pubkey!!) val initialHash = ByteArray(64) val nonce = byteArrayOf(1, 2, 3, 4, 5, 6, 7, 8) - whenever(ctx.proofOfWorkRepository.getItem(initialHash)).thenReturn(ProofOfWorkRepository.Item(`object`, 1001, 1002)) + whenever(ctx.proofOfWorkRepository.getItem(initialHash)).thenReturn(ProofOfWorkRepository.Item(objectMessage, 1001, 1002)) whenever(ctx.messageRepository.getMessage(initialHash)).thenReturn(plaintext) ctx.proofOfWorkService.onNonceCalculated(initialHash, nonce) verify(ctx.proofOfWorkRepository).removeObject(eq(initialHash)) - verify(ctx.inventory).storeObject(eq(`object`)) - verify(ctx.networkHandler).offer(eq(`object`.inventoryVector)) - assertThat(plaintext.inventoryVector, equalTo(`object`.inventoryVector)) + verify(ctx.inventory).storeObject(eq(objectMessage)) + verify(ctx.networkHandler).offer(eq(objectMessage.inventoryVector)) + assertThat(plaintext.inventoryVector, equalTo(objectMessage.inventoryVector)) } } diff --git a/core/src/test/java/ch/dissem/bitmessage/SignatureTest.kt b/core/src/test/java/ch/dissem/bitmessage/SignatureTest.kt index 5fe9f7a..a3b965c 100644 --- a/core/src/test/java/ch/dissem/bitmessage/SignatureTest.kt +++ b/core/src/test/java/ch/dissem/bitmessage/SignatureTest.kt @@ -29,9 +29,9 @@ import org.junit.Test class SignatureTest : TestBase() { @Test fun `ensure validation works`() { - val `object` = TestUtils.loadObjectMessage(3, "V3Pubkey.payload") - val pubkey = `object`.payload as Pubkey - assertTrue(`object`.isSignatureValid(pubkey)) + val objectMessage = TestUtils.loadObjectMessage(3, "V3Pubkey.payload") + val pubkey = objectMessage.payload as Pubkey + assertTrue(objectMessage.isSignatureValid(pubkey)) } @Test @@ -52,11 +52,11 @@ class SignatureTest : TestBase() { fun `ensure message is properly signed`() { val identity = TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8") - val `object` = TestUtils.loadObjectMessage(3, "V1Msg.payload") - val msg = `object`.payload as Msg + val objectMessage = TestUtils.loadObjectMessage(3, "V1Msg.payload") + val msg = objectMessage.payload as Msg msg.decrypt(identity.privateKey!!.privateEncryptionKey) assertNotNull(msg.plaintext) assertEquals(TestUtils.loadContact().pubkey, msg.plaintext!!.from.pubkey) - assertTrue(`object`.isSignatureValid(msg.plaintext!!.from.pubkey!!)) + assertTrue(objectMessage.isSignatureValid(msg.plaintext!!.from.pubkey!!)) } } diff --git a/core/src/test/java/ch/dissem/bitmessage/entity/BitmessageAddressTest.kt b/core/src/test/java/ch/dissem/bitmessage/entity/BitmessageAddressTest.kt index 1177ea2..95dcee1 100644 --- a/core/src/test/java/ch/dissem/bitmessage/entity/BitmessageAddressTest.kt +++ b/core/src/test/java/ch/dissem/bitmessage/entity/BitmessageAddressTest.kt @@ -31,9 +31,9 @@ import java.util.* class BitmessageAddressTest : TestBase() { @Test fun `ensure feature flag is calculated correctly`() { - Assert.assertEquals(1, Pubkey.Feature.bitfield(DOES_ACK).toLong()) - assertEquals(2, Pubkey.Feature.bitfield(INCLUDE_DESTINATION).toLong()) - assertEquals(3, Pubkey.Feature.bitfield(DOES_ACK, INCLUDE_DESTINATION).toLong()) + Assert.assertEquals(1, Pubkey.Feature.bitfield(DOES_ACK)) + assertEquals(2, Pubkey.Feature.bitfield(INCLUDE_DESTINATION)) + assertEquals(3, Pubkey.Feature.bitfield(DOES_ACK, INCLUDE_DESTINATION)) } @Test @@ -84,9 +84,9 @@ class BitmessageAddressTest : TestBase() { val address = BitmessageAddress("BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ") Assert.assertArrayEquals(Bytes.fromHex("007402be6e76c3cb87caa946d0c003a3d4d8e1d5"), address.ripe) - val `object` = TestUtils.loadObjectMessage(3, "V3Pubkey.payload") - val pubkey = `object`.payload as Pubkey - assertTrue(`object`.isSignatureValid(pubkey)) + val objectMessage = TestUtils.loadObjectMessage(3, "V3Pubkey.payload") + val pubkey = objectMessage.payload as Pubkey + assertTrue(objectMessage.isSignatureValid(pubkey)) try { address.pubkey = pubkey } catch (e: Exception) { @@ -100,10 +100,10 @@ class BitmessageAddressTest : TestBase() { @Test fun `ensure V4Pubkey can be imported`() { val address = BitmessageAddress("BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h") - val `object` = TestUtils.loadObjectMessage(4, "V4Pubkey.payload") - `object`.decrypt(address.publicDecryptionKey) - val pubkey = `object`.payload as V4Pubkey - assertTrue(`object`.isSignatureValid(pubkey)) + val objectMessage = TestUtils.loadObjectMessage(4, "V4Pubkey.payload") + objectMessage.decrypt(address.publicDecryptionKey) + val pubkey = objectMessage.payload as V4Pubkey + assertTrue(objectMessage.isSignatureValid(pubkey)) try { address.pubkey = pubkey } catch (e: Exception) { diff --git a/core/src/test/java/ch/dissem/bitmessage/entity/SerializationTest.kt b/core/src/test/java/ch/dissem/bitmessage/entity/SerializationTest.kt index a9fbfea..22b2d4f 100644 --- a/core/src/test/java/ch/dissem/bitmessage/entity/SerializationTest.kt +++ b/core/src/test/java/ch/dissem/bitmessage/entity/SerializationTest.kt @@ -170,12 +170,12 @@ class SerializationTest : TestBase() { private fun doTest(resourceName: String, version: Int, expectedPayloadType: Class<*>) { val data = TestUtils.getBytes(resourceName) val `in` = ByteArrayInputStream(data) - val `object` = Factory.getObjectMessage(version, `in`, data.size) + val objectMessage = Factory.getObjectMessage(version, `in`, data.size) val out = ByteArrayOutputStream() - assertNotNull(`object`) - `object`!!.write(out) + assertNotNull(objectMessage) + objectMessage!!.write(out) assertArrayEquals(data, out.toByteArray()) - assertEquals(expectedPayloadType.canonicalName, `object`.payload.javaClass.canonicalName) + assertEquals(expectedPayloadType.canonicalName, objectMessage.payload.javaClass.canonicalName) } @Test diff --git a/core/src/test/java/ch/dissem/bitmessage/testutils/TestInventory.kt b/core/src/test/java/ch/dissem/bitmessage/testutils/TestInventory.kt index f147151..fcdae32 100644 --- a/core/src/test/java/ch/dissem/bitmessage/testutils/TestInventory.kt +++ b/core/src/test/java/ch/dissem/bitmessage/testutils/TestInventory.kt @@ -42,12 +42,12 @@ class TestInventory : Inventory { return ArrayList(inventory.values) } - override fun storeObject(`object`: ObjectMessage) { - inventory.put(`object`.inventoryVector, `object`) + override fun storeObject(objectMessage: ObjectMessage) { + inventory.put(objectMessage.inventoryVector, objectMessage) } - override fun contains(`object`: ObjectMessage): Boolean { - return inventory.containsKey(`object`.inventoryVector) + override fun contains(objectMessage: ObjectMessage): Boolean { + return inventory.containsKey(objectMessage.inventoryVector) } override fun cleanup() { diff --git a/core/src/test/java/ch/dissem/bitmessage/utils/CollectionsTest.kt b/core/src/test/java/ch/dissem/bitmessage/utils/CollectionsTest.kt index dcfad1f..bfb3fe5 100644 --- a/core/src/test/java/ch/dissem/bitmessage/utils/CollectionsTest.kt +++ b/core/src/test/java/ch/dissem/bitmessage/utils/CollectionsTest.kt @@ -27,6 +27,6 @@ class CollectionsTest { fun `ensure select random returns maximum possible items`() { val list = LinkedList<Int>() list += 0..9 - assertEquals(9, Collections.selectRandom(9, list).size.toLong()) + assertEquals(9, Collections.selectRandom(9, list).size) } } diff --git a/core/src/test/java/ch/dissem/bitmessage/utils/ConversationServiceTest.kt b/core/src/test/java/ch/dissem/bitmessage/utils/ConversationServiceTest.kt index 97e1a2d..cb26e5f 100644 --- a/core/src/test/java/ch/dissem/bitmessage/utils/ConversationServiceTest.kt +++ b/core/src/test/java/ch/dissem/bitmessage/utils/ConversationServiceTest.kt @@ -16,7 +16,6 @@ package ch.dissem.bitmessage.utils -import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography import ch.dissem.bitmessage.entity.BitmessageAddress import ch.dissem.bitmessage.entity.Plaintext import ch.dissem.bitmessage.entity.Plaintext.Type.MSG @@ -30,19 +29,13 @@ import org.junit.Assert.assertThat import org.junit.Test import java.util.* -class ConversationServiceTest { +class ConversationServiceTest : TestBase() { private val alice = BitmessageAddress("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8") private val bob = BitmessageAddress("BM-2cTtkBnb4BUYDndTKun6D9PjtueP2h1bQj") private val messageRepository = mock<MessageRepository>() private val conversationService = spy(ConversationService(messageRepository)) - companion object { - init { - Singleton.initialize(BouncyCryptography()) - } - } - @Test fun `ensure conversation is sorted properly`() { MockitoKotlin.registerInstanceCreator { UUID.randomUUID() } diff --git a/core/src/test/java/ch/dissem/bitmessage/utils/EncodeTest.kt b/core/src/test/java/ch/dissem/bitmessage/utils/EncodeTest.kt index 9027162..0fa4361 100644 --- a/core/src/test/java/ch/dissem/bitmessage/utils/EncodeTest.kt +++ b/core/src/test/java/ch/dissem/bitmessage/utils/EncodeTest.kt @@ -111,11 +111,11 @@ class EncodeTest { fun checkBytes(stream: ByteArrayOutputStream, vararg bytes: Int) { - assertEquals(bytes.size.toLong(), stream.size().toLong()) + assertEquals(bytes.size, stream.size()) val streamBytes = stream.toByteArray() for (i in bytes.indices) { - assertEquals(bytes[i].toByte().toLong(), streamBytes[i].toLong()) + assertEquals(bytes[i].toByte(), streamBytes[i]) } } } diff --git a/core/src/test/java/ch/dissem/bitmessage/utils/TestUtils.kt b/core/src/test/java/ch/dissem/bitmessage/utils/TestUtils.kt index 397f926..6f72ba3 100644 --- a/core/src/test/java/ch/dissem/bitmessage/utils/TestUtils.kt +++ b/core/src/test/java/ch/dissem/bitmessage/utils/TestUtils.kt @@ -43,7 +43,7 @@ object TestUtils { @JvmStatic fun int16(number: Int): ByteArray { val out = ByteArrayOutputStream() - Encode.int16(number.toLong(), out) + Encode.int16(number, out) return out.toByteArray() } @@ -85,9 +85,9 @@ object TestUtils { @Throws(DecryptionFailedException::class) @JvmStatic fun loadContact(): BitmessageAddress { val address = BitmessageAddress("BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h") - val `object` = TestUtils.loadObjectMessage(3, "V4Pubkey.payload") - `object`.decrypt(address.publicDecryptionKey) - address.pubkey = `object`.payload as V4Pubkey + val objectMessage = TestUtils.loadObjectMessage(3, "V4Pubkey.payload") + objectMessage.decrypt(address.publicDecryptionKey) + address.pubkey = objectMessage.payload as V4Pubkey return address } diff --git a/cryptography-bc/src/main/java/ch/dissem/bitmessage/cryptography/bc/BouncyCryptography.kt b/cryptography-bc/src/main/java/ch/dissem/bitmessage/cryptography/bc/BouncyCryptography.kt index 89e38e2..5ce7027 100644 --- a/cryptography-bc/src/main/java/ch/dissem/bitmessage/cryptography/bc/BouncyCryptography.kt +++ b/cryptography-bc/src/main/java/ch/dissem/bitmessage/cryptography/bc/BouncyCryptography.kt @@ -40,8 +40,7 @@ import java.util.* * As Spongycastle can't be used on the Oracle JVM, and Bouncycastle doesn't work properly on Android (thanks, Google), * this is the Bouncycastle implementation. */ -object BouncyCryptography : AbstractCryptography(BouncyCastleProvider()) { - private val EC_CURVE_PARAMETERS = CustomNamedCurves.getByName("secp256k1") +class BouncyCryptography : AbstractCryptography(BouncyCastleProvider()) { override fun crypt(encrypt: Boolean, data: ByteArray, key_e: ByteArray, initializationVector: ByteArray): ByteArray { val cipher = PaddedBufferedBlockCipher( @@ -119,4 +118,8 @@ object BouncyCryptography : AbstractCryptography(BouncyCastleProvider()) { BigInteger(1, y) ).getEncoded(false) } + + companion object { + private val EC_CURVE_PARAMETERS = CustomNamedCurves.getByName("secp256k1") + } } diff --git a/cryptography-bc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.kt b/cryptography-bc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.kt index c51c296..1897512 100644 --- a/cryptography-bc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.kt +++ b/cryptography-bc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.kt @@ -163,7 +163,7 @@ class CryptographyTest { val TEST_RIPEMD160 = DatatypeConverter.parseHexBinary("" + "cd566972b5e50104011a92b59fa8e0b1234851ae") - private val crypto = BouncyCryptography + private val crypto = BouncyCryptography() init { Singleton.initialize(crypto) diff --git a/cryptography-sc/src/main/java/ch/dissem/bitmessage/cryptography/sc/SpongyCryptography.kt b/cryptography-sc/src/main/java/ch/dissem/bitmessage/cryptography/sc/SpongyCryptography.kt index f2e2320..1465fb3 100644 --- a/cryptography-sc/src/main/java/ch/dissem/bitmessage/cryptography/sc/SpongyCryptography.kt +++ b/cryptography-sc/src/main/java/ch/dissem/bitmessage/cryptography/sc/SpongyCryptography.kt @@ -40,8 +40,7 @@ import java.util.* * As Spongycastle can't be used on the Oracle JVM, and Bouncycastle doesn't work properly on Android (thanks, Google), * this is the Spongycastle implementation. */ -object SpongyCryptography : AbstractCryptography(BouncyCastleProvider()) { - private val EC_CURVE_PARAMETERS = CustomNamedCurves.getByName("secp256k1") +class SpongyCryptography : AbstractCryptography(BouncyCastleProvider()) { override fun crypt(encrypt: Boolean, data: ByteArray, key_e: ByteArray, initializationVector: ByteArray): ByteArray { val cipher = PaddedBufferedBlockCipher( @@ -119,4 +118,8 @@ object SpongyCryptography : AbstractCryptography(BouncyCastleProvider()) { BigInteger(1, y) ).getEncoded(false) } + + companion object { + private val EC_CURVE_PARAMETERS = CustomNamedCurves.getByName("secp256k1") + } } diff --git a/cryptography-sc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.kt b/cryptography-sc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.kt index c8eafe0..15a9313 100644 --- a/cryptography-sc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.kt +++ b/cryptography-sc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.kt @@ -162,7 +162,7 @@ class CryptographyTest { val TEST_RIPEMD160 = DatatypeConverter.parseHexBinary("" + "cd566972b5e50104011a92b59fa8e0b1234851ae") - private val crypto = SpongyCryptography + private val crypto = SpongyCryptography() init { Singleton.initialize(crypto) diff --git a/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java b/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java index 2c9ca8e..1da2910 100644 --- a/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java +++ b/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java @@ -61,7 +61,7 @@ public class Main { .messageRepo(new JdbcMessageRepository(jdbcConfig)) .powRepo(new JdbcProofOfWorkRepository(jdbcConfig)) .networkHandler(new NioNetworkHandler()) - .cryptography(BouncyCryptography.INSTANCE) + .cryptography(new BouncyCryptography()) .port(48444); if (options.localPort != null) { ctxBuilder.nodeRegistry(new NodeRegistry() { diff --git a/demo/src/test/java/ch/dissem/bitmessage/SystemTest.java b/demo/src/test/java/ch/dissem/bitmessage/SystemTest.java index 293741f..224cc1a 100644 --- a/demo/src/test/java/ch/dissem/bitmessage/SystemTest.java +++ b/demo/src/test/java/ch/dissem/bitmessage/SystemTest.java @@ -19,7 +19,6 @@ package ch.dissem.bitmessage; import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography; import ch.dissem.bitmessage.entity.BitmessageAddress; import ch.dissem.bitmessage.entity.Plaintext; -import ch.dissem.bitmessage.networking.DefaultNetworkHandler; import ch.dissem.bitmessage.networking.nio.NioNetworkHandler; import ch.dissem.bitmessage.ports.DefaultLabeler; import ch.dissem.bitmessage.ports.Labeler; @@ -29,14 +28,10 @@ import ch.dissem.bitmessage.utils.TTL; import org.junit.After; import org.junit.Before; import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; import org.mockito.Mockito; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.Arrays; -import java.util.List; import java.util.UUID; import java.util.concurrent.TimeUnit; @@ -49,74 +44,62 @@ import static org.mockito.ArgumentMatchers.any; /** * @author Christian Basler */ -@RunWith(Parameterized.class) public class SystemTest { private static int port = 6000; - private final NetworkHandler aliceNetworkHandler; - private final NetworkHandler bobNetworkHandler; private BitmessageContext alice; - private TestListener aliceListener = new TestListener(); - private Labeler aliceLabeler = Mockito.spy(new DebugLabeler("Alice")); private BitmessageAddress aliceIdentity; + private Labeler aliceLabeler; private BitmessageContext bob; - private TestListener bobListener = new TestListener(); + private TestListener bobListener; private BitmessageAddress bobIdentity; - public SystemTest(NetworkHandler peer, NetworkHandler node) { - this.aliceNetworkHandler = peer; - this.bobNetworkHandler = node; - } - - @Parameterized.Parameters - @SuppressWarnings("deprecation") - public static List<Object[]> parameters() { - return Arrays.asList(new Object[][]{ - {new NioNetworkHandler(), new DefaultNetworkHandler()}, - {new NioNetworkHandler(), new NioNetworkHandler()} - }); - } - @Before public void setUp() { - int alicePort = port++; - int bobPort = port++; TTL.msg(5 * MINUTE); TTL.getpubkey(5 * MINUTE); TTL.pubkey(5 * MINUTE); - JdbcConfig aliceDB = new JdbcConfig("jdbc:h2:mem:alice;DB_CLOSE_DELAY=-1", "sa", ""); - alice = new BitmessageContext.Builder() - .addressRepo(new JdbcAddressRepository(aliceDB)) - .inventory(new JdbcInventory(aliceDB)) - .messageRepo(new JdbcMessageRepository(aliceDB)) - .powRepo(new JdbcProofOfWorkRepository(aliceDB)) - .port(alicePort) - .nodeRegistry(new TestNodeRegistry(bobPort)) - .networkHandler(aliceNetworkHandler) - .cryptography(BouncyCryptography.INSTANCE) - .listener(aliceListener) - .labeler(aliceLabeler) - .build(); - alice.startup(); - aliceIdentity = alice.createIdentity(false, DOES_ACK); - - JdbcConfig bobDB = new JdbcConfig("jdbc:h2:mem:bob;DB_CLOSE_DELAY=-1", "sa", ""); - bob = new BitmessageContext.Builder() - .addressRepo(new JdbcAddressRepository(bobDB)) - .inventory(new JdbcInventory(bobDB)) - .messageRepo(new JdbcMessageRepository(bobDB)) - .powRepo(new JdbcProofOfWorkRepository(bobDB)) - .port(bobPort) - .nodeRegistry(new TestNodeRegistry(alicePort)) - .networkHandler(bobNetworkHandler) - .cryptography(BouncyCryptography.INSTANCE) - .listener(bobListener) - .labeler(new DebugLabeler("Bob")) - .build(); - bob.startup(); - bobIdentity = bob.createIdentity(false, DOES_ACK); + int alicePort = port++; + int bobPort = port++; + { + JdbcConfig aliceDB = new JdbcConfig("jdbc:h2:mem:alice;DB_CLOSE_DELAY=-1", "sa", ""); + aliceLabeler = Mockito.spy(new DebugLabeler("Alice")); + TestListener aliceListener = new TestListener(); + alice = new BitmessageContext.Builder() + .addressRepo(new JdbcAddressRepository(aliceDB)) + .inventory(new JdbcInventory(aliceDB)) + .messageRepo(new JdbcMessageRepository(aliceDB)) + .powRepo(new JdbcProofOfWorkRepository(aliceDB)) + .port(alicePort) + .nodeRegistry(new TestNodeRegistry(bobPort)) + .networkHandler(new NioNetworkHandler()) + .cryptography(new BouncyCryptography()) + .listener(aliceListener) + .labeler(aliceLabeler) + .build(); + alice.startup(); + aliceIdentity = alice.createIdentity(false, DOES_ACK); + } + { + JdbcConfig bobDB = new JdbcConfig("jdbc:h2:mem:bob;DB_CLOSE_DELAY=-1", "sa", ""); + bobListener = new TestListener(); + bob = new BitmessageContext.Builder() + .addressRepo(new JdbcAddressRepository(bobDB)) + .inventory(new JdbcInventory(bobDB)) + .messageRepo(new JdbcMessageRepository(bobDB)) + .powRepo(new JdbcProofOfWorkRepository(bobDB)) + .port(bobPort) + .nodeRegistry(new TestNodeRegistry(alicePort)) + .networkHandler(new NioNetworkHandler()) + .cryptography(new BouncyCryptography()) + .listener(bobListener) + .labeler(new DebugLabeler("Bob")) + .build(); + bob.startup(); + bobIdentity = bob.createIdentity(false, DOES_ACK); + } ((DebugLabeler) alice.labeler()).init(aliceIdentity, bobIdentity); ((DebugLabeler) bob.labeler()).init(aliceIdentity, bobIdentity); } diff --git a/extensions/src/main/kotlin/ch/dissem/bitmessage/extensions/CryptoCustomMessage.kt b/extensions/src/main/kotlin/ch/dissem/bitmessage/extensions/CryptoCustomMessage.kt index 1c76bdb..d5a2349 100644 --- a/extensions/src/main/kotlin/ch/dissem/bitmessage/extensions/CryptoCustomMessage.kt +++ b/extensions/src/main/kotlin/ch/dissem/bitmessage/extensions/CryptoCustomMessage.kt @@ -65,7 +65,7 @@ class CryptoCustomMessage<T : Streamable> : CustomMessage { Encode.varInt(identity.version, out) Encode.varInt(identity.stream, out) - Encode.int32(privateKey.pubkey.behaviorBitfield.toLong(), out) + Encode.int32(privateKey.pubkey.behaviorBitfield, out) out.write(privateKey.pubkey.signingKey, 1, 64) out.write(privateKey.pubkey.encryptionKey, 1, 64) if (identity.version >= 3) { @@ -114,7 +114,7 @@ class CryptoCustomMessage<T : Streamable> : CustomMessage { container?.write(out) ?: throw IllegalStateException("not encrypted yet") } - interface Reader<T> { + interface Reader<out T> { fun read(sender: BitmessageAddress, `in`: InputStream): T } diff --git a/networking/src/main/java/ch/dissem/bitmessage/networking/nio/NioNetworkHandler.java b/networking/src/main/java/ch/dissem/bitmessage/networking/nio/NioNetworkHandler.java index 7f6dc5d..40e79d0 100644 --- a/networking/src/main/java/ch/dissem/bitmessage/networking/nio/NioNetworkHandler.java +++ b/networking/src/main/java/ch/dissem/bitmessage/networking/nio/NioNetworkHandler.java @@ -25,10 +25,10 @@ import ch.dissem.bitmessage.entity.valueobject.NetworkAddress; import ch.dissem.bitmessage.exception.ApplicationException; import ch.dissem.bitmessage.exception.NodeException; import ch.dissem.bitmessage.factory.V3MessageReader; -import ch.dissem.bitmessage.networking.AbstractConnection; import ch.dissem.bitmessage.ports.NetworkHandler; import ch.dissem.bitmessage.utils.DebugUtils; import ch.dissem.bitmessage.utils.Property; +import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -75,8 +75,9 @@ public class NioNetworkHandler implements NetworkHandler, InternalContext.Contex private Thread starter; + @NotNull @Override - public Future<Void> synchronize(final InetAddress server, final int port, final long timeoutInSeconds) { + public Future<Void> synchronize(@NotNull final InetAddress server, final int port, final long timeoutInSeconds) { return threadPool.submit(new Callable<Void>() { @Override public Void call() throws Exception { @@ -97,8 +98,9 @@ public class NioNetworkHandler implements NetworkHandler, InternalContext.Contex }); } + @NotNull @Override - public CustomMessage send(InetAddress server, int port, CustomMessage request) { + public CustomMessage send(@NotNull InetAddress server, int port, @NotNull CustomMessage request) { try (SocketChannel channel = SocketChannel.open(new InetSocketAddress(server, port))) { channel.configureBlocking(true); ByteBuffer headerBuffer = ByteBuffer.allocate(HEADER_SIZE); @@ -129,7 +131,7 @@ public class NioNetworkHandler implements NetworkHandler, InternalContext.Contex if (networkMessage != null && networkMessage.getPayload() instanceof CustomMessage) { return (CustomMessage) networkMessage.getPayload(); } else { - if (networkMessage == null || networkMessage.getPayload() == null) { + if (networkMessage == null) { throw new NodeException("Empty response from node " + server); } else { throw new NodeException("Unexpected response from node " + server + ": " @@ -391,7 +393,7 @@ public class NioNetworkHandler implements NetworkHandler, InternalContext.Contex } @Override - public void offer(InventoryVector iv) { + public void offer(@NotNull InventoryVector iv) { List<ConnectionInfo> target = new LinkedList<>(); for (ConnectionInfo connection : connections.keySet()) { if (connection.getState() == ACTIVE && !connection.knowsOf(iv)) { @@ -405,7 +407,7 @@ public class NioNetworkHandler implements NetworkHandler, InternalContext.Contex } @Override - public void request(Collection<InventoryVector> inventoryVectors) { + public void request(@NotNull Collection<InventoryVector> inventoryVectors) { if (!isRunning()) { requestedObjects.clear(); return; @@ -468,6 +470,7 @@ public class NioNetworkHandler implements NetworkHandler, InternalContext.Contex } } + @NotNull @Override public Property getNetworkStatus() { TreeSet<Long> streams = new TreeSet<>(); @@ -520,7 +523,7 @@ public class NioNetworkHandler implements NetworkHandler, InternalContext.Contex } @Override - public void setContext(InternalContext context) { + public void setContext(@NotNull InternalContext context) { this.ctx = context; } } diff --git a/networking/src/test/java/ch/dissem/bitmessage/networking/NetworkHandlerTest.java b/networking/src/test/java/ch/dissem/bitmessage/networking/NetworkHandlerTest.java index a4e7d54..f079ede 100644 --- a/networking/src/test/java/ch/dissem/bitmessage/networking/NetworkHandlerTest.java +++ b/networking/src/test/java/ch/dissem/bitmessage/networking/NetworkHandlerTest.java @@ -98,7 +98,7 @@ public class NetworkHandlerTest { .port(peerAddress.getPort()) .nodeRegistry(new TestNodeRegistry()) .networkHandler(peerNetworkHandler) - .cryptography(BouncyCryptography.INSTANCE) + .cryptography(new BouncyCryptography()) .listener(mock(BitmessageContext.Listener.class)) .customCommandHandler(new CustomCommandHandler() { @Override @@ -133,7 +133,7 @@ public class NetworkHandlerTest { .port(6002) .nodeRegistry(new TestNodeRegistry(peerAddress)) .networkHandler(nodeNetworkHandler) - .cryptography(BouncyCryptography.INSTANCE) + .cryptography(new BouncyCryptography()) .listener(mock(BitmessageContext.Listener.class)) .build(); } diff --git a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcAddressRepository.java b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcAddressRepository.java index b296bc3..2f1e107 100644 --- a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcAddressRepository.java +++ b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcAddressRepository.java @@ -21,6 +21,7 @@ import ch.dissem.bitmessage.entity.payload.Pubkey; import ch.dissem.bitmessage.entity.payload.V3Pubkey; import ch.dissem.bitmessage.entity.payload.V4Pubkey; import ch.dissem.bitmessage.entity.valueobject.PrivateKey; +import ch.dissem.bitmessage.exception.ApplicationException; import ch.dissem.bitmessage.factory.Factory; import ch.dissem.bitmessage.ports.AddressRepository; import org.slf4j.Logger; @@ -137,16 +138,14 @@ public class JdbcAddressRepository extends JdbcHelper implements AddressReposito try ( Connection connection = config.getConnection(); Statement stmt = connection.createStatement(); - ResultSet rs = stmt.executeQuery("SELECT COUNT(*) FROM Address " + + ResultSet rs = stmt.executeQuery("SELECT '1' FROM Address " + "WHERE address='" + address.getAddress() + "'") ) { - if (rs.next()) { - return rs.getInt(1) > 0; - } + return rs.next(); } catch (SQLException e) { LOG.error(e.getMessage(), e); + throw new ApplicationException(e); } - return false; } @Override diff --git a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcMessageRepository.java b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcMessageRepository.java deleted file mode 100644 index e9fffb6..0000000 --- a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcMessageRepository.java +++ /dev/null @@ -1,344 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.repository; - -import ch.dissem.bitmessage.entity.Plaintext; -import ch.dissem.bitmessage.entity.valueobject.InventoryVector; -import ch.dissem.bitmessage.entity.valueobject.Label; -import ch.dissem.bitmessage.exception.ApplicationException; -import ch.dissem.bitmessage.ports.AbstractMessageRepository; -import ch.dissem.bitmessage.ports.MessageRepository; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.io.InputStream; -import java.sql.*; -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; -import java.util.UUID; - -import static ch.dissem.bitmessage.repository.JdbcHelper.writeBlob; - -public class JdbcMessageRepository extends AbstractMessageRepository implements MessageRepository { - private static final Logger LOG = LoggerFactory.getLogger(JdbcMessageRepository.class); - - private final JdbcConfig config; - - public JdbcMessageRepository(JdbcConfig config) { - this.config = config; - } - - @Override - protected List<Label> findLabels(String where) { - try ( - Connection connection = config.getConnection() - ) { - return findLabels(connection, where); - } catch (SQLException e) { - LOG.error(e.getMessage(), e); - } - return new ArrayList<>(); - } - - private Label getLabel(ResultSet rs) throws SQLException { - String typeName = rs.getString("type"); - Label.Type type = null; - if (typeName != null) { - type = Label.Type.valueOf(typeName); - } - Label label = new Label(rs.getString("label"), type, rs.getInt("color")); - label.setId(rs.getLong("id")); - - return label; - } - - @Override - public int countUnread(Label label) { - String where; - if (label == null) { - where = ""; - } else { - where = "id IN (SELECT message_id FROM Message_Label WHERE label_id=" + label.getId() + ") AND "; - } - where += "id IN (SELECT message_id FROM Message_Label WHERE label_id IN (" + - "SELECT id FROM Label WHERE type = '" + Label.Type.UNREAD.name() + "'))"; - - try ( - Connection connection = config.getConnection(); - Statement stmt = connection.createStatement(); - ResultSet rs = stmt.executeQuery("SELECT count(*) FROM Message WHERE " + where) - ) { - if (rs.next()) { - return rs.getInt(1); - } - } catch (SQLException e) { - LOG.error(e.getMessage(), e); - } - return 0; - } - - @Override - protected List<Plaintext> find(String where) { - List<Plaintext> result = new LinkedList<>(); - try ( - Connection connection = config.getConnection(); - Statement stmt = connection.createStatement(); - ResultSet rs = stmt.executeQuery( - "SELECT id, iv, type, sender, recipient, data, ack_data, sent, received, initial_hash, status, ttl, retries, next_try, conversation " + - "FROM Message WHERE " + where) - ) { - while (rs.next()) { - byte[] iv = rs.getBytes("iv"); - InputStream data = rs.getBinaryStream("data"); - Plaintext.Type type = Plaintext.Type.valueOf(rs.getString("type")); - Plaintext.Builder builder = Plaintext.readWithoutSignature(type, data); - long id = rs.getLong("id"); - builder.id(id); - builder.IV(InventoryVector.fromHash(iv)); - builder.from(getCtx().getAddressRepository().getAddress(rs.getString("sender"))); - builder.to(getCtx().getAddressRepository().getAddress(rs.getString("recipient"))); - builder.ackData(rs.getBytes("ack_data")); - builder.sent(rs.getObject("sent", Long.class)); - builder.received(rs.getObject("received", Long.class)); - builder.status(Plaintext.Status.valueOf(rs.getString("status"))); - builder.ttl(rs.getLong("ttl")); - builder.retries(rs.getInt("retries")); - builder.nextTry(rs.getObject("next_try", Long.class)); - builder.conversation(rs.getObject("conversation", UUID.class)); - builder.labels(findLabels(connection, - "id IN (SELECT label_id FROM Message_Label WHERE message_id=" + id + ") ORDER BY ord")); - Plaintext message = builder.build(); - message.setInitialHash(rs.getBytes("initial_hash")); - result.add(message); - } - } catch (SQLException e) { - LOG.error(e.getMessage(), e); - } - return result; - } - - private List<Label> findLabels(Connection connection, String where) { - List<Label> result = new ArrayList<>(); - try ( - Statement stmt = connection.createStatement(); - ResultSet rs = stmt.executeQuery("SELECT id, label, type, color FROM Label WHERE " + where) - ) { - while (rs.next()) { - result.add(getLabel(rs)); - } - } catch (SQLException e) { - LOG.error(e.getMessage(), e); - } - return result; - } - - @Override - public void save(Plaintext message) { - saveContactIfNecessary(message.getFrom()); - saveContactIfNecessary(message.getTo()); - - try (Connection connection = config.getConnection()) { - try { - connection.setAutoCommit(false); - save(connection, message); - updateParents(connection, message); - updateLabels(connection, message); - connection.commit(); - } catch (IOException | SQLException e) { - connection.rollback(); - throw e; - } - } catch (IOException | SQLException e) { - throw new ApplicationException(e); - } - } - - private void save(Connection connection, Plaintext message) throws IOException, SQLException { - if (message.getId() == null) { - insert(connection, message); - } else { - update(connection, message); - } - } - - private void updateLabels(Connection connection, Plaintext message) throws SQLException { - // remove existing labels - try (Statement stmt = connection.createStatement()) { - stmt.executeUpdate("DELETE FROM Message_Label WHERE message_id=" + message.getId()); - } - // save new labels - try (PreparedStatement ps = connection.prepareStatement("INSERT INTO Message_Label VALUES (" + - message.getId() + ", ?)")) { - for (Label label : message.getLabels()) { - ps.setLong(1, (Long) label.getId()); - ps.executeUpdate(); - } - } - } - - private void updateParents(Connection connection, Plaintext message) throws SQLException { - if (message.getInventoryVector() == null || message.getParents().isEmpty()) { - // There are no parents to save yet (they are saved in the extended data, that's enough for now) - return; - } - // remove existing parents - try (PreparedStatement ps = connection.prepareStatement("DELETE FROM Message_Parent WHERE child=?")) { - ps.setBytes(1, message.getInitialHash()); - ps.executeUpdate(); - } - byte[] childIV = message.getInventoryVector().getHash(); - // save new parents - int order = 0; - try (PreparedStatement ps = connection.prepareStatement("INSERT INTO Message_Parent VALUES (?, ?, ?, ?)")) { - for (InventoryVector parentIV : message.getParents()) { - Plaintext parent = getMessage(parentIV); - mergeConversations(connection, parent.getConversationId(), message.getConversationId()); - order++; - ps.setBytes(1, parentIV.getHash()); - ps.setBytes(2, childIV); - ps.setInt(3, order); // FIXME: this might not be necessary - ps.setObject(4, message.getConversationId()); - ps.executeUpdate(); - } - } - } - - private void insert(Connection connection, Plaintext message) throws SQLException, IOException { - try (PreparedStatement ps = connection.prepareStatement( - "INSERT INTO Message (iv, type, sender, recipient, data, ack_data, sent, received, " + - "status, initial_hash, ttl, retries, next_try, conversation) " + - "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", - Statement.RETURN_GENERATED_KEYS) - ) { - ps.setBytes(1, message.getInventoryVector() == null ? null : message.getInventoryVector().getHash()); - ps.setString(2, message.getType().name()); - ps.setString(3, message.getFrom().getAddress()); - ps.setString(4, message.getTo() == null ? null : message.getTo().getAddress()); - writeBlob(ps, 5, message); - ps.setBytes(6, message.getAckData()); - ps.setObject(7, message.getSent()); - ps.setObject(8, message.getReceived()); - ps.setString(9, message.getStatus() == null ? null : message.getStatus().name()); - ps.setBytes(10, message.getInitialHash()); - ps.setLong(11, message.getTTL()); - ps.setInt(12, message.getRetries()); - ps.setObject(13, message.getNextTry()); - ps.setObject(14, message.getConversationId()); - - ps.executeUpdate(); - // get generated id - try (ResultSet rs = ps.getGeneratedKeys()) { - rs.next(); - message.setId(rs.getLong(1)); - } - } - } - - private void update(Connection connection, Plaintext message) throws SQLException, IOException { - try (PreparedStatement ps = connection.prepareStatement( - "UPDATE Message SET iv=?, type=?, sender=?, recipient=?, data=?, ack_data=?, sent=?, received=?, " + - "status=?, initial_hash=?, ttl=?, retries=?, next_try=? " + - "WHERE id=?")) { - ps.setBytes(1, message.getInventoryVector() == null ? null : message.getInventoryVector().getHash()); - ps.setString(2, message.getType().name()); - ps.setString(3, message.getFrom().getAddress()); - ps.setString(4, message.getTo() == null ? null : message.getTo().getAddress()); - writeBlob(ps, 5, message); - ps.setBytes(6, message.getAckData()); - ps.setObject(7, message.getSent()); - ps.setObject(8, message.getReceived()); - ps.setString(9, message.getStatus() == null ? null : message.getStatus().name()); - ps.setBytes(10, message.getInitialHash()); - ps.setLong(11, message.getTTL()); - ps.setInt(12, message.getRetries()); - ps.setObject(13, message.getNextTry()); - ps.setLong(14, (Long) message.getId()); - ps.executeUpdate(); - } - } - - @Override - public void remove(Plaintext message) { - try (Connection connection = config.getConnection()) { - connection.setAutoCommit(false); - try (Statement stmt = connection.createStatement()) { - stmt.executeUpdate("DELETE FROM Message_Label WHERE message_id = " + message.getId()); - stmt.executeUpdate("DELETE FROM Message WHERE id = " + message.getId()); - connection.commit(); - } catch (SQLException e) { - try { - connection.rollback(); - } catch (SQLException e1) { - LOG.debug(e1.getMessage(), e); - } - LOG.error(e.getMessage(), e); - } - } catch (SQLException e) { - LOG.error(e.getMessage(), e); - } - } - - @Override - public List<UUID> findConversations(Label label) { - String where; - if (label == null) { - where = "id NOT IN (SELECT message_id FROM Message_Label)"; - } else { - where = "id IN (SELECT message_id FROM Message_Label WHERE label_id=" + label.getId() + ")"; - } - List<UUID> result = new LinkedList<>(); - try ( - Connection connection = config.getConnection(); - Statement stmt = connection.createStatement(); - ResultSet rs = stmt.executeQuery( - "SELECT DISTINCT conversation FROM Message WHERE " + where) - ) { - while (rs.next()) { - result.add((UUID) rs.getObject(1)); - } - } catch (SQLException e) { - LOG.error(e.getMessage(), e); - } - return result; - } - - /** - * Replaces every occurrence of the source conversation ID with the target ID - * - * @param source ID of the conversation to be merged - * @param target ID of the merge target - */ - private void mergeConversations(Connection connection, UUID source, UUID target) { - try ( - PreparedStatement ps1 = connection.prepareStatement( - "UPDATE Message SET conversation=? WHERE conversation=?"); - PreparedStatement ps2 = connection.prepareStatement( - "UPDATE Message_Parent SET conversation=? WHERE conversation=?") - ) { - ps1.setObject(1, target); - ps1.setObject(2, source); - ps1.executeUpdate(); - ps2.setObject(1, target); - ps2.setObject(2, source); - ps2.executeUpdate(); - } catch (SQLException e) { - LOG.error(e.getMessage(), e); - } - } -} diff --git a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcMessageRepository.kt b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcMessageRepository.kt new file mode 100644 index 0000000..2f4f851 --- /dev/null +++ b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcMessageRepository.kt @@ -0,0 +1,344 @@ +/* + * Copyright 2015 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.repository + +import ch.dissem.bitmessage.entity.Plaintext +import ch.dissem.bitmessage.entity.valueobject.InventoryVector +import ch.dissem.bitmessage.entity.valueobject.Label +import ch.dissem.bitmessage.ports.AbstractMessageRepository +import ch.dissem.bitmessage.ports.MessageRepository +import ch.dissem.bitmessage.repository.JdbcHelper.writeBlob +import org.slf4j.LoggerFactory +import java.io.IOException +import java.sql.Connection +import java.sql.ResultSet +import java.sql.SQLException +import java.sql.Statement +import java.util.* + +class JdbcMessageRepository(private val config: JdbcConfig) : AbstractMessageRepository(), MessageRepository { + + override fun findLabels(where: String): List<Label> { + try { + config.connection.use { + connection -> + return findLabels(connection, where) + } + } catch (e: SQLException) { + LOG.error(e.message, e) + return ArrayList() + } + } + + private fun getLabel(rs: ResultSet): Label { + val typeName = rs.getString("type") + val type = if (typeName == null) { + null + } else { + Label.Type.valueOf(typeName) + } + val label = Label(rs.getString("label"), type, rs.getInt("color")) + label.id = rs.getLong("id") + + return label + } + + override fun countUnread(label: Label?): Int { + val where = if (label == null) { + "" + } else { + "id IN (SELECT message_id FROM Message_Label WHERE label_id=${label.id}) AND " + } + "id IN (SELECT message_id FROM Message_Label WHERE label_id IN (" + + "SELECT id FROM Label WHERE type = '" + Label.Type.UNREAD.name + "'))" + + try { + config.connection.use { connection -> + connection.createStatement().use { stmt -> + stmt.executeQuery("SELECT count(*) FROM Message WHERE $where").use { rs -> + if (rs.next()) { + return rs.getInt(1) + } + } + } + } + } catch (e: SQLException) { + LOG.error(e.message, e) + } + return 0 + } + + override fun find(where: String): List<Plaintext> { + val result = LinkedList<Plaintext>() + try { + config.connection.use { connection -> + connection.createStatement().use { stmt -> + stmt.executeQuery( + """SELECT id, iv, type, sender, recipient, data, ack_data, sent, received, initial_hash, status, ttl, retries, next_try, conversation + FROM Message WHERE $where""").use { rs -> + while (rs.next()) { + val iv = rs.getBytes("iv") + val data = rs.getBinaryStream("data") + val type = Plaintext.Type.valueOf(rs.getString("type")) + val builder = Plaintext.readWithoutSignature(type, data) + val id = rs.getLong("id") + builder.id(id) + builder.IV(InventoryVector.fromHash(iv)) + builder.from(ctx.addressRepository.getAddress(rs.getString("sender"))!!) + builder.to(ctx.addressRepository.getAddress(rs.getString("recipient"))) + builder.ackData(rs.getBytes("ack_data")) + builder.sent(rs.getObject("sent", Long::class.java)) + builder.received(rs.getObject("received", Long::class.java)) + builder.status(Plaintext.Status.valueOf(rs.getString("status"))) + builder.ttl(rs.getLong("ttl")) + builder.retries(rs.getInt("retries")) + builder.nextTry(rs.getObject("next_try", Long::class.java)) + builder.conversation(rs.getObject("conversation", UUID::class.java)) + builder.labels(findLabels(connection, + "id IN (SELECT label_id FROM Message_Label WHERE message_id=$id) ORDER BY ord")) + val message = builder.build() + message.initialHash = rs.getBytes("initial_hash") + result.add(message) + } + } + } + } + } catch (e: SQLException) { + LOG.error(e.message, e) + } + + return result + } + + private fun findLabels(connection: Connection, where: String): List<Label> { + val result = ArrayList<Label>() + try { + connection.createStatement().use { stmt -> + stmt.executeQuery("SELECT id, label, type, color FROM Label WHERE $where").use { rs -> + while (rs.next()) { + result.add(getLabel(rs)) + } + } + } + } catch (e: SQLException) { + LOG.error(e.message, e) + } + + return result + } + + override fun save(message: Plaintext) { + saveContactIfNecessary(message.from) + saveContactIfNecessary(message.to) + + config.connection.use { connection -> + try { + connection.autoCommit = false + save(connection, message) + updateParents(connection, message) + updateLabels(connection, message) + connection.commit() + } catch (e: Exception) { + connection.rollback() + throw e + } + } + } + + private fun save(connection: Connection, message: Plaintext) { + if (message.id == null) { + insert(connection, message) + } else { + update(connection, message) + } + } + + private fun updateLabels(connection: Connection, message: Plaintext) { + // remove existing labels + connection.createStatement().use { stmt -> stmt.executeUpdate("DELETE FROM Message_Label WHERE message_id=${message.id!!}") } + // save new labels + connection.prepareStatement("INSERT INTO Message_Label VALUES (${message.id}, ?)").use { ps -> + for (label in message.labels) { + ps.setLong(1, (label.id as Long?)!!) + ps.executeUpdate() + } + } + } + + private fun updateParents(connection: Connection, message: Plaintext) { + if (message.inventoryVector == null || message.parents.isEmpty()) { + // There are no parents to save yet (they are saved in the extended data, that's enough for now) + return + } + // remove existing parents + connection.prepareStatement("DELETE FROM Message_Parent WHERE child=?").use { ps -> + ps.setBytes(1, message.initialHash) + ps.executeUpdate() + } + val childIV = message.inventoryVector!!.hash + // save new parents + var order = 0 + connection.prepareStatement("INSERT INTO Message_Parent VALUES (?, ?, ?, ?)").use { ps -> + for (parentIV in message.parents) { + val parent = getMessage(parentIV) + mergeConversations(connection, parent!!.conversationId, message.conversationId) + order++ + ps.setBytes(1, parentIV.hash) + ps.setBytes(2, childIV) + ps.setInt(3, order) // FIXME: this might not be necessary + ps.setObject(4, message.conversationId) + ps.executeUpdate() + } + } + } + + private fun insert(connection: Connection, message: Plaintext) { + connection.prepareStatement( + "INSERT INTO Message (iv, type, sender, recipient, data, ack_data, sent, received, " + + "status, initial_hash, ttl, retries, next_try, conversation) " + + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + Statement.RETURN_GENERATED_KEYS).use { ps -> + ps.setBytes(1, if (message.inventoryVector == null) null else message.inventoryVector!!.hash) + ps.setString(2, message.type.name) + ps.setString(3, message.from.address) + ps.setString(4, if (message.to == null) null else message.to!!.address) + writeBlob(ps, 5, message) + ps.setBytes(6, message.ackData) + ps.setObject(7, message.sent) + ps.setObject(8, message.received) + ps.setString(9, message.status.name) + ps.setBytes(10, message.initialHash) + ps.setLong(11, message.ttl) + ps.setInt(12, message.retries) + ps.setObject(13, message.nextTry) + ps.setObject(14, message.conversationId) + + ps.executeUpdate() + // get generated id + ps.generatedKeys.use { rs -> + rs.next() + message.id = rs.getLong(1) + } + } + } + + @Throws(SQLException::class, IOException::class) + private fun update(connection: Connection, message: Plaintext) { + connection.prepareStatement( + "UPDATE Message SET iv=?, type=?, sender=?, recipient=?, data=?, ack_data=?, sent=?, received=?, " + + "status=?, initial_hash=?, ttl=?, retries=?, next_try=? " + + "WHERE id=?").use { ps -> + ps.setBytes(1, if (message.inventoryVector == null) null else message.inventoryVector!!.hash) + ps.setString(2, message.type.name) + ps.setString(3, message.from.address) + ps.setString(4, if (message.to == null) null else message.to!!.address) + writeBlob(ps, 5, message) + ps.setBytes(6, message.ackData) + ps.setObject(7, message.sent) + ps.setObject(8, message.received) + ps.setString(9, message.status.name) + ps.setBytes(10, message.initialHash) + ps.setLong(11, message.ttl) + ps.setInt(12, message.retries) + ps.setObject(13, message.nextTry) + ps.setLong(14, (message.id as Long?)!!) + ps.executeUpdate() + } + } + + override fun remove(message: Plaintext) { + try { + config.connection.use { connection -> + connection.autoCommit = false + try { + connection.createStatement().use { stmt -> + stmt.executeUpdate("DELETE FROM Message_Label WHERE message_id = " + message.id!!) + stmt.executeUpdate("DELETE FROM Message WHERE id = " + message.id!!) + connection.commit() + } + } catch (e: SQLException) { + try { + connection.rollback() + } catch (e1: SQLException) { + LOG.debug(e1.message, e) + } + + LOG.error(e.message, e) + } + } + } catch (e: SQLException) { + LOG.error(e.message, e) + } + + } + + override fun findConversations(label: Label?): List<UUID> { + val where: String + if (label == null) { + where = "id NOT IN (SELECT message_id FROM Message_Label)" + } else { + where = "id IN (SELECT message_id FROM Message_Label WHERE label_id=" + label.id + ")" + } + val result = LinkedList<UUID>() + try { + config.connection.use { connection -> + connection.createStatement().use { stmt -> + stmt.executeQuery( + "SELECT DISTINCT conversation FROM Message WHERE " + where).use { rs -> + while (rs.next()) { + result.add(rs.getObject(1) as UUID) + } + } + } + } + } catch (e: SQLException) { + LOG.error(e.message, e) + } + + return result + } + + /** + * Replaces every occurrence of the source conversation ID with the target ID + + * @param source ID of the conversation to be merged + * * + * @param target ID of the merge target + */ + private fun mergeConversations(connection: Connection, source: UUID, target: UUID) { + try { + connection.prepareStatement( + "UPDATE Message SET conversation=? WHERE conversation=?").use { ps1 -> + connection.prepareStatement( + "UPDATE Message_Parent SET conversation=? WHERE conversation=?").use { ps2 -> + ps1.setObject(1, target) + ps1.setObject(2, source) + ps1.executeUpdate() + ps2.setObject(1, target) + ps2.setObject(2, source) + ps2.executeUpdate() + } + } + } catch (e: SQLException) { + LOG.error(e.message, e) + } + + } + + companion object { + private val LOG = LoggerFactory.getLogger(JdbcMessageRepository::class.java) + } +} diff --git a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepository.java b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepository.java index 2af1450..c509e4a 100644 --- a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepository.java +++ b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepository.java @@ -105,9 +105,9 @@ public class JdbcProofOfWorkRepository extends JdbcHelper implements ProofOfWork "nonce_trials_per_byte, extra_bytes, expiration_time, message_id) " + "VALUES (?, ?, ?, ?, ?, ?, ?)") ) { - ps.setBytes(1, cryptography().getInitialHash(item.getObject())); - writeBlob(ps, 2, item.getObject()); - ps.setLong(3, item.getObject().getVersion()); + ps.setBytes(1, cryptography().getInitialHash(item.getObjectMessage())); + writeBlob(ps, 2, item.getObjectMessage()); + ps.setLong(3, item.getObjectMessage().getVersion()); ps.setLong(4, item.getNonceTrialsPerByte()); ps.setLong(5, item.getExtraBytes()); @@ -120,7 +120,7 @@ public class JdbcProofOfWorkRepository extends JdbcHelper implements ProofOfWork } ps.executeUpdate(); } catch (IOException | SQLException e) { - LOG.debug("Error storing object of type " + item.getObject().getPayload().getClass().getSimpleName(), e); + LOG.debug("Error storing object of type " + item.getObjectMessage().getPayload().getClass().getSimpleName(), e); throw new ApplicationException(e); } } diff --git a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepositoryTest.kt b/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepositoryTest.kt index 76031e9..1e08859 100644 --- a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepositoryTest.kt +++ b/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepositoryTest.kt @@ -129,7 +129,7 @@ class JdbcProofOfWorkRepositoryTest : TestBase() { fun `ensure item can be retrieved`() { val item = repo.getItem(initialHash1) assertThat(item, notNullValue()) - assertThat<ObjectPayload>(item.`object`.payload, instanceOf<ObjectPayload>(GetPubkey::class.java)) + assertThat<ObjectPayload>(item.objectMessage.payload, instanceOf<ObjectPayload>(GetPubkey::class.java)) assertThat(item.nonceTrialsPerByte, `is`(1000L)) assertThat(item.extraBytes, `is`(1000L)) } @@ -138,7 +138,7 @@ class JdbcProofOfWorkRepositoryTest : TestBase() { fun `ensure ack item can be retrieved`() { val item = repo.getItem(initialHash2) assertThat(item, notNullValue()) - assertThat<ObjectPayload>(item.`object`.payload, instanceOf<ObjectPayload>(GenericPayload::class.java)) + assertThat<ObjectPayload>(item.objectMessage.payload, instanceOf<ObjectPayload>(GenericPayload::class.java)) assertThat(item.nonceTrialsPerByte, `is`(1000L)) assertThat(item.extraBytes, `is`(1000L)) assertThat(item.expirationTime, not<Number>(0)) diff --git a/repositories/src/test/java/ch/dissem/bitmessage/repository/TestBase.kt b/repositories/src/test/java/ch/dissem/bitmessage/repository/TestBase.kt index 107e462..cbd6e76 100644 --- a/repositories/src/test/java/ch/dissem/bitmessage/repository/TestBase.kt +++ b/repositories/src/test/java/ch/dissem/bitmessage/repository/TestBase.kt @@ -21,16 +21,11 @@ import ch.dissem.bitmessage.ports.MultiThreadedPOWEngine import ch.dissem.bitmessage.utils.Singleton import ch.dissem.bitmessage.utils.TestUtils.mockedInternalContext -/** - * Created by chris on 20.07.15. - */ open class TestBase { companion object { init { - val security = BouncyCryptography() - Singleton.initialize(security) mockedInternalContext( - cryptography = security, + cryptography = BouncyCryptography(), proofOfWorkEngine = MultiThreadedPOWEngine() ) } diff --git a/wif/build.gradle b/wif/build.gradle index a466f69..cf317e5 100644 --- a/wif/build.gradle +++ b/wif/build.gradle @@ -15,5 +15,6 @@ dependencies { compile 'org.ini4j:ini4j:0.5.4' testCompile 'junit:junit:4.12' testCompile 'org.mockito:mockito-core:2.7.21' + testCompile 'com.nhaarman:mockito-kotlin:1.4.0' testCompile project(':cryptography-bc') } diff --git a/wif/src/main/java/ch/dissem/bitmessage/wif/WifExporter.java b/wif/src/main/java/ch/dissem/bitmessage/wif/WifExporter.java deleted file mode 100644 index 3f4b370..0000000 --- a/wif/src/main/java/ch/dissem/bitmessage/wif/WifExporter.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.wif; - -import ch.dissem.bitmessage.BitmessageContext; -import ch.dissem.bitmessage.entity.BitmessageAddress; -import ch.dissem.bitmessage.exception.ApplicationException; -import ch.dissem.bitmessage.utils.Base58; -import org.ini4j.Ini; -import org.ini4j.Profile; - -import java.io.*; -import java.util.Collection; - -import static ch.dissem.bitmessage.entity.valueobject.PrivateKey.PRIVATE_KEY_SIZE; -import static ch.dissem.bitmessage.utils.Singleton.cryptography; - -/** - * @author Christian Basler - */ -public class WifExporter { - private final BitmessageContext ctx; - private final Ini ini; - - public WifExporter(BitmessageContext ctx) { - this.ctx = ctx; - this.ini = new Ini(); - } - - public WifExporter addAll() { - for (BitmessageAddress identity : ctx.addresses().getIdentities()) { - addIdentity(identity); - } - return this; - } - - public WifExporter addAll(Collection<BitmessageAddress> identities) { - for (BitmessageAddress identity : identities) { - addIdentity(identity); - } - return this; - } - - public WifExporter addIdentity(BitmessageAddress identity) { - Profile.Section section = ini.add(identity.getAddress()); - section.add("label", identity.getAlias()); - section.add("enabled", true); - section.add("decoy", false); - if (identity.isChan()) { - section.add("chan", identity.isChan()); - } - section.add("noncetrialsperbyte", identity.getPubkey().getNonceTrialsPerByte()); - section.add("payloadlengthextrabytes", identity.getPubkey().getExtraBytes()); - section.add("privsigningkey", exportSecret(identity.getPrivateKey().getPrivateSigningKey())); - section.add("privencryptionkey", exportSecret(identity.getPrivateKey().getPrivateEncryptionKey())); - return this; - } - - private String exportSecret(byte[] privateKey) { - if (privateKey.length != PRIVATE_KEY_SIZE) { - throw new IllegalArgumentException("Private key of length 32 expected, but was " + privateKey.length); - } - byte[] result = new byte[37]; - result[0] = (byte) 0x80; - System.arraycopy(privateKey, 0, result, 1, PRIVATE_KEY_SIZE); - byte[] hash = cryptography().doubleSha256(result, PRIVATE_KEY_SIZE + 1); - System.arraycopy(hash, 0, result, PRIVATE_KEY_SIZE + 1, 4); - return Base58.encode(result); - } - - public void write(File file) throws IOException { - file.createNewFile(); - try (FileOutputStream out = new FileOutputStream(file)) { - write(out); - } - } - - public void write(OutputStream out) throws IOException { - ini.store(out); - } - - @Override - public String toString() { - StringWriter writer = new StringWriter(); - try { - ini.store(writer); - } catch (IOException e) { - throw new ApplicationException(e); - } - return writer.toString(); - } -} diff --git a/wif/src/main/java/ch/dissem/bitmessage/wif/WifExporter.kt b/wif/src/main/java/ch/dissem/bitmessage/wif/WifExporter.kt new file mode 100644 index 0000000..9245ba7 --- /dev/null +++ b/wif/src/main/java/ch/dissem/bitmessage/wif/WifExporter.kt @@ -0,0 +1,103 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Copyright 2015 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.wif + +import ch.dissem.bitmessage.BitmessageContext +import ch.dissem.bitmessage.entity.BitmessageAddress +import ch.dissem.bitmessage.entity.valueobject.PrivateKey.Companion.PRIVATE_KEY_SIZE +import ch.dissem.bitmessage.utils.Base58 +import ch.dissem.bitmessage.utils.Singleton.cryptography +import org.ini4j.Ini +import java.io.File +import java.io.FileOutputStream +import java.io.OutputStream +import java.io.StringWriter + +/** + * @author Christian Basler + */ +class WifExporter(private val ctx: BitmessageContext) { + private val ini = Ini() + + fun addAll(): WifExporter { + ctx.addresses.getIdentities().forEach { addIdentity(it) } + return this + } + + fun addAll(identities: Collection<BitmessageAddress>): WifExporter { + identities.forEach { addIdentity(it) } + return this + } + + fun addIdentity(identity: BitmessageAddress): WifExporter { + val section = ini.add(identity.address) + section.add("label", identity.alias) + section.add("enabled", true) + section.add("decoy", false) + if (identity.isChan) { + section.add("chan", identity.isChan) + } + section.add("noncetrialsperbyte", identity.pubkey!!.nonceTrialsPerByte) + section.add("payloadlengthextrabytes", identity.pubkey!!.extraBytes) + section.add("privsigningkey", exportSecret(identity.privateKey!!.privateSigningKey)) + section.add("privencryptionkey", exportSecret(identity.privateKey!!.privateEncryptionKey)) + return this + } + + private fun exportSecret(privateKey: ByteArray): String { + if (privateKey.size != PRIVATE_KEY_SIZE) { + throw IllegalArgumentException("Private key of length 32 expected, but was " + privateKey.size) + } + val result = ByteArray(37) + result[0] = 0x80.toByte() + System.arraycopy(privateKey, 0, result, 1, PRIVATE_KEY_SIZE) + val hash = cryptography().doubleSha256(result, PRIVATE_KEY_SIZE + 1) + System.arraycopy(hash, 0, result, PRIVATE_KEY_SIZE + 1, 4) + return Base58.encode(result) + } + + fun write(file: File) { + file.createNewFile() + FileOutputStream(file).use { out -> write(out) } + } + + fun write(out: OutputStream) { + ini.store(out) + } + + override fun toString(): String { + val writer = StringWriter() + ini.store(writer) + return writer.toString() + } +} diff --git a/wif/src/main/java/ch/dissem/bitmessage/wif/WifImporter.java b/wif/src/main/java/ch/dissem/bitmessage/wif/WifImporter.java deleted file mode 100644 index 0d010c9..0000000 --- a/wif/src/main/java/ch/dissem/bitmessage/wif/WifImporter.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.wif; - -import ch.dissem.bitmessage.BitmessageContext; -import ch.dissem.bitmessage.entity.BitmessageAddress; -import ch.dissem.bitmessage.entity.payload.Pubkey; -import ch.dissem.bitmessage.factory.Factory; -import ch.dissem.bitmessage.utils.Base58; -import org.ini4j.Ini; -import org.ini4j.Profile; - -import java.io.*; -import java.util.Arrays; -import java.util.Collection; -import java.util.LinkedList; -import java.util.List; -import java.util.Map.Entry; - -import static ch.dissem.bitmessage.utils.Singleton.cryptography; - -/** - * @author Christian Basler - */ -public class WifImporter { - private static final byte WIF_FIRST_BYTE = (byte) 0x80; - private static final int WIF_SECRET_LENGTH = 37; - - private final BitmessageContext ctx; - private final List<BitmessageAddress> identities = new LinkedList<>(); - - public WifImporter(BitmessageContext ctx, File file) throws IOException { - this(ctx, new FileInputStream(file)); - } - - public WifImporter(BitmessageContext ctx, String data) throws IOException { - this(ctx, new ByteArrayInputStream(data.getBytes("utf-8"))); - } - - public WifImporter(BitmessageContext ctx, InputStream in, Pubkey.Feature... features) throws IOException { - this.ctx = ctx; - - Ini ini = new Ini(); - ini.load(in); - - for (Entry<String, Profile.Section> entry : ini.entrySet()) { - if (!entry.getKey().startsWith("BM-")) - continue; - - Profile.Section section = entry.getValue(); - BitmessageAddress address = Factory.createIdentityFromPrivateKey( - entry.getKey(), - getSecret(section.get("privsigningkey")), - getSecret(section.get("privencryptionkey")), - Long.parseLong(section.get("noncetrialsperbyte")), - Long.parseLong(section.get("payloadlengthextrabytes")), - Pubkey.Feature.bitfield(features) - ); - if (section.containsKey("chan")) { - address.setChan(Boolean.parseBoolean(section.get("chan"))); - } - address.setAlias(section.get("label")); - identities.add(address); - } - } - - private byte[] getSecret(String walletImportFormat) throws IOException { - byte[] bytes = Base58.decode(walletImportFormat); - if (bytes[0] != WIF_FIRST_BYTE) - throw new IOException("Unknown format: 0x80 expected as first byte, but secret " + walletImportFormat + - " was " + bytes[0]); - if (bytes.length != WIF_SECRET_LENGTH) - throw new IOException("Unknown format: " + WIF_SECRET_LENGTH + - " bytes expected, but secret " + walletImportFormat + " was " + bytes.length + " long"); - - byte[] hash = cryptography().doubleSha256(bytes, 33); - for (int i = 0; i < 4; i++) { - if (hash[i] != bytes[33 + i]) throw new IOException("Hash check failed for secret " + walletImportFormat); - } - return Arrays.copyOfRange(bytes, 1, 33); - } - - public List<BitmessageAddress> getIdentities() { - return identities; - } - - public WifImporter importAll() { - for (BitmessageAddress identity : identities) { - ctx.addresses().save(identity); - } - return this; - } - - public WifImporter importAll(Collection<BitmessageAddress> identities) { - for (BitmessageAddress identity : identities) { - ctx.addresses().save(identity); - } - return this; - } - - public WifImporter importIdentity(BitmessageAddress identity) { - ctx.addresses().save(identity); - return this; - } -} diff --git a/wif/src/main/java/ch/dissem/bitmessage/wif/WifImporter.kt b/wif/src/main/java/ch/dissem/bitmessage/wif/WifImporter.kt new file mode 100644 index 0000000..5b7e958 --- /dev/null +++ b/wif/src/main/java/ch/dissem/bitmessage/wif/WifImporter.kt @@ -0,0 +1,126 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Copyright 2015 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.wif + +import ch.dissem.bitmessage.BitmessageContext +import ch.dissem.bitmessage.entity.BitmessageAddress +import ch.dissem.bitmessage.entity.payload.Pubkey +import ch.dissem.bitmessage.exception.ApplicationException +import ch.dissem.bitmessage.factory.Factory +import ch.dissem.bitmessage.utils.Base58 +import ch.dissem.bitmessage.utils.Singleton.cryptography +import org.ini4j.Ini +import java.io.ByteArrayInputStream +import java.io.File +import java.io.FileInputStream +import java.io.InputStream +import java.util.* + +/** + * @author Christian Basler + */ +class WifImporter constructor( + private val ctx: BitmessageContext, + `in`: InputStream, + vararg features: Pubkey.Feature +) { + private val identities = LinkedList<BitmessageAddress>() + + constructor(ctx: BitmessageContext, file: File) : this(ctx, FileInputStream(file)) + + constructor(ctx: BitmessageContext, data: String) : this(ctx, ByteArrayInputStream(data.toByteArray(charset("utf-8")))) + + init { + val ini = Ini() + ini.load(`in`) + + for ((key, section) in ini) { + if (!key.startsWith("BM-")) + continue + + val address = Factory.createIdentityFromPrivateKey( + key, + getSecret(section["privsigningkey"] ?: throw ApplicationException("privsigningkey missing for $key")), + getSecret(section["privencryptionkey"] ?: throw ApplicationException("privencryptionkey missing for $key")), + section["noncetrialsperbyte"]?.toLongOrNull() ?: throw ApplicationException("noncetrialsperbyte missing for $key"), + section["payloadlengthextrabytes"]?.toLongOrNull() ?: throw ApplicationException("payloadlengthextrabytes missing for $key"), + Pubkey.Feature.bitfield(*features) + ) + if (section.containsKey("chan")) { + address.isChan = java.lang.Boolean.parseBoolean(section["chan"]) + } + address.alias = section["label"] + identities.add(address) + } + } + + private fun getSecret(walletImportFormat: String): ByteArray { + val bytes = Base58.decode(walletImportFormat) + if (bytes[0] != WIF_FIRST_BYTE) + throw ApplicationException("Unknown format: 0x80 expected as first byte, but secret " + walletImportFormat + + " was " + bytes[0]) + if (bytes.size != WIF_SECRET_LENGTH) + throw ApplicationException("Unknown format: " + WIF_SECRET_LENGTH + + " bytes expected, but secret " + walletImportFormat + " was " + bytes.size + " long") + + val hash = cryptography().doubleSha256(bytes, 33) + (0..3) + .filter { hash[it] != bytes[33 + it] } + .forEach { throw ApplicationException("Hash check failed for secret " + walletImportFormat) } + return Arrays.copyOfRange(bytes, 1, 33) + } + + fun getIdentities(): List<BitmessageAddress> { + return identities + } + + fun importAll(): WifImporter { + identities.forEach { ctx.addresses.save(it) } + return this + } + + fun importAll(identities: Collection<BitmessageAddress>): WifImporter { + identities.forEach { ctx.addresses.save(it) } + return this + } + + fun importIdentity(identity: BitmessageAddress): WifImporter { + ctx.addresses.save(identity) + return this + } + + companion object { + private const val WIF_FIRST_BYTE = 0x80.toByte() + private const val WIF_SECRET_LENGTH = 37 + } +} diff --git a/wif/src/test/java/ch/dissem/bitmessage/wif/WifExporterTest.java b/wif/src/test/java/ch/dissem/bitmessage/wif/WifExporterTest.java deleted file mode 100644 index d5e8bde..0000000 --- a/wif/src/test/java/ch/dissem/bitmessage/wif/WifExporterTest.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.wif; - -import ch.dissem.bitmessage.BitmessageContext; -import ch.dissem.bitmessage.entity.BitmessageAddress; -import ch.dissem.bitmessage.ports.*; -import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography; -import org.junit.Before; -import org.junit.Test; - -import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class WifExporterTest { - private AddressRepository repo = mock(AddressRepository.class); - private BitmessageContext ctx; - private WifImporter importer; - private WifExporter exporter; - - @Before - public void setUp() throws Exception { - ctx = new BitmessageContext.Builder() - .cryptography(BouncyCryptography.INSTANCE) - .networkHandler(mock(NetworkHandler.class)) - .inventory(mock(Inventory.class)) - .messageRepo(mock(MessageRepository.class)) - .powRepo(mock(ProofOfWorkRepository.class)) - .nodeRegistry(mock(NodeRegistry.class)) - .addressRepo(repo) - .build(); - importer = new WifImporter(ctx, getClass().getClassLoader().getResourceAsStream("nuked.dat")); - assertEquals(81, importer.getIdentities().size()); - exporter = new WifExporter(ctx); - } - - @Test - public void testAddAll() throws Exception { - when(repo.getIdentities()).thenReturn(importer.getIdentities()); - exporter.addAll(); - String result = exporter.toString(); - int count = 0; - for (int i = 0; i < result.length(); i++) { - if (result.charAt(i) == '[') count++; - } - assertEquals(importer.getIdentities().size(), count); - } - - @Test - public void testAddAllFromCollection() throws Exception { - exporter.addAll(importer.getIdentities()); - String result = exporter.toString(); - int count = 0; - for (int i = 0; i < result.length(); i++) { - if (result.charAt(i) == '[') count++; - } - assertEquals(importer.getIdentities().size(), count); - } - - @Test - public void testAddIdentity() throws Exception { - String expected = "[BM-2DAjcCFrqFrp88FUxExhJ9kPqHdunQmiyn]" + System.lineSeparator() + - "label = Nuked Address" + System.lineSeparator() + - "enabled = true" + System.lineSeparator() + - "decoy = false" + System.lineSeparator() + - "noncetrialsperbyte = 320" + System.lineSeparator() + - "payloadlengthextrabytes = 14000" + System.lineSeparator() + - "privsigningkey = 5KU2gbe9u4rKJ8PHYb1rvwMnZnAJj4gtV5GLwoYckeYzygWUzB9" + System.lineSeparator() + - "privencryptionkey = 5KHd4c6cavd8xv4kzo3PwnVaYuBgEfg7voPQ5V97aZKgpYBXGck" + System.lineSeparator() + - System.lineSeparator(); - importer = new WifImporter(ctx, expected); - exporter.addIdentity(importer.getIdentities().get(0)); - assertEquals(expected, exporter.toString()); - } - - @Test - public void ensureChanIsAdded() throws Exception { - String expected = "[BM-2cW67GEKkHGonXKZLCzouLLxnLym3azS8r]" + System.lineSeparator() + - "label = general" + System.lineSeparator() + - "enabled = true" + System.lineSeparator() + - "decoy = false" + System.lineSeparator() + - "chan = true" + System.lineSeparator() + - "noncetrialsperbyte = 1000" + System.lineSeparator() + - "payloadlengthextrabytes = 1000" + System.lineSeparator() + - "privsigningkey = 5Jnbdwc4u4DG9ipJxYLznXSvemkRFueQJNHujAQamtDDoX3N1eQ" + System.lineSeparator() + - "privencryptionkey = 5JrDcFtQDv5ydcHRW6dfGUEvThoxCCLNEUaxQfy8LXXgTJzVAcq" + System.lineSeparator() + - System.lineSeparator(); - BitmessageAddress chan = ctx.joinChan("general", "BM-2cW67GEKkHGonXKZLCzouLLxnLym3azS8r"); - exporter.addIdentity(chan); - assertEquals(expected, exporter.toString()); - } -} diff --git a/wif/src/test/java/ch/dissem/bitmessage/wif/WifExporterTest.kt b/wif/src/test/java/ch/dissem/bitmessage/wif/WifExporterTest.kt new file mode 100644 index 0000000..657ec48 --- /dev/null +++ b/wif/src/test/java/ch/dissem/bitmessage/wif/WifExporterTest.kt @@ -0,0 +1,122 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Copyright 2015 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.wif + +import ch.dissem.bitmessage.BitmessageContext +import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography +import ch.dissem.bitmessage.ports.AddressRepository +import com.nhaarman.mockito_kotlin.mock +import com.nhaarman.mockito_kotlin.whenever +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test + +class WifExporterTest { + private val repo = mock<AddressRepository>() + private lateinit var ctx: BitmessageContext + private lateinit var importer: WifImporter + private lateinit var exporter: WifExporter + + @Before + fun setUp() { + ctx = BitmessageContext.Builder() + .cryptography(BouncyCryptography()) + .networkHandler(mock()) + .inventory(mock()) + .messageRepo(mock()) + .powRepo(mock()) + .nodeRegistry(mock()) + .addressRepo(repo) + .listener { } + .build() + importer = WifImporter(ctx, javaClass.classLoader.getResourceAsStream("nuked.dat")) + assertEquals(81, importer.getIdentities().size) + exporter = WifExporter(ctx) + } + + @Test + fun `ensure all identities in context are added`() { + whenever(repo.getIdentities()).thenReturn(importer.getIdentities()) + exporter.addAll() + val result = exporter.toString() + var count = 0 + for (i in 0..result.length - 1) { + if (result[i] == '[') count++ + } + assertEquals(importer.getIdentities().size, count) + } + + @Test + fun `ensure all from a collection are added`() { + exporter.addAll(importer.getIdentities()) + val result = exporter.toString() + var count = 0 + for (i in 0..result.length - 1) { + if (result[i] == '[') count++ + } + assertEquals(importer.getIdentities().size, count) + } + + @Test + fun `ensure identity is added`() { + val expected = "[BM-2DAjcCFrqFrp88FUxExhJ9kPqHdunQmiyn]" + System.lineSeparator() + + "label = Nuked Address" + System.lineSeparator() + + "enabled = true" + System.lineSeparator() + + "decoy = false" + System.lineSeparator() + + "noncetrialsperbyte = 320" + System.lineSeparator() + + "payloadlengthextrabytes = 14000" + System.lineSeparator() + + "privsigningkey = 5KU2gbe9u4rKJ8PHYb1rvwMnZnAJj4gtV5GLwoYckeYzygWUzB9" + System.lineSeparator() + + "privencryptionkey = 5KHd4c6cavd8xv4kzo3PwnVaYuBgEfg7voPQ5V97aZKgpYBXGck" + System.lineSeparator() + + System.lineSeparator() + importer = WifImporter(ctx, expected) + exporter.addIdentity(importer.getIdentities()[0]) + assertEquals(expected, exporter.toString()) + } + + @Test + fun `ensure chan is added`() { + val expected = "[BM-2cW67GEKkHGonXKZLCzouLLxnLym3azS8r]" + System.lineSeparator() + + "label = general" + System.lineSeparator() + + "enabled = true" + System.lineSeparator() + + "decoy = false" + System.lineSeparator() + + "chan = true" + System.lineSeparator() + + "noncetrialsperbyte = 1000" + System.lineSeparator() + + "payloadlengthextrabytes = 1000" + System.lineSeparator() + + "privsigningkey = 5Jnbdwc4u4DG9ipJxYLznXSvemkRFueQJNHujAQamtDDoX3N1eQ" + System.lineSeparator() + + "privencryptionkey = 5JrDcFtQDv5ydcHRW6dfGUEvThoxCCLNEUaxQfy8LXXgTJzVAcq" + System.lineSeparator() + + System.lineSeparator() + val chan = ctx.joinChan("general", "BM-2cW67GEKkHGonXKZLCzouLLxnLym3azS8r") + exporter.addIdentity(chan) + assertEquals(expected, exporter.toString()) + } +} diff --git a/wif/src/test/java/ch/dissem/bitmessage/wif/WifImporterTest.java b/wif/src/test/java/ch/dissem/bitmessage/wif/WifImporterTest.java deleted file mode 100644 index 2116b3a..0000000 --- a/wif/src/test/java/ch/dissem/bitmessage/wif/WifImporterTest.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.wif; - -import ch.dissem.bitmessage.BitmessageContext; -import ch.dissem.bitmessage.entity.BitmessageAddress; -import ch.dissem.bitmessage.ports.*; -import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography; -import org.junit.Before; -import org.junit.Test; - -import java.util.List; - -import static org.junit.Assert.*; -import static org.mockito.Mockito.*; - -public class WifImporterTest { - private AddressRepository repo = mock(AddressRepository.class); - private BitmessageContext ctx; - private WifImporter importer; - - @Before - public void setUp() throws Exception { - ctx = new BitmessageContext.Builder() - .cryptography(BouncyCryptography.INSTANCE) - .networkHandler(mock(NetworkHandler.class)) - .inventory(mock(Inventory.class)) - .messageRepo(mock(MessageRepository.class)) - .powRepo(mock(ProofOfWorkRepository.class)) - .nodeRegistry(mock(NodeRegistry.class)) - .addressRepo(repo) - .build(); - importer = new WifImporter(ctx, getClass().getClassLoader().getResourceAsStream("nuked.dat")); - } - - - @Test - public void testImportSingleIdentity() throws Exception { - importer = new WifImporter(ctx, "[BM-2cWJ4UFRTCehWuWNsW8fJkAYMxU4S8jxci]\n" + - "label = Nuked Address\n" + - "enabled = true\n" + - "decoy = false\n" + - "noncetrialsperbyte = 320\n" + - "payloadlengthextrabytes = 14000\n" + - "privsigningkey = 5JU5t2JA58sP5aJwKAcrYg5EpBA9bJPrBSaFfaZ7ogmwTMDCfHL\n" + - "privencryptionkey = 5Kkx5MwjQcM4kyduKvCEPM6nVNynMdRcg88VQ5iVDWUekMz1igH"); - assertEquals(1, importer.getIdentities().size()); - BitmessageAddress identity = importer.getIdentities().get(0); - assertEquals("BM-2cWJ4UFRTCehWuWNsW8fJkAYMxU4S8jxci", identity.getAddress()); - assertEquals("Nuked Address", identity.getAlias()); - assertEquals(320, identity.getPubkey().getNonceTrialsPerByte()); - assertEquals(14000, identity.getPubkey().getExtraBytes()); - assertNotNull("Private key", identity.getPrivateKey()); - assertEquals(32, identity.getPrivateKey().getPrivateEncryptionKey().length); - assertEquals(32, identity.getPrivateKey().getPrivateSigningKey().length); - assertFalse(identity.isChan()); - } - - @Test - public void testGetIdentities() throws Exception { - List<BitmessageAddress> identities = importer.getIdentities(); - assertEquals(81, identities.size()); - } - - @Test - public void testImportAll() throws Exception { - importer.importAll(); - verify(repo, times(81)).save(any(BitmessageAddress.class)); - } - - @Test - public void testImportAllFromCollection() throws Exception { - List<BitmessageAddress> identities = importer.getIdentities(); - importer.importAll(identities); - for (BitmessageAddress identity : identities) { - verify(repo, times(1)).save(identity); - } - } - - @Test - public void testImportIdentity() throws Exception { - List<BitmessageAddress> identities = importer.getIdentities(); - importer.importIdentity(identities.get(0)); - verify(repo, times(1)).save(identities.get(0)); - } - - @Test - public void ensureChanIsImported() throws Exception { - importer = new WifImporter(ctx, "[BM-2cW67GEKkHGonXKZLCzouLLxnLym3azS8r]\n" + - "label = [chan] general\n" + - "enabled = true\n" + - "decoy = false\n" + - "chan = true\n" + - "noncetrialsperbyte = 1000\n" + - "payloadlengthextrabytes = 1000\n" + - "privsigningkey = 5Jnbdwc4u4DG9ipJxYLznXSvemkRFueQJNHujAQamtDDoX3N1eQ\n" + - "privencryptionkey = 5JrDcFtQDv5ydcHRW6dfGUEvThoxCCLNEUaxQfy8LXXgTJzVAcq\n"); - assertEquals(1, importer.getIdentities().size()); - BitmessageAddress chan = importer.getIdentities().get(0); - assertTrue(chan.isChan()); - } -} diff --git a/wif/src/test/java/ch/dissem/bitmessage/wif/WifImporterTest.kt b/wif/src/test/java/ch/dissem/bitmessage/wif/WifImporterTest.kt new file mode 100644 index 0000000..08b14fa --- /dev/null +++ b/wif/src/test/java/ch/dissem/bitmessage/wif/WifImporterTest.kt @@ -0,0 +1,132 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Copyright 2015 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.wif + +import ch.dissem.bitmessage.BitmessageContext +import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography +import ch.dissem.bitmessage.ports.AddressRepository +import com.nhaarman.mockito_kotlin.any +import com.nhaarman.mockito_kotlin.mock +import com.nhaarman.mockito_kotlin.times +import com.nhaarman.mockito_kotlin.verify +import org.junit.Assert.* +import org.junit.Before +import org.junit.Test + +class WifImporterTest { + private val repo = mock<AddressRepository>() + private lateinit var ctx: BitmessageContext + private lateinit var importer: WifImporter + + @Before + fun setUp() { + ctx = BitmessageContext.Builder() + .cryptography(BouncyCryptography()) + .networkHandler(mock()) + .inventory(mock()) + .messageRepo(mock()) + .powRepo(mock()) + .nodeRegistry(mock()) + .addressRepo(repo) + .listener { } + .build() + importer = WifImporter(ctx, javaClass.classLoader.getResourceAsStream("nuked.dat")) + } + + + @Test + fun `ensure single identity is imported`() { + importer = WifImporter(ctx, "[BM-2cWJ4UFRTCehWuWNsW8fJkAYMxU4S8jxci]\n" + + "label = Nuked Address\n" + + "enabled = true\n" + + "decoy = false\n" + + "noncetrialsperbyte = 320\n" + + "payloadlengthextrabytes = 14000\n" + + "privsigningkey = 5JU5t2JA58sP5aJwKAcrYg5EpBA9bJPrBSaFfaZ7ogmwTMDCfHL\n" + + "privencryptionkey = 5Kkx5MwjQcM4kyduKvCEPM6nVNynMdRcg88VQ5iVDWUekMz1igH") + assertEquals(1, importer.getIdentities().size) + val identity = importer.getIdentities()[0] + assertEquals("BM-2cWJ4UFRTCehWuWNsW8fJkAYMxU4S8jxci", identity.address) + assertEquals("Nuked Address", identity.alias) + assertEquals(320L, identity.pubkey?.nonceTrialsPerByte) + assertEquals(14000L, identity.pubkey?.extraBytes) + assertNotNull("Private key", identity.privateKey) + assertEquals(32, identity.privateKey?.privateEncryptionKey?.size) + assertEquals(32, identity.privateKey?.privateSigningKey?.size) + assertFalse(identity.isChan) + } + + @Test + fun `ensure all identities are retrieved`() { + val identities = importer.getIdentities() + assertEquals(81, identities.size) + } + + @Test + fun `ensure all identities are imported`() { + importer.importAll() + verify(repo, times(81)).save(any()) + } + + @Test + fun `ensure all identities in collection are imported`() { + val identities = importer.getIdentities() + importer.importAll(identities) + for (identity in identities) { + verify(repo, times(1)).save(identity) + } + } + + @Test + fun `ensure single identity from list is imported`() { + val identities = importer.getIdentities() + importer.importIdentity(identities[0]) + verify(repo, times(1)).save(identities[0]) + } + + @Test + fun `ensure chan is imported`() { + importer = WifImporter(ctx, "[BM-2cW67GEKkHGonXKZLCzouLLxnLym3azS8r]\n" + + "label = [chan] general\n" + + "enabled = true\n" + + "decoy = false\n" + + "chan = true\n" + + "noncetrialsperbyte = 1000\n" + + "payloadlengthextrabytes = 1000\n" + + "privsigningkey = 5Jnbdwc4u4DG9ipJxYLznXSvemkRFueQJNHujAQamtDDoX3N1eQ\n" + + "privencryptionkey = 5JrDcFtQDv5ydcHRW6dfGUEvThoxCCLNEUaxQfy8LXXgTJzVAcq\n") + assertEquals(1, importer.getIdentities().size) + val chan = importer.getIdentities()[0] + assertTrue(chan.isChan) + } +} From 322bddcc4fc0bcc7e3f56185a8fc71965b9dcda9 Mon Sep 17 00:00:00 2001 From: Christian Basler <chrigu.meyer@gmail.com> Date: Sun, 25 Jun 2017 20:06:17 +0200 Subject: [PATCH 05/10] Migrated networking and fixed networking tests --- build.gradle | 2 +- .../ch/dissem/bitmessage/BitmessageContext.kt | 4 +- .../bitmessage/DefaultMessageListener.kt | 15 +- .../ch/dissem/bitmessage/InternalContext.kt | 12 - .../dissem/bitmessage/ProofOfWorkService.kt | 10 +- .../entity/valueobject/NetworkAddress.kt | 43 +- .../dissem/bitmessage/factory/BufferPool.kt | 16 +- .../bitmessage/factory/V3MessageReader.kt | 29 +- .../bitmessage/ports/AbstractCryptography.kt | 10 +- .../ports/AbstractMessageRepository.kt | 8 +- .../dissem/bitmessage/ports/DefaultLabeler.kt | 8 +- .../dissem/bitmessage/ports/NetworkHandler.kt | 4 +- .../dissem/bitmessage/ports/NodeRegistry.kt | 2 +- .../cryptography/bc/BouncyCryptography.kt | 0 .../bitmessage/security/CryptographyTest.kt | 0 .../cryptography/sc/SpongyCryptography.kt | 0 .../bitmessage/security/CryptographyTest.kt | 0 networking/build.gradle | 2 +- .../networking/AbstractConnection.java | 343 ------------ .../bitmessage/networking/Connection.java | 225 -------- .../networking/ConnectionOrganizer.java | 118 ---- .../networking/DefaultNetworkHandler.java | 249 --------- .../bitmessage/networking/ServerRunnable.java | 68 --- .../networking/nio/ConnectionInfo.java | 163 ------ .../networking/nio/NioNetworkHandler.java | 529 ------------------ .../bitmessage/networking/nio/Connection.kt | 202 +++++++ .../bitmessage/networking/nio/ConnectionIO.kt | 158 ++++++ .../nio/NetworkConnectionInitializer.kt | 113 ++++ .../networking/nio/NioNetworkHandler.kt | 476 ++++++++++++++++ .../networking/NetworkHandlerTest.java | 279 --------- .../networking/NetworkHandlerTest.kt | 266 +++++++++ .../repository/JdbcAddressRepository.java | 238 -------- .../bitmessage/repository/JdbcConfig.java | 51 -- .../bitmessage/repository/JdbcHelper.java | 46 -- .../bitmessage/repository/JdbcInventory.java | 187 ------- .../repository/JdbcNodeRegistry.java | 201 ------- .../repository/JdbcProofOfWorkRepository.java | 150 ----- .../repository/JdbcAddressRepository.kt | 203 +++++++ .../bitmessage/repository/JdbcConfig.kt | 39 ++ .../bitmessage/repository/JdbcHelper.kt | 38 ++ .../bitmessage/repository/JdbcInventory.kt | 166 ++++++ .../repository/JdbcMessageRepository.kt | 22 +- .../bitmessage/repository/JdbcNodeRegistry.kt | 188 +++++++ .../repository/JdbcProofOfWorkRepository.kt | 130 +++++ .../bitmessage/repository/TestJdbcConfig.java | 4 +- .../ch/dissem/bitmessage/wif/WifExporter.kt | 0 .../ch/dissem/bitmessage/wif/WifImporter.kt | 0 .../dissem/bitmessage/wif/WifExporterTest.kt | 0 .../dissem/bitmessage/wif/WifImporterTest.kt | 0 49 files changed, 2081 insertions(+), 2936 deletions(-) rename cryptography-bc/src/main/{java => kotlin}/ch/dissem/bitmessage/cryptography/bc/BouncyCryptography.kt (100%) rename cryptography-bc/src/test/{java => kotlin}/ch/dissem/bitmessage/security/CryptographyTest.kt (100%) rename cryptography-sc/src/main/{java => kotlin}/ch/dissem/bitmessage/cryptography/sc/SpongyCryptography.kt (100%) rename cryptography-sc/src/test/{java => kotlin}/ch/dissem/bitmessage/security/CryptographyTest.kt (100%) delete mode 100644 networking/src/main/java/ch/dissem/bitmessage/networking/AbstractConnection.java delete mode 100644 networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java delete mode 100644 networking/src/main/java/ch/dissem/bitmessage/networking/ConnectionOrganizer.java delete mode 100644 networking/src/main/java/ch/dissem/bitmessage/networking/DefaultNetworkHandler.java delete mode 100644 networking/src/main/java/ch/dissem/bitmessage/networking/ServerRunnable.java delete mode 100644 networking/src/main/java/ch/dissem/bitmessage/networking/nio/ConnectionInfo.java delete mode 100644 networking/src/main/java/ch/dissem/bitmessage/networking/nio/NioNetworkHandler.java create mode 100644 networking/src/main/kotlin/ch/dissem/bitmessage/networking/nio/Connection.kt create mode 100644 networking/src/main/kotlin/ch/dissem/bitmessage/networking/nio/ConnectionIO.kt create mode 100644 networking/src/main/kotlin/ch/dissem/bitmessage/networking/nio/NetworkConnectionInitializer.kt create mode 100644 networking/src/main/kotlin/ch/dissem/bitmessage/networking/nio/NioNetworkHandler.kt delete mode 100644 networking/src/test/java/ch/dissem/bitmessage/networking/NetworkHandlerTest.java create mode 100644 networking/src/test/java/ch/dissem/bitmessage/networking/NetworkHandlerTest.kt delete mode 100644 repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcAddressRepository.java delete mode 100644 repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcConfig.java delete mode 100644 repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcHelper.java delete mode 100644 repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcInventory.java delete mode 100644 repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcNodeRegistry.java delete mode 100644 repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepository.java create mode 100644 repositories/src/main/kotlin/ch/dissem/bitmessage/repository/JdbcAddressRepository.kt create mode 100644 repositories/src/main/kotlin/ch/dissem/bitmessage/repository/JdbcConfig.kt create mode 100644 repositories/src/main/kotlin/ch/dissem/bitmessage/repository/JdbcHelper.kt create mode 100644 repositories/src/main/kotlin/ch/dissem/bitmessage/repository/JdbcInventory.kt rename repositories/src/main/{java => kotlin}/ch/dissem/bitmessage/repository/JdbcMessageRepository.kt (96%) create mode 100644 repositories/src/main/kotlin/ch/dissem/bitmessage/repository/JdbcNodeRegistry.kt create mode 100644 repositories/src/main/kotlin/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepository.kt rename wif/src/main/{java => kotlin}/ch/dissem/bitmessage/wif/WifExporter.kt (100%) rename wif/src/main/{java => kotlin}/ch/dissem/bitmessage/wif/WifImporter.kt (100%) rename wif/src/test/{java => kotlin}/ch/dissem/bitmessage/wif/WifExporterTest.kt (100%) rename wif/src/test/{java => kotlin}/ch/dissem/bitmessage/wif/WifImporterTest.kt (100%) diff --git a/build.gradle b/build.gradle index 02fa2b4..89325d4 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.1.2-2' + ext.kotlin_version = '1.1.3' repositories { mavenCentral() } diff --git a/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.kt b/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.kt index f3ffa6c..ff3a58d 100644 --- a/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.kt +++ b/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.kt @@ -78,7 +78,7 @@ class BitmessageContext( port: Int = 8444, connectionTTL: Long = 30 * MINUTE, connectionLimit: Int = 150, - sendPubkeyOnIdentityCreation: Boolean, + sendPubkeyOnIdentityCreation: Boolean = true, doMissingProofOfWorkDelayInSeconds: Int = 30 ) { @@ -333,7 +333,7 @@ class BitmessageContext( fun status(): Property { return Property("status", - internals.networkHandler.networkStatus, + internals.networkHandler.getNetworkStatus(), Property("unacknowledged", internals.messageRepository.findMessagesToResend().size) ) } diff --git a/core/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.kt b/core/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.kt index 37c2a75..597f5a3 100644 --- a/core/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.kt +++ b/core/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.kt @@ -32,8 +32,12 @@ import java.util.* internal open class DefaultMessageListener( private val labeler: Labeler, private val listener: BitmessageContext.Listener -) : NetworkHandler.MessageListener { - private var ctx by InternalContext.lateinit +) : NetworkHandler.MessageListener, InternalContext.ContextHolder { + private lateinit var ctx: InternalContext + + override fun setContext(context: InternalContext) { + ctx = context + } override fun receive(objectMessage: ObjectMessage) { val payload = objectMessage.payload @@ -43,7 +47,7 @@ internal open class DefaultMessageListener( receive(objectMessage, payload as GetPubkey) } ObjectType.PUBKEY -> { - receive(objectMessage, payload as Pubkey) + receive(payload as Pubkey) } ObjectType.MSG -> { receive(objectMessage, payload as Msg) @@ -56,9 +60,6 @@ internal open class DefaultMessageListener( receive(payload) } } - else -> { - throw IllegalArgumentException("Unknown payload type " + payload.type!!) - } } } @@ -71,7 +72,7 @@ internal open class DefaultMessageListener( } } - protected fun receive(objectMessage: ObjectMessage, pubkey: Pubkey) { + protected fun receive(pubkey: Pubkey) { try { if (pubkey is V4Pubkey) { ctx.addressRepository.findContact(pubkey.tag)?.let { diff --git a/core/src/main/java/ch/dissem/bitmessage/InternalContext.kt b/core/src/main/java/ch/dissem/bitmessage/InternalContext.kt index 62bba56..a1c7f16 100644 --- a/core/src/main/java/ch/dissem/bitmessage/InternalContext.kt +++ b/core/src/main/java/ch/dissem/bitmessage/InternalContext.kt @@ -28,7 +28,6 @@ import ch.dissem.bitmessage.utils.UnixTime import org.slf4j.LoggerFactory import java.util.* import java.util.concurrent.Executors -import kotlin.reflect.KProperty /** * The internal context should normally only be used for port implementations. If you need it in your client @@ -67,8 +66,6 @@ class InternalContext( get() = _streams.toLongArray() init { - lateinit.instance = this - lateinit = ContextDelegate() Singleton.initialize(cryptography) // TODO: streams of new identities and subscriptions should also be added. This works only after a restart. @@ -218,19 +215,10 @@ class InternalContext( fun setContext(context: InternalContext) } - class ContextDelegate { - internal lateinit var instance: InternalContext - operator fun getValue(thisRef: Any?, property: KProperty<*>) = instance - operator fun setValue(thisRef: Any?, property: KProperty<*>, value: InternalContext) {} - } - companion object { private val LOG = LoggerFactory.getLogger(InternalContext::class.java) @JvmField val NETWORK_NONCE_TRIALS_PER_BYTE: Long = 1000 @JvmField val NETWORK_EXTRA_BYTES: Long = 1000 - - var lateinit = ContextDelegate() - private set } } diff --git a/core/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.kt b/core/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.kt index 67d6637..5d177a6 100644 --- a/core/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.kt +++ b/core/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.kt @@ -22,21 +22,23 @@ import ch.dissem.bitmessage.entity.* import ch.dissem.bitmessage.entity.payload.Msg import ch.dissem.bitmessage.ports.ProofOfWorkEngine import ch.dissem.bitmessage.ports.ProofOfWorkRepository.Item -import ch.dissem.bitmessage.utils.Strings import org.slf4j.LoggerFactory -import java.io.IOException import java.util.* /** * @author Christian Basler */ -class ProofOfWorkService : ProofOfWorkEngine.Callback { +class ProofOfWorkService : ProofOfWorkEngine.Callback, InternalContext.ContextHolder { - private val ctx by InternalContext.lateinit + private lateinit var ctx: InternalContext private val cryptography by lazy { ctx.cryptography } private val powRepo by lazy { ctx.proofOfWorkRepository } private val messageRepo by lazy { ctx.messageRepository } + override fun setContext(context: InternalContext) { + ctx = context + } + fun doMissingProofOfWork(delayInMilliseconds: Long) { val items = powRepo.getItems() if (items.isEmpty()) return diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/NetworkAddress.kt b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/NetworkAddress.kt index 01be7f6..30480a8 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/NetworkAddress.kt +++ b/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/NetworkAddress.kt @@ -23,10 +23,28 @@ import ch.dissem.bitmessage.utils.UnixTime import java.io.OutputStream import java.net.InetAddress import java.net.InetSocketAddress +import java.net.Socket import java.net.SocketAddress import java.nio.ByteBuffer import java.util.* +fun ip6(inetAddress: InetAddress): ByteArray { + val address = inetAddress.address + when (address.size) { + 16 -> { + return address + } + 4 -> { + val ip6 = ByteArray(16) + ip6[10] = 0xff.toByte() + ip6[11] = 0xff.toByte() + System.arraycopy(address, 0, ip6, 12, 4) + return ip6 + } + else -> throw IllegalArgumentException("Weird address " + inetAddress) + } +} + /** * A node's address. It's written in IPv6 format. */ @@ -51,6 +69,9 @@ data class NetworkAddress( val port: Int ) : Streamable { + constructor(time: Long, stream: Long, services: Long = 1, socket: Socket) + : this(time, stream, services, ip6(socket.inetAddress), socket.port) + fun provides(service: Version.Service?): Boolean = service?.isEnabled(services) ?: false fun toInetAddress(): InetAddress { @@ -125,18 +146,7 @@ data class NetworkAddress( } fun ip(inetAddress: InetAddress): Builder { - val addr = inetAddress.address - if (addr.size == 16) { - this.ipv6 = addr - } else if (addr.size == 4) { - val ipv6 = ByteArray(16) - ipv6[10] = 0xff.toByte() - ipv6[11] = 0xff.toByte() - System.arraycopy(addr, 0, ipv6, 12, 4) - this.ipv6 = ipv6 - } else { - throw IllegalArgumentException("Weird address " + inetAddress) - } + ipv6 = ip6(inetAddress) return this } @@ -165,9 +175,8 @@ data class NetworkAddress( fun address(address: SocketAddress): Builder { if (address is InetSocketAddress) { - val inetAddress = address - ip(inetAddress.address) - port(inetAddress.port) + ip(address.address) + port(address.port) } else { throw IllegalArgumentException("Unknown type of address: " + address.javaClass) } @@ -180,4 +189,8 @@ data class NetworkAddress( ) } } + + companion object { + @JvmField val ANY = NetworkAddress(time = 0, stream = 0, services = 0, IPv6 = ByteArray(16), port = 0) + } } diff --git a/core/src/main/java/ch/dissem/bitmessage/factory/BufferPool.kt b/core/src/main/java/ch/dissem/bitmessage/factory/BufferPool.kt index a301d37..8b1d031 100644 --- a/core/src/main/java/ch/dissem/bitmessage/factory/BufferPool.kt +++ b/core/src/main/java/ch/dissem/bitmessage/factory/BufferPool.kt @@ -39,9 +39,9 @@ object BufferPool { @Synchronized fun allocate(capacity: Int): ByteBuffer { val targetSize = getTargetSize(capacity) - val pool = pools[targetSize] - if (pool == null || pool.isEmpty()) { - LOG.trace("Creating new buffer of size " + targetSize!!) + val pool = pools[targetSize] ?: throw IllegalStateException("No pool for size $targetSize available") + if (pool.isEmpty()) { + LOG.trace("Creating new buffer of size $targetSize") return ByteBuffer.allocate(targetSize) } else { return pool.pop() @@ -64,16 +64,14 @@ object BufferPool { @Synchronized fun deallocate(buffer: ByteBuffer) { buffer.clear() - val pool = pools[buffer.capacity()] - pool?.push(buffer) ?: throw IllegalArgumentException("Illegal buffer capacity " + buffer.capacity() + - " one of " + pools.keys + " expected.") + val pool = pools[buffer.capacity()] ?: throw IllegalArgumentException("Illegal buffer capacity ${buffer.capacity()} one of ${pools.keys} expected.") + pool.push(buffer) } - private fun getTargetSize(capacity: Int): Int? { + private fun getTargetSize(capacity: Int): Int { for (size in pools.keys) { if (size >= capacity) return size } - throw IllegalArgumentException("Requested capacity too large: " + - "requested=" + capacity + "; max=" + MAX_PAYLOAD_SIZE) + throw IllegalArgumentException("Requested capacity too large: requested=$capacity; max=$MAX_PAYLOAD_SIZE") } } diff --git a/core/src/main/java/ch/dissem/bitmessage/factory/V3MessageReader.kt b/core/src/main/java/ch/dissem/bitmessage/factory/V3MessageReader.kt index 694e8c0..5b81621 100644 --- a/core/src/main/java/ch/dissem/bitmessage/factory/V3MessageReader.kt +++ b/core/src/main/java/ch/dissem/bitmessage/factory/V3MessageReader.kt @@ -40,23 +40,22 @@ class V3MessageReader { private val messages = LinkedList<NetworkMessage>() - val activeBuffer: ByteBuffer - get() { - if (state != null && state != ReaderState.DATA) { - if (headerBuffer == null) { - headerBuffer = BufferPool.allocateHeaderBuffer() - } + fun getActiveBuffer(): ByteBuffer { + if (state != null && state != ReaderState.DATA) { + if (headerBuffer == null) { + headerBuffer = BufferPool.allocateHeaderBuffer() } - return if (state == ReaderState.DATA) - dataBuffer ?: throw IllegalStateException("data buffer is null") - else - headerBuffer ?: throw IllegalStateException("header buffer is null") } + return if (state == ReaderState.DATA) + dataBuffer ?: throw IllegalStateException("data buffer is null") + else + headerBuffer ?: throw IllegalStateException("header buffer is null") + } fun update() { if (state != ReaderState.DATA) { - activeBuffer - headerBuffer!!.flip() + getActiveBuffer() // in order to initialize + headerBuffer?.flip() ?: throw IllegalStateException("header buffer is null") } when (state) { V3MessageReader.ReaderState.MAGIC -> magic(headerBuffer ?: throw IllegalStateException("header buffer is null")) @@ -93,12 +92,12 @@ class V3MessageReader { BufferPool.deallocate(headerBuffer) val dataBuffer = BufferPool.allocate(length) this.dataBuffer = dataBuffer + dataBuffer.clear() + dataBuffer.limit(length) data(dataBuffer) } private fun data(dataBuffer: ByteBuffer) { - dataBuffer.clear() - dataBuffer.limit(length) if (dataBuffer.position() < length) { return } else { @@ -126,7 +125,7 @@ class V3MessageReader { } } - fun getMessages(): List<NetworkMessage> { + fun getMessages(): MutableList<NetworkMessage> { return messages } diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/AbstractCryptography.kt b/core/src/main/java/ch/dissem/bitmessage/ports/AbstractCryptography.kt index e30f943..b655bd1 100644 --- a/core/src/main/java/ch/dissem/bitmessage/ports/AbstractCryptography.kt +++ b/core/src/main/java/ch/dissem/bitmessage/ports/AbstractCryptography.kt @@ -36,13 +36,17 @@ import javax.crypto.spec.SecretKeySpec /** * Implements everything that isn't directly dependent on either Spongy- or Bouncycastle. */ -abstract class AbstractCryptography protected constructor(@JvmField protected val provider: Provider) : Cryptography { - private val context by InternalContext.lateinit +abstract class AbstractCryptography protected constructor(@JvmField protected val provider: Provider) : Cryptography, InternalContext.ContextHolder { + private lateinit var ctx: InternalContext @JvmField protected val ALGORITHM_ECDSA = "ECDSA" @JvmField protected val ALGORITHM_ECDSA_SHA1 = "SHA1withECDSA" @JvmField protected val ALGORITHM_EVP_SHA256 = "SHA256withECDSA" + override fun setContext(context: InternalContext) { + ctx = context + } + override fun sha512(data: ByteArray, offset: Int, length: Int): ByteArray { val mda = md("SHA-512") mda.update(data, offset, length) @@ -95,7 +99,7 @@ abstract class AbstractCryptography protected constructor(@JvmField protected va val target = getProofOfWorkTarget(objectMessage, max(nonceTrialsPerByte, NETWORK_NONCE_TRIALS_PER_BYTE), max(extraBytes, NETWORK_EXTRA_BYTES)) - context.proofOfWorkEngine.calculateNonce(initialHash, target, callback) + ctx.proofOfWorkEngine.calculateNonce(initialHash, target, callback) } @Throws(InsufficientProofOfWorkException::class) diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/AbstractMessageRepository.kt b/core/src/main/java/ch/dissem/bitmessage/ports/AbstractMessageRepository.kt index c951ad2..b6d2108 100644 --- a/core/src/main/java/ch/dissem/bitmessage/ports/AbstractMessageRepository.kt +++ b/core/src/main/java/ch/dissem/bitmessage/ports/AbstractMessageRepository.kt @@ -27,8 +27,12 @@ import ch.dissem.bitmessage.utils.Strings import ch.dissem.bitmessage.utils.UnixTime import java.util.* -abstract class AbstractMessageRepository : MessageRepository { - protected var ctx by InternalContext.lateinit +abstract class AbstractMessageRepository : MessageRepository, InternalContext.ContextHolder { + protected lateinit var ctx: InternalContext + + override fun setContext(context: InternalContext) { + ctx = context + } protected fun saveContactIfNecessary(contact: BitmessageAddress?) { contact?.let { diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/DefaultLabeler.kt b/core/src/main/java/ch/dissem/bitmessage/ports/DefaultLabeler.kt index 4df9048..cc55db9 100644 --- a/core/src/main/java/ch/dissem/bitmessage/ports/DefaultLabeler.kt +++ b/core/src/main/java/ch/dissem/bitmessage/ports/DefaultLabeler.kt @@ -22,8 +22,12 @@ import ch.dissem.bitmessage.entity.Plaintext.Status.* import ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST import ch.dissem.bitmessage.entity.valueobject.Label -open class DefaultLabeler : Labeler { - private var ctx by InternalContext.lateinit +open class DefaultLabeler : Labeler, InternalContext.ContextHolder { + private lateinit var ctx: InternalContext + + override fun setContext(context: InternalContext) { + ctx = context + } override fun setLabels(msg: Plaintext) { msg.status = RECEIVED diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.kt b/core/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.kt index de7f24c..6425fe3 100644 --- a/core/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.kt +++ b/core/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.kt @@ -73,9 +73,9 @@ interface NetworkHandler { * @param inventoryVectors of the objects to be requested */ - fun request(inventoryVectors: Collection<InventoryVector>) + fun request(inventoryVectors: MutableCollection<InventoryVector>) - val networkStatus: Property + fun getNetworkStatus(): Property val isRunning: Boolean diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/NodeRegistry.kt b/core/src/main/java/ch/dissem/bitmessage/ports/NodeRegistry.kt index 59e3567..922370b 100644 --- a/core/src/main/java/ch/dissem/bitmessage/ports/NodeRegistry.kt +++ b/core/src/main/java/ch/dissem/bitmessage/ports/NodeRegistry.kt @@ -30,5 +30,5 @@ interface NodeRegistry { fun getKnownAddresses(limit: Int, vararg streams: Long): List<NetworkAddress> - fun offerAddresses(addresses: List<NetworkAddress>) + fun offerAddresses(nodes: List<NetworkAddress>) } diff --git a/cryptography-bc/src/main/java/ch/dissem/bitmessage/cryptography/bc/BouncyCryptography.kt b/cryptography-bc/src/main/kotlin/ch/dissem/bitmessage/cryptography/bc/BouncyCryptography.kt similarity index 100% rename from cryptography-bc/src/main/java/ch/dissem/bitmessage/cryptography/bc/BouncyCryptography.kt rename to cryptography-bc/src/main/kotlin/ch/dissem/bitmessage/cryptography/bc/BouncyCryptography.kt diff --git a/cryptography-bc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.kt b/cryptography-bc/src/test/kotlin/ch/dissem/bitmessage/security/CryptographyTest.kt similarity index 100% rename from cryptography-bc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.kt rename to cryptography-bc/src/test/kotlin/ch/dissem/bitmessage/security/CryptographyTest.kt diff --git a/cryptography-sc/src/main/java/ch/dissem/bitmessage/cryptography/sc/SpongyCryptography.kt b/cryptography-sc/src/main/kotlin/ch/dissem/bitmessage/cryptography/sc/SpongyCryptography.kt similarity index 100% rename from cryptography-sc/src/main/java/ch/dissem/bitmessage/cryptography/sc/SpongyCryptography.kt rename to cryptography-sc/src/main/kotlin/ch/dissem/bitmessage/cryptography/sc/SpongyCryptography.kt diff --git a/cryptography-sc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.kt b/cryptography-sc/src/test/kotlin/ch/dissem/bitmessage/security/CryptographyTest.kt similarity index 100% rename from cryptography-sc/src/test/java/ch/dissem/bitmessage/security/CryptographyTest.kt rename to cryptography-sc/src/test/kotlin/ch/dissem/bitmessage/security/CryptographyTest.kt diff --git a/networking/build.gradle b/networking/build.gradle index b01eb40..6ec5920 100644 --- a/networking/build.gradle +++ b/networking/build.gradle @@ -14,7 +14,7 @@ dependencies { compile project(':core') testCompile 'junit:junit:4.12' testCompile 'org.slf4j:slf4j-simple:1.7.25' - testCompile 'org.mockito:mockito-core:2.7.21' + testCompile 'com.nhaarman:mockito-kotlin:1.4.0' testCompile project(path: ':core', configuration: 'testArtifacts') testCompile project(':cryptography-bc') } diff --git a/networking/src/main/java/ch/dissem/bitmessage/networking/AbstractConnection.java b/networking/src/main/java/ch/dissem/bitmessage/networking/AbstractConnection.java deleted file mode 100644 index 8bcd2b0..0000000 --- a/networking/src/main/java/ch/dissem/bitmessage/networking/AbstractConnection.java +++ /dev/null @@ -1,343 +0,0 @@ -/* - * Copyright 2016 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.networking; - -import ch.dissem.bitmessage.BitmessageContext; -import ch.dissem.bitmessage.InternalContext; -import ch.dissem.bitmessage.entity.*; -import ch.dissem.bitmessage.entity.valueobject.InventoryVector; -import ch.dissem.bitmessage.entity.valueobject.NetworkAddress; -import ch.dissem.bitmessage.exception.InsufficientProofOfWorkException; -import ch.dissem.bitmessage.exception.NodeException; -import ch.dissem.bitmessage.ports.NetworkHandler; -import ch.dissem.bitmessage.utils.UnixTime; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentLinkedDeque; - -import static ch.dissem.bitmessage.InternalContext.NETWORK_EXTRA_BYTES; -import static ch.dissem.bitmessage.InternalContext.NETWORK_NONCE_TRIALS_PER_BYTE; -import static ch.dissem.bitmessage.networking.AbstractConnection.Mode.SERVER; -import static ch.dissem.bitmessage.networking.AbstractConnection.Mode.SYNC; -import static ch.dissem.bitmessage.networking.AbstractConnection.State.*; -import static ch.dissem.bitmessage.utils.Singleton.cryptography; -import static ch.dissem.bitmessage.utils.UnixTime.MINUTE; - -/** - * Contains everything used by both the old streams-oriented NetworkHandler and the new NioNetworkHandler, - * respectively their connection objects. - */ -public abstract class AbstractConnection { - private static final Logger LOG = LoggerFactory.getLogger(AbstractConnection.class); - protected final InternalContext ctx; - protected final Mode mode; - protected final NetworkAddress host; - protected final NetworkAddress node; - protected final NetworkHandler.MessageListener listener; - protected final Map<InventoryVector, Long> ivCache; - protected final Deque<MessagePayload> sendingQueue; - protected final Map<InventoryVector, Long> commonRequestedObjects; - protected final Set<InventoryVector> requestedObjects; - - protected volatile State state; - protected long lastObjectTime; - - private final long syncTimeout; - private long syncReadTimeout = Long.MAX_VALUE; - - protected long peerNonce; - protected int version; - protected long[] streams; - private boolean verackSent; - private boolean verackReceived; - - public AbstractConnection(InternalContext context, Mode mode, - NetworkAddress node, - Map<InventoryVector, Long> commonRequestedObjects, - long syncTimeout) { - this.ctx = context; - this.mode = mode; - this.host = new NetworkAddress.Builder().ipv6(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0).port(0).build(); - this.node = node; - this.listener = context.getNetworkListener(); - this.syncTimeout = (syncTimeout > 0 ? UnixTime.now() + syncTimeout : 0); - this.requestedObjects = Collections.newSetFromMap(new ConcurrentHashMap<InventoryVector, Boolean>(10_000)); - this.ivCache = new ConcurrentHashMap<>(); - this.sendingQueue = new ConcurrentLinkedDeque<>(); - this.state = CONNECTING; - this.commonRequestedObjects = commonRequestedObjects; - } - - public Mode getMode() { - return mode; - } - - public NetworkAddress getNode() { - return node; - } - - public State getState() { - return state; - } - - public long[] getStreams() { - return streams; - } - - protected void handleMessage(MessagePayload payload) { - switch (state) { - case ACTIVE: - receiveMessage(payload); - break; - - case DISCONNECTED: - break; - - default: - handleCommand(payload); - break; - } - } - - private void receiveMessage(MessagePayload messagePayload) { - switch (messagePayload.getCommand()) { - case INV: - receiveMessage((Inv) messagePayload); - break; - case GETDATA: - receiveMessage((GetData) messagePayload); - break; - case OBJECT: - receiveMessage((ObjectMessage) messagePayload); - break; - case ADDR: - receiveMessage((Addr) messagePayload); - break; - case CUSTOM: - case VERACK: - case VERSION: - default: - throw new IllegalStateException("Unexpectedly received '" + messagePayload.getCommand() + "' command"); - } - } - - private void receiveMessage(Inv inv) { - int originalSize = inv.getInventory().size(); - updateIvCache(inv.getInventory()); - List<InventoryVector> missing = ctx.getInventory().getMissing(inv.getInventory(), streams); - missing.removeAll(commonRequestedObjects.keySet()); - LOG.trace("Received inventory with " + originalSize + " elements, of which are " - + missing.size() + " missing."); - send(new GetData(missing)); - } - - private void receiveMessage(GetData getData) { - for (InventoryVector iv : getData.getInventory()) { - ObjectMessage om = ctx.getInventory().getObject(iv); - if (om != null) sendingQueue.offer(om); - } - } - - private void receiveMessage(ObjectMessage objectMessage) { - requestedObjects.remove(objectMessage.getInventoryVector()); - if (ctx.getInventory().contains(objectMessage)) { - LOG.trace("Received object " + objectMessage.getInventoryVector() + " - already in inventory"); - return; - } - try { - listener.receive(objectMessage); - cryptography().checkProofOfWork(objectMessage, NETWORK_NONCE_TRIALS_PER_BYTE, NETWORK_EXTRA_BYTES); - ctx.getInventory().storeObject(objectMessage); - // offer object to some random nodes so it gets distributed throughout the network: - ctx.getNetworkHandler().offer(objectMessage.getInventoryVector()); - lastObjectTime = UnixTime.now(); - } catch (InsufficientProofOfWorkException e) { - LOG.warn(e.getMessage()); - // DebugUtils.saveToFile(objectMessage); // this line must not be committed active - } catch (IOException e) { - LOG.error("Stream " + objectMessage.getStream() + ", object type " + objectMessage.getType() + ": " + e.getMessage(), e); - } finally { - if (commonRequestedObjects.remove(objectMessage.getInventoryVector()) == null) { - LOG.debug("Received object that wasn't requested."); - } - } - } - - private void receiveMessage(Addr addr) { - LOG.trace("Received " + addr.getAddresses().size() + " addresses."); - ctx.getNodeRegistry().offerAddresses(addr.getAddresses()); - } - - private void updateIvCache(List<InventoryVector> inventory) { - cleanupIvCache(); - Long now = UnixTime.now(); - for (InventoryVector iv : inventory) { - ivCache.put(iv, now); - } - } - - public void offer(InventoryVector iv) { - sendingQueue.offer(new Inv(Collections.singletonList(iv))); - updateIvCache(Collections.singletonList(iv)); - } - - public boolean knowsOf(InventoryVector iv) { - return ivCache.containsKey(iv); - } - - public boolean requested(InventoryVector iv) { - return requestedObjects.contains(iv); - } - - private void cleanupIvCache() { - long fiveMinutesAgo = UnixTime.now() - 5 * MINUTE; - for (Map.Entry<InventoryVector, Long> entry : ivCache.entrySet()) { - if (entry.getValue() < fiveMinutesAgo) { - ivCache.remove(entry.getKey()); - } - } - } - - private void handleCommand(MessagePayload payload) { - switch (payload.getCommand()) { - case VERSION: - handleVersion((Version) payload); - break; - case VERACK: - if (verackSent) { - activateConnection(); - } - verackReceived = true; - break; - case CUSTOM: - MessagePayload response = ctx.getCustomCommandHandler().handle((CustomMessage) payload); - if (response == null) { - disconnect(); - } else { - send(response); - } - break; - default: - throw new NodeException("Command 'version' or 'verack' expected, but was '" - + payload.getCommand() + "'"); - } - } - - private void activateConnection() { - LOG.info("Successfully established connection with node " + node); - state = ACTIVE; - node.setTime(UnixTime.now()); - if (mode != SYNC) { - sendAddresses(); - ctx.getNodeRegistry().offerAddresses(Collections.singletonList(node)); - } - sendInventory(); - } - - private void sendAddresses() { - List<NetworkAddress> addresses = ctx.getNodeRegistry().getKnownAddresses(1000, streams); - sendingQueue.offer(new Addr(addresses)); - } - - private void sendInventory() { - List<InventoryVector> inventory = ctx.getInventory().getInventory(streams); - for (int i = 0; i < inventory.size(); i += 50000) { - sendingQueue.offer(new Inv(inventory.subList(i, Math.min(inventory.size(), i + 50000)))); - } - } - - private void handleVersion(Version version) { - if (version.getNonce() == ctx.getClientNonce()) { - LOG.info("Tried to connect to self, disconnecting."); - disconnect(); - } else if (version.getVersion() >= BitmessageContext.CURRENT_VERSION) { - this.peerNonce = version.getNonce(); - if (peerNonce == ctx.getClientNonce()) disconnect(); - - this.version = version.getVersion(); - this.streams = version.getStreams(); - verackSent = true; - send(new VerAck()); - if (mode == SERVER) { - send(new Version.Builder().defaults(ctx.getClientNonce()).addrFrom(host).addrRecv(node).build()); - } - if (verackReceived) { - activateConnection(); - } - } else { - LOG.info("Received unsupported version " + version.getVersion() + ", disconnecting."); - disconnect(); - } - } - - @SuppressWarnings("RedundantIfStatement") - protected boolean syncFinished(NetworkMessage msg) { - if (mode != SYNC) { - return false; - } - if (Thread.interrupted()) { - return true; - } - if (state != ACTIVE) { - return false; - } - if (syncTimeout < UnixTime.now()) { - LOG.info("Synchronization timed out"); - return true; - } - if (!sendingQueue.isEmpty()) { - syncReadTimeout = System.currentTimeMillis() + 1000; - return false; - } - if (msg == null) { - return syncReadTimeout < System.currentTimeMillis(); - } else { - syncReadTimeout = System.currentTimeMillis() + 1000; - return false; - } - } - - public void disconnect() { - state = DISCONNECTED; - - // Make sure objects that are still missing are requested from other nodes - ctx.getNetworkHandler().request(requestedObjects); - } - - protected abstract void send(MessagePayload payload); - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - AbstractConnection that = (AbstractConnection) o; - return Objects.equals(node, that.node); - } - - @Override - public int hashCode() { - return Objects.hash(node); - } - - public enum Mode {SERVER, CLIENT, SYNC} - - public enum State {CONNECTING, ACTIVE, DISCONNECTED} -} diff --git a/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java b/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java deleted file mode 100644 index 4dcf29c..0000000 --- a/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java +++ /dev/null @@ -1,225 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.networking; - -import ch.dissem.bitmessage.InternalContext; -import ch.dissem.bitmessage.entity.GetData; -import ch.dissem.bitmessage.entity.MessagePayload; -import ch.dissem.bitmessage.entity.NetworkMessage; -import ch.dissem.bitmessage.entity.Version; -import ch.dissem.bitmessage.entity.valueobject.InventoryVector; -import ch.dissem.bitmessage.entity.valueobject.NetworkAddress; -import ch.dissem.bitmessage.factory.Factory; -import ch.dissem.bitmessage.ports.NetworkHandler.MessageListener; -import ch.dissem.bitmessage.utils.UnixTime; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.Socket; -import java.net.SocketTimeoutException; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; - -import static ch.dissem.bitmessage.networking.AbstractConnection.Mode.CLIENT; -import static ch.dissem.bitmessage.networking.AbstractConnection.Mode.SYNC; -import static ch.dissem.bitmessage.networking.AbstractConnection.State.ACTIVE; -import static ch.dissem.bitmessage.networking.AbstractConnection.State.DISCONNECTED; -import static ch.dissem.bitmessage.utils.UnixTime.MINUTE; - -/** - * A connection to a specific node - */ -class Connection extends AbstractConnection { - public static final int READ_TIMEOUT = 2000; - private static final Logger LOG = LoggerFactory.getLogger(Connection.class); - private static final int CONNECT_TIMEOUT = 5000; - - private final long startTime; - private final Socket socket; - private final ReaderRunnable reader = new ReaderRunnable(); - private final WriterRunnable writer = new WriterRunnable(); - - private InputStream in; - private OutputStream out; - private boolean socketInitialized; - - public Connection(InternalContext context, Mode mode, Socket socket, - Map<InventoryVector, Long> requestedObjectsMap) throws IOException { - this(context, mode, socket, requestedObjectsMap, - new NetworkAddress.Builder().ip(socket.getInetAddress()).port(socket.getPort()).stream(1).build(), - 0); - } - - public Connection(InternalContext context, Mode mode, NetworkAddress node, - Map<InventoryVector, Long> requestedObjectsMap) { - this(context, mode, new Socket(), requestedObjectsMap, - node, 0); - } - - private Connection(InternalContext context, Mode mode, Socket socket, - Map<InventoryVector, Long> commonRequestedObjects, NetworkAddress node, long syncTimeout) { - super(context, mode, node, commonRequestedObjects, syncTimeout); - this.startTime = UnixTime.now(); - this.socket = socket; - } - - public static Connection sync(InternalContext ctx, InetAddress address, int port, MessageListener listener, - long timeoutInSeconds) throws IOException { - return new Connection(ctx, SYNC, new Socket(address, port), - new HashMap<InventoryVector, Long>(), - new NetworkAddress.Builder().ip(address).port(port).stream(1).build(), - timeoutInSeconds); - } - - public long getStartTime() { - return startTime; - } - - public Mode getMode() { - return mode; - } - - public State getState() { - return state; - } - - public NetworkAddress getNode() { - return node; - } - - @Override - protected void send(MessagePayload payload) { - try { - if (payload instanceof GetData) { - requestedObjects.addAll(((GetData) payload).getInventory()); - } - synchronized (this) { - new NetworkMessage(payload).write(out); - } - } catch (IOException e) { - LOG.error(e.getMessage(), e); - disconnect(); - } - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Connection that = (Connection) o; - return Objects.equals(node, that.node); - } - - @Override - public int hashCode() { - return Objects.hash(node); - } - - private synchronized void initSocket(Socket socket) throws IOException { - if (!socketInitialized) { - if (!socket.isConnected()) { - LOG.trace("Trying to connect to node " + node); - socket.connect(new InetSocketAddress(node.toInetAddress(), node.getPort()), CONNECT_TIMEOUT); - } - socket.setSoTimeout(READ_TIMEOUT); - in = socket.getInputStream(); - out = socket.getOutputStream(); - socketInitialized = true; - } - } - - public ReaderRunnable getReader() { - return reader; - } - - public WriterRunnable getWriter() { - return writer; - } - - public class ReaderRunnable implements Runnable { - @Override - public void run() { - try (Socket socket = Connection.this.socket) { - initSocket(socket); - if (mode == CLIENT || mode == SYNC) { - send(new Version.Builder().defaults(ctx.getClientNonce()).addrFrom(host).addrRecv(node).build()); - } - while (state != DISCONNECTED) { - if (mode != SYNC) { - if (state == ACTIVE && requestedObjects.isEmpty() && sendingQueue.isEmpty()) { - Thread.sleep(1000); - } else { - Thread.sleep(100); - } - } - receive(); - } - } catch (Exception e) { - LOG.trace("Reader disconnected from node " + node + ": " + e.getMessage()); - } finally { - disconnect(); - try { - socket.close(); - } catch (Exception e) { - LOG.debug(e.getMessage(), e); - } - } - } - - private void receive() throws InterruptedException { - try { - NetworkMessage msg = Factory.getNetworkMessage(version, in); - if (msg == null) - return; - handleMessage(msg.getPayload()); - if (socket.isClosed() || syncFinished(msg) || checkOpenRequests()) disconnect(); - } catch (SocketTimeoutException ignore) { - if (state == ACTIVE && syncFinished(null)) disconnect(); - } - } - - } - - private boolean checkOpenRequests() { - return !requestedObjects.isEmpty() && lastObjectTime > 0 && (UnixTime.now() - lastObjectTime) > 2 * MINUTE; - } - - public class WriterRunnable implements Runnable { - @Override - public void run() { - try (Socket socket = Connection.this.socket) { - initSocket(socket); - while (state != DISCONNECTED) { - if (sendingQueue.isEmpty()) { - Thread.sleep(1000); - } else { - send(sendingQueue.poll()); - } - } - } catch (IOException | InterruptedException e) { - LOG.trace("Writer disconnected from node " + node + ": " + e.getMessage()); - disconnect(); - } - } - } -} diff --git a/networking/src/main/java/ch/dissem/bitmessage/networking/ConnectionOrganizer.java b/networking/src/main/java/ch/dissem/bitmessage/networking/ConnectionOrganizer.java deleted file mode 100644 index 7e0c96e..0000000 --- a/networking/src/main/java/ch/dissem/bitmessage/networking/ConnectionOrganizer.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright 2016 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.networking; - -import ch.dissem.bitmessage.InternalContext; -import ch.dissem.bitmessage.entity.valueobject.NetworkAddress; -import ch.dissem.bitmessage.utils.UnixTime; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Iterator; -import java.util.List; - -import static ch.dissem.bitmessage.networking.AbstractConnection.Mode.CLIENT; -import static ch.dissem.bitmessage.constants.Network.NETWORK_MAGIC_NUMBER; - -/** - * @author Christian Basler - */ -@Deprecated -@SuppressWarnings("deprecation") -public class ConnectionOrganizer implements Runnable { - private static final Logger LOG = LoggerFactory.getLogger(ConnectionOrganizer.class); - - private final InternalContext ctx; - private final DefaultNetworkHandler networkHandler; - - private Connection initialConnection; - - public ConnectionOrganizer(InternalContext ctx, - DefaultNetworkHandler networkHandler) { - this.ctx = ctx; - this.networkHandler = networkHandler; - } - - @Override - public void run() { - try { - while (networkHandler.isRunning()) { - try { - int active = 0; - long now = UnixTime.now(); - - int diff = networkHandler.connections.size() - ctx.getConnectionLimit(); - if (diff > 0) { - for (Connection c : networkHandler.connections) { - c.disconnect(); - diff--; - if (diff == 0) break; - } - } - boolean forcedDisconnect = false; - for (Iterator<Connection> iterator = networkHandler.connections.iterator(); iterator.hasNext(); ) { - Connection c = iterator.next(); - // Just in case they were all created at the same time, don't disconnect - // all at once. - if (!forcedDisconnect && now - c.getStartTime() > ctx.getConnectionTTL()) { - c.disconnect(); - forcedDisconnect = true; - } - switch (c.getState()) { - case DISCONNECTED: - iterator.remove(); - break; - case ACTIVE: - active++; - break; - default: - // nothing to do - } - } - - if (active < NETWORK_MAGIC_NUMBER) { - List<NetworkAddress> addresses = ctx.getNodeRegistry().getKnownAddresses( - NETWORK_MAGIC_NUMBER - active, ctx.getStreams()); - boolean first = active == 0 && initialConnection == null; - for (NetworkAddress address : addresses) { - Connection c = new Connection(ctx, CLIENT, address, networkHandler.requestedObjects); - if (first) { - initialConnection = c; - first = false; - } - networkHandler.startConnection(c); - } - Thread.sleep(10000); - } else if (initialConnection == null) { - Thread.sleep(30000); - } else { - initialConnection.disconnect(); - initialConnection = null; - Thread.sleep(10000); - } - } catch (InterruptedException e) { - networkHandler.stop(); - } catch (Exception e) { - LOG.error("Error in connection manager. Ignored.", e); - } - } - } finally { - LOG.debug("Connection manager shutting down."); - networkHandler.stop(); - } - } -} diff --git a/networking/src/main/java/ch/dissem/bitmessage/networking/DefaultNetworkHandler.java b/networking/src/main/java/ch/dissem/bitmessage/networking/DefaultNetworkHandler.java deleted file mode 100644 index b3e51e8..0000000 --- a/networking/src/main/java/ch/dissem/bitmessage/networking/DefaultNetworkHandler.java +++ /dev/null @@ -1,249 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.networking; - -import ch.dissem.bitmessage.InternalContext; -import ch.dissem.bitmessage.InternalContext.ContextHolder; -import ch.dissem.bitmessage.entity.CustomMessage; -import ch.dissem.bitmessage.entity.GetData; -import ch.dissem.bitmessage.entity.NetworkMessage; -import ch.dissem.bitmessage.entity.valueobject.InventoryVector; -import ch.dissem.bitmessage.exception.ApplicationException; -import ch.dissem.bitmessage.exception.NodeException; -import ch.dissem.bitmessage.factory.Factory; -import ch.dissem.bitmessage.ports.NetworkHandler; -import ch.dissem.bitmessage.utils.Collections; -import ch.dissem.bitmessage.utils.Property; - -import java.io.IOException; -import java.net.InetAddress; -import java.net.Socket; -import java.util.*; -import java.util.concurrent.*; - -import static ch.dissem.bitmessage.constants.Network.NETWORK_MAGIC_NUMBER; -import static ch.dissem.bitmessage.networking.AbstractConnection.Mode.SERVER; -import static ch.dissem.bitmessage.networking.AbstractConnection.State.ACTIVE; -import static ch.dissem.bitmessage.utils.DebugUtils.inc; -import static ch.dissem.bitmessage.utils.ThreadFactoryBuilder.pool; - -/** - * Handles all the networky stuff. - * - * @deprecated use {@link ch.dissem.bitmessage.networking.nio.NioNetworkHandler NioNetworkHandler} instead. - */ -@Deprecated -public class DefaultNetworkHandler implements NetworkHandler, ContextHolder { - - final Collection<Connection> connections = new ConcurrentLinkedQueue<>(); - private final ExecutorService pool = Executors.newCachedThreadPool( - pool("network") - .lowPrio() - .daemon() - .build()); - private InternalContext ctx; - private ServerRunnable server; - private volatile boolean running; - - final Map<InventoryVector, Long> requestedObjects = new ConcurrentHashMap<>(50_000); - - @Override - public void setContext(InternalContext context) { - this.ctx = context; - } - - @Override - public Future<?> synchronize(InetAddress server, int port, long timeoutInSeconds) { - try { - Connection connection = Connection.sync(ctx, server, port, ctx.getNetworkListener(), timeoutInSeconds); - Future<?> reader = pool.submit(connection.getReader()); - pool.execute(connection.getWriter()); - return reader; - } catch (IOException e) { - throw new ApplicationException(e); - } - } - - @Override - public CustomMessage send(InetAddress server, int port, CustomMessage request) { - try (Socket socket = new Socket(server, port)) { - socket.setSoTimeout(Connection.READ_TIMEOUT); - new NetworkMessage(request).write(socket.getOutputStream()); - NetworkMessage networkMessage = Factory.getNetworkMessage(3, socket.getInputStream()); - if (networkMessage != null && networkMessage.getPayload() instanceof CustomMessage) { - return (CustomMessage) networkMessage.getPayload(); - } else { - if (networkMessage == null) { - throw new NodeException("No response from node " + server); - } else { - throw new NodeException("Unexpected response from node " + - server + ": " + networkMessage.getPayload().getCommand()); - } - } - } catch (IOException e) { - throw new NodeException(e.getMessage(), e); - } - } - - @Override - public void start() { - if (running) { - throw new IllegalStateException("Network already running - you need to stop first."); - } - try { - running = true; - connections.clear(); - server = new ServerRunnable(ctx, this); - pool.execute(server); - pool.execute(new ConnectionOrganizer(ctx, this)); - } catch (IOException e) { - throw new ApplicationException(e); - } - } - - @Override - public boolean isRunning() { - return running; - } - - @Override - public void stop() { - server.close(); - synchronized (connections) { - running = false; - for (Connection c : connections) { - c.disconnect(); - } - } - requestedObjects.clear(); - } - - void startConnection(Connection c) { - if (!running) return; - - synchronized (connections) { - if (!running) return; - - // prevent connecting twice to the same node - if (connections.contains(c)) { - return; - } - connections.add(c); - } - pool.execute(c.getReader()); - pool.execute(c.getWriter()); - } - - @Override - public void offer(final InventoryVector iv) { - List<Connection> target = new LinkedList<>(); - for (Connection connection : connections) { - if (connection.getState() == ACTIVE && !connection.knowsOf(iv)) { - target.add(connection); - } - } - List<Connection> randomSubset = Collections.selectRandom(NETWORK_MAGIC_NUMBER, target); - for (Connection connection : randomSubset) { - connection.offer(iv); - } - } - - @Override - public Property getNetworkStatus() { - TreeSet<Long> streams = new TreeSet<>(); - TreeMap<Long, Integer> incomingConnections = new TreeMap<>(); - TreeMap<Long, Integer> outgoingConnections = new TreeMap<>(); - - for (Connection connection : connections) { - if (connection.getState() == ACTIVE) { - for (long stream : connection.getStreams()) { - streams.add(stream); - if (connection.getMode() == SERVER) { - inc(incomingConnections, stream); - } else { - inc(outgoingConnections, stream); - } - } - } - } - Property[] streamProperties = new Property[streams.size()]; - int i = 0; - for (Long stream : streams) { - int incoming = incomingConnections.containsKey(stream) ? incomingConnections.get(stream) : 0; - int outgoing = outgoingConnections.containsKey(stream) ? outgoingConnections.get(stream) : 0; - streamProperties[i] = new Property("stream " + stream, - null, new Property("nodes", incoming + outgoing), - new Property("incoming", incoming), - new Property("outgoing", outgoing) - ); - i++; - } - return new Property("network", null, - new Property("connectionManager", running ? "running" : "stopped"), - new Property("connections", streamProperties), - new Property("requestedObjects", requestedObjects.size()) - ); - } - - @Override - public void request(Collection<InventoryVector> inventoryVectors) { - if (!running || inventoryVectors.isEmpty()) return; - - Map<Connection, List<InventoryVector>> distribution = new HashMap<>(); - for (Connection connection : connections) { - if (connection.getState() == ACTIVE) { - distribution.put(connection, new LinkedList<InventoryVector>()); - } - } - Iterator<InventoryVector> iterator = inventoryVectors.iterator(); - if (!iterator.hasNext()) { - return; - } - InventoryVector next = iterator.next(); - Connection previous = null; - do { - for (Connection connection : distribution.keySet()) { - if (connection == previous) { - next = iterator.next(); - } - if (connection.knowsOf(next)) { - List<InventoryVector> ivs = distribution.get(connection); - if (ivs.size() == GetData.MAX_INVENTORY_SIZE) { - connection.send(new GetData(ivs)); - ivs.clear(); - } - ivs.add(next); - iterator.remove(); - - if (iterator.hasNext()) { - next = iterator.next(); - previous = connection; - } else { - break; - } - } - } - } while (iterator.hasNext()); - - for (Connection connection : distribution.keySet()) { - List<InventoryVector> ivs = distribution.get(connection); - if (!ivs.isEmpty()) { - connection.send(new GetData(ivs)); - } - } - } -} diff --git a/networking/src/main/java/ch/dissem/bitmessage/networking/ServerRunnable.java b/networking/src/main/java/ch/dissem/bitmessage/networking/ServerRunnable.java deleted file mode 100644 index eca4d5d..0000000 --- a/networking/src/main/java/ch/dissem/bitmessage/networking/ServerRunnable.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2016 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.networking; - -import ch.dissem.bitmessage.InternalContext; -import ch.dissem.bitmessage.ports.NetworkHandler; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.Closeable; -import java.io.IOException; -import java.net.ServerSocket; -import java.net.Socket; - -import static ch.dissem.bitmessage.networking.AbstractConnection.Mode.SERVER; - -/** - * @author Christian Basler - */ -@Deprecated -public class ServerRunnable implements Runnable, Closeable { - private static final Logger LOG = LoggerFactory.getLogger(ServerRunnable.class); - private final InternalContext ctx; - private final ServerSocket serverSocket; - private final DefaultNetworkHandler networkHandler; - - public ServerRunnable(InternalContext ctx, DefaultNetworkHandler networkHandler) throws IOException { - this.ctx = ctx; - this.networkHandler = networkHandler; - this.serverSocket = new ServerSocket(ctx.getPort()); - } - - @Override - public void run() { - while (!serverSocket.isClosed()) { - try { - Socket socket = serverSocket.accept(); - socket.setSoTimeout(Connection.READ_TIMEOUT); - networkHandler.startConnection(new Connection(ctx, SERVER, socket, networkHandler.requestedObjects)); - } catch (IOException e) { - LOG.debug(e.getMessage(), e); - } - } - } - - @Override - public void close() { - try { - serverSocket.close(); - } catch (IOException e) { - LOG.debug(e.getMessage(), e); - } - } -} diff --git a/networking/src/main/java/ch/dissem/bitmessage/networking/nio/ConnectionInfo.java b/networking/src/main/java/ch/dissem/bitmessage/networking/nio/ConnectionInfo.java deleted file mode 100644 index 012b43f..0000000 --- a/networking/src/main/java/ch/dissem/bitmessage/networking/nio/ConnectionInfo.java +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright 2016 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.networking.nio; - -import ch.dissem.bitmessage.InternalContext; -import ch.dissem.bitmessage.entity.GetData; -import ch.dissem.bitmessage.entity.MessagePayload; -import ch.dissem.bitmessage.entity.NetworkMessage; -import ch.dissem.bitmessage.entity.Version; -import ch.dissem.bitmessage.entity.valueobject.InventoryVector; -import ch.dissem.bitmessage.entity.valueobject.NetworkAddress; -import ch.dissem.bitmessage.exception.NodeException; -import ch.dissem.bitmessage.factory.V3MessageReader; -import ch.dissem.bitmessage.networking.AbstractConnection; -import ch.dissem.bitmessage.utils.UnixTime; - -import java.nio.ByteBuffer; -import java.util.*; - -import static ch.dissem.bitmessage.networking.AbstractConnection.Mode.CLIENT; -import static ch.dissem.bitmessage.networking.AbstractConnection.Mode.SYNC; - -/** - * Represents the current state of a connection. - */ -public class ConnectionInfo extends AbstractConnection { - private final ByteBuffer headerOut = ByteBuffer.allocate(24); - private ByteBuffer payloadOut; - private V3MessageReader reader = new V3MessageReader(); - private boolean syncFinished; - private long lastUpdate = System.currentTimeMillis(); - - public ConnectionInfo(InternalContext context, Mode mode, NetworkAddress node, - Map<InventoryVector, Long> commonRequestedObjects, long syncTimeout) { - super(context, mode, node, commonRequestedObjects, syncTimeout); - headerOut.flip(); - if (mode == CLIENT || mode == SYNC) { - send(new Version.Builder().defaults(ctx.getClientNonce()).addrFrom(host).addrRecv(node).build()); - } - } - - public State getState() { - return state; - } - - public boolean knowsOf(InventoryVector iv) { - return ivCache.containsKey(iv); - } - - public Queue<MessagePayload> getSendingQueue() { - return sendingQueue; - } - - public ByteBuffer getInBuffer() { - if (reader == null) { - throw new NodeException("Node is disconnected"); - } - return reader.getActiveBuffer(); - } - - public void updateWriter() { - if (!headerOut.hasRemaining() && !sendingQueue.isEmpty()) { - headerOut.clear(); - MessagePayload payload = sendingQueue.poll(); - payloadOut = new NetworkMessage(payload).writeHeaderAndGetPayloadBuffer(headerOut); - headerOut.flip(); - lastUpdate = System.currentTimeMillis(); - } - } - - public ByteBuffer[] getOutBuffers() { - return new ByteBuffer[]{headerOut, payloadOut}; - } - - public void cleanupBuffers() { - if (payloadOut != null && !payloadOut.hasRemaining()) { - payloadOut = null; - } - } - - public void updateReader() { - reader.update(); - if (!reader.getMessages().isEmpty()) { - Iterator<NetworkMessage> iterator = reader.getMessages().iterator(); - NetworkMessage msg = null; - while (iterator.hasNext()) { - msg = iterator.next(); - handleMessage(msg.getPayload()); - iterator.remove(); - } - syncFinished = syncFinished(msg); - } - lastUpdate = System.currentTimeMillis(); - } - - public void updateSyncStatus() { - if (!syncFinished) { - syncFinished = (reader == null || reader.getMessages().isEmpty()) && syncFinished(null); - } - } - - public boolean isExpired() { - switch (state) { - case CONNECTING: - // the TCP timeout starts out at 20 seconds - return lastUpdate < System.currentTimeMillis() - 20_000; - case ACTIVE: - // after verack messages are exchanged, the timeout is raised to 10 minutes - return lastUpdate < System.currentTimeMillis() - 600_000; - case DISCONNECTED: - return true; - default: - throw new IllegalStateException("Unknown state: " + state); - } - } - - @Override - public void disconnect() { - super.disconnect(); - if (reader != null) { - reader.cleanup(); - reader = null; - } - payloadOut = null; - } - - public boolean isSyncFinished() { - return syncFinished; - } - - @Override - protected void send(MessagePayload payload) { - sendingQueue.add(payload); - if (payload instanceof GetData) { - Long now = UnixTime.now(); - List<InventoryVector> inventory = ((GetData) payload).getInventory(); - requestedObjects.addAll(inventory); - for (InventoryVector iv : inventory) { - commonRequestedObjects.put(iv, now); - } - } - } - - public boolean isWritePending() { - return !sendingQueue.isEmpty() - || headerOut != null && headerOut.hasRemaining() - || payloadOut != null && payloadOut.hasRemaining(); - } -} diff --git a/networking/src/main/java/ch/dissem/bitmessage/networking/nio/NioNetworkHandler.java b/networking/src/main/java/ch/dissem/bitmessage/networking/nio/NioNetworkHandler.java deleted file mode 100644 index 40e79d0..0000000 --- a/networking/src/main/java/ch/dissem/bitmessage/networking/nio/NioNetworkHandler.java +++ /dev/null @@ -1,529 +0,0 @@ -/* - * Copyright 2016 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.networking.nio; - -import ch.dissem.bitmessage.InternalContext; -import ch.dissem.bitmessage.entity.CustomMessage; -import ch.dissem.bitmessage.entity.GetData; -import ch.dissem.bitmessage.entity.NetworkMessage; -import ch.dissem.bitmessage.entity.valueobject.InventoryVector; -import ch.dissem.bitmessage.entity.valueobject.NetworkAddress; -import ch.dissem.bitmessage.exception.ApplicationException; -import ch.dissem.bitmessage.exception.NodeException; -import ch.dissem.bitmessage.factory.V3MessageReader; -import ch.dissem.bitmessage.ports.NetworkHandler; -import ch.dissem.bitmessage.utils.DebugUtils; -import ch.dissem.bitmessage.utils.Property; -import org.jetbrains.annotations.NotNull; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.NoRouteToHostException; -import java.nio.ByteBuffer; -import java.nio.channels.*; -import java.util.*; -import java.util.concurrent.*; - -import static ch.dissem.bitmessage.constants.Network.HEADER_SIZE; -import static ch.dissem.bitmessage.constants.Network.NETWORK_MAGIC_NUMBER; -import static ch.dissem.bitmessage.networking.AbstractConnection.Mode.CLIENT; -import static ch.dissem.bitmessage.networking.AbstractConnection.Mode.SERVER; -import static ch.dissem.bitmessage.networking.AbstractConnection.Mode.SYNC; -import static ch.dissem.bitmessage.networking.AbstractConnection.State.ACTIVE; -import static ch.dissem.bitmessage.networking.AbstractConnection.State.DISCONNECTED; -import static ch.dissem.bitmessage.utils.Collections.selectRandom; -import static ch.dissem.bitmessage.utils.ThreadFactoryBuilder.pool; -import static java.nio.channels.SelectionKey.*; - -/** - * Network handler using java.nio, resulting in less threads. - */ -public class NioNetworkHandler implements NetworkHandler, InternalContext.ContextHolder { - private static final Logger LOG = LoggerFactory.getLogger(NioNetworkHandler.class); - private static final long REQUESTED_OBJECTS_MAX_TIME = 2 * 60_000; // 2 minutes - private static final Long DELAYED = Long.MIN_VALUE; - - private final ExecutorService threadPool = Executors.newCachedThreadPool( - pool("network") - .lowPrio() - .daemon() - .build()); - - private InternalContext ctx; - private Selector selector; - private ServerSocketChannel serverChannel; - private Queue<NetworkAddress> connectionQueue = new ConcurrentLinkedQueue<>(); - private Map<ConnectionInfo, SelectionKey> connections = new ConcurrentHashMap<>(); - private final Map<InventoryVector, Long> requestedObjects = new ConcurrentHashMap<>(10_000); - - private Thread starter; - - @NotNull - @Override - public Future<Void> synchronize(@NotNull final InetAddress server, final int port, final long timeoutInSeconds) { - return threadPool.submit(new Callable<Void>() { - @Override - public Void call() throws Exception { - try (SocketChannel channel = SocketChannel.open(new InetSocketAddress(server, port))) { - channel.configureBlocking(false); - ConnectionInfo connection = new ConnectionInfo(ctx, SYNC, - new NetworkAddress.Builder().ip(server).port(port).stream(1).build(), - new HashMap<InventoryVector, Long>(), timeoutInSeconds); - while (channel.isConnected() && !connection.isSyncFinished()) { - write(channel, connection); - read(channel, connection); - Thread.sleep(10); - } - LOG.info("Synchronization finished"); - } - return null; - } - }); - } - - @NotNull - @Override - public CustomMessage send(@NotNull InetAddress server, int port, @NotNull CustomMessage request) { - try (SocketChannel channel = SocketChannel.open(new InetSocketAddress(server, port))) { - channel.configureBlocking(true); - ByteBuffer headerBuffer = ByteBuffer.allocate(HEADER_SIZE); - ByteBuffer payloadBuffer = new NetworkMessage(request).writeHeaderAndGetPayloadBuffer(headerBuffer); - headerBuffer.flip(); - while (headerBuffer.hasRemaining()) { - channel.write(headerBuffer); - } - while (payloadBuffer.hasRemaining()) { - channel.write(payloadBuffer); - } - - V3MessageReader reader = new V3MessageReader(); - while (channel.isConnected() && reader.getMessages().isEmpty()) { - if (channel.read(reader.getActiveBuffer()) > 0) { - reader.update(); - } else { - throw new NodeException("No response from node " + server); - } - } - NetworkMessage networkMessage; - if (reader.getMessages().isEmpty()) { - throw new NodeException("No response from node " + server); - } else { - networkMessage = reader.getMessages().get(0); - } - - if (networkMessage != null && networkMessage.getPayload() instanceof CustomMessage) { - return (CustomMessage) networkMessage.getPayload(); - } else { - if (networkMessage == null) { - throw new NodeException("Empty response from node " + server); - } else { - throw new NodeException("Unexpected response from node " + server + ": " - + networkMessage.getPayload().getClass()); - } - } - } catch (IOException e) { - throw new ApplicationException(e); - } - } - - @Override - public void start() { - if (selector != null && selector.isOpen()) { - throw new IllegalStateException("Network already running - you need to stop first."); - } - try { - selector = Selector.open(); - } catch (IOException e) { - throw new ApplicationException(e); - } - requestedObjects.clear(); - - starter = thread("connection manager", new Runnable() { - @Override - public void run() { - while (selector.isOpen()) { - int missing = NETWORK_MAGIC_NUMBER; - for (ConnectionInfo connectionInfo : connections.keySet()) { - if (connectionInfo.getState() == ACTIVE) { - missing--; - if (missing == 0) break; - } - } - if (missing > 0) { - List<NetworkAddress> addresses = ctx.getNodeRegistry().getKnownAddresses(100, ctx.getStreams()); - addresses = selectRandom(missing, addresses); - for (NetworkAddress address : addresses) { - if (!isConnectedTo(address)) { - connectionQueue.offer(address); - } - } - } - - Iterator<Map.Entry<ConnectionInfo, SelectionKey>> it = connections.entrySet().iterator(); - while (it.hasNext()) { - Map.Entry<ConnectionInfo, SelectionKey> e = it.next(); - if (!e.getValue().isValid() || e.getKey().isExpired()) { - try { - e.getValue().channel().close(); - } catch (Exception ignore) { - } - e.getValue().cancel(); - e.getValue().attach(null); - e.getKey().disconnect(); - it.remove(); - } - } - - // The list 'requested objects' helps to prevent downloading an object - // twice. From time to time there is an error though, and an object is - // never downloaded. To prevent a large list of failed objects and give - // them a chance to get downloaded again, we will attempt to download an - // object from another node after some time out. - long timedOut = System.currentTimeMillis() - REQUESTED_OBJECTS_MAX_TIME; - List<InventoryVector> delayed = new LinkedList<>(); - Iterator<Map.Entry<InventoryVector, Long>> iterator = requestedObjects.entrySet().iterator(); - while (iterator.hasNext()) { - Map.Entry<InventoryVector, Long> e = iterator.next(); - //noinspection NumberEquality - if (e.getValue() == DELAYED) { - iterator.remove(); - } else if (e.getValue() < timedOut) { - delayed.add(e.getKey()); - e.setValue(DELAYED); - } - } - request(delayed); - - try { - Thread.sleep(30_000); - } catch (InterruptedException e) { - return; - } - } - } - }); - - thread("selector worker", new Runnable() { - @Override - public void run() { - try { - serverChannel = ServerSocketChannel.open(); - serverChannel.configureBlocking(false); - serverChannel.socket().bind(new InetSocketAddress(ctx.getPort())); - serverChannel.register(selector, OP_ACCEPT, null); - - while (selector.isOpen()) { - selector.select(1000); - Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator(); - while (keyIterator.hasNext()) { - SelectionKey key = keyIterator.next(); - keyIterator.remove(); - if (key.attachment() == null) { - try { - if (key.isAcceptable()) { - // handle accept - try { - SocketChannel accepted = ((ServerSocketChannel) key.channel()).accept(); - accepted.configureBlocking(false); - ConnectionInfo connection = new ConnectionInfo(ctx, SERVER, - new NetworkAddress.Builder() - .ip(accepted.socket().getInetAddress()) - .port(accepted.socket().getPort()) - .stream(1) - .build(), - requestedObjects, 0 - ); - connections.put( - connection, - accepted.register(selector, OP_READ | OP_WRITE, connection) - ); - } catch (AsynchronousCloseException e) { - LOG.trace(e.getMessage()); - } catch (IOException e) { - LOG.error(e.getMessage(), e); - } - } - } catch (CancelledKeyException e) { - LOG.debug(e.getMessage(), e); - } - } else { - // handle read/write - SocketChannel channel = (SocketChannel) key.channel(); - ConnectionInfo connection = (ConnectionInfo) key.attachment(); - try { - if (key.isConnectable()) { - if (!channel.finishConnect()) { - continue; - } - } - if (key.isWritable()) { - write(channel, connection); - } - if (key.isReadable()) { - read(channel, connection); - } - if (connection.getState() == DISCONNECTED) { - key.interestOps(0); - channel.close(); - } else if (connection.isWritePending()) { - key.interestOps(OP_READ | OP_WRITE); - } else { - key.interestOps(OP_READ); - } - } catch (CancelledKeyException | NodeException | IOException e) { - connection.disconnect(); - } - } - } - // set interest ops - for (Map.Entry<ConnectionInfo, SelectionKey> e : connections.entrySet()) { - try { - if (e.getValue().isValid() - && (e.getValue().interestOps() & OP_WRITE) == 0 - && (e.getValue().interestOps() & OP_CONNECT) == 0 - && !e.getKey().getSendingQueue().isEmpty()) { - e.getValue().interestOps(OP_READ | OP_WRITE); - } - } catch (CancelledKeyException x) { - e.getKey().disconnect(); - } - } - // start new connections - if (!connectionQueue.isEmpty()) { - NetworkAddress address = connectionQueue.poll(); - try { - SocketChannel channel = SocketChannel.open(); - channel.configureBlocking(false); - channel.connect(new InetSocketAddress(address.toInetAddress(), address.getPort())); - ConnectionInfo connection = new ConnectionInfo(ctx, CLIENT, - address, - requestedObjects, 0 - ); - connections.put( - connection, - channel.register(selector, OP_CONNECT, connection) - ); - } catch (NoRouteToHostException ignore) { - // We'll try to connect to many offline nodes, so - // this is expected to happen quite a lot. - } catch (AsynchronousCloseException e) { - // The exception is expected if the network is being - // shut down, as we actually do asynchronously close - // the connections. - if (isRunning()) { - LOG.error(e.getMessage(), e); - } - } catch (IOException e) { - LOG.error(e.getMessage(), e); - } - } - } - selector.close(); - } catch (ClosedSelectorException ignore) { - } catch (IOException e) { - throw new ApplicationException(e); - } - } - }); - } - - private static void write(SocketChannel channel, ConnectionInfo connection) - throws IOException { - writeBuffer(connection.getOutBuffers(), channel); - - connection.updateWriter(); - - writeBuffer(connection.getOutBuffers(), channel); - connection.cleanupBuffers(); - } - - private static void writeBuffer(ByteBuffer[] buffers, SocketChannel channel) throws IOException { - if (buffers[1] == null) { - if (buffers[0].hasRemaining()) { - channel.write(buffers[0]); - } - } else if (buffers[1].hasRemaining() || buffers[0].hasRemaining()) { - channel.write(buffers); - } - } - - private static void read(SocketChannel channel, ConnectionInfo connection) throws IOException { - if (channel.read(connection.getInBuffer()) > 0) { - connection.updateReader(); - } - connection.updateSyncStatus(); - } - - private Thread thread(String threadName, Runnable runnable) { - Thread thread = new Thread(runnable, threadName); - thread.setDaemon(true); - thread.setPriority(Thread.MIN_PRIORITY); - thread.start(); - return thread; - } - - @Override - public void stop() { - try { - serverChannel.socket().close(); - selector.close(); - for (SelectionKey selectionKey : connections.values()) { - selectionKey.channel().close(); - } - } catch (IOException e) { - throw new ApplicationException(e); - } - } - - @Override - public void offer(@NotNull InventoryVector iv) { - List<ConnectionInfo> target = new LinkedList<>(); - for (ConnectionInfo connection : connections.keySet()) { - if (connection.getState() == ACTIVE && !connection.knowsOf(iv)) { - target.add(connection); - } - } - List<ConnectionInfo> randomSubset = selectRandom(NETWORK_MAGIC_NUMBER, target); - for (ConnectionInfo connection : randomSubset) { - connection.offer(iv); - } - } - - @Override - public void request(@NotNull Collection<InventoryVector> inventoryVectors) { - if (!isRunning()) { - requestedObjects.clear(); - return; - } - Iterator<InventoryVector> iterator = inventoryVectors.iterator(); - if (!iterator.hasNext()) { - return; - } - - Map<ConnectionInfo, List<InventoryVector>> distribution = new HashMap<>(); - for (ConnectionInfo connection : connections.keySet()) { - if (connection.getState() == ACTIVE) { - distribution.put(connection, new LinkedList<InventoryVector>()); - } - } - if (distribution.isEmpty()) { - return; - } - InventoryVector next = iterator.next(); - ConnectionInfo previous = null; - do { - for (ConnectionInfo connection : distribution.keySet()) { - if (connection == previous || previous == null) { - if (iterator.hasNext()) { - previous = connection; - next = iterator.next(); - } else { - break; - } - } - if (connection.knowsOf(next) && !connection.requested(next)) { - List<InventoryVector> ivs = distribution.get(connection); - if (ivs.size() == GetData.MAX_INVENTORY_SIZE) { - connection.send(new GetData(ivs)); - ivs.clear(); - } - ivs.add(next); - iterator.remove(); - - if (iterator.hasNext()) { - next = iterator.next(); - previous = connection; - } else { - break; - } - } - } - } while (iterator.hasNext()); - - // remove objects nobody knows of - for (InventoryVector iv : inventoryVectors) { - requestedObjects.remove(iv); - } - - for (ConnectionInfo connection : distribution.keySet()) { - List<InventoryVector> ivs = distribution.get(connection); - if (!ivs.isEmpty()) { - connection.send(new GetData(ivs)); - } - } - } - - @NotNull - @Override - public Property getNetworkStatus() { - TreeSet<Long> streams = new TreeSet<>(); - TreeMap<Long, Integer> incomingConnections = new TreeMap<>(); - TreeMap<Long, Integer> outgoingConnections = new TreeMap<>(); - - for (ConnectionInfo connection : connections.keySet()) { - if (connection.getState() == ACTIVE) { - for (long stream : connection.getStreams()) { - streams.add(stream); - if (connection.getMode() == SERVER) { - DebugUtils.inc(incomingConnections, stream); - } else { - DebugUtils.inc(outgoingConnections, stream); - } - } - } - } - Property[] streamProperties = new Property[streams.size()]; - int i = 0; - for (Long stream : streams) { - int incoming = incomingConnections.containsKey(stream) ? incomingConnections.get(stream) : 0; - int outgoing = outgoingConnections.containsKey(stream) ? outgoingConnections.get(stream) : 0; - streamProperties[i] = new Property("stream " + stream, - null, new Property("nodes", incoming + outgoing), - new Property("incoming", incoming), - new Property("outgoing", outgoing) - ); - i++; - } - return new Property("network", null, - new Property("connectionManager", isRunning() ? "running" : "stopped"), - new Property("connections", streamProperties), - new Property("requestedObjects", requestedObjects.size()) - ); - } - - private boolean isConnectedTo(NetworkAddress address) { - for (ConnectionInfo c : connections.keySet()) { - if (c.getNode().equals(address)) { - return true; - } - } - return false; - } - - @Override - public boolean isRunning() { - return selector != null && selector.isOpen() && starter.isAlive(); - } - - @Override - public void setContext(@NotNull InternalContext context) { - this.ctx = context; - } -} diff --git a/networking/src/main/kotlin/ch/dissem/bitmessage/networking/nio/Connection.kt b/networking/src/main/kotlin/ch/dissem/bitmessage/networking/nio/Connection.kt new file mode 100644 index 0000000..03552b6 --- /dev/null +++ b/networking/src/main/kotlin/ch/dissem/bitmessage/networking/nio/Connection.kt @@ -0,0 +1,202 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.networking.nio + +import ch.dissem.bitmessage.InternalContext +import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_EXTRA_BYTES +import ch.dissem.bitmessage.InternalContext.Companion.NETWORK_NONCE_TRIALS_PER_BYTE +import ch.dissem.bitmessage.entity.* +import ch.dissem.bitmessage.entity.valueobject.InventoryVector +import ch.dissem.bitmessage.entity.valueobject.NetworkAddress +import ch.dissem.bitmessage.exception.InsufficientProofOfWorkException +import ch.dissem.bitmessage.ports.NetworkHandler +import ch.dissem.bitmessage.utils.Singleton.cryptography +import ch.dissem.bitmessage.utils.UnixTime +import ch.dissem.bitmessage.utils.UnixTime.MINUTE +import org.slf4j.LoggerFactory +import java.io.IOException +import java.util.* +import java.util.concurrent.ConcurrentHashMap + +/** + * Contains everything used by both the old streams-oriented NetworkHandler and the new NioNetworkHandler, + * respectively their connection objects. + */ +class Connection( + private val ctx: InternalContext, + val mode: Mode, + val node: NetworkAddress, + private val commonRequestedObjects: MutableMap<InventoryVector, Long>, + syncTimeout: Long +) { + private val requestedObjects: MutableSet<InventoryVector> = Collections.newSetFromMap(ConcurrentHashMap<InventoryVector, Boolean>(10000)) + + internal val io = ConnectionIO(mode, syncTimeout, commonRequestedObjects, requestedObjects, { state }, this::handleMessage) + private var initializer: NetworkConnectionInitializer? = NetworkConnectionInitializer(ctx, node, mode, io::send) { s -> + state = State.ACTIVE + streams = s + initializer = null + } + + private val listener: NetworkHandler.MessageListener = ctx.networkListener + private val ivCache: MutableMap<InventoryVector, Long> = ConcurrentHashMap() + + private var lastObjectTime: Long = 0 + + lateinit var streams: LongArray + protected set + + @Volatile var state = State.CONNECTING + private set + + val isSyncFinished + get() = io.isSyncFinished + + val nothingToSend + get() = io.sendingQueue.isEmpty() + + init { + initializer!!.start() + } + + fun send(payload: MessagePayload) = io.send(payload) + + protected fun handleMessage(payload: MessagePayload) { + when (state) { + State.CONNECTING -> initializer!!.handleCommand(payload) + State.ACTIVE -> receiveMessage(payload) + State.DISCONNECTED -> disconnect() + } + } + + private fun receiveMessage(messagePayload: MessagePayload) { + when (messagePayload.command) { + MessagePayload.Command.INV -> receiveMessage(messagePayload as Inv) + MessagePayload.Command.GETDATA -> receiveMessage(messagePayload as GetData) + MessagePayload.Command.OBJECT -> receiveMessage(messagePayload as ObjectMessage) + MessagePayload.Command.ADDR -> receiveMessage(messagePayload as Addr) + else -> throw IllegalStateException("Unexpectedly received '${messagePayload.command}' command") + } + } + + private fun receiveMessage(inv: Inv) { + val originalSize = inv.inventory.size + updateIvCache(inv.inventory) + val missing = ctx.inventory.getMissing(inv.inventory, *streams) + LOG.trace("Received inventory with $originalSize elements, of which are ${missing.size} missing.") + io.send(GetData(missing - commonRequestedObjects.keys)) + } + + private fun receiveMessage(getData: GetData) { + getData.inventory.forEach { iv -> ctx.inventory.getObject(iv)?.let { obj -> io.send(obj) } } + } + + private fun receiveMessage(objectMessage: ObjectMessage) { + requestedObjects.remove(objectMessage.inventoryVector) + if (ctx.inventory.contains(objectMessage)) { + LOG.trace("Received object " + objectMessage.inventoryVector + " - already in inventory") + return + } + try { + listener.receive(objectMessage) + cryptography().checkProofOfWork(objectMessage, NETWORK_NONCE_TRIALS_PER_BYTE, NETWORK_EXTRA_BYTES) + ctx.inventory.storeObject(objectMessage) + // offer object to some random nodes so it gets distributed throughout the network: + ctx.networkHandler.offer(objectMessage.inventoryVector) + lastObjectTime = UnixTime.now + } catch (e: InsufficientProofOfWorkException) { + LOG.warn(e.message) + // DebugUtils.saveToFile(objectMessage); // this line must not be committed active + } catch (e: IOException) { + LOG.error("Stream " + objectMessage.stream + ", object type " + objectMessage.type + ": " + e.message, e) + } finally { + if (commonRequestedObjects.remove(objectMessage.inventoryVector) == null) { + LOG.debug("Received object that wasn't requested.") + } + } + } + + private fun receiveMessage(addr: Addr) { + LOG.trace("Received " + addr.addresses.size + " addresses.") + ctx.nodeRegistry.offerAddresses(addr.addresses) + } + + private fun updateIvCache(inventory: List<InventoryVector>) { + cleanupIvCache() + val now = UnixTime.now + for (iv in inventory) { + ivCache.put(iv, now) + } + } + + fun offer(iv: InventoryVector) { + io.send(Inv(listOf(iv))) + updateIvCache(listOf(iv)) + } + + fun knowsOf(iv: InventoryVector): Boolean { + return ivCache.containsKey(iv) + } + + fun requested(iv: InventoryVector): Boolean { + return requestedObjects.contains(iv) + } + + private fun cleanupIvCache() { + val fiveMinutesAgo = UnixTime.now - 5 * MINUTE + for ((key, value) in ivCache) { + if (value < fiveMinutesAgo) { + ivCache.remove(key) + } + } + } + + // the TCP timeout starts out at 20 seconds + // after verack messages are exchanged, the timeout is raised to 10 minutes + fun isExpired(): Boolean = when (state) { + State.CONNECTING -> io.lastUpdate < System.currentTimeMillis() - 20000 + State.ACTIVE -> io.lastUpdate < System.currentTimeMillis() - 600000 + State.DISCONNECTED -> true + } + + fun disconnect() { + state = State.DISCONNECTED + io.disconnect() + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is Connection) return false + return node == other.node + } + + override fun hashCode(): Int { + return Objects.hash(node) + } + + enum class State { + CONNECTING, ACTIVE, DISCONNECTED + } + + enum class Mode { + SERVER, CLIENT, SYNC + } + + companion object { + private val LOG = LoggerFactory.getLogger(Connection::class.java) + } +} diff --git a/networking/src/main/kotlin/ch/dissem/bitmessage/networking/nio/ConnectionIO.kt b/networking/src/main/kotlin/ch/dissem/bitmessage/networking/nio/ConnectionIO.kt new file mode 100644 index 0000000..ec926e3 --- /dev/null +++ b/networking/src/main/kotlin/ch/dissem/bitmessage/networking/nio/ConnectionIO.kt @@ -0,0 +1,158 @@ +/* + * Copyright 2016 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.networking.nio + +import ch.dissem.bitmessage.entity.GetData +import ch.dissem.bitmessage.entity.MessagePayload +import ch.dissem.bitmessage.entity.NetworkMessage +import ch.dissem.bitmessage.entity.valueobject.InventoryVector +import ch.dissem.bitmessage.exception.NodeException +import ch.dissem.bitmessage.factory.V3MessageReader +import ch.dissem.bitmessage.utils.UnixTime +import org.slf4j.LoggerFactory +import java.nio.ByteBuffer +import java.util.* +import java.util.concurrent.ConcurrentLinkedDeque + +/** + * Represents the current state of a connection. + */ +class ConnectionIO( + private val mode: Connection.Mode, + syncTimeout: Long, + private val commonRequestedObjects: MutableMap<InventoryVector, Long>, + private val requestedObjects: MutableSet<InventoryVector>, + private val getState: () -> Connection.State, + private val handleMessage: (MessagePayload) -> Unit +) { + private val headerOut: ByteBuffer = ByteBuffer.allocate(24) + private var payloadOut: ByteBuffer? = null + private var reader: V3MessageReader? = V3MessageReader() + internal val sendingQueue: Deque<MessagePayload> = ConcurrentLinkedDeque<MessagePayload>() + + internal var lastUpdate = System.currentTimeMillis() + private set + + private val syncTimeout: Long = if (syncTimeout > 0) UnixTime.now + syncTimeout else 0 + private var syncReadTimeout = java.lang.Long.MAX_VALUE + + init { + headerOut.flip() + } + + val inBuffer: ByteBuffer + get() = reader?.getActiveBuffer() ?: throw NodeException("Node is disconnected") + + fun updateWriter() { + if (!headerOut.hasRemaining() && !sendingQueue.isEmpty()) { + headerOut.clear() + val payload = sendingQueue.poll() + payloadOut = NetworkMessage(payload).writeHeaderAndGetPayloadBuffer(headerOut) + headerOut.flip() + lastUpdate = System.currentTimeMillis() + } + } + + val outBuffers: Array<ByteBuffer> + get() = payloadOut?.let { arrayOf(headerOut, it) } ?: arrayOf(headerOut) + + fun cleanupBuffers() { + payloadOut?.let { + if (!it.hasRemaining()) payloadOut = null + } + } + + fun updateReader() { + reader?.let { reader -> + reader.update() + if (!reader.getMessages().isEmpty()) { + val iterator = reader.getMessages().iterator() + var msg: NetworkMessage? = null + while (iterator.hasNext()) { + msg = iterator.next() + handleMessage(msg.payload) + iterator.remove() + } + isSyncFinished = syncFinished(msg) + } + lastUpdate = System.currentTimeMillis() + } + } + + fun updateSyncStatus() { + if (!isSyncFinished) { + isSyncFinished = reader?.getMessages()?.isEmpty() ?: true && syncFinished(null) + } + } + + protected fun syncFinished(msg: NetworkMessage?): Boolean { + if (mode != Connection.Mode.SYNC) { + return false + } + if (Thread.interrupted() || getState() == Connection.State.DISCONNECTED) { + return true + } + if (getState() == Connection.State.CONNECTING) { + return false + } + if (syncTimeout < UnixTime.now) { + LOG.info("Synchronization timed out") + return true + } + if (!nothingToSend()) { + syncReadTimeout = System.currentTimeMillis() + 1000 + return false + } + if (msg == null) { + return syncReadTimeout < System.currentTimeMillis() + } else { + syncReadTimeout = System.currentTimeMillis() + 1000 + return false + } + } + + fun disconnect() { + reader?.let { + it.cleanup() + reader = null + } + payloadOut = null + } + + fun send(payload: MessagePayload) { + sendingQueue.add(payload) + if (payload is GetData) { + val now = UnixTime.now + val inventory = payload.inventory + requestedObjects.addAll(inventory) + inventory.forEach { iv -> commonRequestedObjects.put(iv, now) } + } + } + + var isSyncFinished = false + + val isWritePending: Boolean + get() = !sendingQueue.isEmpty() + || headerOut.hasRemaining() + || payloadOut?.hasRemaining() ?: false + + fun nothingToSend() = sendingQueue.isEmpty() + + companion object { + val LOG = LoggerFactory.getLogger(ConnectionIO::class.java) + } +} diff --git a/networking/src/main/kotlin/ch/dissem/bitmessage/networking/nio/NetworkConnectionInitializer.kt b/networking/src/main/kotlin/ch/dissem/bitmessage/networking/nio/NetworkConnectionInitializer.kt new file mode 100644 index 0000000..4fd6d7c --- /dev/null +++ b/networking/src/main/kotlin/ch/dissem/bitmessage/networking/nio/NetworkConnectionInitializer.kt @@ -0,0 +1,113 @@ +/* + * Copyright 2017 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.networking.nio + +import ch.dissem.bitmessage.BitmessageContext +import ch.dissem.bitmessage.InternalContext +import ch.dissem.bitmessage.entity.* +import ch.dissem.bitmessage.entity.valueobject.NetworkAddress +import ch.dissem.bitmessage.exception.NodeException +import ch.dissem.bitmessage.utils.UnixTime +import org.slf4j.LoggerFactory + +/** + * Handles the initialization phase of connection and, due to their design, custom commands. + */ +class NetworkConnectionInitializer( + private val ctx: InternalContext, + val node: NetworkAddress, + val mode: Connection.Mode, + val send: (MessagePayload) -> Unit, + val markActive: (LongArray) -> Unit +) { + private lateinit var version: Version + + private var verackSent: Boolean = false + private var verackReceived: Boolean = false + + fun start() { + if (mode == Connection.Mode.CLIENT || mode == Connection.Mode.SYNC) { + send(Version(nonce = ctx.clientNonce, addrFrom = NetworkAddress.ANY, addrRecv = node)) + } + } + + fun handleCommand(payload: MessagePayload) { + when (payload.command) { + MessagePayload.Command.VERSION -> handleVersion(payload as Version) + MessagePayload.Command.VERACK -> { + if (verackSent) { + activateConnection() + } + verackReceived = true + } + MessagePayload.Command.CUSTOM -> { + ctx.customCommandHandler.handle(payload as CustomMessage)?.let { response -> + send(response) + } ?: throw NodeException("No response for custom command available") + } + else -> throw NodeException("Command 'version' or 'verack' expected, but was '${payload.command}'") + } + } + + private fun handleVersion(version: Version) { + if (version.nonce == ctx.clientNonce) { + throw NodeException("Tried to connect to self, disconnecting.") + } else if (version.version >= BitmessageContext.CURRENT_VERSION) { + this.version = version + verackSent = true + send(VerAck()) + if (mode == Connection.Mode.SERVER) { + send(Version.Builder().defaults(ctx.clientNonce).addrFrom(NetworkAddress.ANY).addrRecv(node).build()) + } + if (verackReceived) { + activateConnection() + } + } else { + throw NodeException("Received unsupported version " + version.version + ", disconnecting.") + } + } + + private fun activateConnection() { + LOG.info("Successfully established connection with node " + node) + markActive(version.streams) + node.time = UnixTime.now + if (mode != Connection.Mode.SYNC) { + sendAddresses() + ctx.nodeRegistry.offerAddresses(listOf(node)) + } + sendInventory() + } + + + private fun sendAddresses() { + val addresses = ctx.nodeRegistry.getKnownAddresses(1000, *version.streams) + send(Addr(addresses)) + } + + private fun sendInventory() { + val inventory = ctx.inventory.getInventory(*version.streams) + var i = 0 + while (i < inventory.size) { + send(Inv(inventory.subList(i, Math.min(inventory.size, i + 50000)))) + i += 50000 + } + } + + companion object { + val LOG = LoggerFactory.getLogger(NetworkConnectionInitializer::class.java)!! + } +} diff --git a/networking/src/main/kotlin/ch/dissem/bitmessage/networking/nio/NioNetworkHandler.kt b/networking/src/main/kotlin/ch/dissem/bitmessage/networking/nio/NioNetworkHandler.kt new file mode 100644 index 0000000..8900b49 --- /dev/null +++ b/networking/src/main/kotlin/ch/dissem/bitmessage/networking/nio/NioNetworkHandler.kt @@ -0,0 +1,476 @@ +/* + * Copyright 2016 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.networking.nio + +import ch.dissem.bitmessage.InternalContext +import ch.dissem.bitmessage.constants.Network.HEADER_SIZE +import ch.dissem.bitmessage.constants.Network.NETWORK_MAGIC_NUMBER +import ch.dissem.bitmessage.entity.CustomMessage +import ch.dissem.bitmessage.entity.GetData +import ch.dissem.bitmessage.entity.NetworkMessage +import ch.dissem.bitmessage.entity.valueobject.InventoryVector +import ch.dissem.bitmessage.entity.valueobject.NetworkAddress +import ch.dissem.bitmessage.exception.NodeException +import ch.dissem.bitmessage.factory.V3MessageReader +import ch.dissem.bitmessage.networking.nio.Connection.Mode.* +import ch.dissem.bitmessage.ports.NetworkHandler +import ch.dissem.bitmessage.utils.Collections.selectRandom +import ch.dissem.bitmessage.utils.DebugUtils +import ch.dissem.bitmessage.utils.Property +import ch.dissem.bitmessage.utils.ThreadFactoryBuilder.Companion.pool +import ch.dissem.bitmessage.utils.UnixTime.now +import org.slf4j.LoggerFactory +import java.io.IOException +import java.net.InetAddress +import java.net.InetSocketAddress +import java.net.NoRouteToHostException +import java.nio.ByteBuffer +import java.nio.channels.* +import java.nio.channels.SelectionKey.* +import java.util.* +import java.util.concurrent.* + +/** + * Network handler using java.nio, resulting in less threads. + */ +class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder { + + private val threadPool = Executors.newCachedThreadPool( + pool("network") + .lowPrio() + .daemon() + .build()) + + private lateinit var ctx: InternalContext + private var selector: Selector? = null + private var serverChannel: ServerSocketChannel? = null + private val connectionQueue = ConcurrentLinkedQueue<NetworkAddress>() + private val connections = ConcurrentHashMap<Connection, SelectionKey>() + private val requestedObjects = ConcurrentHashMap<InventoryVector, Long>(10000) + + private var starter: Thread? = null + + override fun setContext(context: InternalContext) { + ctx = context + } + + override fun synchronize(server: InetAddress, port: Int, timeoutInSeconds: Long): Future<Void> { + return threadPool.submit(Callable<Void> { + SocketChannel.open(InetSocketAddress(server, port)).use { channel -> + channel.configureBlocking(false) + val connection = Connection(ctx, SYNC, + NetworkAddress.Builder().ip(server).port(port).stream(1).build(), + HashMap<InventoryVector, Long>(), timeoutInSeconds) + while (channel.isConnected && !connection.isSyncFinished) { + write(channel, connection.io) + read(channel, connection.io) + Thread.sleep(10) + } + LOG.info("Synchronization finished") + } + null + }) + } + + override fun send(server: InetAddress, port: Int, request: CustomMessage): CustomMessage { + SocketChannel.open(InetSocketAddress(server, port)).use { channel -> + channel.configureBlocking(true) + val headerBuffer = ByteBuffer.allocate(HEADER_SIZE) + val payloadBuffer = NetworkMessage(request).writeHeaderAndGetPayloadBuffer(headerBuffer) + headerBuffer.flip() + while (headerBuffer.hasRemaining()) { + channel.write(headerBuffer) + } + while (payloadBuffer.hasRemaining()) { + channel.write(payloadBuffer) + } + + val reader = V3MessageReader() + while (channel.isConnected && reader.getMessages().isEmpty()) { + if (channel.read(reader.getActiveBuffer()) > 0) { + reader.update() + } else { + throw NodeException("No response from node $server") + } + } + val networkMessage: NetworkMessage? + if (reader.getMessages().isEmpty()) { + throw NodeException("No response from node " + server) + } else { + networkMessage = reader.getMessages().first() + } + + if (networkMessage.payload is CustomMessage) { + return networkMessage.payload as CustomMessage + } else { + throw NodeException("Unexpected response from node $server: ${networkMessage.payload.javaClass}") + } + } + } + + override fun start() { + if (selector?.isOpen ?: false) { + throw IllegalStateException("Network already running - you need to stop first.") + } + val selector = Selector.open() + this.selector = selector + + requestedObjects.clear() + + starter = thread("connection manager") { + while (selector.isOpen) { + var missing = NETWORK_MAGIC_NUMBER + for (connection in connections.keys) { + if (connection.state == Connection.State.ACTIVE) { + missing-- + if (missing == 0) break + } + } + if (missing > 0) { + var addresses = ctx.nodeRegistry.getKnownAddresses(100, *ctx.streams) + addresses = selectRandom(missing, addresses) + for (address in addresses) { + if (!isConnectedTo(address)) { + connectionQueue.offer(address) + } + } + } + + val it = connections.entries.iterator() + while (it.hasNext()) { + val e = it.next() + if (!e.value.isValid || e.key.isExpired()) { + try { + e.value.channel().close() + } catch (ignore: Exception) { + } + + e.value.cancel() + e.value.attach(null) + e.key.disconnect() + it.remove() + } + } + + // The list 'requested objects' helps to prevent downloading an object + // twice. From time to time there is an error though, and an object is + // never downloaded. To prevent a large list of failed objects and give + // them a chance to get downloaded again, we will attempt to download an + // object from another node after some time out. + val timedOut = System.currentTimeMillis() - REQUESTED_OBJECTS_MAX_TIME + val delayed = mutableListOf<InventoryVector>() + val iterator = requestedObjects.entries.iterator() + while (iterator.hasNext()) { + val e = iterator.next() + + if (e.value == DELAYED) { + iterator.remove() + } else if (e.value < timedOut) { + delayed.add(e.key) + e.setValue(DELAYED) + } + } + request(delayed) + + try { + Thread.sleep(30000) + } catch (e: InterruptedException) { + return@thread + } + } + } + + thread("selector worker", { + try { + val serverChannel = ServerSocketChannel.open() + this.serverChannel = serverChannel + serverChannel.configureBlocking(false) + serverChannel.socket().bind(InetSocketAddress(ctx.port)) + serverChannel.register(selector, OP_ACCEPT, null) + + while (selector.isOpen) { + selector.select(1000) + val keyIterator = selector.selectedKeys().iterator() + while (keyIterator.hasNext()) { + val key = keyIterator.next() + keyIterator.remove() + if (key.attachment() == null) { + try { + if (key.isAcceptable) { + // handle accept + try { + val accepted = (key.channel() as ServerSocketChannel).accept() + accepted.configureBlocking(false) + val connection = Connection(ctx, SERVER, + NetworkAddress( + time = now, + stream = 1L, + socket = accepted.socket()!! + ), + requestedObjects, 0 + ) + connections.put( + connection, + accepted.register(selector, OP_READ or OP_WRITE, connection) + ) + } catch (e: AsynchronousCloseException) { + LOG.trace(e.message) + } catch (e: IOException) { + LOG.error(e.message, e) + } + + } + } catch (e: CancelledKeyException) { + LOG.debug(e.message, e) + } + + } else { + // handle read/write + val channel = key.channel() as SocketChannel + val connection = key.attachment() as Connection + try { + if (key.isConnectable) { + if (!channel.finishConnect()) { + continue + } + } + if (key.isWritable) { + write(channel, connection.io) + } + if (key.isReadable) { + read(channel, connection.io) + } + if (connection.state == Connection.State.DISCONNECTED) { + key.interestOps(0) + channel.close() + } else if (connection.io.isWritePending) { + key.interestOps(OP_READ or OP_WRITE) + } else { + key.interestOps(OP_READ) + } + } catch (e: CancelledKeyException) { + connection.disconnect() + } catch (e: NodeException) { + connection.disconnect() + } catch (e: IOException) { + connection.disconnect() + } + } + } + // set interest ops + for ((connection, selectionKey) in connections) { + try { + if (selectionKey.isValid + && selectionKey.interestOps() and OP_WRITE == 0 + && selectionKey.interestOps() and OP_CONNECT == 0 + && !connection.nothingToSend) { + selectionKey.interestOps(OP_READ or OP_WRITE) + } + } catch (x: CancelledKeyException) { + connection.disconnect() + } + + } + // start new connections + if (!connectionQueue.isEmpty()) { + val address = connectionQueue.poll() + try { + val channel = SocketChannel.open() + channel.configureBlocking(false) + channel.connect(InetSocketAddress(address.toInetAddress(), address.port)) + val connection = Connection(ctx, CLIENT, address, requestedObjects, 0) + connections.put( + connection, + channel.register(selector, OP_CONNECT, connection) + ) + } catch (ignore: NoRouteToHostException) { + // We'll try to connect to many offline nodes, so + // this is expected to happen quite a lot. + } catch (e: AsynchronousCloseException) { + // The exception is expected if the network is being + // shut down, as we actually do asynchronously close + // the connections. + if (isRunning) { + LOG.error(e.message, e) + } + } catch (e: IOException) { + LOG.error(e.message, e) + } + + } + } + selector.close() + } catch (_: ClosedSelectorException) { + } + }) + } + + private fun thread(threadName: String, runnable: () -> Unit): Thread { + val thread = Thread(runnable, threadName) + thread.isDaemon = true + thread.priority = Thread.MIN_PRIORITY + thread.start() + return thread + } + + override fun stop() { + serverChannel?.socket()?.close() + selector?.close() + for (selectionKey in connections.values) { + selectionKey.channel().close() + } + } + + override fun offer(iv: InventoryVector) { + val targetConnections = connections.keys.filter { it.state == Connection.State.ACTIVE && !it.knowsOf(iv) } + selectRandom(NETWORK_MAGIC_NUMBER, targetConnections).forEach { it.offer(iv) } + } + + override fun request(inventoryVectors: MutableCollection<InventoryVector>) { + if (!isRunning) { + requestedObjects.clear() + return + } + val iterator = inventoryVectors.iterator() + if (!iterator.hasNext()) { + return + } + + val distribution = HashMap<Connection, MutableList<InventoryVector>>() + for (connection in connections.keys) { + if (connection.state == Connection.State.ACTIVE) { + distribution.put(connection, mutableListOf<InventoryVector>()) + } + } + if (distribution.isEmpty()) { + return + } + var next = iterator.next() + var previous: Connection? = null + do { + for (connection in distribution.keys) { + if (connection === previous || previous == null) { + if (iterator.hasNext()) { + previous = connection + next = iterator.next() + } else { + break + } + } + if (connection.knowsOf(next) && !connection.requested(next)) { + val ivs = distribution[connection] ?: throw IllegalStateException("distribution not available for $connection") + if (ivs.size == GetData.MAX_INVENTORY_SIZE) { + connection.send(GetData(ivs)) + ivs.clear() + } + ivs.add(next) + iterator.remove() + + if (iterator.hasNext()) { + next = iterator.next() + previous = connection + } else { + break + } + } + } + } while (iterator.hasNext()) + + // remove objects nobody knows of + for (iv in inventoryVectors) { + requestedObjects.remove(iv) + } + + for (connection in distribution.keys) { + val ivs = distribution[connection] ?: throw IllegalStateException("distribution not available for $connection") + if (!ivs.isEmpty()) { + connection.send(GetData(ivs)) + } + } + } + + override fun getNetworkStatus(): Property { + val streams = TreeSet<Long>() + val incomingConnections = TreeMap<Long, Int>() + val outgoingConnections = TreeMap<Long, Int>() + + for (connection in connections.keys) { + if (connection.state == Connection.State.ACTIVE) { + for (stream in connection.streams) { + streams.add(stream) + if (connection.mode == SERVER) { + DebugUtils.inc(incomingConnections, stream) + } else { + DebugUtils.inc(outgoingConnections, stream) + } + } + } + } + val streamProperties = mutableListOf<Property>() + for (stream in streams) { + val incoming = incomingConnections[stream] ?: 0 + val outgoing = outgoingConnections[stream] ?: 0 + streamProperties.add(Property("stream " + stream, Property("nodes", incoming + outgoing), + Property("incoming", incoming), + Property("outgoing", outgoing) + )) + } + return Property("network", + Property("connectionManager", if (isRunning) "running" else "stopped"), + Property("connections", *streamProperties.toTypedArray()), + Property("requestedObjects", requestedObjects.size) + ) + } + + private fun isConnectedTo(address: NetworkAddress): Boolean { + for (c in connections.keys) { + if (c.node == address) { + return true + } + } + return false + } + + override val isRunning: Boolean + get() = selector?.isOpen ?: false && starter?.isAlive ?: false + + companion object { + private val LOG = LoggerFactory.getLogger(NioNetworkHandler::class.java) + private val REQUESTED_OBJECTS_MAX_TIME = (2 * 60000).toLong() // 2 minutes in ms + private val DELAYED = java.lang.Long.MIN_VALUE + + private fun write(channel: SocketChannel, connection: ConnectionIO) { + writeBuffer(connection.outBuffers, channel) + + connection.updateWriter() + + writeBuffer(connection.outBuffers, channel) + connection.cleanupBuffers() + } + + private fun writeBuffer(buffers: Array<ByteBuffer>, channel: SocketChannel) { + if (buffers.any { buf -> buf.hasRemaining() }) channel.write(buffers) + } + + private fun read(channel: SocketChannel, connection: ConnectionIO) { + if (channel.read(connection.inBuffer) > 0) { + connection.updateReader() + } + connection.updateSyncStatus() + } + } +} diff --git a/networking/src/test/java/ch/dissem/bitmessage/networking/NetworkHandlerTest.java b/networking/src/test/java/ch/dissem/bitmessage/networking/NetworkHandlerTest.java deleted file mode 100644 index f079ede..0000000 --- a/networking/src/test/java/ch/dissem/bitmessage/networking/NetworkHandlerTest.java +++ /dev/null @@ -1,279 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.networking; - -import ch.dissem.bitmessage.BitmessageContext; -import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography; -import ch.dissem.bitmessage.entity.CustomMessage; -import ch.dissem.bitmessage.entity.MessagePayload; -import ch.dissem.bitmessage.entity.valueobject.NetworkAddress; -import ch.dissem.bitmessage.exception.NodeException; -import ch.dissem.bitmessage.networking.nio.NioNetworkHandler; -import ch.dissem.bitmessage.ports.*; -import ch.dissem.bitmessage.testutils.TestInventory; -import ch.dissem.bitmessage.utils.Property; -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.DisableOnDebug; -import org.junit.rules.TestRule; -import org.junit.rules.Timeout; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.Future; - -import static ch.dissem.bitmessage.utils.Singleton.cryptography; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.notNullValue; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; -import static org.mockito.Mockito.mock; - -/** - * Tests network handlers. This test is parametrized, so it can test both the nio and classic implementation - * as well as their combinations. It might be slightly over the top and will most probably be cleaned up once - * the nio implementation is deemed stable. - */ -@RunWith(Parameterized.class) -public class NetworkHandlerTest { - private static final Logger LOG = LoggerFactory.getLogger(NetworkHandlerTest.class); - private static NetworkAddress peerAddress = new NetworkAddress.Builder().ipv4(127, 0, 0, 1).port(6001).build(); - - private TestInventory peerInventory; - private TestInventory nodeInventory; - - private BitmessageContext peer; - private BitmessageContext node; - - private final NetworkHandler peerNetworkHandler; - private final NetworkHandler nodeNetworkHandler; - - @Rule - public final TestRule timeout = new DisableOnDebug(Timeout.seconds(60)); - - public NetworkHandlerTest(NetworkHandler peer, NetworkHandler node) { - this.peerNetworkHandler = peer; - this.nodeNetworkHandler = node; - } - - @Parameterized.Parameters - @SuppressWarnings("deprecation") - public static List<Object[]> parameters() { - return Arrays.asList(new Object[][]{ - {new DefaultNetworkHandler(), new DefaultNetworkHandler()}, - {new DefaultNetworkHandler(), new NioNetworkHandler()}, - {new NioNetworkHandler(), new DefaultNetworkHandler()}, - {new NioNetworkHandler(), new NioNetworkHandler()} - }); - } - - @Before - public void setUp() throws InterruptedException { - peerInventory = new TestInventory(); - peer = new BitmessageContext.Builder() - .addressRepo(mock(AddressRepository.class)) - .inventory(peerInventory) - .messageRepo(mock(MessageRepository.class)) - .powRepo(mock(ProofOfWorkRepository.class)) - .port(peerAddress.getPort()) - .nodeRegistry(new TestNodeRegistry()) - .networkHandler(peerNetworkHandler) - .cryptography(new BouncyCryptography()) - .listener(mock(BitmessageContext.Listener.class)) - .customCommandHandler(new CustomCommandHandler() { - @Override - public MessagePayload handle(CustomMessage request) { - byte[] data = request.getData(); - if (data.length > 0) { - switch (data[0]) { - case 0: - return null; - case 1: - break; - case 3: - data[0] = 0; - break; - default: - break; - } - } - return new CustomMessage("test response", request.getData()); - } - }) - .build(); - peer.startup(); - Thread.sleep(100); - - nodeInventory = new TestInventory(); - node = new BitmessageContext.Builder() - .addressRepo(mock(AddressRepository.class)) - .inventory(nodeInventory) - .messageRepo(mock(MessageRepository.class)) - .powRepo(mock(ProofOfWorkRepository.class)) - .port(6002) - .nodeRegistry(new TestNodeRegistry(peerAddress)) - .networkHandler(nodeNetworkHandler) - .cryptography(new BouncyCryptography()) - .listener(mock(BitmessageContext.Listener.class)) - .build(); - } - - @After - public void cleanUp() { - shutdown(peer); - shutdown(node); - shutdown(nodeNetworkHandler); - } - - private static void shutdown(BitmessageContext ctx) { - if (!ctx.isRunning()) return; - - ctx.shutdown(); - do { - try { - Thread.sleep(100); - } catch (InterruptedException ignore) { - } - } while (ctx.isRunning()); - } - - private static void shutdown(NetworkHandler networkHandler) { - if (!networkHandler.isRunning()) return; - - networkHandler.stop(); - do { - try { - Thread.sleep(100); - } catch (InterruptedException ignore) { - if (networkHandler.isRunning()) { - LOG.warn("Thread interrupted while waiting for network shutdown - " + - "this could cause problems in subsequent tests."); - } - return; - } - } while (networkHandler.isRunning()); - } - - private Property waitForNetworkStatus(BitmessageContext ctx) throws InterruptedException { - Property status; - do { - Thread.sleep(100); - status = ctx.status().getProperty("network", "connections", "stream 1"); - } while (status == null); - return status; - } - - @Test - public void ensureNodesAreConnecting() throws Exception { - node.startup(); - - Property nodeStatus = waitForNetworkStatus(node); - Property peerStatus = waitForNetworkStatus(peer); - - assertEquals(1, nodeStatus.getProperty("outgoing").getValue()); - assertEquals(1, peerStatus.getProperty("incoming").getValue()); - } - - @Test - public void ensureCustomMessageIsSentAndResponseRetrieved() throws Exception { - byte[] data = cryptography().randomBytes(8); - data[0] = (byte) 1; - CustomMessage request = new CustomMessage("test request", data); - node.startup(); - - CustomMessage response = nodeNetworkHandler.send(peerAddress.toInetAddress(), peerAddress.getPort(), request); - - assertThat(response, notNullValue()); - assertThat(response.getCustomCommand(), is("test response")); - assertThat(response.getData(), is(data)); - } - - @Test(expected = NodeException.class) - public void ensureCustomMessageWithoutResponseYieldsException() throws Exception { - byte[] data = cryptography().randomBytes(8); - data[0] = (byte) 0; - CustomMessage request = new CustomMessage("test request", data); - - CustomMessage response = nodeNetworkHandler.send(peerAddress.toInetAddress(), peerAddress.getPort(), request); - - assertThat(response, notNullValue()); - assertThat(response.getCustomCommand(), is("test response")); - assertThat(response.getData(), is(request.getData())); - } - - @Test - public void ensureObjectsAreSynchronizedIfBothHaveObjects() throws Exception { - peerInventory.init( - "V4Pubkey.payload", - "V5Broadcast.payload" - ); - - nodeInventory.init( - "V1Msg.payload", - "V4Pubkey.payload" - ); - - Future<?> future = nodeNetworkHandler.synchronize(peerAddress.toInetAddress(), peerAddress.getPort(), 10); - future.get(); - assertInventorySize(3, nodeInventory); - assertInventorySize(3, peerInventory); - } - - @Test - public void ensureObjectsAreSynchronizedIfOnlyPeerHasObjects() throws Exception { - peerInventory.init( - "V4Pubkey.payload", - "V5Broadcast.payload" - ); - - nodeInventory.init(); - - Future<?> future = nodeNetworkHandler.synchronize(peerAddress.toInetAddress(), peerAddress.getPort(), 10); - future.get(); - assertInventorySize(2, nodeInventory); - assertInventorySize(2, peerInventory); - } - - @Test - public void ensureObjectsAreSynchronizedIfOnlyNodeHasObjects() throws Exception { - peerInventory.init(); - - nodeInventory.init( - "V1Msg.payload" - ); - - Future<?> future = nodeNetworkHandler.synchronize(peerAddress.toInetAddress(), peerAddress.getPort(), 10); - future.get(); - assertInventorySize(1, nodeInventory); - assertInventorySize(1, peerInventory); - } - - private void assertInventorySize(int expected, TestInventory inventory) throws InterruptedException { - long timeout = System.currentTimeMillis() + 1000; - while (expected != inventory.getInventory().size() && System.currentTimeMillis() < timeout) { - Thread.sleep(10); - } - assertEquals(expected, inventory.getInventory().size()); - } - -} diff --git a/networking/src/test/java/ch/dissem/bitmessage/networking/NetworkHandlerTest.kt b/networking/src/test/java/ch/dissem/bitmessage/networking/NetworkHandlerTest.kt new file mode 100644 index 0000000..4eaface --- /dev/null +++ b/networking/src/test/java/ch/dissem/bitmessage/networking/NetworkHandlerTest.kt @@ -0,0 +1,266 @@ +/* + * Copyright 2015 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.networking + +import ch.dissem.bitmessage.BitmessageContext +import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography +import ch.dissem.bitmessage.entity.CustomMessage +import ch.dissem.bitmessage.entity.MessagePayload +import ch.dissem.bitmessage.entity.valueobject.NetworkAddress +import ch.dissem.bitmessage.exception.NodeException +import ch.dissem.bitmessage.networking.nio.NioNetworkHandler +import ch.dissem.bitmessage.ports.* +import ch.dissem.bitmessage.testutils.TestInventory +import ch.dissem.bitmessage.utils.Property +import ch.dissem.bitmessage.utils.Singleton.cryptography +import com.nhaarman.mockito_kotlin.mock +import org.hamcrest.Matchers.`is` +import org.hamcrest.Matchers.notNullValue +import org.junit.After +import org.junit.Assert.* +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.rules.DisableOnDebug +import org.junit.rules.TestRule +import org.junit.rules.Timeout +import org.slf4j.LoggerFactory + +/** + * Tests network handlers. This test is parametrized, so it can test both the nio and classic implementation + * as well as their combinations. It might be slightly over the top and will most probably be cleaned up once + * the nio implementation is deemed stable. + */ +class NetworkHandlerTest { + + private lateinit var peerInventory: TestInventory + private lateinit var nodeInventory: TestInventory + + private lateinit var peer: BitmessageContext + private lateinit var node: BitmessageContext + + private lateinit var peerNetworkHandler: NetworkHandler + private lateinit var nodeNetworkHandler: NetworkHandler + + @JvmField @Rule val timeout: TestRule = DisableOnDebug(Timeout.seconds(60)) + + @Before + fun setUp() { + peerInventory = TestInventory() + peerNetworkHandler = NioNetworkHandler() + peer = BitmessageContext( + cryptography = BouncyCryptography(), + inventory = peerInventory, + nodeRegistry = TestNodeRegistry(), + networkHandler = peerNetworkHandler, + addressRepository = mock<AddressRepository>(), + messageRepository = mock<MessageRepository>(), + proofOfWorkRepository = mock<ProofOfWorkRepository>(), + customCommandHandler = object : CustomCommandHandler { + override fun handle(request: CustomMessage): MessagePayload? { + val data = request.getData() + if (data.isNotEmpty()) { + when (data[0]) { + 0.toByte() -> return null + 1.toByte() -> { + } + 3.toByte() -> data[0] = 0 + } + } + return CustomMessage("test response", request.getData()) + } + }, + listener = mock<BitmessageContext.Listener>(), + port = peerAddress.port + ) + peer.startup() + Thread.sleep(100) + + nodeInventory = TestInventory() + nodeNetworkHandler = NioNetworkHandler() + node = BitmessageContext( + cryptography = BouncyCryptography(), + inventory = nodeInventory, + nodeRegistry = TestNodeRegistry(peerAddress), + networkHandler = nodeNetworkHandler, + addressRepository = mock<AddressRepository>(), + messageRepository = mock<MessageRepository>(), + proofOfWorkRepository = mock<ProofOfWorkRepository>(), + customCommandHandler = object : CustomCommandHandler { + override fun handle(request: CustomMessage): MessagePayload? { + val data = request.getData() + if (data.isNotEmpty()) { + when (data[0]) { + 0.toByte() -> return null + 1.toByte() -> { + } + 3.toByte() -> data[0] = 0 + } + } + return CustomMessage("test response", request.getData()) + } + }, + listener = mock<BitmessageContext.Listener>(), + port = 6002 + ) + } + + @After + fun cleanUp() { + shutdown(peer) + shutdown(node) + shutdown(nodeNetworkHandler) + } + + private fun waitForNetworkStatus(ctx: BitmessageContext): Property { + var status: Property? + do { + Thread.sleep(100) + status = ctx.status().getProperty("network", "connections", "stream 1") + } while (status == null) + return status + } + + @Test + fun `ensure nodes are connecting`() { + node.startup() + + val nodeStatus = waitForNetworkStatus(node) + val peerStatus = waitForNetworkStatus(peer) + + assertEquals(1, nodeStatus.getProperty("outgoing")!!.value) + assertEquals(1, peerStatus.getProperty("incoming")!!.value) + } + + @Test + fun `ensure CustomMessage is sent and response retrieved`() { + val data = cryptography().randomBytes(8) + data[0] = 1.toByte() + val request = CustomMessage("test request", data) + node.startup() + + val response = nodeNetworkHandler.send(peerAddress.toInetAddress(), peerAddress.port, request) + + assertThat(response, notNullValue()) + assertThat(response.customCommand, `is`("test response")) + assertThat(response.getData(), `is`(data)) + } + + @Test(expected = NodeException::class) + fun `ensure CustomMessage without response yields exception`() { + val data = cryptography().randomBytes(8) + data[0] = 0.toByte() + val request = CustomMessage("test request", data) + + val response = nodeNetworkHandler.send(peerAddress.toInetAddress(), peerAddress.port, request) + + assertThat(response, notNullValue()) + assertThat(response.customCommand, `is`("test response")) + assertThat(response.getData(), `is`(request.getData())) + } + + @Test + fun `ensure objects are synchronized if both have objects`() { + peerInventory.init( + "V4Pubkey.payload", + "V5Broadcast.payload" + ) + + nodeInventory.init( + "V1Msg.payload", + "V4Pubkey.payload" + ) + + val future = nodeNetworkHandler.synchronize(peerAddress.toInetAddress(), peerAddress.port, 10) + future.get() + assertInventorySize(3, nodeInventory) + assertInventorySize(3, peerInventory) + } + + @Test + fun `ensure objects are synchronized if only peer has objects`() { + peerInventory.init( + "V4Pubkey.payload", + "V5Broadcast.payload" + ) + + nodeInventory.init() + + val future = nodeNetworkHandler.synchronize(peerAddress.toInetAddress(), peerAddress.port, 10) + future.get() + assertInventorySize(2, nodeInventory) + assertInventorySize(2, peerInventory) + } + + @Test + fun `ensure objects are synchronized if only node has objects`() { + peerInventory.init() + + nodeInventory.init( + "V1Msg.payload" + ) + + val future = nodeNetworkHandler.synchronize(peerAddress.toInetAddress(), peerAddress.port, 10) + future.get() + assertInventorySize(1, nodeInventory) + assertInventorySize(1, peerInventory) + } + + private fun assertInventorySize(expected: Int, inventory: TestInventory) { + val timeout = System.currentTimeMillis() + 1000 + while (expected != inventory.getInventory().size && System.currentTimeMillis() < timeout) { + Thread.sleep(10) + } + assertEquals(expected.toLong(), inventory.getInventory().size.toLong()) + } + + companion object { + private val LOG = LoggerFactory.getLogger(NetworkHandlerTest::class.java) + private val peerAddress = NetworkAddress.Builder().ipv4(127, 0, 0, 1).port(6001).build() + + private fun shutdown(ctx: BitmessageContext) { + if (!ctx.isRunning) return + + ctx.shutdown() + do { + try { + Thread.sleep(100) + } catch (ignore: InterruptedException) { + } + + } while (ctx.isRunning) + } + + private fun shutdown(networkHandler: NetworkHandler) { + if (!networkHandler.isRunning) return + + networkHandler.stop() + do { + try { + Thread.sleep(100) + } catch (ignore: InterruptedException) { + if (networkHandler.isRunning) { + LOG.warn("Thread interrupted while waiting for network shutdown - " + "this could cause problems in subsequent tests.") + } + return + } + + } while (networkHandler.isRunning) + } + } + +} diff --git a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcAddressRepository.java b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcAddressRepository.java deleted file mode 100644 index 2f1e107..0000000 --- a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcAddressRepository.java +++ /dev/null @@ -1,238 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.repository; - -import ch.dissem.bitmessage.entity.BitmessageAddress; -import ch.dissem.bitmessage.entity.payload.Pubkey; -import ch.dissem.bitmessage.entity.payload.V3Pubkey; -import ch.dissem.bitmessage.entity.payload.V4Pubkey; -import ch.dissem.bitmessage.entity.valueobject.PrivateKey; -import ch.dissem.bitmessage.exception.ApplicationException; -import ch.dissem.bitmessage.factory.Factory; -import ch.dissem.bitmessage.ports.AddressRepository; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.sql.*; -import java.util.Arrays; -import java.util.LinkedList; -import java.util.List; - -public class JdbcAddressRepository extends JdbcHelper implements AddressRepository { - private static final Logger LOG = LoggerFactory.getLogger(JdbcAddressRepository.class); - - public JdbcAddressRepository(JdbcConfig config) { - super(config); - } - - @Override - public BitmessageAddress findContact(byte[] ripeOrTag) { - for (BitmessageAddress address : find("public_key is null")) { - if (address.getVersion() > 3) { - if (Arrays.equals(ripeOrTag, address.getTag())) return address; - } else { - if (Arrays.equals(ripeOrTag, address.getRipe())) return address; - } - } - return null; - } - - @Override - public BitmessageAddress findIdentity(byte[] ripeOrTag) { - for (BitmessageAddress address : find("private_key is not null")) { - if (address.getVersion() > 3) { - if (Arrays.equals(ripeOrTag, address.getTag())) return address; - } else { - if (Arrays.equals(ripeOrTag, address.getRipe())) return address; - } - } - return null; - } - - @Override - public List<BitmessageAddress> getIdentities() { - return find("private_key IS NOT NULL"); - } - - @Override - public List<BitmessageAddress> getChans() { - return find("chan = '1'"); - } - - @Override - public List<BitmessageAddress> getSubscriptions() { - return find("subscribed = '1'"); - } - - @Override - public List<BitmessageAddress> getSubscriptions(long broadcastVersion) { - if (broadcastVersion > 4) { - return find("subscribed = '1' AND version > 3"); - } else { - return find("subscribed = '1' AND version <= 3"); - } - } - - @Override - public List<BitmessageAddress> getContacts() { - return find("private_key IS NULL OR chan = '1'"); - } - - private List<BitmessageAddress> find(String where) { - List<BitmessageAddress> result = new LinkedList<>(); - try ( - Connection connection = config.getConnection(); - Statement stmt = connection.createStatement(); - ResultSet rs = stmt.executeQuery("SELECT address, alias, public_key, private_key, subscribed, chan " + - "FROM Address WHERE " + where) - ) { - while (rs.next()) { - BitmessageAddress address; - - InputStream privateKeyStream = rs.getBinaryStream("private_key"); - if (privateKeyStream == null) { - address = new BitmessageAddress(rs.getString("address")); - Blob publicKeyBlob = rs.getBlob("public_key"); - if (publicKeyBlob != null) { - Pubkey pubkey = Factory.readPubkey(address.getVersion(), address.getStream(), - publicKeyBlob.getBinaryStream(), (int) publicKeyBlob.length(), false); - if (address.getVersion() == 4 && pubkey instanceof V3Pubkey) { - pubkey = new V4Pubkey((V3Pubkey) pubkey); - } - address.setPubkey(pubkey); - } - } else { - PrivateKey privateKey = PrivateKey.read(privateKeyStream); - address = new BitmessageAddress(privateKey); - } - address.setAlias(rs.getString("alias")); - address.setSubscribed(rs.getBoolean("subscribed")); - address.setChan(rs.getBoolean("chan")); - - result.add(address); - } - } catch (SQLException e) { - LOG.error(e.getMessage(), e); - } - return result; - } - - private boolean exists(BitmessageAddress address) { - try ( - Connection connection = config.getConnection(); - Statement stmt = connection.createStatement(); - ResultSet rs = stmt.executeQuery("SELECT '1' FROM Address " + - "WHERE address='" + address.getAddress() + "'") - ) { - return rs.next(); - } catch (SQLException e) { - LOG.error(e.getMessage(), e); - throw new ApplicationException(e); - } - } - - @Override - public void save(BitmessageAddress address) { - try { - if (exists(address)) { - update(address); - } else { - insert(address); - } - } catch (IOException | SQLException e) { - LOG.error(e.getMessage(), e); - } - } - - private void update(BitmessageAddress address) throws IOException, SQLException { - StringBuilder statement = new StringBuilder("UPDATE Address SET alias=?"); - if (address.getPubkey() != null) { - statement.append(", public_key=?"); - } - if (address.getPrivateKey() != null) { - statement.append(", private_key=?"); - } - statement.append(", subscribed=?, chan=? WHERE address=?"); - try ( - Connection connection = config.getConnection(); - PreparedStatement ps = connection.prepareStatement(statement.toString()) - ) { - int i = 0; - ps.setString(++i, address.getAlias()); - if (address.getPubkey() != null) { - writePubkey(ps, ++i, address.getPubkey()); - } - if (address.getPrivateKey() != null) { - writeBlob(ps, ++i, address.getPrivateKey()); - } - ps.setBoolean(++i, address.isSubscribed()); - ps.setBoolean(++i, address.isChan()); - ps.setString(++i, address.getAddress()); - ps.executeUpdate(); - } - } - - private void insert(BitmessageAddress address) throws IOException, SQLException { - try ( - Connection connection = config.getConnection(); - PreparedStatement ps = connection.prepareStatement( - "INSERT INTO Address (address, version, alias, public_key, private_key, subscribed, chan) " + - "VALUES (?, ?, ?, ?, ?, ?, ?)") - ) { - ps.setString(1, address.getAddress()); - ps.setLong(2, address.getVersion()); - ps.setString(3, address.getAlias()); - writePubkey(ps, 4, address.getPubkey()); - writeBlob(ps, 5, address.getPrivateKey()); - ps.setBoolean(6, address.isSubscribed()); - ps.setBoolean(7, address.isChan()); - ps.executeUpdate(); - } - } - - protected void writePubkey(PreparedStatement ps, int parameterIndex, Pubkey data) throws SQLException, IOException { - if (data != null) { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - data.writeUnencrypted(out); - ps.setBytes(parameterIndex, out.toByteArray()); - } else { - ps.setBytes(parameterIndex, null); - } - } - - @Override - public void remove(BitmessageAddress address) { - try ( - Connection connection = config.getConnection(); - Statement stmt = connection.createStatement() - ) { - stmt.executeUpdate("DELETE FROM Address WHERE address = '" + address.getAddress() + "'"); - } catch (SQLException e) { - LOG.error(e.getMessage(), e); - } - } - - @Override - public BitmessageAddress getAddress(String address) { - List<BitmessageAddress> result = find("address = '" + address + "'"); - if (result.size() > 0) return result.get(0); - return null; - } -} diff --git a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcConfig.java b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcConfig.java deleted file mode 100644 index 7448b19..0000000 --- a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcConfig.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.repository; - -import org.flywaydb.core.Flyway; - -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.SQLException; - -/** - * The base configuration for all JDBC based repositories. You should only make one instance, - * as flyway initializes/updates the database at object creation. - */ -public class JdbcConfig { - protected final Flyway flyway; - protected final String dbUrl; - protected final String dbUser; - protected final String dbPassword; - - public JdbcConfig(String dbUrl, String dbUser, String dbPassword) { - this.dbUrl = dbUrl; - this.dbUser = dbUser; - this.dbPassword = dbPassword; - this.flyway = new Flyway(); - flyway.setDataSource(dbUrl, dbUser, dbPassword); - flyway.migrate(); - } - - public JdbcConfig() { - this("jdbc:h2:~/jabit;AUTO_SERVER=TRUE", "sa", null); - } - - public Connection getConnection() throws SQLException { - return DriverManager.getConnection(dbUrl, dbUser, dbPassword); - } -} diff --git a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcHelper.java b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcHelper.java deleted file mode 100644 index 8548267..0000000 --- a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcHelper.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.repository; - -import ch.dissem.bitmessage.entity.Streamable; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.sql.PreparedStatement; -import java.sql.SQLException; - -/** - * Helper class that does Flyway migration, provides JDBC connections and some helper methods. - */ -public abstract class JdbcHelper { - - protected final JdbcConfig config; - - protected JdbcHelper(JdbcConfig config) { - this.config = config; - } - - public static void writeBlob(PreparedStatement ps, int parameterIndex, Streamable data) throws SQLException, IOException { - if (data == null) { - ps.setBytes(parameterIndex, null); - } else { - ByteArrayOutputStream os = new ByteArrayOutputStream(); - data.write(os); - ps.setBytes(parameterIndex, os.toByteArray()); - } - } -} diff --git a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcInventory.java b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcInventory.java deleted file mode 100644 index 65ea15f..0000000 --- a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcInventory.java +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.repository; - -import ch.dissem.bitmessage.entity.ObjectMessage; -import ch.dissem.bitmessage.entity.payload.ObjectType; -import ch.dissem.bitmessage.entity.valueobject.InventoryVector; -import ch.dissem.bitmessage.exception.ApplicationException; -import ch.dissem.bitmessage.factory.Factory; -import ch.dissem.bitmessage.ports.Inventory; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.sql.*; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import static ch.dissem.bitmessage.utils.SqlStrings.join; -import static ch.dissem.bitmessage.utils.UnixTime.MINUTE; -import static ch.dissem.bitmessage.utils.UnixTime.now; - -public class JdbcInventory extends JdbcHelper implements Inventory { - private static final Logger LOG = LoggerFactory.getLogger(JdbcInventory.class); - - private final Map<Long, Map<InventoryVector, Long>> cache = new ConcurrentHashMap<>(); - - public JdbcInventory(JdbcConfig config) { - super(config); - } - - @Override - public List<InventoryVector> getInventory(long... streams) { - List<InventoryVector> result = new LinkedList<>(); - for (long stream : streams) { - getCache(stream).entrySet().stream() - .filter(e -> e.getValue() > now()) - .forEach(e -> result.add(e.getKey())); - } - return result; - } - - private Map<InventoryVector, Long> getCache(long stream) { - Map<InventoryVector, Long> result = cache.get(stream); - if (result == null) { - synchronized (cache) { - if (cache.get(stream) == null) { - result = new ConcurrentHashMap<>(); - cache.put(stream, result); - try ( - Connection connection = config.getConnection(); - Statement stmt = connection.createStatement(); - ResultSet rs = stmt.executeQuery("SELECT hash, expires FROM Inventory " + - "WHERE expires > " + (now() - 5 * MINUTE) + " AND stream = " + stream) - ) { - while (rs.next()) { - result.put(InventoryVector.fromHash(rs.getBytes("hash")), rs.getLong("expires")); - } - } catch (SQLException e) { - LOG.error(e.getMessage(), e); - } - } - } - } - return result; - } - - @Override - public List<InventoryVector> getMissing(List<InventoryVector> offer, long... streams) { - for (long stream : streams) { - offer.removeAll(getCache(stream).keySet()); - } - return offer; - } - - @Override - public ObjectMessage getObject(InventoryVector vector) { - try ( - Connection connection = config.getConnection(); - Statement stmt = connection.createStatement(); - ResultSet rs = stmt.executeQuery("SELECT data, version FROM Inventory WHERE hash = X'" + vector + "'") - ) { - if (rs.next()) { - Blob data = rs.getBlob("data"); - return Factory.getObjectMessage(rs.getInt("version"), data.getBinaryStream(), (int) data.length()); - } else { - LOG.info("Object requested that we don't have. IV: " + vector); - return null; - } - } catch (Exception e) { - LOG.error(e.getMessage(), e); - throw new ApplicationException(e); - } - } - - @Override - public List<ObjectMessage> getObjects(long stream, long version, ObjectType... types) { - StringBuilder query = new StringBuilder("SELECT data, version FROM Inventory WHERE 1=1"); - if (stream > 0) { - query.append(" AND stream = ").append(stream); - } - if (version > 0) { - query.append(" AND version = ").append(version); - } - if (types.length > 0) { - query.append(" AND type IN (").append(join(types)).append(')'); - } - try ( - Connection connection = config.getConnection(); - Statement stmt = connection.createStatement(); - ResultSet rs = stmt.executeQuery(query.toString()) - ) { - List<ObjectMessage> result = new LinkedList<>(); - while (rs.next()) { - Blob data = rs.getBlob("data"); - result.add(Factory.getObjectMessage(rs.getInt("version"), data.getBinaryStream(), (int) data.length())); - } - return result; - } catch (Exception e) { - LOG.error(e.getMessage(), e); - throw new ApplicationException(e); - } - } - - @Override - public void storeObject(ObjectMessage object) { - if (getCache(object.getStream()).containsKey(object.getInventoryVector())) - return; - - try ( - Connection connection = config.getConnection(); - PreparedStatement ps = connection.prepareStatement("INSERT INTO Inventory " + - "(hash, stream, expires, data, type, version) VALUES (?, ?, ?, ?, ?, ?)") - ) { - InventoryVector iv = object.getInventoryVector(); - LOG.trace("Storing object " + iv); - ps.setBytes(1, iv.getHash()); - ps.setLong(2, object.getStream()); - ps.setLong(3, object.getExpiresTime()); - writeBlob(ps, 4, object); - ps.setLong(5, object.getType()); - ps.setLong(6, object.getVersion()); - ps.executeUpdate(); - getCache(object.getStream()).put(iv, object.getExpiresTime()); - } catch (SQLException e) { - LOG.debug("Error storing object of type " + object.getPayload().getClass().getSimpleName(), e); - } catch (Exception e) { - LOG.error(e.getMessage(), e); - } - } - - @Override - public boolean contains(ObjectMessage object) { - return getCache(object.getStream()).entrySet().stream() - .anyMatch(x -> x.getKey().equals(object.getInventoryVector())); - } - - @Override - public void cleanup() { - try ( - Connection connection = config.getConnection(); - Statement stmt = connection.createStatement() - ) { - stmt.executeUpdate("DELETE FROM Inventory WHERE expires < " + (now() - 5 * MINUTE)); - } catch (SQLException e) { - LOG.debug(e.getMessage(), e); - } - for (Map<InventoryVector, Long> c : cache.values()) { - c.entrySet().removeIf(e -> e.getValue() < (now() - 5 * MINUTE)); - } - } -} diff --git a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcNodeRegistry.java b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcNodeRegistry.java deleted file mode 100644 index acecd33..0000000 --- a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcNodeRegistry.java +++ /dev/null @@ -1,201 +0,0 @@ -/* - * Copyright 2016 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.repository; - -import ch.dissem.bitmessage.entity.valueobject.NetworkAddress; -import ch.dissem.bitmessage.exception.ApplicationException; -import ch.dissem.bitmessage.ports.NodeRegistry; -import ch.dissem.bitmessage.utils.Collections; -import ch.dissem.bitmessage.utils.SqlStrings; -import ch.dissem.bitmessage.utils.Strings; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.sql.*; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import static ch.dissem.bitmessage.ports.NodeRegistryHelper.loadStableNodes; -import static ch.dissem.bitmessage.utils.UnixTime.*; - -public class JdbcNodeRegistry extends JdbcHelper implements NodeRegistry { - private static final Logger LOG = LoggerFactory.getLogger(JdbcNodeRegistry.class); - private Map<Long, Set<NetworkAddress>> stableNodes; - - public JdbcNodeRegistry(JdbcConfig config) { - super(config); - cleanUp(); - } - - private void cleanUp() { - try ( - Connection connection = config.getConnection(); - PreparedStatement ps = connection.prepareStatement( - "DELETE FROM Node WHERE time<?") - ) { - ps.setLong(1, now() - 28 * DAY); - ps.executeUpdate(); - } catch (SQLException e) { - LOG.error(e.getMessage(), e); - } - } - - private NetworkAddress loadExisting(NetworkAddress node) { - String query = - "SELECT stream, address, port, services, time" + - " FROM Node" + - " WHERE stream = " + node.getStream() + - " AND address = X'" + Strings.hex(node.getIPv6()) + "'" + - " AND port = " + node.getPort(); - try ( - Connection connection = config.getConnection(); - Statement stmt = connection.createStatement(); - ResultSet rs = stmt.executeQuery(query) - ) { - if (rs.next()) { - return new NetworkAddress.Builder() - .stream(rs.getLong("stream")) - .ipv6(rs.getBytes("address")) - .port(rs.getInt("port")) - .services(rs.getLong("services")) - .time(rs.getLong("time")) - .build(); - } else { - return null; - } - } catch (Exception e) { - LOG.error(e.getMessage(), e); - throw new ApplicationException(e); - } - } - - @Override - public void clear() { - try ( - Connection connection = config.getConnection(); - PreparedStatement ps = connection.prepareStatement( - "DELETE FROM Node") - ) { - ps.executeUpdate(); - } catch (SQLException e) { - LOG.error(e.getMessage(), e); - } - } - - @Override - public List<NetworkAddress> getKnownAddresses(int limit, long... streams) { - List<NetworkAddress> result = new LinkedList<>(); - String query = - "SELECT stream, address, port, services, time" + - " FROM Node WHERE stream IN (" + SqlStrings.join(streams) + ")" + - " ORDER BY TIME DESC" + - " LIMIT " + limit; - try ( - Connection connection = config.getConnection(); - Statement stmt = connection.createStatement(); - ResultSet rs = stmt.executeQuery(query) - ) { - while (rs.next()) { - result.add( - new NetworkAddress.Builder() - .stream(rs.getLong("stream")) - .ipv6(rs.getBytes("address")) - .port(rs.getInt("port")) - .services(rs.getLong("services")) - .time(rs.getLong("time")) - .build() - ); - } - } catch (Exception e) { - LOG.error(e.getMessage(), e); - throw new ApplicationException(e); - } - if (result.isEmpty()) { - synchronized (this) { - if (stableNodes == null) { - stableNodes = loadStableNodes(); - } - } - for (long stream : streams) { - Set<NetworkAddress> nodes = stableNodes.get(stream); - if (nodes != null && !nodes.isEmpty()) { - result.add(Collections.selectRandom(nodes)); - } - } - if (result.isEmpty()) { - // There might have been an error resolving domain names due to a missing internet exception. - // Try to load the stable nodes again next time. - stableNodes = null; - } - } - return result; - } - - @Override - public void offerAddresses(List<NetworkAddress> nodes) { - cleanUp(); - nodes.stream() - .filter(node -> node.getTime() < now() + 2 * MINUTE && node.getTime() > now() - 28 * DAY) - .forEach(node -> { - synchronized (this) { - NetworkAddress existing = loadExisting(node); - if (existing == null) { - insert(node); - } else if (node.getTime() > existing.getTime()) { - update(node); - } - } - }); - } - - private void insert(NetworkAddress node) { - try ( - Connection connection = config.getConnection(); - PreparedStatement ps = connection.prepareStatement( - "INSERT INTO Node (stream, address, port, services, time) " + - "VALUES (?, ?, ?, ?, ?)") - ) { - ps.setLong(1, node.getStream()); - ps.setBytes(2, node.getIPv6()); - ps.setInt(3, node.getPort()); - ps.setLong(4, node.getServices()); - ps.setLong(5, node.getTime()); - ps.executeUpdate(); - } catch (SQLException e) { - LOG.error(e.getMessage(), e); - } - } - - private void update(NetworkAddress node) { - try ( - Connection connection = config.getConnection(); - PreparedStatement ps = connection.prepareStatement( - "UPDATE Node SET services=?, time=? WHERE stream=? AND address=? AND port=?") - ) { - ps.setLong(1, node.getServices()); - ps.setLong(2, node.getTime()); - ps.setLong(3, node.getStream()); - ps.setBytes(4, node.getIPv6()); - ps.setInt(5, node.getPort()); - ps.executeUpdate(); - } catch (SQLException e) { - LOG.error(e.getMessage(), e); - } - } -} diff --git a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepository.java b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepository.java deleted file mode 100644 index c509e4a..0000000 --- a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepository.java +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright 2016 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.repository; - -import ch.dissem.bitmessage.InternalContext; -import ch.dissem.bitmessage.entity.ObjectMessage; -import ch.dissem.bitmessage.exception.ApplicationException; -import ch.dissem.bitmessage.factory.Factory; -import ch.dissem.bitmessage.ports.ProofOfWorkRepository; -import ch.dissem.bitmessage.utils.Strings; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.sql.*; -import java.util.LinkedList; -import java.util.List; - -import static ch.dissem.bitmessage.utils.Singleton.cryptography; - -/** - * @author Christian Basler - */ -public class JdbcProofOfWorkRepository extends JdbcHelper implements ProofOfWorkRepository, InternalContext.ContextHolder { - private static final Logger LOG = LoggerFactory.getLogger(JdbcProofOfWorkRepository.class); - private InternalContext ctx; - - public JdbcProofOfWorkRepository(JdbcConfig config) { - super(config); - } - - @Override - public Item getItem(byte[] initialHash) { - try ( - Connection connection = config.getConnection(); - PreparedStatement ps = connection.prepareStatement("SELECT data, version, nonce_trials_per_byte, " + - "extra_bytes, expiration_time, message_id FROM POW WHERE initial_hash=?") - ) { - ps.setBytes(1, initialHash); - try (ResultSet rs = ps.executeQuery()) { - if (rs.next()) { - Blob data = rs.getBlob("data"); - if (rs.getObject("message_id") == null) { - return new Item( - Factory.getObjectMessage(rs.getInt("version"), data.getBinaryStream(), (int) data.length()), - rs.getLong("nonce_trials_per_byte"), - rs.getLong("extra_bytes") - ); - } else { - return new Item( - Factory.getObjectMessage(rs.getInt("version"), data.getBinaryStream(), (int) data.length()), - rs.getLong("nonce_trials_per_byte"), - rs.getLong("extra_bytes"), - rs.getLong("expiration_time"), - ctx.getMessageRepository().getMessage(rs.getLong("message_id")) - ); - } - } else { - throw new IllegalArgumentException("Object requested that we don't have. Initial hash: " + Strings.hex(initialHash)); - } - } - } catch (SQLException e) { - LOG.error(e.getMessage(), e); - throw new ApplicationException(e); - } - } - - @Override - public List<byte[]> getItems() { - try ( - Connection connection = config.getConnection(); - Statement stmt = connection.createStatement(); - ResultSet rs = stmt.executeQuery("SELECT initial_hash FROM POW") - ) { - List<byte[]> result = new LinkedList<>(); - while (rs.next()) { - result.add(rs.getBytes("initial_hash")); - } - return result; - } catch (SQLException e) { - LOG.error(e.getMessage(), e); - throw new ApplicationException(e); - } - } - - @Override - public void putObject(Item item) { - try ( - Connection connection = config.getConnection(); - PreparedStatement ps = connection.prepareStatement("INSERT INTO POW (initial_hash, data, version, " + - "nonce_trials_per_byte, extra_bytes, expiration_time, message_id) " + - "VALUES (?, ?, ?, ?, ?, ?, ?)") - ) { - ps.setBytes(1, cryptography().getInitialHash(item.getObjectMessage())); - writeBlob(ps, 2, item.getObjectMessage()); - ps.setLong(3, item.getObjectMessage().getVersion()); - ps.setLong(4, item.getNonceTrialsPerByte()); - ps.setLong(5, item.getExtraBytes()); - - if (item.getMessage() == null) { - ps.setObject(6, null); - ps.setObject(7, null); - } else { - ps.setLong(6, item.getExpirationTime()); - ps.setLong(7, (Long) item.getMessage().getId()); - } - ps.executeUpdate(); - } catch (IOException | SQLException e) { - LOG.debug("Error storing object of type " + item.getObjectMessage().getPayload().getClass().getSimpleName(), e); - throw new ApplicationException(e); - } - } - - @Override - public void putObject(ObjectMessage object, long nonceTrialsPerByte, long extraBytes) { - putObject(new Item(object, nonceTrialsPerByte, extraBytes)); - } - - @Override - public void removeObject(byte[] initialHash) { - try ( - Connection connection = config.getConnection(); - PreparedStatement ps = connection.prepareStatement("DELETE FROM POW WHERE initial_hash=?") - ) { - ps.setBytes(1, initialHash); - ps.executeUpdate(); - } catch (SQLException e) { - LOG.debug(e.getMessage(), e); - } - } - - @Override - public void setContext(InternalContext context) { - this.ctx = context; - } -} diff --git a/repositories/src/main/kotlin/ch/dissem/bitmessage/repository/JdbcAddressRepository.kt b/repositories/src/main/kotlin/ch/dissem/bitmessage/repository/JdbcAddressRepository.kt new file mode 100644 index 0000000..29a0f87 --- /dev/null +++ b/repositories/src/main/kotlin/ch/dissem/bitmessage/repository/JdbcAddressRepository.kt @@ -0,0 +1,203 @@ +/* + * Copyright 2015 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.repository + +import ch.dissem.bitmessage.entity.BitmessageAddress +import ch.dissem.bitmessage.entity.payload.Pubkey +import ch.dissem.bitmessage.entity.payload.V3Pubkey +import ch.dissem.bitmessage.entity.payload.V4Pubkey +import ch.dissem.bitmessage.entity.valueobject.PrivateKey +import ch.dissem.bitmessage.factory.Factory +import ch.dissem.bitmessage.ports.AddressRepository +import org.slf4j.LoggerFactory +import java.io.ByteArrayOutputStream +import java.io.IOException +import java.sql.PreparedStatement +import java.sql.SQLException +import java.util.* + +class JdbcAddressRepository(config: JdbcConfig) : JdbcHelper(config), AddressRepository { + + override fun findContact(ripeOrTag: ByteArray) = find("private_key is null").firstOrNull { + if (it.version > 3) { + Arrays.equals(ripeOrTag, it.tag) + } else { + Arrays.equals(ripeOrTag, it.ripe) + } + } + + override fun findIdentity(ripeOrTag: ByteArray) = find("private_key is not null").firstOrNull { + if (it.version > 3) { + Arrays.equals(ripeOrTag, it.tag) + } else { + Arrays.equals(ripeOrTag, it.ripe) + } + } + + override fun getIdentities() = find("private_key IS NOT NULL") + + override fun getChans() = find("chan = '1'") + + override fun getSubscriptions() = find("subscribed = '1'") + + override fun getSubscriptions(broadcastVersion: Long): List<BitmessageAddress> = if (broadcastVersion > 4) { + find("subscribed = '1' AND version > 3") + } else { + find("subscribed = '1' AND version <= 3") + } + + override fun getContacts() = find("private_key IS NULL OR chan = '1'") + + private fun find(where: String): List<BitmessageAddress> { + val result = LinkedList<BitmessageAddress>() + try { + config.getConnection().use { connection -> + connection.createStatement().use { stmt -> + stmt.executeQuery(""" + SELECT address, alias, public_key, private_key, subscribed, chan + FROM Address + WHERE $where + """).use { rs -> + while (rs.next()) { + val address: BitmessageAddress + + val privateKeyStream = rs.getBinaryStream("private_key") + if (privateKeyStream == null) { + address = BitmessageAddress(rs.getString("address")) + rs.getBlob("public_key")?.let { publicKeyBlob -> + var pubkey: Pubkey = Factory.readPubkey(address.version, address.stream, + publicKeyBlob.binaryStream, publicKeyBlob.length().toInt(), false)!! + if (address.version == 4L && pubkey is V3Pubkey) { + pubkey = V4Pubkey(pubkey) + } + address.pubkey = pubkey + } + } else { + val privateKey = PrivateKey.read(privateKeyStream) + address = BitmessageAddress(privateKey) + } + address.alias = rs.getString("alias") + address.isSubscribed = rs.getBoolean("subscribed") + address.isChan = rs.getBoolean("chan") + + result.add(address) + } + } + } + } + } catch (e: SQLException) { + LOG.error(e.message, e) + } + + return result + } + + private fun exists(address: BitmessageAddress): Boolean { + config.getConnection().use { connection -> + connection.createStatement().use { stmt -> + stmt.executeQuery("SELECT '1' FROM Address " + + "WHERE address='" + address.address + "'").use { rs -> return rs.next() } + } + } + } + + override fun save(address: BitmessageAddress) { + try { + if (exists(address)) { + update(address) + } else { + insert(address) + } + } catch (e: IOException) { + LOG.error(e.message, e) + } catch (e: SQLException) { + LOG.error(e.message, e) + } + } + + private fun update(address: BitmessageAddress) { + val statement = StringBuilder("UPDATE Address SET alias=?") + if (address.pubkey != null) { + statement.append(", public_key=?") + } + if (address.privateKey != null) { + statement.append(", private_key=?") + } + statement.append(", subscribed=?, chan=? WHERE address=?") + config.getConnection().use { connection -> + connection.prepareStatement(statement.toString()).use { ps -> + var i = 0 + ps.setString(++i, address.alias) + if (address.pubkey != null) { + writePubkey(ps, ++i, address.pubkey) + } + if (address.privateKey != null) { + JdbcHelper.writeBlob(ps, ++i, address.privateKey) + } + ps.setBoolean(++i, address.isSubscribed) + ps.setBoolean(++i, address.isChan) + ps.setString(++i, address.address) + ps.executeUpdate() + } + } + } + + private fun insert(address: BitmessageAddress) { + config.getConnection().use { connection -> + connection.prepareStatement( + "INSERT INTO Address (address, version, alias, public_key, private_key, subscribed, chan) " + "VALUES (?, ?, ?, ?, ?, ?, ?)").use { ps -> + ps.setString(1, address.address) + ps.setLong(2, address.version) + ps.setString(3, address.alias) + writePubkey(ps, 4, address.pubkey) + JdbcHelper.writeBlob(ps, 5, address.privateKey) + ps.setBoolean(6, address.isSubscribed) + ps.setBoolean(7, address.isChan) + ps.executeUpdate() + } + } + } + + private fun writePubkey(ps: PreparedStatement, parameterIndex: Int, data: Pubkey?) { + if (data != null) { + val out = ByteArrayOutputStream() + data.writeUnencrypted(out) + ps.setBytes(parameterIndex, out.toByteArray()) + } else { + ps.setBytes(parameterIndex, null) + } + } + + override fun remove(address: BitmessageAddress) { + try { + config.getConnection().use { connection -> connection.createStatement().use { stmt -> stmt.executeUpdate("DELETE FROM Address WHERE address = '" + address.address + "'") } } + } catch (e: SQLException) { + LOG.error(e.message, e) + } + + } + + override fun getAddress(address: String): BitmessageAddress? { + val result = find("address = '$address'") + if (result.isNotEmpty()) return result[0] + return null + } + + companion object { + private val LOG = LoggerFactory.getLogger(JdbcAddressRepository::class.java) + } +} diff --git a/repositories/src/main/kotlin/ch/dissem/bitmessage/repository/JdbcConfig.kt b/repositories/src/main/kotlin/ch/dissem/bitmessage/repository/JdbcConfig.kt new file mode 100644 index 0000000..311bf70 --- /dev/null +++ b/repositories/src/main/kotlin/ch/dissem/bitmessage/repository/JdbcConfig.kt @@ -0,0 +1,39 @@ +/* + * Copyright 2015 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.repository + +import org.flywaydb.core.Flyway +import java.sql.DriverManager + +/** + * The base configuration for all JDBC based repositories. You should only make one instance, + * as flyway initializes/updates the database at object creation. + */ +open class JdbcConfig @JvmOverloads constructor( + protected val dbUrl: String = "jdbc:h2:~/jabit;AUTO_SERVER=TRUE", + protected val dbUser: String = "sa", + protected val dbPassword: String? = null +) { + protected val flyway = Flyway() + + init { + flyway.setDataSource(dbUrl, dbUser, dbPassword) + flyway.migrate() + } + + fun getConnection() = DriverManager.getConnection(dbUrl, dbUser, dbPassword) +} diff --git a/repositories/src/main/kotlin/ch/dissem/bitmessage/repository/JdbcHelper.kt b/repositories/src/main/kotlin/ch/dissem/bitmessage/repository/JdbcHelper.kt new file mode 100644 index 0000000..ce8ca1f --- /dev/null +++ b/repositories/src/main/kotlin/ch/dissem/bitmessage/repository/JdbcHelper.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2015 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.repository + +import ch.dissem.bitmessage.entity.Streamable +import java.io.ByteArrayOutputStream +import java.sql.PreparedStatement + +/** + * Helper class that does Flyway migration, provides JDBC connections and some helper methods. + */ +abstract class JdbcHelper protected constructor(protected val config: JdbcConfig) { + companion object { + @JvmStatic fun writeBlob(ps: PreparedStatement, parameterIndex: Int, data: Streamable?) { + if (data == null) { + ps.setBytes(parameterIndex, null) + } else { + val os = ByteArrayOutputStream() + data.write(os) + ps.setBytes(parameterIndex, os.toByteArray()) + } + } + } +} diff --git a/repositories/src/main/kotlin/ch/dissem/bitmessage/repository/JdbcInventory.kt b/repositories/src/main/kotlin/ch/dissem/bitmessage/repository/JdbcInventory.kt new file mode 100644 index 0000000..637c5dd --- /dev/null +++ b/repositories/src/main/kotlin/ch/dissem/bitmessage/repository/JdbcInventory.kt @@ -0,0 +1,166 @@ +/* + * Copyright 2015 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.repository + +import ch.dissem.bitmessage.entity.ObjectMessage +import ch.dissem.bitmessage.entity.payload.ObjectType +import ch.dissem.bitmessage.entity.valueobject.InventoryVector +import ch.dissem.bitmessage.factory.Factory +import ch.dissem.bitmessage.ports.Inventory +import ch.dissem.bitmessage.utils.SqlStrings.join +import ch.dissem.bitmessage.utils.UnixTime.MINUTE +import ch.dissem.bitmessage.utils.UnixTime.now +import org.slf4j.LoggerFactory +import java.sql.SQLException +import java.util.* +import java.util.concurrent.ConcurrentHashMap + +class JdbcInventory(config: JdbcConfig) : JdbcHelper(config), Inventory { + + private val cache = ConcurrentHashMap<Long, MutableMap<InventoryVector, Long>>() + + override fun getInventory(vararg streams: Long): List<InventoryVector> { + val result = LinkedList<InventoryVector>() + for (stream in streams) { + getCache(stream).entries.stream() + .filter { e -> e.value > now } + .forEach { e -> result.add(e.key) } + } + return result + } + + private fun getCache(stream: Long): MutableMap<InventoryVector, Long> { + var result: MutableMap<InventoryVector, Long>? = cache[stream] + if (result == null) { + synchronized(cache) { + if (cache[stream] == null) { + val map = ConcurrentHashMap<InventoryVector, Long>() + cache.put(stream, map) + result = map + try { + config.getConnection().use { connection -> + connection.createStatement().use { stmt -> + stmt.executeQuery("SELECT hash, expires FROM Inventory " + + "WHERE expires > " + (now - 5 * MINUTE) + " AND stream = " + stream).use { rs -> + while (rs.next()) { + map.put(InventoryVector(rs.getBytes("hash")), rs.getLong("expires")) + } + } + } + } + } catch (e: SQLException) { + LOG.error(e.message, e) + } + } + } + } + return result!! + } + + override fun getMissing(offer: List<InventoryVector>, vararg streams: Long): List<InventoryVector> = offer - streams.flatMap { getCache(it).keys } + + override fun getObject(vector: InventoryVector): ObjectMessage? { + config.getConnection().use { connection -> + connection.createStatement().use { stmt -> + stmt.executeQuery("SELECT data, version FROM Inventory WHERE hash = X'$vector'").use { rs -> + if (rs.next()) { + val data = rs.getBlob("data") + return Factory.getObjectMessage(rs.getInt("version"), data.binaryStream, data.length().toInt()) + } else { + LOG.info("Object requested that we don't have. IV: " + vector) + return null + } + } + } + } + } + + override fun getObjects(stream: Long, version: Long, vararg types: ObjectType): List<ObjectMessage> { + val query = StringBuilder("SELECT data, version FROM Inventory WHERE 1=1") + if (stream > 0) { + query.append(" AND stream = ").append(stream) + } + if (version > 0) { + query.append(" AND version = ").append(version) + } + if (types.isNotEmpty()) { + query.append(" AND type IN (").append(join(*types)).append(')') + } + config.getConnection().use { connection -> + connection.createStatement().use { stmt -> + stmt.executeQuery(query.toString()).use { rs -> + val result = LinkedList<ObjectMessage>() + while (rs.next()) { + val data = rs.getBlob("data") + result.add(Factory.getObjectMessage(rs.getInt("version"), data.binaryStream, data.length().toInt())!!) + } + return result + } + } + } + } + + override fun storeObject(objectMessage: ObjectMessage) { + if (getCache(objectMessage.stream).containsKey(objectMessage.inventoryVector)) + return + + try { + config.getConnection().use { connection -> + connection.prepareStatement("INSERT INTO Inventory " + "(hash, stream, expires, data, type, version) VALUES (?, ?, ?, ?, ?, ?)").use { ps -> + val iv = objectMessage.inventoryVector + LOG.trace("Storing object " + iv) + ps.setBytes(1, iv.hash) + ps.setLong(2, objectMessage.stream) + ps.setLong(3, objectMessage.expiresTime) + JdbcHelper.Companion.writeBlob(ps, 4, objectMessage) + ps.setLong(5, objectMessage.type) + ps.setLong(6, objectMessage.version) + ps.executeUpdate() + getCache(objectMessage.stream).put(iv, objectMessage.expiresTime) + } + } + } catch (e: SQLException) { + LOG.debug("Error storing object of type " + objectMessage.payload.javaClass.simpleName, e) + } catch (e: Exception) { + LOG.error(e.message, e) + } + } + + override fun contains(objectMessage: ObjectMessage): Boolean { + return getCache(objectMessage.stream).any { (key, _) -> key == objectMessage.inventoryVector } + } + + override fun cleanup() { + try { + config.getConnection().use { connection -> + connection.createStatement().use { stmt -> + stmt.executeUpdate("DELETE FROM Inventory WHERE expires < " + (now - 5 * MINUTE)) + } + } + } catch (e: SQLException) { + LOG.debug(e.message, e) + } + + for (c in cache.values) { + c.entries.removeIf { e -> e.value < now - 5 * MINUTE } + } + } + + companion object { + private val LOG = LoggerFactory.getLogger(JdbcInventory::class.java) + } +} diff --git a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcMessageRepository.kt b/repositories/src/main/kotlin/ch/dissem/bitmessage/repository/JdbcMessageRepository.kt similarity index 96% rename from repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcMessageRepository.kt rename to repositories/src/main/kotlin/ch/dissem/bitmessage/repository/JdbcMessageRepository.kt index 2f4f851..6daf32c 100644 --- a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcMessageRepository.kt +++ b/repositories/src/main/kotlin/ch/dissem/bitmessage/repository/JdbcMessageRepository.kt @@ -21,7 +21,7 @@ import ch.dissem.bitmessage.entity.valueobject.InventoryVector import ch.dissem.bitmessage.entity.valueobject.Label import ch.dissem.bitmessage.ports.AbstractMessageRepository import ch.dissem.bitmessage.ports.MessageRepository -import ch.dissem.bitmessage.repository.JdbcHelper.writeBlob +import ch.dissem.bitmessage.repository.JdbcHelper.Companion.writeBlob import org.slf4j.LoggerFactory import java.io.IOException import java.sql.Connection @@ -34,7 +34,7 @@ class JdbcMessageRepository(private val config: JdbcConfig) : AbstractMessageRep override fun findLabels(where: String): List<Label> { try { - config.connection.use { + config.getConnection().use { connection -> return findLabels(connection, where) } @@ -66,7 +66,7 @@ class JdbcMessageRepository(private val config: JdbcConfig) : AbstractMessageRep "SELECT id FROM Label WHERE type = '" + Label.Type.UNREAD.name + "'))" try { - config.connection.use { connection -> + config.getConnection().use { connection -> connection.createStatement().use { stmt -> stmt.executeQuery("SELECT count(*) FROM Message WHERE $where").use { rs -> if (rs.next()) { @@ -84,7 +84,7 @@ class JdbcMessageRepository(private val config: JdbcConfig) : AbstractMessageRep override fun find(where: String): List<Plaintext> { val result = LinkedList<Plaintext>() try { - config.connection.use { connection -> + config.getConnection().use { connection -> connection.createStatement().use { stmt -> stmt.executeQuery( """SELECT id, iv, type, sender, recipient, data, ack_data, sent, received, initial_hash, status, ttl, retries, next_try, conversation @@ -100,13 +100,13 @@ class JdbcMessageRepository(private val config: JdbcConfig) : AbstractMessageRep builder.from(ctx.addressRepository.getAddress(rs.getString("sender"))!!) builder.to(ctx.addressRepository.getAddress(rs.getString("recipient"))) builder.ackData(rs.getBytes("ack_data")) - builder.sent(rs.getObject("sent", Long::class.java)) - builder.received(rs.getObject("received", Long::class.java)) + builder.sent(rs.getObject("sent") as Long?) + builder.received(rs.getObject("received") as Long?) builder.status(Plaintext.Status.valueOf(rs.getString("status"))) builder.ttl(rs.getLong("ttl")) builder.retries(rs.getInt("retries")) - builder.nextTry(rs.getObject("next_try", Long::class.java)) - builder.conversation(rs.getObject("conversation", UUID::class.java)) + builder.nextTry(rs.getObject("next_try") as Long?) + builder.conversation(rs.getObject("conversation") as UUID? ?: UUID.randomUUID()) builder.labels(findLabels(connection, "id IN (SELECT label_id FROM Message_Label WHERE message_id=$id) ORDER BY ord")) val message = builder.build() @@ -144,7 +144,7 @@ class JdbcMessageRepository(private val config: JdbcConfig) : AbstractMessageRep saveContactIfNecessary(message.from) saveContactIfNecessary(message.to) - config.connection.use { connection -> + config.getConnection().use { connection -> try { connection.autoCommit = false save(connection, message) @@ -261,7 +261,7 @@ class JdbcMessageRepository(private val config: JdbcConfig) : AbstractMessageRep override fun remove(message: Plaintext) { try { - config.connection.use { connection -> + config.getConnection().use { connection -> connection.autoCommit = false try { connection.createStatement().use { stmt -> @@ -294,7 +294,7 @@ class JdbcMessageRepository(private val config: JdbcConfig) : AbstractMessageRep } val result = LinkedList<UUID>() try { - config.connection.use { connection -> + config.getConnection().use { connection -> connection.createStatement().use { stmt -> stmt.executeQuery( "SELECT DISTINCT conversation FROM Message WHERE " + where).use { rs -> diff --git a/repositories/src/main/kotlin/ch/dissem/bitmessage/repository/JdbcNodeRegistry.kt b/repositories/src/main/kotlin/ch/dissem/bitmessage/repository/JdbcNodeRegistry.kt new file mode 100644 index 0000000..ccdbe75 --- /dev/null +++ b/repositories/src/main/kotlin/ch/dissem/bitmessage/repository/JdbcNodeRegistry.kt @@ -0,0 +1,188 @@ +/* + * Copyright 2016 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.repository + +import ch.dissem.bitmessage.entity.valueobject.NetworkAddress +import ch.dissem.bitmessage.ports.NodeRegistry +import ch.dissem.bitmessage.ports.NodeRegistryHelper.loadStableNodes +import ch.dissem.bitmessage.utils.Collections +import ch.dissem.bitmessage.utils.SqlStrings +import ch.dissem.bitmessage.utils.Strings +import ch.dissem.bitmessage.utils.UnixTime.DAY +import ch.dissem.bitmessage.utils.UnixTime.MINUTE +import ch.dissem.bitmessage.utils.UnixTime.now +import org.slf4j.LoggerFactory +import java.sql.SQLException +import java.util.* + +class JdbcNodeRegistry(config: JdbcConfig) : JdbcHelper(config), NodeRegistry { + private var stableNodes: Map<Long, Set<NetworkAddress>> = emptyMap() + get() { + if (field.isEmpty()) + field = loadStableNodes() + return field + } + + init { + cleanUp() + } + + private fun cleanUp() { + try { + config.getConnection().use { connection -> + connection.prepareStatement("DELETE FROM Node WHERE time<?").use { ps -> + ps.setLong(1, now - 28 * DAY) + ps.executeUpdate() + } + } + } catch (e: SQLException) { + LOG.error(e.message, e) + } + } + + private fun loadExisting(node: NetworkAddress): NetworkAddress? { + val query = """ + SELECT stream, address, port, services, time + FROM Node + WHERE stream = ${node.stream} AND address = X'${Strings.hex(node.IPv6)}' AND port = ${node.port} + """ + config.getConnection().use { connection -> + connection.createStatement().use { stmt -> + stmt.executeQuery(query).use { rs -> + if (rs.next()) { + return NetworkAddress.Builder() + .stream(rs.getLong("stream")) + .ipv6(rs.getBytes("address")) + .port(rs.getInt("port")) + .services(rs.getLong("services")) + .time(rs.getLong("time")) + .build() + } else { + return null + } + } + } + } + } + + override fun clear() { + try { + config.getConnection().use { connection -> + connection.prepareStatement("DELETE FROM Node").use { ps -> ps.executeUpdate() } + } + } catch (e: SQLException) { + LOG.error(e.message, e) + } + } + + override fun getKnownAddresses(limit: Int, vararg streams: Long): List<NetworkAddress> { + val result = LinkedList<NetworkAddress>() + val query = """ + SELECT stream, address, port, services, time + FROM Node + WHERE stream IN (${SqlStrings.join(*streams)}) + ORDER BY TIME DESC LIMIT $limit + """ + config.getConnection().use { connection -> + connection.createStatement().use { stmt -> + stmt.executeQuery(query).use { rs -> + while (rs.next()) { + result.add( + NetworkAddress.Builder() + .stream(rs.getLong("stream")) + .ipv6(rs.getBytes("address")) + .port(rs.getInt("port")) + .services(rs.getLong("services")) + .time(rs.getLong("time")) + .build() + ) + } + } + } + } + + if (result.isEmpty()) { + streams + .asSequence() + .mapNotNull { stableNodes[it] } + .filter { it.isNotEmpty() } + .mapTo(result) { Collections.selectRandom(it) } + if (result.isEmpty()) { + // There might have been an error resolving domain names due to a missing internet connection. + // Try to load the stable nodes again next time. + stableNodes = emptyMap() + } + } + return result + } + + override fun offerAddresses(nodes: List<NetworkAddress>) { + cleanUp() + nodes.stream() + .filter { (time) -> time < now + 2 * MINUTE && time > now - 28 * DAY } + .forEach { node -> + synchronized(this) { + val existing = loadExisting(node) + if (existing == null) { + insert(node) + } else if (node.time > existing.time) { + update(node) + } + } + } + } + + private fun insert(node: NetworkAddress) { + try { + config.getConnection().use { connection -> + connection.prepareStatement( + "INSERT INTO Node (stream, address, port, services, time) VALUES (?, ?, ?, ?, ?)").use { ps -> + ps.setLong(1, node.stream) + ps.setBytes(2, node.IPv6) + ps.setInt(3, node.port) + ps.setLong(4, node.services) + ps.setLong(5, node.time) + ps.executeUpdate() + } + } + } catch (e: SQLException) { + LOG.error(e.message, e) + } + } + + private fun update(node: NetworkAddress) { + try { + config.getConnection().use { connection -> + connection.prepareStatement( + "UPDATE Node SET services=?, time=? WHERE stream=? AND address=? AND port=?").use { ps -> + ps.setLong(1, node.services) + ps.setLong(2, node.time) + ps.setLong(3, node.stream) + ps.setBytes(4, node.IPv6) + ps.setInt(5, node.port) + ps.executeUpdate() + } + } + } catch (e: SQLException) { + LOG.error(e.message, e) + } + } + + companion object { + private val LOG = LoggerFactory.getLogger(JdbcNodeRegistry::class.java) + } +} diff --git a/repositories/src/main/kotlin/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepository.kt b/repositories/src/main/kotlin/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepository.kt new file mode 100644 index 0000000..96567de --- /dev/null +++ b/repositories/src/main/kotlin/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepository.kt @@ -0,0 +1,130 @@ +/* + * Copyright 2016 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.repository + +import ch.dissem.bitmessage.InternalContext +import ch.dissem.bitmessage.entity.ObjectMessage +import ch.dissem.bitmessage.factory.Factory +import ch.dissem.bitmessage.ports.ProofOfWorkRepository +import ch.dissem.bitmessage.utils.Singleton.cryptography +import ch.dissem.bitmessage.utils.Strings +import org.slf4j.LoggerFactory +import java.sql.SQLException + +/** + * @author Christian Basler + */ +class JdbcProofOfWorkRepository(config: JdbcConfig) : JdbcHelper(config), ProofOfWorkRepository, InternalContext.ContextHolder { + private lateinit var ctx: InternalContext + + override fun getItem(initialHash: ByteArray): ProofOfWorkRepository.Item { + config.getConnection().use { connection -> + connection.prepareStatement(""" + SELECT data, version, nonce_trials_per_byte, extra_bytes, expiration_time, message_id + FROM POW + WHERE initial_hash=? + """).use { ps -> + ps.setBytes(1, initialHash) + ps.executeQuery().use { rs -> + if (rs.next()) { + val data = rs.getBlob("data") + if (rs.getObject("message_id") == null) { + return ProofOfWorkRepository.Item( + Factory.getObjectMessage(rs.getInt("version"), data.binaryStream, data.length().toInt())!!, + rs.getLong("nonce_trials_per_byte"), + rs.getLong("extra_bytes") + ) + } else { + return ProofOfWorkRepository.Item( + Factory.getObjectMessage(rs.getInt("version"), data.binaryStream, data.length().toInt())!!, + rs.getLong("nonce_trials_per_byte"), + rs.getLong("extra_bytes"), + rs.getLong("expiration_time"), + ctx.messageRepository.getMessage(rs.getLong("message_id")) + ) + } + } else { + throw IllegalArgumentException("Object requested that we don't have. Initial hash: " + Strings.hex(initialHash)) + } + } + } + } + } + + override fun getItems(): List<ByteArray> { + config.getConnection().use { connection -> + connection.createStatement().use { stmt -> + stmt.executeQuery("SELECT initial_hash FROM POW").use { rs -> + val result = mutableListOf<ByteArray>() + while (rs.next()) { + result.add(rs.getBytes("initial_hash")) + } + return result + } + } + } + } + + override fun putObject(item: ProofOfWorkRepository.Item) { + config.getConnection().use { connection -> + connection.prepareStatement(""" + INSERT INTO + POW (initial_hash, data, version, nonce_trials_per_byte, extra_bytes, expiration_time, message_id) + VALUES (?, ?, ?, ?, ?, ?, ?)""").use { ps -> + ps.setBytes(1, cryptography().getInitialHash(item.objectMessage)) + JdbcHelper.Companion.writeBlob(ps, 2, item.objectMessage) + ps.setLong(3, item.objectMessage.version) + ps.setLong(4, item.nonceTrialsPerByte) + ps.setLong(5, item.extraBytes) + + if (item.message == null) { + ps.setObject(6, null) + ps.setObject(7, null) + } else { + ps.setLong(6, item.expirationTime!!) + ps.setLong(7, item.message!!.id as Long) + } + ps.executeUpdate() + } + } + } + + override fun putObject(objectMessage: ObjectMessage, nonceTrialsPerByte: Long, extraBytes: Long) { + putObject(ProofOfWorkRepository.Item(objectMessage, nonceTrialsPerByte, extraBytes)) + } + + override fun removeObject(initialHash: ByteArray) { + try { + config.getConnection().use { connection -> + connection.prepareStatement("DELETE FROM POW WHERE initial_hash=?").use { ps -> + ps.setBytes(1, initialHash) + ps.executeUpdate() + } + } + } catch (e: SQLException) { + LOG.debug(e.message, e) + } + } + + override fun setContext(context: InternalContext) { + ctx = context + } + + companion object { + private val LOG = LoggerFactory.getLogger(JdbcProofOfWorkRepository::class.java) + } +} diff --git a/repositories/src/test/java/ch/dissem/bitmessage/repository/TestJdbcConfig.java b/repositories/src/test/java/ch/dissem/bitmessage/repository/TestJdbcConfig.java index edb8f85..8b96ed8 100644 --- a/repositories/src/test/java/ch/dissem/bitmessage/repository/TestJdbcConfig.java +++ b/repositories/src/test/java/ch/dissem/bitmessage/repository/TestJdbcConfig.java @@ -42,7 +42,7 @@ public class TestJdbcConfig extends JdbcConfig { } public void reset() { - flyway.clean(); - flyway.migrate(); + getFlyway().clean(); + getFlyway().migrate(); } } diff --git a/wif/src/main/java/ch/dissem/bitmessage/wif/WifExporter.kt b/wif/src/main/kotlin/ch/dissem/bitmessage/wif/WifExporter.kt similarity index 100% rename from wif/src/main/java/ch/dissem/bitmessage/wif/WifExporter.kt rename to wif/src/main/kotlin/ch/dissem/bitmessage/wif/WifExporter.kt diff --git a/wif/src/main/java/ch/dissem/bitmessage/wif/WifImporter.kt b/wif/src/main/kotlin/ch/dissem/bitmessage/wif/WifImporter.kt similarity index 100% rename from wif/src/main/java/ch/dissem/bitmessage/wif/WifImporter.kt rename to wif/src/main/kotlin/ch/dissem/bitmessage/wif/WifImporter.kt diff --git a/wif/src/test/java/ch/dissem/bitmessage/wif/WifExporterTest.kt b/wif/src/test/kotlin/ch/dissem/bitmessage/wif/WifExporterTest.kt similarity index 100% rename from wif/src/test/java/ch/dissem/bitmessage/wif/WifExporterTest.kt rename to wif/src/test/kotlin/ch/dissem/bitmessage/wif/WifExporterTest.kt diff --git a/wif/src/test/java/ch/dissem/bitmessage/wif/WifImporterTest.kt b/wif/src/test/kotlin/ch/dissem/bitmessage/wif/WifImporterTest.kt similarity index 100% rename from wif/src/test/java/ch/dissem/bitmessage/wif/WifImporterTest.kt rename to wif/src/test/kotlin/ch/dissem/bitmessage/wif/WifImporterTest.kt From aee5debdd23b585ae283bec574136ab3afedce1a Mon Sep 17 00:00:00 2001 From: Christian Basler <chrigu.meyer@gmail.com> Date: Tue, 27 Jun 2017 17:22:48 +0200 Subject: [PATCH 06/10] Fixed system test and added some fixes for Java backwards compatibility --- .../bitmessage/DefaultMessageListener.kt | 25 +++++----- .../ch/dissem/bitmessage/entity/Plaintext.kt | 16 +++--- .../bitmessage/entity/payload/CryptoBox.kt | 2 +- .../entity/payload/GenericPayload.kt | 2 +- .../bitmessage/entity/payload/GetPubkey.kt | 2 +- .../dissem/bitmessage/entity/payload/Msg.kt | 2 +- .../bitmessage/entity/payload/ObjectType.kt | 2 +- .../bitmessage/entity/payload/V2Pubkey.kt | 2 +- .../bitmessage/entity/payload/V3Pubkey.kt | 2 +- .../bitmessage/entity/payload/V4Broadcast.kt | 2 +- .../bitmessage/entity/payload/V4Pubkey.kt | 2 +- .../bitmessage/entity/payload/V5Broadcast.kt | 2 +- .../ch/dissem/bitmessage/utils/Property.kt | 3 +- .../cryptography/sc/SpongyCryptography.kt | 2 +- demo/build.gradle | 2 +- .../java/ch/dissem/bitmessage/SystemTest.java | 13 ++--- .../bitmessage/networking/nio/Connection.kt | 8 +-- .../networking/nio/NioNetworkHandler.kt | 2 +- .../networking/TestNodeRegistry.java | 49 ------------------- .../bitmessage/networking/TestNodeRegistry.kt | 41 ++++++++++++++++ .../repository/JdbcMessageRepository.kt | 2 +- 21 files changed, 89 insertions(+), 94 deletions(-) delete mode 100644 networking/src/test/java/ch/dissem/bitmessage/networking/TestNodeRegistry.java create mode 100644 networking/src/test/java/ch/dissem/bitmessage/networking/TestNodeRegistry.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.kt b/core/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.kt index 597f5a3..826c9cd 100644 --- a/core/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.kt +++ b/core/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.kt @@ -133,21 +133,20 @@ internal open class DefaultMessageListener( } protected fun receive(objectMessage: ObjectMessage, broadcast: Broadcast) { - val tag = if (broadcast is V5Broadcast) broadcast.tag else null - for (subscription in ctx.addressRepository.getSubscriptions(broadcast.version)) { - if (tag != null && !Arrays.equals(tag, subscription.tag)) { - continue - } - try { - broadcast.decrypt(subscription.publicDecryptionKey) - if (!objectMessage.isSignatureValid(broadcast.plaintext!!.from.pubkey!!)) { - LOG.warn("Broadcast with IV " + objectMessage.inventoryVector + " was successfully decrypted, but signature check failed. Ignoring.") - } else { - receive(objectMessage.inventoryVector, broadcast.plaintext!!) + val tag = (broadcast as? V5Broadcast)?.tag + ctx.addressRepository.getSubscriptions(broadcast.version) + .filter { tag == null || Arrays.equals(tag, it.tag) } + .forEach { + try { + broadcast.decrypt(it.publicDecryptionKey) + if (!objectMessage.isSignatureValid(broadcast.plaintext!!.from.pubkey!!)) { + LOG.warn("Broadcast with IV " + objectMessage.inventoryVector + " was successfully decrypted, but signature check failed. Ignoring.") + } else { + receive(objectMessage.inventoryVector, broadcast.plaintext!!) + } + } catch (_: DecryptionFailedException) { } - } catch (_: DecryptionFailedException) { } - } } protected fun receive(iv: InventoryVector, msg: Plaintext) { diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.kt b/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.kt index 462ae83..3dcf0dc 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.kt +++ b/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.kt @@ -36,14 +36,14 @@ import java.util.* import java.util.Collections import kotlin.collections.HashSet -fun message(encoding: Plaintext.Encoding, subject: String, body: String): ByteArray = when (encoding) { +internal fun message(encoding: Plaintext.Encoding, subject: String, body: String): ByteArray = when (encoding) { SIMPLE -> "Subject:$subject\nBody:$body".toByteArray() EXTENDED -> Message.Builder().subject(subject).body(body).build().zip() TRIVIAL -> (subject + body).toByteArray() IGNORE -> ByteArray(0) } -fun ackData(type: Plaintext.Type, ackData: ByteArray?): ByteArray? { +internal fun ackData(type: Plaintext.Type, ackData: ByteArray?): ByteArray? { if (ackData != null) { return ackData } else if (type == MSG) { @@ -67,6 +67,7 @@ class Plaintext private constructor( val conversationId: UUID = UUID.randomUUID(), var inventoryVector: InventoryVector? = null, var signature: ByteArray? = null, + sent: Long? = null, val received: Long? = null, var initialHash: ByteArray? = null, ttl: Long = TTL.msg, @@ -117,7 +118,7 @@ class Plaintext private constructor( } val encoding: Encoding? by lazy { Encoding.fromCode(encodingCode) } - var sent: Long? = null + var sent: Long? = sent private set var retries: Int = 0 private set @@ -145,9 +146,9 @@ class Plaintext private constructor( type = type, from = from, to = to, - encoding = encoding.code, + encodingCode = encoding.code, message = message, - ackMessage = ackData(type, ackData), + ackData = ackData(type, ackData), conversationId = conversationId, inventoryVector = inventoryVector, signature = signature, @@ -214,9 +215,9 @@ class Plaintext private constructor( type = type, from = from, to = to, - encoding = encoding.code, + encoding = encoding, message = message(encoding, subject, body), - ackMessage = ackData(type, ackData), + ackData = ackData(type, ackData), conversationId = conversationId, inventoryVector = null, signature = null, @@ -248,6 +249,7 @@ class Plaintext private constructor( conversationId = builder.conversation ?: UUID.randomUUID(), inventoryVector = builder.inventoryVector, signature = builder.signature, + sent = builder.sent, received = builder.received, initialHash = null, ttl = builder.ttl, diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.kt b/core/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.kt index d6df388..29f26fe 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.kt +++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.kt @@ -195,7 +195,7 @@ class CryptoBox : Streamable { companion object { private val LOG = LoggerFactory.getLogger(CryptoBox::class.java) - fun read(stream: InputStream, length: Int): CryptoBox { + @JvmStatic fun read(stream: InputStream, length: Int): CryptoBox { val counter = AccessCounter() return Builder() .IV(Decode.bytes(stream, 16, counter)) diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/GenericPayload.kt b/core/src/main/java/ch/dissem/bitmessage/entity/payload/GenericPayload.kt index 336630a..10f0448 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/GenericPayload.kt +++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/GenericPayload.kt @@ -53,7 +53,7 @@ class GenericPayload(version: Long, override val stream: Long, val data: ByteArr } companion object { - fun read(version: Long, stream: Long, `is`: InputStream, length: Int): GenericPayload { + @JvmStatic fun read(version: Long, stream: Long, `is`: InputStream, length: Int): GenericPayload { return GenericPayload(version, stream, Decode.bytes(`is`, length)) } } diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/GetPubkey.kt b/core/src/main/java/ch/dissem/bitmessage/entity/payload/GetPubkey.kt index 3ae9370..e218d6d 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/GetPubkey.kt +++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/GetPubkey.kt @@ -58,7 +58,7 @@ class GetPubkey : ObjectPayload { } companion object { - fun read(`is`: InputStream, stream: Long, length: Int, version: Long): GetPubkey { + @JvmStatic fun read(`is`: InputStream, stream: Long, length: Int, version: Long): GetPubkey { return GetPubkey(version, stream, Decode.bytes(`is`, length)) } } diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/Msg.kt b/core/src/main/java/ch/dissem/bitmessage/entity/payload/Msg.kt index 597220e..c2ebd8b 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/Msg.kt +++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/Msg.kt @@ -96,7 +96,7 @@ class Msg : ObjectPayload, Encrypted, PlaintextHolder { companion object { val ACK_LENGTH = 32 - fun read(`in`: InputStream, stream: Long, length: Int): Msg { + @JvmStatic fun read(`in`: InputStream, stream: Long, length: Int): Msg { return Msg(stream, CryptoBox.read(`in`, length)) } } diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectType.kt b/core/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectType.kt index 967a3fd..ec7065a 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectType.kt +++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectType.kt @@ -26,7 +26,7 @@ enum class ObjectType constructor(val number: Long) { BROADCAST(3); companion object { - fun fromNumber(number: Long): ObjectType? { + @JvmStatic fun fromNumber(number: Long): ObjectType? { return values().firstOrNull { it.number == number } } } diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V2Pubkey.kt b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V2Pubkey.kt index 28acf6f..c1c7253 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V2Pubkey.kt +++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V2Pubkey.kt @@ -80,7 +80,7 @@ open class V2Pubkey constructor(version: Long, override val stream: Long, overri } companion object { - fun read(`in`: InputStream, stream: Long): V2Pubkey { + @JvmStatic fun read(`in`: InputStream, stream: Long): V2Pubkey { return V2Pubkey( version = 2, stream = stream, diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V3Pubkey.kt b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V3Pubkey.kt index 80dec2b..53faed8 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V3Pubkey.kt +++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V3Pubkey.kt @@ -134,7 +134,7 @@ class V3Pubkey protected constructor( } companion object { - fun read(`is`: InputStream, stream: Long): V3Pubkey { + @JvmStatic fun read(`is`: InputStream, stream: Long): V3Pubkey { return V3Pubkey( version = 3, stream = stream, diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V4Broadcast.kt b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V4Broadcast.kt index b5ef096..e270754 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V4Broadcast.kt +++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V4Broadcast.kt @@ -52,7 +52,7 @@ open class V4Broadcast : Broadcast { } companion object { - fun read(`in`: InputStream, stream: Long, length: Int): V4Broadcast { + @JvmStatic fun read(`in`: InputStream, stream: Long, length: Int): V4Broadcast { return V4Broadcast(4, stream, CryptoBox.read(`in`, length), null) } } diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V4Pubkey.kt b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V4Pubkey.kt index d755864..1fc1196 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V4Pubkey.kt +++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V4Pubkey.kt @@ -127,7 +127,7 @@ class V4Pubkey : Pubkey, Encrypted { } companion object { - fun read(`in`: InputStream, stream: Long, length: Int, encrypted: Boolean): V4Pubkey { + @JvmStatic fun read(`in`: InputStream, stream: Long, length: Int, encrypted: Boolean): V4Pubkey { if (encrypted) return V4Pubkey(stream, Decode.bytes(`in`, 32), diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V5Broadcast.kt b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V5Broadcast.kt index 4ab9af9..68b7454 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V5Broadcast.kt +++ b/core/src/main/java/ch/dissem/bitmessage/entity/payload/V5Broadcast.kt @@ -51,7 +51,7 @@ class V5Broadcast : V4Broadcast { } companion object { - fun read(`is`: InputStream, stream: Long, length: Int): V5Broadcast { + @JvmStatic fun read(`is`: InputStream, stream: Long, length: Int): V5Broadcast { return V5Broadcast(stream, Decode.bytes(`is`, 32), CryptoBox.read(`is`, length - 32)) } } diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/Property.kt b/core/src/main/java/ch/dissem/bitmessage/utils/Property.kt index bea1621..323c5cb 100644 --- a/core/src/main/java/ch/dissem/bitmessage/utils/Property.kt +++ b/core/src/main/java/ch/dissem/bitmessage/utils/Property.kt @@ -28,7 +28,8 @@ package ch.dissem.bitmessage.utils class Property private constructor(val name: String, val value: Any? = null, val properties: Array<Property> = emptyArray()) { constructor(name: String, value: Any) : this(name = name, value = value, properties = emptyArray()) - constructor(name: String, vararg properties: Property) : this(name, null, Array(properties.size, { i -> properties[i] })) + constructor(name: String, vararg properties: Property) : this(name, null, arrayOf(*properties)) + constructor(name: String, properties: List<Property>) : this(name, null, properties.toTypedArray()) /** * Returns the property if available or `null` otherwise. diff --git a/cryptography-sc/src/main/kotlin/ch/dissem/bitmessage/cryptography/sc/SpongyCryptography.kt b/cryptography-sc/src/main/kotlin/ch/dissem/bitmessage/cryptography/sc/SpongyCryptography.kt index 1465fb3..1db64b3 100644 --- a/cryptography-sc/src/main/kotlin/ch/dissem/bitmessage/cryptography/sc/SpongyCryptography.kt +++ b/cryptography-sc/src/main/kotlin/ch/dissem/bitmessage/cryptography/sc/SpongyCryptography.kt @@ -40,7 +40,7 @@ import java.util.* * As Spongycastle can't be used on the Oracle JVM, and Bouncycastle doesn't work properly on Android (thanks, Google), * this is the Spongycastle implementation. */ -class SpongyCryptography : AbstractCryptography(BouncyCastleProvider()) { +open class SpongyCryptography : AbstractCryptography(BouncyCastleProvider()) { override fun crypt(encrypt: Boolean, data: ByteArray, key_e: ByteArray, initializationVector: ByteArray): ByteArray { val cipher = PaddedBufferedBlockCipher( diff --git a/demo/build.gradle b/demo/build.gradle index 159dbc0..a00dcf4 100644 --- a/demo/build.gradle +++ b/demo/build.gradle @@ -33,5 +33,5 @@ dependencies { compile 'com.h2database:h2:1.4.194' compile 'org.apache.commons:commons-lang3:3.5' testCompile 'junit:junit:4.12' - testCompile 'org.mockito:mockito-core:2.7.21' + testCompile 'com.nhaarman:mockito-kotlin:1.4.0' } diff --git a/demo/src/test/java/ch/dissem/bitmessage/SystemTest.java b/demo/src/test/java/ch/dissem/bitmessage/SystemTest.java index 224cc1a..2cfe84f 100644 --- a/demo/src/test/java/ch/dissem/bitmessage/SystemTest.java +++ b/demo/src/test/java/ch/dissem/bitmessage/SystemTest.java @@ -22,13 +22,11 @@ import ch.dissem.bitmessage.entity.Plaintext; import ch.dissem.bitmessage.networking.nio.NioNetworkHandler; import ch.dissem.bitmessage.ports.DefaultLabeler; import ch.dissem.bitmessage.ports.Labeler; -import ch.dissem.bitmessage.ports.NetworkHandler; import ch.dissem.bitmessage.repository.*; import ch.dissem.bitmessage.utils.TTL; import org.junit.After; import org.junit.Before; import org.junit.Test; -import org.mockito.Mockito; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -37,6 +35,9 @@ import java.util.concurrent.TimeUnit; import static ch.dissem.bitmessage.entity.payload.Pubkey.Feature.DOES_ACK; import static ch.dissem.bitmessage.utils.UnixTime.MINUTE; +import static com.nhaarman.mockito_kotlin.MockitoKt.spy; +import static com.nhaarman.mockito_kotlin.MockitoKt.timeout; +import static com.nhaarman.mockito_kotlin.MockitoKt.verify; import static org.hamcrest.CoreMatchers.equalTo; import static org.junit.Assert.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -65,7 +66,7 @@ public class SystemTest { int bobPort = port++; { JdbcConfig aliceDB = new JdbcConfig("jdbc:h2:mem:alice;DB_CLOSE_DELAY=-1", "sa", ""); - aliceLabeler = Mockito.spy(new DebugLabeler("Alice")); + aliceLabeler = spy(new DebugLabeler("Alice")); TestListener aliceListener = new TestListener(); alice = new BitmessageContext.Builder() .addressRepo(new JdbcAddressRepository(aliceDB)) @@ -110,17 +111,17 @@ public class SystemTest { bob.shutdown(); } - @Test(timeout = 60_000) + @Test(timeout = 120_000) public void ensureAliceCanSendMessageToBob() throws Exception { String originalMessage = UUID.randomUUID().toString(); alice.send(aliceIdentity, new BitmessageAddress(bobIdentity.getAddress()), "Subject", originalMessage); - Plaintext plaintext = bobListener.get(15, TimeUnit.MINUTES); + Plaintext plaintext = bobListener.get(2, TimeUnit.MINUTES); assertThat(plaintext.getType(), equalTo(Plaintext.Type.MSG)); assertThat(plaintext.getText(), equalTo(originalMessage)); - Mockito.verify(aliceLabeler, Mockito.timeout(TimeUnit.MINUTES.toMillis(15)).atLeastOnce()) + verify(aliceLabeler, timeout(TimeUnit.MINUTES.toMillis(2)).atLeastOnce()) .markAsAcknowledged(any()); } diff --git a/networking/src/main/kotlin/ch/dissem/bitmessage/networking/nio/Connection.kt b/networking/src/main/kotlin/ch/dissem/bitmessage/networking/nio/Connection.kt index 03552b6..22293c9 100644 --- a/networking/src/main/kotlin/ch/dissem/bitmessage/networking/nio/Connection.kt +++ b/networking/src/main/kotlin/ch/dissem/bitmessage/networking/nio/Connection.kt @@ -75,7 +75,7 @@ class Connection( fun send(payload: MessagePayload) = io.send(payload) - protected fun handleMessage(payload: MessagePayload) { + private fun handleMessage(payload: MessagePayload) { when (state) { State.CONNECTING -> initializer!!.handleCommand(payload) State.ACTIVE -> receiveMessage(payload) @@ -108,7 +108,7 @@ class Connection( private fun receiveMessage(objectMessage: ObjectMessage) { requestedObjects.remove(objectMessage.inventoryVector) if (ctx.inventory.contains(objectMessage)) { - LOG.trace("Received object " + objectMessage.inventoryVector + " - already in inventory") + LOG.trace("Received object ${objectMessage.inventoryVector} - already in inventory") return } try { @@ -122,7 +122,7 @@ class Connection( LOG.warn(e.message) // DebugUtils.saveToFile(objectMessage); // this line must not be committed active } catch (e: IOException) { - LOG.error("Stream " + objectMessage.stream + ", object type " + objectMessage.type + ": " + e.message, e) + LOG.error("Stream ${objectMessage.stream}, object type ${objectMessage.type}: ${e.message}", e) } finally { if (commonRequestedObjects.remove(objectMessage.inventoryVector) == null) { LOG.debug("Received object that wasn't requested.") @@ -131,7 +131,7 @@ class Connection( } private fun receiveMessage(addr: Addr) { - LOG.trace("Received " + addr.addresses.size + " addresses.") + LOG.trace("Received ${addr.addresses.size} addresses.") ctx.nodeRegistry.offerAddresses(addr.addresses) } diff --git a/networking/src/main/kotlin/ch/dissem/bitmessage/networking/nio/NioNetworkHandler.kt b/networking/src/main/kotlin/ch/dissem/bitmessage/networking/nio/NioNetworkHandler.kt index 8900b49..171b4bb 100644 --- a/networking/src/main/kotlin/ch/dissem/bitmessage/networking/nio/NioNetworkHandler.kt +++ b/networking/src/main/kotlin/ch/dissem/bitmessage/networking/nio/NioNetworkHandler.kt @@ -431,7 +431,7 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder { } return Property("network", Property("connectionManager", if (isRunning) "running" else "stopped"), - Property("connections", *streamProperties.toTypedArray()), + Property("connections", streamProperties), Property("requestedObjects", requestedObjects.size) ) } diff --git a/networking/src/test/java/ch/dissem/bitmessage/networking/TestNodeRegistry.java b/networking/src/test/java/ch/dissem/bitmessage/networking/TestNodeRegistry.java deleted file mode 100644 index fb00b26..0000000 --- a/networking/src/test/java/ch/dissem/bitmessage/networking/TestNodeRegistry.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.networking; - -import ch.dissem.bitmessage.entity.valueobject.NetworkAddress; -import ch.dissem.bitmessage.ports.NodeRegistry; - -import java.util.Arrays; -import java.util.List; - -/** - * Empty {@link NodeRegistry} that doesn't do anything, but shouldn't break things either. - */ -class TestNodeRegistry implements NodeRegistry { - private List<NetworkAddress> nodes; - - public TestNodeRegistry(NetworkAddress... nodes) { - this.nodes = Arrays.asList(nodes); - } - - @Override - public void clear() { - // no op - } - - @Override - public List<NetworkAddress> getKnownAddresses(int limit, long... streams) { - return nodes; - } - - @Override - public void offerAddresses(List<NetworkAddress> addresses) { - // Ignore - } -} diff --git a/networking/src/test/java/ch/dissem/bitmessage/networking/TestNodeRegistry.kt b/networking/src/test/java/ch/dissem/bitmessage/networking/TestNodeRegistry.kt new file mode 100644 index 0000000..3c00bf4 --- /dev/null +++ b/networking/src/test/java/ch/dissem/bitmessage/networking/TestNodeRegistry.kt @@ -0,0 +1,41 @@ +/* + * Copyright 2015 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.networking + +import ch.dissem.bitmessage.entity.valueobject.NetworkAddress +import ch.dissem.bitmessage.ports.NodeRegistry + +import java.util.Arrays + +/** + * Empty [NodeRegistry] that doesn't do anything, but shouldn't break things either. + */ +internal class TestNodeRegistry(vararg nodes: NetworkAddress) : NodeRegistry { + private val nodes: List<NetworkAddress> = listOf(*nodes) + + override fun clear() { + // no op + } + + override fun getKnownAddresses(limit: Int, vararg streams: Long): List<NetworkAddress> { + return nodes + } + + override fun offerAddresses(addresses: List<NetworkAddress>) { + // Ignore + } +} diff --git a/repositories/src/main/kotlin/ch/dissem/bitmessage/repository/JdbcMessageRepository.kt b/repositories/src/main/kotlin/ch/dissem/bitmessage/repository/JdbcMessageRepository.kt index 6daf32c..ad5e834 100644 --- a/repositories/src/main/kotlin/ch/dissem/bitmessage/repository/JdbcMessageRepository.kt +++ b/repositories/src/main/kotlin/ch/dissem/bitmessage/repository/JdbcMessageRepository.kt @@ -98,7 +98,7 @@ class JdbcMessageRepository(private val config: JdbcConfig) : AbstractMessageRep builder.id(id) builder.IV(InventoryVector.fromHash(iv)) builder.from(ctx.addressRepository.getAddress(rs.getString("sender"))!!) - builder.to(ctx.addressRepository.getAddress(rs.getString("recipient"))) + rs.getString("recipient")?.let { builder.to(ctx.addressRepository.getAddress(it)) } builder.ackData(rs.getBytes("ack_data")) builder.sent(rs.getObject("sent") as Long?) builder.received(rs.getObject("received") as Long?) From a245288359d37311ae441f6f155de0a2fd7641a0 Mon Sep 17 00:00:00 2001 From: Christian Basler <chrigu.meyer@gmail.com> Date: Wed, 28 Jun 2017 00:01:05 +0200 Subject: [PATCH 07/10] Migrated everything except - the Bytes utilities class - it's easier to do in Java as with Kotlin byte + byte = int - the demo project, which I'm not sure I'll migrate. Maybe I'll make a new Kotlin Demo application --- .../ch/dissem/bitmessage/BitmessageContext.kt | 0 .../bitmessage/DefaultMessageListener.kt | 0 .../ch/dissem/bitmessage/InternalContext.kt | 0 .../dissem/bitmessage/ProofOfWorkService.kt | 0 .../dissem/bitmessage/constants/constants.kt | 2 +- .../ch/dissem/bitmessage/entity/Addr.kt | 0 .../bitmessage/entity/BitmessageAddress.kt | 0 .../dissem/bitmessage/entity/CustomMessage.kt | 0 .../ch/dissem/bitmessage/entity/Encrypted.kt | 0 .../ch/dissem/bitmessage/entity/GetData.kt | 0 .../ch/dissem/bitmessage/entity/Inv.kt | 0 .../bitmessage/entity/MessagePayload.kt | 0 .../bitmessage/entity/NetworkMessage.kt | 0 .../dissem/bitmessage/entity/ObjectMessage.kt | 0 .../ch/dissem/bitmessage/entity/Plaintext.kt | 0 .../bitmessage/entity/PlaintextHolder.kt | 0 .../ch/dissem/bitmessage/entity/Streamable.kt | 0 .../ch/dissem/bitmessage/entity/VerAck.kt | 0 .../ch/dissem/bitmessage/entity/Version.kt | 0 .../bitmessage/entity/payload/Broadcast.kt | 0 .../bitmessage/entity/payload/CryptoBox.kt | 0 .../entity/payload/GenericPayload.kt | 0 .../bitmessage/entity/payload/GetPubkey.kt | 4 +- .../dissem/bitmessage/entity/payload/Msg.kt | 0 .../entity/payload/ObjectPayload.kt | 0 .../bitmessage/entity/payload/ObjectType.kt | 0 .../bitmessage/entity/payload/Pubkey.kt | 0 .../bitmessage/entity/payload/V2Pubkey.kt | 0 .../bitmessage/entity/payload/V3Pubkey.kt | 0 .../bitmessage/entity/payload/V4Broadcast.kt | 0 .../bitmessage/entity/payload/V4Pubkey.kt | 0 .../bitmessage/entity/payload/V5Broadcast.kt | 0 .../entity/valueobject/ExtendedEncoding.kt | 0 .../entity/valueobject/InventoryVector.kt | 2 +- .../bitmessage/entity/valueobject/Label.kt | 0 .../entity/valueobject/NetworkAddress.kt | 3 + .../entity/valueobject/PrivateKey.kt | 0 .../entity/valueobject/extended/Attachment.kt | 0 .../entity/valueobject/extended/Message.kt | 0 .../entity/valueobject/extended/Vote.kt | 0 .../exception/AddressFormatException.kt | 0 .../exception/ApplicationException.kt | 0 .../exception/DecryptionFailedException.kt | 0 .../InsufficientProofOfWorkException.kt | 0 .../bitmessage/exception/NodeException.kt | 0 .../dissem/bitmessage/factory/BufferPool.kt | 0 .../factory/ExtendedEncodingFactory.kt | 0 .../ch/dissem/bitmessage/factory/Factory.kt | 13 +- .../bitmessage/factory/V3MessageFactory.kt | 2 +- .../bitmessage/factory/V3MessageReader.kt | 0 .../bitmessage/ports/AbstractCryptography.kt | 0 .../ports/AbstractMessageRepository.kt | 0 .../bitmessage/ports/AddressRepository.kt | 0 .../dissem/bitmessage/ports/Cryptography.kt | 0 .../bitmessage/ports/CustomCommandHandler.kt | 0 .../dissem/bitmessage/ports/DefaultLabeler.kt | 0 .../ch/dissem/bitmessage/ports/Inventory.kt | 0 .../ch/dissem/bitmessage/ports/Labeler.kt | 0 .../bitmessage/ports/MessageRepository.kt | 0 .../ports/MultiThreadedPOWEngine.kt | 0 .../dissem/bitmessage/ports/NetworkHandler.kt | 0 .../dissem/bitmessage/ports/NodeRegistry.kt | 0 .../bitmessage/ports/NodeRegistryHelper.kt | 10 +- .../bitmessage/ports/ProofOfWorkEngine.kt | 0 .../bitmessage/ports/ProofOfWorkRepository.kt | 0 .../bitmessage/ports/SimplePOWEngine.kt | 0 .../dissem/bitmessage/utils/AccessCounter.kt | 0 .../ch/dissem/bitmessage/utils/Base58.kt | 0 .../dissem/bitmessage/utils/CallbackWaiter.kt | 0 .../ch/dissem/bitmessage/utils/Collections.kt | 0 .../bitmessage/utils/ConversationService.kt | 0 .../ch/dissem/bitmessage/utils/DebugUtils.kt | 2 +- .../ch/dissem/bitmessage/utils/Decode.kt | 0 .../ch/dissem/bitmessage/utils/Encode.kt | 0 .../ch/dissem/bitmessage/utils/Numbers.kt | 0 .../ch/dissem/bitmessage/utils/Points.kt | 0 .../ch/dissem/bitmessage/utils/Property.kt | 0 .../ch/dissem/bitmessage/utils/Singleton.kt | 0 .../ch/dissem/bitmessage/utils/SqlStrings.kt | 0 .../ch/dissem/bitmessage/utils/Strings.kt | 0 .../ch/dissem/bitmessage/utils/TTL.kt | 0 .../bitmessage/utils/ThreadFactoryBuilder.kt | 0 .../ch/dissem/bitmessage/utils/UnixTime.kt | 0 .../bitmessage/BitmessageContextTest.kt | 0 .../ch/dissem/bitmessage/DecryptionTest.kt | 0 .../bitmessage/DefaultMessageListenerTest.kt | 0 .../ch/dissem/bitmessage/EncryptionTest.kt | 0 .../bitmessage/ProofOfWorkServiceTest.kt | 0 .../ch/dissem/bitmessage/SignatureTest.kt | 0 .../entity/BitmessageAddressTest.kt | 0 .../bitmessage/entity/ExtendedEncodingTest.kt | 0 .../bitmessage/entity/SerializationTest.kt | 0 .../bitmessage/ports/ProofOfWorkEngineTest.kt | 12 +- .../bitmessage/testutils/TestInventory.kt | 0 .../ch/dissem/bitmessage/utils/BytesTest.kt | 0 .../bitmessage/utils/CollectionsTest.kt | 0 .../utils/ConversationServiceTest.kt | 0 .../ch/dissem/bitmessage/utils/DecodeTest.kt | 0 .../ch/dissem/bitmessage/utils/EncodeTest.kt | 0 .../dissem/bitmessage/utils/SqlStringsTest.kt | 0 .../ch/dissem/bitmessage/utils/StringsTest.kt | 0 .../ch/dissem/bitmessage/utils/TestBase.kt | 0 .../ch/dissem/bitmessage/utils/TestUtils.kt | 0 .../java/ch/dissem/bitmessage/demo/Main.java | 4 +- .../{TestListener.java => TestListener.kt} | 27 +- .../dissem/bitmessage/TestNodeRegistry.java | 2 +- .../networking/NetworkHandlerTest.kt | 0 .../bitmessage/networking/TestNodeRegistry.kt | 2 +- repositories/build.gradle | 2 +- .../repository/JdbcAddressRepositoryTest.java | 183 --------- .../repository/JdbcInventoryTest.java | 143 ------- .../repository/JdbcMessageRepositoryTest.java | 352 ------------------ .../repository/JdbcNodeRegistryTest.java | 103 ----- .../repository/JdbcAddressRepositoryTest.kt | 180 +++++++++ .../repository/JdbcInventoryTest.kt | 138 +++++++ .../repository/JdbcMessageRepositoryTest.kt | 336 +++++++++++++++++ .../repository/JdbcNodeRegistryTest.kt | 97 +++++ .../JdbcProofOfWorkRepositoryTest.kt | 0 .../dissem/bitmessage/repository/TestBase.kt | 0 .../bitmessage/repository/TestJdbcConfig.kt} | 36 +- 120 files changed, 810 insertions(+), 845 deletions(-) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/BitmessageContext.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/DefaultMessageListener.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/InternalContext.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/ProofOfWorkService.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/constants/constants.kt (96%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/entity/Addr.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/entity/BitmessageAddress.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/entity/CustomMessage.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/entity/Encrypted.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/entity/GetData.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/entity/Inv.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/entity/MessagePayload.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/entity/NetworkMessage.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/entity/ObjectMessage.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/entity/Plaintext.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/entity/PlaintextHolder.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/entity/Streamable.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/entity/VerAck.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/entity/Version.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/entity/payload/Broadcast.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/entity/payload/CryptoBox.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/entity/payload/GenericPayload.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/entity/payload/GetPubkey.kt (97%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/entity/payload/Msg.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/entity/payload/ObjectPayload.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/entity/payload/ObjectType.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/entity/payload/Pubkey.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/entity/payload/V2Pubkey.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/entity/payload/V3Pubkey.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/entity/payload/V4Broadcast.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/entity/payload/V4Pubkey.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/entity/payload/V5Broadcast.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/entity/valueobject/ExtendedEncoding.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/entity/valueobject/InventoryVector.kt (97%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/entity/valueobject/Label.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/entity/valueobject/NetworkAddress.kt (97%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/entity/valueobject/PrivateKey.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/entity/valueobject/extended/Attachment.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/entity/valueobject/extended/Message.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/entity/valueobject/extended/Vote.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/exception/AddressFormatException.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/exception/ApplicationException.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/exception/DecryptionFailedException.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/exception/InsufficientProofOfWorkException.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/exception/NodeException.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/factory/BufferPool.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/factory/ExtendedEncodingFactory.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/factory/Factory.kt (92%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/factory/V3MessageFactory.kt (99%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/factory/V3MessageReader.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/ports/AbstractCryptography.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/ports/AbstractMessageRepository.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/ports/AddressRepository.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/ports/Cryptography.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/ports/CustomCommandHandler.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/ports/DefaultLabeler.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/ports/Inventory.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/ports/Labeler.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/ports/MessageRepository.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/ports/MultiThreadedPOWEngine.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/ports/NetworkHandler.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/ports/NodeRegistry.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/ports/NodeRegistryHelper.kt (88%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/ports/ProofOfWorkEngine.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/ports/ProofOfWorkRepository.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/ports/SimplePOWEngine.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/utils/AccessCounter.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/utils/Base58.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/utils/CallbackWaiter.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/utils/Collections.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/utils/ConversationService.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/utils/DebugUtils.kt (97%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/utils/Decode.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/utils/Encode.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/utils/Numbers.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/utils/Points.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/utils/Property.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/utils/Singleton.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/utils/SqlStrings.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/utils/Strings.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/utils/TTL.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/utils/ThreadFactoryBuilder.kt (100%) rename core/src/main/{java => kotlin}/ch/dissem/bitmessage/utils/UnixTime.kt (100%) rename core/src/test/{java => kotlin}/ch/dissem/bitmessage/BitmessageContextTest.kt (100%) rename core/src/test/{java => kotlin}/ch/dissem/bitmessage/DecryptionTest.kt (100%) rename core/src/test/{java => kotlin}/ch/dissem/bitmessage/DefaultMessageListenerTest.kt (100%) rename core/src/test/{java => kotlin}/ch/dissem/bitmessage/EncryptionTest.kt (100%) rename core/src/test/{java => kotlin}/ch/dissem/bitmessage/ProofOfWorkServiceTest.kt (100%) rename core/src/test/{java => kotlin}/ch/dissem/bitmessage/SignatureTest.kt (100%) rename core/src/test/{java => kotlin}/ch/dissem/bitmessage/entity/BitmessageAddressTest.kt (100%) rename core/src/test/{java => kotlin}/ch/dissem/bitmessage/entity/ExtendedEncodingTest.kt (100%) rename core/src/test/{java => kotlin}/ch/dissem/bitmessage/entity/SerializationTest.kt (100%) rename core/src/test/{java => kotlin}/ch/dissem/bitmessage/ports/ProofOfWorkEngineTest.kt (84%) rename core/src/test/{java => kotlin}/ch/dissem/bitmessage/testutils/TestInventory.kt (100%) rename core/src/test/{java => kotlin}/ch/dissem/bitmessage/utils/BytesTest.kt (100%) rename core/src/test/{java => kotlin}/ch/dissem/bitmessage/utils/CollectionsTest.kt (100%) rename core/src/test/{java => kotlin}/ch/dissem/bitmessage/utils/ConversationServiceTest.kt (100%) rename core/src/test/{java => kotlin}/ch/dissem/bitmessage/utils/DecodeTest.kt (100%) rename core/src/test/{java => kotlin}/ch/dissem/bitmessage/utils/EncodeTest.kt (100%) rename core/src/test/{java => kotlin}/ch/dissem/bitmessage/utils/SqlStringsTest.kt (100%) rename core/src/test/{java => kotlin}/ch/dissem/bitmessage/utils/StringsTest.kt (100%) rename core/src/test/{java => kotlin}/ch/dissem/bitmessage/utils/TestBase.kt (100%) rename core/src/test/{java => kotlin}/ch/dissem/bitmessage/utils/TestUtils.kt (100%) rename demo/src/test/java/ch/dissem/bitmessage/{TestListener.java => TestListener.kt} (50%) rename networking/src/test/{java => kotlin}/ch/dissem/bitmessage/networking/NetworkHandlerTest.kt (100%) rename networking/src/test/{java => kotlin}/ch/dissem/bitmessage/networking/TestNodeRegistry.kt (94%) delete mode 100644 repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcAddressRepositoryTest.java delete mode 100644 repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcInventoryTest.java delete mode 100644 repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcMessageRepositoryTest.java delete mode 100644 repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcNodeRegistryTest.java create mode 100644 repositories/src/test/kotlin/ch/dissem/bitmessage/repository/JdbcAddressRepositoryTest.kt create mode 100644 repositories/src/test/kotlin/ch/dissem/bitmessage/repository/JdbcInventoryTest.kt create mode 100644 repositories/src/test/kotlin/ch/dissem/bitmessage/repository/JdbcMessageRepositoryTest.kt create mode 100644 repositories/src/test/kotlin/ch/dissem/bitmessage/repository/JdbcNodeRegistryTest.kt rename repositories/src/test/{java => kotlin}/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepositoryTest.kt (100%) rename repositories/src/test/{java => kotlin}/ch/dissem/bitmessage/repository/TestBase.kt (100%) rename repositories/src/test/{java/ch/dissem/bitmessage/repository/TestJdbcConfig.java => kotlin/ch/dissem/bitmessage/repository/TestJdbcConfig.kt} (51%) diff --git a/core/src/main/java/ch/dissem/bitmessage/BitmessageContext.kt b/core/src/main/kotlin/ch/dissem/bitmessage/BitmessageContext.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/BitmessageContext.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/BitmessageContext.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.kt b/core/src/main/kotlin/ch/dissem/bitmessage/DefaultMessageListener.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/DefaultMessageListener.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/InternalContext.kt b/core/src/main/kotlin/ch/dissem/bitmessage/InternalContext.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/InternalContext.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/InternalContext.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.kt b/core/src/main/kotlin/ch/dissem/bitmessage/ProofOfWorkService.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/ProofOfWorkService.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/ProofOfWorkService.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/constants/constants.kt b/core/src/main/kotlin/ch/dissem/bitmessage/constants/constants.kt similarity index 96% rename from core/src/main/java/ch/dissem/bitmessage/constants/constants.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/constants/constants.kt index ee4feee..23d4c63 100644 --- a/core/src/main/java/ch/dissem/bitmessage/constants/constants.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/constants/constants.kt @@ -17,7 +17,7 @@ package ch.dissem.bitmessage.constants /** - * Created by chrigu on 03.06.17. + * Some network constants */ object Network { @JvmField val NETWORK_MAGIC_NUMBER = 8 diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/Addr.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/Addr.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/entity/Addr.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/entity/Addr.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/BitmessageAddress.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/entity/BitmessageAddress.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/CustomMessage.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/CustomMessage.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/entity/CustomMessage.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/entity/CustomMessage.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/Encrypted.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/Encrypted.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/entity/Encrypted.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/entity/Encrypted.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/GetData.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/GetData.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/entity/GetData.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/entity/GetData.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/Inv.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/Inv.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/entity/Inv.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/entity/Inv.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/MessagePayload.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/MessagePayload.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/entity/MessagePayload.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/entity/MessagePayload.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/NetworkMessage.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/NetworkMessage.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/entity/NetworkMessage.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/entity/NetworkMessage.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/ObjectMessage.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/entity/ObjectMessage.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/Plaintext.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/entity/Plaintext.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/entity/Plaintext.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/PlaintextHolder.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/PlaintextHolder.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/entity/PlaintextHolder.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/entity/PlaintextHolder.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/Streamable.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/Streamable.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/entity/Streamable.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/entity/Streamable.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/VerAck.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/VerAck.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/entity/VerAck.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/entity/VerAck.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/Version.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/Version.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/entity/Version.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/entity/Version.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/Broadcast.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/Broadcast.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/CryptoBox.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/CryptoBox.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/GenericPayload.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/GenericPayload.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/entity/payload/GenericPayload.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/GenericPayload.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/GetPubkey.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/GetPubkey.kt similarity index 97% rename from core/src/main/java/ch/dissem/bitmessage/entity/payload/GetPubkey.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/GetPubkey.kt index e218d6d..fc6375f 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/payload/GetPubkey.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/GetPubkey.kt @@ -28,9 +28,7 @@ import java.nio.ByteBuffer class GetPubkey : ObjectPayload { override val type: ObjectType = ObjectType.GET_PUBKEY - - override var stream: Long = 0 - private set + override val stream: Long /** * @return an array of bytes that represent either the ripe, or the tag of an address, depending on the diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/Msg.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/Msg.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/entity/payload/Msg.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/Msg.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectPayload.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/ObjectPayload.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectPayload.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/ObjectPayload.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectType.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/ObjectType.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/entity/payload/ObjectType.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/ObjectType.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/Pubkey.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/Pubkey.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/entity/payload/Pubkey.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/Pubkey.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V2Pubkey.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/V2Pubkey.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/entity/payload/V2Pubkey.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/V2Pubkey.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V3Pubkey.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/V3Pubkey.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/entity/payload/V3Pubkey.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/V3Pubkey.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V4Broadcast.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/V4Broadcast.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/entity/payload/V4Broadcast.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/V4Broadcast.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V4Pubkey.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/V4Pubkey.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/entity/payload/V4Pubkey.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/V4Pubkey.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/payload/V5Broadcast.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/V5Broadcast.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/entity/payload/V5Broadcast.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/entity/payload/V5Broadcast.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/ExtendedEncoding.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/valueobject/ExtendedEncoding.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/entity/valueobject/ExtendedEncoding.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/entity/valueobject/ExtendedEncoding.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/InventoryVector.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/valueobject/InventoryVector.kt similarity index 97% rename from core/src/main/java/ch/dissem/bitmessage/entity/valueobject/InventoryVector.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/entity/valueobject/InventoryVector.kt index e46ea72..144211a 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/InventoryVector.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/entity/valueobject/InventoryVector.kt @@ -48,7 +48,7 @@ data class InventoryVector constructor( } override fun toString(): String { - return Strings.hex(hash).toString() + return Strings.hex(hash) } companion object { diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/Label.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/valueobject/Label.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/entity/valueobject/Label.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/entity/valueobject/Label.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/NetworkAddress.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/valueobject/NetworkAddress.kt similarity index 97% rename from core/src/main/java/ch/dissem/bitmessage/entity/valueobject/NetworkAddress.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/entity/valueobject/NetworkAddress.kt index 30480a8..829bbfa 100644 --- a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/NetworkAddress.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/entity/valueobject/NetworkAddress.kt @@ -72,6 +72,9 @@ data class NetworkAddress( constructor(time: Long, stream: Long, services: Long = 1, socket: Socket) : this(time, stream, services, ip6(socket.inetAddress), socket.port) + constructor(time: Long, stream: Long, services: Long = 1, inetAddress: InetAddress, port: Int) + : this(time, stream, services, ip6(inetAddress), port) + fun provides(service: Version.Service?): Boolean = service?.isEnabled(services) ?: false fun toInetAddress(): InetAddress { diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/PrivateKey.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/valueobject/PrivateKey.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/entity/valueobject/PrivateKey.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/entity/valueobject/PrivateKey.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/extended/Attachment.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/valueobject/extended/Attachment.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/entity/valueobject/extended/Attachment.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/entity/valueobject/extended/Attachment.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/extended/Message.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/valueobject/extended/Message.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/entity/valueobject/extended/Message.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/entity/valueobject/extended/Message.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/entity/valueobject/extended/Vote.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/valueobject/extended/Vote.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/entity/valueobject/extended/Vote.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/entity/valueobject/extended/Vote.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/exception/AddressFormatException.kt b/core/src/main/kotlin/ch/dissem/bitmessage/exception/AddressFormatException.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/exception/AddressFormatException.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/exception/AddressFormatException.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/exception/ApplicationException.kt b/core/src/main/kotlin/ch/dissem/bitmessage/exception/ApplicationException.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/exception/ApplicationException.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/exception/ApplicationException.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/exception/DecryptionFailedException.kt b/core/src/main/kotlin/ch/dissem/bitmessage/exception/DecryptionFailedException.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/exception/DecryptionFailedException.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/exception/DecryptionFailedException.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/exception/InsufficientProofOfWorkException.kt b/core/src/main/kotlin/ch/dissem/bitmessage/exception/InsufficientProofOfWorkException.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/exception/InsufficientProofOfWorkException.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/exception/InsufficientProofOfWorkException.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/exception/NodeException.kt b/core/src/main/kotlin/ch/dissem/bitmessage/exception/NodeException.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/exception/NodeException.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/exception/NodeException.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/factory/BufferPool.kt b/core/src/main/kotlin/ch/dissem/bitmessage/factory/BufferPool.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/factory/BufferPool.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/factory/BufferPool.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/factory/ExtendedEncodingFactory.kt b/core/src/main/kotlin/ch/dissem/bitmessage/factory/ExtendedEncodingFactory.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/factory/ExtendedEncodingFactory.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/factory/ExtendedEncodingFactory.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/factory/Factory.kt b/core/src/main/kotlin/ch/dissem/bitmessage/factory/Factory.kt similarity index 92% rename from core/src/main/java/ch/dissem/bitmessage/factory/Factory.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/factory/Factory.kt index 767e66b..9780c4b 100644 --- a/core/src/main/java/ch/dissem/bitmessage/factory/Factory.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/factory/Factory.kt @@ -21,7 +21,7 @@ import ch.dissem.bitmessage.entity.NetworkMessage import ch.dissem.bitmessage.entity.ObjectMessage import ch.dissem.bitmessage.entity.Plaintext import ch.dissem.bitmessage.entity.payload.* -import ch.dissem.bitmessage.entity.payload.ObjectType.MSG +import ch.dissem.bitmessage.entity.payload.ObjectType.* import ch.dissem.bitmessage.entity.valueobject.PrivateKey import ch.dissem.bitmessage.exception.NodeException import ch.dissem.bitmessage.utils.Singleton.cryptography @@ -56,7 +56,7 @@ object Factory { } - @JvmStatic fun getObjectMessage(version: Int, stream: InputStream, length: Int): ObjectMessage? { + @JvmStatic fun getObjectMessage(@Suppress("UNUSED_PARAMETER") version: Int, stream: InputStream, length: Int): ObjectMessage? { try { return V3MessageFactory.readObject(stream, length) } catch (e: IOException) { @@ -141,11 +141,10 @@ object Factory { val type = ObjectType.fromNumber(objectType) if (type != null) { when (type) { - ObjectType.GET_PUBKEY -> return parseGetPubkey(version, streamNumber, stream, length) - ObjectType.PUBKEY -> return parsePubkey(version, streamNumber, stream, length) + GET_PUBKEY -> return parseGetPubkey(version, streamNumber, stream, length) + PUBKEY -> return parsePubkey(version, streamNumber, stream, length) MSG -> return parseMsg(version, streamNumber, stream, length) - ObjectType.BROADCAST -> return parseBroadcast(version, streamNumber, stream, length) - else -> LOG.error("This should not happen, someone broke something in the code!") + BROADCAST -> return parseBroadcast(version, streamNumber, stream, length) } } // fallback: just store the message - we don't really care what it is @@ -172,7 +171,7 @@ object Factory { return pubkey ?: GenericPayload.read(version, streamNumber, stream, length) } - @JvmStatic private fun parseMsg(version: Long, streamNumber: Long, stream: InputStream, length: Int): ObjectPayload { + @JvmStatic private fun parseMsg(@Suppress("UNUSED_PARAMETER") version: Long, streamNumber: Long, stream: InputStream, length: Int): ObjectPayload { return Msg.read(stream, streamNumber, length) } diff --git a/core/src/main/java/ch/dissem/bitmessage/factory/V3MessageFactory.kt b/core/src/main/kotlin/ch/dissem/bitmessage/factory/V3MessageFactory.kt similarity index 99% rename from core/src/main/java/ch/dissem/bitmessage/factory/V3MessageFactory.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/factory/V3MessageFactory.kt index bc62662..57fc49f 100644 --- a/core/src/main/java/ch/dissem/bitmessage/factory/V3MessageFactory.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/factory/V3MessageFactory.kt @@ -99,7 +99,7 @@ object V3MessageFactory { } catch (e: Exception) { if (LOG.isTraceEnabled) { LOG.trace("Could not parse object payload - using generic payload instead", e) - LOG.trace(Strings.hex(data).toString()) + LOG.trace(Strings.hex(data)) } payload = GenericPayload(version, stream, data) } diff --git a/core/src/main/java/ch/dissem/bitmessage/factory/V3MessageReader.kt b/core/src/main/kotlin/ch/dissem/bitmessage/factory/V3MessageReader.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/factory/V3MessageReader.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/factory/V3MessageReader.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/AbstractCryptography.kt b/core/src/main/kotlin/ch/dissem/bitmessage/ports/AbstractCryptography.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/ports/AbstractCryptography.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/ports/AbstractCryptography.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/AbstractMessageRepository.kt b/core/src/main/kotlin/ch/dissem/bitmessage/ports/AbstractMessageRepository.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/ports/AbstractMessageRepository.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/ports/AbstractMessageRepository.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/AddressRepository.kt b/core/src/main/kotlin/ch/dissem/bitmessage/ports/AddressRepository.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/ports/AddressRepository.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/ports/AddressRepository.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/Cryptography.kt b/core/src/main/kotlin/ch/dissem/bitmessage/ports/Cryptography.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/ports/Cryptography.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/ports/Cryptography.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/CustomCommandHandler.kt b/core/src/main/kotlin/ch/dissem/bitmessage/ports/CustomCommandHandler.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/ports/CustomCommandHandler.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/ports/CustomCommandHandler.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/DefaultLabeler.kt b/core/src/main/kotlin/ch/dissem/bitmessage/ports/DefaultLabeler.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/ports/DefaultLabeler.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/ports/DefaultLabeler.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/Inventory.kt b/core/src/main/kotlin/ch/dissem/bitmessage/ports/Inventory.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/ports/Inventory.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/ports/Inventory.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/Labeler.kt b/core/src/main/kotlin/ch/dissem/bitmessage/ports/Labeler.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/ports/Labeler.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/ports/Labeler.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/MessageRepository.kt b/core/src/main/kotlin/ch/dissem/bitmessage/ports/MessageRepository.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/ports/MessageRepository.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/ports/MessageRepository.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/MultiThreadedPOWEngine.kt b/core/src/main/kotlin/ch/dissem/bitmessage/ports/MultiThreadedPOWEngine.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/ports/MultiThreadedPOWEngine.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/ports/MultiThreadedPOWEngine.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.kt b/core/src/main/kotlin/ch/dissem/bitmessage/ports/NetworkHandler.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/ports/NetworkHandler.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/NodeRegistry.kt b/core/src/main/kotlin/ch/dissem/bitmessage/ports/NodeRegistry.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/ports/NodeRegistry.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/ports/NodeRegistry.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/NodeRegistryHelper.kt b/core/src/main/kotlin/ch/dissem/bitmessage/ports/NodeRegistryHelper.kt similarity index 88% rename from core/src/main/java/ch/dissem/bitmessage/ports/NodeRegistryHelper.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/ports/NodeRegistryHelper.kt index 1b947dd..921218d 100644 --- a/core/src/main/java/ch/dissem/bitmessage/ports/NodeRegistryHelper.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/ports/NodeRegistryHelper.kt @@ -17,6 +17,7 @@ package ch.dissem.bitmessage.ports import ch.dissem.bitmessage.entity.valueobject.NetworkAddress +import ch.dissem.bitmessage.utils.UnixTime import org.slf4j.LoggerFactory import java.io.IOException import java.net.InetAddress @@ -46,9 +47,12 @@ object NodeRegistryHelper { val portIndex = line.lastIndexOf(':') val inetAddresses = InetAddress.getAllByName(line.substring(0, portIndex)) val port = Integer.valueOf(line.substring(portIndex + 1))!! - for (inetAddress in inetAddresses) { - streamSet.add(NetworkAddress.Builder().ip(inetAddress).port(port).stream(stream).build()) - } + inetAddresses.mapTo(streamSet) { NetworkAddress( + time = UnixTime.now, + stream = stream, + inetAddress = it, + port = port + ) } } } catch (e: IOException) { LOG.warn(e.message, e) diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/ProofOfWorkEngine.kt b/core/src/main/kotlin/ch/dissem/bitmessage/ports/ProofOfWorkEngine.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/ports/ProofOfWorkEngine.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/ports/ProofOfWorkEngine.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/ProofOfWorkRepository.kt b/core/src/main/kotlin/ch/dissem/bitmessage/ports/ProofOfWorkRepository.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/ports/ProofOfWorkRepository.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/ports/ProofOfWorkRepository.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/ports/SimplePOWEngine.kt b/core/src/main/kotlin/ch/dissem/bitmessage/ports/SimplePOWEngine.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/ports/SimplePOWEngine.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/ports/SimplePOWEngine.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/AccessCounter.kt b/core/src/main/kotlin/ch/dissem/bitmessage/utils/AccessCounter.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/utils/AccessCounter.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/utils/AccessCounter.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/Base58.kt b/core/src/main/kotlin/ch/dissem/bitmessage/utils/Base58.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/utils/Base58.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/utils/Base58.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/CallbackWaiter.kt b/core/src/main/kotlin/ch/dissem/bitmessage/utils/CallbackWaiter.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/utils/CallbackWaiter.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/utils/CallbackWaiter.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/Collections.kt b/core/src/main/kotlin/ch/dissem/bitmessage/utils/Collections.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/utils/Collections.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/utils/Collections.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/ConversationService.kt b/core/src/main/kotlin/ch/dissem/bitmessage/utils/ConversationService.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/utils/ConversationService.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/utils/ConversationService.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/DebugUtils.kt b/core/src/main/kotlin/ch/dissem/bitmessage/utils/DebugUtils.kt similarity index 97% rename from core/src/main/java/ch/dissem/bitmessage/utils/DebugUtils.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/utils/DebugUtils.kt index 31b2e65..acbbabf 100644 --- a/core/src/main/java/ch/dissem/bitmessage/utils/DebugUtils.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/utils/DebugUtils.kt @@ -37,7 +37,7 @@ object DebugUtils { } @JvmStatic fun <K> inc(map: MutableMap<K, Int>, key: K) { - val value = map.get(key); + val value = map[key] if (value == null) { map.put(key, 1) } else { diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/Decode.kt b/core/src/main/kotlin/ch/dissem/bitmessage/utils/Decode.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/utils/Decode.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/utils/Decode.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/Encode.kt b/core/src/main/kotlin/ch/dissem/bitmessage/utils/Encode.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/utils/Encode.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/utils/Encode.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/Numbers.kt b/core/src/main/kotlin/ch/dissem/bitmessage/utils/Numbers.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/utils/Numbers.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/utils/Numbers.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/Points.kt b/core/src/main/kotlin/ch/dissem/bitmessage/utils/Points.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/utils/Points.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/utils/Points.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/Property.kt b/core/src/main/kotlin/ch/dissem/bitmessage/utils/Property.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/utils/Property.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/utils/Property.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/Singleton.kt b/core/src/main/kotlin/ch/dissem/bitmessage/utils/Singleton.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/utils/Singleton.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/utils/Singleton.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/SqlStrings.kt b/core/src/main/kotlin/ch/dissem/bitmessage/utils/SqlStrings.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/utils/SqlStrings.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/utils/SqlStrings.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/Strings.kt b/core/src/main/kotlin/ch/dissem/bitmessage/utils/Strings.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/utils/Strings.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/utils/Strings.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/TTL.kt b/core/src/main/kotlin/ch/dissem/bitmessage/utils/TTL.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/utils/TTL.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/utils/TTL.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/ThreadFactoryBuilder.kt b/core/src/main/kotlin/ch/dissem/bitmessage/utils/ThreadFactoryBuilder.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/utils/ThreadFactoryBuilder.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/utils/ThreadFactoryBuilder.kt diff --git a/core/src/main/java/ch/dissem/bitmessage/utils/UnixTime.kt b/core/src/main/kotlin/ch/dissem/bitmessage/utils/UnixTime.kt similarity index 100% rename from core/src/main/java/ch/dissem/bitmessage/utils/UnixTime.kt rename to core/src/main/kotlin/ch/dissem/bitmessage/utils/UnixTime.kt diff --git a/core/src/test/java/ch/dissem/bitmessage/BitmessageContextTest.kt b/core/src/test/kotlin/ch/dissem/bitmessage/BitmessageContextTest.kt similarity index 100% rename from core/src/test/java/ch/dissem/bitmessage/BitmessageContextTest.kt rename to core/src/test/kotlin/ch/dissem/bitmessage/BitmessageContextTest.kt diff --git a/core/src/test/java/ch/dissem/bitmessage/DecryptionTest.kt b/core/src/test/kotlin/ch/dissem/bitmessage/DecryptionTest.kt similarity index 100% rename from core/src/test/java/ch/dissem/bitmessage/DecryptionTest.kt rename to core/src/test/kotlin/ch/dissem/bitmessage/DecryptionTest.kt diff --git a/core/src/test/java/ch/dissem/bitmessage/DefaultMessageListenerTest.kt b/core/src/test/kotlin/ch/dissem/bitmessage/DefaultMessageListenerTest.kt similarity index 100% rename from core/src/test/java/ch/dissem/bitmessage/DefaultMessageListenerTest.kt rename to core/src/test/kotlin/ch/dissem/bitmessage/DefaultMessageListenerTest.kt diff --git a/core/src/test/java/ch/dissem/bitmessage/EncryptionTest.kt b/core/src/test/kotlin/ch/dissem/bitmessage/EncryptionTest.kt similarity index 100% rename from core/src/test/java/ch/dissem/bitmessage/EncryptionTest.kt rename to core/src/test/kotlin/ch/dissem/bitmessage/EncryptionTest.kt diff --git a/core/src/test/java/ch/dissem/bitmessage/ProofOfWorkServiceTest.kt b/core/src/test/kotlin/ch/dissem/bitmessage/ProofOfWorkServiceTest.kt similarity index 100% rename from core/src/test/java/ch/dissem/bitmessage/ProofOfWorkServiceTest.kt rename to core/src/test/kotlin/ch/dissem/bitmessage/ProofOfWorkServiceTest.kt diff --git a/core/src/test/java/ch/dissem/bitmessage/SignatureTest.kt b/core/src/test/kotlin/ch/dissem/bitmessage/SignatureTest.kt similarity index 100% rename from core/src/test/java/ch/dissem/bitmessage/SignatureTest.kt rename to core/src/test/kotlin/ch/dissem/bitmessage/SignatureTest.kt diff --git a/core/src/test/java/ch/dissem/bitmessage/entity/BitmessageAddressTest.kt b/core/src/test/kotlin/ch/dissem/bitmessage/entity/BitmessageAddressTest.kt similarity index 100% rename from core/src/test/java/ch/dissem/bitmessage/entity/BitmessageAddressTest.kt rename to core/src/test/kotlin/ch/dissem/bitmessage/entity/BitmessageAddressTest.kt diff --git a/core/src/test/java/ch/dissem/bitmessage/entity/ExtendedEncodingTest.kt b/core/src/test/kotlin/ch/dissem/bitmessage/entity/ExtendedEncodingTest.kt similarity index 100% rename from core/src/test/java/ch/dissem/bitmessage/entity/ExtendedEncodingTest.kt rename to core/src/test/kotlin/ch/dissem/bitmessage/entity/ExtendedEncodingTest.kt diff --git a/core/src/test/java/ch/dissem/bitmessage/entity/SerializationTest.kt b/core/src/test/kotlin/ch/dissem/bitmessage/entity/SerializationTest.kt similarity index 100% rename from core/src/test/java/ch/dissem/bitmessage/entity/SerializationTest.kt rename to core/src/test/kotlin/ch/dissem/bitmessage/entity/SerializationTest.kt diff --git a/core/src/test/java/ch/dissem/bitmessage/ports/ProofOfWorkEngineTest.kt b/core/src/test/kotlin/ch/dissem/bitmessage/ports/ProofOfWorkEngineTest.kt similarity index 84% rename from core/src/test/java/ch/dissem/bitmessage/ports/ProofOfWorkEngineTest.kt rename to core/src/test/kotlin/ch/dissem/bitmessage/ports/ProofOfWorkEngineTest.kt index 4ce5ec6..2bd3a0a 100644 --- a/core/src/test/java/ch/dissem/bitmessage/ports/ProofOfWorkEngineTest.kt +++ b/core/src/test/kotlin/ch/dissem/bitmessage/ports/ProofOfWorkEngineTest.kt @@ -41,13 +41,14 @@ class ProofOfWorkEngineTest : TestBase() { val waiter1 = CallbackWaiter<ByteArray>() engine.calculateNonce(initialHash, target, object : ProofOfWorkEngine.Callback { + @Suppress("NAME_SHADOWING") override fun onNonceCalculated(initialHash: ByteArray, nonce: ByteArray) { waiter1.setValue(nonce) } }) - val nonce = waiter1.waitForValue()!! - println("Calculating nonce took " + waiter1.time + "ms") - assertTrue(Bytes.lt(cryptography().doubleSha512(nonce, initialHash), target, 8)) + val nonce1 = waiter1.waitForValue()!! + println("Calculating nonce1 took ${waiter1.time}ms") + assertTrue(Bytes.lt(cryptography().doubleSha512(nonce1, initialHash), target, 8)) // Let's add a second (shorter) run to find possible multi threading issues val initialHash2 = cryptography().sha512(byteArrayOf(1, 3, 6, 5)) @@ -56,13 +57,14 @@ class ProofOfWorkEngineTest : TestBase() { val waiter2 = CallbackWaiter<ByteArray>() engine.calculateNonce(initialHash2, target2, object : ProofOfWorkEngine.Callback { + @Suppress("NAME_SHADOWING") override fun onNonceCalculated(initialHash: ByteArray, nonce: ByteArray) { waiter2.setValue(nonce) } }) val nonce2 = waiter2.waitForValue()!! - println("Calculating nonce took " + waiter2.time + "ms") + println("Calculating nonce1 took ${waiter2.time}ms") assertTrue(Bytes.lt(cryptography().doubleSha512(nonce2, initialHash2), target2, 8)) - assertTrue("Second nonce must be quicker to find", waiter1.time > waiter2.time) + assertTrue("Second nonce1 must be quicker to find", waiter1.time > waiter2.time) } } diff --git a/core/src/test/java/ch/dissem/bitmessage/testutils/TestInventory.kt b/core/src/test/kotlin/ch/dissem/bitmessage/testutils/TestInventory.kt similarity index 100% rename from core/src/test/java/ch/dissem/bitmessage/testutils/TestInventory.kt rename to core/src/test/kotlin/ch/dissem/bitmessage/testutils/TestInventory.kt diff --git a/core/src/test/java/ch/dissem/bitmessage/utils/BytesTest.kt b/core/src/test/kotlin/ch/dissem/bitmessage/utils/BytesTest.kt similarity index 100% rename from core/src/test/java/ch/dissem/bitmessage/utils/BytesTest.kt rename to core/src/test/kotlin/ch/dissem/bitmessage/utils/BytesTest.kt diff --git a/core/src/test/java/ch/dissem/bitmessage/utils/CollectionsTest.kt b/core/src/test/kotlin/ch/dissem/bitmessage/utils/CollectionsTest.kt similarity index 100% rename from core/src/test/java/ch/dissem/bitmessage/utils/CollectionsTest.kt rename to core/src/test/kotlin/ch/dissem/bitmessage/utils/CollectionsTest.kt diff --git a/core/src/test/java/ch/dissem/bitmessage/utils/ConversationServiceTest.kt b/core/src/test/kotlin/ch/dissem/bitmessage/utils/ConversationServiceTest.kt similarity index 100% rename from core/src/test/java/ch/dissem/bitmessage/utils/ConversationServiceTest.kt rename to core/src/test/kotlin/ch/dissem/bitmessage/utils/ConversationServiceTest.kt diff --git a/core/src/test/java/ch/dissem/bitmessage/utils/DecodeTest.kt b/core/src/test/kotlin/ch/dissem/bitmessage/utils/DecodeTest.kt similarity index 100% rename from core/src/test/java/ch/dissem/bitmessage/utils/DecodeTest.kt rename to core/src/test/kotlin/ch/dissem/bitmessage/utils/DecodeTest.kt diff --git a/core/src/test/java/ch/dissem/bitmessage/utils/EncodeTest.kt b/core/src/test/kotlin/ch/dissem/bitmessage/utils/EncodeTest.kt similarity index 100% rename from core/src/test/java/ch/dissem/bitmessage/utils/EncodeTest.kt rename to core/src/test/kotlin/ch/dissem/bitmessage/utils/EncodeTest.kt diff --git a/core/src/test/java/ch/dissem/bitmessage/utils/SqlStringsTest.kt b/core/src/test/kotlin/ch/dissem/bitmessage/utils/SqlStringsTest.kt similarity index 100% rename from core/src/test/java/ch/dissem/bitmessage/utils/SqlStringsTest.kt rename to core/src/test/kotlin/ch/dissem/bitmessage/utils/SqlStringsTest.kt diff --git a/core/src/test/java/ch/dissem/bitmessage/utils/StringsTest.kt b/core/src/test/kotlin/ch/dissem/bitmessage/utils/StringsTest.kt similarity index 100% rename from core/src/test/java/ch/dissem/bitmessage/utils/StringsTest.kt rename to core/src/test/kotlin/ch/dissem/bitmessage/utils/StringsTest.kt diff --git a/core/src/test/java/ch/dissem/bitmessage/utils/TestBase.kt b/core/src/test/kotlin/ch/dissem/bitmessage/utils/TestBase.kt similarity index 100% rename from core/src/test/java/ch/dissem/bitmessage/utils/TestBase.kt rename to core/src/test/kotlin/ch/dissem/bitmessage/utils/TestBase.kt diff --git a/core/src/test/java/ch/dissem/bitmessage/utils/TestUtils.kt b/core/src/test/kotlin/ch/dissem/bitmessage/utils/TestUtils.kt similarity index 100% rename from core/src/test/java/ch/dissem/bitmessage/utils/TestUtils.kt rename to core/src/test/kotlin/ch/dissem/bitmessage/utils/TestUtils.kt diff --git a/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java b/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java index 1da2910..059af66 100644 --- a/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java +++ b/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java @@ -81,8 +81,8 @@ public class Main { } @Override - public void offerAddresses(List<NetworkAddress> addresses) { - LOG.info("Local node registry ignored offered addresses: " + addresses); + public void offerAddresses(List<NetworkAddress> nodes) { + LOG.info("Local node registry ignored offered addresses: " + nodes); } }); } else { diff --git a/demo/src/test/java/ch/dissem/bitmessage/TestListener.java b/demo/src/test/java/ch/dissem/bitmessage/TestListener.kt similarity index 50% rename from demo/src/test/java/ch/dissem/bitmessage/TestListener.java rename to demo/src/test/java/ch/dissem/bitmessage/TestListener.kt index d2b1cd5..abb1bfd 100644 --- a/demo/src/test/java/ch/dissem/bitmessage/TestListener.java +++ b/demo/src/test/java/ch/dissem/bitmessage/TestListener.kt @@ -14,29 +14,28 @@ * limitations under the License. */ -package ch.dissem.bitmessage; +package ch.dissem.bitmessage -import ch.dissem.bitmessage.entity.Plaintext; +import ch.dissem.bitmessage.entity.Plaintext -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.CompletableFuture +import java.util.concurrent.TimeUnit /** - * Created by chrig on 02.02.2016. + * Test listener that allows you to wait for a message in tests. */ -public class TestListener implements BitmessageContext.Listener { - private CompletableFuture<Plaintext> future = new CompletableFuture<>(); +class TestListener : BitmessageContext.Listener { + private var future = CompletableFuture<Plaintext>() - @Override - public void receive(Plaintext plaintext) { - future.complete(plaintext); + override fun receive(plaintext: Plaintext) { + future.complete(plaintext) } - public void reset() { - future = new CompletableFuture<>(); + fun reset() { + future = CompletableFuture<Plaintext>() } - public Plaintext get(long timeout, TimeUnit unit) throws Exception { - return future.get(timeout, unit); + operator fun get(timeout: Long, unit: TimeUnit): Plaintext { + return future.get(timeout, unit) } } diff --git a/demo/src/test/java/ch/dissem/bitmessage/TestNodeRegistry.java b/demo/src/test/java/ch/dissem/bitmessage/TestNodeRegistry.java index 3ac7348..09f7a85 100644 --- a/demo/src/test/java/ch/dissem/bitmessage/TestNodeRegistry.java +++ b/demo/src/test/java/ch/dissem/bitmessage/TestNodeRegistry.java @@ -50,7 +50,7 @@ class TestNodeRegistry implements NodeRegistry { } @Override - public void offerAddresses(List<NetworkAddress> addresses) { + public void offerAddresses(List<NetworkAddress> nodes) { // Ignore } } diff --git a/networking/src/test/java/ch/dissem/bitmessage/networking/NetworkHandlerTest.kt b/networking/src/test/kotlin/ch/dissem/bitmessage/networking/NetworkHandlerTest.kt similarity index 100% rename from networking/src/test/java/ch/dissem/bitmessage/networking/NetworkHandlerTest.kt rename to networking/src/test/kotlin/ch/dissem/bitmessage/networking/NetworkHandlerTest.kt diff --git a/networking/src/test/java/ch/dissem/bitmessage/networking/TestNodeRegistry.kt b/networking/src/test/kotlin/ch/dissem/bitmessage/networking/TestNodeRegistry.kt similarity index 94% rename from networking/src/test/java/ch/dissem/bitmessage/networking/TestNodeRegistry.kt rename to networking/src/test/kotlin/ch/dissem/bitmessage/networking/TestNodeRegistry.kt index 3c00bf4..a454c91 100644 --- a/networking/src/test/java/ch/dissem/bitmessage/networking/TestNodeRegistry.kt +++ b/networking/src/test/kotlin/ch/dissem/bitmessage/networking/TestNodeRegistry.kt @@ -35,7 +35,7 @@ internal class TestNodeRegistry(vararg nodes: NetworkAddress) : NodeRegistry { return nodes } - override fun offerAddresses(addresses: List<NetworkAddress>) { + override fun offerAddresses(nodes: List<NetworkAddress>) { // Ignore } } diff --git a/repositories/build.gradle b/repositories/build.gradle index 79b8578..1be3306 100644 --- a/repositories/build.gradle +++ b/repositories/build.gradle @@ -17,7 +17,7 @@ dependencies { compile 'org.flywaydb:flyway-core:4.1.2' testCompile 'junit:junit:4.12' testCompile 'com.h2database:h2:1.4.194' - testCompile 'org.mockito:mockito-core:2.7.21' + testCompile 'com.nhaarman:mockito-kotlin:1.4.0' testCompile project(path: ':core', configuration: 'testArtifacts') testCompile project(':cryptography-bc') } diff --git a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcAddressRepositoryTest.java b/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcAddressRepositoryTest.java deleted file mode 100644 index c028837..0000000 --- a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcAddressRepositoryTest.java +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.repository; - -import ch.dissem.bitmessage.entity.BitmessageAddress; -import ch.dissem.bitmessage.entity.valueobject.PrivateKey; -import org.junit.Before; -import org.junit.Test; - -import java.util.List; - -import static ch.dissem.bitmessage.entity.payload.Pubkey.Feature.DOES_ACK; -import static org.junit.Assert.*; - -public class JdbcAddressRepositoryTest extends TestBase { - public static final String CONTACT_A = "BM-2cW7cD5cDQJDNkE7ibmyTxfvGAmnPqa9Vt"; - public static final String CONTACT_B = "BM-2cTtkBnb4BUYDndTKun6D9PjtueP2h1bQj"; - public static final String CONTACT_C = "BM-2cV5f9EpzaYARxtoruSpa6pDoucSf9ZNke"; - public String IDENTITY_A; - public String IDENTITY_B; - - private TestJdbcConfig config; - private JdbcAddressRepository repo; - - @Before - public void setUp() throws InterruptedException { - config = new TestJdbcConfig(); - config.reset(); - - repo = new JdbcAddressRepository(config); - - repo.save(new BitmessageAddress(CONTACT_A)); - repo.save(new BitmessageAddress(CONTACT_B)); - repo.save(new BitmessageAddress(CONTACT_C)); - - BitmessageAddress identityA = new BitmessageAddress(new PrivateKey(false, 1, 1000, 1000, DOES_ACK)); - repo.save(identityA); - IDENTITY_A = identityA.getAddress(); - BitmessageAddress identityB = new BitmessageAddress(new PrivateKey(false, 1, 1000, 1000)); - repo.save(identityB); - IDENTITY_B = identityB.getAddress(); - } - - @Test - public void testFindContact() throws Exception { - BitmessageAddress address = new BitmessageAddress(CONTACT_A); - assertEquals(4, address.getVersion()); - assertEquals(address, repo.findContact(address.getTag())); - assertNull(repo.findIdentity(address.getTag())); - } - - @Test - public void testFindIdentity() throws Exception { - BitmessageAddress identity = new BitmessageAddress(IDENTITY_A); - assertEquals(4, identity.getVersion()); - assertNull(repo.findContact(identity.getTag())); - - BitmessageAddress storedIdentity = repo.findIdentity(identity.getTag()); - assertEquals(identity, storedIdentity); - assertTrue(storedIdentity.has(DOES_ACK)); - } - - @Test - public void testGetIdentities() throws Exception { - List<BitmessageAddress> identities = repo.getIdentities(); - assertEquals(2, identities.size()); - for (BitmessageAddress identity : identities) { - assertNotNull(identity.getPrivateKey()); - } - } - - @Test - public void testGetSubscriptions() throws Exception { - addSubscription("BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h"); - addSubscription("BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ"); - addSubscription("BM-2D9QKN4teYRvoq2fyzpiftPh9WP9qggtzh"); - List<BitmessageAddress> subscriptions = repo.getSubscriptions(); - assertEquals(3, subscriptions.size()); - } - - @Test - public void testGetSubscriptionsForVersion() throws Exception { - addSubscription("BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h"); - addSubscription("BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ"); - addSubscription("BM-2D9QKN4teYRvoq2fyzpiftPh9WP9qggtzh"); - - List<BitmessageAddress> subscriptions; - - subscriptions = repo.getSubscriptions(5); - assertEquals(1, subscriptions.size()); - - subscriptions = repo.getSubscriptions(4); - assertEquals(2, subscriptions.size()); - } - - @Test - public void testGetContacts() throws Exception { - List<BitmessageAddress> contacts = repo.getContacts(); - assertEquals(3, contacts.size()); - for (BitmessageAddress contact : contacts) { - assertNull(contact.getPrivateKey()); - } - } - - @Test - public void ensureNewAddressIsSaved() throws Exception { - repo.save(new BitmessageAddress(new PrivateKey(false, 1, 1000, 1000))); - List<BitmessageAddress> identities = repo.getIdentities(); - assertEquals(3, identities.size()); - } - - @Test - public void ensureExistingAddressIsUpdated() throws Exception { - BitmessageAddress address = repo.getAddress(CONTACT_A); - address.setAlias("Test-Alias"); - repo.save(address); - address = repo.getAddress(address.getAddress()); - assertEquals("Test-Alias", address.getAlias()); - } - - @Test - public void ensureExistingKeysAreNotDeleted() { - BitmessageAddress address = new BitmessageAddress(IDENTITY_A); - address.setAlias("Test"); - repo.save(address); - BitmessageAddress identityA = repo.getAddress(IDENTITY_A); - assertNotNull(identityA.getPubkey()); - assertNotNull(identityA.getPrivateKey()); - assertEquals("Test", identityA.getAlias()); - assertFalse(identityA.isChan()); - } - - @Test - public void ensureNewChanIsSavedAndUpdated() { - BitmessageAddress chan = BitmessageAddress.chan(1, "test"); - repo.save(chan); - BitmessageAddress address = repo.getAddress(chan.getAddress()); - assertNotNull(address); - assertTrue(address.isChan()); - - address.setAlias("Test"); - repo.save(address); - - address = repo.getAddress(chan.getAddress()); - assertNotNull(address); - assertTrue(address.isChan()); - assertEquals("Test", address.getAlias()); - } - - @Test - public void testRemove() throws Exception { - BitmessageAddress address = repo.getAddress(IDENTITY_A); - repo.remove(address); - assertNull(repo.getAddress(IDENTITY_A)); - } - - @Test - public void testGetAddress() throws Exception { - BitmessageAddress address = repo.getAddress(IDENTITY_A); - assertNotNull(address); - assertNotNull(address.getPrivateKey()); - } - - private void addSubscription(String address) { - BitmessageAddress subscription = new BitmessageAddress(address); - subscription.setSubscribed(true); - repo.save(subscription); - } -} diff --git a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcInventoryTest.java b/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcInventoryTest.java deleted file mode 100644 index 0a5c387..0000000 --- a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcInventoryTest.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.repository; - -import ch.dissem.bitmessage.entity.BitmessageAddress; -import ch.dissem.bitmessage.entity.ObjectMessage; -import ch.dissem.bitmessage.entity.payload.GetPubkey; -import ch.dissem.bitmessage.entity.payload.ObjectPayload; -import ch.dissem.bitmessage.entity.valueobject.InventoryVector; -import ch.dissem.bitmessage.ports.Inventory; -import org.junit.Before; -import org.junit.Test; - -import java.util.LinkedList; -import java.util.List; - -import static ch.dissem.bitmessage.entity.payload.ObjectType.GET_PUBKEY; -import static ch.dissem.bitmessage.entity.payload.ObjectType.MSG; -import static ch.dissem.bitmessage.utils.UnixTime.DAY; -import static ch.dissem.bitmessage.utils.UnixTime.now; -import static org.junit.Assert.*; - -public class JdbcInventoryTest extends TestBase { - private TestJdbcConfig config; - private Inventory inventory; - - private InventoryVector inventoryVector1; - private InventoryVector inventoryVector2; - private InventoryVector inventoryVectorIgnore; - - @Before - public void setUp() throws Exception { - config = new TestJdbcConfig(); - config.reset(); - - inventory = new JdbcInventory(config); - - ObjectMessage object1 = getObjectMessage(1, 300, getGetPubkey()); - inventoryVector1 = object1.getInventoryVector(); - inventory.storeObject(object1); - - ObjectMessage object2 = getObjectMessage(2, 300, getGetPubkey()); - inventoryVector2 = object2.getInventoryVector(); - inventory.storeObject(object2); - - ObjectMessage ignore = getObjectMessage(1, -1 * DAY, getGetPubkey()); - inventoryVectorIgnore = ignore.getInventoryVector(); - inventory.storeObject(ignore); - } - - @Test - public void testGetInventory() throws Exception { - List<InventoryVector> inventoryVectors = inventory.getInventory(1); - assertEquals(1, inventoryVectors.size()); - - inventoryVectors = inventory.getInventory(2); - assertEquals(1, inventoryVectors.size()); - } - - @Test - public void testGetMissing() throws Exception { - InventoryVector newIV = getObjectMessage(1, 200, getGetPubkey()).getInventoryVector(); - List<InventoryVector> offer = new LinkedList<>(); - offer.add(newIV); - offer.add(inventoryVector1); - List<InventoryVector> missing = inventory.getMissing(offer, 1, 2); - assertEquals(1, missing.size()); - assertEquals(newIV, missing.get(0)); - } - - @Test - public void testGetObject() throws Exception { - ObjectMessage object = inventory.getObject(inventoryVectorIgnore); - assertNotNull(object); - assertEquals(1, object.getStream()); - assertEquals(inventoryVectorIgnore, object.getInventoryVector()); - } - - @Test - public void testGetObjects() throws Exception { - List<ObjectMessage> objects = inventory.getObjects(1, 4); - assertEquals(2, objects.size()); - - objects = inventory.getObjects(1, 4, GET_PUBKEY); - assertEquals(2, objects.size()); - - objects = inventory.getObjects(1, 4, MSG); - assertEquals(0, objects.size()); - } - - @Test - public void testStoreObject() throws Exception { - ObjectMessage object = getObjectMessage(5, 0, getGetPubkey()); - inventory.storeObject(object); - - assertNotNull(inventory.getObject(object.getInventoryVector())); - } - - @Test - public void testContains() { - ObjectMessage object = getObjectMessage(5, 0, getGetPubkey()); - - assertFalse(inventory.contains(object)); - - inventory.storeObject(object); - - assertTrue(inventory.contains(object)); - } - - @Test - public void testCleanup() throws Exception { - assertNotNull(inventory.getObject(inventoryVectorIgnore)); - inventory.cleanup(); - assertNull(inventory.getObject(inventoryVectorIgnore)); - } - - private ObjectMessage getObjectMessage(long stream, long TTL, ObjectPayload payload) { - return new ObjectMessage.Builder() - .nonce(new byte[8]) - .expiresTime(now() + TTL) - .stream(stream) - .payload(payload) - .build(); - } - - private GetPubkey getGetPubkey() { - return new GetPubkey(new BitmessageAddress("BM-2cW7cD5cDQJDNkE7ibmyTxfvGAmnPqa9Vt")); - } -} diff --git a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcMessageRepositoryTest.java b/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcMessageRepositoryTest.java deleted file mode 100644 index dd0bd07..0000000 --- a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcMessageRepositoryTest.java +++ /dev/null @@ -1,352 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.repository; - -import ch.dissem.bitmessage.BitmessageContext; -import ch.dissem.bitmessage.InternalContext; -import ch.dissem.bitmessage.entity.BitmessageAddress; -import ch.dissem.bitmessage.entity.ObjectMessage; -import ch.dissem.bitmessage.entity.Plaintext; -import ch.dissem.bitmessage.entity.valueobject.ExtendedEncoding; -import ch.dissem.bitmessage.entity.valueobject.Label; -import ch.dissem.bitmessage.entity.valueobject.PrivateKey; -import ch.dissem.bitmessage.entity.valueobject.extended.Message; -import ch.dissem.bitmessage.ports.*; -import ch.dissem.bitmessage.utils.TestUtils; -import ch.dissem.bitmessage.utils.UnixTime; -import org.hamcrest.BaseMatcher; -import org.hamcrest.Description; -import org.hamcrest.Matcher; -import org.junit.Before; -import org.junit.Test; - -import java.util.Arrays; -import java.util.List; -import java.util.UUID; - -import static ch.dissem.bitmessage.entity.Plaintext.Type.MSG; -import static ch.dissem.bitmessage.entity.payload.Pubkey.Feature.DOES_ACK; -import static ch.dissem.bitmessage.utils.Singleton.cryptography; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; -import static org.mockito.Mockito.mock; - -public class JdbcMessageRepositoryTest extends TestBase { - private BitmessageAddress contactA; - private BitmessageAddress contactB; - private BitmessageAddress identity; - - private MessageRepository repo; - - private Label inbox; - private Label sent; - private Label drafts; - private Label unread; - - @Before - public void setUp() throws Exception { - TestJdbcConfig config = new TestJdbcConfig(); - config.reset(); - AddressRepository addressRepo = new JdbcAddressRepository(config); - repo = new JdbcMessageRepository(config); - new InternalContext( - cryptography(), - mock(Inventory.class), - mock(NodeRegistry.class), - mock(NetworkHandler.class), - addressRepo, - repo, - mock(ProofOfWorkRepository.class), - mock(ProofOfWorkEngine.class), - mock(CustomCommandHandler.class), - mock(BitmessageContext.Listener.class), - mock(Labeler.class), - 12345, - 10, 10 - ); - BitmessageAddress tmp = new BitmessageAddress(new PrivateKey(false, 1, 1000, 1000, DOES_ACK)); - contactA = new BitmessageAddress(tmp.getAddress()); - contactA.setPubkey(tmp.getPubkey()); - addressRepo.save(contactA); - contactB = new BitmessageAddress("BM-2cTtkBnb4BUYDndTKun6D9PjtueP2h1bQj"); - addressRepo.save(contactB); - - identity = new BitmessageAddress(new PrivateKey(false, 1, 1000, 1000, DOES_ACK)); - addressRepo.save(identity); - - inbox = repo.getLabels(Label.Type.INBOX).get(0); - sent = repo.getLabels(Label.Type.SENT).get(0); - drafts = repo.getLabels(Label.Type.DRAFT).get(0); - unread = repo.getLabels(Label.Type.UNREAD).get(0); - - addMessage(contactA, identity, Plaintext.Status.RECEIVED, inbox, unread); - addMessage(identity, contactA, Plaintext.Status.DRAFT, drafts); - addMessage(identity, contactB, Plaintext.Status.DRAFT, unread); - } - - @Test - public void ensureLabelsAreRetrieved() throws Exception { - List<Label> labels = repo.getLabels(); - assertEquals(5, labels.size()); - } - - @Test - public void ensureLabelsCanBeRetrievedByType() throws Exception { - List<Label> labels = repo.getLabels(Label.Type.INBOX); - assertEquals(1, labels.size()); - assertEquals("Inbox", labels.get(0).toString()); - } - - @Test - public void ensureMessagesCanBeFoundByLabel() throws Exception { - List<Plaintext> messages = repo.findMessages(inbox); - assertEquals(1, messages.size()); - Plaintext m = messages.get(0); - assertEquals(contactA, m.getFrom()); - assertEquals(identity, m.getTo()); - assertEquals(Plaintext.Status.RECEIVED, m.getStatus()); - } - - @Test - public void ensureUnreadMessagesCanBeFoundForAllLabels() { - int unread = repo.countUnread(null); - assertThat(unread, is(2)); - } - - @Test - public void ensureUnreadMessagesCanBeFoundByLabel() { - int unread = repo.countUnread(inbox); - assertThat(unread, is(1)); - } - - @Test - public void ensureMessageCanBeRetrievedByInitialHash() { - byte[] initialHash = new byte[64]; - Plaintext message = repo.findMessages(contactA).get(0); - message.setInitialHash(initialHash); - repo.save(message); - Plaintext other = repo.getMessage(initialHash); - assertThat(other, is(message)); - } - - @Test - public void ensureAckMessageCanBeUpdatedAndRetrieved() { - byte[] initialHash = new byte[64]; - Plaintext message = repo.findMessages(contactA).get(0); - message.setInitialHash(initialHash); - ObjectMessage ackMessage = message.getAckMessage(); - repo.save(message); - Plaintext other = repo.getMessage(initialHash); - assertThat(other, is(message)); - assertThat(other.getAckMessage(), is(ackMessage)); - } - - @Test - public void testFindMessagesByStatus() throws Exception { - List<Plaintext> messages = repo.findMessages(Plaintext.Status.RECEIVED); - assertEquals(1, messages.size()); - Plaintext m = messages.get(0); - assertEquals(contactA, m.getFrom()); - assertEquals(identity, m.getTo()); - assertEquals(Plaintext.Status.RECEIVED, m.getStatus()); - } - - @Test - public void testFindMessagesByStatusAndRecipient() throws Exception { - List<Plaintext> messages = repo.findMessages(Plaintext.Status.DRAFT, contactB); - assertEquals(1, messages.size()); - Plaintext m = messages.get(0); - assertEquals(identity, m.getFrom()); - assertEquals(contactB, m.getTo()); - assertEquals(Plaintext.Status.DRAFT, m.getStatus()); - } - - @Test - public void testSave() throws Exception { - Plaintext message = new Plaintext.Builder(MSG) - .IV(TestUtils.randomInventoryVector()) - .from(identity) - .to(contactA) - .message("Subject", "Message") - .status(Plaintext.Status.DOING_PROOF_OF_WORK) - .build(); - repo.save(message); - - assertNotNull(message.getId()); - - message.addLabels(inbox); - repo.save(message); - - List<Plaintext> messages = repo.findMessages(Plaintext.Status.DOING_PROOF_OF_WORK); - - assertEquals(1, messages.size()); - assertNotNull(messages.get(0).getInventoryVector()); - } - - @Test - public void testUpdate() throws Exception { - List<Plaintext> messages = repo.findMessages(Plaintext.Status.DRAFT, contactA); - Plaintext message = messages.get(0); - message.setInventoryVector(TestUtils.randomInventoryVector()); - repo.save(message); - - messages = repo.findMessages(Plaintext.Status.DRAFT, contactA); - assertEquals(1, messages.size()); - assertNotNull(messages.get(0).getInventoryVector()); - } - - @Test - public void ensureMessageIsRemoved() throws Exception { - Plaintext toRemove = repo.findMessages(Plaintext.Status.DRAFT, contactB).get(0); - List<Plaintext> messages = repo.findMessages(Plaintext.Status.DRAFT); - assertEquals(2, messages.size()); - repo.remove(toRemove); - messages = repo.findMessages(Plaintext.Status.DRAFT); - assertThat(messages, hasSize(1)); - } - - @Test - public void ensureUnacknowledgedMessagesAreFoundForResend() throws Exception { - Plaintext message = new Plaintext.Builder(MSG) - .IV(TestUtils.randomInventoryVector()) - .from(identity) - .to(contactA) - .message("Subject", "Message") - .sent(UnixTime.now()) - .status(Plaintext.Status.SENT) - .ttl(2) - .build(); - message.updateNextTry(); - assertThat(message.getRetries(), is(1)); - assertThat(message.getNextTry(), greaterThan(UnixTime.now())); - assertThat(message.getNextTry(), lessThanOrEqualTo(UnixTime.now() + 2)); - repo.save(message); - Thread.sleep(4100); // somewhat longer than 2*TTL - List<Plaintext> messagesToResend = repo.findMessagesToResend(); - assertThat(messagesToResend, hasSize(1)); - - message.updateNextTry(); - assertThat(message.getRetries(), is(2)); - assertThat(message.getNextTry(), greaterThan(UnixTime.now())); - repo.save(message); - messagesToResend = repo.findMessagesToResend(); - assertThat(messagesToResend, empty()); - } - - @Test - public void ensureParentsAreSaved() { - Plaintext parent = storeConversation(); - - List<Plaintext> responses = repo.findResponses(parent); - assertThat(responses, hasSize(2)); - assertThat(responses, hasItem(hasMessage("Re: new test", "Nice!"))); - assertThat(responses, hasItem(hasMessage("Re: new test", "PS: it did work!"))); - } - - @Test - public void ensureConversationCanBeRetrieved() { - Plaintext root = storeConversation(); - List<UUID> conversations = repo.findConversations(inbox); - assertThat(conversations, hasSize(2)); - assertThat(conversations, hasItem(root.getConversationId())); - } - - private Plaintext addMessage(BitmessageAddress from, BitmessageAddress to, Plaintext.Status status, Label... labels) { - ExtendedEncoding content = new Message.Builder() - .subject("Subject") - .body("Message") - .build(); - return addMessage(from, to, content, status, labels); - } - - private Plaintext addMessage(BitmessageAddress from, BitmessageAddress to, - ExtendedEncoding content, Plaintext.Status status, Label... labels) { - Plaintext message = new Plaintext.Builder(MSG) - .IV(TestUtils.randomInventoryVector()) - .from(from) - .to(to) - .message(content) - .status(status) - .labels(Arrays.asList(labels)) - .build(); - repo.save(message); - return message; - } - - private Plaintext storeConversation() { - Plaintext older = addMessage(identity, contactA, - new Message.Builder() - .subject("hey there") - .body("does it work?") - .build(), - Plaintext.Status.SENT, sent); - - Plaintext root = addMessage(identity, contactA, - new Message.Builder() - .subject("new test") - .body("There's a new test in town!") - .build(), - Plaintext.Status.SENT, sent); - - addMessage(contactA, identity, - new Message.Builder() - .subject("Re: new test") - .body("Nice!") - .addParent(root) - .build(), - Plaintext.Status.RECEIVED, inbox); - - addMessage(contactA, identity, - new Message.Builder() - .subject("Re: new test") - .body("PS: it did work!") - .addParent(root) - .addParent(older) - .build(), - Plaintext.Status.RECEIVED, inbox); - - return repo.getMessage(root.getId()); - } - - private Matcher<Plaintext> hasMessage(String subject, String body) { - return new BaseMatcher<Plaintext>() { - @Override - public void describeTo(Description description) { - description.appendText("Subject: ").appendText(subject); - description.appendText(", "); - description.appendText("Body: ").appendText(body); - } - - @Override - public boolean matches(Object item) { - if (item instanceof Plaintext) { - Plaintext message = (Plaintext) item; - if (subject != null && !subject.equals(message.getSubject())) { - return false; - } - if (body != null && !body.equals(message.getText())) { - return false; - } - return true; - } else { - return false; - } - } - }; - } -} diff --git a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcNodeRegistryTest.java b/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcNodeRegistryTest.java deleted file mode 100644 index 1078e24..0000000 --- a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcNodeRegistryTest.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright 2015 Christian Basler - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package ch.dissem.bitmessage.repository; - -import ch.dissem.bitmessage.entity.valueobject.NetworkAddress; -import ch.dissem.bitmessage.ports.NodeRegistry; -import org.junit.Before; -import org.junit.Test; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import static ch.dissem.bitmessage.utils.UnixTime.now; -import static org.hamcrest.Matchers.empty; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; - -/** - * Please note that some tests fail if there is no internet connection, - * as the initial nodes' IP addresses are determined by DNS lookup. - */ -public class JdbcNodeRegistryTest extends TestBase { - private TestJdbcConfig config; - private NodeRegistry registry; - - @Before - public void setUp() throws Exception { - config = new TestJdbcConfig(); - config.reset(); - registry = new JdbcNodeRegistry(config); - - registry.offerAddresses(Arrays.asList( - createAddress(1, 8444, 1, now()), - createAddress(2, 8444, 1, now()), - createAddress(3, 8444, 1, now()), - createAddress(4, 8444, 2, now()) - )); - } - - @Test - public void ensureGetKnownNodesWithoutStreamsYieldsEmpty() { - assertThat(registry.getKnownAddresses(10), empty()); - } - - @Test - public void ensurePredefinedNodeIsReturnedWhenDatabaseIsEmpty() throws Exception { - config.reset(); - List<NetworkAddress> knownAddresses = registry.getKnownAddresses(2, 1); - assertEquals(1, knownAddresses.size()); - } - - @Test - public void testGetKnownAddresses() throws Exception { - List<NetworkAddress> knownAddresses = registry.getKnownAddresses(2, 1); - assertEquals(2, knownAddresses.size()); - - knownAddresses = registry.getKnownAddresses(1000, 1); - assertEquals(3, knownAddresses.size()); - } - - @Test - public void testOfferAddresses() throws Exception { - registry.offerAddresses(Arrays.asList( - createAddress(1, 8444, 1, now()), - createAddress(10, 8444, 1, now()), - createAddress(11, 8444, 1, now()) - )); - - List<NetworkAddress> knownAddresses = registry.getKnownAddresses(1000, 1); - assertEquals(5, knownAddresses.size()); - - registry.offerAddresses(Collections.singletonList( - createAddress(1, 8445, 1, now()) - )); - - knownAddresses = registry.getKnownAddresses(1000, 1); - assertEquals(6, knownAddresses.size()); - } - - private NetworkAddress createAddress(int lastByte, int port, long stream, long time) { - return new NetworkAddress.Builder() - .ipv6(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, lastByte) - .port(port) - .stream(stream) - .time(time) - .build(); - } -} diff --git a/repositories/src/test/kotlin/ch/dissem/bitmessage/repository/JdbcAddressRepositoryTest.kt b/repositories/src/test/kotlin/ch/dissem/bitmessage/repository/JdbcAddressRepositoryTest.kt new file mode 100644 index 0000000..5e05aac --- /dev/null +++ b/repositories/src/test/kotlin/ch/dissem/bitmessage/repository/JdbcAddressRepositoryTest.kt @@ -0,0 +1,180 @@ +/* + * Copyright 2015 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.repository + +import ch.dissem.bitmessage.entity.BitmessageAddress +import ch.dissem.bitmessage.entity.payload.Pubkey.Feature.DOES_ACK +import ch.dissem.bitmessage.entity.valueobject.PrivateKey +import org.junit.Assert.* +import org.junit.Before +import org.junit.Test + +class JdbcAddressRepositoryTest : TestBase() { + private val CONTACT_A = "BM-2cW7cD5cDQJDNkE7ibmyTxfvGAmnPqa9Vt" + private val CONTACT_B = "BM-2cTtkBnb4BUYDndTKun6D9PjtueP2h1bQj" + private val CONTACT_C = "BM-2cV5f9EpzaYARxtoruSpa6pDoucSf9ZNke" + + private lateinit var IDENTITY_A: String + private lateinit var IDENTITY_B: String + + private lateinit var config: TestJdbcConfig + private lateinit var repo: JdbcAddressRepository + + @Before + fun setUp() { + config = TestJdbcConfig() + config.reset() + + repo = JdbcAddressRepository(config) + + repo.save(BitmessageAddress(CONTACT_A)) + repo.save(BitmessageAddress(CONTACT_B)) + repo.save(BitmessageAddress(CONTACT_C)) + + val identityA = BitmessageAddress(PrivateKey(false, 1, 1000, 1000, DOES_ACK)) + repo.save(identityA) + IDENTITY_A = identityA.address + val identityB = BitmessageAddress(PrivateKey(false, 1, 1000, 1000)) + repo.save(identityB) + IDENTITY_B = identityB.address + } + + @Test + fun `ensure contact can be found`() { + val address = BitmessageAddress(CONTACT_A) + assertEquals(4, address.version) + assertEquals(address, repo.findContact(address.tag!!)) + assertNull(repo.findIdentity(address.tag!!)) + } + + @Test + fun `ensure identity can be found`() { + val identity = BitmessageAddress(IDENTITY_A) + assertEquals(4, identity.version) + assertNull(repo.findContact(identity.tag!!)) + + val storedIdentity = repo.findIdentity(identity.tag!!) + assertEquals(identity, storedIdentity) + assertTrue(storedIdentity!!.has(DOES_ACK)) + } + + @Test + fun `ensure identities are retrieved`() { + val identities = repo.getIdentities() + assertEquals(2, identities.size.toLong()) + for (identity in identities) { + assertNotNull(identity.privateKey) + } + } + + @Test + fun `ensure subscriptions are retrieved`() { + addSubscription("BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h") + addSubscription("BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ") + addSubscription("BM-2D9QKN4teYRvoq2fyzpiftPh9WP9qggtzh") + val subscriptions = repo.getSubscriptions() + assertEquals(3, subscriptions.size.toLong()) + } + + @Test + fun `ensure subscriptions are retrieved for given version`() { + addSubscription("BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h") + addSubscription("BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ") + addSubscription("BM-2D9QKN4teYRvoq2fyzpiftPh9WP9qggtzh") + + var subscriptions = repo.getSubscriptions(5) + + assertEquals(1, subscriptions.size.toLong()) + + subscriptions = repo.getSubscriptions(4) + assertEquals(2, subscriptions.size.toLong()) + } + + @Test + fun `ensure contacts are retrieved`() { + val contacts = repo.getContacts() + assertEquals(3, contacts.size.toLong()) + for (contact in contacts) { + assertNull(contact.privateKey) + } + } + + @Test + fun `ensure new address is saved`() { + repo.save(BitmessageAddress(PrivateKey(false, 1, 1000, 1000))) + val identities = repo.getIdentities() + assertEquals(3, identities.size.toLong()) + } + + @Test + fun `ensure existing address is updated`() { + var address = repo.getAddress(CONTACT_A) + address!!.alias = "Test-Alias" + repo.save(address) + address = repo.getAddress(address.address) + assertEquals("Test-Alias", address!!.alias) + } + + @Test + fun `ensure existing keys are not deleted`() { + val address = BitmessageAddress(IDENTITY_A) + address.alias = "Test" + repo.save(address) + val identityA = repo.getAddress(IDENTITY_A) + assertNotNull(identityA!!.pubkey) + assertNotNull(identityA.privateKey) + assertEquals("Test", identityA.alias) + assertFalse(identityA.isChan) + } + + @Test + fun `ensure new chan is saved and updated`() { + val chan = BitmessageAddress.chan(1, "test") + repo.save(chan) + var address = repo.getAddress(chan.address) + assertNotNull(address) + assertTrue(address!!.isChan) + + address.alias = "Test" + repo.save(address) + + address = repo.getAddress(chan.address) + assertNotNull(address) + assertTrue(address!!.isChan) + assertEquals("Test", address.alias) + } + + @Test + fun `ensure address is removed`() { + val address = repo.getAddress(IDENTITY_A) + repo.remove(address!!) + assertNull(repo.getAddress(IDENTITY_A)) + } + + @Test + fun `ensure address can be retrieved`() { + val address = repo.getAddress(IDENTITY_A) + assertNotNull(address) + assertNotNull(address!!.privateKey) + } + + private fun addSubscription(address: String) { + val subscription = BitmessageAddress(address) + subscription.isSubscribed = true + repo.save(subscription) + } +} diff --git a/repositories/src/test/kotlin/ch/dissem/bitmessage/repository/JdbcInventoryTest.kt b/repositories/src/test/kotlin/ch/dissem/bitmessage/repository/JdbcInventoryTest.kt new file mode 100644 index 0000000..4aa6e69 --- /dev/null +++ b/repositories/src/test/kotlin/ch/dissem/bitmessage/repository/JdbcInventoryTest.kt @@ -0,0 +1,138 @@ +/* + * Copyright 2015 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.repository + +import ch.dissem.bitmessage.entity.BitmessageAddress +import ch.dissem.bitmessage.entity.ObjectMessage +import ch.dissem.bitmessage.entity.payload.GetPubkey +import ch.dissem.bitmessage.entity.payload.ObjectPayload +import ch.dissem.bitmessage.entity.payload.ObjectType.GET_PUBKEY +import ch.dissem.bitmessage.entity.payload.ObjectType.MSG +import ch.dissem.bitmessage.entity.valueobject.InventoryVector +import ch.dissem.bitmessage.ports.Inventory +import ch.dissem.bitmessage.utils.UnixTime.DAY +import ch.dissem.bitmessage.utils.UnixTime.now +import org.junit.Assert.* +import org.junit.Before +import org.junit.Test +import java.util.* + +class JdbcInventoryTest : TestBase() { + private lateinit var config: TestJdbcConfig + private lateinit var inventory: Inventory + + private lateinit var inventoryVector1: InventoryVector + private lateinit var inventoryVector2: InventoryVector + private lateinit var inventoryVectorIgnore: InventoryVector + + @Before + fun setUp() { + config = TestJdbcConfig() + config.reset() + + inventory = JdbcInventory(config) + + val object1 = getObjectMessage(1, 300, getPubkey) + inventoryVector1 = object1.inventoryVector + inventory.storeObject(object1) + + val object2 = getObjectMessage(2, 300, getPubkey) + inventoryVector2 = object2.inventoryVector + inventory.storeObject(object2) + + val ignore = getObjectMessage(1, -1 * DAY, getPubkey) + inventoryVectorIgnore = ignore.inventoryVector + inventory.storeObject(ignore) + } + + @Test + fun `ensure inventory can be retrieved`() { + var inventoryVectors = inventory.getInventory(1) + assertEquals(1, inventoryVectors.size.toLong()) + + inventoryVectors = inventory.getInventory(2) + assertEquals(1, inventoryVectors.size.toLong()) + } + + @Test + fun `ensure the IVs of missing objects are returned`() { + val newIV = getObjectMessage(1, 200, getPubkey).inventoryVector + val offer = LinkedList<InventoryVector>() + offer.add(newIV) + offer.add(inventoryVector1) + val missing = inventory.getMissing(offer, 1, 2) + assertEquals(1, missing.size.toLong()) + assertEquals(newIV, missing[0]) + } + + @Test + fun `ensure single object can be retrieved`() { + val `object` = inventory.getObject(inventoryVectorIgnore) + assertNotNull(`object`) + assertEquals(1, `object`!!.stream) + assertEquals(inventoryVectorIgnore, `object`.inventoryVector) + } + + @Test + fun `ensure objects can be retrieved`() { + var objects = inventory.getObjects(1, 4) + assertEquals(2, objects.size.toLong()) + + objects = inventory.getObjects(1, 4, GET_PUBKEY) + assertEquals(2, objects.size.toLong()) + + objects = inventory.getObjects(1, 4, MSG) + assertEquals(0, objects.size.toLong()) + } + + @Test + fun `ensure object can be stored`() { + val `object` = getObjectMessage(5, 0, getPubkey) + inventory.storeObject(`object`) + + assertNotNull(inventory.getObject(`object`.inventoryVector)) + } + + @Test + fun `ensure contained objects are recognized`() { + val `object` = getObjectMessage(5, 0, getPubkey) + + assertFalse(inventory.contains(`object`)) + + inventory.storeObject(`object`) + + assertTrue(inventory.contains(`object`)) + } + + @Test + fun `ensure inventory is cleaned up`() { + assertNotNull(inventory.getObject(inventoryVectorIgnore)) + inventory.cleanup() + assertNull(inventory.getObject(inventoryVectorIgnore)) + } + + private fun getObjectMessage(stream: Long, TTL: Long, payload: ObjectPayload): ObjectMessage { + return ObjectMessage( + nonce = ByteArray(8), + expiresTime = now + TTL, + stream = stream, + payload = payload + ) + } + + private val getPubkey: GetPubkey = GetPubkey(BitmessageAddress("BM-2cW7cD5cDQJDNkE7ibmyTxfvGAmnPqa9Vt")) +} diff --git a/repositories/src/test/kotlin/ch/dissem/bitmessage/repository/JdbcMessageRepositoryTest.kt b/repositories/src/test/kotlin/ch/dissem/bitmessage/repository/JdbcMessageRepositoryTest.kt new file mode 100644 index 0000000..91753f5 --- /dev/null +++ b/repositories/src/test/kotlin/ch/dissem/bitmessage/repository/JdbcMessageRepositoryTest.kt @@ -0,0 +1,336 @@ +/* + * Copyright 2015 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.repository + +import ch.dissem.bitmessage.cryptography.bc.BouncyCryptography +import ch.dissem.bitmessage.entity.BitmessageAddress +import ch.dissem.bitmessage.entity.ObjectMessage +import ch.dissem.bitmessage.entity.Plaintext +import ch.dissem.bitmessage.entity.Plaintext.Type.MSG +import ch.dissem.bitmessage.entity.payload.Pubkey.Feature.DOES_ACK +import ch.dissem.bitmessage.entity.valueobject.ExtendedEncoding +import ch.dissem.bitmessage.entity.valueobject.Label +import ch.dissem.bitmessage.entity.valueobject.PrivateKey +import ch.dissem.bitmessage.entity.valueobject.extended.Message +import ch.dissem.bitmessage.ports.MessageRepository +import ch.dissem.bitmessage.utils.TestUtils +import ch.dissem.bitmessage.utils.TestUtils.mockedInternalContext +import ch.dissem.bitmessage.utils.UnixTime +import org.hamcrest.BaseMatcher +import org.hamcrest.CoreMatchers.`is` +import org.hamcrest.Description +import org.hamcrest.Matcher +import org.hamcrest.Matchers.* +import org.junit.Assert.* +import org.junit.Before +import org.junit.Test +import java.util.* + +class JdbcMessageRepositoryTest : TestBase() { + private lateinit var contactA: BitmessageAddress + private lateinit var contactB: BitmessageAddress + private lateinit var identity: BitmessageAddress + + private lateinit var repo: MessageRepository + + private lateinit var inbox: Label + private lateinit var sent: Label + private lateinit var drafts: Label + private lateinit var unread: Label + + @Before + fun setUp() { + val config = TestJdbcConfig() + config.reset() + val addressRepo = JdbcAddressRepository(config) + repo = JdbcMessageRepository(config) + mockedInternalContext( + cryptography = BouncyCryptography(), + addressRepository = addressRepo, + messageRepository = repo, + port = 12345, + connectionTTL = 10, + connectionLimit = 10 + ) + val tmp = BitmessageAddress(PrivateKey(false, 1, 1000, 1000, DOES_ACK)) + contactA = BitmessageAddress(tmp.address) + contactA.pubkey = tmp.pubkey + addressRepo.save(contactA) + contactB = BitmessageAddress("BM-2cTtkBnb4BUYDndTKun6D9PjtueP2h1bQj") + addressRepo.save(contactB) + + identity = BitmessageAddress(PrivateKey(false, 1, 1000, 1000, DOES_ACK)) + addressRepo.save(identity) + + inbox = repo.getLabels(Label.Type.INBOX)[0] + sent = repo.getLabels(Label.Type.SENT)[0] + drafts = repo.getLabels(Label.Type.DRAFT)[0] + unread = repo.getLabels(Label.Type.UNREAD)[0] + + addMessage(contactA, identity, Plaintext.Status.RECEIVED, inbox, unread) + addMessage(identity, contactA, Plaintext.Status.DRAFT, drafts) + addMessage(identity, contactB, Plaintext.Status.DRAFT, unread) + } + + @Test + fun `ensure labels are retrieved`() { + val labels = repo.getLabels() + assertEquals(5, labels.size.toLong()) + } + + @Test + fun `ensure labels can be retrieved by type`() { + val labels = repo.getLabels(Label.Type.INBOX) + assertEquals(1, labels.size.toLong()) + assertEquals("Inbox", labels[0].toString()) + } + + @Test + fun `ensure messages can be found by label`() { + val messages = repo.findMessages(inbox) + assertEquals(1, messages.size.toLong()) + val m = messages[0] + assertEquals(contactA, m.from) + assertEquals(identity, m.to) + assertEquals(Plaintext.Status.RECEIVED, m.status) + } + + @Test + fun `ensure unread messages can be found for all labels`() { + val unread = repo.countUnread(null) + assertThat(unread, `is`(2)) + } + + @Test + fun `ensure unread messages can be found by label`() { + val unread = repo.countUnread(inbox) + assertThat(unread, `is`(1)) + } + + @Test + fun `ensure message can be retrieved by initial hash`() { + val initialHash = ByteArray(64) + val message = repo.findMessages(contactA)[0] + message.initialHash = initialHash + repo.save(message) + val other = repo.getMessage(initialHash) + assertThat<Plaintext>(other, `is`(message)) + } + + @Test + fun `ensure ack message can be updated and retrieved`() { + val initialHash = ByteArray(64) + val message = repo.findMessages(contactA)[0] + message.initialHash = initialHash + val ackMessage = message.ackMessage + repo.save(message) + val other = repo.getMessage(initialHash)!! + assertThat<Plaintext>(other, `is`(message)) + assertThat<ObjectMessage>(other.ackMessage, `is`<ObjectMessage>(ackMessage)) + } + + @Test + fun `ensure messages can be found by status`() { + val messages = repo.findMessages(Plaintext.Status.RECEIVED) + assertEquals(1, messages.size.toLong()) + val m = messages[0] + assertEquals(contactA, m.from) + assertEquals(identity, m.to) + assertEquals(Plaintext.Status.RECEIVED, m.status) + } + + @Test + fun `ensure messages can be found by status and recipient`() { + val messages = repo.findMessages(Plaintext.Status.DRAFT, contactB) + assertEquals(1, messages.size.toLong()) + val m = messages[0] + assertEquals(identity, m.from) + assertEquals(contactB, m.to) + assertEquals(Plaintext.Status.DRAFT, m.status) + } + + @Test + fun `ensure message can be saved`() { + val message = Plaintext.Builder(MSG) + .IV(TestUtils.randomInventoryVector()) + .from(identity) + .to(contactA) + .message("Subject", "Message") + .status(Plaintext.Status.DOING_PROOF_OF_WORK) + .build() + repo.save(message) + + assertNotNull(message.id) + + message.addLabels(inbox) + repo.save(message) + + val messages = repo.findMessages(Plaintext.Status.DOING_PROOF_OF_WORK) + + assertEquals(1, messages.size.toLong()) + assertNotNull(messages[0].inventoryVector) + } + + @Test + fun `ensure message can be updated`() { + var messages = repo.findMessages(Plaintext.Status.DRAFT, contactA) + val message = messages[0] + message.inventoryVector = TestUtils.randomInventoryVector() + repo.save(message) + + messages = repo.findMessages(Plaintext.Status.DRAFT, contactA) + assertEquals(1, messages.size.toLong()) + assertNotNull(messages[0].inventoryVector) + } + + @Test + fun `ensure message is removed`() { + val toRemove = repo.findMessages(Plaintext.Status.DRAFT, contactB)[0] + var messages = repo.findMessages(Plaintext.Status.DRAFT) + assertEquals(2, messages.size.toLong()) + repo.remove(toRemove) + messages = repo.findMessages(Plaintext.Status.DRAFT) + assertThat(messages, hasSize<Plaintext>(1)) + } + + @Test + fun `ensure unacknowledged messages are found for resend`() { + val message = Plaintext.Builder(MSG) + .IV(TestUtils.randomInventoryVector()) + .from(identity) + .to(contactA) + .message("Subject", "Message") + .sent(UnixTime.now) + .status(Plaintext.Status.SENT) + .ttl(2) + .build() + message.updateNextTry() + assertThat(message.retries, `is`(1)) + assertThat<Long>(message.nextTry, greaterThan(UnixTime.now)) + assertThat<Long>(message.nextTry, lessThanOrEqualTo(UnixTime.now + 2)) + repo.save(message) + Thread.sleep(4100) // somewhat longer than 2*TTL + var messagesToResend = repo.findMessagesToResend() + assertThat(messagesToResend, hasSize<Plaintext>(1)) + + message.updateNextTry() + assertThat(message.retries, `is`(2)) + assertThat<Long>(message.nextTry, greaterThan(UnixTime.now)) + repo.save(message) + messagesToResend = repo.findMessagesToResend() + assertThat(messagesToResend, empty<Plaintext>()) + } + + @Test + fun `ensure parents are saved`() { + val parent = storeConversation() + + val responses = repo.findResponses(parent) + assertThat(responses, hasSize<Plaintext>(2)) + assertThat(responses, hasItem(hasMessage("Re: new test", "Nice!"))) + assertThat(responses, hasItem(hasMessage("Re: new test", "PS: it did work!"))) + } + + @Test + fun `ensure conversation can be retrieved`() { + val root = storeConversation() + val conversations = repo.findConversations(inbox) + assertThat(conversations, hasSize<UUID>(2)) + assertThat(conversations, hasItem(root.conversationId)) + } + + private fun addMessage(from: BitmessageAddress, to: BitmessageAddress, status: Plaintext.Status, vararg labels: Label): Plaintext { + val content = Message.Builder() + .subject("Subject") + .body("Message") + .build() + return addMessage(from, to, content, status, *labels) + } + + private fun addMessage(from: BitmessageAddress, to: BitmessageAddress, + content: ExtendedEncoding, status: Plaintext.Status, vararg labels: Label): Plaintext { + val message = Plaintext.Builder(MSG) + .IV(TestUtils.randomInventoryVector()) + .from(from) + .to(to) + .message(content) + .status(status) + .labels(Arrays.asList(*labels)) + .build() + repo.save(message) + return message + } + + private fun storeConversation(): Plaintext { + val older = addMessage(identity, contactA, + Message.Builder() + .subject("hey there") + .body("does it work?") + .build(), + Plaintext.Status.SENT, sent) + + val root = addMessage(identity, contactA, + Message.Builder() + .subject("new test") + .body("There's a new test in town!") + .build(), + Plaintext.Status.SENT, sent) + + addMessage(contactA, identity, + Message.Builder() + .subject("Re: new test") + .body("Nice!") + .addParent(root) + .build(), + Plaintext.Status.RECEIVED, inbox) + + addMessage(contactA, identity, + Message.Builder() + .subject("Re: new test") + .body("PS: it did work!") + .addParent(root) + .addParent(older) + .build(), + Plaintext.Status.RECEIVED, inbox) + + return repo.getMessage(root.id!!) + } + + private fun hasMessage(subject: String?, body: String?): Matcher<Plaintext> { + return object : BaseMatcher<Plaintext>() { + override fun describeTo(description: Description) { + description.appendText("Subject: ").appendText(subject) + description.appendText(", ") + description.appendText("Body: ").appendText(body) + } + + override fun matches(item: Any): Boolean { + if (item is Plaintext) { + if (subject != null && subject != item.subject) { + return false + } + if (body != null && body != item.text) { + return false + } + return true + } else { + return false + } + } + } + } +} diff --git a/repositories/src/test/kotlin/ch/dissem/bitmessage/repository/JdbcNodeRegistryTest.kt b/repositories/src/test/kotlin/ch/dissem/bitmessage/repository/JdbcNodeRegistryTest.kt new file mode 100644 index 0000000..2b604d4 --- /dev/null +++ b/repositories/src/test/kotlin/ch/dissem/bitmessage/repository/JdbcNodeRegistryTest.kt @@ -0,0 +1,97 @@ +/* + * Copyright 2015 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.repository + +import ch.dissem.bitmessage.entity.valueobject.NetworkAddress +import ch.dissem.bitmessage.ports.NodeRegistry +import ch.dissem.bitmessage.utils.UnixTime.now +import org.hamcrest.Matchers.empty +import org.junit.Assert.assertEquals +import org.junit.Assert.assertThat +import org.junit.Before +import org.junit.Test +import java.util.* + +/** + * Please note that some tests fail if there is no internet connection, + * as the initial nodes' IP addresses are determined by DNS lookup. + */ +class JdbcNodeRegistryTest : TestBase() { + private lateinit var config: TestJdbcConfig + private lateinit var registry: NodeRegistry + + @Before + fun setUp() { + config = TestJdbcConfig() + config.reset() + registry = JdbcNodeRegistry(config) + + registry.offerAddresses(Arrays.asList( + createAddress(1, 8444, 1, now), + createAddress(2, 8444, 1, now), + createAddress(3, 8444, 1, now), + createAddress(4, 8444, 2, now) + )) + } + + @Test + fun `ensure getKnownNodes() without streams yields empty`() { + assertThat(registry.getKnownAddresses(10), empty<NetworkAddress>()) + } + + @Test + fun `ensure predefined node is returned when database is empty`() { + config.reset() + val knownAddresses = registry.getKnownAddresses(2, 1) + assertEquals(1, knownAddresses.size.toLong()) + } + + @Test + fun `ensure known addresses are retrieved`() { + var knownAddresses = registry.getKnownAddresses(2, 1) + assertEquals(2, knownAddresses.size.toLong()) + + knownAddresses = registry.getKnownAddresses(1000, 1) + assertEquals(3, knownAddresses.size.toLong()) + } + + @Test + fun `ensure offered addresses are added`() { + registry.offerAddresses(Arrays.asList( + createAddress(1, 8444, 1, now), + createAddress(10, 8444, 1, now), + createAddress(11, 8444, 1, now) + )) + + var knownAddresses = registry.getKnownAddresses(1000, 1) + assertEquals(5, knownAddresses.size.toLong()) + + registry.offerAddresses(listOf(createAddress(1, 8445, 1, now))) + + knownAddresses = registry.getKnownAddresses(1000, 1) + assertEquals(6, knownAddresses.size.toLong()) + } + + private fun createAddress(lastByte: Int, port: Int, stream: Long, time: Long): NetworkAddress { + return NetworkAddress.Builder() + .ipv6(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, lastByte) + .port(port) + .stream(stream) + .time(time) + .build() + } +} diff --git a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepositoryTest.kt b/repositories/src/test/kotlin/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepositoryTest.kt similarity index 100% rename from repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepositoryTest.kt rename to repositories/src/test/kotlin/ch/dissem/bitmessage/repository/JdbcProofOfWorkRepositoryTest.kt diff --git a/repositories/src/test/java/ch/dissem/bitmessage/repository/TestBase.kt b/repositories/src/test/kotlin/ch/dissem/bitmessage/repository/TestBase.kt similarity index 100% rename from repositories/src/test/java/ch/dissem/bitmessage/repository/TestBase.kt rename to repositories/src/test/kotlin/ch/dissem/bitmessage/repository/TestBase.kt diff --git a/repositories/src/test/java/ch/dissem/bitmessage/repository/TestJdbcConfig.java b/repositories/src/test/kotlin/ch/dissem/bitmessage/repository/TestJdbcConfig.kt similarity index 51% rename from repositories/src/test/java/ch/dissem/bitmessage/repository/TestJdbcConfig.java rename to repositories/src/test/kotlin/ch/dissem/bitmessage/repository/TestJdbcConfig.kt index 8b96ed8..c5c9ec6 100644 --- a/repositories/src/test/java/ch/dissem/bitmessage/repository/TestJdbcConfig.java +++ b/repositories/src/test/kotlin/ch/dissem/bitmessage/repository/TestJdbcConfig.kt @@ -14,35 +14,25 @@ * limitations under the License. */ -package ch.dissem.bitmessage.repository; +package ch.dissem.bitmessage.repository -import org.h2.tools.Server; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.sql.SQLException; +import org.h2.tools.Server +import org.slf4j.LoggerFactory /** - * JdbcConfig to be used for tests. Uses an in-memory database and adds a useful {@link #reset()} method resetting + * JdbcConfig to be used for tests. Uses an in-memory database and adds a useful [.reset] method resetting * the database. */ -public class TestJdbcConfig extends JdbcConfig { - private static final Logger LOG = LoggerFactory.getLogger(TestJdbcConfig.class); +class TestJdbcConfig : JdbcConfig("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1", "sa", null) { - static { - try { - Server.createTcpServer().start(); - } catch (SQLException e) { - LOG.error(e.getMessage(), e); + fun reset() { + flyway.clean() + flyway.migrate() + } + + companion object { + init { + Server.createTcpServer().start() } } - - public TestJdbcConfig() { - super("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1", "sa", null); - } - - public void reset() { - getFlyway().clean(); - getFlyway().migrate(); - } } From a8addf946b7ea49e281e92d96380ee6f008cd7fc Mon Sep 17 00:00:00 2001 From: Christian Basler <chrigu.meyer@gmail.com> Date: Sun, 2 Jul 2017 08:37:43 +0200 Subject: [PATCH 08/10] Minor improvements and version bumps --- build.gradle | 6 +++--- core/build.gradle | 2 +- .../kotlin/ch/dissem/bitmessage/entity/NetworkMessage.kt | 1 - .../main/kotlin/ch/dissem/bitmessage/entity/Plaintext.kt | 8 ++++---- .../ch/dissem/bitmessage/entity/valueobject/Label.kt | 4 ++-- .../ch/dissem/bitmessage/ports/AbstractCryptography.kt | 2 -- cryptography-bc/build.gradle | 4 ++-- cryptography-sc/build.gradle | 4 ++-- demo/build.gradle | 7 ++++--- extensions/build.gradle | 2 +- networking/build.gradle | 2 +- repositories/build.gradle | 8 +++----- .../ch/dissem/bitmessage/repository/JdbcInventory.kt | 2 +- .../dissem/bitmessage/repository/JdbcMessageRepository.kt | 1 - wif/build.gradle | 3 +-- 15 files changed, 25 insertions(+), 31 deletions(-) diff --git a/build.gradle b/build.gradle index 89325d4..176e6a2 100644 --- a/build.gradle +++ b/build.gradle @@ -8,11 +8,10 @@ buildscript { } } plugins { - id 'com.github.ben-manes.versions' version '0.14.0' + id 'com.github.ben-manes.versions' version '0.15.0' } subprojects { - apply plugin: 'java' apply plugin: 'kotlin' apply plugin: 'maven' apply plugin: 'signing' @@ -21,6 +20,7 @@ subprojects { apply plugin: 'com.github.ben-manes.versions' sourceCompatibility = 1.7 + targetCompatibility = 1.7 group = 'ch.dissem.jabit' repositories { @@ -28,7 +28,7 @@ subprojects { maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } } dependencies { - compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version" + compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" } diff --git a/core/build.gradle b/core/build.gradle index f43c541..ef0c51a 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -28,6 +28,6 @@ dependencies { compile 'ch.dissem.msgpack:msgpack:1.0.0' testCompile 'junit:junit:4.12' testCompile 'org.hamcrest:hamcrest-library:1.3' - testCompile 'com.nhaarman:mockito-kotlin:1.4.0' + testCompile 'com.nhaarman:mockito-kotlin:1.5.0' testCompile project(':cryptography-bc') } diff --git a/core/src/main/kotlin/ch/dissem/bitmessage/entity/NetworkMessage.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/NetworkMessage.kt index d0c427a..c1a251d 100644 --- a/core/src/main/kotlin/ch/dissem/bitmessage/entity/NetworkMessage.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/entity/NetworkMessage.kt @@ -40,7 +40,6 @@ data class NetworkMessage( return byteArrayOf(d[0], d[1], d[2], d[3]) } - @Throws(IOException::class) override fun write(out: OutputStream) { // magic Encode.int32(MAGIC, out) diff --git a/core/src/main/kotlin/ch/dissem/bitmessage/entity/Plaintext.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/Plaintext.kt index 3dcf0dc..9a8c90e 100644 --- a/core/src/main/kotlin/ch/dissem/bitmessage/entity/Plaintext.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/entity/Plaintext.kt @@ -36,14 +36,14 @@ import java.util.* import java.util.Collections import kotlin.collections.HashSet -internal fun message(encoding: Plaintext.Encoding, subject: String, body: String): ByteArray = when (encoding) { +private fun message(encoding: Plaintext.Encoding, subject: String, body: String): ByteArray = when (encoding) { SIMPLE -> "Subject:$subject\nBody:$body".toByteArray() EXTENDED -> Message.Builder().subject(subject).body(body).build().zip() TRIVIAL -> (subject + body).toByteArray() IGNORE -> ByteArray(0) } -internal fun ackData(type: Plaintext.Type, ackData: ByteArray?): ByteArray? { +private fun ackData(type: Plaintext.Type, ackData: ByteArray?): ByteArray? { if (ackData != null) { return ackData } else if (type == MSG) { @@ -54,7 +54,7 @@ internal fun ackData(type: Plaintext.Type, ackData: ByteArray?): ByteArray? { } /** - * The unencrypted message to be sent by 'msg' or 'broadcast'. + * A plaintext message before encryption or after decryption. */ class Plaintext private constructor( val type: Type, @@ -464,7 +464,7 @@ class Plaintext private constructor( } fun removeLabel(type: Label.Type) { - labels.removeIf { it.type == type } + labels.removeAll { it.type == type } } fun isUnread(): Boolean { diff --git a/core/src/main/kotlin/ch/dissem/bitmessage/entity/valueobject/Label.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/valueobject/Label.kt index 7b221df..6fd4fd6 100644 --- a/core/src/main/kotlin/ch/dissem/bitmessage/entity/valueobject/Label.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/entity/valueobject/Label.kt @@ -21,11 +21,11 @@ import java.util.* data class Label( private val label: String, - val type: Label.Type?, + val type: Label.Type? = null, /** * RGBA representation for the color. */ - var color: Int + var color: Int = 0 ) : Serializable { var id: Any? = null diff --git a/core/src/main/kotlin/ch/dissem/bitmessage/ports/AbstractCryptography.kt b/core/src/main/kotlin/ch/dissem/bitmessage/ports/AbstractCryptography.kt index b655bd1..f6b779b 100644 --- a/core/src/main/kotlin/ch/dissem/bitmessage/ports/AbstractCryptography.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/ports/AbstractCryptography.kt @@ -111,7 +111,6 @@ abstract class AbstractCryptography protected constructor(@JvmField protected va } } - @Throws(GeneralSecurityException::class) protected fun doSign(data: ByteArray, privKey: java.security.PrivateKey): ByteArray { // TODO: change this to ALGORITHM_EVP_SHA256 once it's generally used in the network val sig = Signature.getInstance(ALGORITHM_ECDSA_SHA1, provider) @@ -121,7 +120,6 @@ abstract class AbstractCryptography protected constructor(@JvmField protected va } - @Throws(GeneralSecurityException::class) protected fun doCheckSignature(data: ByteArray, signature: ByteArray, publicKey: PublicKey): Boolean { for (algorithm in arrayOf(ALGORITHM_ECDSA_SHA1, ALGORITHM_EVP_SHA256)) { val sig = Signature.getInstance(algorithm, provider) diff --git a/cryptography-bc/build.gradle b/cryptography-bc/build.gradle index 9adb0b5..a1b7dff 100644 --- a/cryptography-bc/build.gradle +++ b/cryptography-bc/build.gradle @@ -12,8 +12,8 @@ uploadArchives { dependencies { compile project(':core') - compile 'org.bouncycastle:bcprov-jdk15on:1.56' + compile 'org.bouncycastle:bcprov-jdk15on:1.57' testCompile 'junit:junit:4.12' - testCompile 'org.mockito:mockito-core:2.7.21' + testCompile 'com.nhaarman:mockito-kotlin:1.5.0' testCompile project(path: ':core', configuration: 'testArtifacts') } diff --git a/cryptography-sc/build.gradle b/cryptography-sc/build.gradle index d69b841..b335a7c 100644 --- a/cryptography-sc/build.gradle +++ b/cryptography-sc/build.gradle @@ -12,8 +12,8 @@ uploadArchives { dependencies { compile project(':core') - compile 'com.madgag.spongycastle:prov:1.54.0.0' + compile 'com.madgag.spongycastle:prov:1.56.0.0' testCompile 'junit:junit:4.12' - testCompile 'org.mockito:mockito-core:2.7.21' + testCompile 'com.nhaarman:mockito-kotlin:1.5.0' testCompile project(path: ':core', configuration: 'testArtifacts') } diff --git a/demo/build.gradle b/demo/build.gradle index a00dcf4..3cda8ca 100644 --- a/demo/build.gradle +++ b/demo/build.gradle @@ -15,6 +15,7 @@ uploadArchives { } sourceCompatibility = 1.8 +targetCompatibility = 1.8 test.enabled = Boolean.valueOf(systemTestsEnabled) @@ -30,8 +31,8 @@ dependencies { compile project(':wif') compile 'org.slf4j:slf4j-simple:1.7.25' compile 'args4j:args4j:2.33' - compile 'com.h2database:h2:1.4.194' - compile 'org.apache.commons:commons-lang3:3.5' + compile 'com.h2database:h2:1.4.196' + compile 'org.apache.commons:commons-lang3:3.6' testCompile 'junit:junit:4.12' - testCompile 'com.nhaarman:mockito-kotlin:1.4.0' + testCompile 'com.nhaarman:mockito-kotlin:1.5.0' } diff --git a/extensions/build.gradle b/extensions/build.gradle index 1b3cc8b..188c581 100644 --- a/extensions/build.gradle +++ b/extensions/build.gradle @@ -30,7 +30,7 @@ dependencies { compile project(':core') testCompile 'junit:junit:4.12' testCompile 'org.slf4j:slf4j-simple:1.7.25' - testCompile 'org.mockito:mockito-core:2.7.21' + testCompile 'com.nhaarman:mockito-kotlin:1.5.0' testCompile project(path: ':core', configuration: 'testArtifacts') testCompile project(':cryptography-bc') } diff --git a/networking/build.gradle b/networking/build.gradle index 6ec5920..212ff46 100644 --- a/networking/build.gradle +++ b/networking/build.gradle @@ -14,7 +14,7 @@ dependencies { compile project(':core') testCompile 'junit:junit:4.12' testCompile 'org.slf4j:slf4j-simple:1.7.25' - testCompile 'com.nhaarman:mockito-kotlin:1.4.0' + testCompile 'com.nhaarman:mockito-kotlin:1.5.0' testCompile project(path: ':core', configuration: 'testArtifacts') testCompile project(':cryptography-bc') } diff --git a/repositories/build.gradle b/repositories/build.gradle index 1be3306..dfeb9ad 100644 --- a/repositories/build.gradle +++ b/repositories/build.gradle @@ -10,14 +10,12 @@ uploadArchives { } } -sourceCompatibility = 1.8 - dependencies { compile project(':core') - compile 'org.flywaydb:flyway-core:4.1.2' + compile 'org.flywaydb:flyway-core:4.2.0' testCompile 'junit:junit:4.12' - testCompile 'com.h2database:h2:1.4.194' - testCompile 'com.nhaarman:mockito-kotlin:1.4.0' + testCompile 'com.h2database:h2:1.4.196' + testCompile 'com.nhaarman:mockito-kotlin:1.5.0' testCompile project(path: ':core', configuration: 'testArtifacts') testCompile project(':cryptography-bc') } diff --git a/repositories/src/main/kotlin/ch/dissem/bitmessage/repository/JdbcInventory.kt b/repositories/src/main/kotlin/ch/dissem/bitmessage/repository/JdbcInventory.kt index 637c5dd..07d8efc 100644 --- a/repositories/src/main/kotlin/ch/dissem/bitmessage/repository/JdbcInventory.kt +++ b/repositories/src/main/kotlin/ch/dissem/bitmessage/repository/JdbcInventory.kt @@ -156,7 +156,7 @@ class JdbcInventory(config: JdbcConfig) : JdbcHelper(config), Inventory { } for (c in cache.values) { - c.entries.removeIf { e -> e.value < now - 5 * MINUTE } + c.entries.removeAll { e -> e.value < now - 5 * MINUTE } } } diff --git a/repositories/src/main/kotlin/ch/dissem/bitmessage/repository/JdbcMessageRepository.kt b/repositories/src/main/kotlin/ch/dissem/bitmessage/repository/JdbcMessageRepository.kt index ad5e834..0717e8e 100644 --- a/repositories/src/main/kotlin/ch/dissem/bitmessage/repository/JdbcMessageRepository.kt +++ b/repositories/src/main/kotlin/ch/dissem/bitmessage/repository/JdbcMessageRepository.kt @@ -235,7 +235,6 @@ class JdbcMessageRepository(private val config: JdbcConfig) : AbstractMessageRep } } - @Throws(SQLException::class, IOException::class) private fun update(connection: Connection, message: Plaintext) { connection.prepareStatement( "UPDATE Message SET iv=?, type=?, sender=?, recipient=?, data=?, ack_data=?, sent=?, received=?, " + diff --git a/wif/build.gradle b/wif/build.gradle index cf317e5..0f2bb35 100644 --- a/wif/build.gradle +++ b/wif/build.gradle @@ -14,7 +14,6 @@ dependencies { compile project(':core') compile 'org.ini4j:ini4j:0.5.4' testCompile 'junit:junit:4.12' - testCompile 'org.mockito:mockito-core:2.7.21' - testCompile 'com.nhaarman:mockito-kotlin:1.4.0' + testCompile 'com.nhaarman:mockito-kotlin:1.5.0' testCompile project(':cryptography-bc') } From 35d74868697b2a257ec6b7c13aee428bcc83b2fa Mon Sep 17 00:00:00 2001 From: Christian Basler <chrigu.meyer@gmail.com> Date: Sun, 2 Jul 2017 11:46:27 +0200 Subject: [PATCH 09/10] Added dependency management and bumped gradle version --- build.gradle | 33 +++++++++++++++++++++-- core/build.gradle | 10 +++---- cryptography-bc/build.gradle | 6 ++--- cryptography-sc/build.gradle | 6 ++--- demo/build.gradle | 12 ++++----- extensions/build.gradle | 6 ++--- gradle/wrapper/gradle-wrapper.jar | Bin 53324 -> 54212 bytes gradle/wrapper/gradle-wrapper.properties | 4 +-- gradlew | 22 ++++++++++----- gradlew.bat | 6 ----- networking/build.gradle | 6 ++--- repositories/build.gradle | 8 +++--- wif/build.gradle | 6 ++--- 13 files changed, 78 insertions(+), 47 deletions(-) diff --git a/build.gradle b/build.gradle index 176e6a2..4e228e0 100644 --- a/build.gradle +++ b/build.gradle @@ -9,6 +9,7 @@ buildscript { } plugins { id 'com.github.ben-manes.versions' version '0.15.0' + id "io.spring.dependency-management" version "1.0.3.RELEASE" } subprojects { @@ -17,6 +18,7 @@ subprojects { apply plugin: 'signing' apply plugin: 'jacoco' apply plugin: 'gitflow-version' + apply plugin: 'io.spring.dependency-management' apply plugin: 'com.github.ben-manes.versions' sourceCompatibility = 1.7 @@ -28,8 +30,8 @@ subprojects { maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } } dependencies { - compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" - compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" + compile "org.jetbrains.kotlin:kotlin-stdlib-jre7" + compile "org.jetbrains.kotlin:kotlin-reflect" } test { @@ -107,4 +109,31 @@ subprojects { } check.dependsOn jacocoTestReport + + dependencyManagement { + dependencies { + dependencySet(group: 'org.jetbrains.kotlin', version: "$kotlin_version") { + entry 'kotlin-stdlib-jre7' + entry 'kotlin-reflect' + } + dependencySet(group: 'org.slf4j', version: '1.7.25') { + entry 'slf4j-api' + entry 'slf4j-simple' + } + + dependency 'ch.dissem.msgpack:msgpack:1.0.0' + dependency 'org.bouncycastle:bcprov-jdk15on:1.57' + dependency 'com.madgag.spongycastle:prov:1.56.0.0' + dependency 'org.apache.commons:commons-lang3:3.6' + dependency 'org.flywaydb:flyway-core:4.2.0' + + dependency 'args4j:args4j:2.33' + dependency 'org.ini4j:ini4j:0.5.4' + dependency 'com.h2database:h2:1.4.196' + + dependency 'junit:junit:4.12' + dependency 'org.hamcrest:hamcrest-library:1.3' + dependency 'com.nhaarman:mockito-kotlin:1.5.0' + } + } } diff --git a/core/build.gradle b/core/build.gradle index ef0c51a..eb8118c 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -24,10 +24,10 @@ artifacts { } dependencies { - compile 'org.slf4j:slf4j-api:1.7.25' - compile 'ch.dissem.msgpack:msgpack:1.0.0' - testCompile 'junit:junit:4.12' - testCompile 'org.hamcrest:hamcrest-library:1.3' - testCompile 'com.nhaarman:mockito-kotlin:1.5.0' + compile 'org.slf4j:slf4j-api' + compile 'ch.dissem.msgpack:msgpack' + testCompile 'junit:junit' + testCompile 'org.hamcrest:hamcrest-library' + testCompile 'com.nhaarman:mockito-kotlin' testCompile project(':cryptography-bc') } diff --git a/cryptography-bc/build.gradle b/cryptography-bc/build.gradle index a1b7dff..8fb1175 100644 --- a/cryptography-bc/build.gradle +++ b/cryptography-bc/build.gradle @@ -12,8 +12,8 @@ uploadArchives { dependencies { compile project(':core') - compile 'org.bouncycastle:bcprov-jdk15on:1.57' - testCompile 'junit:junit:4.12' - testCompile 'com.nhaarman:mockito-kotlin:1.5.0' + compile 'org.bouncycastle:bcprov-jdk15on' + testCompile 'junit:junit' + testCompile 'com.nhaarman:mockito-kotlin' testCompile project(path: ':core', configuration: 'testArtifacts') } diff --git a/cryptography-sc/build.gradle b/cryptography-sc/build.gradle index b335a7c..6e8085c 100644 --- a/cryptography-sc/build.gradle +++ b/cryptography-sc/build.gradle @@ -12,8 +12,8 @@ uploadArchives { dependencies { compile project(':core') - compile 'com.madgag.spongycastle:prov:1.56.0.0' - testCompile 'junit:junit:4.12' - testCompile 'com.nhaarman:mockito-kotlin:1.5.0' + compile 'com.madgag.spongycastle:prov' + testCompile 'junit:junit' + testCompile 'com.nhaarman:mockito-kotlin' testCompile project(path: ':core', configuration: 'testArtifacts') } diff --git a/demo/build.gradle b/demo/build.gradle index 3cda8ca..3699414 100644 --- a/demo/build.gradle +++ b/demo/build.gradle @@ -29,10 +29,10 @@ dependencies { compile project(':repositories') compile project(':cryptography-bc') compile project(':wif') - compile 'org.slf4j:slf4j-simple:1.7.25' - compile 'args4j:args4j:2.33' - compile 'com.h2database:h2:1.4.196' - compile 'org.apache.commons:commons-lang3:3.6' - testCompile 'junit:junit:4.12' - testCompile 'com.nhaarman:mockito-kotlin:1.5.0' + compile 'org.slf4j:slf4j-simple' + compile 'args4j:args4j' + compile 'com.h2database:h2' + compile 'org.apache.commons:commons-lang3' + testCompile 'junit:junit' + testCompile 'com.nhaarman:mockito-kotlin' } diff --git a/extensions/build.gradle b/extensions/build.gradle index 188c581..2ec242e 100644 --- a/extensions/build.gradle +++ b/extensions/build.gradle @@ -28,9 +28,9 @@ uploadArchives { dependencies { compile project(':core') - testCompile 'junit:junit:4.12' - testCompile 'org.slf4j:slf4j-simple:1.7.25' - testCompile 'com.nhaarman:mockito-kotlin:1.5.0' + testCompile 'junit:junit' + testCompile 'org.slf4j:slf4j-simple' + testCompile 'com.nhaarman:mockito-kotlin' testCompile project(path: ':core', configuration: 'testArtifacts') testCompile project(':cryptography-bc') } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 3baa851b28c65f87dd36a6748e1a85cf360c1301..c9f10ca3aae0effbc09843516439732a7cad6cff 100644 GIT binary patch delta 21803 zcmY(pV~}Rivb9@gmu=g&ZQHiZx4OD)+qP|2mu=g&zdq;Qh`mq5${)E_<gXQTj%Q@% zWFF{HDJZ<63@8{X5D*j;5NM8xbUZu(%6~Et+u|c3ARr+1ctK^%i}X{hd*?f6V4(k$ zJY@eXsl<l)|4RNhn<4t|?41hZKi}5BLxD%3fPj)vfPg3yndH$E^@q>^$r|B4C@V-m z)$z1mTEG5c)-i<xfeI%ud9)><;;3xCE7IXuFN|o(*!YB3p;e;ovRVmy7oF*?HxYF! zc#k$2&b2}F<;&fC@c+d5XM1&4sT=1%KAgO4ccs5%9!yQ@bl>M<7(mqF{lM?lBjO_* zOfjF|ib5%jL+xk%f>yW#OzQ}H+F&}s?p-nGjPRJU?qXp@I?)GtjB7@?z>&Vu<8Mz5 z`n>M?czF`I>=2AVdh*Zk5F9Rr4tS=VAUxd0Gv*w;fi&ixEJ+N$V$Kkp?4cO>CY&hT z-JT)xql{Rr1<cgHo|s(@VXO{CcD)nV_MncWu6@?l^=XK_V$T==ES_12x(H1Wj??_4 zMmTQeAi8M}V2;g_oY<lqq%65MGx71yuduPM@o+H-u+DK5;NVKULqa6TMBn8rN|&ay zGIGylbxLw_xe1M=TH_hCt+%;Lj-@k#mZ<}3M9%OLgxOf!EMLn)+dCrC`MoWDN3Zz~ z&f@){D@VLl4$k%f{rNfCq6noB^7LuqLM(K8G}4+@nfeWnx)$SG6c0Ew$`X&bYde-Q z=i(F#q)NF=_lfUGL3Dm0TuWSL6>C|`UL&fot~C#Bd>*jr3FX=y@uebii6K(n^43@v zb@ua?X^qJ4!xjBEL8~t66G^9-zrj1AX|-CK6lQEdi_@V2Q@H9BtILwP@k%bEswx)i zYo@(;-KZ)JgTS&&oZ0}znTusmWcC%!BZ3OLQBq0Qs<lKgJ_{D@{yR=XxeL_O=S~+Z zv;IYxigLW=$RtS=G6*atH|Pmt9b0g(sdHSc^CMVFY%&&=_6!&acR0$BTjHaF@^UR; ztEe<C?l=~}qV0Ghgb|-Ycx5?D4Z)$SLyuy^<rOFqtzup&>x2!amKcwk1}zcN2jHHl z2zqBM9HkzmH6@-vnITn;{$%2U`B&tn3hWoPPf<pSc?1qj*)i(d+F{|Js98HnXgd#O zN~7T*gl{{Tz7^CCY@N$8CJ}yyl3T77LA5$6_ev*#&gZ3C)_NA6B9>Y0Onax;xOJj6 zl}Dq*RVPgmlR;Tsc{a6@ltnr22q~DtM%+}=Oo-Lf#W3-hT2}G`vDa0Cj*<$L4wk{d z`;Q*;VPLB`*^u?2UBe(zt9nO9AZw3Y#aBdz7sDN6)vlRt;kT}nXIWC~Bi2+kBec|L zi6uCIms@H?l*576KE@o5_#p&sX<GTrwm75Q5Ki7v1ZIy1rYG{Ie&9xBqkZCB=@z?7 zy&tJdyI<*%+B0nmZ#XTGU^ldg;(}_4zPXI5Ed~b$hVaLKD5HW@xq_{Fca-)Hh9$Fm z^07(ULB4~vaF>EzT&iC|OV&%3q3Dd5Rc8<|llJjI9Rl|WYyO#lCCBfV5V&fW<gA56 zX)iy=!q3DOjfJ#OjazO&g=t{`nCk28cR>4$(ddOF+nKjj`<SfnFGZc)U!3xF@-8-< zH94CiQrIxz11R1~d{PAo(7eJ@h@qPheX8~E+`b@w=kMmerUr?CimQ_PK1SKgNe}?$ z%&#UG(3_(8%Wc-ma$eld%rg}AtM0H<a;9|7wG@0aHfwlK*S2;)MAee{*m}j7@Grzb z(80OXlS5i4rDh8`3$?%f`Zl<(x}F@Uru_Ay78&T4`SB-AmTu5x*p_&iveZ=n{YW)A zF@Zg8sOerEv#YHowg&qgluA#X6j?y&$YWr&JiRw`rpX^3+|00@_GWm6%n___c_bxE z!ZcTN?St!{pdF}p;=elXe_RB>s11y#YCPAcTYur@+5FOm7;Z+ke0hi<5UpXpXFmSe zjI6vhLNq70s4J~*1yGJX{DRimClM!I*z!l%+)p=Pc=XX6t0^G!wSW1FDsuu5T%2A2 zn7+oX1Yz;}{)l!*lyTdVyvK#=i@BzT>WjFxhW3P>4&r<B&GF%TlMrv_S>f&B)0v;I zvoa8E`JGaX0T*;ADbVDZ0neX;oQogCi<yC;BjVB(_C(EQ1>h9Tp>GDEMo0KT)uPP8 zVLAg72ut23+<67%j-G?FMu`Is`(!kEJ1BE{V2Y#2Ry4IX6(mT|=P)Uvz}9E?=8|1E zfdpb&=+6kpz%4E4^~Fi{=*YUvH0pSBx&kG6oxtUwnfF1aDH1g5iT%?#%~)qliZ#X6 z?^t7(%$g<(m$r3GZkofpo1eG&p0~SE(@uGp*D^1zrr?C@wzfAcZ|nfbKA8*!Zi1e@ z!_WNv+a5_sz3$<cvHM!Qp=k#m7%6jY(OqkT0>G^%;O>g99tn%~9pijM?XfjJ4EnKt zY5o3LgTUNBKt#Ah;G3}+r|T-j&cFp4#$cnOq$iG9(}Go;{t22+#*1?Vd~2-t)n;e9 zcvDxXd7=bjbY6c{0_OpH%o)dXLOeQDbjNdCsb!n$qN2T-{yq2*0E8+EJlkPuoQXNS zp>EFK9C6ni$Y^<QMEkLw9Sg<?#C7q1TfA(0fw}P4)JI|tU{|@QQ#!|2^)ku7id=!@ ziW(BgFK~;|)R+M|oE15FF1GjU;Fnlr8aEdTq$l;M-!FPWynO%{$Srw1Ggp!fsk(Gu zEBB=qWw?MoY|4)4B649Q+miNZUn7))fvRTvk{Y!5k{aoka^pZn`;6p#fcH#x#+ncd z4nfO<1;B2K@6w6!l9N(su*junfs5eqp*ggaH}uc-9+fN)(^&kRP|svh^;h4wphQ7% z&HHX~hNpmV{UpGMi;R%ux<R2G8vS=nqOeR5sS7bUF==LLBXhc)q9TYr5a-<)>|dwd zyhQ&!Phl9v<fV7T+ak5qCCm^iZhR6nydsjIroZCS??hTTksWB^8tB#MVCG}}yb7v{ zsE}loY6H(-zf=0$7F)rN6IPI9r>naILOcWS-Q+7ybrb-aeOmW^Dbnvs5yK{j{Rf(I zTB{)x;Xg?fF;_Ko|MSGqb#X_t5XV#pz2=22SK)M{HVdU#qih|2=PlTYgX(s1=-A8Q z<~)lwO)YI5pQilPPXR0dfd4Ngom^_VRQyA!jekM>FC3gL7%ZHPOl=eMz()Uzm>T^s zi6Q@snM_u%Gn^oRfDnHH0kQlqGx0zX19V()EU^6fwdU6^o`~%C)wyaC1<5E{63`{% z<;n_2gu=7h>W$A^SoLHKE~7O%jhm&72~lCJxKdPPHW1VHLi8CYXR#_sJwqJ9S#$?) zVcRa@I3^v1JbU!k?x?O8S-xX#_Plmp-nN%^zF$TKfH@-!&OC98;eC-Y78$Ge0Fm7L zp7MiHF{IMg4_(^bvoRP;pJZ<L3E1=%duD8!*ybBb>Jt$$`jXit^j2v$qg^q0(!P+k zc&YVy_PBUzZCAHR-P$}oo6rPqc)-})HM`az=d~w83)yWUVsHj?w`y<(D5rEe6S?i2 z`jk0coNh+GUAI|+26qi^!{gj30G`xr7~hR-9Hi56AA7#KO~Z9(Y~?(Xy2^PQFcddn zCB)MjuNW0a&@+R2T(>mbW<4HG`Z!4pm~^Y(p4*j*^`(}b_qK)HG+zm97m`OaG&<8w zM;q2V+)<DZ?A4}`IRdHK57iMQjT(NH>WTi(hs_NOWg8lYZC8Kmowb%P04OOoQ&PEt ziRzw&j43hub=_47IN|QmeeMlrGjtrj1|34|4qP$!oC#WJPwi_H4Y-sh_u5m>7+CvO z!p>dyfqmDKv&CEYT}K`B<)QGH!-3LCy3sM=T9LwH=1W%QI}*D{mKxv0BpWHIGpqdZ zUNFkF4(({Q*uw<jSGsp@0RJt#DJ!}rni>w#xLEe2D+8T`;dN&%c>8GYCRJ=MdB1G3 zgyueS3&~_EpXuhn5QVSsIc0m3f>=P6vY0$GZu%7$R?^=g=nW@SgY|hVb!(j^?W|<4 z(aH%6oR(j>C6zzFV{eJ6xxLa!xfP5y>S0!4{NGP0875$P8^V!706a%*pU#!LANcY! zM#Z*<m@9C&14f`CoV12jF<X5rcM*Ms7rDFPAy=wJ4!bz7CBHT9=#Z^esTeR{TevYt z;6Q8l+(37f20iY$*KYep-a;z3FQipLB<}SG%hPHw(##CBf3w=8j5la$nWuS;J;)97 zJRm!zL-b&9z4!cH0C1Art`4@5BR<q(zXJQ0?ZJTx48+2rGUe}4K=?=GLc69EY#Vu} z%A!qV`ks&rC7qZJcUi9iAHa3j@{+N5FPiTL8#<SSwEp_Ub+GXq?dxo-*(>>Nup1Sk z1K}TjQME`ujEecLc@-3E%cmg{Aty1E`HKZTcr$al3UG=o2AI}X)3;Z!V81ArI@+m5 zRP#P~5|2g)SG9oZI+fh*D^akBCK|pw2f4uRjy)M{F<$|-=ec^Xh?y7ZGih|2u>y0R zhdF*DWd{}a;U;^SxH2{5>Y&3O&!GcPtL!tN4Y_i=5vgv*zN|<{Iv&G5W>E4ds=5;^ zQlN3|TnMgi0JK_6Tnp3K`c1zVRQn>k^W)+U-WHQocj*3cwywwjOI>-aT~k^%1JRrx z$rx5QWf)-aF#1+)F9Ks_O33(E?-%#G>dbD3LNna7(>5H*#2u6eOOln!&TIb$;-5}_ zL0iWcNJ}39RetP>VJ>*b%~*I`K@RwR=$y28?3|&}0ssMYJ`Pe%QX%F_?uZ4Y7)Lz} z@+ruWicHuHt~GIe(BY(=rEUwy++3-PWx)e`RHI1O)Ue7Od58UeSw`oMSZRijy|E#5 zHw)cXkDY3P>rTTp$%`mGk+W1tgWoMe*j8PP74b^^`76!HqE_$oF&(bXKumgm&-aEW zYpNt!2EdaxRhBGMGC1(%CtiBHe_AyVRIX{O?T_Bg)=widQ`7pB{ktlC#_B?A>V=4n zA#P8k9|#xUFenSFq-wq#i0>|8@elt>d%NsOcUHahY}@GP4=h0h{&AgztqKb@F*)<z zcU(rgm0InJCQTg7hZLw-P^ngGswPsTSgm5g2;h84t5HKMn?p0V)$v#Bt=1<t*CzzY zE1mdU(K7PL=^ZKm5Y;CR_-E+VM=$4}56TG4X=u1aQ77rgLPGbwrqzjK{{RoHZM=Td zzwsDjY1r5EgDSJ9%~AqfIq{GaU3=C+_+oAExG{+wA65?!yfz}*kH}+X?|P8{<QeFO zGC*K2Nvy@I&lBlVG}@Gtb&mDi2g<AP-yM*jw>c}Nukc%V%ice~uT~AdwqQmR_uhgL z8<EDEZqLq97Uu!KU1)u&0YWRgdbp^b2zUD*yfG?uk@h4mUy?x#H1cTKV>iac9e2)_ zJ-N(VSu^{(7xl9HTFH=E2z#<$1pgyz|D{)jU}_-$C08%rRecB`KtO_EKtQbjD_4rZ z(1~&&n1E!JD|=)iR6fw8VKXZywYp_#JDcDN+E+BVksw1^*)S_*JL7j77!=LMEuulf zBQ059h%XZyIbR@xaZkuHWa+Z&*mjR=u9s~W3v<Bt`!lE@R;(RqeB&OWg%REFO!npa z_&#%xiIHwI6WCTe(fHOnyFGZ3&vb(KcP<fD1Hh_D*pjIVBkj_cV%IeuKGahP0T`;& zMMM7|z8c>~0!WjND-Y-mrF%=otFsoU=AB(_YKRjxOc*s-&sLf+l?s`qd+i7g(7h+L z8`q$F{lEU0lG~fSI(Y+io5<dw^B>A=6pRKTzi{4!zxaOox)m)Q*vpOg=s_`;A6$G= zX#nQ8f0e?CJmBN|x0Csn5E=Yf{LfLTv8mvdHH)xM{u*oA+H6s|PyTi26xM1x$%BfW zzGHCAu!&0uZus-zX~@mnbQ_lc3J4vGdn>et(ah#y67f#MysbOBg747AWhR~Krj(N9 zGh7{Cp`g@$GpbbP#4+|{{_`4Jes+c#J_~4AKCY-hr9i6pHR>IS%bqjtHLvk7n@-a# z>>c_2RuGn^QzX$S$`ec5O-O4?Kyn|P1vTF^651czCRxcs+&7HYz+@VSN1;JwU3cjr zy3<~dLfhuF!Y#B<LytT7&8v33AliOehWndJ7bn$6nw@!hz}#jm3d)3TM5q7BXr1Uh z4gi`u)^SLt)JL}3CXIeJ<nVt9sSyTg84UG5-J<MD%-6+#<in1T7!QV>2saE5$kzDq zLDj(WS9oTgFb_b~cGR()6JMo;gUwfj{tW|dCz2|NBMfHZ*1%&6LC$h<Nt;^n6w=ya zU8!Dd-K`qwQJz4CYIjk+{vP=g(EG0OT6V+Hn7L(}0(I>5oW-@{oBHCr!?VNX^|4di z17V2s-SSK4w(S=|(w+!2CbR4qAWnfeG7y3#98BJtl005i2z}w+l+023c!9XyEG@o2 zzF*E5xtNsy7sKxcN#A%?WKI%ti$u=Q2V$=R6oP&Uu|1Op-v$(dQ3~!mDPQRj1qw%@ zp<Jo`##deB+*HHFS78tZvfCzJev(0q;cpo!;qQhJ3pU=;JsKE2i(j3i0QZLwzX57K z%e9h$h)t@4sQBj1swxi}*9iH3>W{&c(z=eG`>OH?hxX|<iy0iQl3YB1ZIF1SDBTS= zMYZuTi>N|A$BFl_?HmhCOFG{LR&m0a8O4BCnO5?1`7)!)g7{BNwn;Kp@HJza9p~^R zCR!FT-2Qiy+VxsRU5#E60IBp6r-jb@Pm0N#4;p<@s<LickbC93zJs?)q|yt%N*H`Q zxVfL%wDaWL_+-)<9$QHf{1zA01k+%nsUC{BSt^X;tORZ-l5L?bJ5{?)_|&8AK~JIU zh_G9+kIqHrqlNUtQY>oGUp)2L0lR$SMh6QC-^5NwzJFb(u7wf|Km~(`Y@;H+`wVxB z4Jzs)G;@({7Hg;uR>6lol|>GdT|<~>XGYdZ84VQQ$`jf$?dHpyvfmAkwk!mOy=ko> zk0ZT93aPo)phkP}yZxUkf*fwMW3u@Mrpi-H|KY+Fzeu!<{kZ<Oh?De~Ts-4%Fv-q3 z6Z_$bOe5(>?d8@m05+^o-`IwI`VqhB`vSer@QN#MZ0!YRpMCaMvB9W2*7YDf9*<LR z%!^@ajF9i2o(z$*S8{w+%?;_oFSH5zw49hoPodG)TRU~9NFVJ{4^zG{3^!klorOC; zEPuj=)LRiaf%!XTda9w8q+c^DcP3t6D4$767f(eGaQk-_0Brx71B}<=7=yir5DnK) z?A|#i@J8;6whANpQMRs%y^ffjh}@mui~4-F>m>~GlGNnOPtyxqHlBKeINbiR0+z3Z zF#>xtle=vXL2A_D^x<{=9_6p(-vtM?o-(5w`}YC1{#(kl6qVXG<#qmJ#>l3Fra8PY z9-Ex-{l|O@fLqWAnoH{q@pbk9F#~;4Iy^(KxyM*c5E|0Bk*6{yla~3`x$~jc!P;tu zt&&n&<fT`k!JwNE;$WZoD{pNZm-D9a!KZm}$2YIhYUeN)GN<cI>Q47zj#$iZRuq%N z2lI42Pnr+M>y!_hOgmO*Hwzq#=$=BU8LSst_JPL%!1LyYH}f#;fOO+;_sl#ygAwla z77C4NI`ltH^pqmip|olti*VUYqb(EXo7**Yqu6mD;P<=LBKYMNwl8q}#p2>9!Ed9T zI?5e&tbsH_&k6MsdibAoL&)QcMh-tabvjWPxyUr=(wG|+*>b$x^W9f7RoBDH&gScs zu^A_)0OE5n6})3Vkj5KzL+98``v%yc9l3|QRaZ(5%?gfMoLP6MCJ6@+Z59{%W0_jW zRVrZjpB;@tbB&iBXY69EkZ!j{UK(QCT|c&ojcN6v&W^)6FiwZsDPHD3>80k354*q2 zbE>*JBwlfb@U?G_7LZ&854xBdz0j#$ZfLog0e+2Prpj|#qAcaBw8rVGzUX(8YL>N% zji3mQQ49D{Kl(A=w_QjdjKA9d+({OocYE*#r+I2H7)b1U9>Z`~Xu9K#9cav9)jU$C zc+~DH4v$xMeJNs>8!w4f?dXcVh*jkahBNxsd&9Tt!P9jb1fX{ZS(+lO%yDh<cU%+8 z0~oL@rhK!0TT*KeuS4JQhk7ikcB-hJR_?=7t2tS`I%l^jh}9bTft6dHUe$ziS8;9f zA$A?T@gm9$*X(6SV8rm<Khg(t#9@ZDg0+VVDLY-0iK@URMa<+|?9T)AYrSIB?yjjT z2RuLTj!MuutTI9^hm<@V+M=4?tGNhg0kU{39&PbnxMAQ<CN8U!x;s4a5qj{eL`#0S zHeAmna`IQixz48Zq+Y6op(SGlCx`gr@OnncJ4M4g^#nhCh~L3R&G?Xu<oeRgo|<rb zc@?`zZor|75148U`*q&Xu@F`1L(K19KnP6oNhz6kjJf--6k4o-e@x-MR1Q?N0es;S zuj)q1c%&I3hZ;gxqm=3RMu)AAX|gfQBh=nErB^0w`XM(Xn0u6DO41{p&++-A!XXd_ z@pT+2<pnqQ$d55gd6S)coJgm=2%~@Az_tmxGKV13U~U0w!!xX}9}NFj5pTS;Q*Zu( z)Dkoh5Z%9?K_aU>N+KBx9>8Nk9q}7=B85a0-2xa=@)yz2w6Th`m6Rn+8#Gv!pa~RP zKI(9fJ6J=~I5X)MM#l@n?RsiY3Pxs%iue0EU`{}oN23AR!7(GtC3USgormP->+{6| zNWWi!S=du&#As4RGtN<Hgpht*bBGKpHGY30;TJ)QnQp&YPZ!D%44|~=PaFAOM-Ua( zmS(b}$Vlg3$+mXLFU>?pny{C)_6=XJTn3LoMYAdr8X^p^gVC-i5lYEbL`2R)tPDPi zK8)yU7{o;L80b@ZD+nEWjFogZIlYx|Wa)G*H3K!e=p7r;x$RkL5M!zZYEqb#j0LUz z-^KR!%rs8htXBzgsQ@xHmiZRzwaE;$U52~DfWo=vMZKq<j9In9ILVC^b5wJ^2y=3D z*6b<nyE66PypPRD=!Bj03>jh(#g$50f~6DPMcSx-%dEC7W)yDCs?zD!F-T08)|iv> zx&~59^=;1a1@4j{-WR?+q}Le_scGFt$6&vfpfzc08>_M%y#S<Kt7&alatvNyQ|2?W zlTH&4I$AAOHYG|4c}i6G8SygZW+a{cw2+gIO;id~e}}t{RVA9M3QY~GgKJs-U=9=n zz(>P7uhagk+O-VcK%<|gqvQ-!0%sva`OuH!2A1ZN=-1m!%%h<kh%=;K9fE|R?ZE{G zZbD0PGUx}j1t2kci#xSiJ9UR}`uDd#XrkI4h(zR+<`}Y>DfCOcY6{NKzm31^RRjeo zp!!D4l*Q;{Cv<2TcR|7MkCtIL(+Mm>ceG3oehBxczDoCt_{s%rwUu<Yl^me7QqrUZ zIiUJR-khRcdh1i+w5Xf%B%?CsvQBu@8>hL8mcAG^0>ZlciRoLyELfb+Hl;;bc%9`7 znJq8O?1`B9)v;q~UhxHPXZ=wbzXg0^GAuaiI>J6%y!?Xo1r=D;cOWAhqseruq)N0& zRY<vJRl#nM9x>9gx9QiaR#%;Jg3aMd_0s*+n$v^^iH_kQXBJ?aUW3YLUYp?}%NJvP z8qFec0Qc8laQ1rC2t7lWrkU-GsD9y{^4ZhP{fy{#+0t}CHnUw?PA$>X+L7{?W+-Mq zk)EtmYzIAFvD>$2-W>KMtZn7-#?_=19w{%O0dpl>3lLR_h~6bxGZbd5%gnZ<AiY)h z&|8`z*;LyLvLx9B)+!=#!@u3BdZYv&O2mZa0D>Ebjc~JgGj`Fn^A>N6ez|YC2q3v$ zP58w+f$qQEe?-v+Ot@VQqm~0iHe*eisB3^mkczYMnaL$1Xap#@!k<5fRMGGz6!s>< z0D_aRB{a7hh>`-oRfXyzrm^mz59>7hbLudasaJUp5I#ioNVl@@p7JU5gl=RBaS_9i z0pqXaXWJemwBNzs61q1Wzo0xX!Db41LgckW|9$|2I3p%L9tngS>GnaXBlX*XOoR;< z_MVDsM&{&_GaV6*n_xQ$BlqD+i)7ix5{Dl=>WGutP@Mj#2~XMO4B3#nRiwbCzHr}d z6GliTEtm%P5H=^NkC_BKPbe^@rT@CB1Q-Lr_lnCcTzXXY13XVHqGaL1C_EvkF=6n^ zc6Uj@2EGjlA_f?Kz8_FSne&Yp5EK}d#SUk(2Yw~!<ODzgXOK&dp7}-)I+eK`vIpX4 z5i$C^BE85JxaFm??hI&wtSTf6NKnLwwk9S_$P>TvaNfL1eMA1|ZUp<EL31J<@cMuG z(8hDL@c%ZVTkktmm~cQq+gLzAME}1Lg#yr<*c!RGWNSkEqOSOT=QMS)U_%h0Bhs$c zt&Tw9(HyMe?$^b~8P?M`Nt*VrMyZon-C0}kvB3VdpYNzyz_r^Vx5DYPn`gJeJF&D| z(T_(QwZDk_=&5Cw+dsB+dE}obf7{+z!k-U0)-c@ZIskOv044#iv#~p0`anFxNr2y+ zIo1POpTW2}DT6E!p@A{rpsBa?a5wCE2MxbpcC|onhFzRJ_2)l}algj}DZaG_VX?O@ z4B2WhE&D&<{(0e~QG$fQP&eP9K8ZUfF5k^Qid!kr+=0@g+jMg4x2l*KtXJNiyuC>7 z9#g^y2FU#HUlMM<!+jce91!2=27rb5gvj1R;U0RAo|-+J`5=e!Ju$A&ZrmQSP=|>< z9_QB@)&m09&%)nCz6zr+Zl8jK2ymCfD%{^v{6AU=pOIN#m0(9Zzi>x-FJ2Kqf5Ly} z?{={LNn5`qMt{y<>q9gwU&DzAgXpm-)mW{G^TPA!g*#?h^Dhd<`kahTjRC>*vg2qp zR^gV*!_<0Br^e0n109-$k<A^N6yQzP#@*!Xmc+zBz$`ga>-GjC_q-b@P_Z4;!*3#9 zfaU4;v!o-Q8o`It)xYue-=h21J%tB@9<v#3edGKT7jNpzxpmB5ol3%#bp;;2hIxR? zYtc_tf!3B*s37L)$tXFhOaKI)M~Tl!a|yHi3;4?Tk!`DwBHlI1Y#V;dv26yU=QOv2 zn?=>7tjB=I!WtGn4sTAK9>bL>l{0`l^BDo=u|-ep*qLd<!(A2%>^+OBLXRzNdVTP% zU222RNX|ToNPc`=R?}X)MkW&&z%G|nQ(gL6=R%a}R7JJ!BR4)K4=A;`ujxirbo(>H zUapl(%>-p@MT4E%{B?a-?>V;Ew7?fPy*e&Cm673W7u%6(BNhIhboF*-E9LG&$54OK zo7UiRA2&%C$$g-;<KCzqGIF>ziCq52)MnbJ#m91l-Ycz6;kdN4m;y)sWj@E&-+nTs zrFOi1I*PS=W=tkO3J`0Obbjdfd%1y$#7iLcCA`yaO36C_Xl@mbO&d#Di-(MkOiU!> z<YN5u&X%q7SZm?AEfamU3#=VaW^V6kK4Vo<C(N++s8~g&yS|YAb;2&eo>BRT4Udv2 z!lD%Kc&_MZt<(N&rbLx>YxUn${~X+XBI*hweUoS-*DdU11VGKbk~6KS<8c@DSW+E^ zVb#1pUoDpzKjvbZz@OyP8q@Zbk|`xq;ijf~RHsS0sV9b+z(h=OQOT*Aj;RWHs5Nyd zdBl?95Swb$R@1{_eN$yPQDM5`lH}R%<dy6g&57m-VzY5B&3L#Sl%1V$WF_XLL=9g_ zBas}q=Q+GS4><H($_(9Hqzxv^>dI^|YpJWEBX^P=XL4dVmNR9vAch?km50Y*QW!G# z6Tp}lyG(huHcq6f=_diHuT%}eNlDeHKwPmlZlr1nD<f~o*yncYrm{!ekmU{dkozR} zpFrh^xT2+wY@e+}p3AA2raD%3+twhELbG#T%@2210l0?AqtuQ%WO~xzpF9=6>A^V2 zxuz47cgQ&<u9k;q%H1J5MUvaI4b#_ep!yWa)uQ`Adufw*WX`-jCy{&0)+=0MXsS#v z#XH+Vi=s0SbpP{T)FWxE;kLvj;h-g)l=cflS0$XJ-g0vk&zbvmLu*wr6e^DgPAb3t z?uBt$10ve8@Dc~{a48*ThjeZ!K!nclXHZ3qHFxt9Lq|dm1|<p(@DC`wABV!{$|}I- z)|C<6rZ?;t$LFPI<m){pXx8wbvQLe#q7taQMcX;Klb<n@6TPHRB4f;Lmx0HQi_^*G ze6@^-M|Km=aXI`9%5SR-u-CiaT*b}w$}Dq40cJx(IJaR`;zx9`kxVM2L(u5(V>pHD zvQ-+%_;OL1#===e5;SbVOI}j%8T-Y=S}-Zr&p6|_Q^Og~161p(%4&LAtWM*orNh}l zUY|pPEBGo|NFGbGbPYA-D)wLn&M(H7`?}=p>C^JV@H#z5Gbgso+T4QO!s;iKeGD1P zfC-Px3B^@SB=!*wIq&J31TS-@Nzz?6-vcp*qOY~eSN<!p#u$R9`}QmmoRWggObfFx z{7g*cd~XgLRi;{#;;xkR!58VPW6Saashw=LU@{lk<g(Q_<$h+FN@XH#HQi}7ch=EJ zw2Q)it*Mq`>E#kT{TFtI^_HoP((kJ>Kt_hKa*?ESGV_qK@(HUxSI@IGn=M7K28Ee1 z|Ah`lrLGbl<U@`_BYU->WW%#kj6m%+vW8mTc2l((p3J(^VB^=|b5runEqHzTd9aE| zscG60S^OJCb)D`!>Iz+?L#lKmYDT98^_6#p7|-zQGu|_hT2k`2rQ60mIo-TEpm(Qy zRe9ec1u4qbK#ir|DLf*>ysY^fnk2z6YVGBC4OsnlOTYA^Z0D05CB9-hf~*`SVLio0 zF5B+espcH`k6o#c$B>OKz_`V;DIr)@xTmeguNcFF%HM1~Qezu#DSM}^3T$GkY`w%^ zjsCA%?M*_=Tlx;PYrC#_`F)8s;J8O?LQc}Kk*c#KnyaYnXa%TROJ`<_^&~L!8Ospc zD15XSIQsL~zCZPWX9EJ!W%yzhor><kXW7Bq@EWB}EKHB5mEGH<M|R_Zmkzp6x)f*) z8%ybX;pWWI*%8m(4?$*^R9K!SmIL&1af-^Z?8?m2M~c2%1K#6=1Gbj};3=gb*JeP} z?wV%AMh%0S#U4wmT}V&kCE#>|T1C0>>W9%?ss3@*c)`yHFK1!Gv!a;R=78g~ie*1@ zI>+PM$ndiI&&?Tk+=|U7SzPx+&u5WCwTH+X_=^A2s*yQ2hH9Ejt-->w2OGh7S(Pt% zPzj#L^nI#sKD@G9jbgz7Ks`$g7X&?UHOy+#ksme>*s5kmt@=ht)BQ9n)GV<khwI6f zc&gix%Uk!Y2=2B$-w(K;`v8lz$%FsT$a9Sa;;#Cw6xq~C8xm1Wrc`$@bAe?En~fvh z@`3lE*SvnxpNF=*?-$EI^9+l!+Ov7)^C9BXZ|#Z=h5<HG&Fr`x0I(~V3<eQ}2yj-t zK&82DS?XQ!(^dIuo+R<czZ&zDjs({f%}HGH)>6F{jR9!jX%rhg+!%Gv<!$m27yG;= zLt>G%N3c}`47+C1!_-~4y^_-g`@DVGM$-hEs^tEn7FCC1k(U)dfq1F2x_uQnvt~`C z3?^;~5reRrEV$5UfVvin*=;0fT0^{gyQ?|k-?#5!>U|-Sc>$6Nj!(}iFQ<Io3-%<) zQK#Uxve(wuo+#q<Mk>H7yn${Cd+WEyAT7G$tXSI}0T($oY|CtXrdRCR(amlCHD^x_ zMVF;>h}-IY(G*K1-5aM7r_mP~Fst9I3iukOcHoy`V6}p=05Dj~n~nhkV7%D9cl^fh zcJdjLV;6<{d7)1#{oP=LKC~xV;dkc|a7Px6);MDfqpc~+wLf;NGI>6p4Y7ciLJUaE z!B!*0)&o>{qTy_xSqY3slxyHFWQkjC+~Gn_aXyh(mw_)<@mqS_(MHU|5hC9#pWMA| zjP@6`YrjN%Kq~#D5%^ONaBmbORyD^$je${@)uIOk!XBFI3z+y_<zyoHxqz5nZ6J}J zhWmS@S!u7Pvo~nZZ;b=CUom!`_`M+p7EoY!Y@ZPL$BoFy9iNnR!#jA?69Lt!bLqAW zc`}Y+cPmpna-Ua+H^<;zhvCe=M8l36YK9JEzZec&07}Cbl4A^u2@xF1?XqouuJB;n z%-Sx#pm?!*n*VgbtUggj*l)^lxQD#xj%*)-cn^HQtnO?#2Y~vbJH|O_jD<}FL8ler zQ3*4P5tgQ}vrI;?;<?vQ4<n@Xb8A9e5c-GqvBvoz_-hou7Az@U->X!oUG4)l|7E^s zyaDI{16mQ>LJ-hqajtEA(L-TR39f%3{C$cCS{WN*<ILE#SICozBh>G!&oLxLguZ9Z zvAu-aQR*Xvj^T^Vr})cFtR<0JCk|48@1?NI978c^!i<A5Gbfo{&nhlJZut&MJ?!UZ zhDCtk<xNnz@^yNg?BjXHBpzDCE3po#+QnB!2<S-6*%6G8xbp$a&uqSX;evIv0R0x^ zy23T0yXKRW=!FLS)e7{wlgJtad340u4n<4ybzn07!E{is|9kVQyydG_?tJqr*Ki9L zCe{V_x+{=wa#@Znw(;}I;f%_#pz`#N0+WD(hG``t4;j@sAoJH<^RF9u2>0O?o{5o5 zRlwZe6-@55l!mNK*|1URqt9y3F4N^yDi!hh^jjILSwqh9C?2wC*xch)6sR34g8o~q zH>ltn>$zkf%)01s(|}k(VkriSgnUWH_(kG;QRN#+QXS!J+@_i#vdyodggQUEDQ~<( zz-~}FArgd1(yE=s?pP%*9kyzRG!(@hyG(GURBW%F9@%7+UucIkSm>SnG)R&o5MV?r z&e^>lhU?H)Q0XC27V%Bo&Tt?LT-){il-3qg0Q^7R>wl~lgdIT!USbUa9RS)_^{82Z zf+d|NU6&9{7;_mZ+!TQa8xc+_ZWS@E4ioW?+$B*roou^Z1o=XpKwbMve9LYa&)Nzt z87w-&ZmYRPy|TKwxn=pe`MJBN_~u`9{j__SGfUBfkoWq&Y&&pW{AAtu8tA`$B+ekt zfqXLxeqAPt$B!_`<*@z+@Up(mm)&#G3!3rj??L15x8}ep2Edxy7O(~b=L{tlQ^DM# z(!<YI^QvX!4J?4Lrk759V!_NjK2!nC9LEimf?HzGK;NG6YIsW*af$4C0cT7dfa;E$ z0aqM2zg5MRC}&jo)*NF8pL}@z9)L9)DFB)}7@qGPnvpqbmmT5*sEka*C^D_Qd$sdw z9Nw*UG54L^wgY!9QFT!ZPV~W=Jk7I8E>NPX!v;Tmc8Pmupz<5+&Ahg+&Dazdk_n#N zb6YJ#zJhG$Iobp)-+Br-B%P33Re*G*w^I7%57;qV{d;}a0@Qpf2IGLBSb1x=7x~pC z!cQMxB5gz`XtxQdag+#Xaq7$+^k4qkq`G%Ma^k=#SSt44rkV+ZLQ&&H&y&|gmgCL! zd1bbe4MqW8AB&#XWM(SAr4RN2T_kZi*rb4c7n_>^{nY9a=C6%wr5UEsp!7vUVJera zPqSZ^Xc84xU<Aj()>yH#-fk<*FLyf#DN)O0XRo$dtM38WA7GaZ9LqH8Y0HU5x-B(n zmmpnePqWtW<I8AjPp(&qCCnaKlN*q3FBNCXE@U2<YsjUR2a|Zq)s{a!o|?-w@`WXT zJ9crPhuavHXxXD=Vk;jE6iYOATo4*J@ewDTw9#qi@<B$HocZC!n+~iN*Rm6P>q&&> zNLDQwXV3w3qH%&p4(Nfnc3?=%HEGi6>A%3b`Lz;JBK2C*$8-n~E@V+MqSLk$qFOGF z#o_+!|Mm<;U#m(F@h#=<R&NEKnQ}a$?Fknp^vfGuWRr1VL`GlGs=>B(%5k(Tol9nA zn+O|CN#&B~*k?0FaT1ew=2TgeN(>Y?s^f@$F7gF9Fq6n0;N3HARjf7TL#sqIls38B zz^2mL$%&_vi;F&mR0##w3VA%7+LsJdG~I79RrYBiO~ZwxBUx)&ESdK#oyI$?B6H69 zm}&{{&Gge@UP_7^ZqxogNs1{_K1S!uvG0xzhx=sfPt0b2%c2xEE@9!z_aK!UJg?P) zr3C?m!HK8l)R~%<a&}V=7E&!IHVJC16kwXA^{?byUZR$r*y;pZ+T4V`u*sN=gQ`e? zl$4tkX`9u8L}Tf7sA0f5EXx%(3T&w3>^m7+ep7ypRB%9W53lvu&7$Cy%pJ5%;;_q> zsq9wuc@~wd)jee~Bj11VS*?bOPw6owY)AvNIw#`!(EOgZyho>lUd^S>G_4fM;6PNu zx%C6!n&D~6#cUN$$o8cgMOyE&YG0mC@lx<xBtDTKl@;j@;X`fJx-+b%#o}yn>^K}m zV~q$$1rt}SU58m|tj<mJ7&A&DrAo+ME9(*R8ipb++*qthkwHv@<}Ix=gUyKlAUy(j zR%ZusT<I<khl0>zD5`%U+AUWen5#C@`UG+W;zDrqUHu)^64!GWA?vJ>#zqQ7erC;J zbq|a>C2{PbNAaCtA*|D29qyZ#eeQc*Qvs1D)<lU7oaQ_!<j0S;W)e-0Kyz}j8>tjG zjzk*l0juUKHq*&B<ee)R8}F*apCbbRGU!tVM9JctUSR)5G5{nvP+8mredgI$`Ve6A zH%%{{K_h0|ql;}N4$qk;E@xf`T3Aw+2`Sjd!$_$(i4#Fd9p^=t&7NRwGK)E?Cgk&= z^hDgh_i4wY>rGW>Ozn>DUH575C-}hUpz-c<zN>JDRQ1j?GB=Bz>mY2rY*_(b#K!Fb z<~pv|pKlN88G5Z%kKgX&<&>okJ*==rTAY*Czw4ZL@D{3cUpi%#63Lqsp{hCEgxACr zGL{Qa7PyV<r53bln=0gkmzVd3WlZnDDe7NUAFSnf3XJGwf+y}(+&$czilpdx%<RmZ zDzm5YhAPXwmYC&=Pfop(w@U#Am8^n)sK288Yvxt{t$44@3zWIS`N<cDo*^Ya-!>ci zN~4v~f<(_OJW*ejT%c}O-@AQ!CD)$t!2pE#d&=(Nd<(}7_HpX@2n@>GVS`pv)xKc> zXw;@QVV3rKDjLDWVtcjaU8%h>Wz#uvXqAM*Gc>-5;~Wok)H~y?<-P!(f5XmweHKdJ z((#>LhYDYe8<jWv9*GPewH^i5HiIL`Q@cx$!AK5LlN6*SmbHwtga_MT;k964OMNM0 zeM;OFlzh!inEcNsRuc4i<F7Px<Hl$ikV<&A68G&mGz2%|$k0cA+xhtK*}%!JZ|g<O z!OxXug9!U%g2ON4I#2*LAo7LDPM`*nY8$#PEVLk39weDy-=VT8_FDao5Fl}MGeEs* zm#QTmn(ZQTHyxMxD;paZmA{%)9oYs{T<=7*`CGBIMFyey!kgCAY7T#`PNX|zNly>C z5!RUnyP<;(HX1+UMB9D__9@MYw&`7@&b9Z}IZi!USli7S*BTI7WC@1_jX8>nx0hB& z3vER%Dr3fCIAorxjepL1Fc*j3Flj1$d4#ICRG>-OIsYraqyjFPRNDBg2{XZ2F(m6c zYM4j4S+DwvG7fra4SYQfIGF&QmA~Mg$X|7DRMlS>7mx;7r-o2E#l5ijPUca;E9p?_ z8<eZ~Cgp__I0HB>4LlU=M3NE9>pP*koF1id6z`+g^Eq*Ml{laj=AB5PReH19CQ;2% zdZQGmynRvq?g^;(w5=+p4AFLjg_2ou&d=F1reDP3un|G_6Ht8RO3pn2JuAag8Qz<` zX;9!Xq3-Qth)`vadbo#zHLKuFHrV5%q#PnZmZ>c%jRsULtNP28OuZilMwfT-=R^fv zDJo<M3fV4QkaVOVW(6Oj(lSAplI%yx<NWE?o=mWmHICV3Kx4|xh=WS<EZ`1Vmp>x7 zzkvCXJR*3&q5cWl8Pcp(fvEUI|4}_^eZyPKt7(pv?mfw+`gBR&L38~K+>z1omW?jC zXV^3OP6ilSj96M!;V5;<P05d~iR_2I4AZR!ev1;&eEuHq-Lp~qrrQbG^#0@nZg%L9 z5XJ}J8Bh@*E9JB4WSFohOD-0tv$IY(=}PQE6kG};IhK3}FE%T504s^fNOBv=(LMLB zfX|j>DZwjs2E8w@L4lD7x68$4K8MpN0G^!Kqy=1lFPhCrR%SOjUq7WO$Yh(H>mrb| zH`HUelg8hfsLSyMB|_mjjqcgjC_27V=$h{K{d%(dGRB(){GRCVx&-4OO4Oalm`B1l z>OX8zGn1R8Ttiu=r2@?E^(|t^hwswSCE_H6ldvTE$)q_9ulzDdK}}|F-=7LSkWO8) z8wLEnMdq0}bN06ST766ZNFG19IA`B-7+!FWBGB3vp(2^XysCHPe{@^7TH_yz%I~qO z^3i>wa!!=JCPNuHV$6r6$1#bD>hyazlk5*yQk}m%l)TC+%08{NQ5YhTl1cT%ZkKv4 zWDX$UTP1Fo`MDsPy47+|kl~n|BQZc~*8t3a{Ms0d8ND+r?cr@tMH1Xt!8bFZ*S(Jx z73nbi1Eq~Q&HFX<qGzZClzrZ;$s3|fzfZ|lym$oBH=QcjZKcD!5jTiIfHLYjK2n5* z-lG4Z>7slqWl#qe>tmbz;I3<E7HX{i3(dl;GX5n}Weee@V5w*?e~~Tz@tq7e^9s<J zhgKRi$;2U@z`%mRp1`+UDy>$+B0o+#vB2p9BB|WEsc2_C^%+WHW7wVznsLxAnMKJO z;$=GHbd;YLIQy~ehEI`|aoJfPbl|^2Q($P-0xhmThT0BKSk%!xwBA<EA^u3o%mS21 zu<%i}cybEIg3<^xd8+I3R=|^8xeC~=!x`ONTr}M<9HLviyJcRBPEqZPZ^LA3#YopH zT`0>P52H*CbOqOwlGWIw39Ts+=F&OnR)k-FMv*Z62!CMCNo4du@B_ZWxO0P}^t{W` z;g|F}5<#`3naq`BfJ^XB8%fr{B`@x}XM7v4gkN|SVGqTbl_w!s=hDO~7z4O+o2JP$ zmVr4~%tm8Ud05V;o=%89XZ{^}&Xi0x=?&M#fAkOTiNKFPh60!DcQG3VlGBVV$t(VJ z!j4T1Z{E)H2!OG1Ibteb4*hl#+Ch}Fr$KlRY)?j0`=iP#sFG8>FCjjr1%Wpq)Q;Ss zKId8ya`<4!wZgf{>|9e+I|tC0Op>vfTR9_Fgq}6-z<Pc%wBy<mZWU=fn|*-qKxsUl z{q+v9?fWQK^=y2kr}^ihq7Z>2pCKUr0;Uu{e|McIV_BU^azTezrW*zJlnP*HAJ1Vy zFjWR~3_lxHyiI(gH~OuNHTi@pG?xAm>!-;j*l3-9blD;VvqYU!{0$%m%)U%^vjT|m zVBaG?BWt*YOP7wpA0RAaUhKyasKFl~Pao-o#5#`c*RD>!M)eV>K~<S;Q<hkfu7VLe zqseF01x;{tJ&~qLu2^H~L-O5c(-+}gg3fs7tw7!8$ri;R+!oASsq#ueEtNKcvK4QU zt9l^C#|s~<ptodR;sFLp(`irK9R%XSLxxMBkAy{Of}NHEcmZ0lR6;ugFbtNWr$q1U zlO4#q2>%Z4iS#?XABO!n`|aSPBn+d1k!aqsGJNN8iz=Ux1*0#kTi9_kT8ejWNdvor z*##0v2ThD|6LPt!fpwepW8DdK#dJPu0IwDh^Bg&0Blzq!2*B679G@K%!dpgs0UZa^ zwiho@%(>1Pj!!!F_x8c5njMYgjM1a%#Xm@(%FYP^53(^?f>kxp=aOV=-li7%`sA@h z0;)HZ*%KZ{?=EPbii{Dfwrf|bw(H5$P%WSGOG1auN%4%Go!?8#HsLvvX6(wiM~Y2E ziWd?+NJsqE1%&MYUG7+p1|axR8{bJoCB#a44(NmbX0~RZPjsJ-=jaE-e6gF3O0_5} z2w_Q-bKE9rR^k+x_1&i<Z(3~0N8Xm%m3y0)uQal@Ld`ic1Xn~jV=TW1_5f@9^OPqG z?VvOfhqhc!gD33N;yzE&X70)<UYNfgqf-~_AT75F0d{pxal?|~Z<X`D{COlPRKWQ} zB<lUC9lxuhJI-#;UT#xp2Fy^aWZvcwtoOUKqF%Cc22bC5?A8f(Z@9d-rAj~X+Ya-8 zNC?ku4<kfGR5#U?+MMI2wI=99T<tS&tSW3CP-_A&U@rDJUu*N-;;^$nTd?2YYItto zx$&Uo0ayiWjOr?TZ=jD@POSK#i@bt1FAL{H)>b$LZg$LoFG4*=NgPs@Cgc7QZL!JL z^6KRu5H!f-fhqXKKi|v1>kZ&uV#ou*bRSj`gYGo?57{e`+CZHRw#OS1EUEnik$28e z9T-Ff2!yeJAD9l2AZ;;JnASUuzI+VpKkCjl0kK#J^TeWlbjP*AldU8JK1e4kggp*| zvNs|_yaAM)K->uZ+p)V}Oq%bE#wJjXyfB14kh@>-b;vu<Kv~^kBN(S*nqSzq0t|B# zyKSN7^E3ek1e>zn$@+g%ZLBE1;`BpP2?jO^=3?hOR{ebjL!KqDW&olB^?U;1+e0_m zfR+%4SsnH!mqTWnW+(gM)INXhy%-YLvBDxaMIRS2qfww>m+Mm98=;wg*;BN2-WU*p znR(PaJ8DG|Y*{DDBF{?JobCESY6<%Z6<_>-q`)aNG<ROrgZ$d3sw}N=x!opb9Q|Y# z3=o4qhskh<@Z(Ron^*Cg{fpp(!kl+M0ODVd$eP_zhMwT$K4?^zY^0y5Rz#>6`RIT2 zpv}8wdIMGuknj^``7~W1CVymf4$ZJ!=n4(ij>qFGzZYvfh&ZJXFYyZG*KRs+P2FU? z*-80uJlm6uqO##PNm^CvEtRPVlunrinerZ|h$2Au`RqtWAkG?un3aIQy62Oj0PhfE z$ZZra3!IRzV|mW5KlPbGXY^-}8xBH0b0V{rJnW1M^5-dEzX%DW^<4R^3oaz=;%^Ke z^{4~3g)I3oxYdOx8qYvYClQ3sNM_$bhtCLv&JfB5zoE1V3Txh2DE*>*-xbnHSur-Q zARlp7-FaHpc8r^gUc0e)uEDFa0nsK5WiXd8ahU*w3jDuykhtGaYYXkoa0dVoNQ&0& z0RG>SW1<Y!5h)SWi^z5|-5&F=<J6rAr(1x_86XjY0#2@uun&x;A9%B$2<78m#py4& zf^QE9D8dY+dN6CeIx*Vc#sup=8@4{9&p_nEk2CK4Q(>CBrM&?iB*rAi0MA?1ahkgP zw@?|RTsg;%DaJ$De1II2>gkR$yAol6ZAp4J_|T8^PdrRKHm{j_4Jo<Dd<9ECQS|T+ zwB9KFbN1$bZ@=L5{<<pi?b=c3hc(4tKdfrs81=jdfyZ8Z*Dgqf`01ay9TjtZgbT@v zyTQq_P`*%IDlV}|0g*8TUFm82>(K1%txbhd@#|x7|J884t@MNG<HRj5ixi&UXwycq znM|wM&rVT7T(lKOj`V)Z)16$BF4HsByx`5iKLY=^vDO-Hcu@cY1VoMbFaCFFA-5kQ zv114ckgW;ni?V|CBM|3z|Hc~!CmuuuwF|C}DHSFsiMJ8Z584$*1kH6gh#v1M&drpu z6z@uEw?3cVB<Hc<n$VQI#R+{Tn|bYe(>>q)^4i<kNGlV1S1Yi4w*Auc<#+SZGn)A0 z^~wa4IfRbJ<JcN8<r%K4Po0`d#aUP&85nsBa0aivJxKQNlD^ij-e+qYjrIBRtPWf$ zFN@Pe^X%sBQW(a$6$f`vR=Nt<sWPV6Cid!4qjyN0*ihSy^`P_=?C1INOa<%K8HVYS z==V{p8u?JA9n}IysL-To-;51K?b?Y&C@UL=R5N8H>LowACu--t98DsKExSEXY6r*y z`nk|z4+g3E17j;*G5KruQ9OnFDbReShuL|{_ZMEH2y&BIa~}>KV0s7zdMOcd6G9CV zqH^vJCKBc}K3^*WYo&CqQzllv+C;wn2V)&7UeSC0UmI5f4psNYWv@u8!Pv<%_HAUD zkdl3CvGirD8DuP<B|>~7S+d71WeeHr8%wrABEyhn#!^~HwpL3iQbhjuPWqb5|2)rq z-1D5@?_JJ$&vl<W=Y7k@F#m+v?IKu_zkOz^U$G{q%wZ9FEmnvXb27s}iq_S{irNRn z$vGE-(I{bj<iUtg?D=n;XBQXQnH!QnNH6%BTP4Q)D8r#uhQzI(x=9^Pzi}sK!G35b zJL2-aa~w56L4h^yOULU`KiDuB)~dcY)p;xi<l%z2TeXK=PC2e#j;Npr=GVlRpCP?B z=}6)0+_GxibJ;YLTW4RPbb<dT#_*^1XvsTG-R8+L1S4SCxQ9L0d}?yiP_?o9m9THn zNGMvrAk6RQHu>9_<7cWA9IcYmoUe9NMojs?Dv)OB5=y*<e1%Lx?pF}8%G_Ub)6n!F z^OZu6zh;k2@T5$4UTH~DuMagm-S3-)Oqq`3_L$KXh)pyVcbi~Fnnaq-Z(X)3k>A^- zBqtlIKTJR)Lr`z)5Uwlx4Tk&;{oh}5%ILzH@oFs=m7S5_&vY$FyOH(wjE|ZnCIS2Q z{jKAR;uWZ~FJcRczG6ZpiN-^!am`P+uRZjV|FD>Rj2k<YEgO!L7-eH5PPrLj7cE(B z1KTwDD|!x9^rpNKx65)sUJYb96WM61`7r8KG}{k?_75v*`PJjLrzXB*%}*}nSRi?A z8U_$*IEiEX48PVSHWGtL`o15TY)trN)N&H}%48<^LLFTEZ0}Lqf`|McN(?3!7F4F{ z?mcbLQ6`^B(Y5QQ1oBz}jPn(ly!{SZ`HRDDn%`VaTB5zv7}XsTrqPKLww(9I+&DXY z)rkGz!IiWa=P{24+0mRt{xuuzLV?F^FE!f>`{vA6Oj|0zA7q*(seTu3PJXzhpX<G( zSDqI$I{uCSXM~>YcOXV@Qpx<^jP122t&#$#C;5NIH+y`?{LB}N#;9kvtEt=9nYHMt zuWVoAv41<>WO?EmR>es(r8Q_CC+;@2w{#1&lVHNxOP(D_Sj(8b>k%K`p#C(|6zOOC zq_FLW5^n=Tt?(ne>f$1KA6(e7!pJ^b_R5Ic=~=?)XZc>4G6`w32M>QoACZVx&sV-l zvKlnl>f?^APan4|N1!N@-2bAkp5^Z*hv6H=yU<4zW%nGp9d}qf?~<&=@dR?b{mnT3 z$}0rRtjVT<QMILkYnJx?b*Va6@XUxx^WD5A;o(Le#Zu`B2u94%f&r=K;9k}dZg=r0 zaTT4b{3Y&(j3~HAHX3bIZtYC;WIlV=OKu(4DEzY`e3o00$EQ@RVcS5PX{D(thsWvK zJ-i&l8X?$$`mXA*8bgPkzQyZe0+O$9Mimo?%r?f-hNz+;Vxi~hGW(LauQzSSRKNIo zTA)txW?AE3UNT5AOiHyz`w9QFh`;sNK&KoVvWQYjbjZq(8u8+?_|bOtnM9JewLyW@ zzDr4s$&p^InRU$f<W2*PBKHQizEhA&3$6Czs=OZJuk447e3o~v!K1W{m}@nuu+K4^ zp!7|U>#a*a#?j~<%ItyS{D6wiV5Xo&M^aRv*kD!I8*^W@cBbW*(`9m~`_b)>((Q0o zLi4rl0rHQ;suP4y_TM`7CWu`0ScvFrZT(WqaBM0B9WSU@nyBzK<h+(aQJL+DuZ)39 zb?6{Uw9!`C?)$-_&DEGeyl0ECa>WbWU4m=KYFXd(t3&T@hP{Y#)RV?m6e~M^*RgUR z3_clg`nhf2!Xc-naT(GF%nf&^@Rsy|%6DhdKSi=~r7wjCX&jT3>d2LSI~MK!ki=0H zU}yE^m6nQ9=SSCSbg8i8j@Z8j_)<RjqfCl}@09sCom$`@#=kQ%otJaV^RDvK+&7?2 zNGOXy<c*wtrQlio_sRH5GsCjS<6(mFj#?TqlWa>}{xM3recS4&*=eH#^Es92smJVs znkbf!ECXio88^1R)G<qQt@^;@(sd%(qr956J?*W@5~=zD&|Ua=>q!Ex=gz2b`{x?r zf@Yp~X&8l?qMxGN!EYwDCxfGnly`*m1ru2OE7{7zV`rGF+OvJXA~@cBbvpYF`6Qg$ zcejQ*?_esIBlDnS>9vmDrx#szA#+#>ag}5HVq@w9&IXBoxqnh0;qLo<q!^P^GRz0e z-uTz~(z(05<+&nKxlU)V|H$QaDwruh@#(qZ<3<%7s^$}+QV|l~t9=K9F0)D$lVD5* zVcchnX`Kl51@Waw&x>n*1eCk8<u)-9WrqrB2@WlLro)`OcXAD(V(WQ^A6~7yKzu8D zpo9Box@aW#=~Vmd{JNtg;oP>!@^%yXnYR<u83+64nB%j;^_^L}F633bKk_}6m(_GR zHhfF1-o*iq8l-Oq^EI=%3o^$qS5?)c>?H^uye#3INaoB0=_7-s%!q>tCDs;K$VmCl zJgG-O5#?tdVNxQ}zq+&@*D9fix4npO-ILD6W>$PB($nRQgsp`rI(o2Ge~tO6`PhU^ zsbonYvNX5yJQt(Gvwq67hlj$5O$tBSg@wNoA#g{);<;6c<3)=F3J+h@4U%ge=P<#S zE%URuR|nD|F5paUxW>otvB5^OV>%@FNxsz+ky{OxB$>YK6uFGGH`d}BGi<w5D<U;v z-1kmnF2zKV{MwhjBQ@&ai84Xmj<J%Ovasx;&RLGH+82Pt4w;k52Q-?OLIYX_#VS}< z%8H~*tT8Wl-pV?#D)IgmKkt+4OqqVh1T6&9(2?jJ4Y(5TC%a-1J~wyWX-<A&c>9aw z%(liS6ynh;&$B;oP`b;h*)y`WtBh53lHE54$;3(o)rD!zn*+^4LOQ5kuvnUD2zWk- zd-3_|$Ey1&<v{4C+oZP~cX4;rFPf><d3{<%O=ptsj<;`h@Jm+BWKuwSHS?NDDieH* zJ{_6ib9&yV7e|zuOAIJiE!$nUT&h4!YpQ=ah?8_>SdHT3){ek)s0Qv(jw>IZ)w%xF zN&Qfez>UMprc#vZ&w`}YFE%q%%jiS)ZKGmS{AarJ!vAjFjjI0o!S~AZ&L<;-gT@v? z;Lk5=&mTWH#gDCM1Ap5nC{J?Fdc!$I*tSfat?dmBrkw9h{UKY^*D}!Qy{P1i0GhK# z&(An_+@LH-e+LincH1-HuNxDu$9rZ_lNP+MJ2gcf`TNw+$5W(_u2rT20;v4d^jG@B zu1R?E{9G8xj(eF=s#4TVMrZCI#!%+vhoJ|(mX6DnV%BLbP3Ns=CFC@IsJ#bt59b=J znZM4NsX>V)xtgzKaM*h|3v`Z<92LKLD}MVT!*SUIDQWJate4``HGcG?KX)U3*ED&J z3I2Lp?Z=YCNPlKmamN6CA8>QFSs#7_o$&Q{k)-QZPZhlQjfRG+7xSiIec{j^7J7V6 zLLvP)Adj7(A6_8LZ=vsZ#&yB^gi2djpHf`$JMopKek=&hM9()%&fi7f&-UbFr|<8* z*9ntnaX%5pk0Gwmn_K<)0+o%P&xiXC8y~IGhS{@NX8{}Mcb|au6U#rq`mFdwm_1Da zd@y_RT`th$fyt)B2&hyTJ6I}g=&uP=Fd^kv1E|g-1UWMWS!z%3yd1^CRG~`!san4p zGgv5$jh(YL<TU~Wf;GRu{uahP48Kx5p=x6gA50deH896zL$f%1TwlS&(h$swfz$pg zO9m1^VNhfH^@jn7Z5G<Tu7P$+yrP*$5E{z{FR11Qy(}7FyqQNA0z6y?(28=nZo)y3 z-=;#Y{Atqww+*ixor#cGSgEr=Sny7G3y@DjfR)eqVRj0)@PU~=a@%NwRxNvAgUYSY zAYwahFpqDy{6;TeRYG9u^moxn7<SQ3B)lm8a?KX7x*HP%gC>l`gk0Ce33!l&r0M1p zmfsEIa0<{k>*?N`l;J{qM$<qibij31h7o;)K%xe{l0TWhziOrYfm@2xlyH#u(Bn7e zN&Uf}mZ9M#)%Z6VbS4t#RsbRg!E5gy26&gPxbDf$O{Ag_<px_Q3|JT#L|~){B^rs7 zhXW98WudnYmWcr4>{eY@#13?kx6^ZICv3nln~`B)5QTB@dNfXt2<Il_U|$blB=S)j z>DR{u9$0Ak(BiJbaUCE+Z$%HMlVK|8?Gl)U4dtKxX|^sjyi*$+V83Jc&W+&+jFF*$ zRmB1;WLeyL_&^U$NW$-3#PvrUc$bQ$tBK>Pg!S@(Z9#}^)bBQv2C}ok$ybAsA7X*A zZgJpa02|QM!~NR@h~vzqJ5T^}WWWamoKxUN<4}890f(Ml^c?Zf&cD(?9T{Nyh!jlE zH4l&@2pETQz+8qD0oL7+%ie#}q$YRE(Mwcyc1<Y;CA|kP6ilLkKMn7IW#24Ou?v#O z7C=KlzR5*!0};Ld1s9|?L0{b~4BW%p!9A=Elgu0lJd4{89O#DOrJ0R6kRaY2niClQ zc@WLF1JwVv79>AdYT~#HXa)GNV!*)gn!!L{I}+fc$o!5c+BA4~LPmcMqZz&PoXAFl zql;>I-FGoCw99UsT%`z*qW}|@^9T|)ltvTQ<0-UJ7{GwZfV^J;z0rbMw~`5{+`~ZL z!vtRLg#`9y(*#m`Md=p_$Z140f`UZBrI&{ZbIk+pbcsXD4%>5(zbAuzu3rITtAl%^ zJDvp~Nr7I$(SuusZh;EEf!k?6Ou-={%^+i(;N~cBra~qkE~g>5s{ao{ndVen6%FCj z%S*4bvD88p+QJt>r*vRC$7*Q!UweiX5PpP!%~TErQrmOyKTvBq4>bKSkS=X~nj1in zAfSxr-84Odo;;NlQ2j9IvIb1OP2;-c&7{kZ7Si{Fq+_7XDlpQyW-#V7>HXtcYqf)3 z0l<jb@IYoH0iT<|B980-n18>u(7%p=Us>teF%n?WF1UvUG!@bdY6jP|oCNj$2ZxV} A>;M1& delta 20882 zcmZ6yQ;;UWwlvzdZQHhO+qUhmZQHhOP209HZM%CK^H1#iaQ42B8ByykqcSUV)kp>C zOd%+Ok}N0~3=j|$6cCvBqf`<CA?p7jUT&@r5g;HS%_Jce><jGdbL>Z#2Nz(V|C@Oe z`!`dK1M~mQ{0Geu|2O)e{x6hL&9H^}e@936!q(70fPkF9fPlnOoQ*J2gvOzfRJ~o@ z%<Yw29G%Qv+^o%AQ}#x&0A;H7jwr%N0T@&f<BLaCMs;ahYd&fx(T<BoIM8uX(9H7H z@^XUq#-Nt<o@S0oj}6Z;_G}N3?<KJ<LTN<6sCUD2*L+Vie7rqx$7kzzKvP~eByqve zu;8G!)H@ta@o{Jzs#<He_^7&_wj>DSgY7}SaFdt@2UYsvovM6h0I8LsawDy3-ypAL zer}9Mdcjv}ll5VUH^i>z=KwUh%63fVTEb^ia)np(4P+H>5|X=C!<*`MsMPfXnpC*8 zD<Y)k(<dsF5pQio^J#nOz2~s)lwMIaPsVHTfQg)(Cs+-vbX}1f)h+|79Meop2sm7Y zrk^0dZwI1OUj8Z*z<~g$R6nb#Y*#TPD(|i_)r_<m6XH<4U8H6{f#N9$jWsm}i&iO) zs~Iz0d)j3x&!34q5@LA1Mx|g`&wI$u0~@n(q0J#tz9oDDT(6<VPr$JG#J2`FaP3?! zkf?{yv-g5agg@5OpZxIMKXAGV{&eEfj8^K=fC#L7dFd}nfQ<)>1c{oKt*drga!O>V zDql78?}}Eg>WeFbEN8jeMMDc=fb+<6-4dxzah`0t0UAd8JkqC}0-Ux^N(j-oxlWxi zh@Bp3ju65Ja+i#9+{pm(&z?fA=`V$?F>Vz@rwh2#iYOP8TD<_R_(3>_*eQNWTwH78 z<+k79(4_QR6uQ4=f$iNB0kE2rTffVcN6<4n6)-ke82^`vDcmpz|3$Z!KK(q9|Kc0o zk^c55I1tdEe-vc>58n_*i2!LDvQDVxcz^A)DXhEgiG_%j?0~{rm4c}-mV?tbOssSY z;mg3$J0>~wyLaPq<YoPd1TH__@6Yo}SV-x$AjhyJCC5W}j)NYCMzI<5u^0(P_B(RY z`|%Mz2>zbk{r$rs`18364}^G}LeyT=0)kvRvUDQpiDZEHb}WV@;0B1EmUg~`F4VlX z7Um{T?<Z0y*-r=pngdfNjb!J-9bxt2kW!rocSe~1ECOSqLSqFs3(Lk4qYtM^8g@d$ znbR}@>n1D-zzWDjSu2gF<e7ITQ@uI!QFM1B`y>xlIZI<EFQ=7YShLm5PO?o>N@I!( zuEX*uD8^KWMsMe+&IXA0)DQ{F$W+edbmz0#t>UP<2|u^lgj*1%D-p<3;Qk@=LbXVb zW~ZK2>WL`{sZP^?a94k4WsTme(8})2h&1rkDoN9tYISMKEi-dx=Ww9@=(QQ$Vl;a; z5yn;JWz)BxHd_%Xl4VZKwLz{_bcRCNDt5rj#zu9azpb;f^#RbC;$#(@SB2xtQgt)% zwX@B{{CPj{W;b2VEFjkP$eG1TKu&S6kRV&>N``>93`ZYUdI>%bGLDUut9m+QW`#A5 zTu23(UHOYh1Py#R&)z(y5?jlC32~LoF~51EJ%TzYJ8`;0C!FKNm|CZw`8Vpz4xT^{ z-`JLC*Cq3h@ESmLb{hvb)PJ$&1a6c;XXVaDemukC<=%gF)O~4CeP~PB+(b$K+(MXN zdqqvRfau+K4KG|*wmm0-OD@P{m=DTT%DRq(m6p+Xmqh|gt3kV!rHo~*VaMG!tT~qI zk%eGL0lTMBHJDFO2Agp(xzYK-44>M@N2ARQ&1jm+Bo=Vfq&XiufrS}L#W0aM7!%M- zc5L2DV!SLPo^jALX0A6<6QGy;fMbx6G^iTTOMEOZ*KBzEaZ8P3;&;|eT*Pt{?>d4P z4Tb0E{7jNwZ5F<2XQsDkXA%dOrD0azMS7bnZpX?Z_U2sOcOw!op5STT8}au2E;JS~ z@%fMxn*d0%%+H_>3oZZ5c19RJ%1>6pKxc25s(OVMe^#`%DTa@Um{&}4Fi)L%SqwiR z;3>FwG>f9?PVG}GQ4?aWYrjY)yVhzS#l%9v=iy0StqlhKnF+4+p@eDYnF?o=51~@X zD8I_4ovEOjU_xmj?YHMhRuA{Pss@>fv6nB+Jp<eXq1UAmbb1Rd4__mnR`(Xx&|F=` z5uhfmil@|=SBD>LL%^0Bw63B?^Uq-GGpzf!RBHO*o+lt?^mnGZHoTpt4I2%nSJ%h{ zNvtts`9u8XL-^8?d<UIE8uPdY+85j7gCZfL$U95K@{klUx#D!P|7{=0V!mUGQ$ft` z&;@`|G~HEC)pt(y<h3!V-|lpf1c%pLz9&#2smIA~#B@>^iu;`#N{Tqod|iUJ>vj2F zlmR@+3VJtqOPxAD$jJmRm=Hv*78<NquEY)B)f2Ttt(KR5WI$~Y0KYmbKurJz*)Ivn z*w#PBc$lRo@C`T1T8V81Q<pWi)ya9CCjy`{ASl1&k^FNzZ%osG=-Z`KX7`B0q1Vq# zdd)Ul=urqp)S8qZHc4#<6Xs1(JlkT_zO&<s?~ZbWZd|-`iA6>+PI0*Fid6qjLZD|r z;l*met=RZT!TP~n?i`cV9k!q6PZ%(SfA-VSlEIxd5b0CjlleyFx-lrnsnQG$nF)9Z z;VXORtRwvVaJ)o@OX3M0Pn2Zc9|`FU)sQ<v;%{5j1TieZ-!BB+KcWg4Tz+1UGPF9x z@a~xW4Cxh|{EX@4{rxSPU+IDf(&`tI|Jo7^$t-G^kUS<cr;%R)`b@`NVKL<u`OZN= zSr^MPKpc-T){8iRvP_(M%#`+<`@?{e>Mt3!fy!l+#`7+6JN*Du83zCK?s4n6XQfSV zJS3T3IH!A<86$X2<!QSOeH<vI^bL7mLN42O+dgz@I?L+LHg@H9sU$WHGtosS-z(w& zN<xd7(GtjiYl0vC8rXm1c*dyz)(De#Yua-NARrtBARvnWXoTY^e2XgVBLw3A85gd- z_Fdc*e?fWxtRLD*n;>P+<|f@REz;1Gtr&@S1Sbv>ymaCQQsO-m(WRMZa_lwf?Od{0 zolcC*YQK{1qDF0k3B9_q^a9m}jeq-VQCnNvs)nuO)lFaD+w0!;C(Rqc!h<u%4C#n* z;M?NVH}~vs?wy~Z!T0B~WRw*#0I5)5E@O-`jwB9Xm>mql25dbxS%Fi?LeOvzhWL;( z4^B0RYHpXzmIA6bp<0@Sb3l@hQOMh=m3;vN3CfmcIq{VRGynK>4K!~of4B_lhBFU; zkIkp(BU8$)diVjHJ$wwRH)Rf7eeCj4n^>-rUG1zl%MQbM|0EKIIT<?&oINjF3aBc` zm~;hnMf#@4=HVBb)W1Bzc-PY%xO%Av&YwJic-5$Sxd*0)V2)juStph&Q`I8FoPB!N zJ+o2!1q~HEy0#RoOH0ZI&K<uj*+Jch^(r5(0o9#61niN`&n~Hfd%-%YzmtS*n=Q2i z)t%h{+Z@KA?;#gz9BvO~ynm>CbjoGZguMWO+MIiaih%6TJOm@Y?cTdYf8Wp@FmQ<1 zOn>*P<|89hR6EfL;Wv_HzjNK5(w0h7<-#o}IHZmxM^T4)QtzM^I%MIV8E7w1MjK!s z)V$k#^y=&h&<k|w9NK0E!^hcPa&S6!w-!~oFOQY4=5VstId-{;?v}U7h0SJM^*jTz zBMGi*t=mv6YF)vX-~V{gSzj>Q#t=vgKiX#bC3#s^BpGEd;wieVxluejjZ|JppYQHU z6qj!jjK~1E_E2L+UYM5cI-=y^FPsmT>U8zpCrgp-6QEAI9AG(q&;2T1j@?rtOJnoI zww;Mo#)QG1{LRB(zTLdqscgk}2$BeRs$(-PZ;QT2UP@TJI%enY+A!D6?0tD?{KeL^ z8H-I{W!00kQEIBhhlF^MMDH_{dgoTLe#w#|$EwdA<4A%mMF>R>x66VvQ#$I_UIU?< zM~Sy(>`{)=Z9Zm7JB3$~^MKP7)k%`dJE!I`Ra&Twb&YD_E7Q+ONg{`e_s9$wuHNiz zf>w=osc7}Mh)HL#l^4&VkQ93f&lL`95cPbTaG+?T#sIyGL?{_zl2x#I_AZGOLJ|kF zW<u4*j*u(dYPMtTIl(Olw~KYqPFH(py+?r%t%9n}BmKLxbf`A96Fj+W{>;)8Wr&Km zZJaV6Ln30qy__OJmBv)ppY09^q;T3a^1zXn)$o9Vh;HV~Hrb_yb%W=RRt{J9yQzvt z5DwUTU4`m@6$ljWa4QF4awjv&7Lci4eWh8*+_ctmZ@R}IHTHNu*|IU^Ly)4!D%!Vt zksz+(T3>~iU`b`_!AJBNwg9=5bI~y3Q(3m#@=?Gg0QTR_qFRuc(dP!_ZqsP@OegW< z$G+%GSXrw0wY0jmwo<M_I9H3|GY*FPOvqdyZ!d#SdeI=W*m)(e`t-ylnup#X14e?- z)RuP+6Kkq970FJCE!q=Wb2v>#m=Z0Esjb*~O}o=%uuAlqlFMVH%PHY&nlKBSN1}0K z#a84fz$YSWlUCZHrp*8tr01TE#-W@zJ{!_W(zJMr+S){0wd*6O%Dc_i(0foSIOlF1 zr2J=C?xG|NlOFC<v0SN*MT@D6sAeNr;ZHA{2f9h%L4C#45b05BoUsMG<OgKIZ4XrE zv7Q2#$BUH&qN!C{$cnYoKC5yL?4}##g>4nBcDQJ<l)7%(+DHHnlJL^y(3mnNW$E(k z8_>t2X~N536KK$d{N`Qu`(Y6(LG+%b&?8qpQ|fi(4~ZF>XLEn!ZERWF#1tHJv1By7 zq*-7TE^Lx3*FRxxvrD)fW~NG^_0-xeToEp&bm;8O(t2GiDeuveu8N{=CRBU787U!| z)w24oDLrZkYeoS2gg6RCF*?iz`<OZ1Mk8R=mMetx&D*iu=tmz+sTQSdanV`~7}~d4 zmf<P)&*?QTY>}AIg{dagsk$JRR*Zt&HECDb@HLbob#@G$?Qjgnv{B6bp`)hL)!w~P zQN48N40KZuPlr}BDSE<|_U6u2g)@Ys)#qNDtn#I&=U{-$K#`|9kK8xScWyw@5)psN zlKV4uT78kNx=$#9`h`S4LF~Ai(^T<%b!(1;(uL2T+Myox{vb!S4~o5#`?KH27HN)$ zy6QiC`_VTaBQmp~wbJMQ!Y7Q68HM8Y$x!EOZSLxos%SsWQ@$S@U>>)jxlZ87KEj9F z>ir=D#g72Z4~4Y12mJ|#Tl0`1&zdjxpYEx*`+&5&6Gti6f~mI{6vWCeqHj3dUkgWZ zRhi}zadtx%-PeA)l=K}M3#S=mXSRo(Nh>DKvZBH^7Z!5Mrv91fg8{f1cp7!=<(!7I z(YE+fzq1!({CSot=x64xY9z9lM*#kxC+8X;Ieq{kxYxv~{iAO=UIi+#Y)=%rBc10I z>B7h1FZ;Ax;>>d*faHm!5&CU2Ej_DQg+BdWQP<=`(XjC0vaAVt6UX3+yMo%?a`&@- z_?iP2^XyW|ZnLE=fBCAqK1z%1vzS^-w;OC+(X1!^;F;}i6c>h$KMvh4WiMW%df6|H zJOV((R`yC=c2=k}N--%OYC3s#exTJwC`9bW_0n`(ycl2gMRuAL`KW;cygGffb=PQl zcyqM|t#tYbOO|p<v{HEPZSAP&#bf=N2ik;{yGI;{jnIiCcw9o2UnHUCJ9JtB+Bo59 z!1bDFRtrPY!Y7%3HJ^~6>Ialy+2isf2Na-SF(>%9P!F~&WYORmw%r0{wR89{2GxMG zi0)$N72^C8*^KHhx;tbVNp*p-dFn6yv;j&ZIs$xK>u8-CV_@Sf7lkW=>a*5(uVRgq zb6<l)Otl{Y!PKZN<uE+e1@jt@F|~jsLzF6m^wTtBj42Esnt>@_CDlklvUG!KIb482 zW7QXE7RTqYEl2s|A1@k|c2RzB6!0cp$4D$66{qsaE-e!j1<8>td=3cT=S=d&%ptsA zhCC)5oW#gf4`%LgCwWtXzt_=!lBNVt3o5@<13*pr704?ebKnXFMg=9YFLiz@pNap9 z9CoPxlocLpekqJCl}xdv0pJOr9Kis*tdcyY-;ICleH>)w@KFSNX&t;3%LErCVg72I z>_5EF49t6ybmV*K3Vq~UBqd7Dc353Qp2S+(zn#3poiwLbmC4IL<^rNM!Km<@r|=X2 zD%P75yulZw6eoBs73tjswQVWZV{R8(w_*Z|aUzFMO*g}$8hu43Y=PdJ(_w(!g3d>5 zhc;D>xjwR|i;H8qysOO-369qxB^Yc$i`5lTuBZj$b-cM#7;sC!8GVSk8KwO-MvUMe zSpB(EbILBbtfS=q#Q1wCSc;AY2HRkXs@p~f>5mWu$nt(C9WY1hEWKnog8Zy&-(hDB zzncYrX=Upa+C4w6CN~{@4?F-AV^a$rlV$2!LViJ?NflpHy~Fai1|R8o=pnj5DoaY- zq64g?m|;X-`i<)h)#bKi&$xO43FqYRau#a?XjGqrX63Po?rlrGAhcXOVe(-Gh1EIZ zt*D>wS2MokX1J(|!yGe08qoK}5PFg~7$eY9Y@Rs<=ZkRe?h{%PO<90MgSGF*16Tj` ziYAdJD~Q$SX^GuR-ZU5MW+L?lQ7}O`>pa&-8m5+9u)>QE9f2q{rgLhx#-$6i(V1+a zfUz#idZMT&VTz>t)L3Z_M!UhMnXAnG<W3WqqL)h=si&Tid4!1q9Qv7A&7o%O^6vaw z@oH*O(X)Kg^B;1&oEyMZVVbGPX{9XrWX2{h?j!;3QW?#1R)twII(9BMFj<u@LlsAd z#ox0e*2bYZ;MoTPG6ggoQ9hTm&POFBVXVF@UIiot`PUb%CFg-FdgVs8b+BPa^JqOt zBo%!h7kxfsHi=j&7FHlu28(B%i}@s63l0nP%*C#YM{!S{jwZk_V$S&P+N#O6UyU)9 zsE6j&xb#(lq|VGX0f;O;!WF87X~@a~;1@F8(V6vQngssYFehGdZz9~b+lsl#H<a-+ zUJAc<a4#rYeed1zsk|O?H3fvgPeRbFXz@6+3<!u`nBo{4xa4HKPmCYaH1W%!BkT|g zM165S7Uo7};>rO_*onpjv*jqgdD5Jwr8`B^9Se10uQ?MVuQ^g_mIG0G1kVBa{ec8Y zr%>Qhzg*SD1?BZ3EL3DaQrLhQQ*8T$9R<<XPNz*}>JYz=L;8iYw=@Wz!JlYpt05{Y z3(EN=M-$=_Ti|%oLtMzs>WZ<|;3qLRT<YCB%}?~i4NC!x!o_|(IsJ0J!sJ4-=NudB zA#Epayt~k|7DDs>ZkQIcWm#^Vdw$RCwXY^8>rD`6>XL*GB8EVuoM;MyBEQ^2+;ntD zfo829=$;hv#pd5_pJIBwKdmj)nFb0h7@jb^@3?<5ri}V4pk(HFoW4S)0<!gC>Cal> z6E$kQ(mwz(U&8x~pH{!jcnF@+KQIklLlqk6p)j78_3rxE7%Js-78EV*;AxiF9j_b& z3AuiJlrAcgiCb!mCapR|u4Al7+JJGk4~A~oA{fd)T0z6RL+42FPoWk+(8}Y?7R*<K zDcqOPtv(k#>Ba3(J%oNuH~ulestRN%EPICUPWS>8yRFr7E_^egkU-QB6hab@ifxJS zmx40c5|dvL*WL$9g!YRg4%gNUzHECv9d<<{EO{Q3>wyZ2B$NX`!AXGG(4REg2FR|e zx={>sE<;)e=o(kOY17y~skIXj7xo-sNzf+AyjDciNFey;7hm$pfKH@<7Sb+I5V@ih z$^!!gdMfjMTm$!#S6fFRAhsP7evx=<b)_RL9v8lWO`4-t_~wf#)+<C*3DzyQGzL1^ z*ZL$~PSZkGmaplOR~kBD(nAnoIMC~rZWL>_wtFu&b+*wDRJYAyK1b8EeIzWS{90DI zL)PIyYjL%&(1Q<OU*O_0p&?<8)Z&9JE{FhN*#SK|wwn${2&6#}1qfK=$5r-DX|yoc zSdj9YpW%6<ew}SPDsDR`*Q^lX{ib?B<s)Cags|pr^h*bO+x)a*CTOEqm(1|ypMH!% zf`3cWP){AGT*G{RS&sHE)qwC<T%6+D>gFYs3g~c|;B2uE?hvlqKFr~*OzjVD{GkDW z^@q%bX=MR1f^Xe*gt*bD4|Yp71@Jfp%t-9Qy+3pJp2Rq5gI_HLJcnd^F*089SDZ;d z`>k`_yxsfe8#pad4}da&pZ`UQ<gq05Mub;!))haj2Ild^?|@tKw(qyBs-IQu#HpvP zjW|9S2wwxXaeLgx`OVpR`_*#}N5%y(Ph1<)k`KdtUalBf^m<lx6|CbGvW2KBvrk;> znEZkVbvp#IPnBDY2G6lUXWl<;*ZCWxhNlM8!KH-wNB7hEO5-=WG!$kVUZFL@w!Ihr zS+Btk$VS=;%RX~*7aWE`aE9nYAJoGal1n6Cfe?c8pa(R1$u8p6K5Wcxg!~yGz<10< z%%A=!fPJl%bjTO!Y@N8@QCRLya)d9E$_wf>0IQW?_!G79+4u_{Bz_!d#1p7@|5(;< z8{X-a2xmZ%ap-d0_|rSe0Aie<Eye&C?fW3TQ1gP_t1I1O3GqDQzS%5o{X+7b*KXi@ zBm6Z&dmaE)8GTl<fcvb9yCn`d_Z|-S$k($d<D9d9N7*RQ@VY?mzII;vtm^A3W?E4^ z?1iM-_g!#h!u1K`NGKVcXU>Lbm4~zu3$aEBV{=TSSkU#*I++af&YllRRB7z$9g2^z z;fr<8S8t(dg3dK?7_M<yBRQ}Q>}?J*(tX6+xk=LcwGt*O!u9Z%1nwDtqSY5`<ONRQ zi%xybPWF{yjl6<Zlpd%LZP6z`5W0biM3B6|ujNYd4TFG`lwmpBmK$K7L#ycUx!kiS zen2MN=$Xi4HgM&ZxJrJpnfh#Zbm9_D?7`!da9v`uSgImWyiitPB6NWpf(AL#vmG7> zzkm*!Dg=!5EThSCj!OiPJ;>(~I>tY!2%c&J9e2bm?<rk&9030wM-VQ-cjGn^e%F3w z4b<~O&FB0j!x^_hvOjdb%?9lQS@gkot_4j$U-Xv9yboEnAF@VBrSKO>1|a0{O={&I z%5z01d_k&)1el{J^4_r7FWDbz=WU$*L8u`gy_?-~X7o?o<9Y=IR}VU%T$UgTuYzK~ z1`ZPv`%-kY8-FPt9Q<C>U7$pw&bk(tUsO>J(<!#kdUMED(l*yb;fIX>i*0l+j#eRo z;S3P|bX0n6pfdkKJ=xhOx&)X90TpW_%NDKy>yDsMz4rHY%r<%2AsVU3{WUvVu8mCj zdY&)5jogUjCg204J;K;o{S>K;Sfb_{v##V)KQmf~!+hIrQNW0$&a(A%1il+y>@O3m zgjCE-WsLk*&~c~WDs6ffEWA1&?N`<P;>4QJ*2_@<5$O4HGQ0unVysD<Uxab{$@v+N zi+?-7y7*%JHr$%QHNux>56HxRX*^4$C9kl0u%!MFp4`-&{cdb&$@#pv8}@X)=brn0 zImhS8L3lUU<FW%?%jBA$!@QCA3LPu#O~?1N5yK<&=s>XhOS}@+skZrS@h|BAW_7!R za#17yy1+E(6bB>hl#?+WK%9oQ?zRRJ|4=h{HOinJGnf+yTF$6~stj%+8+2GBR<cn# zPiageql8#`B^~N{I)n8m?w_rUeh$X)20!`zPsTqhfJKjbvk=b5RYcFMEMC{SnLg9+ zk9kK%;NBoY^LJ-W2}TRDUPfaL3EES$o#EbCaJolh>2OG9>}jn-!0@*Z-$8Q3i&h#^ z1`}BcvMtJ*xt)>f5af%L6KBGE<7mYA%e&H;<h#}o<%`mXi>?G_GKva_pK5l|<}wIh z(S10O^Ozhna2)fj5pCBDN6XrZeID6QW|rCu{OZzsKM~rEK0PfF9&4Q&n|&2^o_K-I z<&Qv1v8Vj<+*YeLK!cssp(cH{RvLJZ&JgAt(SG!G1(dzIMuG>YgML;@u^VMJcFvk7 z$L+pSBb%gkg$aYGhxlw3&baGelO_~WqpwCUQoshEgTKYvojp4y$37~B<)nx;6JOsj zCZqP{6t2We0z6CJgpZ6k`!YSV$4(uQM-G9py{Nqu-_6quuwzu*SqxzIu1j;M2rutC z+Z-m_?qFY@kU1#ZSd3MZo5h~gx8OGO5kdE^He<N8QgBzBzNCcIRrkrmE=9rsTd{6U zJhqsVY34pt`xoAk?1Tgp)l+*xc7VC*CGr5vR7w0oSVHkYx7tX#7YmAy1qneE&O~`d z_k)oY#&Dwq_?+X2?^KIFgB(xrTh2F8O&kU;JguPCMPE~UMT-K<pB;T}`>H`hOTq{Y zpNX4=u`PtHx7o)Z!Gaqe(Spz4vp={DCtwUGNQ4uhddm)R!t{@Ns$D+;)o>55v0T?0 zfmm41<mSWRl;f{dU30*0_0XNGKw<bpVBXb2u$NE)6s%M2?Rm%f!5|@<Ly*DeANV6) zLjr&O{b)Up%LSzs{@phFqE6$cN~Pzsi%Bs}+oseQtFApZ(OgHeKC07Eo`o+swMD1; z(-=)aqsY~eyosn5gROSw)C7q}s_zud8Jyf*f1@K;X6Z?t)zVpV)Za!|OM7zAGLv8R z@Wm?%Xn<NTT<*UAffQMtt%x=X(rF^!sdu)hxs-cXk2{_c=dsz!b~X|y;`OUxi}l@~ z9zqj4v`Gr=kLFMbDNTXu&exmAHKx*Al_ewTD}IE~%l~Pi*j1x0VZ~l8j}Ri*wkdl> zM&4njXQ?LAB28e2$``M{@B>{x|GS3x^CWZtppP57YK>W<Te!yO3BM1<a6d0uJcHt0 zP%JsZF#>8>mP<e-Za#|mOTim;I$M-vx>A=rl!63^$h?m|J)9lJ6bwq0{AR|X7(tx? zW=>cKDK?xuUJB(rcrm278Vjuemw({Prt||Y5JCYAvC4g>&tf9zAXJP)qZT^lwcZT@ znCea7cAsY#L<xcV5N5nFWq>IR!<c^y?UQ{X2VW11#x6yF;)#ZK#<0B|Line7O=_Y# z9LYE1&?b7gJ0>~gl95R7Q7}F$DoyM%i$5>+x<#*zG!EQg-FUmvCG_UL#>Uv2yv?8E zTcbL<RD0_^Kt7&-LH7r~t)CU&PY(dB@``B>2Xx5%f^SWqznnFKJYZm9P)m2Ikb8x% zoTvCJc;t;kz&wK|yqvQxnWLzA*5!Whlh;KE1lAw(>qO!0Y0ISNirokAdmQb2ApY-d zC$-i=w*0SCAjJX#qWae~;HTh@AOO-dy^ZkHvH!|9PdacchdLy;Q&ZEC+b_+EPw%Fa zN~_{btlBMQ7q3m;vT1Kzhi+~XK*3N^(G=s6poxjsbqHTTL<SaPjN;yX3Oo#fI^Ms{ z%5k)8+rtB{bKd&i`tIKS$9+G4yCMq081<n%9@7WxAP?$>Q2oja*g+S=MgTw*9XM!r zjVE+~J2enGA|By;St6l#GDgbaJs;}}s~sDGbMMm+`<ii)4f~!vf0}s>H2M(*B2K-D zHa{9?`I7}=f9x?1OM<><`Vl7H46{r_FwyQqntM?NMx5|c4v&9(kp)VQMvsSnqYCH7 zD@NxfOkb<Ae7XS@AQQ&FKLA4S@sjdB&0e(y1u#c8xv&OP9>4v7451EUfc>Zg86oe} zfcj~s6+a%@h`K{>t{%U`;S|-6a^BOWKH3TQp^Gv1(aHB=k3QdnGGB*@0zwHPA5g#8 z0t=`s_m^<-v2Sm1@GtQ3Hu!mVI()6XoGLB7%;bbC?^x^`){<6KPyzVL({iO09MoD% z_N7bd>CGNild<(i=J|p3b5;qGB0r}}XA>Orr_sH$=k0U5xO05Ao^Gv%zaI7bM0-j| zLx%aoQ22SlkAj!Wnd@>iL=<VYPBZhHP$uJxc6eqRbPl$%rIvnZB3O;xpy&~9muJxY z@n^J_v1uu%B#K;pbO4H@ZO%DOd={mzWE5z%SFAHp@zAO<lrQoA<q{MvrWH>&?cOZq z7UZ#Lh^yDkayV{T1TF1ka`EBbLcd05KQz!Zz4`e0KVP)Mhbo8G?ME)O+zm;}5Y5WR z++D@uW%=SV(|3xppo@0JgJ#>piHqFp2a7;*r#LB9ps)PE&j9FcZAcfUUX7wbBF66d zH~HVp{#)YXmhu!>ePg5C-F90m73`|}$x^Jm<E$iRD{@p^Mc#QOL}j}8CBuCvwXI3a zrmznN8_=h4QlAg5WBV&@o%Bms>?t#cd_FM>W&a|+V_tTM`rtr$>-4f5?g~4y_BlpX zVR1iUE%l{^BY<eWn7s~KcCao{k_qzVmYM?U{BwC`$=oA_V8E|R#q)Gb#Nc0Py>w~| zOJ5=?YYp3ps6DbdjQri?XeleS^dwny-RtI97&pu1pX?7(tl;F%3l&+YVx{Gg2slYj zVl-A<`6A$L$Kt;UOC(~lhhm*J+ObuiyOQs_l8k!SIRJ$AJKZL))eZ&ROodUYuF<)= zwWXc?lG9{opEq4G$*nb&m4Q`;Nyh6@Eij(cRNGgPwWiqE<*Nl!6_XtYc1$+wv8sRD zz+cuG9?@%m{-()b_EOgub_MPeZ|gPt7m3znjxbhrYspXSHJC|NzOx4m^XNNyaiuh) zsQT15q61pHIl?eQBi<G=otCrRRJj}y<*+rh_UD(ZEobQ_aPTs2&&M6r`QrAYhW1-* zFtzz<JdVw2#u{Zu%e<PRGY-mV5DsL}sj+B&0tHpHcT^knb#yczDEUf`+&iU53&iya z#uF{%tKVaszSKFcdUF<~<1(z+Sj&za<70Ew`2e(bmuXQKR3$~cI6n+Me<!w`!2YUo z{E*|ai~F!8U30KZcE+VYH{bV@3}%m|!rS!>zBr<%zyBdb{xamNIP#uD8#L;OK5M%z z#rpd5;peQ|8Xeonz&LYOFGqr%vGi&=^P7oQTOF+|G;3EGC{r~^2ABVl;cj8J^=6Uf z&IaJAVv2q(9cg69Tcn((!pDjz5#XqK|L(jTeFqv7M~)wV#t_$-Urgh|-X+1=7M=qW zN3Z8fZx3Tz<@GZc+$Mi>m?X_#7x6}(JbB+q!0|$D%7|VLPr6D#Aae=l%a!6GpW-lO zTjvk2`fNRVra1-H=Yrk=^E2J|a@l2qr2rUhpaCu$Y5-F&Q-QfhXZnh+rjbph8BnRC zp`?;UcSFE3?&(^<vt{w?3K&$*$zL-yama;0o6YsU)(WBYNg?tHRJwgDHI~1Hz<aLE z_qq05@ryYTSKXpB?b5%t&e=)Rd_<t-&YkY_nt$0-QAnWL>%WTL#ZoiOW9Y5S-~gNn z%7@mJAMj)xnTy&nR992z$95@&rhY8;&Rs@>?CJ3yYW&I670%yE%k)?QUp5%pbPny| zML_2MVdr46#`$zn9D|9D8l!NpIwp4^d|J$C&YdQEThsOWwSKZuqR>C;6=+P?0(Ugk zY4aMRCcItj;M%b`@okO$)YbF>UIrLZb{yI=nKuQm@GkREw6RFVY|oFr@U}SlhSk<d ze0-Ia;b9eB(|gXdI#W-A2SJm?L1|AL4=`qqWoFTRE!nP*9Tgx;fKhFdZQY28^(dp~ z4U7}tF95$iM*I1!149zyZ#T_UOI3qjOCfkzpp{DSIfq!{<$Ini_4GMcl?Dt<h&G5z zIe5&8x^!(D?%lm+tP2dNR|bYV=SL>t2yrU{)1DQ^&K8-`q0r=oIU<;wwoo}{g#pIW zanH2oScO>K;-_;}H%zY*v#gBOjYueYfd^(+F(yfzFg0e1Oz6;_`Tso?m!zJBD_gW4 zGtcAD)HTCY;_J|sB(Qk+YyjJO>t?5wVZB{=ZBrikRL4_xW*F*d`tpmEcw<XaSH__v z^byLa*1WM>7&z{f4z||*NbX6Ho%}K8+H@TiMd*){#Q_+!EnJJ*woYpro3@nPwv@cK z#wH#z7&UpLs(GUlInHq{?Q7b$&Fk5SMaCub;iN}{izH4fpB9Uc8-Q;&Q~28<eyuZ* zCUjYDhrooZRWAHCt`KEGRLVoT`rHtxIr28-z6~)hj^PpSPT(_D3Cc3PKwoF#=JR%7 zzL15Tn3GPp3{?VQ5foL9)E17)X#Ny9nkGvSN9r>_Y?su^F>i%e9B3_DLS;K&kQa|r zzQAm{t*)hCa8pOES%CB~qi{QW-KuYr%2qdah6k|L#kS(u5B@k>t1#)QamVaW4TU!k zUwEVq(V`b^JFeg|SFSwmo&XHXnWGtO9d`|51bF2WkMi@#Iu&=ZPMW@G7H;u*md%sj z@G|>&a~2?ZsL*+`@Y;58_D4w2_8wJFc9&m#mmnG4iJ};JXn;j=<lXPOB6@R$SJdkB zOvYA$Z1H&om9{!<JAWw5G@~<Owb$)iyKU<Hwn)|1hR}2i7wV?;FbIqQXwcPRq?eY? zV>-ta)9{nN3<EuR$I*SL?<>pV>rsKnJ9Brbd+=9WJvf<ro9m982@o9N56Oe#2N@IM zLtXZWJ!TL;X8-{9rZ04$Ula;qq9e{*SEQs`Npg&5tc?LPG!0C(TNr#*L)kcTQ*GQ4 z^biAdm}8~JMZ9lnPVj=f&5OIfS>JP}J6o{-X6L6l$Pn6mu&VB7NFnVZl=q;!dLhep z=L!GzgN57B>z!u)uzBau);TXmI$1(C1G<f83zFl{7C`4R>ow#J(`=u8<WVR6-n|PX z)g_a$2gyME8Qs@|pH#(U-XLWC(Js+qUrNc38;I=Zi}Q%5ag%;js8rnej;m}T;)<Uf zW5OEl0N$fkd`Z5(v@TWcQp$#&;zB~PFg~5Eh0{gDWV&}V%^S9grAN6@YZINr?s@ey zzOZ+X6R_+9W?siyb1Nnu`q5`Ep}o+N-7M4-oT??SP28JBurn0%z(Fx{vokOpVrn{< zK9)-#S5MF4i>e44PfKmQA#t^a=OcRivSP$}RK(JOobYjd>DEvIf_M8$w3WELdvK{? zRdT=oBly2wI25O}rum;276JQbhH3t@c5|Q)k@BU22Jk`~NB@CT*jlzx=YlRBP6dTs zNt_FeYZ^q-Mj=W>Dzpf)@6E-yxWCZYxluk!H>uc&T9#@`;o?^~Ld)f%+>DmLMx#{l z^tqe8=;Qsc^mOOhr57>mN1VOs_5SYjzJpg3c)BkET8oLJX+LB@$GvCi>eXXtP`Z~v z2S?Eh1ZYxo9go+1d&TYaX^dVubr8P2Klp$<Dk)PmP2Ed)do@IJ9?rt-6&Ei752%kK z^vXZ__2}&pXLwe3BHpRoJAmIk90x%AG(?|x6$HQ4XoVqGYK6DJ5GW~=cI`wU(!3`U zB34(9MyQuE6Aw_FJSOT9yqZoWBvjrXBX<GR0Kd4=2}ez72*$_N{O}5t9;Eq-4pd<T z>P%Sk)gG|@))MBWv*$k_z`+eu3k;JZ6eounWhMUkWAw@MngBofU+ZP`o|7lI0XqzU zfs64DH9y>a<$Ha>e#)qUh9k)LBE5jpqw)8s=z=3ln1K3X!~vwrE$_m%zM5kvfa7=& zV7SMpn-}5j`s(U>lNRZmr@UZcn3-Py<+crF@qGd#D4BybzxBD3y>MJ5T!<>6x38q3 zt)-@ff4E{ud~<()b1@@`AqRYxlx7DfPO{D(1or9@63oy3l&LJuUHo`#Z)F3Sbg_ak z3R^{*wRjxjU*~(PUpS8wM*+U(krkB$xR(+kjK@Aqf%f;?kqY(P+7`B4uai7xo8lZZ z3?g@rc$9B1yNW`7thwjHP|~04c?lJ&3PENc<}GFpsVF;!PKnT1k&=lFOlFd=;g{JN za^_EBdvD^=otL7+abO)Edwy?Z^C*ZDl@Lc}4G4}U`(@q*)>n~|&fLyON)sat*k;4_ zraJ9JH~-=j+j0qSIgd2RpD~uj<bExywW8w#&G6vq{Zn<#;2P_JF*cKgTy-sOpnTqp zD6M%a!lQtllxmJLFa`^7;z)P%9j}UHwpR!f;LE)N4N7xwDPi`d1g+sQ!Hd)UUATTW z=8{w+-yZwe&BrEeFpY1t(>U}EfaLL29H;+QlQ2GdH<i=<$dks#RPSCwXEh>A=8!mP zjm@p`8WMK81yz^El4ebd#Hy1%!updrcogf9_wbN<5-44bFD-653>h1VjNidx#qnuJ z&$*({CI2|HFThyb<sx#^HtTddo!kq%O$Tn%39)X2^@|?IVN4z;{*XcgPy(w@20t*e z?51ANi_QL%##<^+ZNd@9Le2ICGoW#g3qLS=7wM{3c;H22vtWt)H-WbD@a;V$T<}N{ z=BM_M@tros=2YZnDWzohPiOeR;T_Juy>=U^BLw)02RnF-TeW*e_`hQSuymi!2Gz9_ ztys*t<HlV+?1Zd8Otyip02h_vC+D2!Bi5E(4*4d<4hh{9Y13GAcH)Ph=`aSNI><-$ zT5IVTT<ih^*^W4L5jvx4^9smWcd8uAsbaGpC1e44v`7(os@TeVRd(gjZ7qp<<4#m| zkkL&t5%key1=Frf#W+o|C<+<zERreqCMhs9<)SE=CM{%HHf8c^z(p(fE$ugd3L~V7 zGcAvW8A2J3usGf7h`5U3zH)X|l(|XQU^@pt0cYJpqp)WzGqOlhX1-kfa!XN(1<CJf zT$3{>eECe%BB-1lWPa;Vn{_hV9LqB6`6N?`k);usCKVYtYm+oIOt@{%F6u+Q6Kw$< zdIk%tw`^iu^54awfbtqJb*a$^>awMpQrlvQ+QktaW!lClz8xLZ=<QL8R6I_S#S&#x zF0{CRbx;e*C7W`Bt#&^D6fEhwMY$B`w&=2e^9j4GDK6S#Wt4ky)Vgw_y%uW50f%^P z#@jIl&tdPVn2KZK4KW6*;_J9$<P*-b9A{j1_n!~8J(1;Az?;kbgAoD-|FXMVeDlgN z3nRg5N?laaN+xm#L#a-9LoRf@gro|zK`pztIp;ps<|E8?m1+N)S3J13na83jEe><- zb-fn<Zqp+q_-LLfSber+$;8pnRA-rhC1XTo>MBi*K_LTX##nv-J(aMzHzuSjZ;xu7 zhpz%s`$gRdAOUgOd$}j)M3JuLX7<A8qv@VN9P<Q$I#i#N(zHQ?c^83C(NaA?H_&85 zV}UrLw|Pu8bUBW1zPCG!{_N<fhj$tro`BA^=Z=9wNe)3AeP#w$G5o{Z+_E+Qz}}6e zh^i%nex>pC(#32AWz0)1qeO;eGGx~M38rM7Nt0I)pptFK0cxLeGt!i_rfr8*3wu-k zl&#J051A6HPhyjYaCW409o@x@CpgiO!J7QAnLh4*n)+2e?>Pn|si2=##mUOaOW$ET z{aku*t6fQ#Qpbg;sxYy-Id_RGpGleL%f*x<U4h!ZH&JOkAQgK|%`X9<pQG@^xk~>N zB4$Jkz*@#0r{xQFo(LJEO^o5Uy5TjWOk}?dy%O51G`JJ@%zxNDi*;Fb&p%Vqd5{wp zn2osq@SfM`9||EPMUKj9HfNj3D`^2u+J1Vp;SCwi=iL3vw>y?;_oDVK(#wkPa`VOX zK~yrl^LObU$H{ZfXV)X^6u!!NtmxM08%V<#0NM_GPaFvh=OWgh-2+Z_6hLL$-~wQ> zH5~dbs`aO)4)t4e>Wfcg`rQeS#wI7kXH<fBs$&JJdEir@9Rh?e`K}uZ6&@vS;v9Yy z4ejfP0^5y4+0>X5<9LS#6HcMDaRmEkNeLpPKUngBPzQ_Z68{l=Wsfa-dBI|k97T@? z@GC@>iqza&R=81Ui2>#h&Jt8BnnH20I5bTthC)#?Rm`$hvV?jf<tqJ8;ea)YaF_O$ zN-KjFTU3nBO5qk$n7Qb$VrsfG94+RkG*l>g<5i^v!Jt*tPA?et-)$6eZLH~c5`kAZ zJU;aHb!Ees%3%<6lB5iyJl7(uugmtD08{?H7G#K`ibm)T6IKUFScBj?t`OP>EFJs1 zI9>vhJ!M|Mghy^}nFNeOVzCc$(MKY}^b##8?zdxzk;pc}aprKYz5xC{hh$k|ZarBm z5E%0|XDa-#f+dcJ#dMMm)|D$T1s0MDJ8qEOuhW`2c(>4AjQfuC{#=n8CDB3$0LV*9 zVMvXhk|~DO7;%{zoRuUv&v`O_yRs~vMrV@D;17=lZrK!OGwnSKXG`>C{o;ow2ju`y z%VW*PE9}J4DO`*OX2X;QSDgEoEespvkhLC5&`Z~Kcgu#l`NqS`ZF5gXeTvUr{a`p7 zN4c{#PDWXi^}IjTMfT8X(HOKIz&V~p2Sj&i!w0dNoXM%|4%rS@XJdgk>IT(ojaxHo z4O-8AEnQt<U+>nYIhZBTU<d;0MwI+d7Eg#=ag8?s(V|y#GI2NhEU`aV`bm#3!D+yc zzNcGY7763tGq4+CYsqSV`5sKh&YuU5+=I#8(}P_lML#ZRj?KffA!lp@K+3#!bE2%d zyouo*(H-3H>U-VN*L%)?HsMd+zVK^l(*BjPF>i8z_HOsJ!E_$t59$|9@hLeSu)17n zn%pQ(xhttZZh1z7(MkuCy(g>j8<Tcf9DM46l&oXLO|wkf5x5ueAuY5&a*7sGOD2I= z6MhNVt<%k(Wd+0c1`?Hwq3Ql^rCV}Z&bKvQqCkPzo!sxd>sBh_urj&7S#Vm$hPacv zLwO8uvkY%b)Xz!Q(ByK3f}9BsGe;_yF37z$C|70w|C(!a4%w^l-->zbzZG+m|9D*h z2{ORe6L%GTcO6GNnok-J7+5(1jERVLLk13-OhE*dH&B?FSm9_)UUzJXD1kL!S(22p z2(hqth;#v@EYp(^$x0Sir<SV}KEb1|el_4)G5{P<XigYsHr;$g@uDN}w#Ryw-*U(O zDeyf1*UyDuP~yuUyW3f7#8b>qbL35oj|eanSp;c{Y-Sosf*FJ@3TMjVrR4KLHCEOp z#!7q?38gz?23O)#>}0N#`0K6Yexw^KBq7<Ve9H-KeB_`Lj?iWp@qU=&3rY~<!3ixP z^3NhxK=8PUm)=qAQ502v>=E@vKGH+$41hwSbzb6L4~&x3%b>`As*%^8({`go;}D=Q z&4I3AMiEO*YTspz{YKpyB|k^=2Q<CHN!$Lnwki3(OCD%GKj$R#_j8bcNN%glOmqEH zVH%@%_y)C`xgKM|oUg<j$Fd}Ui4s%av(&=tdX7s#G4srQ=VFiblasXyF#%W$JY@kv zc~Ht(s0Lt%#=}AB+zP{df%;FleGFiiW4h@JA0~k^gGDbh)S_fdhtWqlM%fczZQ;)@ zUQ0iVDfg+8wad)&E3T<ZgqOsW%fdY!wq{y(tqL&ApTc9NV0*L}3ifrnrp&oGy$%UW zIyK+snx=kcKr@r(w~|8ij)^;s9sw51r-#o1XBW&m>XY;^uZd64hs!vd>nH&Dt<tnd zLP0nVCF>4@<uuhgg;8wH;EZB87zShB-9Ky<R@19jN%y2XoO&BWWRIB2gK#<<eA1RV z7!#_!y<R5Z4mXFX1;u)^G0hWh;P>Ly+$e;*^S`!W@=0!7@r_j#q*QyuxZ}6^tkHuH z`vAYm7|N|jbGxpY@muCk=QP0epn@kL9Mj$Imj*s=qq1Z}q^?s<y!=M(F~?4Q2_oa7 zrau)vqiM%bT3qO+dh>Pe#l<_f!QumK@08C=r=*+QbSGlNg%`f#xtHpQZm}=!9aX3= zO!(Q=ds!tQ%#c;5&cv--f$y6l*wei?LX~H491{6UWBl&k7oYG5kq7V-xs2>rdaU^F z3PyO9i7aS-eP+yLv{lZiAcae@@)Ezii#;RZq%ls(h55_S?F;=Zs|$2aRHKNfootY2 zzwEH$U2gpBU1WTJ<Rp-;+mxChMLv#QO{>KW8QLXa3eOs?zTXKic-E)<FbDK^{!n^3 zr1&y@zm;QHa`Sk`nLGf{K->s&B*sO+LNP0`HAiT1w0#(GsldU_j!{+JdoWUY2SF2d zl`^YPH+EbxOM|c?qZu>}zhi?AnoAqydZxA5Cfnb&em|atMViAN!)M8PUplMz2)$+Y zFIlshh0dD!Ai^b=db(Z@X6sX7aS$+^91Ffy@Ay*Ll+|0nRt1>$$X%k(UOGDwxHJgu zd0Zqy+Tu3ff$$prM8=(-cH5@FYc2-I!qihqOBUaFdXsYA3)AR__)q^0-(~WCDC##U z3U8{rEc=xru(|rh62-XY;v0)QO8kVSZ>3OH^{ZFi$iomuuKuAsW~Uc;7xFS6&%NX1 zYgnRza9(tqYyogP0A=+u)0GLY1KX$#^8CH;nC_$5q3?oo0tXvELi|)&*%*&FNgyD) zWM%J|(F1R^t!wW+tgd6=l9v9aS9T&kapo5J*MDPdb9-|;219IPFH=VlG_Bhkg1+h& zJ5&YQ9zur(IbWD7%_>jW_7aYSaIqPjmr9qLFxC&U%m8q)AL&?3_{eo6FGB0%_<&qa zzM^}E#Gm>Hi{6}$H)5lR)e2U^2YdRMmTF4NJ3Im1M%27Edo26Ke59f&8-{%9VA#B+ z^UIBi^#_M!i`uH_27ftIYw0h#>rX`Yv=%>!fTgqfDpLTuplVrKQlUDwCglDAarbS@ zIaCp^Q#-(-G~5DX9qwUSC#+6KgcsJJdU=DP&lX)k)Xlb&c+5RE3Ca~1IMpUm%nM^Z zA@|Q+h57*7Io&Zt)Y#uOZ4L9HoE(r!`k)do7OZfbZw-7T3r+o>&OzMFfjS-XR*o1n z*WLiKqP+S>k!qgk)zm(zWKrFTVz@5X%s$A8TvUMBtn}llsKzT_-QS?RcTSH(+UFWB z2sOxkL|f;KZu}4%khjX?iOQ6X5`$lfK6!ko<=4jzg{arN(&gb?uKc$qjIoT9Sv|~9 z@|`Lt$|~}Q1Oe(?wH}8CCf3Hi0As{(q~bI6&ICht9|SBczn+4CH{}v#)}8*8fHz3J z-v->u==tHQ<~x2q&?FHxK&LxO1eF_vBT{AG7zpb9aYx&7K<}<eyf|qC4Z#1K-IO3> z|Mv%~WK$VjY$zZgSwtWp;{T|d&^Q!;!PL&!)iq2r3V>#b9bnPY!^#WCje(*?l~@jM zcS{13+`2-R=&{ZAiw;^}$+?+#lWup$&e8%}&AzYt#i5Nrz0?3*HzB><j%wjZU5!tt zPDA$#^9yt3ENAx`S!0IrBmV7k@7TBC%=c_M4Nw38AN2TAH8%}w1m#}bVZ;IGnjcd+ zssDDC8!LtZA6L&E1f>xOMT|KmHJJ~gAsCW0_mdkNS7$p$rOCdRMn^nWHThVEAyEDo zuxPLqs6Kf636EkvkT3G#6yJ=a`Du@8-gm|}(7b0y&%XNr?ahnz1UpuGf$Xb2(C4Z( zs>=1#$L69WtHR`~-Q$DrtKS3o!S^-qLBSO#-;1J`-;-i1%=t)<$5DucW3tMP6D-`v znem|UA5i0M>8?BKa*cbihY!!+qYis{9Wxy$s(RAti=6+i{Ki-PJbEB4@l;UUp%;#p z*5bFxim%DJjm@9Mg(s1Lu8}AoAA6O@j~Yj~DY4RR2{l@isesqn<Z=LPUIew)RWy$c z7T2AfI~(oj;w3MPcx!OYXq|@k8%aSW5UwU%fNJl`A&%(tnwEFb>$TaBHR5}?IopoS zbo!FTiZvV`>$tAVbX$<UOV*+7a>iN8nUV}J*=|5SZ`NWarmeKFcIP2KB`@5~S!RyQ zzu?`qVa(Q+RbairOHTuGr%RvOn^=DBLE{t;wItI}OKqUgLs;V64nC{ZqS=h$U`?CB zGs<Zzme~y#-HmP4i4mJ~IgHV5I@GSF72fjZv$>coCOx1+&rUm-mfLQW=bQ6+^Y#qg zbFt$-#Tz45#G#zeeAxv!C<I>d@(m@<d<~@%4>?Y4YUqe@+MEEwlvPd{blM*4L)%^W zQoK1cJhOa64GY}`T}Qmu*S*N6<w&W6PY?{luj<Ud|6d(f0uR;p$7e9fHe<=Y6B&^u zYAU3x&z7>3co}2~&)AnzlagLpN;j>R3Y9(b30VqRvW%fT&zhx>C-L&4BL8zI`p@CL z`}xdg=6rtN?{7Qj+;h*Jdw(|^@}v|=FC*>4;@`hhQpk-D?MzQJJKZ$9zb;0!)`BCy zK-tR7Ts0|^vsdMTDgE+0TdwmT-s;5H<yE9;8D>YAToa|MO?v$q8^BXCqAM1oagXX^ zs}<&!d?6*Q--&8vZNR2B@<8lVjHkk689vYN`%>7!(nza%QJ%IDoT!>NDfUc^Lh>v1 zcn(puW28%5Z^r_yOmy_)ZMC<($hft))4ZnIsJ%jEM3CqDbPx}5dOVwCl6L+~oU_-w z!l}%mu5i10cb;d}6!!(ITET@QwbF)Eb?N8QH^0RVUQB5#Gd8PF)2fK+QphFN>N+Uq zM>hrv^$3SNs5C2dHZ;)Aa;{I)UcgZr=X1dA8cQB(!(U?GW%;M7`>45WPwj6dJ@-|% z5?1E#4*Y6K2u?~DE1asXDtsk69^8|jLc2WIYp4B|C&$`9@Uzl;l@V1Q%80TcwSZOK zCF`+Rjtb?e)UGFL4``nplb&XKUF3G%*%9M7+|I$_m>#0+R_XJ4f60Ko+%%`1<CmQU z^SKWbhNiSfXCto(hq&2Q=X8&C)3d)coKUFg{B4RzyM3J!k=ck7o-%j$ZP=W5{u<9! zvyAA|K7~(giT2)3=CeJA#V`C(;E+V6n|-Z#X>reic$j}%uK#`;de20`qZ=CAZR!^z zg#PB-(mOGDU`hOsMm9S;6OO;KHEkWYb-m_$EYy2Ml3+-<6kO-66hy8nE#7&v1jo|+ zsmILmYRAGvoIs8fZDOJ_t^E&<bTf;k9UAHOy2hO3?_*=B+`Kl(7D|@AR7%%}QmSe0 zO)YOz-N_F^(jmr=pGfs_d$!X`-`;L1e&H&2dR>*D(moH>hKH37M=r;?(u#zGrCh>N z<Rzq>9YiMjxAry8jf7V94_v@(BX3`~Lu$C3e%^7%vpnxAc9)rxLnIYFjU&4!{=rUl z$DZ_UAO)3mwVgUS$Dc8k`gl5IF4f3@8urFvp2(RzliOb%%6i6kd%&DW^dnrnSA&>w zF<!D1+1zim>-V88AM>6Ld{T>$=Zk6+zaSz;8g0nmP%Sd%+STJfE%CP5s{*4St126e z_pUo3ABIhd45VasJsjJ=c_K;2tdw1#Pu15dsN~N#uN|LfvlO50*R3=^RN3J~Xr=xW z*f)05M=kp*fgb(K<eZb%Q00w#9?5RI71&9sM?w`BV~M}ECgpTQYl(~#?*yoaV=XUm zd0EkYn)z!463JMfbw<Wr_$EGTskWRjU1&!5k)JxfDDTTX&4<yUS&90FMDj^j&&Iip zifyt7p2uvWf7RI*Ls25X!ueW7m#9$hE*`kuBX<1Io6IL!J^>?p$7gK3KAhsKnzqFC zMbZVt%3oLg-a!{rzVm68n)=zkMCg$!e<`;DzmiN(m}vh=TyRRg<=MlFAr84F#pa&j zuQ67CpTm6rb@r>PoV1Mi3#x^l=GN0;E`p)1@}Y?W9~I5Y%QPF?dCUC86W`QE4^R`> zBJLsrbBR3ovU!>A`Smtmlw3%^c<31U=@@9GDPYvBM|9i^4uqa7>QXv!wy1<72BT_n zDQ8;5!~gO^+?BSuj!PD5A0MO{u!Sd=6SiLDeRqCZugMN;FFKmzexu+T`SXyfo<YK8 zykt0s^wHc?sUIv`<X%?YVv8KSdPv2TdgoEsP1B|D(%ZIGoBC=)<@%aK>C#gIzsmHf zJtc?h2M5ky^LXd(yHCqHM>chO?u9^<sZ8p6<1;hS5{V-mncGvn@Sh81e;Xc~P8e&- z`Zz5?qt8@Y>|s&W88|K#!bK=cYM306HN1HLVpI}dVsJvjrh1=l$g6=mUl*3q%2-`u zJ|#wecV66}(h^Itw&6qL8v}YmskzsWNDEbvEcU9J#a^O+#M~16+Oy|*=MoEig@Ne1 z*)3P4nF94@n$NY|4+B+c;t}r}X{vT}CB8;xgZ@y;L5*Xj<S(L!`kb6*aZ~U1ZX{+B zUyqfXO`S2vOS%so%*rC<I$HC`6%S^#TlL232o!c6u1{h8mVwdwW)WDwLyrG0_Ag0| z9W;rV<ma&+X`B5P`;8k*XE*8{`<R)xeUkW)BVS<QeSodmH^Q?O57({7pgtjKZVJn9 zm7~qa=blBaymihH69b2t#rt#SnZ=K%iie?g>(e+aGk*Mv$a?0sTZITKb9*UL3u<Q` zYr!$g1%p08`XbV4?96ihkS0ief|3FGqkH-;KQq42V*--X+`6H*ad^SXOZJR(1l?JH z<UJ+tq4sE~J!Ib|e<#G}y!{fAUoWzR`2ERo%<&_<HAZ+W%LdjTuk53RjgYDup<TR~ z4|~AC!B`sE?os{4${3W7fl)sT9s$q>6uEq4L?MJ)N$Rpbw9FAI0Huo<2Y6(8`QXJY z?nwc^DS@7l?$r#g6#@ojR4+3;16_{#y8kzkAK(Na<|KN7&C47GY6&4Uh_Dq$Nn;TW zE<sRJNpKUB<51D1pUS}9s3=yJW&FR^V3K53GB$ZAC>xM_!AA~K62Zn$;>1Z%S?v~E z*71HPkhA?DU#bwv4TlUJ5J$QjMVXC2<$54I=X!<_e4PRpQ)I|uBO100o*~?aWk(Xo z0&BcDdc5V$72ts`2L_`KX<guD5Yr?j*JhN=$Y`G+iXd4~JU!h5e{5-12MN`WfYeh& z09i?JHQ`4s32dVPaQ(J4!vm@uyXL=Z|Nj&6t5Z0u$)LWgmu5~O^o%Lw4J(O(--i`k zlcAq~#-snmLnH|U&`&CB2+$2ph#+LO91vk`O7-f{-ui3{5OPRl9W=h6O(didbC97C z>?!{vMI-0v&QgFP0Y35&^^h}S<)#36kDi=<E++sCBK~<4N0m4ulo~Nh<{m={C}L&m zgH7)8fllP3<R4Ric_O>{9)l(`V<l+mK*L7J$an{7W%M!x{>>t+#ZgiSwrL|$(98|V zzPF(y&ZCG+zC5B!ms#m!lvt5#shJ4;ISVRV>8^MKzKf%{J&}M4A`;dv&Ws<I=lR<N z3pVJ0qXFUPj{*G>h<=9zGk&+Q*HQyJ26G;qdSLwtK8Oa*P;q$*qP|G@2o<&hy}jV} z_I613BV?#q8OiPVc`icXZ(Tb#4FR|xP`?|3cZ4!RRX-_(_|tIA?#LZ~dH4zl^(GG} z&;oIIk2BmKY~o|aC;K|JJpkd}XT@Mh5Pp=(NRLGj-)b9<;eI!+0-qZAR@)efLZa@- z39X%-j4o7ZLA$B^0wNQl0}XhFbfF2AUW#QzmP=7U$jvyYm#bS*fNlZ+$RSIuT+CrO z6ZAULL8^$rt*uZP<98SeW157S@h0salUD#f8Kerv_oXw!-VCQHTyfZz3{Hz;U}@R~ z>3?<~VQ<|FIcm)qj^Q~V7hBfGnbMAiaVno-W*8oPo7habML}<PqM<-$0ty+u#~TJO z9BAeY+J#0ZK=(xg-*g~Sdl4Y%A^L4_B2Uv$4+Kk)WSTy6Qd9-K&aeZ^<seW^NYQvH zBZ-YgYsP}{ll61Dz<3i_FW|)NE@yD?DPpJt%ytM%wFlA52Xw)fhU|1!AyPH6Le}Ou RFhkJ!r-9)`2iY^U^*?Fdp78(x diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 6f15c32..0cf2d0f 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Mon Apr 03 17:55:37 CEST 2017 +#Sun Jul 02 11:22:52 CEST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-3.4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.0-bin.zip diff --git a/gradlew b/gradlew index 27309d9..4453cce 100755 --- a/gradlew +++ b/gradlew @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/usr/bin/env sh ############################################################################## ## @@ -154,11 +154,19 @@ if $cygwin ; then esac fi -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") +# Escape application args +save ( ) { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " } -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" +APP_ARGS=$(save "$@") -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index f6d5974..e95643d 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -49,7 +49,6 @@ goto fail @rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args :win9xME_args @rem Slurp the command line arguments. @@ -60,11 +59,6 @@ set _SKIP=2 if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ :execute @rem Setup the command line diff --git a/networking/build.gradle b/networking/build.gradle index 212ff46..489729c 100644 --- a/networking/build.gradle +++ b/networking/build.gradle @@ -12,9 +12,9 @@ uploadArchives { dependencies { compile project(':core') - testCompile 'junit:junit:4.12' - testCompile 'org.slf4j:slf4j-simple:1.7.25' - testCompile 'com.nhaarman:mockito-kotlin:1.5.0' + testCompile 'junit:junit' + testCompile 'org.slf4j:slf4j-simple' + testCompile 'com.nhaarman:mockito-kotlin' testCompile project(path: ':core', configuration: 'testArtifacts') testCompile project(':cryptography-bc') } diff --git a/repositories/build.gradle b/repositories/build.gradle index dfeb9ad..ce3b83a 100644 --- a/repositories/build.gradle +++ b/repositories/build.gradle @@ -12,10 +12,10 @@ uploadArchives { dependencies { compile project(':core') - compile 'org.flywaydb:flyway-core:4.2.0' - testCompile 'junit:junit:4.12' - testCompile 'com.h2database:h2:1.4.196' - testCompile 'com.nhaarman:mockito-kotlin:1.5.0' + compile 'org.flywaydb:flyway-core' + testCompile 'junit:junit' + testCompile 'com.h2database:h2' + testCompile 'com.nhaarman:mockito-kotlin' testCompile project(path: ':core', configuration: 'testArtifacts') testCompile project(':cryptography-bc') } diff --git a/wif/build.gradle b/wif/build.gradle index 0f2bb35..aa7cbb9 100644 --- a/wif/build.gradle +++ b/wif/build.gradle @@ -12,8 +12,8 @@ uploadArchives { dependencies { compile project(':core') - compile 'org.ini4j:ini4j:0.5.4' - testCompile 'junit:junit:4.12' - testCompile 'com.nhaarman:mockito-kotlin:1.5.0' + compile 'org.ini4j:ini4j' + testCompile 'junit:junit' + testCompile 'com.nhaarman:mockito-kotlin' testCompile project(':cryptography-bc') } From d3a06e7639dc96ebe66a845b60f908ed92031cf9 Mon Sep 17 00:00:00 2001 From: Christian Basler <chrigu.meyer@gmail.com> Date: Wed, 5 Jul 2017 00:37:45 +0200 Subject: [PATCH 10/10] Added version to user agent info, and other minor fixes --- build.gradle | 8 ++++++ core/build.gradle | 26 ++++++++++++++++--- .../ch/dissem/bitmessage/BitmessageContext.kt | 11 ++++++++ .../bitmessage/DefaultMessageListener.kt | 2 +- .../ch/dissem/bitmessage/InternalContext.kt | 2 ++ .../ch/dissem/bitmessage/entity/Version.kt | 2 +- .../ch/dissem/bitmessage/utils/Property.kt | 6 ++++- .../bitmessage/BitmessageContextTest.kt | 11 +++++++- .../ch/dissem/bitmessage/utils/TestUtils.kt | 1 + .../java/ch/dissem/bitmessage/demo/Main.java | 2 ++ .../nio/NetworkConnectionInitializer.kt | 2 +- .../networking/nio/NioNetworkHandler.kt | 10 +++---- 12 files changed, 69 insertions(+), 14 deletions(-) diff --git a/build.gradle b/build.gradle index 4e228e0..2169e2f 100644 --- a/build.gradle +++ b/build.gradle @@ -54,6 +54,14 @@ subprojects { archives javadocJar, sourcesJar } + jar { + manifest { + attributes 'Implementation-Title': "Jabit ${project.name.capitalize()}", + 'Implementation-Version': version + } + baseName "jabit-${project.name}" + } + signing { required { isRelease && project.getProperties().get("signing.keyId")?.length() > 0 } sign configurations.archives diff --git a/core/build.gradle b/core/build.gradle index eb8118c..6af7a84 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -25,9 +25,27 @@ artifacts { dependencies { compile 'org.slf4j:slf4j-api' - compile 'ch.dissem.msgpack:msgpack' - testCompile 'junit:junit' - testCompile 'org.hamcrest:hamcrest-library' - testCompile 'com.nhaarman:mockito-kotlin' + compile 'ch.dissem.msgpack:msgpack:1.0.0' + testCompile 'junit:junit:4.12' + testCompile 'org.hamcrest:hamcrest-library:1.3' + testCompile 'com.nhaarman:mockito-kotlin:1.5.0' testCompile project(':cryptography-bc') } + +def generatedResources = "${project.buildDir}/generated-resources/main" + +sourceSets { + main { + output.dir(generatedResources, builtBy: 'generateVersionInfo') + } +} +task('generateVersionInfo') { + doLast { + def dir = new File(generatedResources) + if (!dir.exists()) { + dir.mkdirs() + } + def file = new File(generatedResources, "version") + file.write(project.version.toString()) + } +} diff --git a/core/src/main/kotlin/ch/dissem/bitmessage/BitmessageContext.kt b/core/src/main/kotlin/ch/dissem/bitmessage/BitmessageContext.kt index ff3a58d..2f09636 100644 --- a/core/src/main/kotlin/ch/dissem/bitmessage/BitmessageContext.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/BitmessageContext.kt @@ -75,6 +75,7 @@ class BitmessageContext( }, listener: Listener, labeler: Labeler = DefaultLabeler(), + userAgent: String? = null, port: Int = 8444, connectionTTL: Long = 30 * MINUTE, connectionLimit: Int = 150, @@ -99,6 +100,7 @@ class BitmessageContext( }, builder.listener, builder.labeler ?: DefaultLabeler(), + builder.userAgent, builder.port, builder.connectionTTL, builder.connectionLimit, @@ -333,6 +335,7 @@ class BitmessageContext( fun status(): Property { return Property("status", + Property("user agent", internals.userAgent), internals.networkHandler.getNetworkStatus(), Property("unacknowledged", internals.messageRepository.findMessagesToResend().size) ) @@ -361,6 +364,7 @@ class BitmessageContext( internal var cryptography by Delegates.notNull<Cryptography>() internal var customCommandHandler: CustomCommandHandler? = null internal var labeler: Labeler? = null + internal var userAgent: String? = null internal var listener by Delegates.notNull<Listener>() internal var connectionLimit = 150 internal var connectionTTL = 30 * MINUTE @@ -480,6 +484,7 @@ class BitmessageContext( customCommandHandler, listener, labeler, + userAgent?.let { "/$it/Jabit:$version/" } ?: "/Jabit:$version/", port, connectionTTL, connectionLimit @@ -494,5 +499,11 @@ class BitmessageContext( companion object { @JvmField val CURRENT_VERSION = 3 private val LOG = LoggerFactory.getLogger(BitmessageContext::class.java) + + val version: String by lazy { + BitmessageContext::class.java.getResource("/version")?.readText() ?: "local build" + } + @JvmStatic get + } } diff --git a/core/src/main/kotlin/ch/dissem/bitmessage/DefaultMessageListener.kt b/core/src/main/kotlin/ch/dissem/bitmessage/DefaultMessageListener.kt index 826c9cd..ac30587 100644 --- a/core/src/main/kotlin/ch/dissem/bitmessage/DefaultMessageListener.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/DefaultMessageListener.kt @@ -29,7 +29,7 @@ import ch.dissem.bitmessage.utils.Strings.hex import org.slf4j.LoggerFactory import java.util.* -internal open class DefaultMessageListener( +open class DefaultMessageListener( private val labeler: Labeler, private val listener: BitmessageContext.Listener ) : NetworkHandler.MessageListener, InternalContext.ContextHolder { diff --git a/core/src/main/kotlin/ch/dissem/bitmessage/InternalContext.kt b/core/src/main/kotlin/ch/dissem/bitmessage/InternalContext.kt index a1c7f16..42ed782 100644 --- a/core/src/main/kotlin/ch/dissem/bitmessage/InternalContext.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/InternalContext.kt @@ -51,6 +51,8 @@ class InternalContext( listener: BitmessageContext.Listener, val labeler: Labeler, + val userAgent: String, + val port: Int, val connectionTTL: Long, val connectionLimit: Int diff --git a/core/src/main/kotlin/ch/dissem/bitmessage/entity/Version.kt b/core/src/main/kotlin/ch/dissem/bitmessage/entity/Version.kt index 07d6d47..5f65bcc 100644 --- a/core/src/main/kotlin/ch/dissem/bitmessage/entity/Version.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/entity/Version.kt @@ -62,7 +62,7 @@ class Version constructor( /** * User Agent (0x00 if string is 0 bytes long). Sending nodes must not include a user_agent longer than 5000 bytes. */ - val userAgent: String = "/Jabit:0.0.1/", + val userAgent: String, /** * The stream numbers that the emitting node is interested in. Sending nodes must not include more than 160000 diff --git a/core/src/main/kotlin/ch/dissem/bitmessage/utils/Property.kt b/core/src/main/kotlin/ch/dissem/bitmessage/utils/Property.kt index 323c5cb..91fbb24 100644 --- a/core/src/main/kotlin/ch/dissem/bitmessage/utils/Property.kt +++ b/core/src/main/kotlin/ch/dissem/bitmessage/utils/Property.kt @@ -25,7 +25,11 @@ package ch.dissem.bitmessage.utils * If you need a real JSON representation, please add a method `toJson()`. * */ -class Property private constructor(val name: String, val value: Any? = null, val properties: Array<Property> = emptyArray()) { +class Property private constructor( + val name: String, + val value: Any? = null, + val properties: Array<Property> +) { constructor(name: String, value: Any) : this(name = name, value = value, properties = emptyArray()) constructor(name: String, vararg properties: Property) : this(name, null, arrayOf(*properties)) diff --git a/core/src/test/kotlin/ch/dissem/bitmessage/BitmessageContextTest.kt b/core/src/test/kotlin/ch/dissem/bitmessage/BitmessageContextTest.kt index 4814ac7..24e8ae3 100644 --- a/core/src/test/kotlin/ch/dissem/bitmessage/BitmessageContextTest.kt +++ b/core/src/test/kotlin/ch/dissem/bitmessage/BitmessageContextTest.kt @@ -28,6 +28,7 @@ import ch.dissem.bitmessage.ports.DefaultLabeler import ch.dissem.bitmessage.ports.ProofOfWorkEngine import ch.dissem.bitmessage.ports.ProofOfWorkRepository import ch.dissem.bitmessage.testutils.TestInventory +import ch.dissem.bitmessage.utils.Property import ch.dissem.bitmessage.utils.Singleton.cryptography import ch.dissem.bitmessage.utils.Strings.hex import ch.dissem.bitmessage.utils.TTL @@ -98,7 +99,9 @@ class BitmessageContextTest { .inventory(inventory) .listener(listener) .messageRepo(mock()) - .networkHandler(mock()) + .networkHandler(mock { + on { getNetworkStatus() } doReturn Property("test", "mocked") + }) .nodeRegistry(mock()) .labeler(spy(DefaultLabeler())) .powRepo(testPowRepo) @@ -318,4 +321,10 @@ class BitmessageContextTest { ctx.resendUnacknowledgedMessages() verify(ctx.labeler, timeout(1000).times(1)).markAsSent(eq(plaintext)) } + + @Test + fun `ensure status contains user agent`() { + val userAgent = ctx.status().getProperty("user agent")?.value.toString() + assertThat(userAgent, `is`("/Jabit:${BitmessageContext.version}/")) + } } diff --git a/core/src/test/kotlin/ch/dissem/bitmessage/utils/TestUtils.kt b/core/src/test/kotlin/ch/dissem/bitmessage/utils/TestUtils.kt index 6f72ba3..0d92726 100644 --- a/core/src/test/kotlin/ch/dissem/bitmessage/utils/TestUtils.kt +++ b/core/src/test/kotlin/ch/dissem/bitmessage/utils/TestUtils.kt @@ -125,6 +125,7 @@ object TestUtils { customCommandHandler, listener, labeler, + "/Jabit:TEST/", port, connectionTTL, connectionLimit diff --git a/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java b/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java index 059af66..40ee063 100644 --- a/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java +++ b/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java @@ -46,6 +46,8 @@ public class Main { if (System.getProperty("org.slf4j.simpleLogger.logFile") == null) System.setProperty("org.slf4j.simpleLogger.logFile", "./jabit.log"); + System.out.println("Version: " + BitmessageContext.getVersion()); + CmdLineOptions options = new CmdLineOptions(); CmdLineParser parser = new CmdLineParser(options); try { diff --git a/networking/src/main/kotlin/ch/dissem/bitmessage/networking/nio/NetworkConnectionInitializer.kt b/networking/src/main/kotlin/ch/dissem/bitmessage/networking/nio/NetworkConnectionInitializer.kt index 4fd6d7c..df915cd 100644 --- a/networking/src/main/kotlin/ch/dissem/bitmessage/networking/nio/NetworkConnectionInitializer.kt +++ b/networking/src/main/kotlin/ch/dissem/bitmessage/networking/nio/NetworkConnectionInitializer.kt @@ -41,7 +41,7 @@ class NetworkConnectionInitializer( fun start() { if (mode == Connection.Mode.CLIENT || mode == Connection.Mode.SYNC) { - send(Version(nonce = ctx.clientNonce, addrFrom = NetworkAddress.ANY, addrRecv = node)) + send(Version(nonce = ctx.clientNonce, addrFrom = NetworkAddress.ANY, addrRecv = node, userAgent = ctx.userAgent)) } } diff --git a/networking/src/main/kotlin/ch/dissem/bitmessage/networking/nio/NioNetworkHandler.kt b/networking/src/main/kotlin/ch/dissem/bitmessage/networking/nio/NioNetworkHandler.kt index 171b4bb..3a0464a 100644 --- a/networking/src/main/kotlin/ch/dissem/bitmessage/networking/nio/NioNetworkHandler.kt +++ b/networking/src/main/kotlin/ch/dissem/bitmessage/networking/nio/NioNetworkHandler.kt @@ -408,18 +408,18 @@ class NioNetworkHandler : NetworkHandler, InternalContext.ContextHolder { val incomingConnections = TreeMap<Long, Int>() val outgoingConnections = TreeMap<Long, Int>() - for (connection in connections.keys) { - if (connection.state == Connection.State.ACTIVE) { - for (stream in connection.streams) { + connections.keys + .filter { it.state == Connection.State.ACTIVE } + .forEach { + for (stream in it.streams) { streams.add(stream) - if (connection.mode == SERVER) { + if (it.mode == SERVER) { DebugUtils.inc(incomingConnections, stream) } else { DebugUtils.inc(outgoingConnections, stream) } } } - } val streamProperties = mutableListOf<Property>() for (stream in streams) { val incoming = incomingConnections[stream] ?: 0