package com.mygeopay.core.uri;
/*
* Copyright 2012, 2014 the original author or authors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import com.mygeopay.core.coins.BitcoinMain;
import com.mygeopay.core.coins.BitcoinTest;
import com.mygeopay.core.coins.CoinType;
import com.mygeopay.core.coins.DashMain;
import com.mygeopay.core.coins.DogecoinMain;
import com.mygeopay.core.coins.LitecoinMain;
import com.mygeopay.core.coins.NuBitsMain;
import com.mygeopay.core.coins.NuSharesMain;
import com.mygeopay.core.coins.PeercoinMain;
import com.mygeopay.core.util.GenericUtils;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import org.junit.Test;
import java.io.UnsupportedEncodingException;
import static org.junit.Assert.*;
public class CoinURITest {
private CoinURI testObject = null;
final CoinType BTC = BitcoinMain.get();
final CoinType BTC_TEST = BitcoinTest.get();
final CoinType LTC = LitecoinMain.get();
final CoinType DOGE = DogecoinMain.get();
final CoinType PPC = PeercoinMain.get();
final CoinType DASH = DashMain.get();
final CoinType NBT = NuBitsMain.get();
final CoinType NSR = NuSharesMain.get();
private static final String MAINNET_GOOD_ADDRESS = "1KzTSfqjF2iKCduwz59nv2uqh1W2JsTxZH";
@Test
public void testConvertToCoinURI() throws Exception {
Address goodAddress = new Address(BitcoinMain.get(), MAINNET_GOOD_ADDRESS);
// simple example
assertEquals("bitcoin:" + MAINNET_GOOD_ADDRESS + "?amount=12.34&label=Hello&message=AMessage", CoinURI.convertToCoinURI(goodAddress, BTC.value("12.34"), "Hello", "AMessage"));
// example with spaces, ampersand and plus
assertEquals("bitcoin:" + MAINNET_GOOD_ADDRESS + "?amount=12.34&label=Hello%20World&message=Mess%20%26%20age%20%2B%20hope", CoinURI.convertToCoinURI(goodAddress, BTC.value("12.34"), "Hello World", "Mess & age + hope"));
// no amount, label present, message present
assertEquals("bitcoin:" + MAINNET_GOOD_ADDRESS + "?label=Hello&message=glory", CoinURI.convertToCoinURI(goodAddress, null, "Hello", "glory"));
// amount present, no label, message present
assertEquals("bitcoin:" + MAINNET_GOOD_ADDRESS + "?amount=0.1&message=glory", CoinURI.convertToCoinURI(goodAddress, BTC.value("0.1"), null, "glory"));
assertEquals("bitcoin:" + MAINNET_GOOD_ADDRESS + "?amount=0.1&message=glory", CoinURI.convertToCoinURI(goodAddress, BTC.value("0.1"), "", "glory"));
// amount present, label present, no message
assertEquals("bitcoin:" + MAINNET_GOOD_ADDRESS + "?amount=12.34&label=Hello", CoinURI.convertToCoinURI(goodAddress, BTC.value("12.34"), "Hello", null));
assertEquals("bitcoin:" + MAINNET_GOOD_ADDRESS + "?amount=12.34&label=Hello", CoinURI.convertToCoinURI(goodAddress, BTC.value("12.34"), "Hello", ""));
// amount present, no label, no message
assertEquals("bitcoin:" + MAINNET_GOOD_ADDRESS + "?amount=1000", CoinURI.convertToCoinURI(goodAddress, BTC.value("1000"), null, null));
assertEquals("bitcoin:" + MAINNET_GOOD_ADDRESS + "?amount=1000", CoinURI.convertToCoinURI(goodAddress, BTC.value("1000"), "", ""));
// no amount, label present, no message
assertEquals("bitcoin:" + MAINNET_GOOD_ADDRESS + "?label=Hello", CoinURI.convertToCoinURI(goodAddress, null, "Hello", null));
// no amount, no label, message present
assertEquals("bitcoin:" + MAINNET_GOOD_ADDRESS + "?message=Agatha", CoinURI.convertToCoinURI(goodAddress, null, null, "Agatha"));
assertEquals("bitcoin:" + MAINNET_GOOD_ADDRESS + "?message=Agatha", CoinURI.convertToCoinURI(goodAddress, null, "", "Agatha"));
// no amount, no label, no message
assertEquals("bitcoin:" + MAINNET_GOOD_ADDRESS, CoinURI.convertToCoinURI(goodAddress, null, null, null));
assertEquals("bitcoin:" + MAINNET_GOOD_ADDRESS, CoinURI.convertToCoinURI(goodAddress, null, "", ""));
}
@Test
public void testAltChainsConvertToCoinURI() throws Exception {
byte[] hash160 = new Address(BitcoinMain.get(), MAINNET_GOOD_ADDRESS).getHash160();
String goodAddressStr;
Address goodAddress;
// Litecoin
goodAddress = new Address(LitecoinMain.get(), hash160);
goodAddressStr = goodAddress.toString();
assertEquals("litecoin:" + goodAddressStr + "?amount=12.34&label=Hello&message=AMessage", CoinURI.convertToCoinURI(goodAddress, LTC.value("12.34"), "Hello", "AMessage"));
// Dogecoin
goodAddress = new Address(DogecoinMain.get(), hash160);
goodAddressStr = goodAddress.toString();
assertEquals("dogecoin:" + goodAddressStr + "?amount=12.34&label=Hello&message=AMessage", CoinURI.convertToCoinURI(goodAddress, DOGE.value("12.34"), "Hello", "AMessage"));
// Peercoin
goodAddress = new Address(PeercoinMain.get(), hash160);
goodAddressStr = goodAddress.toString();
assertEquals("peercoin:" + goodAddressStr + "?amount=12.34&label=Hello&message=AMessage", CoinURI.convertToCoinURI(goodAddress, PPC.value("12.34"), "Hello", "AMessage"));
// Darkcoin
goodAddress = new Address(DashMain.get(), hash160);
goodAddressStr = goodAddress.toString();
assertEquals("dash:" + goodAddressStr + "?amount=12.34&label=Hello&message=AMessage", CoinURI.convertToCoinURI(goodAddress, DASH.value("12.34"), "Hello", "AMessage"));
}
@Test
public void testSharedCoinURI() throws Exception {
byte[] hash160 = new Address(BitcoinMain.get(), MAINNET_GOOD_ADDRESS).getHash160();
// Bitcoin and Bitcoin Testnet
Address address = new Address(BTC, hash160);
testObject = new CoinURI(BTC.getUriScheme() + ":" + address);
assertEquals(BTC, testObject.getType());
assertEquals(address, testObject.getAddress());
Address addressTestnet = new Address(BTC_TEST, hash160);
testObject = new CoinURI(BTC_TEST.getUriScheme() + ":" + addressTestnet);
assertEquals(BTC_TEST, testObject.getType());
assertEquals(addressTestnet, testObject.getAddress());
// NuBits and NuShares
Address nuBitAddress = new Address(NBT, hash160);
testObject = new CoinURI(NBT.getUriScheme() + ":" + nuBitAddress);
assertEquals(NBT, testObject.getType());
assertEquals(nuBitAddress, testObject.getAddress());
Address nuSharesAddress = new Address(NSR, hash160);
testObject = new CoinURI(NSR.getUriScheme() + ":" + nuSharesAddress);
assertEquals(NSR, testObject.getType());
assertEquals(nuSharesAddress, testObject.getAddress());
}
@Test
public void testAltChainsGoodAmount() throws Exception {
byte[] hash160 = new Address(BitcoinMain.get(), MAINNET_GOOD_ADDRESS).getHash160();
String goodAddressStr;
Address goodAddress;
// Litecoin
goodAddress = new Address(LitecoinMain.get(), hash160);
goodAddressStr = goodAddress.toString();
testObject = new CoinURI(LitecoinMain.get(), "litecoin:" + goodAddressStr + "?amount=12.34");
assertEquals("12.34", GenericUtils.formatCoinValue(LitecoinMain.get(), testObject.getAmount()));
// Dogecoin
goodAddress = new Address(DogecoinMain.get(), hash160);
goodAddressStr = goodAddress.toString();
testObject = new CoinURI(DogecoinMain.get(), "dogecoin:" + goodAddressStr + "?amount=12.34");
assertEquals("12.34", GenericUtils.formatCoinValue(DogecoinMain.get(), testObject.getAmount()));
// Peercoin
goodAddress = new Address(PeercoinMain.get(), hash160);
goodAddressStr = goodAddress.toString();
testObject = new CoinURI(PeercoinMain.get(), "peercoin:" + goodAddressStr + "?amount=12.34");
assertEquals("12.34", GenericUtils.formatCoinValue(PeercoinMain.get(), testObject.getAmount()));
// Darkcoin
goodAddress = new Address(DashMain.get(), hash160);
goodAddressStr = goodAddress.toString();
testObject = new CoinURI(DashMain.get(), "dash:" + goodAddressStr + "?amount=12.34");
assertEquals("12.34", GenericUtils.formatCoinValue(DashMain.get(), testObject.getAmount()));
}
@Test
public void testGood_Simple() throws CoinURIParseException {
testObject = new CoinURI(BitcoinMain.get(), BitcoinMain.get().getUriScheme() + ":" + MAINNET_GOOD_ADDRESS);
assertNotNull(testObject);
assertNull("Unexpected amount", testObject.getAmount());
assertNull("Unexpected label", testObject.getLabel());
assertEquals("Unexpected label", 20, testObject.getAddress().getHash160().length);
}
/**
* Test a broken URI (bad scheme)
*/
@Test
public void testBad_Scheme() {
try {
testObject = new CoinURI(BitcoinMain.get(), "blimpcoin:" + MAINNET_GOOD_ADDRESS);
fail("Expecting BitcoinURIParseException");
} catch (CoinURIParseException e) {
}
}
/**
* Test a broken URI (bad syntax)
*/
@Test
public void testBad_BadSyntax() {
// Various illegal characters
try {
testObject = new CoinURI(BitcoinMain.get(), BitcoinMain.get().getUriScheme() + "|" + MAINNET_GOOD_ADDRESS);
fail("Expecting CoinURIParseException");
} catch (CoinURIParseException e) {
assertTrue(e.getMessage().contains("Bad URI syntax"));
}
try {
testObject = new CoinURI(BitcoinMain.get(), BitcoinMain.get().getUriScheme() + ":" + MAINNET_GOOD_ADDRESS + "\\");
fail("Expecting CoinURIParseException");
} catch (CoinURIParseException e) {
assertTrue(e.getMessage().contains("Bad URI syntax"));
}
// Separator without field
try {
testObject = new CoinURI(BitcoinMain.get(), BitcoinMain.get().getUriScheme() + ":");
fail("Expecting CoinURIParseException");
} catch (CoinURIParseException e) {
assertTrue(e.getMessage().contains("Bad URI syntax"));
}
}
/**
* Test a broken URI (missing address)
*/
@Test
public void testBad_Address() {
try {
testObject = new CoinURI(BitcoinMain.get(), BitcoinMain.get().getUriScheme());
fail("Expecting CoinURIParseException");
} catch (CoinURIParseException e) {
}
}
/**
* Test a broken URI (bad address type)
*/
@Test
public void testBad_IncorrectAddressType() {
try {
testObject = new CoinURI(BitcoinTest.get(), BitcoinTest.get().getUriScheme() + ":" + MAINNET_GOOD_ADDRESS);
fail("Expecting CoinURIParseException");
} catch (CoinURIParseException e) {
assertTrue(e.getMessage().contains("Bad address"));
}
}
/**
* Handles a simple amount
*
* @throws CoinURIParseException
* If something goes wrong
*/
@Test
public void testGood_Amount() throws CoinURIParseException {
// Test the decimal parsing
testObject = new CoinURI(BitcoinMain.get(), BitcoinMain.get().getUriScheme() + ":" + MAINNET_GOOD_ADDRESS
+ "?amount=6543210.12345678");
assertEquals(654321012345678L, testObject.getAmount().value);
// Test the decimal parsing
testObject = new CoinURI(BitcoinMain.get(), BitcoinMain.get().getUriScheme() + ":" + MAINNET_GOOD_ADDRESS
+ "?amount=.12345678");
assertEquals(12345678L, testObject.getAmount().value);
// Test the integer parsing
testObject = new CoinURI(BitcoinMain.get(), BitcoinMain.get().getUriScheme() + ":" + MAINNET_GOOD_ADDRESS
+ "?amount=6543210");
assertEquals(654321000000000L, testObject.getAmount().value);
}
/**
* Handles a simple label
*
* @throws CoinURIParseException
* If something goes wrong
*/
@Test
public void testGood_Label() throws CoinURIParseException {
testObject = new CoinURI(BitcoinMain.get(), BitcoinMain.get().getUriScheme() + ":" + MAINNET_GOOD_ADDRESS
+ "?label=Hello%20World");
assertEquals("Hello World", testObject.getLabel());
}
/**
* Handles a simple label with an embedded ampersand and plus
*
* @throws CoinURIParseException
* If something goes wrong
* @throws UnsupportedEncodingException
*/
@Test
public void testGood_LabelWithAmpersandAndPlus() throws Exception {
String testString = "Hello Earth & Mars + Venus";
String encodedLabel = CoinURI.encodeURLString(testString);
testObject = new CoinURI(BitcoinMain.get(), BitcoinMain.get().getUriScheme() + ":" + MAINNET_GOOD_ADDRESS + "?label="
+ encodedLabel);
assertEquals(testString, testObject.getLabel());
}
/**
* Handles a Russian label (Unicode test)
*
* @throws CoinURIParseException
* If something goes wrong
* @throws UnsupportedEncodingException
*/
@Test
public void testGood_LabelWithRussian() throws Exception {
// Moscow in Russian in Cyrillic
String moscowString = "\u041c\u043e\u0441\u043a\u0432\u0430";
String encodedLabel = CoinURI.encodeURLString(moscowString);
testObject = new CoinURI(BitcoinMain.get(), BitcoinMain.get().getUriScheme() + ":" + MAINNET_GOOD_ADDRESS + "?label="
+ encodedLabel);
assertEquals(moscowString, testObject.getLabel());
}
/**
* Handles a simple message
*
* @throws CoinURIParseException
* If something goes wrong
*/
@Test
public void testGood_Message() throws CoinURIParseException {
testObject = new CoinURI(BitcoinMain.get(), BitcoinMain.get().getUriScheme() + ":" + MAINNET_GOOD_ADDRESS
+ "?message=Hello%20World");
assertEquals("Hello World", testObject.getMessage());
}
/**
* Handles various well-formed combinations
*
* @throws CoinURIParseException
* If something goes wrong
*/
@Test
public void testGood_Combinations() throws CoinURIParseException {
testObject = new CoinURI(BitcoinMain.get(), BitcoinMain.get().getUriScheme() + ":" + MAINNET_GOOD_ADDRESS
+ "?amount=6543210&label=Hello%20World&message=Be%20well");
assertEquals(
"CoinURI['address'='1KzTSfqjF2iKCduwz59nv2uqh1W2JsTxZH','amount'='6543210BTC','label'='Hello World','message'='Be well']",
testObject.toString());
}
/**
* Handles a badly formatted amount field
*
* @throws CoinURIParseException
* If something goes wrong
*/
@Test
public void testBad_Amount() throws CoinURIParseException {
// Missing
try {
testObject = new CoinURI(BitcoinMain.get(), BitcoinMain.get().getUriScheme() + ":" + MAINNET_GOOD_ADDRESS
+ "?amount=");
fail("Expecting CoinURIParseException");
} catch (CoinURIParseException e) {
assertTrue(e.getMessage().contains("amount"));
}
// Non-decimal (BIP 21)
try {
testObject = new CoinURI(BitcoinMain.get(), BitcoinMain.get().getUriScheme() + ":" + MAINNET_GOOD_ADDRESS
+ "?amount=12X4");
fail("Expecting CoinURIParseException");
} catch (CoinURIParseException e) {
assertTrue(e.getMessage().contains("amount"));
}
}
@Test
public void testEmpty_Label() throws CoinURIParseException {
assertNull(new CoinURI(BitcoinMain.get(), BitcoinMain.get().getUriScheme() + ":" + MAINNET_GOOD_ADDRESS
+ "?label=").getLabel());
}
@Test
public void testEmpty_Message() throws CoinURIParseException {
assertNull(new CoinURI(BitcoinMain.get(), BitcoinMain.get().getUriScheme() + ":" + MAINNET_GOOD_ADDRESS
+ "?message=").getMessage());
}
/**
* Handles duplicated fields (sneaky address overwrite attack)
*
* @throws CoinURIParseException
* If something goes wrong
*/
@Test
public void testBad_Duplicated() throws CoinURIParseException {
try {
testObject = new CoinURI(BitcoinMain.get(), BitcoinMain.get().getUriScheme() + ":" + MAINNET_GOOD_ADDRESS
+ "?address=aardvark");
fail("Expecting CoinURIParseException");
} catch (CoinURIParseException e) {
assertTrue(e.getMessage().contains("address"));
}
}
@Test
public void testGood_ManyEquals() throws CoinURIParseException {
assertEquals("aardvark=zebra", new CoinURI(BitcoinMain.get(), BitcoinMain.get().getUriScheme() + ":"
+ MAINNET_GOOD_ADDRESS + "?label=aardvark=zebra").getLabel());
}
/**
* Handles case when there are too many question marks
*
* @throws CoinURIParseException
* If something goes wrong
*/
@Test
public void testBad_TooManyQuestionMarks() throws CoinURIParseException {
try {
testObject = new CoinURI(BitcoinMain.get(), BitcoinMain.get().getUriScheme() + ":" + MAINNET_GOOD_ADDRESS
+ "?label=aardvark?message=zebra");
fail("Expecting CoinURIParseException");
} catch (CoinURIParseException e) {
assertTrue(e.getMessage().contains("Too many question marks"));
}
}
/**
* Handles unknown fields (required and not required)
*
* @throws CoinURIParseException
* If something goes wrong
*/
@Test
public void testUnknown() throws CoinURIParseException {
// Unknown not required field
testObject = new CoinURI(BitcoinMain.get(), BitcoinMain.get().getUriScheme() + ":" + MAINNET_GOOD_ADDRESS
+ "?aardvark=true");
assertEquals("CoinURI['address'='1KzTSfqjF2iKCduwz59nv2uqh1W2JsTxZH','aardvark'='true']", testObject.toString());
assertEquals("true", (String) testObject.getParameterByName("aardvark"));
// Unknown not required field (isolated)
try {
testObject = new CoinURI(BitcoinMain.get(), BitcoinMain.get().getUriScheme() + ":" + MAINNET_GOOD_ADDRESS
+ "?aardvark");
fail("Expecting CoinURIParseException");
} catch (CoinURIParseException e) {
assertTrue(e.getMessage().contains("no separator"));
}
// Unknown and required field
try {
testObject = new CoinURI(BitcoinMain.get(), BitcoinMain.get().getUriScheme() + ":" + MAINNET_GOOD_ADDRESS
+ "?req-aardvark=true");
fail("Expecting CoinURIParseException");
} catch (CoinURIParseException e) {
assertTrue(e.getMessage().contains("req-aardvark"));
}
}
@Test
public void brokenURIs() throws CoinURIParseException {
// Check we can parse the incorrectly formatted URIs produced by blockchain.info and its iPhone app.
String str = "bitcoin://1KzTSfqjF2iKCduwz59nv2uqh1W2JsTxZH?amount=0.01000000";
CoinURI uri = new CoinURI(str);
assertEquals("1KzTSfqjF2iKCduwz59nv2uqh1W2JsTxZH", uri.getAddress().toString());
assertEquals(BTC.value(Coin.CENT), uri.getAmount());
}
@Test(expected = CoinURIParseException.class)
public void testBad_AmountTooPrecise() throws CoinURIParseException {
new CoinURI(BitcoinMain.get(), BitcoinMain.get().getUriScheme() + ":" + MAINNET_GOOD_ADDRESS
+ "?amount=0.123456789");
}
@Test(expected = CoinURIParseException.class)
public void testBad_NegativeAmount() throws CoinURIParseException {
new CoinURI(BitcoinMain.get(), BitcoinMain.get().getUriScheme() + ":" + MAINNET_GOOD_ADDRESS
+ "?amount=-1");
}
// @Test(expected = CoinURIParseException.class)
// public void testBad_TooLargeAmount() throws CoinURIParseException {
// new CoinURI(BitcoinMain.get(), BitcoinMain.get().getUriScheme() + ":" + MAINNET_GOOD_ADDRESS
// + "?amount=100000000");
// }
@Test
public void testPaymentProtocolReq() throws Exception {
// Non-backwards compatible form ...
CoinURI uri = new CoinURI(BitcoinTest.get(), "bitcoin:?r=https%3A%2F%2Fbitcoincore.org%2F%7Egavin%2Ff.php%3Fh%3Db0f02e7cea67f168e25ec9b9f9d584f9");
assertEquals("https://bitcoincore.org/~gavin/f.php?h=b0f02e7cea67f168e25ec9b9f9d584f9", uri.getPaymentRequestUrl());
assertNull(uri.getAddress());
// Non-backwards compatible form ...
uri = new CoinURI("bitcoin:?r=https%3A%2F%2Fbitcoincore.org%2F%7Egavin%2Ff.php%3Fh%3Db0f02e7cea67f168e25ec9b9f9d584f9");
assertEquals("https://bitcoincore.org/~gavin/f.php?h=b0f02e7cea67f168e25ec9b9f9d584f9", uri.getPaymentRequestUrl());
assertEquals(BTC, uri.getType());
assertNull(uri.getAddress());
}
}