├── ILP Java - Phase 1.png ├── ILP Java - Phase 2.png ├── src ├── main │ └── java │ │ └── org │ │ └── interledger │ │ ├── package-info.java │ │ ├── ilqp │ │ ├── QuoteErrorResponse.java │ │ ├── QuoteSelectionStrategy.java │ │ ├── QuoteResponse.java │ │ ├── InterledgerQuotingException.java │ │ ├── package-info.java │ │ ├── QuoteRequest.java │ │ ├── QuoteService.java │ │ ├── LiquidityPoint.java │ │ ├── QuoteLiquidityRequest.java │ │ ├── LiquidityCurve.java │ │ ├── QuoteByDestinationAmountResponse.java │ │ └── QuoteBySourceAmountResponse.java │ │ ├── codecs │ │ ├── package-info.java │ │ ├── PskMessageCodec.java │ │ ├── ConditionCodec.java │ │ ├── FulfillmentCodec.java │ │ ├── InterledgerAddressCodec.java │ │ ├── InterledgerPacketTypeCodec.java │ │ ├── InterledgerPaymentRequestCodec.java │ │ ├── packettypes │ │ │ ├── PaymentPacketType.java │ │ │ ├── InterledgerErrorPacketType.java │ │ │ ├── QuoteLiquidityRequestPacketType.java │ │ │ ├── QuoteLiquidityResponsePacketType.java │ │ │ ├── QuoteBySourceAmountRequestPacketType.java │ │ │ ├── QuoteBySourceAmountResponsePacketType.java │ │ │ ├── QuoteByDestinationAmountRequestPacketType.java │ │ │ ├── QuoteByDestinationAmountResponsePacketType.java │ │ │ └── InterledgerPacketType.java │ │ ├── InterledgerPaymentCodec.java │ │ ├── CodecException.java │ │ ├── InterledgerPacketCodec.java │ │ ├── QuoteLiquidityRequestCodec.java │ │ ├── InterledgerProtocolErrorCodec.java │ │ ├── QuoteLiquidityResponseCodec.java │ │ ├── QuoteBySourceAmountRequestCodec.java │ │ ├── QuoteBySourceAmountResponseCodec.java │ │ ├── QuoteByDestinationAmountRequestCodec.java │ │ ├── QuoteByDestinationAmountResponseCodec.java │ │ ├── oer │ │ │ ├── ilp │ │ │ │ ├── InterledgerAddressOerCodec.java │ │ │ │ ├── ConditionOerCodec.java │ │ │ │ ├── FulfillmentOerCodec.java │ │ │ │ ├── InterledgerPacketTypeOerCodec.java │ │ │ │ └── InterledgerPaymentOerCodec.java │ │ │ ├── ipr │ │ │ │ └── InterledgerPaymentRequestOerCodec.java │ │ │ ├── OerUint8Codec.java │ │ │ ├── ilqp │ │ │ │ ├── QuoteLiquidityRequestOerCodec.java │ │ │ │ ├── QuoteBySourceAmountResponseOerCodec.java │ │ │ │ ├── QuoteByDestinationAmountResponseOerCodec.java │ │ │ │ ├── QuoteBySourceAmountRequestOerCodec.java │ │ │ │ ├── QuoteByDestinationAmountRequestOerCodec.java │ │ │ │ └── QuoteLiquidityResponseOerCodec.java │ │ │ ├── OerSequenceOfAddressCodec.java │ │ │ ├── OerOctetStringCodec.java │ │ │ ├── OerUint256Codec.java │ │ │ ├── OerUint32Codec.java │ │ │ ├── OerUint64Codec.java │ │ │ └── OerIA5StringCodec.java │ │ ├── AbstractCodec.java │ │ └── Codec.java │ │ ├── ilp │ │ └── package-info.java │ │ ├── ipr │ │ ├── package-info.java │ │ └── InterledgerPaymentRequest.java │ │ ├── InterledgerProtocolException.java │ │ ├── psk │ │ ├── PskEncryptionType.java │ │ ├── PskNonceHeader.java │ │ └── PskEncryptionHeader.java │ │ └── InterledgerRuntimeException.java └── test │ └── java │ └── org │ └── interledger │ ├── ConditionTest.java │ ├── mocks │ ├── DeterministicSecureRandomAlgorithm.java │ └── DeterministicSecureRandomProvider.java │ ├── psk │ ├── UnEncryptedMessageWriterTest.java │ ├── Aes256BitKeysizeTest.java │ ├── PskMessageBuilderTest.java │ └── UnencryptedMessageReaderTest.java │ ├── codecs │ ├── oer │ │ ├── OerLengthPrefixCodecTestBadLength.java │ │ ├── ilp │ │ │ ├── ConditionOerCodecTests.java │ │ │ ├── InterledgerAddressOerCodecTests.java │ │ │ └── InterledgerPaymentOerCodecTests.java │ │ ├── OerIA5StringCodecTestBadLength.java │ │ ├── OerGeneralizeTimeCodecTestBadFormat.java │ │ ├── OerUint256CodecTest.java │ │ ├── ilqp │ │ │ └── IlqpCodecTests.java │ │ ├── OerUint8CodecTest.java │ │ └── OerUint32CodecTest.java │ ├── CodecContextFactoryTest.java │ └── packets │ │ └── PaymentPacketTypeTests.java │ ├── FulfillmentTest.java │ ├── ilqp │ ├── LiquidityPointTest.java │ ├── QuoteBySourceAmountResponseTest.java │ └── QuoteByDestinationAmountResponseTest.java │ ├── ipr │ └── InterledgerPaymentRequestTest.java │ ├── ilp │ └── InterledgerPaymentTest.java │ └── InterledgerAddressSchemeTest.java ├── .gitignore ├── circle.yml └── architecture.md /ILP Java - Phase 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/interledger-deprecated/java-ilp-core/HEAD/ILP Java - Phase 1.png -------------------------------------------------------------------------------- /ILP Java - Phase 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/interledger-deprecated/java-ilp-core/HEAD/ILP Java - Phase 2.png -------------------------------------------------------------------------------- /src/main/java/org/interledger/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * The main package for top-level Interledger objects. 3 | */ 4 | package org.interledger; 5 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/ilqp/QuoteErrorResponse.java: -------------------------------------------------------------------------------- 1 | package org.interledger.ilqp; 2 | 3 | public interface QuoteErrorResponse { 4 | 5 | String getId(); 6 | 7 | String getMessage(); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /bin/ 2 | /.gradle/ 3 | /build/ 4 | /.nb-gradle/ 5 | /target/ 6 | .classpath 7 | .project 8 | .settings/* 9 | *~ 10 | 11 | # OSX 12 | **/.DS_Store 13 | /*.iml 14 | /.idea 15 | /classes 16 | /out 17 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/ilqp/QuoteSelectionStrategy.java: -------------------------------------------------------------------------------- 1 | package org.interledger.ilqp; 2 | 3 | import java.util.Set; 4 | import java.util.function.Function; 5 | 6 | @FunctionalInterface 7 | public interface QuoteSelectionStrategy 8 | extends Function, QuoteResponse> { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/codecs/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This package holds all codecs for the Interledger project, including top-level interfaces and 3 | * abstract classes that can be utilized to create more specific codecs, such as for ASN.1 OER, 4 | * JSON, Protobuf, and more. 5 | */ 6 | package org.interledger.codecs; 7 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/codecs/PskMessageCodec.java: -------------------------------------------------------------------------------- 1 | package org.interledger.codecs; 2 | 3 | import org.interledger.psk.PskMessage; 4 | 5 | /** 6 | * An implementation of {@link Codec} that reads and writes instances of {@link PskMessage}. 7 | */ 8 | public interface PskMessageCodec extends Codec { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/codecs/ConditionCodec.java: -------------------------------------------------------------------------------- 1 | package org.interledger.codecs; 2 | 3 | import org.interledger.cryptoconditions.Condition; 4 | 5 | /** 6 | * An implementation of {@link Codec} that reads and writes instances of {@link Condition}. 7 | */ 8 | public interface ConditionCodec extends Codec { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/codecs/FulfillmentCodec.java: -------------------------------------------------------------------------------- 1 | package org.interledger.codecs; 2 | 3 | import org.interledger.cryptoconditions.Fulfillment; 4 | 5 | /** 6 | * An implementation of {@link Codec} that reads and writes instances of {@link Fulfillment}. 7 | */ 8 | public interface FulfillmentCodec extends Codec { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/codecs/InterledgerAddressCodec.java: -------------------------------------------------------------------------------- 1 | package org.interledger.codecs; 2 | 3 | import org.interledger.InterledgerAddress; 4 | 5 | /** 6 | * An implementation of {@link Codec} that reads and writes instances of {@link InterledgerAddress}. 7 | */ 8 | public interface InterledgerAddressCodec extends Codec { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/codecs/InterledgerPacketTypeCodec.java: -------------------------------------------------------------------------------- 1 | package org.interledger.codecs; 2 | 3 | import org.interledger.codecs.packettypes.InterledgerPacketType; 4 | 5 | /** 6 | * An implementation of {@link Codec} that reads and writes instances of 7 | * {@link InterledgerPacketType}. 8 | */ 9 | public interface InterledgerPacketTypeCodec extends Codec { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/codecs/InterledgerPaymentRequestCodec.java: -------------------------------------------------------------------------------- 1 | package org.interledger.codecs; 2 | 3 | import org.interledger.ipr.InterledgerPaymentRequest; 4 | 5 | /** 6 | * An implementation of {@link Codec} that reads and writes instances of 7 | * {@link InterledgerPaymentRequest}. 8 | */ 9 | public interface InterledgerPaymentRequestCodec extends Codec { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/ilqp/QuoteResponse.java: -------------------------------------------------------------------------------- 1 | package org.interledger.ilqp; 2 | 3 | import org.interledger.InterledgerPacket; 4 | 5 | import java.time.Duration; 6 | 7 | /** 8 | * A parent interface for all quote responses in ILQP. 9 | */ 10 | public interface QuoteResponse extends InterledgerPacket { 11 | 12 | /** 13 | * How long the sender should put money on hold. 14 | * 15 | * @return An instance of {@link Duration}. 16 | */ 17 | Duration getSourceHoldDuration(); 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/test/java/org/interledger/ConditionTest.java: -------------------------------------------------------------------------------- 1 | package org.interledger; 2 | 3 | import static org.junit.Assert.assertArrayEquals; 4 | 5 | import org.interledger.cryptoconditions.PreimageSha256Condition; 6 | 7 | import org.junit.Test; 8 | 9 | public class ConditionTest { 10 | 11 | @Test 12 | public final void test() { 13 | 14 | byte[] hash = new byte[32]; 15 | assertArrayEquals("Hash is invalid.", 16 | hash, 17 | new PreimageSha256Condition(32, hash).getFingerprint()); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/ilqp/InterledgerQuotingException.java: -------------------------------------------------------------------------------- 1 | package org.interledger.ilqp; 2 | 3 | import org.interledger.InterledgerRuntimeException; 4 | 5 | public class InterledgerQuotingException extends InterledgerRuntimeException { 6 | 7 | private static final long serialVersionUID = 6272999499660262013L; 8 | 9 | public InterledgerQuotingException(String message) { 10 | super(message); 11 | } 12 | 13 | public InterledgerQuotingException(String message, Throwable cause) { 14 | super(message, cause); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/ilp/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This package contains classes related to the core Interledger Protocol (ILP) which is the 3 | * foundational protocol in the overall Interledger suite of protocols. For more details about the 4 | * ILP core protocol, reference 5 | * IL-RFC-3. 6 | * 7 | * @see "https://github.com/interledger/rfcs/blob/master/0003-interledger-protocol" 8 | */ 9 | package org.interledger.ilp; 10 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/ilqp/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This package contains classes related to the Interledger Quoting Protocol (ILQP) which is one 3 | * protocol in the overall Interledger suite of protocols. For more details about the ILQP, 4 | * reference 5 | * IL-RFC-8. 6 | * 7 | * @see "https://github.com/interledger/rfcs/blob/master/0008-interledger-quoting-protocol" 8 | */ 9 | package org.interledger.ilqp; 10 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/ipr/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This package contains classes related to the Interledger Payment Request Protocol (IPR) which is 3 | * one protocol in the overall Interledger suite of protocols. For more details about the IPR, 4 | * reference 5 | * IL-RFC-11. 6 | * 7 | * @see "https://github.com/interledger/rfcs/blob/master/0011-interledger-payment-request" 8 | */ 9 | package org.interledger.ipr; 10 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/codecs/packettypes/PaymentPacketType.java: -------------------------------------------------------------------------------- 1 | package org.interledger.codecs.packettypes; 2 | 3 | import org.interledger.codecs.packettypes.InterledgerPacketType.AbstractInterledgerPacketType; 4 | 5 | import java.net.URI; 6 | 7 | /** 8 | * An implementation of {@link InterledgerPacketType} for ILP Payment packets. 9 | */ 10 | public class PaymentPacketType extends AbstractInterledgerPacketType 11 | implements InterledgerPacketType { 12 | 13 | /** 14 | * No-args Constructor. 15 | */ 16 | public PaymentPacketType() { 17 | super(ILP_PAYMENT_TYPE, URI.create("https://interledger.org/payment_packet")); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/codecs/InterledgerPaymentCodec.java: -------------------------------------------------------------------------------- 1 | package org.interledger.codecs; 2 | 3 | import org.interledger.codecs.packettypes.InterledgerPacketType; 4 | import org.interledger.codecs.packettypes.PaymentPacketType; 5 | import org.interledger.ilp.InterledgerPayment; 6 | 7 | /** 8 | * An implementation of {@link Codec} that reads and writes instances of {@link InterledgerPayment}. 9 | */ 10 | public interface InterledgerPaymentCodec extends InterledgerPacketCodec { 11 | 12 | InterledgerPacketType TYPE = new PaymentPacketType(); 13 | 14 | @Override 15 | default InterledgerPacketType getTypeId() { 16 | return TYPE; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/codecs/packettypes/InterledgerErrorPacketType.java: -------------------------------------------------------------------------------- 1 | package org.interledger.codecs.packettypes; 2 | 3 | import org.interledger.codecs.packettypes.InterledgerPacketType.AbstractInterledgerPacketType; 4 | 5 | import java.net.URI; 6 | 7 | /** 8 | * An implementation of {@link InterledgerPacketType} for ILP Error packets. 9 | */ 10 | public class InterledgerErrorPacketType extends AbstractInterledgerPacketType 11 | implements InterledgerPacketType { 12 | 13 | /** 14 | * No-args Constructor. 15 | */ 16 | public InterledgerErrorPacketType() { 17 | super(INTERLEDGER_PROTOCOL_ERROR, URI.create("https://interledger.org/protocol_error")); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/codecs/CodecException.java: -------------------------------------------------------------------------------- 1 | package org.interledger.codecs; 2 | 3 | import org.interledger.InterledgerRuntimeException; 4 | 5 | /** 6 | * An extension of {@link RuntimeException} to represent errors that occur during encoding or 7 | * decoding. 8 | */ 9 | public class CodecException extends InterledgerRuntimeException { 10 | 11 | private static final long serialVersionUID = 6647367875148981736L; 12 | 13 | public CodecException(String message) { 14 | super(message); 15 | } 16 | 17 | public CodecException(String message, Throwable cause) { 18 | super(message, cause); 19 | } 20 | 21 | public CodecException(Throwable cause) { 22 | super(cause); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/codecs/InterledgerPacketCodec.java: -------------------------------------------------------------------------------- 1 | package org.interledger.codecs; 2 | 3 | import org.interledger.InterledgerPacket; 4 | import org.interledger.codecs.packettypes.InterledgerPacketType; 5 | 6 | /** 7 | * An implementation of {@link Codec} that reads and writes instances of {@link InterledgerPacket} 8 | * having a discrete {@link InterledgerPacketType} and data payload. 9 | */ 10 | public interface InterledgerPacketCodec extends Codec { 11 | 12 | /** 13 | * Accessor for the {@link InterledgerPacketType} of this {@link Codec}. 14 | * 15 | * @return The {@link InterledgerPacketType} instance. 16 | */ 17 | InterledgerPacketType getTypeId(); 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/codecs/QuoteLiquidityRequestCodec.java: -------------------------------------------------------------------------------- 1 | package org.interledger.codecs; 2 | 3 | import org.interledger.codecs.packettypes.InterledgerPacketType; 4 | import org.interledger.codecs.packettypes.QuoteLiquidityRequestPacketType; 5 | import org.interledger.ilqp.QuoteLiquidityRequest; 6 | 7 | /** 8 | * An implementation of {@link Codec} that reads and writes instances of 9 | * {@link QuoteLiquidityRequest}. 10 | */ 11 | public interface QuoteLiquidityRequestCodec 12 | extends InterledgerPacketCodec { 13 | 14 | InterledgerPacketType TYPE = new QuoteLiquidityRequestPacketType(); 15 | 16 | @Override 17 | default InterledgerPacketType getTypeId() { 18 | return TYPE; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/codecs/InterledgerProtocolErrorCodec.java: -------------------------------------------------------------------------------- 1 | package org.interledger.codecs; 2 | 3 | import org.interledger.codecs.packettypes.InterledgerErrorPacketType; 4 | import org.interledger.codecs.packettypes.InterledgerPacketType; 5 | import org.interledger.ilp.InterledgerProtocolError; 6 | 7 | /** 8 | * An implementation of {@link Codec} that reads and writes instances of {@link 9 | * InterledgerProtocolError}. 10 | */ 11 | public interface InterledgerProtocolErrorCodec extends 12 | InterledgerPacketCodec { 13 | 14 | InterledgerPacketType TYPE = new InterledgerErrorPacketType(); 15 | 16 | @Override 17 | default InterledgerPacketType getTypeId() { 18 | return TYPE; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/codecs/packettypes/QuoteLiquidityRequestPacketType.java: -------------------------------------------------------------------------------- 1 | package org.interledger.codecs.packettypes; 2 | 3 | import org.interledger.codecs.packettypes.InterledgerPacketType.AbstractInterledgerPacketType; 4 | 5 | import java.net.URI; 6 | 7 | /** 8 | * An implementation of {@link InterledgerPacketType} for ILQP Liquidity requests. 9 | */ 10 | public class QuoteLiquidityRequestPacketType extends AbstractInterledgerPacketType 11 | implements InterledgerPacketType { 12 | 13 | /** 14 | * No-args Constructor. 15 | */ 16 | public QuoteLiquidityRequestPacketType() { 17 | super(ILQP_QUOTE_LIQUIDITY_REQUEST_TYPE, 18 | URI.create("https://interledger.org/ilqp/quote_liquidity_request")); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/codecs/QuoteLiquidityResponseCodec.java: -------------------------------------------------------------------------------- 1 | package org.interledger.codecs; 2 | 3 | import org.interledger.codecs.packettypes.InterledgerPacketType; 4 | import org.interledger.codecs.packettypes.QuoteLiquidityResponsePacketType; 5 | import org.interledger.ilqp.QuoteLiquidityResponse; 6 | 7 | /** 8 | * An implementation of {@link Codec} that reads and writes instances of 9 | * {@link QuoteLiquidityResponse}. 10 | */ 11 | public interface QuoteLiquidityResponseCodec 12 | extends InterledgerPacketCodec { 13 | 14 | InterledgerPacketType TYPE = new QuoteLiquidityResponsePacketType(); 15 | 16 | @Override 17 | default InterledgerPacketType getTypeId() { 18 | return TYPE; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/codecs/packettypes/QuoteLiquidityResponsePacketType.java: -------------------------------------------------------------------------------- 1 | package org.interledger.codecs.packettypes; 2 | 3 | import org.interledger.codecs.packettypes.InterledgerPacketType.AbstractInterledgerPacketType; 4 | 5 | import java.net.URI; 6 | 7 | /** 8 | * An implementation of {@link InterledgerPacketType} for ILQP Liquidity responses. 9 | */ 10 | public class QuoteLiquidityResponsePacketType extends AbstractInterledgerPacketType 11 | implements InterledgerPacketType { 12 | 13 | /** 14 | * No-args Constructor. 15 | */ 16 | public QuoteLiquidityResponsePacketType() { 17 | super(ILQP_QUOTE_LIQUIDITY_RESPONSE_TYPE, 18 | URI.create("https://interledger.org/ilqp/quote_liquidity_response")); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/codecs/QuoteBySourceAmountRequestCodec.java: -------------------------------------------------------------------------------- 1 | package org.interledger.codecs; 2 | 3 | import org.interledger.codecs.packettypes.InterledgerPacketType; 4 | import org.interledger.codecs.packettypes.QuoteBySourceAmountRequestPacketType; 5 | import org.interledger.ilqp.QuoteBySourceAmountRequest; 6 | 7 | /** 8 | * An implementation of {@link Codec} that reads and writes instances of {@link 9 | * QuoteBySourceAmountRequest}. 10 | */ 11 | public interface QuoteBySourceAmountRequestCodec 12 | extends InterledgerPacketCodec { 13 | 14 | InterledgerPacketType TYPE = new QuoteBySourceAmountRequestPacketType(); 15 | 16 | @Override 17 | default InterledgerPacketType getTypeId() { 18 | return TYPE; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/codecs/QuoteBySourceAmountResponseCodec.java: -------------------------------------------------------------------------------- 1 | package org.interledger.codecs; 2 | 3 | import org.interledger.codecs.packettypes.InterledgerPacketType; 4 | import org.interledger.codecs.packettypes.QuoteBySourceAmountResponsePacketType; 5 | import org.interledger.ilqp.QuoteBySourceAmountResponse; 6 | 7 | /** 8 | * An implementation of {@link Codec} that reads and writes instances of {@link 9 | * QuoteBySourceAmountResponse}. 10 | */ 11 | public interface QuoteBySourceAmountResponseCodec 12 | extends InterledgerPacketCodec { 13 | 14 | InterledgerPacketType TYPE = new QuoteBySourceAmountResponsePacketType(); 15 | 16 | @Override 17 | default InterledgerPacketType getTypeId() { 18 | return TYPE; 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /src/main/java/org/interledger/codecs/packettypes/QuoteBySourceAmountRequestPacketType.java: -------------------------------------------------------------------------------- 1 | package org.interledger.codecs.packettypes; 2 | 3 | import org.interledger.codecs.packettypes.InterledgerPacketType.AbstractInterledgerPacketType; 4 | 5 | import java.net.URI; 6 | 7 | /** 8 | * An implementation of {@link InterledgerPacketType} for ILQP quote requests. 9 | */ 10 | public class QuoteBySourceAmountRequestPacketType extends AbstractInterledgerPacketType implements 11 | InterledgerPacketType { 12 | 13 | /** 14 | * No-args Constructor. 15 | */ 16 | public QuoteBySourceAmountRequestPacketType() { 17 | super(ILQP_QUOTE_BY_SOURCE_AMOUNT_REQUEST_TYPE, 18 | URI.create("https://interledger.org/ilqp/quote_by_source_amount_request")); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/codecs/packettypes/QuoteBySourceAmountResponsePacketType.java: -------------------------------------------------------------------------------- 1 | package org.interledger.codecs.packettypes; 2 | 3 | import org.interledger.codecs.packettypes.InterledgerPacketType.AbstractInterledgerPacketType; 4 | 5 | import java.net.URI; 6 | 7 | /** 8 | * An implementation of {@link InterledgerPacketType} for ILQP quote responses. 9 | */ 10 | public class QuoteBySourceAmountResponsePacketType extends AbstractInterledgerPacketType implements 11 | InterledgerPacketType { 12 | 13 | /** 14 | * No-args Constructor. 15 | */ 16 | public QuoteBySourceAmountResponsePacketType() { 17 | super(ILQP_QUOTE_BY_SOURCE_AMOUNT_RESPONSE_TYPE, 18 | URI.create("https://interledger.org/ilqp/quote_by_source_amount_response")); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/ilqp/QuoteRequest.java: -------------------------------------------------------------------------------- 1 | package org.interledger.ilqp; 2 | 3 | import org.interledger.InterledgerAddress; 4 | import org.interledger.InterledgerPacket; 5 | 6 | import java.time.Duration; 7 | 8 | /** 9 | * A parent interface for all quote requests in ILQP. 10 | */ 11 | public interface QuoteRequest extends InterledgerPacket { 12 | 13 | /** 14 | * The account on the destination ledger that this quote applies to. 15 | * 16 | * @return An instance of {@link InterledgerAddress}. 17 | */ 18 | InterledgerAddress getDestinationAccount(); 19 | 20 | /** 21 | * Returns the amount of time the receiver needs to fulfill the payment. 22 | * @return An instance of {@link Duration} 23 | */ 24 | Duration getDestinationHoldDuration(); 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/codecs/packettypes/QuoteByDestinationAmountRequestPacketType.java: -------------------------------------------------------------------------------- 1 | package org.interledger.codecs.packettypes; 2 | 3 | import org.interledger.codecs.packettypes.InterledgerPacketType.AbstractInterledgerPacketType; 4 | 5 | import java.net.URI; 6 | 7 | /** 8 | * An implementation of {@link InterledgerPacketType} for ILQP Liquidity responses. 9 | */ 10 | public class QuoteByDestinationAmountRequestPacketType extends 11 | AbstractInterledgerPacketType implements InterledgerPacketType { 12 | 13 | /** 14 | * No-args Constructor. 15 | */ 16 | public QuoteByDestinationAmountRequestPacketType() { 17 | super(ILQP_QUOTE_BY_DESTINATION_AMOUNT_REQUEST_TYPE, 18 | URI.create("https://interledger.org/ilqp/quote_by_destination_amount_request")); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/codecs/packettypes/QuoteByDestinationAmountResponsePacketType.java: -------------------------------------------------------------------------------- 1 | package org.interledger.codecs.packettypes; 2 | 3 | import org.interledger.codecs.packettypes.InterledgerPacketType.AbstractInterledgerPacketType; 4 | 5 | import java.net.URI; 6 | 7 | /** 8 | * An implementation of {@link InterledgerPacketType} for ILQP quote responses. 9 | */ 10 | public class QuoteByDestinationAmountResponsePacketType extends 11 | AbstractInterledgerPacketType implements InterledgerPacketType { 12 | 13 | /** 14 | * No-args Constructor. 15 | */ 16 | public QuoteByDestinationAmountResponsePacketType() { 17 | super(ILQP_QUOTE_BY_DESTINATION_AMOUNT_RESPONSE_TYPE, 18 | URI.create("https://interledger.org/ilqp/quote_by_destination_amount_response")); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/codecs/QuoteByDestinationAmountRequestCodec.java: -------------------------------------------------------------------------------- 1 | package org.interledger.codecs; 2 | 3 | import org.interledger.codecs.packettypes.InterledgerPacketType; 4 | import org.interledger.codecs.packettypes.QuoteByDestinationAmountRequestPacketType; 5 | import org.interledger.ilqp.QuoteByDestinationAmountRequest; 6 | 7 | /** 8 | * An implementation of {@link Codec} that reads and writes instances of {@link 9 | * QuoteByDestinationAmountRequest}. 10 | */ 11 | public interface QuoteByDestinationAmountRequestCodec extends 12 | InterledgerPacketCodec { 13 | 14 | InterledgerPacketType TYPE = new QuoteByDestinationAmountRequestPacketType(); 15 | 16 | @Override 17 | default InterledgerPacketType getTypeId() { 18 | return TYPE; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/codecs/QuoteByDestinationAmountResponseCodec.java: -------------------------------------------------------------------------------- 1 | package org.interledger.codecs; 2 | 3 | import org.interledger.codecs.packettypes.InterledgerPacketType; 4 | import org.interledger.codecs.packettypes.QuoteByDestinationAmountResponsePacketType; 5 | import org.interledger.ilqp.QuoteByDestinationAmountResponse; 6 | 7 | /** 8 | * An implementation of {@link Codec} that reads and writes instances of {@link 9 | * QuoteByDestinationAmountResponse}. 10 | */ 11 | public interface QuoteByDestinationAmountResponseCodec extends 12 | InterledgerPacketCodec { 13 | 14 | InterledgerPacketType TYPE = new QuoteByDestinationAmountResponsePacketType(); 15 | 16 | @Override 17 | default InterledgerPacketType getTypeId() { 18 | return TYPE; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/test/java/org/interledger/mocks/DeterministicSecureRandomAlgorithm.java: -------------------------------------------------------------------------------- 1 | package org.interledger.mocks; 2 | 3 | import java.security.SecureRandomSpi; 4 | 5 | public class DeterministicSecureRandomAlgorithm extends SecureRandomSpi { 6 | 7 | private static final long serialVersionUID = 8914286719772377141L; 8 | 9 | @Override 10 | protected void engineSetSeed(byte[] seed) { 11 | for (int i = 0; i < seed.length; i++) { 12 | seed[i] = 13 | DeterministicSecureRandomProvider.SEED[i % DeterministicSecureRandomProvider.SEED.length]; 14 | } 15 | } 16 | 17 | @Override 18 | protected void engineNextBytes(byte[] bytes) { 19 | for (int i = 0; i < bytes.length; i++) { 20 | bytes[i] = 21 | DeterministicSecureRandomProvider.SEED[i % DeterministicSecureRandomProvider.SEED.length]; 22 | } 23 | } 24 | 25 | @Override 26 | protected byte[] engineGenerateSeed(int numBytes) { 27 | byte[] bytes = new byte[numBytes]; 28 | for (int i = 0; i < numBytes; i++) { 29 | bytes[i] = 30 | DeterministicSecureRandomProvider.SEED[i % DeterministicSecureRandomProvider.SEED.length]; 31 | } 32 | return bytes; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/test/java/org/interledger/psk/UnEncryptedMessageWriterTest.java: -------------------------------------------------------------------------------- 1 | package org.interledger.psk; 2 | 3 | public class UnEncryptedMessageWriterTest { 4 | 5 | // @Test 6 | // public void test() { 7 | // 8 | // ReceiverPskContext context = ReceiverPskContext.seed(new byte[16]); 9 | // 10 | // InterledgerPayment payment = PskPayment.builder(context) 11 | // .addPrivateHeader("private header", "\tprivate\theader\tvalue\t") 12 | // .data("{some_application_data: 123}".getBytes(StandardCharsets.UTF_8)) 13 | // .build(); 14 | // 15 | // assertNotNull(payment.getData()); 16 | // 17 | // /* we happen to know that the PSK message is just a UTF-8 encoded string */ 18 | // String messageString = new String(payment.getData(), StandardCharsets.UTF_8); 19 | // 20 | // /* hand-craft what we expect to see. a bit awkward, since the header order isn't terribly 21 | // * predictable from the outside. */ 22 | // String expected = "PSK/1.0\n" 23 | // + "Encryption: none\n" 24 | // + "\n" 25 | // + "private header: private\theader\tvalue\n" 26 | // + "\n" 27 | // + "{some_application_data: 123}"; 28 | // 29 | // assertEquals(expected, messageString); 30 | // } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/InterledgerProtocolException.java: -------------------------------------------------------------------------------- 1 | package org.interledger; 2 | 3 | import org.interledger.ilp.InterledgerProtocolError; 4 | 5 | import java.util.Objects; 6 | 7 | /** 8 | * Base ILP exception, see RFC REF: https://interledger.org/rfcs/0003-interledger-protocol/#errors 9 | */ 10 | public class InterledgerProtocolException extends InterledgerRuntimeException { 11 | 12 | private static final long serialVersionUID = 1L; 13 | 14 | private final InterledgerProtocolError interledgerProtocolError; 15 | 16 | /** 17 | * Required-args constructor. 18 | * 19 | * @param interledgerProtocolError An instance of {@link InterledgerProtocolError} that is the 20 | * underlying error encapsulated by this exception. 21 | */ 22 | public InterledgerProtocolException(final InterledgerProtocolError interledgerProtocolError) { 23 | super("Interledger protocol error."); 24 | this.interledgerProtocolError = 25 | Objects 26 | .requireNonNull(interledgerProtocolError, "interledgerProtocolError must not be null"); 27 | } 28 | 29 | public InterledgerProtocolError getInterledgerProtocolError() { 30 | return interledgerProtocolError; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/test/java/org/interledger/codecs/oer/OerLengthPrefixCodecTestBadLength.java: -------------------------------------------------------------------------------- 1 | package org.interledger.codecs.oer; 2 | 3 | import org.interledger.codecs.CodecContext; 4 | import org.interledger.codecs.oer.OerLengthPrefixCodec.OerLengthPrefix; 5 | 6 | import org.junit.Test; 7 | 8 | import java.io.ByteArrayInputStream; 9 | import java.io.IOException; 10 | 11 | /** 12 | * Tests that the {@link OerLengthPrefixCodec} correctly fails if the required length indicator 13 | * cannot be fully read. 14 | */ 15 | public class OerLengthPrefixCodecTestBadLength { 16 | 17 | @Test(expected = IOException.class) 18 | public void test_BadLengthIndicator() throws IOException { 19 | OerLengthPrefixCodec codec = new OerLengthPrefixCodec(); 20 | CodecContext context = new CodecContext().register(OerLengthPrefix.class, codec); 21 | 22 | /* 23 | * we create an incorrect length indicator that says that the next two bytes encode the length 24 | * of the actual data. however, we'll only supply one byte of information. the codec should 25 | * detect this and fail, since it cannot read the length indicator. 26 | */ 27 | byte lengthOfLength = (byte) ((1 << 7) | 2); 28 | 29 | byte[] lengthIndicator = new byte[] {lengthOfLength, 0}; 30 | 31 | final ByteArrayInputStream inputStream = new ByteArrayInputStream(lengthIndicator); 32 | 33 | codec.read(context, inputStream); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/ilqp/QuoteService.java: -------------------------------------------------------------------------------- 1 | package org.interledger.ilqp; 2 | 3 | import org.interledger.InterledgerAddress; 4 | 5 | 6 | /** 7 | * A quote service delivers quote requests to connectors and returns their response. 8 | * 9 | * @param the type of the input to the service. 10 | * @param the type of the result of the service. 11 | */ 12 | public interface QuoteService { 13 | 14 | /** 15 | * Requests a quote and applies the given selection strategy to return the best quote received. 16 | * 17 | * @param quoteRequest The quote requested. 18 | * @param selectionStrategy A strategy for selecting the best of a given set of quote responses. 19 | * 20 | * @return An instance {@link R} which is the best quote, or null if no responses where received. 21 | */ 22 | R requestQuote(T quoteRequest, QuoteSelectionStrategy selectionStrategy) 23 | throws InterledgerQuotingException; 24 | 25 | /** 26 | * Requests a quote of the connector at the given address. 27 | * 28 | * @param quoteRequest The quote requested. 29 | * @param connector The ILP address of the connector to get the quote of. 30 | * 31 | * @return The quote response of the connector, or null if no response is received. 32 | */ 33 | R requestQuote(T quoteRequest, InterledgerAddress connector) throws InterledgerQuotingException; 34 | } 35 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | machine: 2 | java: 3 | version: oraclejdk8 4 | post: 5 | # apply the JCE unlimited strength policy to allow the PSK 256 bit key length 6 | # solution from http://qiita.com/yoskhdia/items/f4702a3abc4467de69b0 7 | - curl -L --cookie 'oraclelicense=accept-securebackup-cookie;' http://download.oracle.com/otn-pub/java/jce/8/jce_policy-8.zip -o /tmp/jce_policy.zip 8 | - unzip -o /tmp/jce_policy.zip -d /tmp 9 | - sudo mv -f /tmp/UnlimitedJCEPolicyJDK8/US_export_policy.jar $JAVA_HOME/jre/lib/security/US_export_policy.jar 10 | - sudo mv -f /tmp/UnlimitedJCEPolicyJDK8/local_policy.jar $JAVA_HOME/jre/lib/security/local_policy.jar 11 | 12 | dependencies: 13 | override: 14 | - mvn -DskipTests clean install dependency:resolve-plugins dependency:go-offline 15 | 16 | test: 17 | override: 18 | - mvn surefire:test 19 | 20 | post: 21 | # run the checkstyle report 22 | - mvn checkstyle:checkstyle 23 | 24 | # gather the junit reports, see https://circleci.com/docs/test-metadata/#gradle-junit-results 25 | - mkdir -p $CIRCLE_TEST_REPORTS/junit/ 26 | - find . -type f -regex ".*/target/surefire-reports/.*xml" -exec cp {} $CIRCLE_TEST_REPORTS/junit/ \; 27 | 28 | - mkdir -p $CIRCLE_TEST_REPORTS/checkstyle/ 29 | - find . -type f -regex ".*/target/checkstyle-reports/.*xml" -exec cp {} $CIRCLE_TEST_REPORTS/checkstyle/ \; 30 | 31 | # publish the coverage report to codecov.io 32 | - bash <(curl -s https://codecov.io/bash) 33 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/psk/PskEncryptionType.java: -------------------------------------------------------------------------------- 1 | package org.interledger.psk; 2 | 3 | import java.util.Objects; 4 | 5 | /** 6 | * Enumerates the types of encryption that can be used in the Pre-Shared Key Transport Protocol. 7 | */ 8 | public enum PskEncryptionType { 9 | NONE("none"), 10 | AES_256_GCM("aes-256-gcm"); 11 | 12 | private final String name; 13 | 14 | /** 15 | * Internal constructor that binds the enum to the description found in PSK message headers. 16 | * 17 | * @param name The description of the encryption type found in PSK message headers. 18 | */ 19 | PskEncryptionType(final String name) { 20 | this.name = name; 21 | } 22 | 23 | /** 24 | * Convenience method to return the {@link PskEncryptionType} that matches the text description. 25 | * 26 | * @param description The text description of the encryption type, typically found in PSK message 27 | * headers. 28 | * 29 | * @return The matching {@link PskEncryptionType} if one can be found, otherwise null. 30 | */ 31 | public static PskEncryptionType fromString(final String description) { 32 | Objects.requireNonNull(description); 33 | 34 | for (PskEncryptionType type : PskEncryptionType.values()) { 35 | if (type.name.equalsIgnoreCase(description)) { 36 | return type; 37 | } 38 | } 39 | 40 | return null; 41 | } 42 | 43 | @Override 44 | public String toString() { 45 | return name; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/codecs/oer/ilp/InterledgerAddressOerCodec.java: -------------------------------------------------------------------------------- 1 | package org.interledger.codecs.oer.ilp; 2 | 3 | import org.interledger.InterledgerAddress; 4 | import org.interledger.codecs.Codec; 5 | import org.interledger.codecs.CodecContext; 6 | import org.interledger.codecs.InterledgerAddressCodec; 7 | import org.interledger.codecs.oer.OerIA5StringCodec.OerIA5String; 8 | 9 | import java.io.IOException; 10 | import java.io.InputStream; 11 | import java.io.OutputStream; 12 | import java.util.Objects; 13 | 14 | /** 15 | * An implementation of {@link Codec} that reads and writes instances of {@link InterledgerAddress}. 16 | */ 17 | public class InterledgerAddressOerCodec implements InterledgerAddressCodec { 18 | 19 | @Override 20 | public InterledgerAddress read(final CodecContext context, final InputStream inputStream) 21 | throws IOException { 22 | Objects.requireNonNull(context); 23 | Objects.requireNonNull(inputStream); 24 | final String value = context.read(OerIA5String.class, inputStream) 25 | .getValue(); 26 | return InterledgerAddress.of(value); 27 | } 28 | 29 | @Override 30 | public void write(final CodecContext context, final InterledgerAddress instance, 31 | final OutputStream outputStream) throws IOException { 32 | Objects.requireNonNull(context); 33 | Objects.requireNonNull(instance); 34 | Objects.requireNonNull(outputStream); 35 | 36 | context.write(OerIA5String.class, new OerIA5String(instance.getValue()), outputStream); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /architecture.md: -------------------------------------------------------------------------------- 1 | #DRAFT 2 | 3 | This is an attempt to pull together the various peices of work being done on Java implementations of ILP. It will evolve to be a proper description of the architecture in time. 4 | 5 | ## Componenents 6 | 7 | The architecture consists of a number of components that tie together to facilitate a complete end-to-end ILP payment. 8 | 9 | ### Core 10 | 11 | The core library contains non-implementation-specific code that should be used by all implementations. It defines interfaces and some concrete classes for domain specific data models such as Interledger Addresses. 12 | 13 | The core component is implemented in https://github.com/interledger/java-ilp-core/ 14 | 15 | ### Client (possibly rename to node/peer?) 16 | 17 | The client contains the high level orchestration logic for the protocol. It is used by senders, receviers and connectors to perform functions like perform setup, send messages to other clients connected to a common ledger and make transfers to other clients on a ledger. 18 | 19 | ### 20 | 21 | ## Roadmap 22 | 23 | To get us closer to a complete end-to-end solution using Java componenets we should aim to have a working setup as follows: 24 | 25 | ![Phase 1 components](https://raw.githubusercontent.com/interledger/java-ilp-core/development/ILP%20Java%20-%20Phase%201.png) 26 | 27 | Once we have this working we can expand to a more complete setup with multiple connectors and implement the routing and quoting protocols more fully: 28 | 29 | ![Phase 2 components](https://raw.githubusercontent.com/interledger/java-ilp-core/development/ILP%20Java%20-%20Phase%202.png) 30 | -------------------------------------------------------------------------------- /src/test/java/org/interledger/codecs/oer/ilp/ConditionOerCodecTests.java: -------------------------------------------------------------------------------- 1 | package org.interledger.codecs.oer.ilp; 2 | 3 | import static org.hamcrest.CoreMatchers.is; 4 | import static org.hamcrest.MatcherAssert.assertThat; 5 | 6 | import org.interledger.codecs.CodecContext; 7 | import org.interledger.codecs.CodecContextFactory; 8 | 9 | import org.interledger.cryptoconditions.Condition; 10 | import org.interledger.cryptoconditions.PreimageSha256Condition; 11 | 12 | import org.junit.Test; 13 | import org.junit.runner.RunWith; 14 | import org.junit.runners.Parameterized; 15 | import org.junit.runners.Parameterized.Parameter; 16 | import org.junit.runners.Parameterized.Parameters; 17 | 18 | import java.io.ByteArrayInputStream; 19 | import java.io.ByteArrayOutputStream; 20 | import java.util.Arrays; 21 | import java.util.Collection; 22 | 23 | @RunWith(Parameterized.class) 24 | public class ConditionOerCodecTests { 25 | 26 | 27 | // first data value (0) is default 28 | @Parameter 29 | public Condition condition; 30 | 31 | /** 32 | * The data for this test... 33 | */ 34 | @Parameters 35 | public static Collection data() { 36 | return Arrays.asList(new Object[][]{{new PreimageSha256Condition(32, new byte[32])}, 37 | // TODO: Some more test values 38 | }); 39 | } 40 | 41 | @Test 42 | public void testEqualsHashcode() throws Exception { 43 | final CodecContext context = CodecContextFactory.interledger(); 44 | 45 | final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 46 | context.write(condition, outputStream); 47 | 48 | final ByteArrayInputStream byteArrayInputStream = 49 | new ByteArrayInputStream(outputStream.toByteArray()); 50 | 51 | final Condition decodedCondition = context.read(Condition.class, byteArrayInputStream); 52 | assertThat(decodedCondition, is(condition)); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/test/java/org/interledger/codecs/oer/OerIA5StringCodecTestBadLength.java: -------------------------------------------------------------------------------- 1 | package org.interledger.codecs.oer; 2 | 3 | import org.interledger.codecs.CodecContext; 4 | import org.interledger.codecs.oer.OerIA5StringCodec.OerIA5String; 5 | import org.interledger.codecs.oer.OerLengthPrefixCodec.OerLengthPrefix; 6 | 7 | import org.junit.Test; 8 | 9 | import java.io.ByteArrayInputStream; 10 | import java.io.ByteArrayOutputStream; 11 | import java.io.IOException; 12 | import java.util.Arrays; 13 | 14 | /** 15 | * Test to ensure that the {@link OerIA5StringCodec} properly fails when it cannot read the number 16 | * of bytes indicated. 17 | */ 18 | public class OerIA5StringCodecTestBadLength { 19 | 20 | @Test(expected = IOException.class) 21 | public void test_BadLengthIndicator() throws IOException { 22 | OerIA5StringCodec codec = new OerIA5StringCodec(); 23 | CodecContext context = 24 | new CodecContext().register(OerLengthPrefix.class, new OerLengthPrefixCodec()) 25 | .register(OerIA5String.class, codec); 26 | 27 | final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 28 | 29 | codec.write(context, new OerIA5String("A test string of reasonable length"), outputStream); 30 | 31 | final byte[] correctData = outputStream.toByteArray(); 32 | 33 | /* 34 | * we now remove a portion of the data, i.e. the length indicator should claim that there are 34 35 | * bytes, but we have truncated to 10 bytes 36 | */ 37 | 38 | final byte[] truncated = Arrays.copyOfRange(correctData, 0, 10); 39 | 40 | /* create an input stream over the truncated data */ 41 | final ByteArrayInputStream inputStream = new ByteArrayInputStream(truncated); 42 | 43 | /* 44 | * try read the data, the codec should fail since it reaches EOF before consuming the 34 bytes 45 | * indicated. 46 | */ 47 | codec.read(context, inputStream); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/codecs/AbstractCodec.java: -------------------------------------------------------------------------------- 1 | package org.interledger.codecs; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.io.OutputStream; 6 | import java.util.Objects; 7 | 8 | /** 9 | * An abstract implementation of {@link Codec} that works with Interledger packets. 10 | * 11 | * @param The type of object that this codec 12 | */ 13 | public abstract class AbstractCodec implements Codec { 14 | 15 | private final int typeId; 16 | 17 | public AbstractCodec(final int typeId) { 18 | this.typeId = typeId; 19 | } 20 | 21 | /** 22 | * Read an object from the buffer according to the rules defined in the {@link CodecContext}. 23 | * 24 | * @param context An instance of {@link CodecContext}. 25 | * @param inputStream An instance of {@link InputStream} to read data of. 26 | * 27 | * @return An instance of {@link T} as decoded of {@code inputStream}. 28 | */ 29 | public T read(CodecContext context, InputStream inputStream) { 30 | Objects.requireNonNull(context); 31 | Objects.requireNonNull(inputStream); 32 | 33 | return readObject(context, inputStream); 34 | } 35 | 36 | @Override 37 | public void write(final CodecContext context, final T instance, final OutputStream outputStream) 38 | throws IOException { 39 | Objects.requireNonNull(context); 40 | Objects.requireNonNull(instance); 41 | Objects.requireNonNull(outputStream); 42 | 43 | outputStream.write(typeId); 44 | writeObject(context, instance, outputStream); 45 | } 46 | 47 | public int typeId() { 48 | return typeId; 49 | } 50 | 51 | /** 52 | * Read an object of type {@link T} of the supplied {@code inputStream}. 53 | * 54 | * @param context An instance of {@link CodecContext}. 55 | * @param inputStream An instance of {@link InputStream}. 56 | * 57 | * @return An instance of {@link T}. 58 | */ 59 | protected abstract T readObject(CodecContext context, InputStream inputStream); 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/codecs/oer/ilp/ConditionOerCodec.java: -------------------------------------------------------------------------------- 1 | package org.interledger.codecs.oer.ilp; 2 | 3 | import org.interledger.codecs.Codec; 4 | import org.interledger.codecs.CodecContext; 5 | import org.interledger.codecs.ConditionCodec; 6 | import org.interledger.codecs.oer.OerUint256Codec.OerUint256; 7 | import org.interledger.cryptoconditions.Condition; 8 | import org.interledger.cryptoconditions.PreimageSha256Condition; 9 | 10 | import java.io.IOException; 11 | import java.io.InputStream; 12 | import java.io.OutputStream; 13 | import java.util.Objects; 14 | 15 | /** 16 | * An implementation of {@link Codec} that reads and writes instances of {@link Condition}. 17 | * 18 | *

In universal mode ILP, conditions are assumed to always be PreimageSha256 Crypto Conditions 19 | * encoded simply as a 32 byte hash. 20 | * 21 | *

The preimage of the hash is always 32 bytes. 22 | * 23 | */ 24 | public class ConditionOerCodec implements ConditionCodec { 25 | 26 | @Override 27 | public Condition read(final CodecContext context, final InputStream inputStream) 28 | throws IOException { 29 | Objects.requireNonNull(context); 30 | Objects.requireNonNull(inputStream); 31 | final byte[] value = context.read(OerUint256.class, inputStream) 32 | .getValue(); 33 | 34 | //Cost (equal to the length of the preimage) is always 32 bytes in universal mode ILP 35 | return new PreimageSha256Condition(32, value); 36 | } 37 | 38 | @Override 39 | public void write(final CodecContext context, final Condition instance, 40 | final OutputStream outputStream) throws IOException { 41 | Objects.requireNonNull(context); 42 | Objects.requireNonNull(instance); 43 | Objects.requireNonNull(outputStream); 44 | 45 | if (instance.getCost() != 32) { 46 | throw new IllegalArgumentException("Instance.getCost() must be equal to 32"); 47 | } 48 | 49 | context.write(OerUint256.class, new OerUint256(instance.getFingerprint()), outputStream); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/codecs/oer/ilp/FulfillmentOerCodec.java: -------------------------------------------------------------------------------- 1 | package org.interledger.codecs.oer.ilp; 2 | 3 | import org.interledger.codecs.Codec; 4 | import org.interledger.codecs.CodecContext; 5 | import org.interledger.codecs.FulfillmentCodec; 6 | import org.interledger.codecs.oer.OerUint256Codec.OerUint256; 7 | import org.interledger.cryptoconditions.Fulfillment; 8 | import org.interledger.cryptoconditions.PreimageSha256Fulfillment; 9 | 10 | import java.io.IOException; 11 | import java.io.InputStream; 12 | import java.io.OutputStream; 13 | import java.util.Base64; 14 | import java.util.Objects; 15 | 16 | /** 17 | * An implementation of {@link Codec} that reads and writes instances of {@link Fulfillment}. 18 | */ 19 | public class FulfillmentOerCodec implements FulfillmentCodec { 20 | 21 | @Override 22 | public Fulfillment read(final CodecContext context, final InputStream inputStream) 23 | throws IOException { 24 | Objects.requireNonNull(context); 25 | Objects.requireNonNull(inputStream); 26 | final byte[] value = context.read(OerUint256.class, inputStream) 27 | .getValue(); 28 | return new PreimageSha256Fulfillment(value); 29 | } 30 | 31 | @Override 32 | public void write(final CodecContext context, final Fulfillment instance, 33 | final OutputStream outputStream) throws IOException { 34 | Objects.requireNonNull(context); 35 | Objects.requireNonNull(instance); 36 | Objects.requireNonNull(outputStream); 37 | 38 | //TODO Review after https://github.com/interledger/java-crypto-conditions/issues/75 is closed 39 | if (instance instanceof PreimageSha256Fulfillment) { 40 | PreimageSha256Fulfillment fulfillment = (PreimageSha256Fulfillment) instance; 41 | byte[] preimage = Base64.getUrlDecoder().decode(fulfillment.getPreimage()); 42 | context.write(OerUint256.class, new OerUint256(preimage), outputStream); 43 | } else { 44 | throw new IllegalArgumentException("Only PreimageSha256Fulfillment instances can be encoded"); 45 | } 46 | 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/codecs/oer/ilp/InterledgerPacketTypeOerCodec.java: -------------------------------------------------------------------------------- 1 | package org.interledger.codecs.oer.ilp; 2 | 3 | import org.interledger.InterledgerAddress; 4 | import org.interledger.codecs.Codec; 5 | import org.interledger.codecs.CodecContext; 6 | import org.interledger.codecs.CodecException; 7 | import org.interledger.codecs.InterledgerPacketTypeCodec; 8 | import org.interledger.codecs.oer.OerUint8Codec.OerUint8; 9 | import org.interledger.codecs.packettypes.InterledgerPacketType; 10 | import org.interledger.codecs.packettypes.InterledgerPacketType.InvalidPacketTypeException; 11 | 12 | import java.io.IOException; 13 | import java.io.InputStream; 14 | import java.io.OutputStream; 15 | import java.util.Objects; 16 | 17 | /** 18 | * An implementation of {@link Codec} that reads and writes instances of {@link InterledgerAddress}. 19 | */ 20 | public class InterledgerPacketTypeOerCodec implements InterledgerPacketTypeCodec { 21 | 22 | @Override 23 | public InterledgerPacketType read(final CodecContext context, final InputStream inputStream) 24 | throws IOException { 25 | Objects.requireNonNull(context); 26 | Objects.requireNonNull(inputStream); 27 | 28 | final int typeId = context.read(OerUint8.class, inputStream) 29 | .getValue(); 30 | 31 | try { 32 | return InterledgerPacketType.fromTypeId(typeId); 33 | } catch (InvalidPacketTypeException e) { 34 | throw new CodecException("Encountered unsupported Interledger Packet Type. Please extend " 35 | + "InterledgerPacketTypeCodec and register it with the CodecContext to support this" 36 | + "new type.", e); 37 | } 38 | } 39 | 40 | @Override 41 | public void write(final CodecContext context, final InterledgerPacketType instance, 42 | final OutputStream outputStream) throws IOException { 43 | Objects.requireNonNull(context); 44 | Objects.requireNonNull(instance); 45 | Objects.requireNonNull(outputStream); 46 | 47 | context.write(OerUint8.class, new OerUint8(instance.getTypeIdentifier()), outputStream); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/codecs/Codec.java: -------------------------------------------------------------------------------- 1 | package org.interledger.codecs; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.io.OutputStream; 6 | 7 | /** 8 | * A generic coder/decoder interface for all Interledger objects. 9 | */ 10 | public interface Codec { 11 | 12 | /** 13 | * Read an object from the buffer according to the rules defined in the {@link CodecContext}. 14 | * 15 | * @param context An instance of {@link CodecContext}. 16 | * @param inputStream An instance of {@link InputStream} to read data from. 17 | * 18 | * @return An instance of {@link T} as decoded from {@code inputStream}. 19 | * 20 | * @throws IOException If anything goes wrong reading from the {@link InputStream}. 21 | */ 22 | T read(CodecContext context, InputStream inputStream) throws IOException; 23 | 24 | /** 25 | * Write an object to the {@code outputStream} according to the rules defined in the 26 | * {@code context}. 27 | * 28 | * @param context An instance of {@link CodecContext}. 29 | * @param instance An instance of type {@link T}. 30 | * @param outputStream An instance of {@link OutputStream} to write data to. 31 | * 32 | * @throws IOException If anything goes wrong writing to the {@link OutputStream} 33 | */ 34 | void write(CodecContext context, T instance, OutputStream outputStream) throws IOException; 35 | 36 | /** 37 | * Writes an {@link Object} to the {@code outputStream} by attempting to convert it to a proper 38 | * type. 39 | * 40 | * @param context An instance of {@link CodecContext}. 41 | * @param instance An instance of type {@link Object}. 42 | * @param outputStream An instance of {@link OutputStream} to write data to. 43 | * 44 | * @throws IOException If anything goes wrong writing to the {@link OutputStream} 45 | */ 46 | @SuppressWarnings("unchecked") 47 | default void writeObject(final CodecContext context, final Object instance, 48 | final OutputStream outputStream) throws IOException { 49 | write(context, (T) instance, outputStream); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/test/java/org/interledger/FulfillmentTest.java: -------------------------------------------------------------------------------- 1 | package org.interledger; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertTrue; 5 | 6 | import org.interledger.cryptoconditions.Condition; 7 | import org.interledger.cryptoconditions.Fulfillment; 8 | import org.interledger.cryptoconditions.PreimageSha256Condition; 9 | import org.interledger.cryptoconditions.PreimageSha256Fulfillment; 10 | 11 | import org.junit.Test; 12 | 13 | import java.util.Base64; 14 | 15 | public class FulfillmentTest { 16 | 17 | private static final byte[] TEST_PREIMAGE = new byte[32]; 18 | private static final Condition TEST_CONDITION = new PreimageSha256Condition(32, 19 | Base64.getUrlDecoder().decode("Zmh6rfhivXdsj8GLjp-OIAiXFIVu4jOzkCpZHQ1fKSU")); 20 | 21 | // TODO These tests no longer work because the size validation is only done during encoding 22 | // Consider re-instating tests after refactoring crypto-conditions 23 | // @Test(expected = IllegalArgumentException.class) 24 | // public final void testSmallPreimage() { 25 | // new PreimageSha256Fulfillment(new byte[31]); 26 | // } 27 | 28 | // TODO These tests no longer work because the size validation is only done during encoding 29 | // Consider re-instating tests after refactoring crypto-conditions 30 | // @Test(expected = IllegalArgumentException.class) 31 | // public final void testBigPreimage() { 32 | // new PreimageSha256Fulfillment(new byte[33]); 33 | // } 34 | 35 | @Test(expected = NullPointerException.class) 36 | public final void testNullPreimage() { 37 | new PreimageSha256Fulfillment(null); 38 | } 39 | 40 | @Test() 41 | public final void testGetCondition() { 42 | 43 | Fulfillment fulfillment = new PreimageSha256Fulfillment(TEST_PREIMAGE); 44 | assertEquals("Wrong condition", TEST_CONDITION, fulfillment.getCondition()); 45 | 46 | } 47 | 48 | @Test() 49 | public final void testValidate() { 50 | 51 | Fulfillment fulfillment = new PreimageSha256Fulfillment(TEST_PREIMAGE); 52 | assertTrue("Invalid condition", fulfillment.verify(TEST_CONDITION, new byte[]{})); 53 | 54 | } 55 | 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/test/java/org/interledger/mocks/DeterministicSecureRandomProvider.java: -------------------------------------------------------------------------------- 1 | package org.interledger.mocks; 2 | 3 | import java.security.AccessController; 4 | import java.security.PrivilegedAction; 5 | import java.security.Provider; 6 | import java.security.Security; 7 | 8 | /** 9 | * A PRNG that always returns a deterministic outcome for use in testing. 10 | */ 11 | public class DeterministicSecureRandomProvider extends Provider { 12 | 13 | private static final long serialVersionUID = 5364295470454144673L; 14 | 15 | private static final String PROVIDER_NAME = "DeterministicSecureRandomProvider"; 16 | private static final String ALGO_NAME = "DeterministicPRNG"; 17 | 18 | 19 | public static byte[] SEED; 20 | public static String PREVIOUS_STRONG_ALGO; 21 | 22 | protected DeterministicSecureRandomProvider(byte[] seed) { 23 | super(PROVIDER_NAME, 1.0, null); 24 | SEED = seed; 25 | put("SecureRandom." + ALGO_NAME, DeterministicSecureRandomAlgorithm.class.getName()); 26 | } 27 | 28 | /** 29 | * Set this provider as the default PRNG provider. 30 | * 31 | * @param seed The seed to use for any calls to the provider 32 | */ 33 | public static void setAsDefault(byte[] seed) { 34 | 35 | PREVIOUS_STRONG_ALGO = AccessController.doPrivileged( 36 | (PrivilegedAction) () -> Security.getProperty("securerandom.strongAlgorithms")); 37 | 38 | Security.insertProviderAt(new DeterministicSecureRandomProvider(seed), 1); 39 | 40 | AccessController.doPrivileged((PrivilegedAction) () -> { 41 | 42 | final String property = ALGO_NAME + ":" + PROVIDER_NAME; 43 | 44 | Security.setProperty("securerandom.strongAlgorithms", property); 45 | return property; 46 | 47 | }); 48 | } 49 | 50 | /** 51 | * Remove this provider and revert the security settings. 52 | */ 53 | public static void remove() { 54 | 55 | AccessController.doPrivileged((PrivilegedAction) () -> { 56 | 57 | Security.setProperty("securerandom.strongAlgorithms", PREVIOUS_STRONG_ALGO); 58 | return PREVIOUS_STRONG_ALGO; 59 | 60 | }); 61 | 62 | Security.removeProvider(PROVIDER_NAME); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/test/java/org/interledger/psk/Aes256BitKeysizeTest.java: -------------------------------------------------------------------------------- 1 | package org.interledger.psk; 2 | 3 | import org.interledger.InterledgerRuntimeException; 4 | 5 | import org.junit.Test; 6 | 7 | import java.security.InvalidAlgorithmParameterException; 8 | import java.security.InvalidKeyException; 9 | import java.security.NoSuchAlgorithmException; 10 | import java.security.SecureRandom; 11 | import javax.crypto.BadPaddingException; 12 | import javax.crypto.Cipher; 13 | import javax.crypto.IllegalBlockSizeException; 14 | import javax.crypto.KeyGenerator; 15 | import javax.crypto.NoSuchPaddingException; 16 | import javax.crypto.spec.GCMParameterSpec; 17 | import javax.crypto.spec.SecretKeySpec; 18 | 19 | /** 20 | * Test that locally installed provider is working as expected. 21 | * 22 | *

To use 256-bit AES keys you must have Java Cryptography Extension (JCE) Unlimited Strength 23 | * Jurisdiction Policy Files installed. 24 | * 25 | *

@see 27 | * http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html 28 | */ 29 | public class Aes256BitKeysizeTest { 30 | 31 | @Test 32 | public final void test256bitKey() 33 | throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, 34 | InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException { 35 | 36 | SecureRandom sr = SecureRandom.getInstanceStrong(); 37 | byte[] nonce = new byte[16]; 38 | sr.nextBytes(nonce); 39 | 40 | KeyGenerator keygen = KeyGenerator.getInstance("AES"); 41 | keygen.init(256); 42 | byte[] key = keygen.generateKey().getEncoded(); 43 | 44 | byte[] data = new byte[256]; 45 | sr.nextBytes(data); 46 | 47 | Cipher cipher = Cipher.getInstance("AES/GCM/PKCS5Padding"); 48 | GCMParameterSpec paramSpec = new GCMParameterSpec(128, nonce); 49 | 50 | try { 51 | cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"), paramSpec); 52 | cipher.doFinal(data); 53 | } catch (InvalidKeyException e) { 54 | throw new InterledgerRuntimeException("Error loading 256bit key. " 55 | + "Likley cause is missing Unlimited Strength Jurisdiction Policy Files.", e); 56 | } 57 | 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/codecs/oer/ipr/InterledgerPaymentRequestOerCodec.java: -------------------------------------------------------------------------------- 1 | package org.interledger.codecs.oer.ipr; 2 | 3 | import org.interledger.codecs.Codec; 4 | import org.interledger.codecs.CodecContext; 5 | import org.interledger.codecs.InterledgerPaymentRequestCodec; 6 | import org.interledger.codecs.oer.OerUint8Codec.OerUint8; 7 | import org.interledger.cryptoconditions.Condition; 8 | import org.interledger.cryptoconditions.PreimageSha256Condition; 9 | import org.interledger.ilp.InterledgerPayment; 10 | import org.interledger.ipr.InterledgerPaymentRequest; 11 | 12 | import java.io.IOException; 13 | import java.io.InputStream; 14 | import java.io.OutputStream; 15 | import java.util.Objects; 16 | 17 | /** 18 | * An implementation of {@link Codec} that reads and writes instances of 19 | * {@link InterledgerPaymentRequest}. 20 | */ 21 | public class InterledgerPaymentRequestOerCodec implements InterledgerPaymentRequestCodec { 22 | 23 | @Override 24 | public InterledgerPaymentRequest read(final CodecContext context, final InputStream inputStream) 25 | throws IOException { 26 | Objects.requireNonNull(context); 27 | Objects.requireNonNull(inputStream); 28 | 29 | final int version = context.read(OerUint8.class, inputStream) 30 | .getValue(); 31 | 32 | if (version != 2) { 33 | throw new RuntimeException("Unknown IPR version: " + version); 34 | } 35 | 36 | final InterledgerPayment packet = context.read(InterledgerPayment.class, inputStream); 37 | final PreimageSha256Condition condition 38 | = context.read(PreimageSha256Condition.class, inputStream); 39 | 40 | return InterledgerPaymentRequest.builder() 41 | .payment(packet) 42 | .condition(condition) 43 | .build(); 44 | } 45 | 46 | @Override 47 | public void write(final CodecContext context, final InterledgerPaymentRequest instance, 48 | final OutputStream outputStream) throws IOException { 49 | Objects.requireNonNull(context); 50 | Objects.requireNonNull(instance); 51 | Objects.requireNonNull(outputStream); 52 | 53 | context.write(OerUint8.class, new OerUint8(instance.getVersion()), outputStream); 54 | context.write(InterledgerPayment.class, instance.getInterledgerPayment(), outputStream); 55 | context.write(Condition.class, instance.getCondition(), outputStream); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/codecs/oer/OerUint8Codec.java: -------------------------------------------------------------------------------- 1 | package org.interledger.codecs.oer; 2 | 3 | import org.interledger.codecs.Codec; 4 | import org.interledger.codecs.CodecContext; 5 | import org.interledger.codecs.oer.OerUint8Codec.OerUint8; 6 | 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.io.OutputStream; 10 | import java.util.Objects; 11 | 12 | /** 13 | *

An extension of {@link Codec} for reading and writing an ASN.1 OER 8-Bit integer type as 14 | * defined by the Interledger ASN.1 definitions.

All Interledger ASN.1 integer types are 15 | * encoded as fixed-size, non-extensible numbers. Thus, for a UInt8 type, the integer value is 16 | * encoded as an unsigned binary integer in one octet.

17 | */ 18 | public class OerUint8Codec implements Codec { 19 | 20 | @Override 21 | public OerUint8 read(final CodecContext context, final InputStream inputStream) 22 | throws IOException { 23 | Objects.requireNonNull(context); 24 | Objects.requireNonNull(inputStream); 25 | 26 | return new OerUint8(inputStream.read()); 27 | } 28 | 29 | @Override 30 | public void write(CodecContext context, OerUint8 instance, OutputStream outputStream) 31 | throws IOException { 32 | 33 | if (instance.getValue() > 255) { 34 | throw new IllegalArgumentException("Interledger UInt8 values may only contain up to 8 bits!"); 35 | } 36 | 37 | outputStream.write(instance.getValue()); 38 | } 39 | 40 | /** 41 | * Merely a typing mechanism for registering multiple codecs that operate on the same type. 42 | */ 43 | public static class OerUint8 { 44 | 45 | private final int value; 46 | 47 | public OerUint8(final int value) { 48 | this.value = value; 49 | } 50 | 51 | public int getValue() { 52 | return value; 53 | } 54 | 55 | @Override 56 | public boolean equals(Object obj) { 57 | if (this == obj) { 58 | return true; 59 | } 60 | if (obj == null || getClass() != obj.getClass()) { 61 | return false; 62 | } 63 | 64 | OerUint8 that = (OerUint8) obj; 65 | 66 | return value == that.value; 67 | } 68 | 69 | @Override 70 | public int hashCode() { 71 | return value; 72 | } 73 | 74 | @Override 75 | public String toString() { 76 | return "OerUint8{" 77 | + "value=" + value 78 | + '}'; 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/codecs/oer/ilqp/QuoteLiquidityRequestOerCodec.java: -------------------------------------------------------------------------------- 1 | package org.interledger.codecs.oer.ilqp; 2 | 3 | import org.interledger.InterledgerAddress; 4 | import org.interledger.codecs.Codec; 5 | import org.interledger.codecs.CodecContext; 6 | import org.interledger.codecs.QuoteLiquidityRequestCodec; 7 | import org.interledger.codecs.oer.OerUint32Codec.OerUint32; 8 | import org.interledger.codecs.packettypes.InterledgerPacketType; 9 | import org.interledger.ilqp.QuoteLiquidityRequest; 10 | 11 | import java.io.IOException; 12 | import java.io.InputStream; 13 | import java.io.OutputStream; 14 | import java.time.Duration; 15 | import java.time.temporal.ChronoUnit; 16 | import java.util.Objects; 17 | 18 | /** 19 | * An implementation of {@link Codec} that reads and writes instances of 20 | * {@link QuoteLiquidityRequest}. in OER format. 21 | * 22 | * @see "https://github.com/interledger/rfcs/blob/master/asn1/InterledgerQuotingProtocol.asn" 23 | */ 24 | public class QuoteLiquidityRequestOerCodec implements QuoteLiquidityRequestCodec { 25 | 26 | @Override 27 | public QuoteLiquidityRequest read(CodecContext context, InputStream inputStream) 28 | throws IOException { 29 | 30 | Objects.requireNonNull(context); 31 | Objects.requireNonNull(inputStream); 32 | 33 | /* read the Interledger Address. */ 34 | final InterledgerAddress destinationAccount = 35 | context.read(InterledgerAddress.class, inputStream); 36 | 37 | /* read the destination hold duration which is a unit32 */ 38 | long destinationHoldDuration = context.read(OerUint32.class, inputStream).getValue(); 39 | 40 | return QuoteLiquidityRequest.Builder.builder() 41 | .destinationAccount(destinationAccount) 42 | .destinationHoldDuration(Duration.of(destinationHoldDuration, ChronoUnit.MILLIS)) 43 | .build(); 44 | } 45 | 46 | @Override 47 | public void write(CodecContext context, QuoteLiquidityRequest instance, 48 | OutputStream outputStream) throws IOException { 49 | 50 | Objects.requireNonNull(context); 51 | Objects.requireNonNull(instance); 52 | Objects.requireNonNull(outputStream); 53 | 54 | /* write the packet type. */ 55 | context.write(InterledgerPacketType.class, this.getTypeId(), outputStream); 56 | 57 | /* destination account */ 58 | context.write(InterledgerAddress.class, instance.getDestinationAccount(), outputStream); 59 | 60 | /* destination hold duration, in milliseconds */ 61 | context.write(OerUint32.class, new OerUint32(instance.getDestinationHoldDuration().toMillis()), 62 | outputStream); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/codecs/oer/ilqp/QuoteBySourceAmountResponseOerCodec.java: -------------------------------------------------------------------------------- 1 | package org.interledger.codecs.oer.ilqp; 2 | 3 | import org.interledger.codecs.Codec; 4 | import org.interledger.codecs.CodecContext; 5 | import org.interledger.codecs.QuoteBySourceAmountResponseCodec; 6 | import org.interledger.codecs.oer.OerUint32Codec.OerUint32; 7 | import org.interledger.codecs.oer.OerUint64Codec.OerUint64; 8 | import org.interledger.codecs.packettypes.InterledgerPacketType; 9 | import org.interledger.ilqp.QuoteBySourceAmountResponse; 10 | 11 | import java.io.IOException; 12 | import java.io.InputStream; 13 | import java.io.OutputStream; 14 | import java.math.BigInteger; 15 | import java.time.Duration; 16 | import java.time.temporal.ChronoUnit; 17 | import java.util.Objects; 18 | 19 | /** 20 | * An implementation of {@link Codec} that reads and writes instances of {@link 21 | * QuoteBySourceAmountResponse}. in OER format. 22 | * 23 | * @see "https://github.com/interledger/rfcs/blob/master/asn1/InterledgerQuotingProtocol.asn" 24 | */ 25 | public class QuoteBySourceAmountResponseOerCodec implements QuoteBySourceAmountResponseCodec { 26 | 27 | @Override 28 | public QuoteBySourceAmountResponse read(CodecContext context, InputStream inputStream) 29 | throws IOException { 30 | 31 | Objects.requireNonNull(context); 32 | Objects.requireNonNull(inputStream); 33 | 34 | /* read the destination amount, which is a uint64 */ 35 | final BigInteger destinationAmount = context.read(OerUint64.class, inputStream).getValue(); 36 | 37 | /* read the source hold duration which is a unit32 */ 38 | long sourceHoldDuration = context.read(OerUint32.class, inputStream).getValue(); 39 | 40 | return QuoteBySourceAmountResponse.Builder.builder() 41 | .destinationAmount(destinationAmount) 42 | .sourceHoldDuration(Duration.of(sourceHoldDuration, ChronoUnit.MILLIS)).build(); 43 | } 44 | 45 | @Override 46 | public void write(CodecContext context, QuoteBySourceAmountResponse instance, 47 | OutputStream outputStream) throws IOException { 48 | 49 | Objects.requireNonNull(context); 50 | Objects.requireNonNull(instance); 51 | Objects.requireNonNull(outputStream); 52 | 53 | /* Write the packet type. */ 54 | context.write(InterledgerPacketType.class, this.getTypeId(), outputStream); 55 | 56 | /* destination amount */ 57 | context.write(OerUint64.class, new OerUint64(instance.getDestinationAmount()), outputStream); 58 | 59 | /* source hold duration */ 60 | context.write(OerUint32.class, new OerUint32(instance.getSourceHoldDuration().toMillis()), 61 | outputStream); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/codecs/oer/ilqp/QuoteByDestinationAmountResponseOerCodec.java: -------------------------------------------------------------------------------- 1 | package org.interledger.codecs.oer.ilqp; 2 | 3 | import org.interledger.codecs.Codec; 4 | import org.interledger.codecs.CodecContext; 5 | import org.interledger.codecs.QuoteByDestinationAmountResponseCodec; 6 | import org.interledger.codecs.oer.OerUint32Codec.OerUint32; 7 | import org.interledger.codecs.oer.OerUint64Codec.OerUint64; 8 | import org.interledger.codecs.packettypes.InterledgerPacketType; 9 | import org.interledger.ilqp.QuoteByDestinationAmountResponse; 10 | 11 | import java.io.IOException; 12 | import java.io.InputStream; 13 | import java.io.OutputStream; 14 | import java.math.BigInteger; 15 | import java.time.Duration; 16 | import java.time.temporal.ChronoUnit; 17 | import java.util.Objects; 18 | 19 | /** 20 | * An implementation of {@link Codec} that reads and writes instances of {@link 21 | * QuoteByDestinationAmountResponse}. in OER format. 22 | * 23 | * @see "https://github.com/interledger/rfcs/blob/master/asn1/InterledgerQuotingProtocol.asn" 24 | */ 25 | public class QuoteByDestinationAmountResponseOerCodec 26 | implements QuoteByDestinationAmountResponseCodec { 27 | 28 | @Override 29 | public QuoteByDestinationAmountResponse read(CodecContext context, InputStream inputStream) 30 | throws IOException { 31 | 32 | Objects.requireNonNull(context); 33 | Objects.requireNonNull(inputStream); 34 | 35 | /* read the source amount, which is a uint64 */ 36 | BigInteger sourceAmount = context.read(OerUint64.class, inputStream).getValue(); 37 | 38 | /* read the source hold duration which is a unit32 */ 39 | long sourceHoldDuration = context.read(OerUint32.class, inputStream).getValue(); 40 | 41 | return QuoteByDestinationAmountResponse.Builder.builder().sourceAmount(sourceAmount) 42 | .sourceHoldDuration(Duration.of(sourceHoldDuration, ChronoUnit.MILLIS)).build(); 43 | } 44 | 45 | @Override 46 | public void write(CodecContext context, QuoteByDestinationAmountResponse instance, 47 | OutputStream outputStream) throws IOException { 48 | 49 | Objects.requireNonNull(context); 50 | Objects.requireNonNull(instance); 51 | Objects.requireNonNull(outputStream); 52 | 53 | /* Write the packet type. */ 54 | context.write(InterledgerPacketType.class, this.getTypeId(), outputStream); 55 | 56 | /* source amount */ 57 | context.write(OerUint64.class, new OerUint64(instance.getSourceAmount()), outputStream); 58 | 59 | /* source hold duration */ 60 | context.write(OerUint32.class, new OerUint32(instance.getSourceHoldDuration().toMillis()), 61 | outputStream); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/psk/PskNonceHeader.java: -------------------------------------------------------------------------------- 1 | package org.interledger.psk; 2 | 3 | import org.interledger.InterledgerRuntimeException; 4 | 5 | import java.security.NoSuchAlgorithmException; 6 | import java.security.SecureRandom; 7 | import java.util.Arrays; 8 | import java.util.Base64; 9 | import java.util.Objects; 10 | 11 | /** 12 | * Convenience header representing the public Encryption header found in PSK messages. 13 | */ 14 | public class PskNonceHeader extends PskMessage.Header { 15 | 16 | /* the expected length of the nonce, in bytes */ 17 | private static final int NONCE_LEN_BYTES = 16; 18 | 19 | private final byte[] nonce; 20 | 21 | private PskNonceHeader(final byte[] nonce) { 22 | super(WellKnown.NONCE, Base64.getUrlEncoder() 23 | .withoutPadding() 24 | .encodeToString(nonce)); 25 | this.nonce = Arrays.copyOf(nonce, nonce.length); 26 | } 27 | 28 | /** 29 | * Constructs an instance of the header with a randomly generated value. 30 | * 31 | * @return new nonce header 32 | */ 33 | public static PskNonceHeader seed() { 34 | try { 35 | SecureRandom sr = SecureRandom.getInstanceStrong(); 36 | byte[] nonce = new byte[16]; 37 | sr.nextBytes(nonce); 38 | return new PskNonceHeader(nonce); 39 | } catch (NoSuchAlgorithmException nsa) { 40 | throw new InterledgerRuntimeException("Could not generate secure nonce", nsa); 41 | } 42 | } 43 | 44 | /** 45 | * Constructs an instance of the header with the given nonce value. 46 | * 47 | * @param nonce The nonce value. 48 | * @return a {@link PskNonceHeader} instance. 49 | */ 50 | public static PskNonceHeader fromNonce(byte[] nonce) { 51 | Objects.requireNonNull(nonce); 52 | 53 | if (nonce.length != NONCE_LEN_BYTES) { 54 | throw new RuntimeException("Invalid token. Expected 16 bytes."); 55 | } 56 | 57 | byte[] nonceCopy = Arrays.copyOf(nonce, nonce.length); 58 | return new PskNonceHeader(nonceCopy); 59 | } 60 | 61 | /** 62 | * Constructs an instance of the header with the given header's value interpreted as a base64url 63 | * encoded nonce. 64 | * 65 | * @param header The header value. 66 | * @return a {@link PskNonceHeader} instance. 67 | */ 68 | public static PskNonceHeader fromHeader(PskMessage.Header header) { 69 | Objects.requireNonNull(header); 70 | return fromNonce( 71 | Arrays.copyOf(Base64.getUrlDecoder() 72 | .decode(header.getValue()), NONCE_LEN_BYTES)); 73 | } 74 | 75 | 76 | /** 77 | * Convenience method to retrieve the nonce value. 78 | * @return The none in {@link byte[]} format. 79 | */ 80 | public byte[] getNonce() { 81 | return Arrays.copyOf(nonce, nonce.length); 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/test/java/org/interledger/codecs/CodecContextFactoryTest.java: -------------------------------------------------------------------------------- 1 | package org.interledger.codecs; 2 | 3 | import static junit.framework.TestCase.assertTrue; 4 | 5 | import org.interledger.InterledgerAddress; 6 | import org.interledger.codecs.oer.OerIA5StringCodec.OerIA5String; 7 | import org.interledger.codecs.oer.OerLengthPrefixCodec.OerLengthPrefix; 8 | import org.interledger.codecs.oer.OerOctetStringCodec.OerOctetString; 9 | import org.interledger.codecs.oer.OerUint64Codec.OerUint64; 10 | import org.interledger.codecs.oer.OerUint8Codec.OerUint8; 11 | import org.interledger.codecs.packettypes.InterledgerPacketType; 12 | import org.interledger.ilp.InterledgerPayment; 13 | import org.interledger.ilqp.QuoteByDestinationAmountRequest; 14 | import org.interledger.ilqp.QuoteByDestinationAmountResponse; 15 | import org.interledger.ilqp.QuoteBySourceAmountRequest; 16 | import org.interledger.ilqp.QuoteBySourceAmountResponse; 17 | import org.interledger.ilqp.QuoteLiquidityRequest; 18 | import org.interledger.ilqp.QuoteLiquidityResponse; 19 | import org.interledger.ipr.InterledgerPaymentRequest; 20 | import org.interledger.psk.PskMessage; 21 | 22 | import org.junit.Before; 23 | import org.junit.Test; 24 | 25 | /** 26 | * Unit tests for {@link CodecContextFactory}. 27 | */ 28 | public class CodecContextFactoryTest { 29 | 30 | private CodecContext context; 31 | 32 | @Before 33 | public void setup() { 34 | context = CodecContextFactory.interledger(); 35 | } 36 | 37 | @Test 38 | public void interledger() throws Exception { 39 | assertTrue(context.hasRegisteredCodec(OerUint8.class)); 40 | assertTrue(context.hasRegisteredCodec(OerUint64.class)); 41 | assertTrue(context.hasRegisteredCodec(OerOctetString.class)); 42 | assertTrue(context.hasRegisteredCodec(OerLengthPrefix.class)); 43 | assertTrue(context.hasRegisteredCodec(OerIA5String.class)); 44 | 45 | assertTrue(context.hasRegisteredCodec(InterledgerAddress.class)); 46 | assertTrue(context.hasRegisteredCodec(InterledgerPacketType.class)); 47 | assertTrue(context.hasRegisteredCodec(InterledgerPayment.class)); 48 | assertTrue(context.hasRegisteredCodec(InterledgerPaymentRequest.class)); 49 | 50 | //TODO Finish liquidity response 51 | assertTrue(context.hasRegisteredCodec(QuoteLiquidityRequest.class)); 52 | //assertTrue(context.hasRegisteredCodec(QuoteLiquidityResponse.class)); 53 | assertTrue(context.hasRegisteredCodec(QuoteByDestinationAmountRequest.class)); 54 | assertTrue(context.hasRegisteredCodec(QuoteByDestinationAmountResponse.class)); 55 | assertTrue(context.hasRegisteredCodec(QuoteBySourceAmountRequest.class)); 56 | assertTrue(context.hasRegisteredCodec(QuoteBySourceAmountResponse.class)); 57 | 58 | assertTrue(context.hasRegisteredCodec(PskMessage.class)); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/test/java/org/interledger/ilqp/LiquidityPointTest.java: -------------------------------------------------------------------------------- 1 | package org.interledger.ilqp; 2 | 3 | import static junit.framework.TestCase.assertTrue; 4 | import static org.hamcrest.MatcherAssert.assertThat; 5 | import static org.hamcrest.core.Is.is; 6 | import static org.junit.Assert.assertFalse; 7 | import static org.junit.Assert.fail; 8 | 9 | import org.junit.Test; 10 | 11 | import java.math.BigInteger; 12 | 13 | /** 14 | * Unit tests for {@link LiquidityPoint}. 15 | */ 16 | public class LiquidityPointTest { 17 | 18 | @Test 19 | public void testBuild() throws Exception { 20 | final LiquidityPoint liquidityPoint = 21 | LiquidityPoint.builder() 22 | .inputAmount(BigInteger.ONE) 23 | .outputAmount(BigInteger.TEN) 24 | .build(); 25 | 26 | assertThat(liquidityPoint.getInputAmount(), is(BigInteger.ONE)); 27 | assertThat(liquidityPoint.getOutputAmount(), is(BigInteger.TEN)); 28 | } 29 | 30 | @Test 31 | public void testBuildWithNullValues() throws Exception { 32 | try { 33 | LiquidityPoint.builder().build(); 34 | fail(); 35 | } catch (NullPointerException e) { 36 | assertThat(e.getMessage(), is("inputAmount must not be null!")); 37 | } 38 | 39 | try { 40 | LiquidityPoint.builder().inputAmount(BigInteger.ZERO).build(); 41 | fail(); 42 | } catch (NullPointerException e) { 43 | assertThat(e.getMessage(), is("outputAmount must not be null!")); 44 | } 45 | 46 | try { 47 | LiquidityPoint.builder().outputAmount(BigInteger.ZERO).build(); 48 | fail(); 49 | } catch (NullPointerException e) { 50 | assertThat(e.getMessage(), is("inputAmount must not be null!")); 51 | } 52 | } 53 | 54 | @Test 55 | public void testEqualsHashCode() throws Exception { 56 | final LiquidityPoint liquidityPoint1 = 57 | LiquidityPoint.builder() 58 | .inputAmount(BigInteger.ZERO) 59 | .outputAmount(BigInteger.ONE) 60 | .build(); 61 | 62 | final LiquidityPoint liquidityPoint2 = 63 | LiquidityPoint.builder() 64 | .inputAmount(BigInteger.ZERO) 65 | .outputAmount(BigInteger.ONE) 66 | .build(); 67 | 68 | assertTrue(liquidityPoint1.equals(liquidityPoint2)); 69 | assertTrue(liquidityPoint2.equals(liquidityPoint1)); 70 | assertTrue(liquidityPoint1.hashCode() == liquidityPoint2.hashCode()); 71 | 72 | { 73 | final LiquidityPoint liquidityPoint3 = 74 | LiquidityPoint.builder() 75 | .inputAmount(BigInteger.TEN) 76 | .outputAmount(BigInteger.TEN) 77 | .build(); 78 | 79 | assertFalse(liquidityPoint1.equals(liquidityPoint3)); 80 | assertFalse(liquidityPoint3.equals(liquidityPoint1)); 81 | assertFalse(liquidityPoint1.hashCode() == liquidityPoint3.hashCode()); 82 | } 83 | 84 | } 85 | 86 | } -------------------------------------------------------------------------------- /src/main/java/org/interledger/codecs/oer/ilqp/QuoteBySourceAmountRequestOerCodec.java: -------------------------------------------------------------------------------- 1 | package org.interledger.codecs.oer.ilqp; 2 | 3 | import org.interledger.InterledgerAddress; 4 | import org.interledger.codecs.Codec; 5 | import org.interledger.codecs.CodecContext; 6 | import org.interledger.codecs.QuoteBySourceAmountRequestCodec; 7 | import org.interledger.codecs.oer.OerUint32Codec.OerUint32; 8 | import org.interledger.codecs.oer.OerUint64Codec.OerUint64; 9 | import org.interledger.codecs.packettypes.InterledgerPacketType; 10 | import org.interledger.ilqp.QuoteBySourceAmountRequest; 11 | 12 | import java.io.IOException; 13 | import java.io.InputStream; 14 | import java.io.OutputStream; 15 | import java.math.BigInteger; 16 | import java.time.Duration; 17 | import java.time.temporal.ChronoUnit; 18 | import java.util.Objects; 19 | 20 | 21 | /** 22 | * An implementation of {@link Codec} that reads and writes instances of {@link 23 | * QuoteBySourceAmountRequest}. in OER format. 24 | * 25 | * @see "https://github.com/interledger/rfcs/blob/master/asn1/InterledgerQuotingProtocol.asn" 26 | */ 27 | public class QuoteBySourceAmountRequestOerCodec implements QuoteBySourceAmountRequestCodec { 28 | 29 | @Override 30 | public QuoteBySourceAmountRequest read(CodecContext context, InputStream inputStream) 31 | throws IOException { 32 | 33 | Objects.requireNonNull(context); 34 | Objects.requireNonNull(inputStream); 35 | 36 | /* read the destination account Interledger Address. */ 37 | final InterledgerAddress destinationAccount = 38 | context.read(InterledgerAddress.class, inputStream); 39 | 40 | /* read the source amount, which is a uint64 */ 41 | final BigInteger sourceAmount = context.read(OerUint64.class, inputStream).getValue(); 42 | 43 | /* read the destination hold duration which is a unit32 */ 44 | final long destinationHoldDuration = context.read(OerUint32.class, inputStream).getValue(); 45 | 46 | return QuoteBySourceAmountRequest.Builder.builder() 47 | .destinationAccount(destinationAccount) 48 | .sourceAmount(sourceAmount) 49 | .destinationHoldDuration(Duration.of(destinationHoldDuration, ChronoUnit.MILLIS)).build(); 50 | } 51 | 52 | @Override 53 | public void write(CodecContext context, QuoteBySourceAmountRequest instance, 54 | OutputStream outputStream) throws IOException { 55 | 56 | Objects.requireNonNull(context); 57 | Objects.requireNonNull(instance); 58 | Objects.requireNonNull(outputStream); 59 | 60 | /* Write the packet type. */ 61 | context.write(InterledgerPacketType.class, this.getTypeId(), outputStream); 62 | 63 | /* destination account */ 64 | context.write(InterledgerAddress.class, instance.getDestinationAccount(), outputStream); 65 | 66 | /* source amount */ 67 | context.write(OerUint64.class, new OerUint64(instance.getSourceAmount()), outputStream); 68 | 69 | /* destination hold duration, in milliseconds */ 70 | context.write(OerUint32.class, new OerUint32(instance.getDestinationHoldDuration().toMillis()), 71 | outputStream); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/codecs/oer/ilqp/QuoteByDestinationAmountRequestOerCodec.java: -------------------------------------------------------------------------------- 1 | package org.interledger.codecs.oer.ilqp; 2 | 3 | import org.interledger.InterledgerAddress; 4 | import org.interledger.codecs.Codec; 5 | import org.interledger.codecs.CodecContext; 6 | import org.interledger.codecs.QuoteByDestinationAmountRequestCodec; 7 | import org.interledger.codecs.oer.OerUint32Codec.OerUint32; 8 | import org.interledger.codecs.oer.OerUint64Codec.OerUint64; 9 | import org.interledger.codecs.packettypes.InterledgerPacketType; 10 | import org.interledger.ilqp.QuoteByDestinationAmountRequest; 11 | 12 | import java.io.IOException; 13 | import java.io.InputStream; 14 | import java.io.OutputStream; 15 | import java.math.BigInteger; 16 | import java.time.Duration; 17 | import java.time.temporal.ChronoUnit; 18 | import java.util.Objects; 19 | 20 | /** 21 | * An implementation of {@link Codec} that reads and writes instances of {@link 22 | * QuoteByDestinationAmountRequest}. in OER format. 23 | * 24 | * @see "https://github.com/interledger/rfcs/blob/master/asn1/InterledgerQuotingProtocol.asn" 25 | */ 26 | public class QuoteByDestinationAmountRequestOerCodec 27 | implements QuoteByDestinationAmountRequestCodec { 28 | 29 | @Override 30 | public QuoteByDestinationAmountRequest read(CodecContext context, InputStream inputStream) 31 | throws IOException { 32 | 33 | Objects.requireNonNull(context); 34 | Objects.requireNonNull(inputStream); 35 | 36 | /* read the Interledger Address. */ 37 | final InterledgerAddress destinationAccount = 38 | context.read(InterledgerAddress.class, inputStream); 39 | 40 | /* read the destination amount, which is a uint64 */ 41 | final BigInteger destinationAmount = context.read(OerUint64.class, inputStream).getValue(); 42 | 43 | /* read the destination hold duration which is a unit32 */ 44 | final long destinationHoldDuration = context.read(OerUint32.class, inputStream).getValue(); 45 | 46 | return QuoteByDestinationAmountRequest.Builder.builder().destinationAccount(destinationAccount) 47 | .destinationAmount(destinationAmount) 48 | .destinationHoldDuration(Duration.of(destinationHoldDuration, ChronoUnit.MILLIS)).build(); 49 | } 50 | 51 | @Override 52 | public void write(CodecContext context, QuoteByDestinationAmountRequest instance, 53 | OutputStream outputStream) throws IOException { 54 | 55 | Objects.requireNonNull(context); 56 | Objects.requireNonNull(instance); 57 | Objects.requireNonNull(outputStream); 58 | 59 | /* write the packet type. */ 60 | context.write(InterledgerPacketType.class, this.getTypeId(), outputStream); 61 | 62 | /* destination account */ 63 | context.write(InterledgerAddress.class, instance.getDestinationAccount(), outputStream); 64 | 65 | /* destination amount */ 66 | context.write(OerUint64.class, new OerUint64(instance.getDestinationAmount()), outputStream); 67 | 68 | /* destination hold duration, in milliseconds */ 69 | context.write(OerUint32.class, new OerUint32(instance.getDestinationHoldDuration().toMillis()), 70 | outputStream); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/InterledgerRuntimeException.java: -------------------------------------------------------------------------------- 1 | package org.interledger; 2 | 3 | public class InterledgerRuntimeException extends RuntimeException { 4 | 5 | /** 6 | * Constructs a new Interledger runtime exception with the specified detail message. 7 | * The cause is not initialized, and may subsequently be initialized by a 8 | * call to {@link #initCause}. 9 | * 10 | * @param message the detail message. The detail message is saved for 11 | * later retrieval by the {@link #getMessage()} method. 12 | */ 13 | public InterledgerRuntimeException(String message) { 14 | super(message); 15 | } 16 | 17 | /** 18 | * Constructs a new Interledger runtime exception with the specified detail message and 19 | * cause. 20 | * 21 | *

Note that the detail message associated with {@code cause} is not automatically 22 | * incorporated in this runtime exception's detail message. 23 | * 24 | * @param message the detail message (which is saved for later retrieval 25 | * by the {@link #getMessage()} method). 26 | * @param cause the cause (which is saved for later retrieval by the 27 | * {@link #getCause()} method). (A null value is 28 | * permitted, and indicates that the cause is nonexistent or 29 | * unknown.) 30 | */ 31 | public InterledgerRuntimeException(String message, Throwable cause) { 32 | super(message, cause); 33 | } 34 | 35 | /** 36 | * Constructs a new Interledger runtime exception with the specified cause and a 37 | * detail message of (cause==null ? null : cause.toString()) 38 | * (which typically contains the class and detail message of 39 | * cause). This constructor is useful for runtime exceptions 40 | * that are little more than wrappers for other throwables. 41 | * 42 | * @param cause the cause (which is saved for later retrieval by the 43 | * {@link #getCause()} method). (A null value is 44 | * permitted, and indicates that the cause is nonexistent or 45 | * unknown.) 46 | */ 47 | public InterledgerRuntimeException(Throwable cause) { 48 | super(cause); 49 | } 50 | 51 | /** 52 | * Constructs a new Interledger runtime exception with the specified detail 53 | * message, cause, suppression enabled or disabled, and writable 54 | * stack trace enabled or disabled. 55 | * 56 | * @param message the detail message. 57 | * @param cause the cause. (A {@code null} value is permitted, 58 | * and indicates that the cause is nonexistent or unknown.) 59 | * @param enableSuppression whether or not suppression is enabled 60 | * or disabled 61 | * @param writableStackTrace whether or not the stack trace should 62 | * be writable 63 | */ 64 | protected InterledgerRuntimeException(String message, Throwable cause, boolean enableSuppression, 65 | boolean writableStackTrace) { 66 | super(message, cause, enableSuppression, writableStackTrace); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/test/java/org/interledger/codecs/oer/OerGeneralizeTimeCodecTestBadFormat.java: -------------------------------------------------------------------------------- 1 | package org.interledger.codecs.oer; 2 | 3 | import org.interledger.codecs.CodecContext; 4 | import org.interledger.codecs.CodecContextFactory; 5 | import org.interledger.codecs.oer.OerGeneralizedTimeCodec.OerGeneralizedTime; 6 | 7 | import org.junit.Test; 8 | 9 | import java.io.ByteArrayInputStream; 10 | import java.io.IOException; 11 | 12 | /** 13 | * Test cases specifically dealing with reading badly formatted {@link OerGeneralizedTime} 14 | * instances. 15 | */ 16 | public class OerGeneralizeTimeCodecTestBadFormat { 17 | 18 | @Test(expected = IllegalArgumentException.class) 19 | public void test_NoTime() throws IOException { 20 | final CodecContext context = CodecContextFactory.interledger(); 21 | 22 | final byte[] encoded = OerGeneralizedTimeCodecTest.encodeString("20170101"); 23 | final ByteArrayInputStream bis = new ByteArrayInputStream(encoded); 24 | context.read(OerGeneralizedTime.class, bis); 25 | } 26 | 27 | @Test(expected = IllegalArgumentException.class) 28 | public void test_NoMinutes() throws IOException { 29 | final CodecContext context = CodecContextFactory.interledger(); 30 | 31 | final byte[] encoded = OerGeneralizedTimeCodecTest.encodeString("2017010112Z"); 32 | final ByteArrayInputStream bis = new ByteArrayInputStream(encoded); 33 | context.read(OerGeneralizedTime.class, bis); 34 | } 35 | 36 | @Test(expected = IllegalArgumentException.class) 37 | public void test_NoSeconds() throws IOException { 38 | final CodecContext context = CodecContextFactory.interledger(); 39 | 40 | final byte[] encoded = OerGeneralizedTimeCodecTest.encodeString("201701011213Z"); 41 | final ByteArrayInputStream bis = new ByteArrayInputStream(encoded); 42 | context.read(OerGeneralizedTime.class, bis); 43 | } 44 | 45 | @Test(expected = IllegalArgumentException.class) 46 | public void test_NoMillis() throws IOException { 47 | final CodecContext context = CodecContextFactory.interledger(); 48 | 49 | final byte[] encoded = OerGeneralizedTimeCodecTest.encodeString("20170101121314Z"); 50 | final ByteArrayInputStream bis = new ByteArrayInputStream(encoded); 51 | context.read(OerGeneralizedTime.class, bis); 52 | } 53 | 54 | @Test(expected = IllegalArgumentException.class) 55 | public void test_MillisShort() throws IOException { 56 | final CodecContext context = CodecContextFactory.interledger(); 57 | 58 | final byte[] encoded = OerGeneralizedTimeCodecTest.encodeString("20170101121314.01Z"); 59 | final ByteArrayInputStream bis = new ByteArrayInputStream(encoded); 60 | context.read(OerGeneralizedTime.class, bis); 61 | } 62 | 63 | @Test(expected = IllegalArgumentException.class) 64 | public void test_MillisLong() throws IOException { 65 | final CodecContext context = CodecContextFactory.interledger(); 66 | 67 | final byte[] encoded = OerGeneralizedTimeCodecTest.encodeString("20170101121314.0123Z"); 68 | final ByteArrayInputStream bis = new ByteArrayInputStream(encoded); 69 | context.read(OerGeneralizedTime.class, bis); 70 | } 71 | 72 | @Test(expected = IllegalArgumentException.class) 73 | public void test_NoZone() throws IOException { 74 | final CodecContext context = CodecContextFactory.interledger(); 75 | 76 | final byte[] encoded = OerGeneralizedTimeCodecTest.encodeString("20170101121314.012"); 77 | final ByteArrayInputStream bis = new ByteArrayInputStream(encoded); 78 | context.read(OerGeneralizedTime.class, bis); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/test/java/org/interledger/psk/PskMessageBuilderTest.java: -------------------------------------------------------------------------------- 1 | package org.interledger.psk; 2 | 3 | import static org.junit.Assert.assertArrayEquals; 4 | import static org.junit.Assert.assertEquals; 5 | import static org.junit.Assert.assertNotNull; 6 | 7 | import org.interledger.InterledgerRuntimeException; 8 | 9 | import org.junit.Test; 10 | 11 | import java.util.Base64; 12 | 13 | /** 14 | * JUnit to exercise the {@link PskMessage.Builder} implementation. 15 | */ 16 | public class PskMessageBuilderTest { 17 | 18 | @Test 19 | public void test_NonceValueProvided() { 20 | /* add our own nonce header, the builder should not try generate its own */ 21 | String nonce = Base64.getUrlEncoder() 22 | .withoutPadding() 23 | .encodeToString(new byte[16]); 24 | 25 | PskMessage message = 26 | PskMessage.builder() 27 | .addPublicHeader(PskMessage.Header.WellKnown.NONCE, nonce) 28 | .build(); 29 | 30 | assertNotNull(message.getNonceHeader()); 31 | assertEquals(1, message.getPublicHeaders(PskMessage.Header.WellKnown.NONCE) 32 | .size()); 33 | 34 | assertEquals(nonce, message.getNonceHeader() 35 | .getValue()); 36 | } 37 | 38 | @Test 39 | public void test_NonceAndEncryptHeaderGenerated() { 40 | PskMessage message = PskMessage.builder() 41 | .build(); 42 | 43 | assertNotNull(message); 44 | 45 | /* check that a nonce and encryption header was automatically added by the builder */ 46 | assertEquals(2, message.getPublicHeaders() 47 | .size()); 48 | assertNotNull(message.getEncryptionHeader()); 49 | assertEquals(PskEncryptionType.NONE, message.getEncryptionHeader() 50 | .getEncryptionType()); 51 | } 52 | 53 | 54 | @Test(expected = InterledgerRuntimeException.class) 55 | public void test_addPublicHeaderEncryption() { 56 | PskMessage.builder() 57 | .addPublicHeader(PskMessage.Header.WellKnown.ENCRYPTION, "3DES") 58 | .build(); 59 | } 60 | 61 | @Test 62 | public void test_addPrivateHeaderEncryption() { 63 | /* we can add any header we want to the private portion */ 64 | PskMessage message = PskMessage.builder() 65 | .addPrivateHeader(PskMessage.Header.WellKnown.ENCRYPTION, "3DES") 66 | .build(); 67 | 68 | assertNotNull(message); 69 | assertEquals(1, message.getPrivateHeaders() 70 | .size()); 71 | assertEquals("Encryption", message.getPrivateHeaders() 72 | .get(0) 73 | .getName()); 74 | assertEquals("3DES", message.getPrivateHeaders() 75 | .get(0) 76 | .getValue()); 77 | } 78 | 79 | @Test 80 | public void test() { 81 | PskMessage message = 82 | PskMessage.builder() 83 | .addPublicHeader("public_header", "public_header_value") 84 | .addPrivateHeader("private_encryption_header", "3DES") 85 | .data("Application Data".getBytes()) 86 | .build(); 87 | 88 | assertNotNull(message); 89 | assertEquals(3, message.getPublicHeaders() 90 | .size()); 91 | assertEquals(1, message.getPublicHeaders("Nonce") 92 | .size()); 93 | assertEquals("public_header_value", 94 | message.getPublicHeaders("public_header") 95 | .get(0) 96 | .getValue()); 97 | 98 | assertEquals(1, message.getPrivateHeaders() 99 | .size()); 100 | assertEquals("private_encryption_header", message.getPrivateHeaders() 101 | .get(0) 102 | .getName()); 103 | assertEquals("3DES", message.getPrivateHeaders() 104 | .get(0) 105 | .getValue()); 106 | 107 | assertArrayEquals("Application Data".getBytes(), message.getData()); 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /src/test/java/org/interledger/ipr/InterledgerPaymentRequestTest.java: -------------------------------------------------------------------------------- 1 | package org.interledger.ipr; 2 | 3 | import static org.hamcrest.CoreMatchers.is; 4 | import static org.hamcrest.MatcherAssert.assertThat; 5 | 6 | import org.interledger.InterledgerAddress; 7 | import org.interledger.codecs.CodecContext; 8 | import org.interledger.codecs.CodecContextFactory; 9 | import org.interledger.cryptoconditions.Condition; 10 | import org.interledger.cryptoconditions.PreimageSha256Condition; 11 | import org.interledger.ilp.InterledgerPayment; 12 | 13 | import org.junit.Test; 14 | import org.junit.runner.RunWith; 15 | import org.junit.runners.Parameterized; 16 | import org.junit.runners.Parameterized.Parameter; 17 | import org.junit.runners.Parameterized.Parameters; 18 | 19 | import java.io.ByteArrayInputStream; 20 | import java.io.ByteArrayOutputStream; 21 | import java.io.IOException; 22 | import java.math.BigInteger; 23 | import java.util.Arrays; 24 | import java.util.Collection; 25 | 26 | @RunWith(Parameterized.class) 27 | public class InterledgerPaymentRequestTest { 28 | 29 | // first data value (0) is default 30 | @Parameter 31 | public InterledgerPaymentRequest ipr; 32 | 33 | @Parameter(1) 34 | public InterledgerPayment payment; 35 | 36 | @Parameter(2) 37 | public Condition condition; 38 | 39 | /** 40 | * The data for this test... 41 | */ 42 | @Parameters 43 | public static Collection data() throws IOException { 44 | 45 | InterledgerPayment payment1 = 46 | new InterledgerPayment.Builder().destinationAccount(InterledgerAddress.of("test1.foo")) 47 | .destinationAmount(BigInteger.valueOf(100L)).data("test data".getBytes()).build(); 48 | 49 | Condition condition1 = new PreimageSha256Condition(32, new byte[32]); 50 | 51 | InterledgerPaymentRequest ipr1 = 52 | InterledgerPaymentRequest.builder().payment(payment1).condition(condition1).build(); 53 | 54 | InterledgerPayment payment2 = 55 | new InterledgerPayment.Builder().destinationAccount(InterledgerAddress.of("test2.bar")) 56 | .destinationAmount(BigInteger.ZERO).data("other data".getBytes()).build(); 57 | 58 | Condition condition2 = new PreimageSha256Condition(32, 59 | new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 60 | 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 61 | 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2}); 62 | 63 | InterledgerPaymentRequest ipr2 = 64 | InterledgerPaymentRequest.builder().payment(payment2).condition(condition2).build(); 65 | 66 | return Arrays 67 | .asList(new Object[][]{{ipr1, payment1, condition1}, {ipr2, payment2, condition2},}); 68 | } 69 | 70 | @Test 71 | public void testWriteRead() throws Exception { 72 | 73 | final CodecContext context = CodecContextFactory.interledger(); 74 | 75 | // Write the payment to ASN.1... 76 | final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 77 | context.write(ipr, outputStream); 78 | 79 | // Read the bytes using Codecs... 80 | final ByteArrayInputStream asn1OerPaymentBytes = 81 | new ByteArrayInputStream(outputStream.toByteArray()); 82 | 83 | final InterledgerPaymentRequest decodedIpr = 84 | context.read(InterledgerPaymentRequest.class, asn1OerPaymentBytes); 85 | 86 | final InterledgerPayment decodedPayment = decodedIpr.getInterledgerPayment(); 87 | final Condition decodedCondition = decodedIpr.getCondition(); 88 | 89 | // Validate the PSK Info.... 90 | assertThat(decodedPayment.getDestinationAccount(), is(payment.getDestinationAccount())); 91 | assertThat(decodedPayment.getDestinationAmount(), is(payment.getDestinationAmount())); 92 | assertThat(decodedPayment.getData(), is(payment.getData())); 93 | 94 | assertThat(decodedCondition, is(condition)); 95 | 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/codecs/oer/OerSequenceOfAddressCodec.java: -------------------------------------------------------------------------------- 1 | package org.interledger.codecs.oer; 2 | 3 | import org.interledger.InterledgerAddress; 4 | import org.interledger.codecs.Codec; 5 | import org.interledger.codecs.CodecContext; 6 | import org.interledger.codecs.oer.OerSequenceOfAddressCodec.OerSequenceOfAddress; 7 | import org.interledger.codecs.oer.OerUint8Codec.OerUint8; 8 | 9 | import java.io.IOException; 10 | import java.io.InputStream; 11 | import java.io.OutputStream; 12 | import java.util.List; 13 | import java.util.Objects; 14 | import java.util.stream.Collectors; 15 | import java.util.stream.IntStream; 16 | 17 | /** 18 | *

A sequence-of type is encoded as a quantity field followed by the encoding of each occurrence 19 | * of the repeating field of the sequence-of type (zero or more). Extensible and non-extensible 20 | * sequence-of types are encoded in the same way. The quantity field is set to the number of 21 | * occurrences (not to the number of octets), and is encoded as an integer type with a lower bound 22 | * of zero and no upper bound.

23 | */ 24 | public class OerSequenceOfAddressCodec implements Codec { 25 | 26 | @Override 27 | public OerSequenceOfAddress read(final CodecContext context, final InputStream inputStream) 28 | throws IOException { 29 | Objects.requireNonNull(context); 30 | Objects.requireNonNull(inputStream); 31 | 32 | // Read the quantity to get the number of addresses... 33 | final int numAddresses = context.read(OerUint8.class, inputStream).getValue(); 34 | 35 | final List addressList = IntStream.range(0, numAddresses) 36 | .mapToObj(index -> { 37 | try { 38 | return context.read(InterledgerAddress.class, inputStream); 39 | } catch (IOException e) { 40 | throw new RuntimeException(e); 41 | } 42 | }) 43 | .collect(Collectors.toList()); 44 | 45 | return new OerSequenceOfAddress(addressList); 46 | } 47 | 48 | @Override 49 | public void write(CodecContext context, OerSequenceOfAddress instance, OutputStream outputStream) 50 | throws IOException { 51 | 52 | // Write the length... 53 | context.write(new OerUint8(instance.getInterledgerAddresses().size()), outputStream); 54 | 55 | // Write the addresses... 56 | instance.getInterledgerAddresses().forEach(address -> { 57 | try { 58 | context.write(address, outputStream); 59 | } catch (IOException e) { 60 | throw new RuntimeException(e); 61 | } 62 | }); 63 | } 64 | 65 | /** 66 | * An typing mechanism for registering codecs. 67 | */ 68 | public static class OerSequenceOfAddress { 69 | 70 | private List interledgerAddresses; 71 | 72 | public OerSequenceOfAddress(final List interledgerAddresses) { 73 | this.interledgerAddresses = interledgerAddresses; 74 | } 75 | 76 | public List getInterledgerAddresses() { 77 | return interledgerAddresses; 78 | } 79 | 80 | @Override 81 | public boolean equals(Object object) { 82 | if (this == object) { 83 | return true; 84 | } 85 | if (object == null || getClass() != object.getClass()) { 86 | return false; 87 | } 88 | 89 | OerSequenceOfAddress that = (OerSequenceOfAddress) object; 90 | 91 | return interledgerAddresses.equals(that.interledgerAddresses); 92 | } 93 | 94 | @Override 95 | public int hashCode() { 96 | return interledgerAddresses.hashCode(); 97 | } 98 | 99 | @Override 100 | public String toString() { 101 | return "OerSequenceOf{" 102 | + "interledgerAddresses=" + interledgerAddresses 103 | + '}'; 104 | } 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /src/test/java/org/interledger/codecs/oer/ilp/InterledgerAddressOerCodecTests.java: -------------------------------------------------------------------------------- 1 | package org.interledger.codecs.oer.ilp; 2 | 3 | import static org.hamcrest.CoreMatchers.is; 4 | import static org.hamcrest.MatcherAssert.assertThat; 5 | 6 | import org.interledger.InterledgerAddress; 7 | import org.interledger.codecs.CodecContext; 8 | import org.interledger.codecs.CodecContextFactory; 9 | 10 | import org.junit.Test; 11 | import org.junit.runner.RunWith; 12 | import org.junit.runners.Parameterized; 13 | import org.junit.runners.Parameterized.Parameter; 14 | import org.junit.runners.Parameterized.Parameters; 15 | 16 | import java.io.ByteArrayInputStream; 17 | import java.io.ByteArrayOutputStream; 18 | import java.util.Arrays; 19 | import java.util.Collection; 20 | 21 | @RunWith(Parameterized.class) 22 | public class InterledgerAddressOerCodecTests { 23 | 24 | private static final String JUST_RIGHT = 25 | "g.012345678901234567890123456789012345678901234567890123456789012345678901234567890123456" 26 | + "78901234567890123456789012345678901234567890123456789012345678901234567890123456789" 27 | + "01234567890123456789012345678901234567890123456789012345678901234567890123456789012" 28 | + "34567890123456789012345678901234567890123456789012345678901234567890123456789012345" 29 | + "67890123456789012345678901234567890123456789012345678901234567890123456789012345678" 30 | + "90123456789012345678901234567890123456789012345678901234567890123456789012345678901" 31 | + "23456789012345678901234567890123456789012345678901234567890123456789012345678901234" 32 | + "56789012345678901234567890123456789012345678901234567890123456789012345678901234567" 33 | + "89012345678901234567890123456789012345678901234567890123456789012345678901234567890" 34 | + "12345678901234567890123456789012345678901234567890123456789012345678901234567890123" 35 | + "45678901234567890123456789012345678901234567890123456789012345678901234567890123456" 36 | + "78901234567890123456789012345678901234567890123456789012345678901234567890123456789" 37 | + "012345678912312349393"; 38 | 39 | // first data value (0) is default 40 | @Parameter 41 | public InterledgerAddress interledgerAddress; 42 | 43 | /** 44 | * The data for this test... 45 | */ 46 | @Parameters 47 | public static Collection data() { 48 | return Arrays.asList(new Object[][]{{InterledgerAddress.builder().value("test1.").build()}, 49 | {InterledgerAddress.builder().value("test3.foo").build()}, 50 | {InterledgerAddress.builder().value("test2.foo").build()}, 51 | {InterledgerAddress.builder().value("test1.foo").build()}, 52 | {InterledgerAddress.builder().value("test1.foo.bar.baz").build()}, 53 | {InterledgerAddress.builder().value("private.secret.account").build()}, 54 | {InterledgerAddress.builder().value("example.foo").build()}, 55 | {InterledgerAddress.builder().value("peer.foo").build()}, 56 | {InterledgerAddress.builder().value("self.foo").build()}, 57 | {InterledgerAddress.builder().value(JUST_RIGHT).build()},}); 58 | } 59 | 60 | @Test 61 | public void testEqualsHashcode() throws Exception { 62 | final CodecContext context = CodecContextFactory.interledger(); 63 | 64 | final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 65 | context.write(interledgerAddress, outputStream); 66 | 67 | final ByteArrayInputStream byteArrayInputStream = 68 | new ByteArrayInputStream(outputStream.toByteArray()); 69 | 70 | final InterledgerAddress decodedInterledgerAddress = 71 | context.read(InterledgerAddress.class, byteArrayInputStream); 72 | assertThat(decodedInterledgerAddress.getClass().getName(), 73 | is(interledgerAddress.getClass().getName())); 74 | assertThat(decodedInterledgerAddress, is(interledgerAddress)); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/codecs/oer/OerOctetStringCodec.java: -------------------------------------------------------------------------------- 1 | package org.interledger.codecs.oer; 2 | 3 | import static java.lang.String.format; 4 | 5 | import org.interledger.codecs.Codec; 6 | import org.interledger.codecs.CodecContext; 7 | import org.interledger.codecs.CodecException; 8 | import org.interledger.codecs.oer.OerLengthPrefixCodec.OerLengthPrefix; 9 | import org.interledger.codecs.oer.OerOctetStringCodec.OerOctetString; 10 | 11 | import java.io.IOException; 12 | import java.io.InputStream; 13 | import java.io.OutputStream; 14 | import java.util.Arrays; 15 | import java.util.Objects; 16 | 17 | /** 18 | *

An extension of {@link Codec} for reading and writing an ASN.1 OER OctetString.

The 19 | * encoding of OctetString types depends on the size constraint present in the type, if any. 20 | * Interledger's usage of OctetString always uses a dynamic size constraint, so the encoding of the 21 | * string value consists of a length prefix followed by the encodings of each octet.

After 22 | * encoding a length-prefix using an instance of {@link OerLengthPrefixCodec}, each byte in the 23 | * supplied byte array will be encoded in one octet with the highest-order bit set to zero.

24 | */ 25 | public class OerOctetStringCodec implements Codec { 26 | 27 | @Override 28 | public OerOctetString read(final CodecContext context, final InputStream inputStream) 29 | throws IOException { 30 | Objects.requireNonNull(context); 31 | Objects.requireNonNull(inputStream); 32 | 33 | // Detect the length of the encoded OctetString... 34 | final int lengthPrefix = context.read(OerLengthPrefix.class, inputStream) 35 | .getLength(); 36 | 37 | final byte[] returnable = new byte[lengthPrefix]; 38 | 39 | if (lengthPrefix == 0) { 40 | return new OerOctetString(returnable); 41 | } 42 | 43 | int bytesRead = inputStream.read(returnable); 44 | if (bytesRead < lengthPrefix) { 45 | throw new CodecException( 46 | format("Unexpected end of stream. Expected %s bytes but only read %s.", 47 | lengthPrefix, bytesRead)); 48 | } 49 | return new OerOctetString(returnable); 50 | } 51 | 52 | @Override 53 | public void write(final CodecContext context, final OerOctetString instance, 54 | final OutputStream outputStream) throws IOException { 55 | Objects.requireNonNull(context); 56 | Objects.requireNonNull(instance); 57 | Objects.requireNonNull(outputStream); 58 | 59 | // Write the length-prefix, and move the buffer index to the correct spot. 60 | final int numOctets = instance.getValue().length; 61 | context.write(OerLengthPrefix.class, new OerLengthPrefix(numOctets), outputStream); 62 | 63 | // Write the OctetString bytes to the buffer. 64 | if (numOctets > 0) { 65 | outputStream.write(instance.getValue()); 66 | } 67 | } 68 | 69 | /** 70 | * A typing mechanism for registering multiple codecs that operate on the same type (in this case, 71 | * byte[]). 72 | */ 73 | public static class OerOctetString { 74 | 75 | private final byte[] value; 76 | 77 | public OerOctetString(final byte[] value) { 78 | this.value = Objects.requireNonNull(value); 79 | } 80 | 81 | public byte[] getValue() { 82 | return value; 83 | } 84 | 85 | @Override 86 | public boolean equals(Object obj) { 87 | if (this == obj) { 88 | return true; 89 | } 90 | if (obj == null || getClass() != obj.getClass()) { 91 | return false; 92 | } 93 | 94 | OerOctetString that = (OerOctetString) obj; 95 | 96 | return Arrays.equals(value, that.value); 97 | } 98 | 99 | @Override 100 | public int hashCode() { 101 | return Arrays.hashCode(value); 102 | } 103 | 104 | @Override 105 | public String toString() { 106 | return "OctetString{" + "value=" + Arrays.toString(value) + '}'; 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/test/java/org/interledger/ilp/InterledgerPaymentTest.java: -------------------------------------------------------------------------------- 1 | package org.interledger.ilp; 2 | 3 | import static junit.framework.TestCase.assertTrue; 4 | import static org.hamcrest.CoreMatchers.not; 5 | import static org.hamcrest.CoreMatchers.nullValue; 6 | import static org.hamcrest.MatcherAssert.assertThat; 7 | import static org.hamcrest.core.Is.is; 8 | import static org.junit.Assert.assertFalse; 9 | import static org.junit.Assert.fail; 10 | import static org.mockito.Mockito.mock; 11 | 12 | import org.interledger.InterledgerAddress; 13 | import org.interledger.ilp.InterledgerPayment.Builder; 14 | 15 | import org.junit.Test; 16 | 17 | import java.math.BigInteger; 18 | 19 | /** 20 | * Unit tests for {@link InterledgerPayment} and {@link InterledgerPayment.Builder}. 21 | */ 22 | public class InterledgerPaymentTest { 23 | 24 | @Test 25 | public void testBuild() throws Exception { 26 | final InterledgerAddress destinationAccount = mock(InterledgerAddress.class); 27 | final BigInteger destinationAmount = BigInteger.valueOf(25L); 28 | byte[] data = new byte[]{127}; 29 | 30 | final InterledgerPayment interledgerPayment = 31 | InterledgerPayment.builder().destinationAccount(destinationAccount) 32 | .destinationAmount(destinationAmount).data(data).build(); 33 | 34 | assertThat(interledgerPayment.getDestinationAccount(), is(destinationAccount)); 35 | assertThat(interledgerPayment.getDestinationAmount(), is(destinationAmount)); 36 | assertThat(interledgerPayment.getData(), is(data)); 37 | } 38 | 39 | @Test 40 | public void testBuildWithNullValues() throws Exception { 41 | try { 42 | new Builder().build(); 43 | fail(); 44 | } catch (NullPointerException e) { 45 | assertThat(e.getMessage(), is("destinationAccount must not be null!")); 46 | } 47 | 48 | try { 49 | new Builder().destinationAccount(mock(InterledgerAddress.class)).build(); 50 | fail(); 51 | } catch (NullPointerException e) { 52 | assertThat(e.getMessage(), is("destinationAmount must not be null!")); 53 | } 54 | 55 | try { 56 | new Builder().destinationAccount(mock(InterledgerAddress.class)) 57 | .destinationAmount(BigInteger.valueOf(100L)) 58 | .build(); 59 | fail(); 60 | } catch (NullPointerException e) { 61 | assertThat(e.getMessage(), is("data must not be null!")); 62 | } 63 | 64 | final InterledgerPayment interledgerPayment = 65 | new Builder().destinationAccount(mock(InterledgerAddress.class)) 66 | .destinationAmount(BigInteger.valueOf(100L)) 67 | .data(new byte[]{}).build(); 68 | assertThat(interledgerPayment, is(not(nullValue()))); 69 | } 70 | 71 | @Test 72 | public void testEqualsHashCode() throws Exception { 73 | final InterledgerAddress destinationAccount = mock(InterledgerAddress.class); 74 | byte[] data = new byte[]{127}; 75 | 76 | final InterledgerPayment interledgerPayment1 = 77 | new Builder().destinationAccount(destinationAccount) 78 | .destinationAmount(BigInteger.valueOf(100L)) 79 | .data(data).build(); 80 | 81 | final InterledgerPayment interledgerPayment2 = 82 | new Builder().destinationAccount(destinationAccount) 83 | .destinationAmount(BigInteger.valueOf(100L)) 84 | .data(data).build(); 85 | 86 | assertTrue(interledgerPayment1.equals(interledgerPayment2)); 87 | assertTrue(interledgerPayment2.equals(interledgerPayment1)); 88 | assertTrue(interledgerPayment1.hashCode() == interledgerPayment2.hashCode()); 89 | 90 | final InterledgerPayment interledgerPaymentOther = new Builder() 91 | .destinationAccount(destinationAccount).destinationAmount(BigInteger.valueOf(10L)) 92 | .data(data).build(); 93 | 94 | assertFalse(interledgerPayment1.equals(interledgerPaymentOther)); 95 | assertFalse(interledgerPaymentOther.equals(interledgerPayment1)); 96 | assertFalse(interledgerPayment1.hashCode() == interledgerPaymentOther.hashCode()); 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /src/test/java/org/interledger/codecs/packets/PaymentPacketTypeTests.java: -------------------------------------------------------------------------------- 1 | package org.interledger.codecs.packets; 2 | 3 | import static junit.framework.TestCase.assertTrue; 4 | import static org.hamcrest.CoreMatchers.is; 5 | import static org.hamcrest.MatcherAssert.assertThat; 6 | 7 | import org.interledger.codecs.Codec; 8 | import org.interledger.codecs.packettypes.InterledgerPacketType; 9 | import org.interledger.codecs.packettypes.PaymentPacketType; 10 | import org.interledger.codecs.packettypes.QuoteByDestinationAmountRequestPacketType; 11 | import org.interledger.codecs.packettypes.QuoteByDestinationAmountResponsePacketType; 12 | import org.interledger.codecs.packettypes.QuoteBySourceAmountRequestPacketType; 13 | import org.interledger.codecs.packettypes.QuoteBySourceAmountResponsePacketType; 14 | import org.interledger.codecs.packettypes.QuoteLiquidityRequestPacketType; 15 | import org.interledger.codecs.packettypes.QuoteLiquidityResponsePacketType; 16 | 17 | import org.junit.Test; 18 | import org.junit.runner.RunWith; 19 | import org.junit.runners.Parameterized; 20 | import org.junit.runners.Parameterized.Parameter; 21 | import org.junit.runners.Parameterized.Parameters; 22 | 23 | import java.util.Arrays; 24 | import java.util.Collection; 25 | 26 | /** 27 | * Unit tests to validate the {@link Codec} functionality for all Interledger packets. 28 | */ 29 | @RunWith(Parameterized.class) 30 | public class PaymentPacketTypeTests { 31 | 32 | // first data value (0) is default 33 | @Parameter 34 | public InterledgerPacketType firstPacket; 35 | 36 | @Parameter(1) 37 | public InterledgerPacketType secondPacket; 38 | 39 | /** 40 | * The data for this test... 41 | */ 42 | @Parameters 43 | public static Collection data() { 44 | return Arrays.asList(new Object[][]{ 45 | {new PaymentPacketType(), new PaymentPacketType()}, 46 | {new QuoteByDestinationAmountRequestPacketType(), 47 | new QuoteByDestinationAmountRequestPacketType()}, 48 | {new QuoteByDestinationAmountResponsePacketType(), 49 | new QuoteByDestinationAmountResponsePacketType()}, 50 | {new QuoteBySourceAmountRequestPacketType(), new QuoteBySourceAmountRequestPacketType()}, 51 | {new QuoteBySourceAmountResponsePacketType(), new QuoteBySourceAmountResponsePacketType()}, 52 | {new QuoteLiquidityRequestPacketType(), new QuoteLiquidityRequestPacketType()}, 53 | {new QuoteLiquidityResponsePacketType(), new QuoteLiquidityResponsePacketType()}, 54 | {new PaymentPacketType(), 55 | InterledgerPacketType.fromTypeId(InterledgerPacketType.ILP_PAYMENT_TYPE)}, 56 | {new QuoteByDestinationAmountRequestPacketType(), 57 | InterledgerPacketType.fromTypeId( 58 | InterledgerPacketType.ILQP_QUOTE_BY_DESTINATION_AMOUNT_REQUEST_TYPE)}, 59 | {new QuoteByDestinationAmountResponsePacketType(), 60 | InterledgerPacketType.fromTypeId( 61 | InterledgerPacketType.ILQP_QUOTE_BY_DESTINATION_AMOUNT_RESPONSE_TYPE)}, 62 | {new QuoteBySourceAmountRequestPacketType(), 63 | InterledgerPacketType.fromTypeId( 64 | InterledgerPacketType.ILQP_QUOTE_BY_SOURCE_AMOUNT_REQUEST_TYPE)}, 65 | {new QuoteBySourceAmountResponsePacketType(), 66 | InterledgerPacketType.fromTypeId( 67 | InterledgerPacketType.ILQP_QUOTE_BY_SOURCE_AMOUNT_RESPONSE_TYPE)}, 68 | 69 | {new QuoteLiquidityRequestPacketType(), 70 | InterledgerPacketType 71 | .fromTypeId(InterledgerPacketType.ILQP_QUOTE_LIQUIDITY_REQUEST_TYPE)}, 72 | {new QuoteLiquidityResponsePacketType(), InterledgerPacketType 73 | .fromTypeId(InterledgerPacketType.ILQP_QUOTE_LIQUIDITY_RESPONSE_TYPE)},}); 74 | } 75 | 76 | @Test 77 | public void testEqualsHashcode() throws Exception { 78 | assertThat(firstPacket, is(secondPacket)); 79 | assertThat(secondPacket, is(firstPacket)); 80 | 81 | assertTrue(firstPacket.equals(secondPacket)); 82 | assertTrue(secondPacket.equals(firstPacket)); 83 | 84 | assertThat(firstPacket.hashCode(), is(secondPacket.hashCode())); 85 | assertThat(firstPacket, is(secondPacket)); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/ilqp/LiquidityPoint.java: -------------------------------------------------------------------------------- 1 | package org.interledger.ilqp; 2 | 3 | import java.math.BigInteger; 4 | import java.util.Objects; 5 | 6 | /** 7 | * Defines a point on the liquidity curve, a mapping between an input amount (X), and an 8 | * output amount (Y). 9 | */ 10 | public interface LiquidityPoint extends Comparable { 11 | 12 | /** 13 | * Returns the input amount associated with a point on the liquidity curve. 14 | * @return A {@link BigInteger} amount. 15 | */ 16 | BigInteger getInputAmount(); 17 | 18 | /** 19 | * Returns the output amount associated with a point on the liquidity curve. 20 | * @return A {@link BigInteger} amount. 21 | */ 22 | BigInteger getOutputAmount(); 23 | 24 | /** 25 | * Helper-method to access a new {@link Builder} instance. 26 | * 27 | * @return A {@link Builder}. 28 | */ 29 | static Builder builder() { 30 | return new Builder(); 31 | } 32 | 33 | class Builder { 34 | 35 | private BigInteger inputAmount; 36 | private BigInteger outputAmount; 37 | 38 | /** 39 | * Sets the input amount into the builder. 40 | * 41 | * @param inputAmount An instance of {@link BigInteger}. 42 | * @return This {@link Builder} instance. 43 | */ 44 | public Builder inputAmount(BigInteger inputAmount) { 45 | this.inputAmount = Objects.requireNonNull(inputAmount); 46 | return this; 47 | } 48 | 49 | /** 50 | * Sets the output amount into the builder. 51 | * 52 | * @param outputAmount An instance of {@link BigInteger}. 53 | * @return This {@link Builder} instance. 54 | */ 55 | public Builder outputAmount(BigInteger outputAmount) { 56 | this.outputAmount = Objects.requireNonNull(outputAmount); 57 | return this; 58 | } 59 | 60 | /** 61 | * The method that actually constructs a {@link LiquidityPoint} instance. 62 | * 63 | * @return An instance of {@link LiquidityPoint}. 64 | */ 65 | public LiquidityPoint build() { 66 | return new Builder.Impl(this); 67 | } 68 | 69 | public static Builder builder() { 70 | return new Builder(); 71 | } 72 | 73 | /** 74 | * A concrete implementation of {@link LiquidityPoint}. 75 | */ 76 | private static class Impl implements LiquidityPoint { 77 | 78 | private final BigInteger inputAmount; 79 | private final BigInteger outputAmount; 80 | 81 | private Impl(final Builder builder) { 82 | Objects.requireNonNull(builder); 83 | 84 | this.inputAmount = 85 | Objects.requireNonNull(builder.inputAmount, "inputAmount must not be null!"); 86 | 87 | this.outputAmount = 88 | Objects.requireNonNull(builder.outputAmount, "outputAmount must not be null!"); 89 | } 90 | 91 | @Override 92 | public int compareTo(LiquidityPoint other) { 93 | /* ordering of liquidity points are based on the input amounts */ 94 | return inputAmount.compareTo(other.getInputAmount()); 95 | } 96 | 97 | @Override 98 | public BigInteger getInputAmount() { 99 | return this.inputAmount; 100 | } 101 | 102 | @Override 103 | public BigInteger getOutputAmount() { 104 | return this.outputAmount; 105 | } 106 | 107 | @Override 108 | public int hashCode() { 109 | return Objects.hash(inputAmount, outputAmount); 110 | } 111 | 112 | @Override 113 | public boolean equals(Object obj) { 114 | if (this == obj) { 115 | return true; 116 | } 117 | 118 | if (obj == null || getClass() != obj.getClass()) { 119 | return false; 120 | } 121 | 122 | Impl impl = (Impl) obj; 123 | 124 | return Objects.equals(inputAmount, impl.inputAmount) 125 | && Objects.equals(outputAmount, impl.outputAmount); 126 | } 127 | 128 | @Override 129 | public String toString() { 130 | return "LiquidityPoint.Impl{inputAmount=" + inputAmount + ", outputAmount=" + outputAmount 131 | + "}"; 132 | } 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/psk/PskEncryptionHeader.java: -------------------------------------------------------------------------------- 1 | package org.interledger.psk; 2 | 3 | import org.interledger.InterledgerRuntimeException; 4 | import org.interledger.psk.PskMessage.Header; 5 | 6 | import java.util.Arrays; 7 | import java.util.Base64; 8 | import java.util.Objects; 9 | 10 | /** 11 | * Convenience header representing the public Encryption header found in PSK messages. 12 | */ 13 | public class PskEncryptionHeader extends PskMessage.Header { 14 | 15 | private final byte[] authTag; 16 | private final PskEncryptionType type; 17 | 18 | private PskEncryptionHeader() { 19 | super(WellKnown.ENCRYPTION, PskEncryptionType.NONE.toString()); 20 | this.type = PskEncryptionType.NONE; 21 | this.authTag = null; 22 | } 23 | 24 | private PskEncryptionHeader(byte[] authTag) { 25 | super(WellKnown.ENCRYPTION, 26 | PskEncryptionType.AES_256_GCM.toString() 27 | + " " 28 | + Base64.getUrlEncoder() 29 | .withoutPadding() 30 | .encodeToString(authTag)); 31 | this.type = PskEncryptionType.AES_256_GCM; 32 | this.authTag = Arrays.copyOf(authTag, authTag.length); 33 | } 34 | 35 | /** 36 | * Constructs an instance of the header with the encryption type NONE. 37 | * @return A {@link PskEncryptionHeader} of no encryption type. 38 | */ 39 | public static PskEncryptionHeader none() { 40 | return new PskEncryptionHeader(); 41 | } 42 | 43 | /** 44 | * Constructs an instance of the header with the AES-GCM encryption type and provided 45 | * authentication tag value. 46 | * 47 | * @param authenticationTag The authentication tag value. May be null. 48 | * @return A {@link PskEncryptionHeader} instance based on the authentication tag if present. 49 | */ 50 | public static PskEncryptionHeader aesGcm(final byte[] authenticationTag) { 51 | Objects.requireNonNull(authenticationTag); 52 | return new PskEncryptionHeader(authenticationTag); 53 | } 54 | 55 | /** 56 | * Constructs an instance of the header of an existing header with the appropriate values. 57 | * 58 | * @param header An existing header 59 | * 60 | * @return An encryption header 61 | * 62 | * @throws RuntimeException if an encryption header can't be constructed of the given header 63 | */ 64 | public static PskEncryptionHeader fromHeader(Header header) { 65 | 66 | String value = header.getValue() 67 | .trim(); 68 | 69 | if (value.equalsIgnoreCase(PskEncryptionType.NONE.toString())) { 70 | return none(); 71 | } 72 | 73 | String[] tokens = value.split(" "); 74 | if (tokens[0].equalsIgnoreCase(PskEncryptionType.AES_256_GCM.toString())) { 75 | if (tokens.length == 1) { 76 | throw new InterledgerRuntimeException("Invalid AES GCM encryption header. No auth tag."); 77 | } 78 | return aesGcm(Base64.getUrlDecoder() 79 | .decode(tokens[1])); 80 | } 81 | 82 | throw new InterledgerRuntimeException("Invalid encryption header value."); 83 | } 84 | 85 | /** 86 | * Convenience method to retrieve the authentication tag value, if it is present in the header. 87 | * @return the authentication tag in {@link byte[]} format. 88 | */ 89 | public byte[] getAuthenticationTag() { 90 | if (type == PskEncryptionType.NONE) { 91 | return null; 92 | } 93 | return Arrays.copyOf(authTag, authTag.length); 94 | } 95 | 96 | /** 97 | * Returns the encryption type indicated in the header. 98 | * @return The {@link PskEncryptionType} from the header. 99 | */ 100 | public PskEncryptionType getEncryptionType() { 101 | return type; 102 | } 103 | 104 | @Override 105 | public String getName() { 106 | return PskMessage.Header.WellKnown.ENCRYPTION; 107 | } 108 | 109 | @Override 110 | public String getValue() { 111 | if (getEncryptionType() == PskEncryptionType.NONE) { 112 | return PskEncryptionType.NONE.toString(); 113 | } 114 | 115 | return PskEncryptionType.AES_256_GCM.toString() + " " 116 | + Base64.getUrlEncoder() 117 | .withoutPadding() 118 | .encodeToString(this.authTag); 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/ilqp/QuoteLiquidityRequest.java: -------------------------------------------------------------------------------- 1 | package org.interledger.ilqp; 2 | 3 | import org.interledger.InterledgerAddress; 4 | 5 | import java.time.Duration; 6 | import java.util.Objects; 7 | 8 | /** 9 | * A request to receive liquidity information between the current ledger and the destination 10 | * account. This information is sufficient to locally quote any amount until the curve expires. 11 | */ 12 | public interface QuoteLiquidityRequest extends QuoteRequest { 13 | 14 | @Override 15 | InterledgerAddress getDestinationAccount(); 16 | 17 | @Override 18 | Duration getDestinationHoldDuration(); 19 | 20 | /** 21 | * A builder for instances of {@link QuoteLiquidityRequest}. 22 | */ 23 | class Builder { 24 | private InterledgerAddress destinationAccount; 25 | private Duration destinationHoldDuration; 26 | 27 | public static Builder builder() { 28 | return new Builder(); 29 | } 30 | 31 | /** 32 | * Set the destination account address into this builder. 33 | * 34 | * @param destinationAccount An instance of {@link InterledgerAddress}. 35 | * @return This {@link Builder} instance. 36 | */ 37 | public Builder destinationAccount(final InterledgerAddress destinationAccount) { 38 | this.destinationAccount = Objects.requireNonNull(destinationAccount); 39 | return this; 40 | } 41 | 42 | /** 43 | * Set the destination hold duration into this builder. 44 | * 45 | * @param destinationHoldDuration An instance of {@link Duration}. 46 | * @return This {@link Builder} instance. 47 | */ 48 | public Builder destinationHoldDuration(final Duration destinationHoldDuration) { 49 | this.destinationHoldDuration = Objects.requireNonNull(destinationHoldDuration); 50 | return this; 51 | } 52 | 53 | /** 54 | * The method that actually constructs a QuoteByLiquidityRequest. 55 | * 56 | * @return An instance of {@link QuoteLiquidityRequest} 57 | */ 58 | public QuoteLiquidityRequest build() { 59 | return new Builder.Impl(this); 60 | } 61 | 62 | 63 | private static class Impl implements QuoteLiquidityRequest { 64 | 65 | private final InterledgerAddress destinationAccount; 66 | private final Duration destinationHoldDuration; 67 | 68 | /** 69 | * Constructs an instance from the values held in the builder. 70 | * 71 | * @param builder A Builder used to construct {@link QuoteLiquidityRequest} instances. 72 | */ 73 | private Impl(final Builder builder) { 74 | Objects.requireNonNull(builder); 75 | 76 | this.destinationAccount = Objects.requireNonNull(builder.destinationAccount, 77 | "destinationAccount must not be null!"); 78 | 79 | this.destinationHoldDuration = Objects.requireNonNull(builder.destinationHoldDuration, 80 | "destinationHoldDuration must not be null!"); 81 | } 82 | 83 | @Override 84 | public InterledgerAddress getDestinationAccount() { 85 | return this.destinationAccount; 86 | } 87 | 88 | @Override 89 | public Duration getDestinationHoldDuration() { 90 | return this.destinationHoldDuration; 91 | } 92 | 93 | @Override 94 | public boolean equals(Object obj) { 95 | if (this == obj) { 96 | return true; 97 | } 98 | if (obj == null || getClass() != obj.getClass()) { 99 | return false; 100 | } 101 | 102 | Impl impl = (Impl) obj; 103 | 104 | return Objects.equals(destinationAccount, impl.destinationAccount) 105 | && Objects.equals(destinationHoldDuration, impl.destinationHoldDuration); 106 | } 107 | 108 | @Override 109 | public int hashCode() { 110 | return Objects.hash(destinationAccount, destinationHoldDuration); 111 | } 112 | 113 | @Override 114 | public String toString() { 115 | return "QuoteLiquidityRequest.Impl{" + "destinationAccount=" + destinationAccount 116 | + ", destinationHoldDuration=" + destinationHoldDuration + "}"; 117 | } 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/test/java/org/interledger/codecs/oer/OerUint256CodecTest.java: -------------------------------------------------------------------------------- 1 | package org.interledger.codecs.oer; 2 | 3 | import static org.hamcrest.CoreMatchers.is; 4 | import static org.hamcrest.MatcherAssert.assertThat; 5 | 6 | import org.interledger.codecs.CodecContext; 7 | import org.interledger.codecs.oer.OerUint256Codec.OerUint256; 8 | 9 | import org.junit.Before; 10 | import org.junit.Test; 11 | import org.junit.runner.RunWith; 12 | import org.junit.runners.Parameterized; 13 | import org.junit.runners.Parameterized.Parameters; 14 | 15 | import java.io.ByteArrayInputStream; 16 | import java.io.ByteArrayOutputStream; 17 | import java.util.Arrays; 18 | import java.util.Collection; 19 | 20 | // TODO: Need negative tests 21 | 22 | /** 23 | * Parameterized unit tests for encoding an instance of {@link OerUint256Codec}. 24 | */ 25 | @RunWith(Parameterized.class) 26 | public class OerUint256CodecTest { 27 | 28 | private CodecContext codecContext; 29 | private OerUint256Codec oerUint256Codec; 30 | private final byte[] inputValue; 31 | private final byte[] asn1OerBytes; 32 | 33 | /** 34 | * Construct an instance of this parameterized test with the supplied inputs. 35 | * 36 | * @param inputValue A {@code byte[]} representing the unsigned 256bit integer to write in OER 37 | * encoding. 38 | * @param asn1OerBytes The expected value, in binary, of the supplied {@code intValue}. 39 | */ 40 | public OerUint256CodecTest(final byte[] inputValue, final byte[] asn1OerBytes) { 41 | this.inputValue = inputValue; 42 | this.asn1OerBytes = asn1OerBytes; 43 | } 44 | 45 | /** 46 | * The data for this test... 47 | */ 48 | @Parameters 49 | public static Collection data() { 50 | return Arrays.asList(new Object[][]{ 51 | // Input Value as a byte[]; Expected same byte[] in ASN.1 52 | {new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 53 | 0, 0, 0, 0, 0, 0}, 54 | new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 55 | 0, 0, 0, 0, 0, 0, 0}}, 56 | {new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 57 | 7, 8, 9, 0, 1, 2}, 58 | new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 59 | 6, 7, 8, 9, 0, 1, 2}},}); 60 | } 61 | 62 | /** 63 | * Test setup. 64 | */ 65 | @Before 66 | public void setUp() throws Exception { 67 | // Register the codec to be tested... 68 | oerUint256Codec = new OerUint256Codec(); 69 | codecContext = new CodecContext().register(OerUint256.class, oerUint256Codec); 70 | } 71 | 72 | @Test 73 | public void read() throws Exception { 74 | final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(asn1OerBytes); 75 | final byte[] actualValue = oerUint256Codec.read(codecContext, byteArrayInputStream).getValue(); 76 | assertThat(actualValue, is(inputValue)); 77 | } 78 | 79 | @Test 80 | public void write() throws Exception { 81 | final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); 82 | oerUint256Codec.write(codecContext, new OerUint256(inputValue), byteArrayOutputStream); 83 | assertThat(byteArrayOutputStream.toByteArray(), is(this.asn1OerBytes)); 84 | } 85 | 86 | @Test 87 | public void writeThenRead() throws Exception { 88 | // Write... 89 | final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); 90 | oerUint256Codec.write(codecContext, new OerUint256(inputValue), byteArrayOutputStream); 91 | assertThat(byteArrayOutputStream.toByteArray(), is(asn1OerBytes)); 92 | 93 | // Read... 94 | final ByteArrayInputStream byteArrayInputStream = 95 | new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); 96 | final OerUint256 decodedValue = oerUint256Codec.read(codecContext, byteArrayInputStream); 97 | 98 | // Write... 99 | final ByteArrayOutputStream byteArrayOutputStream2 = new ByteArrayOutputStream(); 100 | oerUint256Codec.write(codecContext, decodedValue, byteArrayOutputStream2); 101 | assertThat(byteArrayOutputStream2.toByteArray(), is(asn1OerBytes)); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/test/java/org/interledger/codecs/oer/ilp/InterledgerPaymentOerCodecTests.java: -------------------------------------------------------------------------------- 1 | package org.interledger.codecs.oer.ilp; 2 | 3 | import static org.hamcrest.CoreMatchers.is; 4 | import static org.hamcrest.MatcherAssert.assertThat; 5 | 6 | import org.interledger.InterledgerAddress; 7 | import org.interledger.InterledgerPacket; 8 | import org.interledger.codecs.Codec; 9 | import org.interledger.codecs.CodecContext; 10 | import org.interledger.codecs.CodecContextFactory; 11 | import org.interledger.ilp.InterledgerPayment; 12 | 13 | import org.junit.Test; 14 | import org.junit.runner.RunWith; 15 | import org.junit.runners.Parameterized; 16 | import org.junit.runners.Parameterized.Parameter; 17 | import org.junit.runners.Parameterized.Parameters; 18 | 19 | import java.io.ByteArrayInputStream; 20 | import java.io.ByteArrayOutputStream; 21 | import java.io.IOException; 22 | import java.math.BigInteger; 23 | import java.util.Arrays; 24 | import java.util.Collection; 25 | 26 | /** 27 | * Unit tests to validate the {@link Codec} functionality for all {@link InterledgerPayment} 28 | * packets. 29 | */ 30 | @RunWith(Parameterized.class) 31 | public class InterledgerPaymentOerCodecTests { 32 | 33 | // first data value (0) is default 34 | @Parameter 35 | public InterledgerPacket packet; 36 | 37 | /** 38 | * The data for this test... 39 | */ 40 | @Parameters 41 | public static Collection data() { 42 | 43 | // This ByteArrayOutputStream contains a random amount of 32kb for testing purposes. 44 | final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); 45 | for (int i = 0; i < 32768; i++) { 46 | byteArrayOutputStream.write(i); 47 | } 48 | 49 | return Arrays.asList(new Object[][]{{new InterledgerPayment.Builder() 50 | .destinationAccount(InterledgerAddress.builder().value("test3.foo").build()) 51 | .destinationAmount(BigInteger.valueOf(100L)).data(new byte[]{}).build()}, 52 | 53 | {new InterledgerPayment.Builder() 54 | .destinationAccount(InterledgerAddress.builder().value("test1.bar").build()) 55 | .destinationAmount(BigInteger.valueOf(50L)) 56 | .data(new byte[]{1, 2, 3, 4, 5, 6, 7, 8}).build()}, 57 | 58 | {new InterledgerPayment.Builder() 59 | .destinationAccount(InterledgerAddress.builder().value("test1.bar").build()) 60 | .destinationAmount(BigInteger.valueOf(50L)) 61 | .data(byteArrayOutputStream.toByteArray()).build()}, 62 | 63 | }); 64 | } 65 | 66 | /** 67 | * The primary difference between this test and {@link #testInterledgerPaymentCodec()} is that 68 | * this context call specifies the type, whereas the test below determines the type from the 69 | * payload. 70 | */ 71 | @Test 72 | public void testIndividualRead() throws IOException { 73 | final CodecContext context = CodecContextFactory.interledger(); 74 | final ByteArrayInputStream asn1OerPaymentBytes = constructAsn1OerPaymentBytes(); 75 | 76 | final InterledgerPayment payment = context.read(InterledgerPayment.class, asn1OerPaymentBytes); 77 | assertThat(payment, is(packet)); 78 | } 79 | 80 | /** 81 | * The primary difference between this test and {@link #testIndividualRead()} is that this context 82 | * determines the ipr type from the payload, whereas the test above specifies the type in the 83 | * method call. 84 | */ 85 | @Test 86 | public void testInterledgerPaymentCodec() throws Exception { 87 | final CodecContext context = CodecContextFactory.interledger(); 88 | final ByteArrayInputStream asn1OerPaymentBytes = constructAsn1OerPaymentBytes(); 89 | 90 | final InterledgerPacket decodedPacket = context.read(asn1OerPaymentBytes); 91 | assertThat(decodedPacket.getClass().getName(), is(packet.getClass().getName())); 92 | assertThat(decodedPacket, is(packet)); 93 | } 94 | 95 | private ByteArrayInputStream constructAsn1OerPaymentBytes() throws IOException { 96 | final CodecContext context = CodecContextFactory.interledger(); 97 | 98 | final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 99 | context.write(packet, outputStream); 100 | 101 | return new ByteArrayInputStream(outputStream.toByteArray()); 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/codecs/oer/OerUint256Codec.java: -------------------------------------------------------------------------------- 1 | package org.interledger.codecs.oer; 2 | 3 | import static java.lang.String.format; 4 | 5 | import org.interledger.codecs.Codec; 6 | import org.interledger.codecs.CodecContext; 7 | import org.interledger.codecs.CodecException; 8 | import org.interledger.codecs.oer.OerUint256Codec.OerUint256; 9 | 10 | import java.io.IOException; 11 | import java.io.InputStream; 12 | import java.io.OutputStream; 13 | import java.util.Arrays; 14 | import java.util.Objects; 15 | 16 | /** 17 | *

An extension of {@link Codec} for reading and writing an ASN.1 OER 256-Bit integer type as 18 | * defined by the Interledger ASN.1 definitions.

All Interledger ASN.1 integer types are 19 | * encoded as fixed-size, non-extensible numbers. Thus, for a UInt256 type, the integer value is 20 | * encoded as an unsigned binary integer in 32 octets.

21 | */ 22 | public class OerUint256Codec implements Codec { 23 | 24 | /** 25 | * ASN.1 256BitUInt: Alias for a fixed length octet string of 32 octets. 26 | * 27 | * @param context An instance of {@link CodecContext}. 28 | * @param inputStream An instance of @link InputStream}. 29 | * 30 | * @throws IOException If there is a problem writing to the {@code stream}. 31 | * @throws IllegalArgumentException If the input has a value greater than 18446744073709551615. 32 | */ 33 | @Override 34 | public OerUint256 read(final CodecContext context, final InputStream inputStream) 35 | throws IOException { 36 | Objects.requireNonNull(context); 37 | Objects.requireNonNull(inputStream); 38 | 39 | final byte[] returnable = new byte[32]; 40 | int bytesRead = inputStream.read(returnable); 41 | 42 | if (bytesRead != 32) { 43 | throw new CodecException( 44 | format("Attempted to read a UInt256 and only got %s bytes.", bytesRead)); 45 | } 46 | 47 | return new OerUint256(returnable); 48 | } 49 | 50 | /** 51 | * ASN.1 256BitUInt: Alias for a fixed length octet string of 32 octets. 52 | * 53 | * @param context An instance of {@link CodecContext}. 54 | * @param instance An instance of {@link OerUint256}. 55 | * @param outputStream An instance of {@link OutputStream}. 56 | * 57 | * @throws IOException If there is a problem writing to the {@code stream}. 58 | * @throws IllegalArgumentException If the input has a value greater than 18446744073709551615. 59 | */ 60 | @Override 61 | public void write(final CodecContext context, final OerUint256 instance, 62 | final OutputStream outputStream) throws IOException, IllegalArgumentException { 63 | 64 | Objects.requireNonNull(context); 65 | Objects.requireNonNull(instance); 66 | Objects.requireNonNull(outputStream); 67 | 68 | outputStream.write(instance.getValue()); 69 | } 70 | 71 | /** 72 | * Merely a typing mechanism for registering multiple codecs that operate on the same type. 73 | */ 74 | public static class OerUint256 { 75 | 76 | private final byte[] value; 77 | 78 | /** 79 | * Create a new OerUint256 from the given byte array. 80 | * 81 | * @param value a byte array of 32 bytes. 82 | */ 83 | public OerUint256(final byte[] value) { 84 | if (value.length != 32) { 85 | throw new IllegalArgumentException("Value must be exactly 32 bytes."); 86 | } 87 | 88 | this.value = value; 89 | } 90 | 91 | public byte[] getValue() { 92 | return value; 93 | } 94 | 95 | @Override 96 | public boolean equals(Object obj) { 97 | if (this == obj) { 98 | return true; 99 | } 100 | if (obj == null || getClass() != obj.getClass()) { 101 | return false; 102 | } 103 | 104 | OerUint256 oerUint256 = (OerUint256) obj; 105 | 106 | return Arrays.equals(value, oerUint256.value); 107 | } 108 | 109 | @Override 110 | public int hashCode() { 111 | final int prime = 31; 112 | int result = 1; 113 | result = prime * result + Arrays.hashCode(value); 114 | return result; 115 | } 116 | 117 | 118 | @Override 119 | public String toString() { 120 | return "OerUint256{" 121 | + "value=" + Arrays.toString(value) 122 | + '}'; 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/test/java/org/interledger/codecs/oer/ilqp/IlqpCodecTests.java: -------------------------------------------------------------------------------- 1 | package org.interledger.codecs.oer.ilqp; 2 | 3 | import static org.hamcrest.CoreMatchers.is; 4 | import static org.hamcrest.MatcherAssert.assertThat; 5 | 6 | import org.interledger.InterledgerAddress; 7 | import org.interledger.InterledgerPacket; 8 | import org.interledger.codecs.Codec; 9 | import org.interledger.codecs.CodecContext; 10 | import org.interledger.codecs.CodecContextFactory; 11 | import org.interledger.ilqp.LiquidityCurve; 12 | import org.interledger.ilqp.LiquidityPoint; 13 | import org.interledger.ilqp.QuoteByDestinationAmountRequest; 14 | import org.interledger.ilqp.QuoteByDestinationAmountResponse; 15 | import org.interledger.ilqp.QuoteBySourceAmountRequest; 16 | import org.interledger.ilqp.QuoteBySourceAmountResponse; 17 | import org.interledger.ilqp.QuoteLiquidityRequest; 18 | import org.interledger.ilqp.QuoteLiquidityResponse; 19 | 20 | import org.junit.Test; 21 | import org.junit.runner.RunWith; 22 | import org.junit.runners.Parameterized; 23 | import org.junit.runners.Parameterized.Parameter; 24 | import org.junit.runners.Parameterized.Parameters; 25 | 26 | import java.io.ByteArrayInputStream; 27 | import java.io.ByteArrayOutputStream; 28 | import java.math.BigInteger; 29 | import java.time.Duration; 30 | import java.time.Instant; 31 | import java.time.temporal.ChronoUnit; 32 | 33 | /** 34 | * Unit tests to validate the {@link Codec} functionality for all Interledger packets. 35 | */ 36 | @RunWith(Parameterized.class) 37 | public class IlqpCodecTests { 38 | 39 | // first data value (0) is default 40 | @Parameter 41 | public InterledgerPacket packet; 42 | 43 | /** 44 | * The data for this test... 45 | */ 46 | @Parameters 47 | public static Object[] data() { 48 | return new Object[]{ 49 | QuoteBySourceAmountRequest.Builder.builder() 50 | .destinationAccount(InterledgerAddress.of("test1.foo")) 51 | .sourceAmount(BigInteger.valueOf(100L)) 52 | .destinationHoldDuration(Duration.ofSeconds(30)).build(), 53 | QuoteBySourceAmountResponse.Builder.builder() 54 | .destinationAmount(BigInteger.valueOf(95L)) 55 | .sourceHoldDuration(Duration.ofSeconds(30)).build(), 56 | QuoteByDestinationAmountRequest.Builder.builder() 57 | .destinationAccount(InterledgerAddress.of("test2.foo")) 58 | .destinationAmount(BigInteger.valueOf(100L)) 59 | .destinationHoldDuration(Duration.ofSeconds(35)).build(), 60 | QuoteByDestinationAmountResponse.Builder.builder() 61 | .sourceAmount(BigInteger.valueOf(105L)) 62 | .sourceHoldDuration(Duration.ofMinutes(1)).build(), 63 | QuoteLiquidityRequest.Builder.builder() 64 | .destinationAccount(InterledgerAddress.of("test3.foo")) 65 | .destinationHoldDuration(Duration.ofMinutes(5)).build(), 66 | QuoteLiquidityResponse.Builder.builder() 67 | .liquidityCurve( 68 | LiquidityCurve.Builder 69 | .builder() 70 | .liquidityPoint(LiquidityPoint.Builder.builder() 71 | .inputAmount(BigInteger.ZERO).outputAmount(BigInteger.ZERO).build()) 72 | .liquidityPoint(LiquidityPoint.Builder.builder() 73 | .inputAmount(BigInteger.ONE).outputAmount(BigInteger.ONE).build()) 74 | .liquidityPoint(LiquidityPoint.Builder.builder() 75 | .inputAmount(BigInteger.valueOf(5)).outputAmount(BigInteger.TEN).build()) 76 | .build()) 77 | .appliesTo(InterledgerAddress.of("test1.foo")) 78 | .sourceHoldDuration(Duration.of(10, ChronoUnit.MINUTES)) 79 | .expiresAt(Instant.now()) 80 | .build()}; 81 | } 82 | 83 | @Test 84 | public void testName() throws Exception { 85 | final CodecContext context = CodecContextFactory.interledger(); 86 | 87 | final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 88 | context.write(packet, outputStream); 89 | 90 | final ByteArrayInputStream byteArrayInputStream = 91 | new ByteArrayInputStream(outputStream.toByteArray()); 92 | 93 | final InterledgerPacket decodedPacket = context.read(byteArrayInputStream); 94 | assertThat(decodedPacket.getClass().getName(), is(packet.getClass().getName())); 95 | assertThat(decodedPacket, is(packet)); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/ipr/InterledgerPaymentRequest.java: -------------------------------------------------------------------------------- 1 | package org.interledger.ipr; 2 | 3 | import org.interledger.cryptoconditions.Condition; 4 | import org.interledger.ilp.InterledgerPayment; 5 | 6 | import java.util.Objects; 7 | 8 | /** 9 | * An Interledger Payment Request as defined in ILP RFC 11. 10 | * 11 | * @see "https://github.com/interledger/rfcs/blob/master/0011-interledger-payment-request/0011 12 | * -interledger-payment-request.md" 13 | */ 14 | 15 | public interface InterledgerPaymentRequest { 16 | 17 | /** 18 | * Get the default builder. 19 | * 20 | * @return a {@link Builder} instance. 21 | */ 22 | static Builder builder() { 23 | return new Builder(); 24 | } 25 | 26 | /** 27 | * Get the version of this IPR (this interface represents Version 2). 28 | * 29 | * @return The version of the IPR (currently 2) 30 | */ 31 | default int getVersion() { 32 | return 2; 33 | } 34 | 35 | /** 36 | * The Interledger Payment being requested. 37 | * 38 | * @return an Interledger Payment. 39 | */ 40 | InterledgerPayment getInterledgerPayment(); 41 | 42 | /** 43 | * The {@link Condition} to use when sending the payment. 44 | * 45 | * @return a Condition 46 | */ 47 | Condition getCondition(); 48 | 49 | class Builder { 50 | 51 | private InterledgerPayment interledgerPayment; 52 | private Condition condition; 53 | 54 | /** 55 | * Set the Interledger Payment packet for this IPR. 56 | * 57 | * @param interledgerPayment The Interledger Payment packet to use when building this IPR 58 | * 59 | * @return this builder 60 | */ 61 | public Builder payment(InterledgerPayment interledgerPayment) { 62 | this.interledgerPayment = Objects.requireNonNull(interledgerPayment); 63 | return this; 64 | } 65 | 66 | /** 67 | * Set the Condition for this IPR. 68 | * 69 | * @param condition The {@link Condition} to use when building this IPR 70 | * 71 | * @return this builder 72 | */ 73 | public Builder condition(Condition condition) { 74 | this.condition = Objects.requireNonNull(condition); 75 | return this; 76 | } 77 | 78 | /** 79 | * Get the IPR. 80 | * 81 | *

Calling this will result in the internal PSK message being built (and encrypted unless 82 | * encryption is disabled). 83 | * 84 | *

After the PSK message is built the ILP Packet is built and OER encoded before the 85 | * Condition is generated. 86 | * 87 | * @return an Interledger Payment Request. 88 | */ 89 | public InterledgerPaymentRequest build() { 90 | return new Impl(interledgerPayment, condition); 91 | } 92 | 93 | private static final class Impl implements InterledgerPaymentRequest { 94 | 95 | private static final int VERSION = 2; 96 | 97 | private final InterledgerPayment packet; 98 | private final Condition condition; 99 | 100 | public Impl(InterledgerPayment packet, Condition condition) { 101 | this.packet = packet; 102 | this.condition = condition; 103 | } 104 | 105 | public int getVersion() { 106 | return VERSION; 107 | } 108 | 109 | public InterledgerPayment getInterledgerPayment() { 110 | return packet; 111 | } 112 | 113 | public Condition getCondition() { 114 | return condition; 115 | } 116 | 117 | @Override 118 | public boolean equals(Object other) { 119 | if (this == other) { 120 | return true; 121 | } 122 | if (other == null || getClass() != other.getClass()) { 123 | return false; 124 | } 125 | 126 | Impl impl = (Impl) other; 127 | 128 | return packet.equals(impl.packet) 129 | && condition.equals(impl.condition); 130 | } 131 | 132 | @Override 133 | public int hashCode() { 134 | int result = packet.hashCode(); 135 | result = 31 * result + condition.hashCode(); 136 | return result; 137 | } 138 | 139 | @Override 140 | 141 | public String toString() { 142 | return "InterledgerPaymentRequest.Impl{" 143 | + "packet=" + packet 144 | + ", condition=" + condition 145 | + '}'; 146 | } 147 | } 148 | 149 | } 150 | } 151 | 152 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/ilqp/LiquidityCurve.java: -------------------------------------------------------------------------------- 1 | package org.interledger.ilqp; 2 | 3 | import java.util.Collection; 4 | import java.util.HashSet; 5 | import java.util.Objects; 6 | import java.util.Set; 7 | import java.util.SortedSet; 8 | import java.util.TreeSet; 9 | 10 | /** 11 | * Liquidity curves describe the relationship between input and output amount for a given path 12 | * between a pair of ledgers. 13 | * 14 | *

The curve is expressed as a series of points given as coordinates of the form [inputAmount, 15 | * outputAmount]. If a sender sends 'inputAmount' units to the connector, the recipient will receive 16 | * 'outputAmount'. The curve may represent the liquidity through a single connector, or multiple 17 | * liquidity curves can be combined into one to represent the liquidity through a given path of 18 | * connectors. 19 | * 20 | *

Points are ordered by inputAmount. The inputAmount is strictly increasing from point to point. 21 | * The outputAmount is monotonically increasing, meaning each successively point must have an equal 22 | * or greater outputAmount. 23 | * 24 | *

The first point represents the minimum amount that can be transacted, while the final point 25 | * represents the maximum amount that can be transacted. 26 | * 27 | *

If a query does not match a point exactly, implementations MUST use linear interpolation. When 28 | * querying by outputAmount, if multiple points match exactly, the lowest inputAmount of any of 29 | * these points MUST be returned. 30 | */ 31 | public interface LiquidityCurve { 32 | 33 | public Collection getLiquidityPoints(); 34 | 35 | /** 36 | * A builder for instances of {@link LiquidityCurve}. 37 | */ 38 | class Builder { 39 | private Set points = new HashSet<>(); 40 | 41 | /** 42 | * Sets a liquidity curve into this builder, clearing any liquidity points already set. 43 | * 44 | * @param curve An instance of {@link LiquidityCurve}. 45 | * @return This {@link Builder} instance. 46 | */ 47 | public Builder liquidityCurve(final LiquidityCurve curve) { 48 | Objects.requireNonNull(curve); 49 | this.points.clear(); 50 | this.points.addAll(curve.getLiquidityPoints()); 51 | return this; 52 | } 53 | 54 | /** 55 | * Sets an individual liquidity point into the builder to be added to the curve. 56 | * 57 | * @param liquidityPoint An instance of {@link LiquidityPoint}. 58 | * @return This {@link Builder} instance. 59 | */ 60 | public Builder liquidityPoint(final LiquidityPoint liquidityPoint) { 61 | Objects.requireNonNull(liquidityPoint); 62 | this.points.add(liquidityPoint); 63 | return this; 64 | } 65 | 66 | /** 67 | * The method that actually constructs a {@link LiquidityCurve} instance. 68 | * 69 | * @return An instance of {@link LiquidityCurve}. 70 | */ 71 | public LiquidityCurve build() { 72 | return new Builder.Impl(this); 73 | } 74 | 75 | public static Builder builder() { 76 | return new Builder(); 77 | } 78 | 79 | /** 80 | * Concrete implementation of {@link LiquidityCurve}. 81 | */ 82 | private static class Impl implements LiquidityCurve { 83 | private final SortedSet curve; 84 | 85 | private Impl(Builder builder) { 86 | Objects.requireNonNull(builder); 87 | 88 | Objects.requireNonNull(builder.points, "liquiditypoints must not be null!"); 89 | 90 | this.curve = new TreeSet<>(); 91 | 92 | this.curve.addAll(builder.points); 93 | } 94 | 95 | @Override 96 | public Collection getLiquidityPoints() { 97 | return this.curve; 98 | } 99 | 100 | @Override 101 | public int hashCode() { 102 | return Objects.hashCode(curve); 103 | } 104 | 105 | @Override 106 | public boolean equals(Object obj) { 107 | if (this == obj) { 108 | return true; 109 | } 110 | 111 | if (obj == null || getClass() != obj.getClass()) { 112 | return false; 113 | } 114 | 115 | Impl impl = (Impl) obj; 116 | return curve.equals(impl.curve); 117 | } 118 | 119 | @Override 120 | public String toString() { 121 | return "LiquidityCurve.Impl{curve=" + curve + "}"; 122 | } 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/codecs/oer/OerUint32Codec.java: -------------------------------------------------------------------------------- 1 | package org.interledger.codecs.oer; 2 | 3 | import org.interledger.codecs.Codec; 4 | import org.interledger.codecs.CodecContext; 5 | import org.interledger.codecs.oer.OerUint32Codec.OerUint32; 6 | 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.io.OutputStream; 10 | import java.util.Objects; 11 | 12 | /** 13 | *

An extension of {@link Codec} for reading and writing an ASN.1 OER 32-Bit integer type as 14 | * defined by the Interledger ASN.1 definitions.

15 | *

All Interledger ASN.1 integer types are encoded as fixed-size, non-extensible numbers. Thus, 16 | * for a UInt32 type, the integer value is encoded as an unsigned binary integer in 4 octets, and 17 | * supports values in the range (0..4294967295).

18 | */ 19 | public class OerUint32Codec implements Codec { 20 | 21 | /** 22 | * ASN.1 32BitUInt: If the lower bound of the value range constraint is not less than 0 and the 23 | * upper bound is not greater than 4294967295 and the constraint is not extensible, 24 | * the integer value is encoded as an unsigned binary integer in four octets. 25 | * 26 | * @param context An instance of {@link CodecContext}. 27 | * @param inputStream An instance of @link InputStream}. 28 | * 29 | * @throws IOException If there is a problem writing to the {@code stream}. 30 | * @throws IllegalArgumentException If the input has a value greater than 4294967295. 31 | */ 32 | @Override 33 | public OerUint32 read(final CodecContext context, final InputStream inputStream) 34 | throws IOException { 35 | Objects.requireNonNull(context); 36 | Objects.requireNonNull(inputStream); 37 | 38 | long value = 0; 39 | for (int i = 0; i <= 3; i++) { 40 | value <<= Byte.SIZE; 41 | value |= (inputStream.read() & 0xFF); 42 | } 43 | return new OerUint32(value); 44 | } 45 | 46 | /** 47 | * ASN.1 32BitUInt: If the lower bound of the value range constraint is not less than 0 and the 48 | * upper bound is not greater than 4294967295 and the constraint is not extensible, the integer 49 | * value is encoded as an unsigned binary integer in four octets. 50 | * 51 | * @param context An instance of {@link CodecContext}. 52 | * @param instance An instance of {@link OerUint32}. 53 | * @param outputStream An instance of {@link OutputStream}. 54 | * 55 | * @throws IOException If there is a problem writing to the {@code stream}. 56 | * @throws IllegalArgumentException If the input is out of range. 57 | */ 58 | @Override 59 | public void write( 60 | final CodecContext context, final OerUint32 instance, final OutputStream outputStream 61 | ) throws IOException, IllegalArgumentException { 62 | 63 | Objects.requireNonNull(context); 64 | Objects.requireNonNull(instance); 65 | Objects.requireNonNull(outputStream); 66 | 67 | if (instance.getValue() > 4294967295L || instance.getValue() < 0) { 68 | throw new IllegalArgumentException( 69 | "Interledger Uint32 only supports values from 0 to 4294967295, value " 70 | + instance.getValue() + " is out of range."); 71 | } 72 | 73 | long value = instance.getValue(); 74 | for (int i = 3; i >= 0; i--) { 75 | byte octet = ((byte) ((value >> (Byte.SIZE * i)) & 0xFF)); 76 | outputStream.write(octet); 77 | } 78 | } 79 | 80 | /** 81 | * Merely a typing mechanism for registering multiple codecs that operate on the same type. 82 | */ 83 | public static class OerUint32 { 84 | 85 | private final long value; 86 | 87 | public OerUint32(final long value) { 88 | this.value = value; 89 | } 90 | 91 | public long getValue() { 92 | return value; 93 | } 94 | 95 | @Override 96 | public boolean equals(Object obj) { 97 | if (this == obj) { 98 | return true; 99 | } 100 | if (obj == null || getClass() != obj.getClass()) { 101 | return false; 102 | } 103 | 104 | OerUint32 oerUint32 = (OerUint32) obj; 105 | 106 | return value == oerUint32.value; 107 | } 108 | 109 | @Override 110 | public int hashCode() { 111 | return (int) (value ^ (value >>> 32)); 112 | } 113 | 114 | @Override 115 | public String toString() { 116 | return "OerUint32{" 117 | + "value=" + value 118 | + '}'; 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/test/java/org/interledger/ilqp/QuoteBySourceAmountResponseTest.java: -------------------------------------------------------------------------------- 1 | package org.interledger.ilqp; 2 | 3 | import static junit.framework.TestCase.assertTrue; 4 | import static org.hamcrest.MatcherAssert.assertThat; 5 | import static org.hamcrest.core.Is.is; 6 | import static org.junit.Assert.assertFalse; 7 | import static org.junit.Assert.fail; 8 | 9 | import org.junit.Test; 10 | 11 | import java.math.BigInteger; 12 | import java.time.Duration; 13 | import java.time.temporal.ChronoUnit; 14 | 15 | /** 16 | * Unit tests for {@link QuoteBySourceAmountResponse}. 17 | */ 18 | public class QuoteBySourceAmountResponseTest { 19 | 20 | private static final BigInteger destinationAmount = BigInteger.TEN; 21 | private static final Duration sourceHoldDuration = Duration.ZERO; 22 | 23 | @Test 24 | public void testBuild() throws Exception { 25 | final QuoteBySourceAmountResponse quoteResponse = 26 | QuoteBySourceAmountResponse.builder() 27 | .destinationAmount(destinationAmount) 28 | .sourceHoldDuration(sourceHoldDuration).build(); 29 | 30 | assertThat(quoteResponse.getDestinationAmount(), is(destinationAmount)); 31 | assertThat(quoteResponse.getSourceHoldDuration(), is(sourceHoldDuration)); 32 | } 33 | 34 | 35 | @Test 36 | public void testZeroAmount() throws Exception { 37 | final QuoteBySourceAmountResponse quoteRequest = 38 | QuoteBySourceAmountResponse.builder() 39 | .destinationAmount(BigInteger.ZERO) 40 | .sourceHoldDuration(sourceHoldDuration).build(); 41 | 42 | assertThat(quoteRequest.getDestinationAmount(), is(BigInteger.ZERO)); 43 | assertThat(quoteRequest.getSourceHoldDuration(), is(sourceHoldDuration)); 44 | } 45 | 46 | @Test(expected = IllegalArgumentException.class) 47 | public void testNegativeAmount() throws Exception { 48 | try { 49 | QuoteBySourceAmountResponse.builder() 50 | .destinationAmount(BigInteger.valueOf(-11L)) 51 | .sourceHoldDuration(sourceHoldDuration).build(); 52 | fail(); 53 | } catch (IllegalArgumentException e) { 54 | assertThat(e.getMessage(), is("destinationAmount must be at least 0!")); 55 | throw e; 56 | } 57 | } 58 | 59 | @Test 60 | public void testBuildWithNullValues() throws Exception { 61 | try { 62 | QuoteBySourceAmountResponse.builder().build(); 63 | fail(); 64 | } catch (NullPointerException e) { 65 | assertThat(e.getMessage(), is("destinationAmount must not be null!")); 66 | } 67 | 68 | try { 69 | QuoteBySourceAmountResponse.builder() 70 | .destinationAmount(destinationAmount) 71 | .build(); 72 | fail(); 73 | } catch (NullPointerException e) { 74 | assertThat(e.getMessage(), is("sourceHoldDuration must not be null!")); 75 | } 76 | } 77 | 78 | @Test 79 | public void testEqualsHashCode() throws Exception { 80 | final QuoteBySourceAmountResponse quoteRequest1 = 81 | QuoteBySourceAmountResponse.builder() 82 | .destinationAmount(destinationAmount) 83 | .sourceHoldDuration(sourceHoldDuration) 84 | .build(); 85 | 86 | final QuoteBySourceAmountResponse quoteRequest2 = 87 | QuoteBySourceAmountResponse.builder() 88 | .destinationAmount(destinationAmount) 89 | .sourceHoldDuration(sourceHoldDuration) 90 | .build(); 91 | 92 | assertTrue(quoteRequest1.equals(quoteRequest2)); 93 | assertTrue(quoteRequest2.equals(quoteRequest1)); 94 | assertTrue(quoteRequest1.hashCode() == quoteRequest2.hashCode()); 95 | 96 | { 97 | final QuoteBySourceAmountResponse quoteRequest3 = QuoteBySourceAmountResponse 98 | .builder() 99 | .destinationAmount(destinationAmount) 100 | .sourceHoldDuration(Duration.of(1L, ChronoUnit.SECONDS)) 101 | .build(); 102 | 103 | assertFalse(quoteRequest1.equals(quoteRequest3)); 104 | assertFalse(quoteRequest3.equals(quoteRequest1)); 105 | assertFalse(quoteRequest1.hashCode() == quoteRequest3.hashCode()); 106 | } 107 | 108 | { 109 | final QuoteBySourceAmountResponse quoteRequest4 = QuoteBySourceAmountResponse 110 | .builder() 111 | .destinationAmount(BigInteger.ONE) 112 | .sourceHoldDuration(sourceHoldDuration) 113 | .build(); 114 | 115 | assertFalse(quoteRequest1.equals(quoteRequest4)); 116 | assertFalse(quoteRequest4.equals(quoteRequest1)); 117 | assertFalse(quoteRequest1.hashCode() == quoteRequest4.hashCode()); 118 | } 119 | } 120 | } -------------------------------------------------------------------------------- /src/main/java/org/interledger/codecs/oer/ilqp/QuoteLiquidityResponseOerCodec.java: -------------------------------------------------------------------------------- 1 | package org.interledger.codecs.oer.ilqp; 2 | 3 | import org.interledger.InterledgerAddress; 4 | import org.interledger.codecs.Codec; 5 | import org.interledger.codecs.CodecContext; 6 | import org.interledger.codecs.QuoteLiquidityResponseCodec; 7 | import org.interledger.codecs.oer.OerGeneralizedTimeCodec.OerGeneralizedTime; 8 | import org.interledger.codecs.oer.OerLengthPrefixCodec.OerLengthPrefix; 9 | import org.interledger.codecs.oer.OerUint32Codec.OerUint32; 10 | import org.interledger.codecs.oer.OerUint64Codec.OerUint64; 11 | import org.interledger.codecs.packettypes.InterledgerPacketType; 12 | import org.interledger.ilqp.LiquidityCurve; 13 | import org.interledger.ilqp.LiquidityPoint; 14 | import org.interledger.ilqp.QuoteLiquidityResponse; 15 | 16 | import java.io.IOException; 17 | import java.io.InputStream; 18 | import java.io.OutputStream; 19 | import java.math.BigInteger; 20 | import java.time.Duration; 21 | import java.time.Instant; 22 | import java.time.ZonedDateTime; 23 | import java.time.temporal.ChronoUnit; 24 | import java.util.Collection; 25 | import java.util.Objects; 26 | 27 | /** 28 | * An implementation of {@link Codec} that reads and writes instances of 29 | * {@link QuoteLiquidityResponse} to/from ASN.1 OER format. 30 | * 31 | * @see "https://github.com/interledger/rfcs/blob/master/asn1/InterledgerQuotingProtocol.asn" 32 | */ 33 | public class QuoteLiquidityResponseOerCodec implements QuoteLiquidityResponseCodec { 34 | 35 | @Override 36 | public QuoteLiquidityResponse read(CodecContext context, InputStream inputStream) 37 | throws IOException { 38 | 39 | Objects.requireNonNull(context); 40 | Objects.requireNonNull(inputStream); 41 | 42 | /* read the Liquidity curve */ 43 | int nrLiquidityPoints = context.read(OerLengthPrefix.class, inputStream).getLength(); 44 | 45 | final LiquidityCurve.Builder curveBuilder = LiquidityCurve.Builder.builder(); 46 | 47 | for (int i = 0; i < nrLiquidityPoints; i++) { 48 | final BigInteger x = context.read(OerUint64.class, inputStream).getValue(); 49 | final BigInteger y = context.read(OerUint64.class, inputStream).getValue(); 50 | 51 | final LiquidityPoint point = 52 | LiquidityPoint.Builder.builder().inputAmount(x).outputAmount(y).build(); 53 | 54 | curveBuilder.liquidityPoint(point); 55 | } 56 | 57 | /* read the applies-to Address. */ 58 | final InterledgerAddress appliesTo = context.read(InterledgerAddress.class, inputStream); 59 | 60 | /* read the source hold duration which is a unit32 */ 61 | long sourceHoldDuration = context.read(OerUint32.class, inputStream).getValue(); 62 | 63 | /* read the expires-at timestamp */ 64 | Instant expiresAt = context.read(OerGeneralizedTime.class, inputStream).getValue(); 65 | 66 | return QuoteLiquidityResponse.Builder.builder() 67 | .liquidityCurve(curveBuilder.build()) 68 | .appliesTo(appliesTo) 69 | .sourceHoldDuration(Duration.of(sourceHoldDuration, ChronoUnit.MILLIS)) 70 | .expiresAt(expiresAt) 71 | .build(); 72 | } 73 | 74 | @Override 75 | public void write(CodecContext context, QuoteLiquidityResponse instance, 76 | OutputStream outputStream) throws IOException { 77 | 78 | Objects.requireNonNull(context); 79 | Objects.requireNonNull(instance); 80 | Objects.requireNonNull(outputStream); 81 | 82 | /* write the packet type. */ 83 | context.write(InterledgerPacketType.class, this.getTypeId(), outputStream); 84 | 85 | /* the liquidity curve */ 86 | Collection points = instance.getLiquidityCurve().getLiquidityPoints(); 87 | 88 | context.write(OerLengthPrefix.class, new OerLengthPrefix(points.size()), outputStream); 89 | 90 | for (LiquidityPoint liquidityPoint : points) { 91 | context.write(OerUint64.class, new OerUint64(liquidityPoint.getInputAmount()), outputStream); 92 | context.write(OerUint64.class, new OerUint64(liquidityPoint.getOutputAmount()), outputStream); 93 | } 94 | 95 | /* applies-to prefix */ 96 | context.write(InterledgerAddress.class, instance.getAppliesToPrefix(), outputStream); 97 | 98 | /* source hold duration, in milliseconds */ 99 | context.write(OerUint32.class, new OerUint32(instance.getSourceHoldDuration().toMillis()), 100 | outputStream); 101 | 102 | /* expires at */ 103 | context.write(OerGeneralizedTime.class, new OerGeneralizedTime(instance.getExpiresAt()), 104 | outputStream); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/ilqp/QuoteByDestinationAmountResponse.java: -------------------------------------------------------------------------------- 1 | package org.interledger.ilqp; 2 | 3 | import java.math.BigInteger; 4 | import java.time.Duration; 5 | import java.util.Objects; 6 | 7 | /** 8 | * A quote sent in response to a request of type {@link QuoteByDestinationAmountRequest}. 9 | */ 10 | public interface QuoteByDestinationAmountResponse extends QuoteResponse { 11 | 12 | @Override 13 | Duration getSourceHoldDuration(); 14 | 15 | /** 16 | * The amount the sender needs to send based on the requested destination amount. 17 | * 18 | * @return The amount the sender needs to send. 19 | */ 20 | BigInteger getSourceAmount(); 21 | 22 | /** 23 | * Helper-method to access a new {@link Builder} instance. 24 | * 25 | * @return A {@link Builder}. 26 | */ 27 | static Builder builder() { 28 | return new Builder(); 29 | } 30 | 31 | /** 32 | * A builder for instances of {@link QuoteByDestinationAmountRequest}. 33 | */ 34 | class Builder { 35 | 36 | private BigInteger sourceAmount; 37 | private Duration sourceHoldDuration; 38 | 39 | /** 40 | * Constructs a new builder. 41 | * @return A new {@link Builder} instance. 42 | */ 43 | public static Builder builder() { 44 | return new Builder(); 45 | } 46 | 47 | /** 48 | * Set the source amount into this builder. 49 | * 50 | * @param sourceAmount The source amount value. 51 | * @return This {@link Builder} instance. 52 | */ 53 | public Builder sourceAmount(final BigInteger sourceAmount) { 54 | this.sourceAmount = Objects.requireNonNull(sourceAmount); 55 | return this; 56 | } 57 | 58 | /** 59 | * Set the source hold duration into this builder. 60 | * 61 | * @param sourceHoldDuration An instance of {@link Duration}. 62 | * @return This {@link Builder} instance. 63 | */ 64 | public Builder sourceHoldDuration(final Duration sourceHoldDuration) { 65 | this.sourceHoldDuration = Objects.requireNonNull(sourceHoldDuration); 66 | return this; 67 | } 68 | 69 | /** 70 | * The method that actually constructs a QuoteByDestinationAmountResponse instance. 71 | * 72 | * @return An instance of {@link QuoteByDestinationAmountResponse}. 73 | */ 74 | public QuoteByDestinationAmountResponse build() { 75 | return new Builder.Impl(this); 76 | } 77 | 78 | /** 79 | * A private, immutable implementation of {@link QuoteByDestinationAmountResponse}. 80 | */ 81 | public static class Impl implements QuoteByDestinationAmountResponse { 82 | 83 | private final BigInteger sourceAmount; 84 | private final Duration sourceHoldDuration; 85 | 86 | private Impl(final Builder builder) { 87 | Objects.requireNonNull(builder); 88 | 89 | this.sourceAmount = Objects 90 | .requireNonNull(builder.sourceAmount, "sourceAmount must not be null!"); 91 | if (this.sourceAmount.compareTo(BigInteger.ZERO) < 0) { 92 | throw new IllegalArgumentException("destinationAmount must be at least 0!"); 93 | } 94 | 95 | this.sourceHoldDuration = Objects.requireNonNull(builder.sourceHoldDuration, 96 | "sourceHoldDuration must not be null!"); 97 | } 98 | 99 | 100 | @Override 101 | public Duration getSourceHoldDuration() { 102 | return this.sourceHoldDuration; 103 | } 104 | 105 | @Override 106 | public BigInteger getSourceAmount() { 107 | return this.sourceAmount; 108 | } 109 | 110 | @Override 111 | public boolean equals(Object obj) { 112 | if (this == obj) { 113 | return true; 114 | } 115 | if (obj == null || getClass() != obj.getClass()) { 116 | return false; 117 | } 118 | 119 | Impl impl = (Impl) obj; 120 | 121 | if (!sourceAmount.equals(impl.sourceAmount)) { 122 | return false; 123 | } 124 | return sourceHoldDuration.equals(impl.sourceHoldDuration); 125 | } 126 | 127 | @Override 128 | public int hashCode() { 129 | int result = sourceAmount.hashCode(); 130 | result = 31 * result + sourceHoldDuration.hashCode(); 131 | return result; 132 | } 133 | 134 | @Override 135 | public String toString() { 136 | return "QuoteByDestinationAmountResponse.Impl{" 137 | + "sourceAmount=" + sourceAmount 138 | + ", sourceHoldDuration=" + sourceHoldDuration 139 | + '}'; 140 | } 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/test/java/org/interledger/ilqp/QuoteByDestinationAmountResponseTest.java: -------------------------------------------------------------------------------- 1 | package org.interledger.ilqp; 2 | 3 | import static junit.framework.TestCase.assertTrue; 4 | import static org.hamcrest.MatcherAssert.assertThat; 5 | import static org.hamcrest.core.Is.is; 6 | import static org.junit.Assert.assertFalse; 7 | import static org.junit.Assert.fail; 8 | 9 | import org.junit.Test; 10 | 11 | import java.math.BigInteger; 12 | import java.time.Duration; 13 | import java.time.temporal.ChronoUnit; 14 | 15 | /** 16 | * Unit tests for {@link QuoteByDestinationAmountResponse}. 17 | */ 18 | public class QuoteByDestinationAmountResponseTest { 19 | 20 | private static final BigInteger sourceAmount = BigInteger.TEN; 21 | private static final Duration sourceHoldDuration = Duration.ZERO; 22 | 23 | @Test 24 | public void testBuild() throws Exception { 25 | final QuoteByDestinationAmountResponse quoteResponse = 26 | QuoteByDestinationAmountResponse.builder() 27 | .sourceAmount(sourceAmount) 28 | .sourceHoldDuration(sourceHoldDuration).build(); 29 | 30 | assertThat(quoteResponse.getSourceAmount(), is(sourceAmount)); 31 | assertThat(quoteResponse.getSourceHoldDuration(), is(sourceHoldDuration)); 32 | } 33 | 34 | @Test 35 | public void testZeroAmount() throws Exception { 36 | final QuoteByDestinationAmountResponse quoteRequest = 37 | QuoteByDestinationAmountResponse.builder() 38 | .sourceAmount(BigInteger.ZERO) 39 | .sourceHoldDuration(sourceHoldDuration).build(); 40 | 41 | assertThat(quoteRequest.getSourceAmount(), is(BigInteger.ZERO)); 42 | assertThat(quoteRequest.getSourceHoldDuration(), is(sourceHoldDuration)); 43 | } 44 | 45 | @Test(expected = IllegalArgumentException.class) 46 | public void testNegativeAmount() throws Exception { 47 | try { 48 | QuoteByDestinationAmountResponse.builder() 49 | .sourceAmount(BigInteger.valueOf(-11L)) 50 | .sourceHoldDuration(sourceHoldDuration).build(); 51 | fail(); 52 | } catch (IllegalArgumentException e) { 53 | assertThat(e.getMessage(), is("destinationAmount must be at least 0!")); 54 | throw e; 55 | } 56 | } 57 | 58 | @Test 59 | public void testBuildWithNullValues() throws Exception { 60 | try { 61 | QuoteByDestinationAmountResponse.builder().build(); 62 | fail(); 63 | } catch (NullPointerException e) { 64 | assertThat(e.getMessage(), is("sourceAmount must not be null!")); 65 | } 66 | 67 | try { 68 | QuoteByDestinationAmountResponse.builder() 69 | .sourceAmount(sourceAmount) 70 | .build(); 71 | fail(); 72 | } catch (NullPointerException e) { 73 | assertThat(e.getMessage(), is("sourceHoldDuration must not be null!")); 74 | } 75 | } 76 | 77 | @Test 78 | public void testEqualsHashCode() throws Exception { 79 | final QuoteByDestinationAmountResponse quoteResponse1 = 80 | QuoteByDestinationAmountResponse.builder() 81 | .sourceAmount(sourceAmount) 82 | .sourceHoldDuration(sourceHoldDuration) 83 | .build(); 84 | 85 | final QuoteByDestinationAmountResponse quoteResponse2 = 86 | QuoteByDestinationAmountResponse.builder() 87 | .sourceAmount(sourceAmount) 88 | .sourceHoldDuration(sourceHoldDuration) 89 | .build(); 90 | 91 | assertTrue(quoteResponse1.equals(quoteResponse2)); 92 | assertTrue(quoteResponse2.equals(quoteResponse1)); 93 | assertTrue(quoteResponse1.hashCode() == quoteResponse2.hashCode()); 94 | 95 | { 96 | final QuoteByDestinationAmountResponse quoteResponse3 = QuoteByDestinationAmountResponse 97 | .builder() 98 | .sourceAmount(sourceAmount) 99 | .sourceHoldDuration(Duration.of(1L, ChronoUnit.SECONDS)) 100 | .build(); 101 | 102 | assertFalse(quoteResponse1.equals(quoteResponse3)); 103 | assertFalse(quoteResponse3.equals(quoteResponse1)); 104 | assertFalse(quoteResponse1.hashCode() == quoteResponse3.hashCode()); 105 | } 106 | 107 | { 108 | final QuoteByDestinationAmountResponse quoteResponse4 = QuoteByDestinationAmountResponse 109 | .builder() 110 | .sourceAmount(BigInteger.ONE) 111 | .sourceHoldDuration(sourceHoldDuration) 112 | .build(); 113 | 114 | assertFalse(quoteResponse1.equals(quoteResponse4)); 115 | assertFalse(quoteResponse4.equals(quoteResponse1)); 116 | assertFalse(quoteResponse1.hashCode() == quoteResponse4.hashCode()); 117 | } 118 | } 119 | 120 | } -------------------------------------------------------------------------------- /src/main/java/org/interledger/ilqp/QuoteBySourceAmountResponse.java: -------------------------------------------------------------------------------- 1 | package org.interledger.ilqp; 2 | 3 | import java.math.BigInteger; 4 | import java.time.Duration; 5 | import java.util.Objects; 6 | 7 | /** 8 | * A quote sent in response to a request of type {@link QuoteBySourceAmountRequest}. 9 | */ 10 | public interface QuoteBySourceAmountResponse extends QuoteResponse { 11 | 12 | @Override 13 | Duration getSourceHoldDuration(); 14 | 15 | /** 16 | * Returns the amount that will arrive at the receiver. 17 | * @return A {@link BigInteger} amount 18 | */ 19 | BigInteger getDestinationAmount(); 20 | 21 | /** 22 | * Helper-method to access a new {@link Builder} instance. 23 | * 24 | * @return A {@link Builder}. 25 | */ 26 | static Builder builder() { 27 | return new Builder(); 28 | } 29 | 30 | /** 31 | * A builder for constructing instances of {@link QuoteBySourceAmountResponse}. 32 | */ 33 | class Builder { 34 | 35 | private BigInteger destinationAmount; 36 | private Duration sourceHoldDuration; 37 | 38 | /** 39 | * Constructs a new builder. 40 | * @return A new {@link Builder} instance. 41 | */ 42 | public static Builder builder() { 43 | return new Builder(); 44 | } 45 | 46 | /** 47 | * Set the destination amount into this builder. 48 | * 49 | * @param destinationAmount The destination amount value. 50 | * @return This {@link Builder} instance. 51 | */ 52 | public Builder destinationAmount(final BigInteger destinationAmount) { 53 | this.destinationAmount = Objects.requireNonNull(destinationAmount); 54 | return this; 55 | } 56 | 57 | /** 58 | * Set the source hold duration into this builder. 59 | * 60 | * @param sourceHoldDuration An instance of {@link Duration}. 61 | * @return This {@link Builder} instance. 62 | */ 63 | public Builder sourceHoldDuration(final Duration sourceHoldDuration) { 64 | this.sourceHoldDuration = Objects.requireNonNull(sourceHoldDuration); 65 | return this; 66 | } 67 | 68 | /** 69 | * The method that actually constructs a QuoteBySourceAmountResponse instance. 70 | * 71 | * @return An instance of {@link QuoteBySourceAmountResponse}. 72 | */ 73 | public QuoteBySourceAmountResponse build() { 74 | return new Builder.Impl(this); 75 | } 76 | 77 | /** 78 | * A private, immutable implementation of {@link QuoteBySourceAmountResponse}. 79 | */ 80 | private static class Impl implements QuoteBySourceAmountResponse { 81 | 82 | private final BigInteger destinationAmount; 83 | private final Duration sourceHoldDuration; 84 | 85 | private Impl(final Builder builder) { 86 | Objects.requireNonNull(builder); 87 | 88 | this.destinationAmount = Objects 89 | .requireNonNull(builder.destinationAmount, "destinationAmount must not be null!"); 90 | if (this.destinationAmount.compareTo(BigInteger.ZERO) < 0) { 91 | throw new IllegalArgumentException("destinationAmount must be at least 0!"); 92 | } 93 | 94 | this.sourceHoldDuration = Objects.requireNonNull(builder.sourceHoldDuration, 95 | "sourceHoldDuration must not be null!"); 96 | 97 | } 98 | 99 | @Override 100 | public Duration getSourceHoldDuration() { 101 | return this.sourceHoldDuration; 102 | } 103 | 104 | @Override 105 | public BigInteger getDestinationAmount() { 106 | return this.destinationAmount; 107 | } 108 | 109 | @Override 110 | public boolean equals(Object obj) { 111 | if (this == obj) { 112 | return true; 113 | } 114 | if (obj == null || getClass() != obj.getClass()) { 115 | return false; 116 | } 117 | 118 | Impl impl = (Impl) obj; 119 | 120 | if (!destinationAmount.equals(impl.destinationAmount)) { 121 | return false; 122 | } 123 | return sourceHoldDuration.equals(impl.sourceHoldDuration); 124 | } 125 | 126 | @Override 127 | public int hashCode() { 128 | int result = destinationAmount.hashCode(); 129 | result = 31 * result + sourceHoldDuration.hashCode(); 130 | return result; 131 | } 132 | 133 | @Override 134 | public String toString() { 135 | return "QuoteBySourceAmountResponse.Impl{" 136 | + "destinationAmount=" + destinationAmount 137 | + ", sourceHoldDuration=" + sourceHoldDuration 138 | + '}'; 139 | } 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/codecs/oer/ilp/InterledgerPaymentOerCodec.java: -------------------------------------------------------------------------------- 1 | package org.interledger.codecs.oer.ilp; 2 | 3 | import org.interledger.InterledgerAddress; 4 | import org.interledger.codecs.Codec; 5 | import org.interledger.codecs.CodecContext; 6 | import org.interledger.codecs.InterledgerPaymentCodec; 7 | import org.interledger.codecs.oer.OerOctetStringCodec.OerOctetString; 8 | import org.interledger.codecs.oer.OerUint64Codec.OerUint64; 9 | import org.interledger.codecs.packettypes.InterledgerPacketType; 10 | import org.interledger.ilp.InterledgerPayment; 11 | 12 | import java.io.IOException; 13 | import java.io.InputStream; 14 | import java.io.OutputStream; 15 | import java.math.BigInteger; 16 | import java.util.Objects; 17 | 18 | /** 19 | *

An implementation of {@link Codec} that reads and writes instances of {@link 20 | * InterledgerPayment}.

21 | * 22 | *

The ASN.1 OER definition of an InterledgerPayment defines it as an extensible sequence. Thus, 23 | * this sequence must have the following:

24 | * 25 | *

Presence Bitmap The presence bitmap is encoded as a "bit string" with a fixed size 26 | * constraint, and has one bit for each field of the sequence type that has the keyword OPTIONAL or 27 | * DEFAULT, in specification order. Because the InterledgerPayment has no optional/default values, 28 | * there is no presence bitmap. As an example of this, reference "Overview of OER Encoding" () 29 | * example B, which also has no presence bitmap since no fields are optional/default.

30 | * 31 | *

Extension Presence Bitmap This implementation does not currently support extensions, 32 | * and therefore does not encode or decode an "extension presence bitmap". If it did, in order to 33 | * indicate an extension, the presence bitmap must be present, and the MSB of the bitmap must be 1, 34 | * and further rules. Reference section 2.8 "Encoding of a Sequence Type" in "Overview of OER" for 35 | * more details.

36 | * 37 | *

Components The rest of the packet is the concatenation of the encodings of the fields 38 | * of the sequence type that are present in the value, in specification order.

39 | * 40 | * @see "http://www.oss.com/asn1/resources/books-whitepapers-pubs/Overview%20of%20OER.pdf" 41 | */ 42 | public class InterledgerPaymentOerCodec implements InterledgerPaymentCodec { 43 | 44 | @Override 45 | public InterledgerPayment read(final CodecContext context, final InputStream inputStream) 46 | throws IOException { 47 | Objects.requireNonNull(context); 48 | Objects.requireNonNull(inputStream); 49 | 50 | // 1. InterledgerPayment has no "presence bitmap". See javadoc for this class for more details. 51 | 52 | // 2. InterledgerPayment has no "extension presence bitmap". See javadoc for this class for 53 | // more details. 54 | 55 | // 3. Read the destinationAmount, which is a UInt64. 56 | final BigInteger destinationAmount = context.read(OerUint64.class, inputStream).getValue(); 57 | 58 | // 4. Read the Interledger Address. 59 | final InterledgerAddress destinationAccount = 60 | context.read(InterledgerAddress.class, inputStream); 61 | 62 | // 5. Read the data portion of the packet. 63 | final byte[] data = context.read(OerOctetString.class, inputStream) 64 | .getValue(); 65 | 66 | return InterledgerPayment.builder() 67 | .destinationAmount(destinationAmount) 68 | .destinationAccount(destinationAccount) 69 | .data(data) 70 | .build(); 71 | } 72 | 73 | @Override 74 | public void write(final CodecContext context, final InterledgerPayment instance, 75 | final OutputStream outputStream) throws IOException { 76 | Objects.requireNonNull(context); 77 | Objects.requireNonNull(instance); 78 | Objects.requireNonNull(outputStream); 79 | 80 | // 1. InterledgerPayment has no "presence bitmap". See javadoc for this class for more details. 81 | 82 | // 2. InterledgerPayment has no "extension presence bitmap". See javadoc for this class for 83 | // more details. 84 | 85 | // 3. Write the packet type. 86 | context.write(InterledgerPacketType.class, this.getTypeId(), outputStream); 87 | 88 | // 4. Write the amount, which is a UInt64 (fixed at 8 octets) 89 | context 90 | .write(OerUint64.class, new OerUint64(instance.getDestinationAmount()), outputStream); 91 | 92 | // 5. Write the Interledger Address as an IA5String. 93 | context.write(InterledgerAddress.class, instance.getDestinationAccount(), outputStream); 94 | 95 | // 6. Write the data portion of the packet. 96 | context.write(OerOctetString.class, new OerOctetString(instance.getData()), outputStream); 97 | } 98 | 99 | 100 | } 101 | -------------------------------------------------------------------------------- /src/test/java/org/interledger/codecs/oer/OerUint8CodecTest.java: -------------------------------------------------------------------------------- 1 | package org.interledger.codecs.oer; 2 | 3 | import static org.hamcrest.CoreMatchers.is; 4 | import static org.hamcrest.MatcherAssert.assertThat; 5 | 6 | import org.interledger.codecs.CodecContext; 7 | import org.interledger.codecs.oer.OerUint8Codec.OerUint8; 8 | 9 | import com.google.common.io.BaseEncoding; 10 | import org.junit.Before; 11 | import org.junit.Test; 12 | import org.junit.runner.RunWith; 13 | import org.junit.runners.Parameterized; 14 | import org.junit.runners.Parameterized.Parameters; 15 | 16 | import java.io.ByteArrayInputStream; 17 | import java.io.ByteArrayOutputStream; 18 | import java.io.IOException; 19 | import java.util.Arrays; 20 | import java.util.Collection; 21 | 22 | /** 23 | * Parameterized unit tests for encoding an instance of {@link OerUint8Codec}. 24 | */ 25 | @RunWith(Parameterized.class) 26 | public class OerUint8CodecTest { 27 | 28 | private CodecContext codecContext; 29 | private OerUint8Codec oerUint8Codec; 30 | private final int inputValue; 31 | private final byte[] asn1OerBytes; 32 | 33 | /** 34 | * Construct an instance of this parameterized test with the supplied inputs. 35 | * 36 | * @param inputValue A {@code int} representing the unsigned 8bit integer to write in OER 37 | * encoding. 38 | * @param asn1OerBytes The expected value, in binary, of the supplied {@code intValue}. 39 | */ 40 | public OerUint8CodecTest(final int inputValue, final byte[] asn1OerBytes) { 41 | this.inputValue = inputValue; 42 | this.asn1OerBytes = asn1OerBytes; 43 | } 44 | 45 | /** 46 | * The data for this test... 47 | */ 48 | @Parameters 49 | public static Collection data() { 50 | return Arrays.asList(new Object[][]{ 51 | // Input Value as a int; Expected byte[] in ASN.1 52 | // 0 53 | {0, BaseEncoding.base16().decode("00")}, 54 | // 1 55 | {1, BaseEncoding.base16().decode("01")}, 56 | // 2 57 | {2, BaseEncoding.base16().decode("02")}, 58 | // 3 59 | {127, BaseEncoding.base16().decode("7F")}, 60 | // 4 61 | {128, BaseEncoding.base16().decode("80")}, 62 | // 5 63 | {254, BaseEncoding.base16().decode("FE")}, 64 | // 6 65 | {255, BaseEncoding.base16().decode("FF")},}); 66 | } 67 | 68 | /** 69 | * Test setup. 70 | */ 71 | @Before 72 | public void setUp() throws Exception { 73 | // Register the codec to be tested... 74 | oerUint8Codec = new OerUint8Codec(); 75 | codecContext = new CodecContext().register(OerUint8.class, oerUint8Codec); 76 | } 77 | 78 | @Test 79 | public void read() throws Exception { 80 | final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(asn1OerBytes); 81 | final int actualValue = oerUint8Codec.read(codecContext, byteArrayInputStream).getValue(); 82 | assertThat(actualValue, is(inputValue)); 83 | } 84 | 85 | @Test 86 | public void write() throws Exception { 87 | final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); 88 | oerUint8Codec.write(codecContext, new OerUint8(inputValue), byteArrayOutputStream); 89 | assertThat(byteArrayOutputStream.toByteArray(), is(asn1OerBytes)); 90 | } 91 | 92 | @Test 93 | public void writeThenRead() throws Exception { 94 | // Write... 95 | final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); 96 | oerUint8Codec.write(codecContext, new OerUint8(inputValue), byteArrayOutputStream); 97 | assertThat(byteArrayOutputStream.toByteArray(), is(asn1OerBytes)); 98 | 99 | // Read... 100 | final ByteArrayInputStream byteArrayInputStream = 101 | new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); 102 | final OerUint8 decodedValue = oerUint8Codec.read(codecContext, byteArrayInputStream); 103 | 104 | // Write... 105 | final ByteArrayOutputStream byteArrayOutputStream2 = new ByteArrayOutputStream(); 106 | oerUint8Codec.write(codecContext, decodedValue, byteArrayOutputStream2); 107 | assertThat(byteArrayOutputStream2.toByteArray(), is(asn1OerBytes)); 108 | } 109 | 110 | /** 111 | * Validate an overflow amount. 112 | */ 113 | @Test(expected = IllegalArgumentException.class) 114 | public void write8BitUInt_Overflow() throws IOException { 115 | try { 116 | final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); 117 | oerUint8Codec.write(codecContext, new OerUint8(256), byteArrayOutputStream); 118 | } catch (IllegalArgumentException e) { 119 | assertThat(e.getMessage(), is("Interledger UInt8 values may only contain up to 8 bits!")); 120 | throw e; 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/test/java/org/interledger/InterledgerAddressSchemeTest.java: -------------------------------------------------------------------------------- 1 | package org.interledger; 2 | 3 | import static org.hamcrest.CoreMatchers.is; 4 | import static org.hamcrest.MatcherAssert.assertThat; 5 | 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import org.junit.runners.Parameterized; 9 | import org.junit.runners.Parameterized.Parameters; 10 | 11 | import java.util.Arrays; 12 | 13 | /** 14 | * Unit tests for {@link InterledgerAddress.Builder} schemes. 15 | */ 16 | @RunWith(Parameterized.class) 17 | public class InterledgerAddressSchemeTest { 18 | 19 | private static final String EXPECTED_ERROR_MESSAGE = 20 | "Invalid characters in address: ['%s']. Reference Interledger RFC-15 for proper format."; 21 | private final String scheme; 22 | 23 | public InterledgerAddressSchemeTest(final String scheme) { 24 | this.scheme = scheme; 25 | } 26 | 27 | /** 28 | * Generates an {@link Iterable} of arrays containing Strings that will be passed to each of the 29 | * test methods of this test. 30 | */ 31 | @Parameters(name = "{index}: scheme({0})") 32 | public static Iterable schemes() { 33 | return Arrays.asList(new Object[][]{{"g"}, {"private"}, {"example"}, {"peer"}, {"self"}, 34 | {"test1"}, {"test2"}, {"test3"}}); 35 | } 36 | 37 | /** 38 | * Assert that something like "g.foo.bob" is valid. 39 | */ 40 | @Test 41 | public void test_scheme_with_neighborhood_and_account_as_address() throws Exception { 42 | final InterledgerAddress address = 43 | InterledgerAddress.builder().value(this.scheme + ".foo.bob").build(); 44 | assertThat(address.getValue(), is(this.scheme + ".foo.bob")); 45 | assertThat(address.isLedgerPrefix(), is(false)); 46 | } 47 | 48 | /** 49 | * Assert that something like "g.foo.bob." is valid. 50 | */ 51 | @Test 52 | public void test_scheme_with_neighborhood_and_ledger_identifier_as_prefix() throws Exception { 53 | final InterledgerAddress addressPrefix = 54 | InterledgerAddress.builder().value(this.scheme + ".foo.bob.").build(); 55 | assertThat(addressPrefix.getValue(), is(this.scheme + ".foo.bob.")); 56 | assertThat(addressPrefix.isLedgerPrefix(), is(true)); 57 | } 58 | 59 | /** 60 | * Assert that something like "g.foo" is valid. 61 | */ 62 | @Test 63 | public void test_scheme_with_only_address() throws Exception { 64 | final InterledgerAddress address = 65 | InterledgerAddress.builder().value(this.scheme + ".foo").build(); 66 | assertThat(address.getValue(), is(this.scheme + ".foo")); 67 | assertThat(address.isLedgerPrefix(), is(false)); 68 | } 69 | 70 | /** 71 | * Assert that something like "g.foo." is valid. 72 | */ 73 | @Test 74 | public void test_scheme_with_neighborhood_as_prefix() throws Exception { 75 | final InterledgerAddress addressPrefix = 76 | InterledgerAddress.builder().value(this.scheme + ".foo.").build(); 77 | assertThat(addressPrefix.getValue(), is(this.scheme + ".foo.")); 78 | assertThat(addressPrefix.isLedgerPrefix(), is(true)); 79 | } 80 | 81 | /** 82 | * Assert that something like "g." is valid. 83 | */ 84 | @Test 85 | public void test_address_with_only_scheme_prefix() throws Exception { 86 | final InterledgerAddress address = 87 | InterledgerAddress.builder().value(this.scheme + ".").build(); 88 | assertThat(address.getValue(), is(this.scheme + ".")); 89 | assertThat(address.isLedgerPrefix(), is(true)); 90 | } 91 | 92 | /** 93 | * Assert that something like "g" is invalid. 94 | */ 95 | @Test(expected = IllegalArgumentException.class) 96 | public void test_address_with_only_scheme_address() throws Exception { 97 | final String value = this.scheme; 98 | try { 99 | InterledgerAddress.builder().value(this.scheme).build(); 100 | } catch (IllegalArgumentException e) { 101 | assertThat(e.getMessage(), is(String.format(EXPECTED_ERROR_MESSAGE, value))); 102 | throw e; 103 | } 104 | } 105 | 106 | @Test(expected = IllegalArgumentException.class) 107 | public void test_destination_address_with_invalid_scheme() throws Exception { 108 | final String value = this.scheme + "1.foo"; 109 | try { 110 | InterledgerAddress.builder().value(value).build(); 111 | } catch (IllegalArgumentException e) { 112 | assertThat(e.getMessage(), is(String.format(EXPECTED_ERROR_MESSAGE, value))); 113 | throw e; 114 | } 115 | } 116 | 117 | @Test(expected = IllegalArgumentException.class) 118 | public void test_prefix_with_invalid_scheme() throws Exception { 119 | final String value = this.scheme + "1.foo."; 120 | try { 121 | InterledgerAddress.builder().value(this.scheme + "1.foo.").build(); 122 | } catch (IllegalArgumentException e) { 123 | assertThat(e.getMessage(), is(String.format(EXPECTED_ERROR_MESSAGE, value))); 124 | throw e; 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/codecs/packettypes/InterledgerPacketType.java: -------------------------------------------------------------------------------- 1 | package org.interledger.codecs.packettypes; 2 | 3 | import org.interledger.InterledgerRuntimeException; 4 | 5 | import java.net.URI; 6 | import java.util.Objects; 7 | 8 | /** 9 | * An interface that defines how Interledger Packets are typed using ASN.1 OER encoding. 10 | */ 11 | public interface InterledgerPacketType { 12 | 13 | int ILP_PAYMENT_TYPE = 1; 14 | int ILQP_QUOTE_LIQUIDITY_REQUEST_TYPE = 2; 15 | int ILQP_QUOTE_LIQUIDITY_RESPONSE_TYPE = 3; 16 | int ILQP_QUOTE_BY_SOURCE_AMOUNT_REQUEST_TYPE = 4; 17 | int ILQP_QUOTE_BY_SOURCE_AMOUNT_RESPONSE_TYPE = 5; 18 | int ILQP_QUOTE_BY_DESTINATION_AMOUNT_REQUEST_TYPE = 6; 19 | int ILQP_QUOTE_BY_DESTINATION_AMOUNT_RESPONSE_TYPE = 7; 20 | int INTERLEDGER_PROTOCOL_ERROR = 8; 21 | 22 | /** 23 | * A helper method that will translate an integer into an instance of {@link 24 | * InterledgerPacketType}. Note that this method only handled standard Interledger packets types. 25 | * To operate upon non-standard packets, a different method should be used. 26 | * 27 | * @param type The integer type. 28 | * 29 | * @return An instance of {@link InterledgerPacketType}. 30 | * 31 | * @throws InvalidPacketTypeException If the supplied {@code type} is invalid. 32 | */ 33 | static InterledgerPacketType fromTypeId(final int type) throws InvalidPacketTypeException { 34 | switch (type) { 35 | case ILP_PAYMENT_TYPE: 36 | return new PaymentPacketType(); 37 | case ILQP_QUOTE_LIQUIDITY_REQUEST_TYPE: 38 | return new QuoteLiquidityRequestPacketType(); 39 | case ILQP_QUOTE_LIQUIDITY_RESPONSE_TYPE: 40 | return new QuoteLiquidityResponsePacketType(); 41 | case ILQP_QUOTE_BY_SOURCE_AMOUNT_REQUEST_TYPE: 42 | return new QuoteBySourceAmountRequestPacketType(); 43 | case ILQP_QUOTE_BY_SOURCE_AMOUNT_RESPONSE_TYPE: 44 | return new QuoteBySourceAmountResponsePacketType(); 45 | case ILQP_QUOTE_BY_DESTINATION_AMOUNT_REQUEST_TYPE: 46 | return new QuoteByDestinationAmountRequestPacketType(); 47 | case ILQP_QUOTE_BY_DESTINATION_AMOUNT_RESPONSE_TYPE: 48 | return new QuoteByDestinationAmountResponsePacketType(); 49 | case INTERLEDGER_PROTOCOL_ERROR: 50 | return new InterledgerErrorPacketType(); 51 | default: 52 | throw new InvalidPacketTypeException( 53 | String.format("%s is an unsupported Packet Type!", type)); 54 | } 55 | } 56 | 57 | /** 58 | * The packet's type identifier, as specified by IL-RFC-3. 59 | * 60 | * @return An {@link Integer} representing the type of this packet. 61 | */ 62 | Integer getTypeIdentifier(); 63 | 64 | /** 65 | * A URI representing the formal type of this packet per the Interledger Header Type registry 66 | * maintained at IANA. 67 | * 68 | * @return An instance of {@link String}. 69 | * 70 | * @see "http://www.iana.org/assignments/interledger-header-types" 71 | */ 72 | URI getTypeUri(); 73 | 74 | /** 75 | * An exception that indicates if a packet type is invalid for the current implementation. 76 | */ 77 | class InvalidPacketTypeException extends InterledgerRuntimeException { 78 | 79 | private static final long serialVersionUID = 6086784345849001539L; 80 | 81 | public InvalidPacketTypeException(String message) { 82 | super(message); 83 | } 84 | } 85 | 86 | /** 87 | * An abstract implementation of {@link InterledgerPacketType}. 88 | */ 89 | abstract class AbstractInterledgerPacketType implements InterledgerPacketType { 90 | 91 | private final Integer typeIdentifier; 92 | private final URI typeUri; 93 | 94 | protected AbstractInterledgerPacketType(final Integer typeIdentifier, final URI typeUri) { 95 | this.typeIdentifier = Objects.requireNonNull(typeIdentifier); 96 | this.typeUri = Objects.requireNonNull(typeUri); 97 | } 98 | 99 | public Integer getTypeIdentifier() { 100 | return typeIdentifier; 101 | } 102 | 103 | public URI getTypeUri() { 104 | return typeUri; 105 | } 106 | 107 | @Override 108 | public String toString() { 109 | return "AbstractInterledgerPacketType{" 110 | + "typeIdentifier=" + typeIdentifier 111 | + ", typeUri=" + typeUri 112 | + '}'; 113 | } 114 | 115 | @Override 116 | public boolean equals(Object obj) { 117 | if (this == obj) { 118 | return true; 119 | } 120 | if (obj == null || getClass() != obj.getClass()) { 121 | return false; 122 | } 123 | 124 | AbstractInterledgerPacketType that = (AbstractInterledgerPacketType) obj; 125 | 126 | return typeIdentifier.equals(that.typeIdentifier) 127 | && typeUri.equals(that.typeUri); 128 | } 129 | 130 | @Override 131 | public int hashCode() { 132 | int result = typeIdentifier.hashCode(); 133 | result = 31 * result + typeUri.hashCode(); 134 | return result; 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/codecs/oer/OerUint64Codec.java: -------------------------------------------------------------------------------- 1 | package org.interledger.codecs.oer; 2 | 3 | import org.interledger.codecs.Codec; 4 | import org.interledger.codecs.CodecContext; 5 | import org.interledger.codecs.oer.OerUint64Codec.OerUint64; 6 | 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.io.OutputStream; 10 | import java.math.BigInteger; 11 | import java.util.Objects; 12 | 13 | /** 14 | *

An extension of {@link Codec} for reading and writing an ASN.1 OER 64-Bit unsigned integer 15 | * type as defined by the Interledger ASN.1 definitions.

All Interledger ASN.1 integer types 16 | * are encoded as fixed-size, non-extensible numbers. Thus, for a UInt64 type, the integer value is 17 | * encoded as an unsigned binary integer in 8 octets, and supports values in the range 18 | * (0..18446744073709551615).

19 | */ 20 | public class OerUint64Codec implements Codec { 21 | 22 | /** 23 | * ASN.1 64BitUInt: If the lower bound of the value range constraint is not less than 0 and the 24 | * upper bound is not greater than 18446744073709551615 and the constraint is not extensible, the 25 | * integer value is encoded as an unsigned binary integer in eight octets. 26 | * 27 | * @param context An instance of {@link CodecContext}. 28 | * @param inputStream An instance of @link InputStream}. 29 | * 30 | * @throws IOException If there is a problem writing to the {@code stream}. 31 | * @throws IllegalArgumentException If the input has a value greater than 18446744073709551615. 32 | */ 33 | @Override 34 | public OerUint64 read(final CodecContext context, final InputStream inputStream) 35 | throws IOException { 36 | Objects.requireNonNull(context); 37 | Objects.requireNonNull(inputStream); 38 | 39 | byte[] value = new byte[8]; 40 | int read = inputStream.read(value); 41 | 42 | if (read != 8) { 43 | throw new IOException("unexpected end of stream. expected 8 bytes, read " + read); 44 | } 45 | 46 | return new OerUint64(new BigInteger(1, value)); 47 | } 48 | 49 | /** 50 | * ASN.1 64BitUInt: If the lower bound of the value range constraint is not less than 0 and the 51 | * upper bound is not greater than 18446744073709551615 and the constraint is not extensible, the 52 | * integer value is encoded as an unsigned binary integer in eight octets. 53 | * 54 | * @param context An instance of {@link CodecContext}. 55 | * @param instance An instance of {@link OerUint64}. 56 | * @param outputStream An instance of {@link OutputStream}. 57 | * 58 | * @throws IOException If there is a problem writing to the {@code stream}. 59 | * @throws IllegalArgumentException If the input has a value greater than 18446744073709551615. 60 | */ 61 | @Override 62 | public void write(final CodecContext context, final OerUint64 instance, 63 | final OutputStream outputStream) throws IOException, IllegalArgumentException { 64 | 65 | Objects.requireNonNull(context); 66 | Objects.requireNonNull(instance); 67 | Objects.requireNonNull(outputStream); 68 | 69 | byte[] value = instance.getValue() 70 | .toByteArray(); 71 | 72 | /* BigInteger's toByteArray writes data in two's complement, so positive values requiring 64 73 | * bits will include a leading byte set to 0 which we don't want. */ 74 | if (value.length > 8) { 75 | outputStream.write(value, value.length - 8, 8); 76 | return; 77 | } 78 | 79 | /* BigInteger.toByteArray will return the smallest byte array possible. We are committed 80 | * to a fixed number of bytes, so we might need to pad the value out. */ 81 | for (int i = 0; i < 8 - value.length; i++) { 82 | outputStream.write(0); 83 | } 84 | outputStream.write(value); 85 | } 86 | 87 | /** 88 | * Merely a typing mechanism for registering multiple codecs that operate on the same type. 89 | */ 90 | public static class OerUint64 { 91 | 92 | private final BigInteger value; 93 | 94 | /** 95 | * Constructs an OerUint64 instance. 96 | * 97 | * @param value The value to read or write as an OER 64-bit int value. 98 | **/ 99 | public OerUint64(final BigInteger value) { 100 | this.value = value; 101 | } 102 | 103 | public BigInteger getValue() { 104 | return value; 105 | } 106 | 107 | @Override 108 | public boolean equals(Object obj) { 109 | if (this == obj) { 110 | return true; 111 | } 112 | if (obj == null || getClass() != obj.getClass()) { 113 | return false; 114 | } 115 | 116 | OerUint64 oerUint64 = (OerUint64) obj; 117 | 118 | return value.equals(oerUint64.value); 119 | } 120 | 121 | @Override 122 | public int hashCode() { 123 | return value.hashCode(); 124 | } 125 | 126 | @Override 127 | public String toString() { 128 | return "OerUint64{" 129 | + "value=" + value 130 | + '}'; 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/main/java/org/interledger/codecs/oer/OerIA5StringCodec.java: -------------------------------------------------------------------------------- 1 | package org.interledger.codecs.oer; 2 | 3 | import org.interledger.codecs.Codec; 4 | import org.interledger.codecs.CodecContext; 5 | import org.interledger.codecs.oer.OerIA5StringCodec.OerIA5String; 6 | import org.interledger.codecs.oer.OerLengthPrefixCodec.OerLengthPrefix; 7 | 8 | import java.io.ByteArrayOutputStream; 9 | import java.io.IOException; 10 | import java.io.InputStream; 11 | import java.io.OutputStream; 12 | import java.nio.charset.StandardCharsets; 13 | import java.util.Objects; 14 | 15 | /** 16 | * An extension of {@link Codec} for reading and writing an ASN.1 OER IA5String.

The encoding of 17 | * IA5String types depends on the size constraint present in the type, if any. Interledger's usage 18 | * of IA5String always uses a dynamic size constraint, so the encoding of the string value consists 19 | * of a length prefix followed by the encodings of each character.

After encoding a 20 | * length-prefix using an instance of {@link OerLengthPrefixCodec}, each character in the supplied 21 | * {@link String} will be encoded in one octet with the highest-order bit set to zero.

22 | */ 23 | public class OerIA5StringCodec implements Codec { 24 | 25 | @Override 26 | public OerIA5String read(final CodecContext context, final InputStream inputStream) 27 | throws IOException { 28 | Objects.requireNonNull(context); 29 | Objects.requireNonNull(inputStream); 30 | 31 | // Detect the length of the encoded IA5String, and move the buffer index to the correct spot. 32 | final int length = context.read(OerLengthPrefix.class, inputStream) 33 | .getLength(); 34 | 35 | /* beware the 0-length string */ 36 | final String result = (length == 0 ? "" : this.toString(inputStream, length)); 37 | 38 | return new OerIA5String(result); 39 | } 40 | 41 | @Override 42 | public void write(final CodecContext context, final OerIA5String instance, 43 | final OutputStream outputStream) throws IOException { 44 | 45 | Objects.requireNonNull(context); 46 | Objects.requireNonNull(instance); 47 | Objects.requireNonNull(outputStream); 48 | 49 | final byte[] data = instance.getValue() 50 | .getBytes(); 51 | 52 | // Write the length-prefix, and move the buffer index to the correct spot. 53 | context.write(OerLengthPrefix.class, new OerLengthPrefix(data.length), outputStream); 54 | 55 | // Write the String bytes to the buffer. 56 | outputStream.write(data); 57 | } 58 | 59 | /** 60 | * Convert an {@link InputStream} into a {@link String}. Reference the SO below for an interesting 61 | * performance comparison of various InputStream to String methodologies. 62 | * 63 | * @param inputStream An instance of {@link InputStream}. 64 | * 65 | * @return A {@link String} 66 | * 67 | * @throws IOException If the {@code inputStream} is unable to be read properly. 68 | * @see "http://stackoverflow.com/questions/309424/read-convert-an-inputstream-to-a-string" 69 | */ 70 | private String toString(final InputStream inputStream, final int lengthToRead) 71 | throws IOException { 72 | Objects.requireNonNull(inputStream); 73 | ByteArrayOutputStream result = new ByteArrayOutputStream(); 74 | 75 | // Read lengthToRead bytes from the inputStream into the buffer... 76 | byte[] buffer = new byte[lengthToRead]; 77 | int read = inputStream.read(buffer); 78 | 79 | if (read != lengthToRead) { 80 | throw new IOException( 81 | "error reading " + lengthToRead + " bytes from stream, only read " + read); 82 | } 83 | 84 | result.write(buffer, 0, lengthToRead); 85 | return result.toString(StandardCharsets.US_ASCII.name()); 86 | } 87 | 88 | 89 | /** 90 | * A typing mechanism for registering multiple codecs that operate on the same type (in this case, 91 | * {@link String}). 92 | */ 93 | public static class OerIA5String { 94 | 95 | private final String value; 96 | 97 | public OerIA5String(final String value) { 98 | this.value = Objects.requireNonNull(value); 99 | } 100 | 101 | /** 102 | * Accessor for the value of this IA5String, as a {@link String}. 103 | * 104 | * @return An instance of {@link String}. 105 | */ 106 | public String getValue() { 107 | return value; 108 | } 109 | 110 | @Override 111 | public boolean equals(Object obj) { 112 | if (this == obj) { 113 | return true; 114 | } 115 | if (obj == null || getClass() != obj.getClass()) { 116 | return false; 117 | } 118 | 119 | OerIA5String oerIA5String = (OerIA5String) obj; 120 | 121 | return value.equals(oerIA5String.value); 122 | } 123 | 124 | @Override 125 | public int hashCode() { 126 | return value != null ? value.hashCode() : 0; 127 | } 128 | 129 | @Override 130 | public String toString() { 131 | return "IA5String{" 132 | + "value='" + value + '\'' 133 | + '}'; 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/test/java/org/interledger/psk/UnencryptedMessageReaderTest.java: -------------------------------------------------------------------------------- 1 | package org.interledger.psk; 2 | 3 | public class UnencryptedMessageReaderTest { 4 | 5 | // @Test(expected = RuntimeException.class) 6 | // public void test_BadStatusLine() { 7 | // 8 | // String testMessage = "PSK/1.0 GARBAGE\n" 9 | // + "Encryption: something\n" 10 | // + "\n" 11 | // + "binary data goes here"; 12 | // 13 | // PskMessage.builder(testMessage.getBytes(StandardCharsets.UTF_8)).build(); 14 | // } 15 | // 16 | // @Test(expected = RuntimeException.class) 17 | // public void test_BadStatusLineMajorVersion() { 18 | // String testMessage = "PSK/2.0\n" 19 | // + "Encryption: something\n" 20 | // + "\n" 21 | // + "binary data goes here"; 22 | // 23 | // PskMessage.builder(testMessage.getBytes(StandardCharsets.UTF_8)).build(); 24 | // } 25 | // 26 | // @Test() 27 | // public void test_StatusLineMinorVersion() { 28 | // 29 | // String testMessage = "PSK/1.1\n" 30 | // + "Encryption: something\n" 31 | // + "\n" 32 | // + "binary data goes here"; 33 | // 34 | // PskMessage.builder(testMessage.getBytes(StandardCharsets.UTF_8)); 35 | // } 36 | // 37 | // @Test(expected = RuntimeException.class) 38 | // public void test_MissingEncryptionHeader() { 39 | // 40 | // String testMessage = "PSK/1.0\n" 41 | // + "Header: stuff\n" 42 | // + "\n" 43 | // + "binary data goes here"; 44 | // 45 | // PskMessage.builder(testMessage.getBytes(StandardCharsets.UTF_8)).build(); 46 | // } 47 | // 48 | // @Test() 49 | // public void test_NoEncryption() { 50 | // 51 | // String testMessage = "PSK/1.0\n" 52 | // + "Encryption: none\n" 53 | // + "Testheader: test value\n" 54 | // + "\n" 55 | // + "PrivateHeader1: some value\n" 56 | // + "\n" 57 | // + "binary data goes here"; 58 | // 59 | // PskMessage message = PskMessage.builder(testMessage.getBytes(StandardCharsets.UTF_8)).build(); 60 | // 61 | // assertNotNull(message); 62 | // assertEquals(2, message.getPublicHeaders().size()); 63 | // assertEquals("Testheader", message.getPublicHeaders().get(1).getName()); 64 | // assertEquals("test value", message.getPublicHeaders().get(1).getValue()); 65 | // 66 | // assertEquals(1, message.getPrivateHeaders().size()); 67 | // assertEquals("PrivateHeader1", message.getPrivateHeaders().get(0).getName()); 68 | // assertEquals("some value", message.getPrivateHeaders().get(0).getValue()); 69 | // 70 | // assertArrayEquals("binary data goes here".getBytes(StandardCharsets.UTF_8), 71 | // message.getData()); 72 | // } 73 | // 74 | // @Test() 75 | // public void test_DuplicateHeaders() { 76 | // 77 | // String testMessage = "PSK/1.0\n" 78 | // + "Encryption: none\n" 79 | // + "Testheader: test value\n" 80 | // + "Testheader: \tanother value\n" 81 | // + "Testheader: value3\t\t\t \n" 82 | // + "\n" 83 | // + "PrivateHeader1: some value\n" 84 | // + "\n" 85 | // + "binary data goes here"; 86 | // 87 | // 88 | // PskMessage message = PskMessage.builder(testMessage.getBytes(StandardCharsets.UTF_8)).build(); 89 | // 90 | // assertNotNull(message); 91 | // /* we don't collapse duplicate header values at present. note that we remove leading and 92 | // * trailing whitespace from header values though. */ 93 | // assertEquals(4, message.getPublicHeaders().size()); 94 | // 95 | // assertEquals("Testheader", message.getPublicHeaders().get(1).getName()); 96 | // assertEquals("test value", message.getPublicHeaders().get(1).getValue()); 97 | // 98 | // assertEquals("Testheader", message.getPublicHeaders().get(2).getName()); 99 | // assertEquals("another value", message.getPublicHeaders().get(2).getValue()); 100 | // 101 | // assertEquals("Testheader", message.getPublicHeaders().get(3).getName()); 102 | // assertEquals("value3", message.getPublicHeaders().get(3).getValue()); 103 | // } 104 | // 105 | // @Test() 106 | // public void test_WithEncryption() { 107 | // /* this might look weird, but the reader wont try read the data unless the encryption header 108 | // * type is explicitly set to none 'None' */ 109 | // 110 | // String testMessage = "PSK/1.0\n" 111 | // + "Encryption: super-duper-secret-algorithm\n" 112 | // + "Testheader: test value\n" 113 | // + "\n" 114 | // + "PrivateHeader1: some value\n" 115 | // + "\n" 116 | // + "binary data goes here"; 117 | // 118 | // PskMessage message = PskMessage.builder(testMessage.getBytes(StandardCharsets.UTF_8)).build(); 119 | // 120 | // assertNotNull(message); 121 | // assertEquals(2, message.getPublicHeaders().size()); 122 | // assertEquals("Testheader", message.getPublicHeaders().get(1).getName()); 123 | // assertEquals("test value", message.getPublicHeaders().get(1).getValue()); 124 | // 125 | // /* in this case, we wouldn't read the private headers (since they should be encrypted, and the 126 | // * application data is the private headers and body following. */ 127 | // assertEquals(0, message.getPrivateHeaders().size()); 128 | // assertArrayEquals(testMessage.substring(testMessage.indexOf("PrivateHeader")) 129 | // .getBytes(StandardCharsets.UTF_8), message.getData()); 130 | // } 131 | 132 | 133 | } 134 | -------------------------------------------------------------------------------- /src/test/java/org/interledger/codecs/oer/OerUint32CodecTest.java: -------------------------------------------------------------------------------- 1 | package org.interledger.codecs.oer; 2 | 3 | import static org.hamcrest.CoreMatchers.is; 4 | import static org.hamcrest.MatcherAssert.assertThat; 5 | 6 | import org.interledger.codecs.CodecContext; 7 | import org.interledger.codecs.oer.OerUint32Codec.OerUint32; 8 | 9 | import com.google.common.primitives.Ints; 10 | import com.google.common.primitives.Longs; 11 | import org.junit.Before; 12 | import org.junit.Test; 13 | import org.junit.runner.RunWith; 14 | import org.junit.runners.Parameterized; 15 | import org.junit.runners.Parameterized.Parameters; 16 | 17 | import java.io.ByteArrayInputStream; 18 | import java.io.ByteArrayOutputStream; 19 | import java.util.Arrays; 20 | import java.util.Collection; 21 | 22 | /** 23 | * Parameterized unit tests for encoding an instance of {@link OerUint32Codec}. 24 | */ 25 | @RunWith(Parameterized.class) 26 | public class OerUint32CodecTest { 27 | 28 | private CodecContext codecContext; 29 | private OerUint32Codec oerUint32Codec; 30 | private final long inputValue; 31 | private final byte[] asn1OerBytes; 32 | 33 | /** 34 | * Construct an instance of this parameterized test with the supplied inputs. 35 | * 36 | * @param inputValue A {@code int} representing the unsigned 8bit integer to write in OER 37 | * encoding. 38 | * @param asn1OerBytes The expected value, in binary, of the supplied {@code intValue}. 39 | */ 40 | public OerUint32CodecTest(final long inputValue, final byte[] asn1OerBytes) { 41 | this.inputValue = inputValue; 42 | this.asn1OerBytes = asn1OerBytes; 43 | } 44 | 45 | /** 46 | * The data for this test... 47 | */ 48 | @Parameters 49 | public static Collection data() { 50 | return Arrays.asList(new Object[][] 51 | { 52 | // Input Value as a long; Expected byte[] in ASN.1 53 | // 0 54 | {0L, Ints.toByteArray(0)}, 55 | // 1 56 | {1L, Ints.toByteArray(1)}, 57 | // 2 58 | {2L, Ints.toByteArray(2)}, 59 | // 3 60 | {254L, Ints.toByteArray(254)}, 61 | // 4 62 | {255L, Ints.toByteArray(255)}, 63 | 64 | // Two Bytes (16 bits) 65 | // 5 66 | {256L, Ints.toByteArray(256)}, 67 | // 6 68 | {257L, Ints.toByteArray(257)}, 69 | // 7 70 | {65534L, Ints.toByteArray(65534)}, 71 | // 8 72 | {65535L, Ints.toByteArray(65535)}, 73 | 74 | // Three Bytes (24 bits) 75 | // 9 76 | {65536L, Ints.toByteArray(65536)}, 77 | // 10 78 | {65537L, Ints.toByteArray(65537)}, 79 | // 11 80 | {16777214L, Ints.toByteArray(16777214)}, 81 | // 12 82 | {16777215L, Ints.toByteArray(16777215)}, 83 | 84 | // Four Bytes (32 bits) 85 | // 13 86 | {16777216L, Ints.toByteArray(16777216)}, 87 | // 14 88 | {16777217L, Ints.toByteArray(16777217)}, 89 | // 15 bits set. we use Longs to create a byte array, but resize from 8 to 4 bytes 90 | {4294967294L, Arrays.copyOfRange(Longs.toByteArray(4294967294L), 4, 8)}, 91 | // 16 bits set. we use Longs to create a byte array, but resize from 8 to 4 bytes 92 | {4294967295L, Arrays.copyOfRange(Longs.toByteArray(4294967295L), 4, 8)} 93 | } 94 | ); 95 | } 96 | 97 | /** 98 | * Test setup. 99 | */ 100 | @Before 101 | public void setUp() throws Exception { 102 | // Register the codec to be tested... 103 | oerUint32Codec = new OerUint32Codec(); 104 | codecContext = new CodecContext().register(OerUint32.class, oerUint32Codec); 105 | } 106 | 107 | @Test 108 | public void read() throws Exception { 109 | final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(asn1OerBytes); 110 | final long actualValue = oerUint32Codec.read(codecContext, byteArrayInputStream).getValue(); 111 | assertThat(actualValue, is(inputValue)); 112 | } 113 | 114 | @Test 115 | public void write() throws Exception { 116 | final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); 117 | oerUint32Codec.write(codecContext, new OerUint32(inputValue), byteArrayOutputStream); 118 | assertThat(byteArrayOutputStream.toByteArray(), is(this.asn1OerBytes)); 119 | } 120 | 121 | @Test 122 | public void writeThenRead() throws Exception { 123 | // Write... 124 | final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); 125 | oerUint32Codec.write(codecContext, new OerUint32(inputValue), byteArrayOutputStream); 126 | assertThat(byteArrayOutputStream.toByteArray(), is(asn1OerBytes)); 127 | 128 | // Read... 129 | final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream( 130 | byteArrayOutputStream.toByteArray()); 131 | final OerUint32 decodedValue = oerUint32Codec.read(codecContext, byteArrayInputStream); 132 | 133 | // Write... 134 | final ByteArrayOutputStream byteArrayOutputStream2 = new ByteArrayOutputStream(); 135 | oerUint32Codec.write(codecContext, decodedValue, byteArrayOutputStream2); 136 | assertThat(byteArrayOutputStream2.toByteArray(), is(asn1OerBytes)); 137 | } 138 | } --------------------------------------------------------------------------------