Remove a lot of classes and cleanup code

This commit is contained in:
pokkst 2022-09-16 20:19:01 -05:00
parent 460f6bd1b2
commit 209984417b
No known key found for this signature in database
GPG Key ID: 90C2ED85E67A50FF
105 changed files with 1047 additions and 10100 deletions

View File

@ -28,8 +28,9 @@
<activity
android:name="com.journeyapps.barcodescanner.CaptureActivity"
android:screenOrientation="portrait"
tools:replace="android:screenOrientation"
android:stateNotNeeded="true"/>
android:stateNotNeeded="true"
tools:replace="android:screenOrientation" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"

View File

@ -438,7 +438,8 @@ its rights to a jury trial in any resulting litigation.
<h2>Licensed under the MIT License</h2>
<h3>rapidjson (https://github.com/monero-project/monero/blob/master/external/rapidjson)</h3>
Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved.
<h3>easylogging++ (https://github.com/monero-project/monero/tree/master/external/easylogging%2B%2B)</h3>
<h3>easylogging++
(https://github.com/monero-project/monero/tree/master/external/easylogging%2B%2B)</h3>
Copyright (c) 2017 muflihun.com
<h3>zxcvbn4j (https://github.com/nulab/zxcvbn4j)</h3>
Copyright (c) 2014 Nulab Inc
@ -605,7 +606,9 @@ copied and put under another distribution licence
<h2>Boost</h2>
<ul>
<li>Boost (https://sourceforge.net/projects/boost)</li>
<li>Boost/Archive (https://github.com/monero-project/monero/tree/master/external/boost/archive)</li>
<li>Boost/Archive
(https://github.com/monero-project/monero/tree/master/external/boost/archive)
</li>
</ul>
<h3>Boost Software License - Version 1.0 - August 17th, 2003</h3>
Permission is hereby granted, free of charge, to any person or organization
@ -767,10 +770,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
<h3>SIL Open Font License</h3>
<p>Copyright (c) 2014, Indian Type Foundry (info@indiantypefoundry.com).</p>
<p>This Font Software is licensed under the SIL Open Font License, Version 1.1.<br/>
This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL</p>
This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL
</p>
<p>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-<br/>
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007<br/>
&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-</p>
&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-
</p>
<p>PREAMBLE<br/>
The goals of the Open Font License (OFL) are to stimulate worldwide development of
collaborative font projects, to support the font creation efforts of academic and

View File

@ -60,8 +60,6 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved) {
jenv->FindClass("com/m2049r/xmrwallet/model/Transfer")));
class_WalletListener = static_cast<jclass>(jenv->NewGlobalRef(
jenv->FindClass("com/m2049r/xmrwallet/model/WalletListener")));
class_Ledger = static_cast<jclass>(jenv->NewGlobalRef(
jenv->FindClass("com/m2049r/xmrwallet/ledger/Ledger")));
class_WalletStatus = static_cast<jclass>(jenv->NewGlobalRef(
jenv->FindClass("com/m2049r/xmrwallet/model/Wallet$Status")));
return JNI_VERSION_1_6;
@ -705,7 +703,8 @@ JNIEXPORT jboolean JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_initJ(JNIEnv *env, jobject instance,
jstring daemon_address,
jlong upper_transaction_size_limit,
jstring daemon_username, jstring daemon_password, jstring proxy) {
jstring daemon_username, jstring daemon_password,
jstring proxy) {
const char *_daemon_address = env->GetStringUTFChars(daemon_address, nullptr);
const char *_daemon_username = env->GetStringUTFChars(daemon_username, nullptr);
const char *_daemon_password = env->GetStringUTFChars(daemon_password, nullptr);

View File

@ -62,14 +62,16 @@ enum {
HASH_DATA_AREA = 136
};
void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int prehashed, uint64_t height);
void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int prehashed,
uint64_t height);
inline void slow_hash(const void *data, const size_t length, char *hash) {
cn_slow_hash(data, length, hash, 0 /*variant*/, 0 /*prehashed*/, 0 /*height*/);
}
inline void slow_hash_broken(const void *data, char *hash, int variant) {
cn_slow_hash(data, 200 /*sizeof(union hash_state)*/, hash, variant, 1 /*prehashed*/, 0 /*height*/);
cn_slow_hash(data, 200 /*sizeof(union hash_state)*/, hash, variant, 1 /*prehashed*/,
0 /*height*/);
}
#ifdef __cplusplus

View File

@ -1,53 +0,0 @@
/*
*******************************************************************************
* BTChip Bitcoin Hardware Wallet Java API
* (c) 2014 BTChip - 1BTChip7VfTnrPra5jqci7ejnMguuHogTn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
********************************************************************************
*/
package com.btchip;
public class BTChipException extends Exception {
private static final long serialVersionUID = 5512803003827126405L;
public BTChipException(String reason) {
super(reason);
}
public BTChipException(String reason, Throwable cause) {
super(reason, cause);
}
public BTChipException(String reason, int sw) {
super(reason);
this.sw = sw;
}
public int getSW() {
return sw;
}
public String toString() {
if (sw == 0) {
return "BTChip Exception : " + getMessage();
} else {
return "BTChip Exception : " + getMessage() + " " + Integer.toHexString(sw);
}
}
private int sw;
}

View File

@ -1,31 +0,0 @@
/*
*******************************************************************************
* BTChip Bitcoin Hardware Wallet Java API
* (c) 2014 BTChip - 1BTChip7VfTnrPra5jqci7ejnMguuHogTn
* (c) 2018 m2049r
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
********************************************************************************
*/
package com.btchip.comm;
import com.btchip.BTChipException;
public interface BTChipTransport {
byte[] exchange(byte[] command);
void close();
void setDebug(boolean debugFlag);
}

View File

@ -1,126 +0,0 @@
/*
*******************************************************************************
* BTChip Bitcoin Hardware Wallet Java API
* (c) 2014 BTChip - 1BTChip7VfTnrPra5jqci7ejnMguuHogTn
* (c) 2018 m2049r
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
********************************************************************************
*/
package com.btchip.comm;
import java.io.ByteArrayOutputStream;
public class LedgerHelper {
private static final int TAG_APDU = 0x05;
public static byte[] wrapCommandAPDU(int channel, byte[] command, int packetSize) {
ByteArrayOutputStream output = new ByteArrayOutputStream();
if (packetSize < 3) {
throw new IllegalArgumentException("Can't handle Ledger framing with less than 3 bytes for the report");
}
int sequenceIdx = 0;
int offset = 0;
output.write(channel >> 8);
output.write(channel);
output.write(TAG_APDU);
output.write(sequenceIdx >> 8);
output.write(sequenceIdx);
sequenceIdx++;
output.write(command.length >> 8);
output.write(command.length);
int blockSize = (command.length > packetSize - 7 ? packetSize - 7 : command.length);
output.write(command, offset, blockSize);
offset += blockSize;
while (offset != command.length) {
output.write(channel >> 8);
output.write(channel);
output.write(TAG_APDU);
output.write(sequenceIdx >> 8);
output.write(sequenceIdx);
sequenceIdx++;
blockSize = (command.length - offset > packetSize - 5 ? packetSize - 5 : command.length - offset);
output.write(command, offset, blockSize);
offset += blockSize;
}
if ((output.size() % packetSize) != 0) {
byte[] padding = new byte[packetSize - (output.size() % packetSize)];
output.write(padding, 0, padding.length);
}
return output.toByteArray();
}
public static byte[] unwrapResponseAPDU(int channel, byte[] data, int packetSize) {
ByteArrayOutputStream response = new ByteArrayOutputStream();
int offset = 0;
int responseLength;
int sequenceIdx = 0;
if ((data == null) || (data.length < 7 + 5)) {
return null;
}
if (data[offset++] != (channel >> 8)) {
throw new IllegalArgumentException("Invalid channel");
}
if (data[offset++] != (channel & 0xff)) {
throw new IllegalArgumentException("Invalid channel");
}
if (data[offset++] != TAG_APDU) {
throw new IllegalArgumentException("Invalid tag");
}
if (data[offset++] != 0x00) {
throw new IllegalArgumentException("Invalid sequence");
}
if (data[offset++] != 0x00) {
throw new IllegalArgumentException("Invalid sequence");
}
responseLength = ((data[offset++] & 0xff) << 8);
responseLength |= (data[offset++] & 0xff);
if (data.length < 7 + responseLength) {
return null;
}
int blockSize = (responseLength > packetSize - 7 ? packetSize - 7 : responseLength);
response.write(data, offset, blockSize);
offset += blockSize;
while (response.size() != responseLength) {
sequenceIdx++;
if (offset == data.length) {
return null;
}
if (data[offset++] != (channel >> 8)) {
throw new IllegalArgumentException("Invalid channel");
}
if (data[offset++] != (channel & 0xff)) {
throw new IllegalArgumentException("Invalid channel");
}
if (data[offset++] != TAG_APDU) {
throw new IllegalArgumentException("Invalid tag");
}
if (data[offset++] != (sequenceIdx >> 8)) {
throw new IllegalArgumentException("Invalid sequence");
}
if (data[offset++] != (sequenceIdx & 0xff)) {
throw new IllegalArgumentException("Invalid sequence");
}
blockSize = (responseLength - response.size() > packetSize - 5 ? packetSize - 5 : responseLength - response.size());
if (blockSize > data.length - offset) {
return null;
}
response.write(data, offset, blockSize);
offset += blockSize;
}
return response.toByteArray();
}
}

View File

@ -1,149 +0,0 @@
/*
*******************************************************************************
* BTChip Bitcoin Hardware Wallet Java API
* (c) 2014 BTChip - 1BTChip7VfTnrPra5jqci7ejnMguuHogTn
* (c) 2018 m2049r
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
********************************************************************************
*/
package com.btchip.comm.android;
import android.hardware.usb.UsbConstants;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbInterface;
import android.hardware.usb.UsbManager;
import android.hardware.usb.UsbRequest;
import com.btchip.BTChipException;
import com.btchip.comm.BTChipTransport;
import com.btchip.comm.LedgerHelper;
import com.btchip.utils.Dump;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.HashMap;
import timber.log.Timber;
public class BTChipTransportAndroidHID implements BTChipTransport {
public static UsbDevice getDevice(UsbManager manager) {
HashMap<String, UsbDevice> deviceList = manager.getDeviceList();
for (UsbDevice device : deviceList.values()) {
Timber.d("%04X:%04X %s, %s", device.getVendorId(), device.getProductId(), device.getManufacturerName(), device.getProductName());
if (device.getVendorId() == VID) {
final int deviceProductId = device.getProductId();
for (int pid : PID_HIDS) {
if (deviceProductId == pid)
return device;
}
}
}
return null;
}
public static BTChipTransport open(UsbManager manager, UsbDevice device) throws IOException {
UsbDeviceConnection connection = manager.openDevice(device);
if (connection == null) throw new IOException("Device not connected");
// Must only be called once permission is granted (see http://developer.android.com/reference/android/hardware/usb/UsbManager.html)
// Important if enumerating, rather than being awaken by the intent notification
UsbInterface dongleInterface = device.getInterface(0);
UsbEndpoint in = null;
UsbEndpoint out = null;
for (int i = 0; i < dongleInterface.getEndpointCount(); i++) {
UsbEndpoint tmpEndpoint = dongleInterface.getEndpoint(i);
if (tmpEndpoint.getDirection() == UsbConstants.USB_DIR_IN) {
in = tmpEndpoint;
} else {
out = tmpEndpoint;
}
}
connection.claimInterface(dongleInterface, true);
return new BTChipTransportAndroidHID(connection, dongleInterface, in, out);
}
private static final int VID = 0x2C97;
private static final int[] PID_HIDS = {0x0001, 0x0004, 0x0005};
private UsbDeviceConnection connection;
private UsbInterface dongleInterface;
private UsbEndpoint in;
private UsbEndpoint out;
private byte transferBuffer[];
private boolean debug;
public BTChipTransportAndroidHID(UsbDeviceConnection connection, UsbInterface dongleInterface, UsbEndpoint in, UsbEndpoint out) {
this.connection = connection;
this.dongleInterface = dongleInterface;
this.in = in;
this.out = out;
transferBuffer = new byte[HID_BUFFER_SIZE];
}
@Override
public byte[] exchange(byte[] command) {
ByteArrayOutputStream response = new ByteArrayOutputStream();
byte[] responseData = null;
int offset = 0;
if (debug) {
Timber.d("=> %s", Dump.dump(command));
}
command = LedgerHelper.wrapCommandAPDU(LEDGER_DEFAULT_CHANNEL, command, HID_BUFFER_SIZE);
UsbRequest requestOut = new UsbRequest();
requestOut.initialize(connection, out);
while (offset != command.length) {
int blockSize = (command.length - offset > HID_BUFFER_SIZE ? HID_BUFFER_SIZE : command.length - offset);
System.arraycopy(command, offset, transferBuffer, 0, blockSize);
requestOut.queue(ByteBuffer.wrap(transferBuffer), HID_BUFFER_SIZE);
connection.requestWait();
offset += blockSize;
}
requestOut.close();
ByteBuffer responseBuffer = ByteBuffer.allocate(HID_BUFFER_SIZE);
UsbRequest requestIn = new UsbRequest();
requestIn.initialize(connection, in);
while ((responseData = LedgerHelper.unwrapResponseAPDU(LEDGER_DEFAULT_CHANNEL, response.toByteArray(), HID_BUFFER_SIZE)) == null) {
responseBuffer.clear();
requestIn.queue(responseBuffer, HID_BUFFER_SIZE);
connection.requestWait();
responseBuffer.rewind();
responseBuffer.get(transferBuffer, 0, HID_BUFFER_SIZE);
response.write(transferBuffer, 0, HID_BUFFER_SIZE);
}
requestIn.close();
if (debug) {
Timber.d("<= %s", Dump.dump(responseData));
}
return responseData;
}
@Override
public void close() {
connection.releaseInterface(dongleInterface);
connection.close();
}
@Override
public void setDebug(boolean debugFlag) {
this.debug = debugFlag;
}
private static final int HID_BUFFER_SIZE = 64;
private static final int LEDGER_DEFAULT_CHANNEL = 1;
private static final int SW1_DATA_AVAILABLE = 0x61;
}

View File

@ -1,62 +0,0 @@
/*
*******************************************************************************
* BTChip Bitcoin Hardware Wallet Java API
* (c) 2014 BTChip - 1BTChip7VfTnrPra5jqci7ejnMguuHogTn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
********************************************************************************
*/
package com.btchip.utils;
import java.io.ByteArrayOutputStream;
public class Dump {
public static String dump(byte[] buffer, int offset, int length) {
String result = "";
for (int i = 0; i < length; i++) {
String temp = Integer.toHexString((buffer[offset + i]) & 0xff);
if (temp.length() < 2) {
temp = "0" + temp;
}
result += temp;
}
return result;
}
public static String dump(byte[] buffer) {
return dump(buffer, 0, buffer.length);
}
public static byte[] hexToBin(String src) {
ByteArrayOutputStream result = new ByteArrayOutputStream();
int i = 0;
while (i < src.length()) {
char x = src.charAt(i);
if (!((x >= '0' && x <= '9') || (x >= 'A' && x <= 'F') || (x >= 'a' && x <= 'f'))) {
i++;
continue;
}
try {
result.write(Integer.valueOf("" + src.charAt(i) + src.charAt(i + 1), 16));
i += 2;
} catch (Exception e) {
return null;
}
}
return result.toByteArray();
}
}

View File

@ -1,145 +0,0 @@
/*
* Copyright (c) 2018 m2049r
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.levin.data;
import com.m2049r.levin.util.HexHelper;
import com.m2049r.levin.util.LevinReader;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
public class Bucket {
// constants copied from monero p2p & epee
public final static int P2P_COMMANDS_POOL_BASE = 1000;
public final static int COMMAND_HANDSHAKE_ID = P2P_COMMANDS_POOL_BASE + 1;
public final static int COMMAND_TIMED_SYNC_ID = P2P_COMMANDS_POOL_BASE + 2;
public final static int COMMAND_PING_ID = P2P_COMMANDS_POOL_BASE + 3;
public final static int COMMAND_REQUEST_STAT_INFO_ID = P2P_COMMANDS_POOL_BASE + 4;
public final static int COMMAND_REQUEST_NETWORK_STATE_ID = P2P_COMMANDS_POOL_BASE + 5;
public final static int COMMAND_REQUEST_PEER_ID_ID = P2P_COMMANDS_POOL_BASE + 6;
public final static int COMMAND_REQUEST_SUPPORT_FLAGS_ID = P2P_COMMANDS_POOL_BASE + 7;
public final static long LEVIN_SIGNATURE = 0x0101010101012101L; // Bender's nightmare
public final static long LEVIN_DEFAULT_MAX_PACKET_SIZE = 100000000; // 100MB by default
public final static int LEVIN_PACKET_REQUEST = 0x00000001;
public final static int LEVIN_PACKET_RESPONSE = 0x00000002;
public final static int LEVIN_PROTOCOL_VER_0 = 0;
public final static int LEVIN_PROTOCOL_VER_1 = 1;
public final static int LEVIN_OK = 0;
public final static int LEVIN_ERROR_CONNECTION = -1;
public final static int LEVIN_ERROR_CONNECTION_NOT_FOUND = -2;
public final static int LEVIN_ERROR_CONNECTION_DESTROYED = -3;
public final static int LEVIN_ERROR_CONNECTION_TIMEDOUT = -4;
public final static int LEVIN_ERROR_CONNECTION_NO_DUPLEX_PROTOCOL = -5;
public final static int LEVIN_ERROR_CONNECTION_HANDLER_NOT_DEFINED = -6;
public final static int LEVIN_ERROR_FORMAT = -7;
public final static int P2P_SUPPORT_FLAG_FLUFFY_BLOCKS = 0x01;
public final static int P2P_SUPPORT_FLAGS = P2P_SUPPORT_FLAG_FLUFFY_BLOCKS;
final private long signature;
final private long cb;
final public boolean haveToReturnData;
final public int command;
final public int returnCode;
final private int flags;
final private int protcolVersion;
final byte[] payload;
final public Section payloadSection;
// create a request
public Bucket(int command, byte[] payload) throws IOException {
this.signature = LEVIN_SIGNATURE;
this.cb = payload.length;
this.haveToReturnData = true;
this.command = command;
this.returnCode = 0;
this.flags = LEVIN_PACKET_REQUEST;
this.protcolVersion = LEVIN_PROTOCOL_VER_1;
this.payload = payload;
payloadSection = LevinReader.readPayload(payload);
}
// create a response
public Bucket(int command, byte[] payload, int rc) throws IOException {
this.signature = LEVIN_SIGNATURE;
this.cb = payload.length;
this.haveToReturnData = false;
this.command = command;
this.returnCode = rc;
this.flags = LEVIN_PACKET_RESPONSE;
this.protcolVersion = LEVIN_PROTOCOL_VER_1;
this.payload = payload;
payloadSection = LevinReader.readPayload(payload);
}
public Bucket(DataInput in) throws IOException {
signature = in.readLong();
cb = in.readLong();
haveToReturnData = in.readBoolean();
command = in.readInt();
returnCode = in.readInt();
flags = in.readInt();
protcolVersion = in.readInt();
if (signature == Bucket.LEVIN_SIGNATURE) {
if (cb > Integer.MAX_VALUE)
throw new IllegalArgumentException();
payload = new byte[(int) cb];
in.readFully(payload);
} else
throw new IllegalStateException();
payloadSection = LevinReader.readPayload(payload);
}
public Section getPayloadSection() {
return payloadSection;
}
public void send(DataOutput out) throws IOException {
out.writeLong(signature);
out.writeLong(cb);
out.writeBoolean(haveToReturnData);
out.writeInt(command);
out.writeInt(returnCode);
out.writeInt(flags);
out.writeInt(protcolVersion);
out.write(payload);
}
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("sig: ").append(signature).append("\n");
sb.append("cb: ").append(cb).append("\n");
sb.append("call: ").append(haveToReturnData).append("\n");
sb.append("cmd: ").append(command).append("\n");
sb.append("rc: ").append(returnCode).append("\n");
sb.append("flags:").append(flags).append("\n");
sb.append("proto:").append(protcolVersion).append("\n");
sb.append(HexHelper.bytesToHex(payload)).append("\n");
sb.append(payloadSection.toString());
return sb.toString();
}
}

View File

@ -1,125 +0,0 @@
/*
* Copyright (c) 2018 m2049r
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.levin.data;
import com.m2049r.levin.util.HexHelper;
import com.m2049r.levin.util.LevinReader;
import com.m2049r.levin.util.LevinWriter;
import com.m2049r.levin.util.LittleEndianDataOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataOutput;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class Section {
// constants copied from monero p2p & epee
static final public int PORTABLE_STORAGE_SIGNATUREA = 0x01011101;
static final public int PORTABLE_STORAGE_SIGNATUREB = 0x01020101;
static final public byte PORTABLE_STORAGE_FORMAT_VER = 1;
static final public byte PORTABLE_RAW_SIZE_MARK_MASK = 0x03;
static final public byte PORTABLE_RAW_SIZE_MARK_BYTE = 0;
static final public byte PORTABLE_RAW_SIZE_MARK_WORD = 1;
static final public byte PORTABLE_RAW_SIZE_MARK_DWORD = 2;
static final public byte PORTABLE_RAW_SIZE_MARK_INT64 = 3;
static final long MAX_STRING_LEN_POSSIBLE = 2000000000; // do not let string be so big
// data types
static final public byte SERIALIZE_TYPE_INT64 = 1;
static final public byte SERIALIZE_TYPE_INT32 = 2;
static final public byte SERIALIZE_TYPE_INT16 = 3;
static final public byte SERIALIZE_TYPE_INT8 = 4;
static final public byte SERIALIZE_TYPE_UINT64 = 5;
static final public byte SERIALIZE_TYPE_UINT32 = 6;
static final public byte SERIALIZE_TYPE_UINT16 = 7;
static final public byte SERIALIZE_TYPE_UINT8 = 8;
static final public byte SERIALIZE_TYPE_DUOBLE = 9;
static final public byte SERIALIZE_TYPE_STRING = 10;
static final public byte SERIALIZE_TYPE_BOOL = 11;
static final public byte SERIALIZE_TYPE_OBJECT = 12;
static final public byte SERIALIZE_TYPE_ARRAY = 13;
static final public byte SERIALIZE_FLAG_ARRAY = (byte) 0x80;
private final Map<String, Object> entries = new HashMap<String, Object>();
public void add(String key, Object entry) {
entries.put(key, entry);
}
public int size() {
return entries.size();
}
public Set<Map.Entry<String, Object>> entrySet() {
return entries.entrySet();
}
public Object get(String key) {
return entries.get(key);
}
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append("\n");
for (Map.Entry<String, Object> entry : entries.entrySet()) {
sb.append(entry.getKey()).append("=");
final Object value = entry.getValue();
if (value instanceof List) {
@SuppressWarnings("unchecked") final List<Object> list = (List<Object>) value;
for (Object listEntry : list) {
sb.append(listEntry.toString()).append("\n");
}
} else if (value instanceof String) {
sb.append("(").append(value).append(")\n");
} else if (value instanceof byte[]) {
sb.append(HexHelper.bytesToHex((byte[]) value)).append("\n");
} else {
sb.append(value.toString()).append("\n");
}
}
return sb.toString();
}
static public Section fromByteArray(byte[] buffer) {
try {
return LevinReader.readPayload(buffer);
} catch (IOException ex) {
throw new IllegalStateException();
}
}
public byte[] asByteArray() {
try {
ByteArrayOutputStream bas = new ByteArrayOutputStream();
DataOutput out = new LittleEndianDataOutputStream(bas);
LevinWriter writer = new LevinWriter(out);
writer.writePayload(this);
return bas.toByteArray();
} catch (IOException ex) {
throw new IllegalStateException();
}
}
}

View File

@ -1,195 +0,0 @@
/*
* Copyright (c) 2018 m2049r
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.levin.scanner;
import com.m2049r.xmrwallet.data.NodeInfo;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import timber.log.Timber;
public class Dispatcher implements PeerRetriever.OnGetPeers {
static final public int NUM_THREADS = 50;
static final public int MAX_PEERS = 1000;
static final public long MAX_TIME = 30000000000L; //30 seconds
private int peerCount = 0;
final private Set<NodeInfo> knownNodes = new HashSet<>(); // set of nodes to test
final private Set<NodeInfo> rpcNodes = new HashSet<>(); // set of RPC nodes we like
final private ExecutorService exeService = Executors.newFixedThreadPool(NUM_THREADS);
public interface Listener {
void onGet(NodeInfo nodeInfo);
}
private Listener listener;
public Dispatcher(Listener listener) {
this.listener = listener;
}
public Set<NodeInfo> getRpcNodes() {
return rpcNodes;
}
public int getPeerCount() {
return peerCount;
}
public boolean getMorePeers() {
return peerCount < MAX_PEERS;
}
public void awaitTermination(int nodesToFind) {
try {
final long t = System.nanoTime();
while (!jobs.isEmpty()) {
try {
Timber.d("Remaining jobs %d", jobs.size());
final PeerRetriever retrievedPeer = jobs.poll().get();
if (retrievedPeer.isGood() && getMorePeers())
retrievePeers(retrievedPeer);
final NodeInfo nodeInfo = retrievedPeer.getNodeInfo();
Timber.d("Retrieved %s", nodeInfo);
if ((nodeInfo.isValid() || nodeInfo.isFavourite())) {
nodeInfo.setDefaultName();
rpcNodes.add(nodeInfo);
Timber.d("RPC: %s", nodeInfo);
// the following is not totally correct but it works (otherwise we need to
// load much more before filtering - but we don't have time
if (listener != null) listener.onGet(nodeInfo);
if (rpcNodes.size() >= nodesToFind) {
Timber.d("are we done here?");
filterRpcNodes();
if (rpcNodes.size() >= nodesToFind) {
Timber.d("we're done here");
break;
}
}
}
if (System.nanoTime() - t > MAX_TIME) break; // watchdog
} catch (ExecutionException ex) {
Timber.d(ex); // tell us about it and continue
}
}
} catch (InterruptedException ex) {
Timber.d(ex);
} finally {
Timber.d("Shutting down!");
exeService.shutdownNow();
try {
exeService.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
} catch (InterruptedException ex) {
Timber.d(ex);
}
}
filterRpcNodes();
}
static final public int HEIGHT_WINDOW = 1;
private boolean testHeight(long height, long consensus) {
return (height >= (consensus - HEIGHT_WINDOW))
&& (height <= (consensus + HEIGHT_WINDOW));
}
private long calcConsensusHeight() {
Timber.d("Calc Consensus height from %d nodes", rpcNodes.size());
final Map<Long, Integer> nodeHeights = new TreeMap<Long, Integer>();
for (NodeInfo info : rpcNodes) {
if (!info.isValid()) continue;
Integer h = nodeHeights.get(info.getHeight());
if (h == null)
h = 0;
nodeHeights.put(info.getHeight(), h + 1);
}
long consensusHeight = 0;
long consensusCount = 0;
for (Map.Entry<Long, Integer> entry : nodeHeights.entrySet()) {
final long entryHeight = entry.getKey();
int count = 0;
for (long i = entryHeight - HEIGHT_WINDOW; i <= entryHeight + HEIGHT_WINDOW; i++) {
Integer v = nodeHeights.get(i);
if (v == null)
v = 0;
count += v;
}
if (count >= consensusCount) {
consensusCount = count;
consensusHeight = entryHeight;
}
Timber.d("%d - %d/%d", entryHeight, count, entry.getValue());
}
return consensusHeight;
}
private void filterRpcNodes() {
long consensus = calcConsensusHeight();
Timber.d("Consensus Height = %d for %d nodes", consensus, rpcNodes.size());
for (Iterator<NodeInfo> iter = rpcNodes.iterator(); iter.hasNext(); ) {
NodeInfo info = iter.next();
// don't remove favourites
if (!info.isFavourite()) {
if (!testHeight(info.getHeight(), consensus)) {
iter.remove();
Timber.d("Removed %s", info);
}
}
}
}
// TODO: does this NEED to be a ConcurrentLinkedDeque?
private ConcurrentLinkedDeque<Future<PeerRetriever>> jobs = new ConcurrentLinkedDeque<>();
private void retrievePeer(NodeInfo nodeInfo) {
if (knownNodes.add(nodeInfo)) {
Timber.d("\t%d:%s", knownNodes.size(), nodeInfo);
jobs.add(exeService.submit(new PeerRetriever(nodeInfo, this)));
peerCount++; // jobs.size() does not perform well
}
}
private void retrievePeers(PeerRetriever peer) {
for (LevinPeer levinPeer : peer.getPeers()) {
if (getMorePeers())
retrievePeer(new NodeInfo(levinPeer));
else
break;
}
}
public void seedPeers(Collection<NodeInfo> seedNodes) {
for (NodeInfo node : seedNodes) {
if (node.isFavourite()) {
rpcNodes.add(node);
if (listener != null) listener.onGet(node);
}
retrievePeer(node);
}
}
}

View File

@ -1,39 +0,0 @@
/*
* Copyright (c) 2018 m2049r
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.levin.scanner;
import java.net.InetAddress;
import java.net.InetSocketAddress;
public class LevinPeer {
final public InetSocketAddress socketAddress;
final public int version;
final public long height;
final public String top;
public InetSocketAddress getSocketAddress() {
return socketAddress;
}
LevinPeer(InetAddress address, int port, int version, long height, String top) {
this.socketAddress = new InetSocketAddress(address, port);
this.version = version;
this.height = height;
this.top = top;
}
}

View File

@ -1,231 +0,0 @@
/*
* Copyright (c) 2018 m2049r
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.levin.scanner;
import com.m2049r.levin.data.Bucket;
import com.m2049r.levin.data.Section;
import com.m2049r.levin.util.HexHelper;
import com.m2049r.levin.util.LittleEndianDataInputStream;
import com.m2049r.levin.util.LittleEndianDataOutputStream;
import com.m2049r.xmrwallet.data.NodeInfo;
import com.m2049r.xmrwallet.util.Helper;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Callable;
import timber.log.Timber;
public class PeerRetriever implements Callable<PeerRetriever> {
static final public int CONNECT_TIMEOUT = 500; //ms
static final public int SOCKET_TIMEOUT = 500; //ms
static final public long PEER_ID = new Random().nextLong();
static final private byte[] HANDSHAKE = handshakeRequest().asByteArray();
static final private byte[] FLAGS_RESP = flagsResponse().asByteArray();
final private List<LevinPeer> peers = new ArrayList<>();
private NodeInfo nodeInfo;
private OnGetPeers onGetPeersCallback;
public interface OnGetPeers {
boolean getMorePeers();
}
public PeerRetriever(NodeInfo nodeInfo, OnGetPeers onGetPeers) {
this.nodeInfo = nodeInfo;
this.onGetPeersCallback = onGetPeers;
}
public NodeInfo getNodeInfo() {
return nodeInfo;
}
public boolean isGood() {
return !peers.isEmpty();
}
public List<LevinPeer> getPeers() {
return peers;
}
public PeerRetriever call() {
if (isGood()) // we have already been called?
throw new IllegalStateException();
// first check for an rpc service
nodeInfo.findRpcService();
if (onGetPeersCallback.getMorePeers())
try {
Timber.d("%s CONN", nodeInfo.getLevinSocketAddress());
if (!connect())
return this;
Bucket handshakeBucket = new Bucket(Bucket.COMMAND_HANDSHAKE_ID, HANDSHAKE);
handshakeBucket.send(getDataOutput());
while (true) {// wait for response (which may never come)
Bucket recv = new Bucket(getDataInput()); // times out after SOCKET_TIMEOUT
if ((recv.command == Bucket.COMMAND_HANDSHAKE_ID)
&& (!recv.haveToReturnData)) {
readAddressList(recv.payloadSection);
return this;
} else if ((recv.command == Bucket.COMMAND_REQUEST_SUPPORT_FLAGS_ID)
&& (recv.haveToReturnData)) {
Bucket flagsBucket = new Bucket(Bucket.COMMAND_REQUEST_SUPPORT_FLAGS_ID, FLAGS_RESP, 1);
flagsBucket.send(getDataOutput());
} else {// and ignore others
Timber.d("Ignored LEVIN COMMAND %d", recv.command);
}
}
} catch (IOException ex) {
} finally {
disconnect(); // we have what we want - byebye
Timber.d("%s DISCONN", nodeInfo.getLevinSocketAddress());
}
return this;
}
private void readAddressList(Section section) {
Section data = (Section) section.get("payload_data");
int topVersion = (Integer) data.get("top_version");
long currentHeight = (Long) data.get("current_height");
String topId = HexHelper.bytesToHex((byte[]) data.get("top_id"));
Timber.d("PAYLOAD_DATA %d/%d/%s", topVersion, currentHeight, topId);
@SuppressWarnings("unchecked")
List<Section> peerList = (List<Section>) section.get("local_peerlist_new");
if (peerList != null) {
for (Section peer : peerList) {
Section adr = (Section) peer.get("adr");
Integer type = (Integer) adr.get("type");
if ((type == null) || (type != 1))
continue;
Section addr = (Section) adr.get("addr");
if (addr == null)
continue;
Integer ip = (Integer) addr.get("m_ip");
if (ip == null)
continue;
Integer sport = (Integer) addr.get("m_port");
if (sport == null)
continue;
int port = sport;
if (port < 0) // port is unsigned
port = port + 0x10000;
InetAddress inet = HexHelper.toInetAddress(ip);
// make sure this is an address we want to talk to (i.e. a remote address)
if (!inet.isSiteLocalAddress() && !inet.isAnyLocalAddress()
&& !inet.isLoopbackAddress()
&& !inet.isMulticastAddress()
&& !inet.isLinkLocalAddress()) {
peers.add(new LevinPeer(inet, port, topVersion, currentHeight, topId));
}
}
}
}
private Socket socket = null;
private boolean connect() {
if (socket != null) throw new IllegalStateException();
try {
socket = new Socket();
socket.connect(nodeInfo.getLevinSocketAddress(), CONNECT_TIMEOUT);
socket.setSoTimeout(SOCKET_TIMEOUT);
} catch (IOException ex) {
//Timber.d(ex);
return false;
}
return true;
}
private boolean isConnected() {
return socket.isConnected();
}
private void disconnect() {
try {
dataInput = null;
dataOutput = null;
if ((socket != null) && (!socket.isClosed())) {
socket.close();
}
} catch (IOException ex) {
Timber.d(ex);
} finally {
socket = null;
}
}
private DataOutput dataOutput = null;
private DataOutput getDataOutput() throws IOException {
if (dataOutput == null)
synchronized (this) {
if (dataOutput == null)
dataOutput = new LittleEndianDataOutputStream(
socket.getOutputStream());
}
return dataOutput;
}
private DataInput dataInput = null;
private DataInput getDataInput() throws IOException {
if (dataInput == null)
synchronized (this) {
if (dataInput == null)
dataInput = new LittleEndianDataInputStream(
socket.getInputStream());
}
return dataInput;
}
static private Section handshakeRequest() {
Section section = new Section(); // root object
Section nodeData = new Section();
nodeData.add("local_time", (new Date()).getTime());
nodeData.add("my_port", 0);
byte[] networkId = Helper.hexToBytes("1230f171610441611731008216a1a110"); // mainnet
nodeData.add("network_id", networkId);
nodeData.add("peer_id", PEER_ID);
section.add("node_data", nodeData);
Section payloadData = new Section();
payloadData.add("cumulative_difficulty", 1L);
payloadData.add("current_height", 1L);
byte[] genesisHash =
Helper.hexToBytes("418015bb9ae982a1975da7d79277c2705727a56894ba0fb246adaabb1f4632e3");
payloadData.add("top_id", genesisHash);
payloadData.add("top_version", (byte) 1);
section.add("payload_data", payloadData);
return section;
}
static private Section flagsResponse() {
Section section = new Section(); // root object
section.add("support_flags", Bucket.P2P_SUPPORT_FLAGS);
return section;
}
}

View File

@ -1,42 +0,0 @@
/*
* Copyright (c) 2018 m2049r
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.levin.util;
import java.math.BigInteger;
import java.net.InetAddress;
import java.net.UnknownHostException;
public class HexHelper {
static public String bytesToHex(byte[] data) {
if ((data != null) && (data.length > 0))
return String.format("%0" + (data.length * 2) + "X",
new BigInteger(1, data));
else
return "";
}
static public InetAddress toInetAddress(int ip) {
try {
String ipAddress = String.format("%d.%d.%d.%d", (ip & 0xff),
(ip >> 8 & 0xff), (ip >> 16 & 0xff), (ip >> 24 & 0xff));
return InetAddress.getByName(ipAddress);
} catch (UnknownHostException ex) {
throw new IllegalArgumentException(ex);
}
}
}

View File

@ -1,184 +0,0 @@
/*
* Copyright (c) 2018 m2049r
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.levin.util;
import com.m2049r.levin.data.Section;
import java.io.ByteArrayInputStream;
import java.io.DataInput;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
// Full Levin reader as seen on epee
public class LevinReader {
private DataInput in;
private LevinReader(byte[] buffer) {
ByteArrayInputStream bis = new ByteArrayInputStream(buffer);
in = new LittleEndianDataInputStream(bis);
}
static public Section readPayload(byte[] payload) throws IOException {
LevinReader r = new LevinReader(payload);
return r.readPayload();
}
private Section readPayload() throws IOException {
if (in.readInt() != Section.PORTABLE_STORAGE_SIGNATUREA)
throw new IllegalStateException();
if (in.readInt() != Section.PORTABLE_STORAGE_SIGNATUREB)
throw new IllegalStateException();
if (in.readByte() != Section.PORTABLE_STORAGE_FORMAT_VER)
throw new IllegalStateException();
return readSection();
}
private Section readSection() throws IOException {
Section section = new Section();
long count = readVarint();
while (count-- > 0) {
// read section name string
String sectionName = readSectionName();
section.add(sectionName, loadStorageEntry());
}
return section;
}
private Object loadStorageArrayEntry(int type) throws IOException {
type &= ~Section.SERIALIZE_FLAG_ARRAY;
return readArrayEntry(type);
}
private List<Object> readArrayEntry(int type) throws IOException {
List<Object> list = new ArrayList<Object>();
long size = readVarint();
while (size-- > 0)
list.add(read(type));
return list;
}
private Object read(int type) throws IOException {
switch (type) {
case Section.SERIALIZE_TYPE_UINT64:
case Section.SERIALIZE_TYPE_INT64:
return in.readLong();
case Section.SERIALIZE_TYPE_UINT32:
case Section.SERIALIZE_TYPE_INT32:
return in.readInt();
case Section.SERIALIZE_TYPE_UINT16:
return in.readUnsignedShort();
case Section.SERIALIZE_TYPE_INT16:
return in.readShort();
case Section.SERIALIZE_TYPE_UINT8:
return in.readUnsignedByte();
case Section.SERIALIZE_TYPE_INT8:
return in.readByte();
case Section.SERIALIZE_TYPE_OBJECT:
return readSection();
case Section.SERIALIZE_TYPE_STRING:
return readByteArray();
default:
throw new IllegalArgumentException("type " + type
+ " not supported");
}
}
private Object loadStorageEntry() throws IOException {
int type = in.readUnsignedByte();
if ((type & Section.SERIALIZE_FLAG_ARRAY) != 0)
return loadStorageArrayEntry(type);
if (type == Section.SERIALIZE_TYPE_ARRAY)
return readStorageEntryArrayEntry();
else
return readStorageEntry(type);
}
private Object readStorageEntry(int type) throws IOException {
return read(type);
}
private Object readStorageEntryArrayEntry() throws IOException {
int type = in.readUnsignedByte();
if ((type & Section.SERIALIZE_FLAG_ARRAY) != 0)
throw new IllegalStateException("wrong type sequences");
return loadStorageArrayEntry(type);
}
private String readSectionName() throws IOException {
int nameLen = in.readUnsignedByte();
return readString(nameLen);
}
private byte[] read(long count) throws IOException {
if (count > Integer.MAX_VALUE)
throw new IllegalArgumentException();
int len = (int) count;
final byte buffer[] = new byte[len];
in.readFully(buffer);
return buffer;
}
private String readString(long count) throws IOException {
return new String(read(count), StandardCharsets.US_ASCII);
}
private byte[] readByteArray(long count) throws IOException {
return read(count);
}
private byte[] readByteArray() throws IOException {
long len = readVarint();
return readByteArray(len);
}
private long readVarint() throws IOException {
long v = 0;
int b = in.readUnsignedByte();
int sizeMask = b & Section.PORTABLE_RAW_SIZE_MARK_MASK;
switch (sizeMask) {
case Section.PORTABLE_RAW_SIZE_MARK_BYTE:
v = b >>> 2;
break;
case Section.PORTABLE_RAW_SIZE_MARK_WORD:
v = readRest(b, 1) >>> 2;
break;
case Section.PORTABLE_RAW_SIZE_MARK_DWORD:
v = readRest(b, 3) >>> 2;
break;
case Section.PORTABLE_RAW_SIZE_MARK_INT64:
v = readRest(b, 7) >>> 2;
break;
default:
throw new IllegalStateException();
}
return v;
}
// this should be in LittleEndianDataInputStream because it has little
// endian logic
private long readRest(final int firstByte, final int bytes) throws IOException {
long result = firstByte;
for (int i = 1; i < bytes + 1; i++) {
result = result + (((long) in.readUnsignedByte()) << (8 * i));
}
return result;
}
}

View File

@ -1,98 +0,0 @@
/*
* Copyright (c) 2018 m2049r
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.levin.util;
import com.m2049r.levin.data.Section;
import java.io.DataOutput;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Map;
// a simplified Levin Writer WITHOUT support for arrays
public class LevinWriter {
private DataOutput out;
public LevinWriter(DataOutput out) {
this.out = out;
}
public void writePayload(Section section) throws IOException {
out.writeInt(Section.PORTABLE_STORAGE_SIGNATUREA);
out.writeInt(Section.PORTABLE_STORAGE_SIGNATUREB);
out.writeByte(Section.PORTABLE_STORAGE_FORMAT_VER);
putSection(section);
}
private void writeSection(Section section) throws IOException {
out.writeByte(Section.SERIALIZE_TYPE_OBJECT);
putSection(section);
}
private void putSection(Section section) throws IOException {
writeVarint(section.size());
for (Map.Entry<String, Object> kv : section.entrySet()) {
byte[] key = kv.getKey().getBytes(StandardCharsets.US_ASCII);
out.writeByte(key.length);
out.write(key);
write(kv.getValue());
}
}
private void writeVarint(long i) throws IOException {
if (i <= 63) {
out.writeByte(((int) i << 2) | Section.PORTABLE_RAW_SIZE_MARK_BYTE);
} else if (i <= 16383) {
out.writeShort(((int) i << 2) | Section.PORTABLE_RAW_SIZE_MARK_WORD);
} else if (i <= 1073741823) {
out.writeInt(((int) i << 2) | Section.PORTABLE_RAW_SIZE_MARK_DWORD);
} else {
if (i > 4611686018427387903L)
throw new IllegalArgumentException();
out.writeLong((i << 2) | Section.PORTABLE_RAW_SIZE_MARK_INT64);
}
}
private void write(Object object) throws IOException {
if (object instanceof byte[]) {
byte[] value = (byte[]) object;
out.writeByte(Section.SERIALIZE_TYPE_STRING);
writeVarint(value.length);
out.write(value);
} else if (object instanceof String) {
byte[] value = ((String) object)
.getBytes(StandardCharsets.US_ASCII);
out.writeByte(Section.SERIALIZE_TYPE_STRING);
writeVarint(value.length);
out.write(value);
} else if (object instanceof Integer) {
out.writeByte(Section.SERIALIZE_TYPE_UINT32);
out.writeInt((int) object);
} else if (object instanceof Long) {
out.writeByte(Section.SERIALIZE_TYPE_UINT64);
out.writeLong((long) object);
} else if (object instanceof Byte) {
out.writeByte(Section.SERIALIZE_TYPE_UINT8);
out.writeByte((byte) object);
} else if (object instanceof Section) {
writeSection((Section) object);
} else {
throw new IllegalArgumentException();
}
}
}

View File

@ -1,564 +0,0 @@
/*
* Copyright (c) 2018 m2049r
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.levin.util;
import java.io.DataInput;
import java.io.EOFException;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UTFDataFormatException;
/**
* A little endian java.io.DataInputStream (without readLine())
*/
public class LittleEndianDataInputStream extends FilterInputStream implements
DataInput {
/**
* Creates a DataInputStream that uses the specified underlying InputStream.
*
* @param in the specified input stream
*/
public LittleEndianDataInputStream(InputStream in) {
super(in);
}
@Deprecated
public final String readLine() {
throw new UnsupportedOperationException();
}
/**
* Reads some number of bytes from the contained input stream and stores
* them into the buffer array <code>b</code>. The number of bytes actually
* read is returned as an integer. This method blocks until input data is
* available, end of file is detected, or an exception is thrown.
*
* <p>
* If <code>b</code> is null, a <code>NullPointerException</code> is thrown.
* If the length of <code>b</code> is zero, then no bytes are read and
* <code>0</code> is returned; otherwise, there is an attempt to read at
* least one byte. If no byte is available because the stream is at end of
* file, the value <code>-1</code> is returned; otherwise, at least one byte
* is read and stored into <code>b</code>.
*
* <p>
* The first byte read is stored into element <code>b[0]</code>, the next
* one into <code>b[1]</code>, and so on. The number of bytes read is, at
* most, equal to the length of <code>b</code>. Let <code>k</code> be the
* number of bytes actually read; these bytes will be stored in elements
* <code>b[0]</code> through <code>b[k-1]</code>, leaving elements
* <code>b[k]</code> through <code>b[b.length-1]</code> unaffected.
*
* <p>
* The <code>read(b)</code> method has the same effect as: <blockquote>
*
* <pre>
* read(b, 0, b.length)
* </pre>
*
* </blockquote>
*
* @param b the buffer into which the data is read.
* @return the total number of bytes read into the buffer, or
* <code>-1</code> if there is no more data because the end of the
* stream has been reached.
* @throws IOException if the first byte cannot be read for any reason other than
* end of file, the stream has been closed and the underlying
* input stream does not support reading after close, or
* another I/O error occurs.
* @see FilterInputStream#in
* @see InputStream#read(byte[], int, int)
*/
public final int read(byte b[]) throws IOException {
return in.read(b, 0, b.length);
}
/**
* Reads up to <code>len</code> bytes of data from the contained input
* stream into an array of bytes. An attempt is made to read as many as
* <code>len</code> bytes, but a smaller number may be read, possibly zero.
* The number of bytes actually read is returned as an integer.
*
* <p>
* This method blocks until input data is available, end of file is
* detected, or an exception is thrown.
*
* <p>
* If <code>len</code> is zero, then no bytes are read and <code>0</code> is
* returned; otherwise, there is an attempt to read at least one byte. If no
* byte is available because the stream is at end of file, the value
* <code>-1</code> is returned; otherwise, at least one byte is read and
* stored into <code>b</code>.
*
* <p>
* The first byte read is stored into element <code>b[off]</code>, the next
* one into <code>b[off+1]</code>, and so on. The number of bytes read is,
* at most, equal to <code>len</code>. Let <i>k</i> be the number of bytes
* actually read; these bytes will be stored in elements <code>b[off]</code>
* through <code>b[off+</code><i>k</i><code>-1]</code>, leaving elements
* <code>b[off+</code><i>k</i><code>]</code> through
* <code>b[off+len-1]</code> unaffected.
*
* <p>
* In every case, elements <code>b[0]</code> through <code>b[off]</code> and
* elements <code>b[off+len]</code> through <code>b[b.length-1]</code> are
* unaffected.
*
* @param b the buffer into which the data is read.
* @param off the start offset in the destination array <code>b</code>
* @param len the maximum number of bytes read.
* @return the total number of bytes read into the buffer, or
* <code>-1</code> if there is no more data because the end of the
* stream has been reached.
* @throws NullPointerException If <code>b</code> is <code>null</code>.
* @throws IndexOutOfBoundsException If <code>off</code> is negative, <code>len</code> is
* negative, or <code>len</code> is greater than
* <code>b.length - off</code>
* @throws IOException if the first byte cannot be read for any reason other than
* end of file, the stream has been closed and the underlying
* input stream does not support reading after close, or
* another I/O error occurs.
* @see FilterInputStream#in
* @see InputStream#read(byte[], int, int)
*/
public final int read(byte b[], int off, int len) throws IOException {
return in.read(b, off, len);
}
/**
* See the general contract of the <code>readFully</code> method of
* <code>DataInput</code>.
* <p>
* Bytes for this operation are read from the contained input stream.
*
* @param b the buffer into which the data is read.
* @throws EOFException if this input stream reaches the end before reading all
* the bytes.
* @throws IOException the stream has been closed and the contained input stream
* does not support reading after close, or another I/O error
* occurs.
* @see FilterInputStream#in
*/
public final void readFully(byte b[]) throws IOException {
readFully(b, 0, b.length);
}
/**
* See the general contract of the <code>readFully</code> method of
* <code>DataInput</code>.
* <p>
* Bytes for this operation are read from the contained input stream.
*
* @param b the buffer into which the data is read.
* @param off the start offset of the data.
* @param len the number of bytes to read.
* @throws EOFException if this input stream reaches the end before reading all
* the bytes.
* @throws IOException the stream has been closed and the contained input stream
* does not support reading after close, or another I/O error
* occurs.
* @see FilterInputStream#in
*/
public final void readFully(byte b[], int off, int len) throws IOException {
if (len < 0)
throw new IndexOutOfBoundsException();
int n = 0;
while (n < len) {
int count = in.read(b, off + n, len - n);
if (count < 0)
throw new EOFException();
n += count;
}
}
/**
* See the general contract of the <code>skipBytes</code> method of
* <code>DataInput</code>.
* <p>
* Bytes for this operation are read from the contained input stream.
*
* @param n the number of bytes to be skipped.
* @return the actual number of bytes skipped.
* @throws IOException if the contained input stream does not support seek, or
* the stream has been closed and the contained input stream
* does not support reading after close, or another I/O error
* occurs.
*/
public final int skipBytes(int n) throws IOException {
int total = 0;
int cur = 0;
while ((total < n) && ((cur = (int) in.skip(n - total)) > 0)) {
total += cur;
}
return total;
}
/**
* See the general contract of the <code>readBoolean</code> method of
* <code>DataInput</code>.
* <p>
* Bytes for this operation are read from the contained input stream.
*
* @return the <code>boolean</code> value read.
* @throws EOFException if this input stream has reached the end.
* @throws IOException the stream has been closed and the contained input stream
* does not support reading after close, or another I/O error
* occurs.
* @see FilterInputStream#in
*/
public final boolean readBoolean() throws IOException {
int ch = in.read();
if (ch < 0)
throw new EOFException();
return (ch != 0);
}
/**
* See the general contract of the <code>readByte</code> method of
* <code>DataInput</code>.
* <p>
* Bytes for this operation are read from the contained input stream.
*
* @return the next byte of this input stream as a signed 8-bit
* <code>byte</code>.
* @throws EOFException if this input stream has reached the end.
* @throws IOException the stream has been closed and the contained input stream
* does not support reading after close, or another I/O error
* occurs.
* @see FilterInputStream#in
*/
public final byte readByte() throws IOException {
int ch = in.read();
if (ch < 0)
throw new EOFException();
return (byte) (ch);
}
/**
* See the general contract of the <code>readUnsignedByte</code> method of
* <code>DataInput</code>.
* <p>
* Bytes for this operation are read from the contained input stream.
*
* @return the next byte of this input stream, interpreted as an unsigned
* 8-bit number.
* @throws EOFException if this input stream has reached the end.
* @throws IOException the stream has been closed and the contained input stream
* does not support reading after close, or another I/O error
* occurs.
* @see FilterInputStream#in
*/
public final int readUnsignedByte() throws IOException {
int ch = in.read();
if (ch < 0)
throw new EOFException();
return ch;
}
/**
* See the general contract of the <code>readShort</code> method of
* <code>DataInput</code>.
* <p>
* Bytes for this operation are read from the contained input stream.
*
* @return the next two bytes of this input stream, interpreted as a signed
* 16-bit number.
* @throws EOFException if this input stream reaches the end before reading two
* bytes.
* @throws IOException the stream has been closed and the contained input stream
* does not support reading after close, or another I/O error
* occurs.
* @see FilterInputStream#in
*/
public final short readShort() throws IOException {
int ch1 = in.read();
int ch2 = in.read();
if ((ch1 | ch2) < 0)
throw new EOFException();
return (short) ((ch1 << 0) + (ch2 << 8));
}
/**
* See the general contract of the <code>readUnsignedShort</code> method of
* <code>DataInput</code>.
* <p>
* Bytes for this operation are read from the contained input stream.
*
* @return the next two bytes of this input stream, interpreted as an
* unsigned 16-bit integer.
* @throws EOFException if this input stream reaches the end before reading two
* bytes.
* @throws IOException the stream has been closed and the contained input stream
* does not support reading after close, or another I/O error
* occurs.
* @see FilterInputStream#in
*/
public final int readUnsignedShort() throws IOException {
int ch1 = in.read();
int ch2 = in.read();
if ((ch1 | ch2) < 0)
throw new EOFException();
return (ch1 << 0) + (ch2 << 8);
}
/**
* See the general contract of the <code>readChar</code> method of
* <code>DataInput</code>.
* <p>
* Bytes for this operation are read from the contained input stream.
*
* @return the next two bytes of this input stream, interpreted as a
* <code>char</code>.
* @throws EOFException if this input stream reaches the end before reading two
* bytes.
* @throws IOException the stream has been closed and the contained input stream
* does not support reading after close, or another I/O error
* occurs.
* @see FilterInputStream#in
*/
public final char readChar() throws IOException {
int ch1 = in.read();
int ch2 = in.read();
if ((ch1 | ch2) < 0)
throw new EOFException();
return (char) ((ch1 << 0) + (ch2 << 8));
}
/**
* See the general contract of the <code>readInt</code> method of
* <code>DataInput</code>.
* <p>
* Bytes for this operation are read from the contained input stream.
*
* @return the next four bytes of this input stream, interpreted as an
* <code>int</code>.
* @throws EOFException if this input stream reaches the end before reading four
* bytes.
* @throws IOException the stream has been closed and the contained input stream
* does not support reading after close, or another I/O error
* occurs.
* @see FilterInputStream#in
*/
public final int readInt() throws IOException {
int ch1 = in.read();
int ch2 = in.read();
int ch3 = in.read();
int ch4 = in.read();
if ((ch1 | ch2 | ch3 | ch4) < 0)
throw new EOFException();
return ((ch1 << 0) + (ch2 << 8) + (ch3 << 16) + (ch4 << 24));
}
private byte readBuffer[] = new byte[8];
/**
* See the general contract of the <code>readLong</code> method of
* <code>DataInput</code>.
* <p>
* Bytes for this operation are read from the contained input stream.
*
* @return the next eight bytes of this input stream, interpreted as a
* <code>long</code>.
* @throws EOFException if this input stream reaches the end before reading eight
* bytes.
* @throws IOException the stream has been closed and the contained input stream
* does not support reading after close, or another I/O error
* occurs.
* @see FilterInputStream#in
*/
public final long readLong() throws IOException {
readFully(readBuffer, 0, 8);
return (((long) readBuffer[7] << 56)
+ ((long) (readBuffer[6] & 255) << 48)
+ ((long) (readBuffer[5] & 255) << 40)
+ ((long) (readBuffer[4] & 255) << 32)
+ ((long) (readBuffer[3] & 255) << 24)
+ ((readBuffer[2] & 255) << 16) + ((readBuffer[1] & 255) << 8) + ((readBuffer[0] & 255) << 0));
}
/**
* See the general contract of the <code>readFloat</code> method of
* <code>DataInput</code>.
* <p>
* Bytes for this operation are read from the contained input stream.
*
* @return the next four bytes of this input stream, interpreted as a
* <code>float</code>.
* @throws EOFException if this input stream reaches the end before reading four
* bytes.
* @throws IOException the stream has been closed and the contained input stream
* does not support reading after close, or another I/O error
* occurs.
* @see java.io.DataInputStream#readInt()
* @see Float#intBitsToFloat(int)
*/
public final float readFloat() throws IOException {
return Float.intBitsToFloat(readInt());
}
/**
* See the general contract of the <code>readDouble</code> method of
* <code>DataInput</code>.
* <p>
* Bytes for this operation are read from the contained input stream.
*
* @return the next eight bytes of this input stream, interpreted as a
* <code>double</code>.
* @throws EOFException if this input stream reaches the end before reading eight
* bytes.
* @throws IOException the stream has been closed and the contained input stream
* does not support reading after close, or another I/O error
* occurs.
* @see java.io.DataInputStream#readLong()
* @see Double#longBitsToDouble(long)
*/
public final double readDouble() throws IOException {
return Double.longBitsToDouble(readLong());
}
/**
* See the general contract of the <code>readUTF</code> method of
* <code>DataInput</code>.
* <p>
* Bytes for this operation are read from the contained input stream.
*
* @return a Unicode string.
* @throws EOFException if this input stream reaches the end before reading all
* the bytes.
* @throws IOException the stream has been closed and the contained input stream
* does not support reading after close, or another I/O error
* occurs.
* @throws UTFDataFormatException if the bytes do not represent a valid modified UTF-8
* encoding of a string.
* @see java.io.DataInputStream#readUTF(DataInput)
*/
public final String readUTF() throws IOException {
return readUTF(this);
}
/**
* working arrays initialized on demand by readUTF
*/
private byte bytearr[] = new byte[80];
private char chararr[] = new char[80];
/**
* Reads from the stream <code>in</code> a representation of a Unicode
* character string encoded in <a
* href="DataInput.html#modified-utf-8">modified UTF-8</a> format; this
* string of characters is then returned as a <code>String</code>. The
* details of the modified UTF-8 representation are exactly the same as for
* the <code>readUTF</code> method of <code>DataInput</code>.
*
* @param in a data input stream.
* @return a Unicode string.
* @throws EOFException if the input stream reaches the end before all the bytes.
* @throws IOException the stream has been closed and the contained input stream
* does not support reading after close, or another I/O error
* occurs.
* @throws UTFDataFormatException if the bytes do not represent a valid modified UTF-8
* encoding of a Unicode string.
* @see java.io.DataInputStream#readUnsignedShort()
*/
public final static String readUTF(DataInput in) throws IOException {
int utflen = in.readUnsignedShort();
byte[] bytearr = null;
char[] chararr = null;
if (in instanceof LittleEndianDataInputStream) {
LittleEndianDataInputStream dis = (LittleEndianDataInputStream) in;
if (dis.bytearr.length < utflen) {
dis.bytearr = new byte[utflen * 2];
dis.chararr = new char[utflen * 2];
}
chararr = dis.chararr;
bytearr = dis.bytearr;
} else {
bytearr = new byte[utflen];
chararr = new char[utflen];
}
int c, char2, char3;
int count = 0;
int chararr_count = 0;
in.readFully(bytearr, 0, utflen);
while (count < utflen) {
c = (int) bytearr[count] & 0xff;
if (c > 127)
break;
count++;
chararr[chararr_count++] = (char) c;
}
while (count < utflen) {
c = (int) bytearr[count] & 0xff;
switch (c >> 4) {
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
/* 0xxxxxxx */
count++;
chararr[chararr_count++] = (char) c;
break;
case 12:
case 13:
/* 110x xxxx 10xx xxxx */
count += 2;
if (count > utflen)
throw new UTFDataFormatException(
"malformed input: partial character at end");
char2 = (int) bytearr[count - 1];
if ((char2 & 0xC0) != 0x80)
throw new UTFDataFormatException(
"malformed input around byte " + count);
chararr[chararr_count++] = (char) (((c & 0x1F) << 6) | (char2 & 0x3F));
break;
case 14:
/* 1110 xxxx 10xx xxxx 10xx xxxx */
count += 3;
if (count > utflen)
throw new UTFDataFormatException(
"malformed input: partial character at end");
char2 = (int) bytearr[count - 2];
char3 = (int) bytearr[count - 1];
if (((char2 & 0xC0) != 0x80) || ((char3 & 0xC0) != 0x80))
throw new UTFDataFormatException(
"malformed input around byte " + (count - 1));
chararr[chararr_count++] = (char) (((c & 0x0F) << 12)
| ((char2 & 0x3F) << 6) | ((char3 & 0x3F) << 0));
break;
default:
/* 10xx xxxx, 1111 xxxx */
throw new UTFDataFormatException("malformed input around byte "
+ count);
}
}
// The number of chars produced may be less than utflen
return new String(chararr, 0, chararr_count);
}
}

View File

@ -1,403 +0,0 @@
/*
* Copyright (c) 2018 m2049r
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.levin.util;
import java.io.DataOutput;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UTFDataFormatException;
/**
* A little endian java.io.DataOutputStream
*/
public class LittleEndianDataOutputStream extends FilterOutputStream implements
DataOutput {
/**
* The number of bytes written to the data output stream so far. If this
* counter overflows, it will be wrapped to Integer.MAX_VALUE.
*/
protected int written;
/**
* Creates a new data output stream to write data to the specified
* underlying output stream. The counter <code>written</code> is set to
* zero.
*
* @param out the underlying output stream, to be saved for later use.
* @see FilterOutputStream#out
*/
public LittleEndianDataOutputStream(OutputStream out) {
super(out);
}
/**
* Increases the written counter by the specified value until it reaches
* Integer.MAX_VALUE.
*/
private void incCount(int value) {
int temp = written + value;
if (temp < 0) {
temp = Integer.MAX_VALUE;
}
written = temp;
}
/**
* Writes the specified byte (the low eight bits of the argument
* <code>b</code>) to the underlying output stream. If no exception is
* thrown, the counter <code>written</code> is incremented by <code>1</code>
* .
* <p>
* Implements the <code>write</code> method of <code>OutputStream</code>.
*
* @param b the <code>byte</code> to be written.
* @throws IOException if an I/O error occurs.
* @see FilterOutputStream#out
*/
public synchronized void write(int b) throws IOException {
out.write(b);
incCount(1);
}
/**
* Writes <code>len</code> bytes from the specified byte array starting at
* offset <code>off</code> to the underlying output stream. If no exception
* is thrown, the counter <code>written</code> is incremented by
* <code>len</code>.
*
* @param b the data.
* @param off the start offset in the data.
* @param len the number of bytes to write.
* @throws IOException if an I/O error occurs.
* @see FilterOutputStream#out
*/
public synchronized void write(byte b[], int off, int len)
throws IOException {
out.write(b, off, len);
incCount(len);
}
/**
* Flushes this data output stream. This forces any buffered output bytes to
* be written out to the stream.
* <p>
* The <code>flush</code> method of <code>DataOutputStream</code> calls the
* <code>flush</code> method of its underlying output stream.
*
* @throws IOException if an I/O error occurs.
* @see FilterOutputStream#out
* @see OutputStream#flush()
*/
public void flush() throws IOException {
out.flush();
}
/**
* Writes a <code>boolean</code> to the underlying output stream as a 1-byte
* value. The value <code>true</code> is written out as the value
* <code>(byte)1</code>; the value <code>false</code> is written out as the
* value <code>(byte)0</code>. If no exception is thrown, the counter
* <code>written</code> is incremented by <code>1</code>.
*
* @param v a <code>boolean</code> value to be written.
* @throws IOException if an I/O error occurs.
* @see FilterOutputStream#out
*/
public final void writeBoolean(boolean v) throws IOException {
out.write(v ? 1 : 0);
incCount(1);
}
/**
* Writes out a <code>byte</code> to the underlying output stream as a
* 1-byte value. If no exception is thrown, the counter <code>written</code>
* is incremented by <code>1</code>.
*
* @param v a <code>byte</code> value to be written.
* @throws IOException if an I/O error occurs.
* @see FilterOutputStream#out
*/
public final void writeByte(int v) throws IOException {
out.write(v);
incCount(1);
}
/**
* Writes a <code>short</code> to the underlying output stream as two bytes,
* low byte first. If no exception is thrown, the counter
* <code>written</code> is incremented by <code>2</code>.
*
* @param v a <code>short</code> to be written.
* @throws IOException if an I/O error occurs.
* @see FilterOutputStream#out
*/
public final void writeShort(int v) throws IOException {
out.write((v >>> 0) & 0xFF);
out.write((v >>> 8) & 0xFF);
incCount(2);
}
/**
* Writes a <code>char</code> to the underlying output stream as a 2-byte
* value, low byte first. If no exception is thrown, the counter
* <code>written</code> is incremented by <code>2</code>.
*
* @param v a <code>char</code> value to be written.
* @throws IOException if an I/O error occurs.
* @see FilterOutputStream#out
*/
public final void writeChar(int v) throws IOException {
out.write((v >>> 0) & 0xFF);
out.write((v >>> 8) & 0xFF);
incCount(2);
}
/**
* Writes an <code>int</code> to the underlying output stream as four bytes,
* low byte first. If no exception is thrown, the counter
* <code>written</code> is incremented by <code>4</code>.
*
* @param v an <code>int</code> to be written.
* @throws IOException if an I/O error occurs.
* @see FilterOutputStream#out
*/
public final void writeInt(int v) throws IOException {
out.write((v >>> 0) & 0xFF);
out.write((v >>> 8) & 0xFF);
out.write((v >>> 16) & 0xFF);
out.write((v >>> 24) & 0xFF);
incCount(4);
}
private byte writeBuffer[] = new byte[8];
/**
* Writes a <code>long</code> to the underlying output stream as eight
* bytes, low byte first. In no exception is thrown, the counter
* <code>written</code> is incremented by <code>8</code>.
*
* @param v a <code>long</code> to be written.
* @throws IOException if an I/O error occurs.
* @see FilterOutputStream#out
*/
public final void writeLong(long v) throws IOException {
writeBuffer[7] = (byte) (v >>> 56);
writeBuffer[6] = (byte) (v >>> 48);
writeBuffer[5] = (byte) (v >>> 40);
writeBuffer[4] = (byte) (v >>> 32);
writeBuffer[3] = (byte) (v >>> 24);
writeBuffer[2] = (byte) (v >>> 16);
writeBuffer[1] = (byte) (v >>> 8);
writeBuffer[0] = (byte) (v >>> 0);
out.write(writeBuffer, 0, 8);
incCount(8);
}
/**
* Converts the float argument to an <code>int</code> using the
* <code>floatToIntBits</code> method in class <code>Float</code>, and then
* writes that <code>int</code> value to the underlying output stream as a
* 4-byte quantity, low byte first. If no exception is thrown, the counter
* <code>written</code> is incremented by <code>4</code>.
*
* @param v a <code>float</code> value to be written.
* @throws IOException if an I/O error occurs.
* @see FilterOutputStream#out
* @see Float#floatToIntBits(float)
*/
public final void writeFloat(float v) throws IOException {
writeInt(Float.floatToIntBits(v));
}
/**
* Converts the double argument to a <code>long</code> using the
* <code>doubleToLongBits</code> method in class <code>Double</code>, and
* then writes that <code>long</code> value to the underlying output stream
* as an 8-byte quantity, low byte first. If no exception is thrown, the
* counter <code>written</code> is incremented by <code>8</code>.
*
* @param v a <code>double</code> value to be written.
* @throws IOException if an I/O error occurs.
* @see FilterOutputStream#out
* @see Double#doubleToLongBits(double)
*/
public final void writeDouble(double v) throws IOException {
writeLong(Double.doubleToLongBits(v));
}
/**
* Writes out the string to the underlying output stream as a sequence of
* bytes. Each character in the string is written out, in sequence, by
* discarding its high eight bits. If no exception is thrown, the counter
* <code>written</code> is incremented by the length of <code>s</code>.
*
* @param s a string of bytes to be written.
* @throws IOException if an I/O error occurs.
* @see FilterOutputStream#out
*/
public final void writeBytes(String s) throws IOException {
int len = s.length();
for (int i = 0; i < len; i++) {
out.write((byte) s.charAt(i));
}
incCount(len);
}
/**
* Writes a string to the underlying output stream as a sequence of
* characters. Each character is written to the data output stream as if by
* the <code>writeChar</code> method. If no exception is thrown, the counter
* <code>written</code> is incremented by twice the length of <code>s</code>
* .
*
* @param s a <code>String</code> value to be written.
* @throws IOException if an I/O error occurs.
* @see java.io.DataOutputStream#writeChar(int)
* @see FilterOutputStream#out
*/
public final void writeChars(String s) throws IOException {
int len = s.length();
for (int i = 0; i < len; i++) {
int v = s.charAt(i);
out.write((v >>> 0) & 0xFF);
out.write((v >>> 8) & 0xFF);
}
incCount(len * 2);
}
/**
* Writes a string to the underlying output stream using <a
* href="DataInput.html#modified-utf-8">modified UTF-8</a> encoding in a
* machine-independent manner.
* <p>
* First, two bytes are written to the output stream as if by the
* <code>writeShort</code> method giving the number of bytes to follow. This
* value is the number of bytes actually written out, not the length of the
* string. Following the length, each character of the string is output, in
* sequence, using the modified UTF-8 encoding for the character. If no
* exception is thrown, the counter <code>written</code> is incremented by
* the total number of bytes written to the output stream. This will be at
* least two plus the length of <code>str</code>, and at most two plus
* thrice the length of <code>str</code>.
*
* @param str a string to be written.
* @throws IOException if an I/O error occurs.
*/
public final void writeUTF(String str) throws IOException {
writeUTF(str, this);
}
/**
* bytearr is initialized on demand by writeUTF
*/
private byte[] bytearr = null;
/**
* Writes a string to the specified DataOutput using <a
* href="DataInput.html#modified-utf-8">modified UTF-8</a> encoding in a
* machine-independent manner.
* <p>
* First, two bytes are written to out as if by the <code>writeShort</code>
* method giving the number of bytes to follow. This value is the number of
* bytes actually written out, not the length of the string. Following the
* length, each character of the string is output, in sequence, using the
* modified UTF-8 encoding for the character. If no exception is thrown, the
* counter <code>written</code> is incremented by the total number of bytes
* written to the output stream. This will be at least two plus the length
* of <code>str</code>, and at most two plus thrice the length of
* <code>str</code>.
*
* @param str a string to be written.
* @param out destination to write to
* @return The number of bytes written out.
* @throws IOException if an I/O error occurs.
*/
static int writeUTF(String str, DataOutput out) throws IOException {
int strlen = str.length();
int utflen = 0;
int c, count = 0;
/* use charAt instead of copying String to char array */
for (int i = 0; i < strlen; i++) {
c = str.charAt(i);
if ((c >= 0x0001) && (c <= 0x007F)) {
utflen++;
} else if (c > 0x07FF) {
utflen += 3;
} else {
utflen += 2;
}
}
if (utflen > 65535)
throw new UTFDataFormatException("encoded string too long: "
+ utflen + " bytes");
byte[] bytearr = null;
if (out instanceof LittleEndianDataOutputStream) {
LittleEndianDataOutputStream dos = (LittleEndianDataOutputStream) out;
if (dos.bytearr == null || (dos.bytearr.length < (utflen + 2)))
dos.bytearr = new byte[(utflen * 2) + 2];
bytearr = dos.bytearr;
} else {
bytearr = new byte[utflen + 2];
}
bytearr[count++] = (byte) ((utflen >>> 8) & 0xFF);
bytearr[count++] = (byte) ((utflen >>> 0) & 0xFF);
int i = 0;
for (i = 0; i < strlen; i++) {
c = str.charAt(i);
if (!((c >= 0x0001) && (c <= 0x007F)))
break;
bytearr[count++] = (byte) c;
}
for (; i < strlen; i++) {
c = str.charAt(i);
if ((c >= 0x0001) && (c <= 0x007F)) {
bytearr[count++] = (byte) c;
} else if (c > 0x07FF) {
bytearr[count++] = (byte) (0xE0 | ((c >> 12) & 0x0F));
bytearr[count++] = (byte) (0x80 | ((c >> 6) & 0x3F));
bytearr[count++] = (byte) (0x80 | ((c >> 0) & 0x3F));
} else {
bytearr[count++] = (byte) (0xC0 | ((c >> 6) & 0x1F));
bytearr[count++] = (byte) (0x80 | ((c >> 0) & 0x3F));
}
}
out.write(bytearr, 0, utflen + 2);
return utflen + 2;
}
/**
* Returns the current value of the counter <code>written</code>, the number
* of bytes written to this data output stream so far. If the counter
* overflows, it will be wrapped to Integer.MAX_VALUE.
*
* @return the value of the <code>written</code> field.
* @see java.io.DataOutputStream#written
*/
public final int size() {
return written;
}
}

View File

@ -18,46 +18,73 @@ package com.m2049r.xmrwallet.adapter;
import static com.m2049r.xmrwallet.util.DateHelper.DATETIME_FORMATTER;
import android.content.Context;
import android.text.Html;
import android.text.Spanned;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.leanback.widget.DiffCallback;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.progressindicator.CircularProgressIndicator;
import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.data.Crypto;
import com.m2049r.xmrwallet.data.UserNotes;
import com.m2049r.xmrwallet.model.TransactionInfo;
import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.ThemeHelper;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.TimeZone;
import timber.log.Timber;
public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfoAdapter.ViewHolder> {
private List<TransactionInfo> localDataSet;
private TxInfoAdapterListener listener = null;
/**
* Initialize the dataset of the Adapter.
*/
public TransactionInfoAdapter(TxInfoAdapterListener listener) {
this.listener = listener;
this.localDataSet = new ArrayList<>();
}
public void submitList(List<TransactionInfo> dataSet) {
this.localDataSet = dataSet;
notifyDataSetChanged();
}
// Create new views (invoked by the layout manager)
@Override
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
// Create a new view, which defines the UI of the list item
View view = LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.transaction_history_item, viewGroup, false);
return new ViewHolder(listener, view);
}
// Replace the contents of a view (invoked by the layout manager)
@Override
public void onBindViewHolder(ViewHolder viewHolder, final int position) {
TransactionInfo tx = localDataSet.get(position);
viewHolder.bind(tx);
}
// Return the size of your dataset (invoked by the layout manager)
@Override
public int getItemCount() {
return localDataSet.size();
}
public interface TxInfoAdapterListener {
void onClickTransaction(TransactionInfo txInfo);
}
/**
* Provide a reference to the type of views that you are using
* (custom ViewHolder).
@ -69,6 +96,7 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
private final int failedColour;
private TxInfoAdapterListener listener = null;
private TextView amountTextView = null;
public ViewHolder(TxInfoAdapterListener listener, View view) {
super(view);
inboundColour = ThemeHelper.getThemedColor(view.getContext(), R.attr.positiveColor);
@ -165,45 +193,5 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
return DATETIME_FORMATTER.format(new Date(time * 1000));
}
}
/**
* Initialize the dataset of the Adapter.
*/
public TransactionInfoAdapter(TxInfoAdapterListener listener) {
this.listener = listener;
this.localDataSet = new ArrayList<>();
}
public void submitList(List<TransactionInfo> dataSet) {
this.localDataSet = dataSet;
notifyDataSetChanged();
}
// Create new views (invoked by the layout manager)
@Override
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
// Create a new view, which defines the UI of the list item
View view = LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.transaction_history_item, viewGroup, false);
return new ViewHolder(listener, view);
}
// Replace the contents of a view (invoked by the layout manager)
@Override
public void onBindViewHolder(ViewHolder viewHolder, final int position) {
TransactionInfo tx = localDataSet.get(position);
viewHolder.bind(tx);
}
// Return the size of your dataset (invoked by the layout manager)
@Override
public int getItemCount() {
return localDataSet.size();
}
public interface TxInfoAdapterListener {
void onClickTransaction(TransactionInfo txInfo);
}
}

View File

@ -1,232 +0,0 @@
/*
* Copyright (c) 2017 m2049r
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.xmrwallet.data;
import android.net.Uri;
import com.m2049r.xmrwallet.util.OpenAliasHelper;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import lombok.ToString;
import timber.log.Timber;
@ToString
public class BarcodeData {
public enum Security {
NORMAL,
OA_NO_DNSSEC,
OA_DNSSEC
}
final public Crypto asset;
final public List<Crypto> ambiguousAssets;
final public String address;
final public String addressName;
final public String amount;
final public String description;
final public Security security;
public BarcodeData(List<Crypto> assets, String address) {
if (assets.isEmpty())
throw new IllegalArgumentException("no assets specified");
this.addressName = null;
this.description = null;
this.amount = null;
this.security = Security.NORMAL;
this.address = address;
if (assets.size() == 1) {
this.asset = assets.get(0);
this.ambiguousAssets = null;
} else {
this.asset = null;
this.ambiguousAssets = assets;
}
}
public BarcodeData(Crypto asset, String address, String description, String amount) {
this(asset, address, null, description, amount, Security.NORMAL);
}
public BarcodeData(Crypto asset, String address, String addressName, String description, String amount, Security security) {
this.ambiguousAssets = null;
this.asset = asset;
this.address = address;
this.addressName = addressName;
this.description = description;
this.amount = amount;
this.security = security;
}
public Uri getUri() {
return Uri.parse(getUriString());
}
public String getUriString() {
if (asset != Crypto.XMR) throw new IllegalStateException("We can only do XMR stuff!");
StringBuilder sb = new StringBuilder();
sb.append(Crypto.XMR.getUriScheme())
.append(':')
.append(address);
boolean first = true;
if ((description != null) && !description.isEmpty()) {
sb.append(first ? "?" : "&");
first = false;
sb.append(Crypto.XMR.getUriMessage()).append('=').append(Uri.encode(description));
}
if ((amount != null) && !amount.isEmpty()) {
sb.append(first ? "?" : "&");
sb.append(Crypto.XMR.getUriAmount()).append('=').append(amount);
}
return sb.toString();
}
static private BarcodeData parseNaked(String address) {
List<Crypto> possibleAssets = new ArrayList<>();
for (Crypto crypto : Crypto.values()) {
if (crypto.validate(address)) {
possibleAssets.add(crypto);
}
}
if (possibleAssets.isEmpty())
return null;
return new BarcodeData(possibleAssets, address);
}
static public BarcodeData parseUri(String uriString) {
Timber.d("parseBitUri=%s", uriString);
URI uri;
try {
uri = new URI(uriString);
} catch (URISyntaxException ex) {
return null;
}
if (!uri.isOpaque()) return null;
final String scheme = uri.getScheme();
Crypto crypto = Crypto.withScheme(scheme);
if (crypto == null) return null;
String[] parts = uri.getRawSchemeSpecificPart().split("[?]");
if ((parts.length <= 0) || (parts.length > 2)) {
Timber.d("invalid number of parts %d", parts.length);
return null;
}
Map<String, String> parms = new HashMap<>();
if (parts.length == 2) {
String[] args = parts[1].split("&");
for (String arg : args) {
String[] namevalue = arg.split("=");
if (namevalue.length == 0) {
continue;
}
parms.put(Uri.decode(namevalue[0]).toLowerCase(),
namevalue.length > 1 ? Uri.decode(namevalue[1]) : "");
}
}
String addressName = parms.get(crypto.getUriLabel());
String description = parms.get(crypto.getUriMessage());
String address = parts[0]; // no need to decode as there can be no special characters
if (address.isEmpty()) {
Timber.d("no address");
return null;
}
if (!crypto.validate(address)) {
Timber.d("%s address (%s) invalid", crypto, address);
return null;
}
String amount = parms.get(crypto.getUriAmount());
if ((amount != null) && (!amount.isEmpty())) {
try {
Double.parseDouble(amount);
} catch (NumberFormatException ex) {
Timber.d(ex.getLocalizedMessage());
return null; // we have an amount but its not a number!
}
}
return new BarcodeData(crypto, address, addressName, description, amount, Security.NORMAL);
}
static public BarcodeData fromString(String qrCode) {
BarcodeData bcData = parseUri(qrCode);
if (bcData == null) {
// maybe it's naked?
bcData = parseNaked(qrCode);
}
if (bcData == null) {
// check for OpenAlias
bcData = parseOpenAlias(qrCode, false);
}
return bcData;
}
static public BarcodeData parseOpenAlias(String oaString, boolean dnssec) {
Timber.d("parseOpenAlias=%s", oaString);
if (oaString == null) return null;
Map<String, String> oaAttrs = OpenAliasHelper.parse(oaString);
if (oaAttrs == null) return null;
String oaAsset = oaAttrs.get(OpenAliasHelper.OA1_ASSET);
if (oaAsset == null) return null;
String address = oaAttrs.get(OpenAliasHelper.OA1_ADDRESS);
if (address == null) return null;
Crypto crypto = Crypto.withSymbol(oaAsset);
if (crypto == null) {
Timber.i("Unsupported OpenAlias asset %s", oaAsset);
return null;
}
if (!crypto.validate(address)) {
Timber.d("%s address invalid", crypto);
return null;
}
String description = oaAttrs.get(OpenAliasHelper.OA1_DESCRIPTION);
if (description == null) {
description = oaAttrs.get(OpenAliasHelper.OA1_NAME);
}
String amount = oaAttrs.get(OpenAliasHelper.OA1_AMOUNT);
String addressName = oaAttrs.get(OpenAliasHelper.OA1_NAME);
if (amount != null) {
try {
Double.parseDouble(amount);
} catch (NumberFormatException ex) {
Timber.d(ex.getLocalizedMessage());
return null; // we have an amount but its not a number!
}
}
Security sec = dnssec ? BarcodeData.Security.OA_DNSSEC : BarcodeData.Security.OA_NO_DNSSEC;
return new BarcodeData(crypto, address, addressName, description, amount, sec);
}
public boolean isAmbiguous() {
return ambiguousAssets != null;
}
}

View File

@ -1,89 +0,0 @@
package com.m2049r.xmrwallet.data;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.util.validator.BitcoinAddressType;
import com.m2049r.xmrwallet.util.validator.BitcoinAddressValidator;
import com.m2049r.xmrwallet.util.validator.EthAddressValidator;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
public enum Crypto {
XMR("XMR", true, "monero:tx_amount:recipient_name:tx_description", 0, R.drawable.ic_monero, R.drawable.ic_monero_bw, Wallet::isAddressValid),
BTC("BTC", true, "bitcoin:amount:label:message", 0, R.drawable.ic_xmrto_btc, R.drawable.ic_xmrto_btc_off, address -> {
return BitcoinAddressValidator.validate(address, BitcoinAddressType.BTC);
}),
DASH("DASH", true, "dash:amount:label:message", 0, R.drawable.ic_xmrto_dash, R.drawable.ic_xmrto_dash_off, address -> {
return BitcoinAddressValidator.validate(address, BitcoinAddressType.DASH);
}),
DOGE("DOGE", true, "dogecoin:amount:label:message", 0, R.drawable.ic_xmrto_doge, R.drawable.ic_xmrto_doge_off, address -> {
return BitcoinAddressValidator.validate(address, BitcoinAddressType.DOGE);
}),
ETH("ETH", false, "ethereum:amount:label:message", 0, R.drawable.ic_xmrto_eth, R.drawable.ic_xmrto_eth_off, EthAddressValidator::validate),
LTC("LTC", true, "litecoin:amount:label:message", 0, R.drawable.ic_xmrto_ltc, R.drawable.ic_xmrto_ltc_off, address -> {
return BitcoinAddressValidator.validate(address, BitcoinAddressType.LTC);
});
@Getter
@NonNull
private final String symbol;
@Getter
private final boolean casefull;
@NonNull
private final String uriSpec;
@Getter
private final int buttonId;
@Getter
private final int iconEnabledId;
@Getter
private final int iconDisabledId;
@NonNull
private final Validator validator;
@Nullable
public static Crypto withScheme(@NonNull String scheme) {
for (Crypto crypto : values()) {
if (crypto.getUriScheme().equals(scheme)) return crypto;
}
return null;
}
@Nullable
public static Crypto withSymbol(@NonNull String symbol) {
final String upperSymbol = symbol.toUpperCase();
for (Crypto crypto : values()) {
if (crypto.symbol.equals(upperSymbol)) return crypto;
}
return null;
}
interface Validator {
boolean validate(String address);
}
// TODO maybe cache these segments
String getUriScheme() {
return uriSpec.split(":")[0];
}
String getUriAmount() {
return uriSpec.split(":")[1];
}
String getUriLabel() {
return uriSpec.split(":")[2];
}
String getUriMessage() {
return uriSpec.split(":")[3];
}
boolean validate(String address) {
return validator.validate(address);
}
}

View File

@ -35,69 +35,21 @@ public class Node {
static public final String MAINNET = "mainnet";
static public final String STAGENET = "stagenet";
static public final String TESTNET = "testnet";
static class Address {
final private InetAddress inet;
final private String onion;
public boolean isOnion() {
return onion != null;
}
public String getHostName() {
if (inet != null) {
return inet.getHostName();
} else {
return onion;
}
}
public String getHostAddress() {
if (inet != null) {
return inet.getHostAddress();
} else {
return onion;
}
}
private Address(InetAddress address, String onion) {
this.inet = address;
this.onion = onion;
}
static Address of(InetAddress address) {
return new Address(address, null);
}
static Address of(String host) throws UnknownHostException {
if (OnionHelper.isOnionHost(host)) {
return new Address(null, host);
} else {
return new Address(InetAddress.getByName(host), null);
}
}
@Override
public int hashCode() {
return getHostAddress().hashCode();
}
@Override
public boolean equals(Object other) {
return (other instanceof Address) && (getHostAddress().equals(((Address) other).getHostAddress()));
}
}
@Getter
private String name = null;
static private int DEFAULT_LEVIN_PORT = 0;
static private int DEFAULT_RPC_PORT = 0;
@Getter
final private NetworkType networkType;
Address hostAddress;
@Getter
private String host;
@Setter
private final boolean selected = false;
Address hostAddress;
@Getter
@Setter
int rpcPort = 0;
@Getter
private String name = null;
@Getter
private String host;
private int levinPort = 0;
@Getter
@Setter
@ -108,37 +60,6 @@ public class Node {
@Getter
@Setter
private boolean favourite = false;
@Getter
@Setter
private final boolean selected = false;
@Override
public int hashCode() {
return hostAddress.hashCode();
}
// Nodes are equal if they are the same host address:port & are on the same network
@Override
public boolean equals(Object other) {
if (!(other instanceof Node)) return false;
final Node anotherNode = (Node) other;
return (hostAddress.equals(anotherNode.hostAddress)
&& (rpcPort == anotherNode.rpcPort)
&& (networkType == anotherNode.networkType));
}
public boolean isOnion() {
return hostAddress.isOnion();
}
static public Node fromString(String nodeString) {
try {
return new Node(nodeString);
} catch (IllegalArgumentException ex) {
Timber.w(ex);
return null;
}
}
Node(String nodeString) {
if ((nodeString == null) || nodeString.isEmpty())
@ -223,6 +144,92 @@ public class Node {
this.levinPort = getDefaultLevinPort();
}
public Node() {
this.networkType = WalletManager.getInstance().getNetworkType();
}
// constructor used for created nodes from retrieved peer lists
public Node(InetSocketAddress socketAddress) {
this();
this.hostAddress = Address.of(socketAddress.getAddress());
this.host = socketAddress.getHostString();
this.rpcPort = 0; // unknown
this.levinPort = socketAddress.getPort();
this.username = "";
this.password = "";
}
public Node(Node anotherNode) {
networkType = anotherNode.networkType;
overwriteWith(anotherNode);
}
static public Node fromString(String nodeString) {
try {
return new Node(nodeString);
} catch (IllegalArgumentException ex) {
Timber.w(ex);
return null;
}
}
// every node knows its network, but they are all the same
static public int getDefaultLevinPort() {
if (DEFAULT_LEVIN_PORT > 0) return DEFAULT_LEVIN_PORT;
switch (WalletManager.getInstance().getNetworkType()) {
case NetworkType_Mainnet:
DEFAULT_LEVIN_PORT = 18080;
break;
case NetworkType_Testnet:
DEFAULT_LEVIN_PORT = 28080;
break;
case NetworkType_Stagenet:
DEFAULT_LEVIN_PORT = 38080;
break;
default:
throw new IllegalStateException("unsupported net " + WalletManager.getInstance().getNetworkType());
}
return DEFAULT_LEVIN_PORT;
}
// every node knows its network, but they are all the same
static public int getDefaultRpcPort() {
if (DEFAULT_RPC_PORT > 0) return DEFAULT_RPC_PORT;
switch (WalletManager.getInstance().getNetworkType()) {
case NetworkType_Mainnet:
DEFAULT_RPC_PORT = 18081;
break;
case NetworkType_Testnet:
DEFAULT_RPC_PORT = 28081;
break;
case NetworkType_Stagenet:
DEFAULT_RPC_PORT = 38081;
break;
default:
throw new IllegalStateException("unsupported net " + WalletManager.getInstance().getNetworkType());
}
return DEFAULT_RPC_PORT;
}
@Override
public int hashCode() {
return hostAddress.hashCode();
}
// Nodes are equal if they are the same host address:port & are on the same network
@Override
public boolean equals(Object other) {
if (!(other instanceof Node)) return false;
final Node anotherNode = (Node) other;
return (hostAddress.equals(anotherNode.hostAddress)
&& (rpcPort == anotherNode.rpcPort)
&& (networkType == anotherNode.networkType));
}
public boolean isOnion() {
return hostAddress.isOnion();
}
public String toNodeString() {
return toString();
}
@ -255,21 +262,6 @@ public class Node {
return sb.toString();
}
public Node() {
this.networkType = WalletManager.getInstance().getNetworkType();
}
// constructor used for created nodes from retrieved peer lists
public Node(InetSocketAddress socketAddress) {
this();
this.hostAddress = Address.of(socketAddress.getAddress());
this.host = socketAddress.getHostString();
this.rpcPort = 0; // unknown
this.levinPort = socketAddress.getPort();
this.username = "";
this.password = "";
}
public String getAddress() {
return getHostAddress() + ":" + rpcPort;
}
@ -309,11 +301,6 @@ public class Node {
favourite = !favourite;
}
public Node(Node anotherNode) {
networkType = anotherNode.networkType;
overwriteWith(anotherNode);
}
public void overwriteWith(Node anotherNode) {
if (networkType != anotherNode.networkType)
throw new IllegalStateException("network types do not match");
@ -327,45 +314,55 @@ public class Node {
favourite = anotherNode.favourite;
}
static private int DEFAULT_LEVIN_PORT = 0;
static class Address {
final private InetAddress inet;
final private String onion;
// every node knows its network, but they are all the same
static public int getDefaultLevinPort() {
if (DEFAULT_LEVIN_PORT > 0) return DEFAULT_LEVIN_PORT;
switch (WalletManager.getInstance().getNetworkType()) {
case NetworkType_Mainnet:
DEFAULT_LEVIN_PORT = 18080;
break;
case NetworkType_Testnet:
DEFAULT_LEVIN_PORT = 28080;
break;
case NetworkType_Stagenet:
DEFAULT_LEVIN_PORT = 38080;
break;
default:
throw new IllegalStateException("unsupported net " + WalletManager.getInstance().getNetworkType());
}
return DEFAULT_LEVIN_PORT;
private Address(InetAddress address, String onion) {
this.inet = address;
this.onion = onion;
}
static private int DEFAULT_RPC_PORT = 0;
static Address of(InetAddress address) {
return new Address(address, null);
}
// every node knows its network, but they are all the same
static public int getDefaultRpcPort() {
if (DEFAULT_RPC_PORT > 0) return DEFAULT_RPC_PORT;
switch (WalletManager.getInstance().getNetworkType()) {
case NetworkType_Mainnet:
DEFAULT_RPC_PORT = 18081;
break;
case NetworkType_Testnet:
DEFAULT_RPC_PORT = 28081;
break;
case NetworkType_Stagenet:
DEFAULT_RPC_PORT = 38081;
break;
default:
throw new IllegalStateException("unsupported net " + WalletManager.getInstance().getNetworkType());
}
return DEFAULT_RPC_PORT;
static Address of(String host) throws UnknownHostException {
if (OnionHelper.isOnionHost(host)) {
return new Address(null, host);
} else {
return new Address(InetAddress.getByName(host), null);
}
}
public boolean isOnion() {
return onion != null;
}
public String getHostName() {
if (inet != null) {
return inet.getHostName();
} else {
return onion;
}
}
public String getHostAddress() {
if (inet != null) {
return inet.getHostAddress();
} else {
return onion;
}
}
@Override
public int hashCode() {
return getHostAddress().hashCode();
}
@Override
public boolean equals(Object other) {
return (other instanceof Address) && (getHostAddress().equals(((Address) other).getHostAddress()));
}
}
}

View File

@ -1,301 +0,0 @@
/*
* Copyright (c) 2018 m2049r
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.xmrwallet.data;
import android.content.Context;
import android.text.Html;
import android.text.Spanned;
import android.widget.TextView;
import com.m2049r.levin.scanner.LevinPeer;
import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.util.NetCipherHelper;
import com.m2049r.xmrwallet.util.NetCipherHelper.Request;
import com.m2049r.xmrwallet.util.NodePinger;
import com.m2049r.xmrwallet.util.ThemeHelper;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.Calendar;
import java.util.Comparator;
import lombok.Getter;
import lombok.Setter;
import okhttp3.HttpUrl;
import okhttp3.Response;
import okhttp3.ResponseBody;
import timber.log.Timber;
public class NodeInfo extends Node {
final static public int MIN_MAJOR_VERSION = 14;
final static public String RPC_VERSION = "2.0";
@Getter
private long height = 0;
@Getter
private long timestamp = 0;
@Getter
private int majorVersion = 0;
@Getter
private double responseTime = Double.MAX_VALUE;
@Getter
private int responseCode = 0;
@Getter
private boolean tested = false;
@Getter
@Setter
private final boolean selecting = false;
public void clear() {
height = 0;
majorVersion = 0;
responseTime = Double.MAX_VALUE;
responseCode = 0;
timestamp = 0;
tested = false;
}
static public NodeInfo fromString(String nodeString) {
try {
return new NodeInfo(nodeString);
} catch (IllegalArgumentException ex) {
return null;
}
}
public NodeInfo(NodeInfo anotherNode) {
super(anotherNode);
overwriteWith(anotherNode);
}
private SocketAddress levinSocketAddress = null;
synchronized public SocketAddress getLevinSocketAddress() {
if (levinSocketAddress == null) {
// use default peer port if not set - very few peers use nonstandard port
levinSocketAddress = new InetSocketAddress(hostAddress.getHostAddress(), getDefaultLevinPort());
}
return levinSocketAddress;
}
@Override
public int hashCode() {
return super.hashCode();
}
@Override
public boolean equals(Object other) {
return super.equals(other);
}
public NodeInfo(String nodeString) {
super(nodeString);
}
public NodeInfo(LevinPeer levinPeer) {
super(levinPeer.getSocketAddress());
}
public NodeInfo(InetSocketAddress address) {
super(address);
}
public NodeInfo() {
super();
}
public boolean isSuccessful() {
return (responseCode >= 200) && (responseCode < 300);
}
public boolean isUnauthorized() {
return responseCode == HttpURLConnection.HTTP_UNAUTHORIZED;
}
public boolean isValid() {
return isSuccessful() && (majorVersion >= MIN_MAJOR_VERSION) && (responseTime < Double.MAX_VALUE);
}
static public Comparator<NodeInfo> BestNodeComparator = (o1, o2) -> {
if (o1.isValid()) {
if (o2.isValid()) { // both are valid
// higher node wins
int heightDiff = (int) (o2.height - o1.height);
if (heightDiff != 0)
return heightDiff;
// if they are equal, faster node wins
return (int) Math.signum(o1.responseTime - o2.responseTime);
} else {
return -1;
}
} else {
return 1;
}
};
public void overwriteWith(NodeInfo anotherNode) {
super.overwriteWith(anotherNode);
height = anotherNode.height;
timestamp = anotherNode.timestamp;
majorVersion = anotherNode.majorVersion;
responseTime = anotherNode.responseTime;
responseCode = anotherNode.responseCode;
}
public String toNodeString() {
return super.toString();
}
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(super.toString());
sb.append("?rc=").append(responseCode);
sb.append("?v=").append(majorVersion);
sb.append("&h=").append(height);
sb.append("&ts=").append(timestamp);
if (responseTime < Double.MAX_VALUE) {
sb.append("&t=").append(responseTime).append("ms");
}
return sb.toString();
}
private static final int HTTP_TIMEOUT = 1000; //ms
public static final double PING_GOOD = HTTP_TIMEOUT / 3.0; //ms
public static final double PING_MEDIUM = 2 * PING_GOOD; //ms
public static final double PING_BAD = HTTP_TIMEOUT;
public boolean testRpcService() {
return testRpcService(rpcPort);
}
public boolean testRpcService(NodePinger.Listener listener) {
boolean result = testRpcService(rpcPort);
if (listener != null)
listener.publish(this);
return result;
}
private Request rpcServiceRequest(int port) {
final HttpUrl url = new HttpUrl.Builder()
.scheme("http")
.host(getHost())
.port(port)
.addPathSegment("json_rpc")
.build();
final String json = "{\"jsonrpc\":\"2.0\",\"id\":\"0\",\"method\":\"getlastblockheader\"}";
return new Request(url, json, getUsername(), getPassword());
}
private boolean testRpcService(int port) {
Timber.d("Testing %s", toNodeString());
clear();
if (hostAddress.isOnion() && !NetCipherHelper.isTor()) {
tested = true; // sortof
responseCode = 418; // I'm a teapot - or I need an Onion - who knows
return false; // autofail
}
try {
long ta = System.nanoTime();
try (Response response = rpcServiceRequest(port).execute()) {
Timber.d("%s: %s", response.code(), response.request().url());
responseTime = (System.nanoTime() - ta) / 1000000.0;
responseCode = response.code();
if (response.isSuccessful()) {
ResponseBody respBody = response.body(); // closed through Response object
if ((respBody != null) && (respBody.contentLength() < 2000)) { // sanity check
final JSONObject json = new JSONObject(respBody.string());
String rpcVersion = json.getString("jsonrpc");
if (!RPC_VERSION.equals(rpcVersion))
return false;
final JSONObject result = json.getJSONObject("result");
if (!result.has("credits")) // introduced in monero v0.15.0
return false;
final JSONObject header = result.getJSONObject("block_header");
height = header.getLong("height");
timestamp = header.getLong("timestamp");
majorVersion = header.getInt("major_version");
return true; // success
}
}
}
} catch (IOException | JSONException ex) {
Timber.d("EX: %s", ex.getMessage()); //TODO: do something here (show error?)
} finally {
tested = true;
}
return false;
}
static final private int[] TEST_PORTS = {18089}; // check only opt-in port
public boolean findRpcService() {
// if already have an rpcPort, use that
if (rpcPort > 0) return testRpcService(rpcPort);
// otherwise try to find one
for (int port : TEST_PORTS) {
if (testRpcService(port)) { // found a service
this.rpcPort = port;
return true;
}
}
return false;
}
static public final int STALE_NODE_HOURS = 2;
public void showInfo(TextView view, String info, boolean isError) {
final Context ctx = view.getContext();
final Spanned text = Html.fromHtml(ctx.getString(R.string.status,
Integer.toHexString(ThemeHelper.getThemedColor(ctx, R.attr.positiveColor) & 0xFFFFFF),
Integer.toHexString(ThemeHelper.getThemedColor(ctx, android.R.attr.colorBackground) & 0xFFFFFF),
(hostAddress.isOnion() ? "&nbsp;.onion&nbsp;&nbsp;" : ""), " " + info));
view.setText(text);
if (isError)
view.setTextColor(ThemeHelper.getThemedColor(ctx, R.attr.colorError));
else
view.setTextColor(ThemeHelper.getThemedColor(ctx, android.R.attr.textColorSecondary));
}
public void showInfo(TextView view) {
if (!isTested()) {
showInfo(view, "", false);
return;
}
final Context ctx = view.getContext();
final long now = Calendar.getInstance().getTimeInMillis() / 1000;
final long secs = (now - timestamp);
final long mins = secs / 60;
final long hours = mins / 60;
final long days = hours / 24;
String info;
if (mins < 2) {
info = ctx.getString(R.string.node_updated_now, secs);
} else if (hours < 2) {
info = ctx.getString(R.string.node_updated_mins, mins);
} else if (days < 2) {
info = ctx.getString(R.string.node_updated_hours, hours);
} else {
info = ctx.getString(R.string.node_updated_days, days);
}
showInfo(view, info, hours >= STALE_NODE_HOURS);
}
}

View File

@ -28,6 +28,7 @@ import lombok.ToString;
@ToString
@EqualsAndHashCode
public class Subaddress implements Comparable<Subaddress> {
public static final Pattern DEFAULT_LABEL_FORMATTER = Pattern.compile("^[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{2}:[0-9]{2}:[0-9]{2}$");
@Getter
final private int accountIndex;
@Getter
@ -52,8 +53,6 @@ public class Subaddress implements Comparable<Subaddress> {
return address.substring(0, 8) + "" + address.substring(address.length() - 8);
}
public static final Pattern DEFAULT_LABEL_FORMATTER = Pattern.compile("^[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{2}:[0-9]{2}:[0-9]{2}$");
public String getDisplayLabel() {
if (label.isEmpty() || (DEFAULT_LABEL_FORMATTER.matcher(label).matches()))
return ("#" + addressIndex);

View File

@ -26,6 +26,22 @@ import com.m2049r.xmrwallet.util.Helper;
// https://stackoverflow.com/questions/2139134/how-to-send-an-object-from-one-android-activity-to-another-using-intents
public class TxData implements Parcelable {
// this is used to regenerate your object. All Parcelables must have a CREATOR that implements these two methods
public static final Parcelable.Creator<TxData> CREATOR = new Parcelable.Creator<TxData>() {
public TxData createFromParcel(Parcel in) {
return new TxData(in);
}
public TxData[] newArray(int size) {
return new TxData[size];
}
};
private String dstAddr;
private long amount;
private int mixin;
private PendingTransaction.Priority priority;
private UserNotes userNotes;
public TxData() {
}
@ -46,30 +62,26 @@ public class TxData implements Parcelable {
this.priority = priority;
}
protected TxData(Parcel in) {
dstAddr = in.readString();
amount = in.readLong();
mixin = in.readInt();
priority = PendingTransaction.Priority.fromInteger(in.readInt());
}
public String getDestinationAddress() {
return dstAddr;
}
public long getAmount() {
return amount;
}
public double getAmountAsDouble() {
return 1.0 * amount / Helper.ONE_XMR;
}
public int getMixin() {
return mixin;
}
public PendingTransaction.Priority getPriority() {
return priority;
}
public void setDestinationAddress(String dstAddr) {
this.dstAddr = dstAddr;
}
public long getAmount() {
return amount;
}
public void setAmount(long amount) {
this.amount = amount;
}
@ -78,10 +90,22 @@ public class TxData implements Parcelable {
this.amount = Wallet.getAmountFromDouble(amount);
}
public double getAmountAsDouble() {
return 1.0 * amount / Helper.ONE_XMR;
}
public int getMixin() {
return mixin;
}
public void setMixin(int mixin) {
this.mixin = mixin;
}
public PendingTransaction.Priority getPriority() {
return priority;
}
public void setPriority(PendingTransaction.Priority priority) {
this.priority = priority;
}
@ -94,13 +118,6 @@ public class TxData implements Parcelable {
this.userNotes = userNotes;
}
private String dstAddr;
private long amount;
private int mixin;
private PendingTransaction.Priority priority;
private UserNotes userNotes;
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeString(dstAddr);
@ -109,25 +126,6 @@ public class TxData implements Parcelable {
out.writeInt(priority.getValue());
}
// this is used to regenerate your object. All Parcelables must have a CREATOR that implements these two methods
public static final Parcelable.Creator<TxData> CREATOR = new Parcelable.Creator<TxData>() {
public TxData createFromParcel(Parcel in) {
return new TxData(in);
}
public TxData[] newArray(int size) {
return new TxData[size];
}
};
protected TxData(Parcel in) {
dstAddr = in.readString();
amount = in.readLong();
mixin = in.readInt();
priority = PendingTransaction.Priority.fromInteger(in.readInt());
}
@Override
public int describeContents() {
return 0;

View File

@ -1,101 +0,0 @@
/*
* Copyright (c) 2017 m2049r
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.xmrwallet.data;
import android.os.Parcel;
import androidx.annotation.NonNull;
import lombok.Getter;
import lombok.Setter;
public class TxDataBtc extends TxData {
@Getter
@Setter
private String btcSymbol; // the actual non-XMR thing we're sending
@Getter
@Setter
private String xmrtoOrderId; // shown in success screen
@Getter
@Setter
private String btcAddress;
@Getter
@Setter
private double btcAmount;
public TxDataBtc() {
super();
}
public TxDataBtc(TxDataBtc txDataBtc) {
super(txDataBtc);
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeString(btcSymbol);
out.writeString(xmrtoOrderId);
out.writeString(btcAddress);
out.writeDouble(btcAmount);
}
// this is used to regenerate your object. All Parcelables must have a CREATOR that implements these two methods
public static final Creator<TxDataBtc> CREATOR = new Creator<TxDataBtc>() {
public TxDataBtc createFromParcel(Parcel in) {
return new TxDataBtc(in);
}
public TxDataBtc[] newArray(int size) {
return new TxDataBtc[size];
}
};
protected TxDataBtc(Parcel in) {
super(in);
btcSymbol = in.readString();
xmrtoOrderId = in.readString();
btcAddress = in.readString();
btcAmount = in.readDouble();
}
@NonNull
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("xmrtoOrderId:");
sb.append(xmrtoOrderId);
sb.append(",btcSymbol:");
sb.append(btcSymbol);
sb.append(",btcAddress:");
sb.append(btcAddress);
sb.append(",btcAmount:");
sb.append(btcAmount);
return sb.toString();
}
public boolean validateAddress(@NonNull String address) {
if ((btcSymbol == null) || (btcAddress == null)) return false;
final Crypto crypto = Crypto.withSymbol(btcSymbol);
if (crypto == null) return false;
if (crypto.isCasefull()) { // compare as-is
return address.equals(btcAddress);
} else { // normalize & compare (e.g. ETH with and without checksum capitals
return address.equalsIgnoreCase(btcAddress);
}
}
}

View File

@ -4,8 +4,6 @@ import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.TextView;
@ -14,13 +12,8 @@ import androidx.annotation.Nullable;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.model.WalletManager;
import com.m2049r.xmrwallet.util.Constants;
import com.m2049r.xmrwallet.util.Helper;
import java.io.File;
public class InformationBottomSheetDialog extends BottomSheetDialogFragment {
public boolean showCopyButton = false;
public String information = "";

View File

@ -7,20 +7,13 @@ import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.model.WalletManager;
import com.m2049r.xmrwallet.service.BalanceService;
import com.m2049r.xmrwallet.service.TxService;
import com.m2049r.xmrwallet.util.Constants;
import com.m2049r.xmrwallet.util.Helper;
@ -66,6 +59,7 @@ public class PasswordBottomSheetDialog extends BottomSheetDialogFragment {
public interface PasswordListener {
void onPasswordSuccess(String password);
void onPasswordFail();
}
}

View File

@ -2,20 +2,8 @@ package com.m2049r.xmrwallet.fragment.dialog;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.AsyncTask;
import android.os.Bundle;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import com.google.zxing.client.android.Intents;
import com.journeyapps.barcodescanner.ScanContract;
import com.journeyapps.barcodescanner.ScanOptions;
import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.model.PendingTransaction;
import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.service.BalanceService;
import com.m2049r.xmrwallet.service.TxService;
import com.m2049r.xmrwallet.util.Helper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -32,17 +20,22 @@ import androidx.annotation.Nullable;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import com.google.zxing.client.android.Intents;
import com.journeyapps.barcodescanner.ScanContract;
import com.journeyapps.barcodescanner.ScanOptions;
import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.model.PendingTransaction;
import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.service.BalanceService;
import com.m2049r.xmrwallet.service.TxService;
import com.m2049r.xmrwallet.util.Helper;
import java.util.List;
public class SendBottomSheetDialog extends BottomSheetDialogFragment {
private final ActivityResultLauncher<ScanOptions> barcodeLauncher = registerForActivityResult(new ScanContract(),
result -> {
if(result.getContents() != null) {
pasteAddress(result.getContents());
}
});
private final ActivityResultLauncher<String> cameraPermissionsLauncher = registerForActivityResult(new ActivityResultContracts.RequestPermission(),
private final MutableLiveData<Boolean> _sendingMax = new MutableLiveData<>(false);
public LiveData<Boolean> sendingMax = _sendingMax; private final ActivityResultLauncher<String> cameraPermissionsLauncher = registerForActivityResult(new ActivityResultContracts.RequestPermission(),
granted -> {
if (granted) {
onScan();
@ -50,13 +43,15 @@ public class SendBottomSheetDialog extends BottomSheetDialogFragment {
Toast.makeText(getActivity(), getString(R.string.no_camera_permission), Toast.LENGTH_SHORT).show();
}
});
private MutableLiveData<Boolean> _sendingMax = new MutableLiveData<>(false);
public LiveData<Boolean> sendingMax = _sendingMax;
private MutableLiveData<PendingTransaction> _pendingTransaction = new MutableLiveData<>(null);
private final MutableLiveData<PendingTransaction> _pendingTransaction = new MutableLiveData<>(null);
public LiveData<PendingTransaction> pendingTransaction = _pendingTransaction;
private EditText addressEditText;
private final ActivityResultLauncher<ScanOptions> barcodeLauncher = registerForActivityResult(new ScanContract(),
result -> {
if (result.getContents() != null) {
pasteAddress(result.getContents());
}
});
private EditText amountEditText;
private TextView sendAllTextView;
private TextView feeTextView;
@ -247,4 +242,6 @@ public class SendBottomSheetDialog extends BottomSheetDialogFragment {
Toast.makeText(getActivity(), getString(R.string.send_address_invalid), Toast.LENGTH_SHORT).show();
}
}
}

View File

@ -5,25 +5,20 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.fragment.NavHostFragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.bottomsheet.BottomSheetDialog;
import com.m2049r.xmrwallet.MainActivity;
import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.adapter.TransactionInfoAdapter;
@ -32,20 +27,16 @@ import com.m2049r.xmrwallet.fragment.dialog.SendBottomSheetDialog;
import com.m2049r.xmrwallet.model.TransactionInfo;
import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.model.WalletManager;
import com.m2049r.xmrwallet.service.AddressService;
import com.m2049r.xmrwallet.service.BalanceService;
import com.m2049r.xmrwallet.service.BlockchainService;
import com.m2049r.xmrwallet.service.HistoryService;
import com.m2049r.xmrwallet.service.PrefService;
import com.m2049r.xmrwallet.service.TxService;
import com.m2049r.xmrwallet.util.Constants;
import java.util.Collections;
public class HomeFragment extends Fragment implements TransactionInfoAdapter.TxInfoAdapterListener {
private HomeViewModel mViewModel;
long startHeight = 0;
private HomeViewModel mViewModel;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,

View File

@ -1,21 +1,7 @@
package com.m2049r.xmrwallet.fragment.home;
import android.graphics.Bitmap;
import androidx.lifecycle.ViewModel;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.WriterException;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import java.util.HashMap;
import java.util.Map;
import timber.log.Timber;
public class HomeViewModel extends ViewModel {
}

View File

@ -17,7 +17,6 @@ import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.fragment.NavHostFragment;
@ -85,9 +84,12 @@ public class OnboardingFragment extends Fragment {
});
walletSeedEditText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void afterTextChanged(Editable editable) {

View File

@ -5,7 +5,7 @@ import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
public class OnboardingViewModel extends ViewModel {
private MutableLiveData<Boolean> _showMoreOptions = new MutableLiveData<>(false);
private final MutableLiveData<Boolean> _showMoreOptions = new MutableLiveData<>(false);
public LiveData<Boolean> showMoreOptions = _showMoreOptions;
public void onMoreOptionsClicked() {

View File

@ -5,8 +5,6 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.CompoundButton;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
@ -18,9 +16,7 @@ import androidx.lifecycle.ViewModelProvider;
import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.fragment.dialog.InformationBottomSheetDialog;
import com.m2049r.xmrwallet.fragment.dialog.PasswordBottomSheetDialog;
import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.model.WalletManager;
import com.m2049r.xmrwallet.service.BlockchainService;
import com.m2049r.xmrwallet.service.PrefService;
import com.m2049r.xmrwallet.util.Constants;
import com.m2049r.xmrwallet.util.DayNightMode;

View File

@ -1,155 +0,0 @@
/*
* Copyright (c) 2018 m2049r
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.xmrwallet.ledger;
public enum Instruction {
INS_NONE(0x00),
INS_RESET(0x02),
INS_GET_KEY(0x20),
INS_DISPLAY_ADDRESS(0x21),
INS_PUT_KEY(0x22),
INS_GET_CHACHA8_PREKEY(0x24),
INS_VERIFY_KEY(0x26),
INS_MANAGE_SEEDWORDS(0x28),
INS_SECRET_KEY_TO_PUBLIC_KEY(0x30),
INS_GEN_KEY_DERIVATION(0x32),
INS_DERIVATION_TO_SCALAR(0x34),
INS_DERIVE_PUBLIC_KEY(0x36),
INS_DERIVE_SECRET_KEY(0x38),
INS_GEN_KEY_IMAGE(0x3A),
INS_SECRET_KEY_ADD(0x3C),
INS_SECRET_KEY_SUB(0x3E),
INS_GENERATE_KEYPAIR(0x40),
INS_SECRET_SCAL_MUL_KEY(0x42),
INS_SECRET_SCAL_MUL_BASE(0x44),
INS_DERIVE_SUBADDRESS_PUBLIC_KEY(0x46),
INS_GET_SUBADDRESS(0x48),
INS_GET_SUBADDRESS_SPEND_PUBLIC_KEY(0x4A),
INS_GET_SUBADDRESS_SECRET_KEY(0x4C),
INS_OPEN_TX(0x70),
INS_SET_SIGNATURE_MODE(0x72),
INS_GET_ADDITIONAL_KEY(0x74),
INS_STEALTH(0x76),
INS_GEN_COMMITMENT_MASK(0x77),
INS_BLIND(0x78),
INS_UNBLIND(0x7A),
INS_GEN_TXOUT_KEYS(0x7B),
INS_VALIDATE(0x7C),
INS_PREFIX_HASH(0x7D),
INS_MLSAG(0x7E),
INS_CLOSE_TX(0x80),
INS_GET_TX_PROOF(0xA0),
INS_GET_RESPONSE(0xC0),
INS_UNDEFINED(0xFF);
public static Instruction fromByte(byte n) {
switch (n & 0xFF) {
case 0x00:
return INS_NONE;
case 0x02:
return INS_RESET;
case 0x20:
return INS_GET_KEY;
case 0x22:
return INS_PUT_KEY;
case 0x24:
return INS_GET_CHACHA8_PREKEY;
case 0x26:
return INS_VERIFY_KEY;
case 0x30:
return INS_SECRET_KEY_TO_PUBLIC_KEY;
case 0x32:
return INS_GEN_KEY_DERIVATION;
case 0x34:
return INS_DERIVATION_TO_SCALAR;
case 0x36:
return INS_DERIVE_PUBLIC_KEY;
case 0x38:
return INS_DERIVE_SECRET_KEY;
case 0x3A:
return INS_GEN_KEY_IMAGE;
case 0x3C:
return INS_SECRET_KEY_ADD;
case 0x3E:
return INS_SECRET_KEY_SUB;
case 0x40:
return INS_GENERATE_KEYPAIR;
case 0x42:
return INS_SECRET_SCAL_MUL_KEY;
case 0x44:
return INS_SECRET_SCAL_MUL_BASE;
case 0x46:
return INS_DERIVE_SUBADDRESS_PUBLIC_KEY;
case 0x48:
return INS_GET_SUBADDRESS;
case 0x4A:
return INS_GET_SUBADDRESS_SPEND_PUBLIC_KEY;
case 0x4C:
return INS_GET_SUBADDRESS_SECRET_KEY;
case 0x70:
return INS_OPEN_TX;
case 0x72:
return INS_SET_SIGNATURE_MODE;
case 0x74:
return INS_GET_ADDITIONAL_KEY;
case 0x76:
return INS_STEALTH;
case 0x78:
return INS_BLIND;
case 0x7A:
return INS_UNBLIND;
case 0x7C:
return INS_VALIDATE;
case 0x7E:
return INS_MLSAG;
case 0x80:
return INS_CLOSE_TX;
case 0xc0:
return INS_GET_RESPONSE;
default:
return INS_UNDEFINED;
}
}
public int getValue() {
return value;
}
public byte getByteValue() {
return (byte) (value & 0xFF);
}
private final int value;
Instruction(int value) {
this.value = value;
}
}

View File

@ -1,240 +0,0 @@
/*
*******************************************************************************
* BTChip Bitcoin Hardware Wallet Java API
* (c) 2014 BTChip - 1BTChip7VfTnrPra5jqci7ejnMguuHogTn
* Copyright (c) 2018 m2049r
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
********************************************************************************
*/
package com.m2049r.xmrwallet.ledger;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbManager;
import com.btchip.BTChipException;
import com.btchip.comm.BTChipTransport;
import com.btchip.comm.android.BTChipTransportAndroidHID;
import com.m2049r.xmrwallet.BuildConfig;
import com.m2049r.xmrwallet.model.WalletManager;
import com.m2049r.xmrwallet.util.Helper;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import timber.log.Timber;
public class Ledger {
static final public boolean ENABLED = true;
// 5:20 is same as wallet2.cpp::restore()
static public final int LOOKAHEAD_ACCOUNTS = 5;
static public final int LOOKAHEAD_SUBADDRESSES = 20;
static public final String SUBADDRESS_LOOKAHEAD = LOOKAHEAD_ACCOUNTS + ":" + LOOKAHEAD_SUBADDRESSES;
private static final byte PROTOCOL_VERSION = 0x03;
public static final int SW_OK = 0x9000;
public static final int SW_INS_NOT_SUPPORTED = 0x6D00;
public static final int[] OK = {SW_OK};
public static final int MINIMUM_LEDGER_VERSION = (1 << 16) + (8 << 8) + (0); // 1.6.0
public static UsbDevice findDevice(UsbManager usbManager) {
if (!ENABLED) return null;
return BTChipTransportAndroidHID.getDevice(usbManager);
}
static private Ledger Instance = null;
static public String connect(UsbManager usbManager, UsbDevice usbDevice) throws IOException {
if (Instance != null) {
disconnect();
}
Instance = new Ledger(usbManager, usbDevice);
return Name();
}
static public void disconnect() {
// this is not synchronized so as to close immediately
if (Instance != null) {
Instance.close();
Instance = null;
}
}
static public boolean isConnected() {
//TODO synchronize with connect/disconnect?
return Instance != null;
}
static public String Name() {
if (Instance != null) {
return Instance.name;
} else {
return null;
}
}
static public byte[] Exchange(byte[] apdu) {
if (Instance != null) {
Timber.d("INS: %s", Instruction.fromByte(apdu[1]));
return Instance.exchangeRaw(apdu);
} else {
return null;
}
}
static public boolean check() {
if (Instance == null) return false;
byte[] moneroVersion = WalletManager.moneroVersion().getBytes(StandardCharsets.US_ASCII);
try {
byte[] resp = Instance.exchangeApduNoOpt(Instruction.INS_RESET, moneroVersion, OK);
int deviceVersion = (resp[0] << 16) + (resp[1] << 8) + (resp[2]);
if (deviceVersion < MINIMUM_LEDGER_VERSION)
return false;
} catch (BTChipException ex) { // comm error - probably wrong app started on device
return false;
}
return true;
}
final private BTChipTransport transport;
final private String name;
private int lastSW = 0;
private Ledger(UsbManager usbManager, UsbDevice usbDevice) throws IOException {
final BTChipTransport transport = BTChipTransportAndroidHID.open(usbManager, usbDevice);
Timber.d("transport opened = %s", transport.toString());
transport.setDebug(BuildConfig.DEBUG);
this.transport = transport;
this.name = usbDevice.getManufacturerName() + " " + usbDevice.getProductName();
initKey();
}
synchronized private void close() {
initKey(); // don't leak key after we disconnect
transport.close();
Timber.d("transport closed");
lastSW = 0;
}
synchronized private byte[] exchangeRaw(byte[] apdu) {
if (transport == null)
throw new IllegalStateException("No transport (probably closed previously)");
Timber.d("exchangeRaw %02x", apdu[1]);
Instruction ins = Instruction.fromByte(apdu[1]);
if (listener != null) listener.onInstructionSend(ins, apdu);
sniffOut(ins, apdu);
byte[] data = transport.exchange(apdu);
if (listener != null) listener.onInstructionReceive(ins, data);
sniffIn(data);
return data;
}
private byte[] exchange(byte[] apdu) throws BTChipException {
byte[] response = exchangeRaw(apdu);
if (response.length < 2) {
throw new BTChipException("Truncated response");
}
lastSW = ((response[response.length - 2] & 0xff) << 8) |
response[response.length - 1] & 0xff;
byte[] result = new byte[response.length - 2];
System.arraycopy(response, 0, result, 0, response.length - 2);
return result;
}
private byte[] exchangeCheck(byte[] apdu, int[] acceptedSW) throws BTChipException {
byte[] response = exchange(apdu);
if (acceptedSW == null) {
return response;
}
for (int SW : acceptedSW) {
if (lastSW == SW) {
return response;
}
}
throw new BTChipException("Invalid status", lastSW);
}
private byte[] exchangeApduNoOpt(Instruction instruction, byte[] data, int[] acceptedSW)
throws BTChipException {
byte[] apdu = new byte[data.length + 6];
apdu[0] = PROTOCOL_VERSION;
apdu[1] = instruction.getByteValue();
apdu[2] = 0; // p1
apdu[3] = 0; // p2
apdu[4] = (byte) (data.length + 1); // +1 because the opt byte is part of the data
apdu[5] = 0; // opt
System.arraycopy(data, 0, apdu, 6, data.length);
return exchangeCheck(apdu, acceptedSW);
}
public interface Listener {
void onInstructionSend(Instruction ins, byte[] apdu);
void onInstructionReceive(Instruction ins, byte[] data);
}
Listener listener;
static public void setListener(Listener listener) {
if (Instance != null) {
Instance.listener = listener;
}
}
static public void unsetListener(Listener listener) {
if ((Instance != null) && (Instance.listener == listener))
Instance.listener = null;
}
// very stupid hack to extract the view key
// without messing around with monero core code
// NB: as all the ledger comm can be sniffed off the USB cable - there is no security issue here
private boolean snoopKey = false;
private byte[] key;
private void initKey() {
key = Helper.hexToBytes("0000000000000000000000000000000000000000000000000000000000000000");
}
static public String Key() {
if (Instance != null) {
return Helper.bytesToHex(Instance.key).toLowerCase();
} else {
return null;
}
}
private void sniffOut(Instruction ins, byte[] apdu) {
if (ins == Instruction.INS_GET_KEY) {
snoopKey = (apdu[2] == 2);
}
}
private void sniffIn(byte[] data) {
// stupid hack to extract the view key
// without messing around with monero core code
if (snoopKey) {
if (data.length == 34) { // 32 key + result code 9000
long sw = ((data[data.length - 2] & 0xff) << 8) |
(data[data.length - 1] & 0xff);
Timber.e("WS %d", sw);
if (sw == SW_OK) {
System.arraycopy(data, 0, key, 0, 32);
}
}
snoopKey = false;
}
}
}

View File

@ -21,6 +21,12 @@ public enum NetworkType {
NetworkType_Testnet(1),
NetworkType_Stagenet(2);
private final int value;
NetworkType(int value) {
this.value = value;
}
public static NetworkType fromInteger(int n) {
switch (n) {
case 0:
@ -36,10 +42,4 @@ public enum NetworkType {
public int getValue() {
return value;
}
private final int value;
NetworkType(int value) {
this.value = value;
}
}

View File

@ -27,46 +27,6 @@ public class PendingTransaction {
this.handle = handle;
}
public enum Status {
Status_Ok,
Status_Error,
Status_Critical
}
public enum Priority {
Priority_Default(0),
Priority_Low(1),
Priority_Medium(2),
Priority_High(3),
Priority_Last(4);
public static Priority fromInteger(int n) {
switch (n) {
case 0:
return Priority_Default;
case 1:
return Priority_Low;
case 2:
return Priority_Medium;
case 3:
return Priority_High;
}
return null;
}
public int getValue() {
return value;
}
private final int value;
Priority(int value) {
this.value = value;
}
}
public Status getStatus() {
return Status.values()[getStatusJ()];
}
@ -95,4 +55,44 @@ public class PendingTransaction {
public native long getTxCount();
public enum Status {
Status_Ok,
Status_Error,
Status_Critical
}
public enum Priority {
Priority_Default(0),
Priority_Low(1),
Priority_Medium(2),
Priority_High(3),
Priority_Last(4);
private final int value;
Priority(int value) {
this.value = value;
}
public static Priority fromInteger(int n) {
switch (n) {
case 0:
return Priority_Default;
case 1:
return Priority_Low;
case 2:
return Priority_Medium;
case 3:
return Priority_High;
}
return null;
}
public int getValue() {
return value;
}
}
}

View File

@ -30,6 +30,12 @@ public class TransactionHistory {
private final long handle;
int accountIndex;
private List<TransactionInfo> transactions = new ArrayList<>();
public TransactionHistory(long handle, int accountIndex) {
this.handle = handle;
this.accountIndex = accountIndex;
}
public void setAccountFor(Wallet wallet) {
if (accountIndex != wallet.getAccountIndex()) {
@ -38,29 +44,22 @@ public class TransactionHistory {
}
}
public TransactionHistory(long handle, int accountIndex) {
this.handle = handle;
this.accountIndex = accountIndex;
}
private void loadNotes(Wallet wallet) {
for (TransactionInfo info : transactions) {
info.notes = wallet.getUserNote(info.hash);
}
}
public native int getCount(); // over all accounts/subaddresses
//private native long getTransactionByIndexJ(int i);
//private native long getTransactionByIdJ(String id);
public native int getCount(); // over all accounts/subaddresses
public List<TransactionInfo> getAll() {
return transactions;
}
private List<TransactionInfo> transactions = new ArrayList<>();
void refreshWithNotes(Wallet wallet) {
refresh();
loadNotes(wallet);

View File

@ -30,26 +30,15 @@ import lombok.RequiredArgsConstructor;
// this is a POJO for the TransactionInfoAdapter
public class TransactionInfo implements Parcelable, Comparable<TransactionInfo> {
public static final int CONFIRMATION = 10; // blocks
@RequiredArgsConstructor
public enum Direction {
Direction_In(0),
Direction_Out(1);
public static Direction fromInteger(int n) {
switch (n) {
case 0:
return Direction_In;
case 1:
return Direction_Out;
}
return null;
public static final Parcelable.Creator<TransactionInfo> CREATOR = new Parcelable.Creator<TransactionInfo>() {
public TransactionInfo createFromParcel(Parcel in) {
return new TransactionInfo(in);
}
@Getter
private final int value;
public TransactionInfo[] newArray(int size) {
return new TransactionInfo[size];
}
};
public Direction direction;
public boolean isPending;
public boolean isFailed;
@ -100,6 +89,26 @@ public class TransactionInfo implements Parcelable, Comparable<TransactionInfo>
this.transfers = transfers;
}
private TransactionInfo(Parcel in) {
direction = Direction.fromInteger(in.readInt());
isPending = in.readByte() != 0;
isFailed = in.readByte() != 0;
amount = in.readLong();
fee = in.readLong();
blockheight = in.readLong();
hash = in.readString();
timestamp = in.readLong();
paymentId = in.readString();
accountIndex = in.readInt();
addressIndex = in.readInt();
confirmations = in.readLong();
subaddressLabel = in.readString();
transfers = in.readArrayList(Transfer.class.getClassLoader());
txKey = in.readString();
notes = in.readString();
address = in.readString();
}
public boolean isConfirmed() {
return confirmations >= CONFIRMATION;
}
@ -136,36 +145,6 @@ public class TransactionInfo implements Parcelable, Comparable<TransactionInfo>
out.writeString(address);
}
public static final Parcelable.Creator<TransactionInfo> CREATOR = new Parcelable.Creator<TransactionInfo>() {
public TransactionInfo createFromParcel(Parcel in) {
return new TransactionInfo(in);
}
public TransactionInfo[] newArray(int size) {
return new TransactionInfo[size];
}
};
private TransactionInfo(Parcel in) {
direction = Direction.fromInteger(in.readInt());
isPending = in.readByte() != 0;
isFailed = in.readByte() != 0;
amount = in.readLong();
fee = in.readLong();
blockheight = in.readLong();
hash = in.readString();
timestamp = in.readLong();
paymentId = in.readString();
accountIndex = in.readInt();
addressIndex = in.readInt();
confirmations = in.readLong();
subaddressLabel = in.readString();
transfers = in.readArrayList(Transfer.class.getClassLoader());
txKey = in.readString();
notes = in.readString();
address = in.readString();
}
@Override
public int describeContents() {
return 0;
@ -183,4 +162,23 @@ public class TransactionInfo implements Parcelable, Comparable<TransactionInfo>
return this.hash.compareTo(another.hash);
}
}
@RequiredArgsConstructor
public enum Direction {
Direction_In(0),
Direction_Out(1);
@Getter
private final int value;
public static Direction fromInteger(int n) {
switch (n) {
case 0:
return Direction_In;
case 1:
return Direction_Out;
}
return null;
}
}
}

View File

@ -20,20 +20,6 @@ import android.os.Parcel;
import android.os.Parcelable;
public class Transfer implements Parcelable {
public long amount;
public String address;
public Transfer(long amount, String address) {
this.amount = amount;
this.address = address;
}
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeLong(amount);
out.writeString(address);
}
public static final Parcelable.Creator<Transfer> CREATOR = new Parcelable.Creator<Transfer>() {
public Transfer createFromParcel(Parcel in) {
return new Transfer(in);
@ -43,12 +29,25 @@ public class Transfer implements Parcelable {
return new Transfer[size];
}
};
public long amount;
public String address;
public Transfer(long amount, String address) {
this.amount = amount;
this.address = address;
}
private Transfer(Parcel in) {
amount = in.readLong();
address = in.readString();
}
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeLong(amount);
out.writeString(address);
}
@Override
public int describeContents() {
return 0;

View File

@ -33,53 +33,47 @@ import timber.log.Timber;
public class Wallet {
final static public long SWEEP_ALL = Long.MAX_VALUE;
private static final String NEW_ACCOUNT_NAME = "Untitled account"; // src/wallet/wallet2.cpp:941
static {
System.loadLibrary("monerujo");
}
static public class Status {
Status(int status, String errorString) {
this.status = StatusEnum.values()[status];
this.errorString = errorString;
}
final private StatusEnum status;
final private String errorString;
@Nullable
private ConnectionStatus connectionStatus; // optional
public StatusEnum getStatus() {
return status;
}
public String getErrorString() {
return errorString;
}
public void setConnectionStatus(@Nullable ConnectionStatus connectionStatus) {
this.connectionStatus = connectionStatus;
}
@Nullable
public ConnectionStatus getConnectionStatus() {
return connectionStatus;
}
public boolean isOk() {
return (getStatus() == StatusEnum.Status_Ok)
&& ((getConnectionStatus() == null) ||
(getConnectionStatus() == ConnectionStatus.ConnectionStatus_Connected));
}
@Override
@NonNull
public String toString() {
return "Wallet.Status: " + status + "/" + errorString + "/" + connectionStatus;
}
}
boolean synced = false;
private int accountIndex = 0;
private long handle = 0;
private long listenerHandle = 0;
private PendingTransaction pendingTransaction = null;
private TransactionHistory history = null;
Wallet(long handle) {
this.handle = handle;
}
Wallet(long handle, int accountIndex) {
this.handle = handle;
this.accountIndex = accountIndex;
}
public static native String getDisplayAmount(long amount);
public static native long getAmountFromString(String amount);
public static native long getAmountFromDouble(double amount);
public static native String generatePaymentId();
public static native boolean isPaymentIdValid(String payment_id);
public static boolean isAddressValid(String address) {
return isAddressValid(address, WalletManager.getInstance().getNetworkType().getValue());
}
public static native boolean isAddressValid(String address, int networkType);
public static native String getPaymentIdFromAddress(String address, int networkType);
public static native long getMaximumAllowedAmount();
public int getAccountIndex() {
return accountIndex;
@ -95,40 +89,6 @@ public class Wallet {
return new File(getPath()).getName();
}
private long handle = 0;
private long listenerHandle = 0;
Wallet(long handle) {
this.handle = handle;
}
Wallet(long handle, int accountIndex) {
this.handle = handle;
this.accountIndex = accountIndex;
}
@RequiredArgsConstructor
@Getter
public enum Device {
Device_Undefined(0, 0),
Device_Software(50, 200),
Device_Ledger(5, 20);
private final int accountLookahead;
private final int subaddressLookahead;
}
public enum StatusEnum {
Status_Ok,
Status_Error,
Status_Critical
}
public enum ConnectionStatus {
ConnectionStatus_Disconnected,
ConnectionStatus_Connected,
ConnectionStatus_WrongVersion
}
public native String getSeed(String offset);
public native String getSeedLanguage();
@ -153,6 +113,9 @@ public class Wallet {
return getAddress(accountIndex);
}
//TODO virtual void hardForkInfo(uint8_t &version, uint64_t &earliest_height) const = 0;
//TODO virtual bool useForkRules(uint8_t version, int64_t early_blocks) const = 0;
public String getAddress(int accountIndex) {
return getAddressJ(accountIndex, 0);
}
@ -193,19 +156,25 @@ public class Wallet {
public native int nettype();
//TODO virtual void hardForkInfo(uint8_t &version, uint64_t &earliest_height) const = 0;
//TODO virtual bool useForkRules(uint8_t version, int64_t early_blocks) const = 0;
// virtual bool createWatchOnly(const std::string &path, const std::string &password, const std::string &language) const = 0;
// virtual void setRefreshFromBlockHeight(uint64_t refresh_from_block_height) = 0;
public native String getIntegratedAddress(String payment_id);
public native String getSecretViewKey();
// virtual void setRecoveringFromSeed(bool recoveringFromSeed) = 0;
// virtual bool connectToDaemon() = 0;
public native String getSecretSpendKey();
public boolean store() {
return store("");
}
//TODO virtual void setTrustedDaemon(bool arg) = 0;
//TODO virtual bool trustedDaemon() const = 0;
public native synchronized boolean store(String path);
public boolean close() {
@ -225,15 +194,9 @@ public class Wallet {
private native boolean initJ(String daemon_address, long upper_transaction_size_limit,
String daemon_username, String daemon_password, String proxy);
// virtual bool createWatchOnly(const std::string &path, const std::string &password, const std::string &language) const = 0;
// virtual void setRefreshFromBlockHeight(uint64_t refresh_from_block_height) = 0;
public native void setRestoreHeight(long height);
public native long getRestoreHeight();
// virtual void setRecoveringFromSeed(bool recoveringFromSeed) = 0;
// virtual bool connectToDaemon() = 0;
public native void setRestoreHeight(long height);
public ConnectionStatus getConnectionStatus() {
int s = getConnectionStatusJ();
@ -242,9 +205,6 @@ public class Wallet {
private native int getConnectionStatusJ();
//TODO virtual void setTrustedDaemon(bool arg) = 0;
//TODO virtual bool trustedDaemon() const = 0;
public native boolean setProxy(String address);
public long getBalance() {
@ -273,8 +233,6 @@ public class Wallet {
public native long getDaemonBlockChainTargetHeight();
boolean synced = false;
public boolean isSynchronized() {
return synced;
}
@ -283,26 +241,6 @@ public class Wallet {
this.synced = true;
}
public static native String getDisplayAmount(long amount);
public static native long getAmountFromString(String amount);
public static native long getAmountFromDouble(double amount);
public static native String generatePaymentId();
public static native boolean isPaymentIdValid(String payment_id);
public static boolean isAddressValid(String address) {
return isAddressValid(address, WalletManager.getInstance().getNetworkType().getValue());
}
public static native boolean isAddressValid(String address, int networkType);
public static native String getPaymentIdFromAddress(String address, int networkType);
public static native long getMaximumAllowedAmount();
public native void startRefresh();
public native void pauseRefresh();
@ -318,16 +256,13 @@ public class Wallet {
rescanBlockchainAsyncJ();
}
//TODO virtual void setAutoRefreshInterval(int millis) = 0;
//TODO virtual int autoRefreshInterval() const = 0;
private PendingTransaction pendingTransaction = null;
public PendingTransaction getPendingTransaction() {
return pendingTransaction;
}
//TODO virtual void setAutoRefreshInterval(int millis) = 0;
//TODO virtual int autoRefreshInterval() const = 0;
public void disposePendingTransaction() {
if (pendingTransaction != null) {
disposeTransaction(pendingTransaction);
@ -366,7 +301,6 @@ public class Wallet {
int mixin_count,
int priority, int accountIndex);
public PendingTransaction createSweepUnmixableTransaction() {
disposePendingTransaction();
long txHandle = createSweepUnmixableTransactionJ();
@ -376,19 +310,8 @@ public class Wallet {
private native long createSweepUnmixableTransactionJ();
//virtual UnsignedTransaction * loadUnsignedTx(const std::string &unsigned_filename) = 0;
//virtual bool submitTransaction(const std::string &fileName) = 0;
public native void disposeTransaction(PendingTransaction pendingTransaction);
//virtual bool exportKeyImages(const std::string &filename) = 0;
//virtual bool importKeyImages(const std::string &filename) = 0;
//virtual TransactionHistory * history() const = 0;
private TransactionHistory history = null;
public TransactionHistory getHistory() {
if (history == null) {
history = new TransactionHistory(getHistoryJ(), accountIndex);
@ -396,15 +319,21 @@ public class Wallet {
return history;
}
//virtual UnsignedTransaction * loadUnsignedTx(const std::string &unsigned_filename) = 0;
//virtual bool submitTransaction(const std::string &fileName) = 0;
private native long getHistoryJ();
//virtual bool exportKeyImages(const std::string &filename) = 0;
//virtual bool importKeyImages(const std::string &filename) = 0;
//virtual TransactionHistory * history() const = 0;
public void refreshHistory() {
getHistory().refreshWithNotes(this);
}
//virtual AddressBook * addressBook() const = 0;
//virtual void setListener(WalletListener *) = 0;
private native long setListenerJ(WalletListener listener);
public void setListener(WalletListener listener) {
@ -413,6 +342,9 @@ public class Wallet {
public native int getDefaultMixin();
//virtual AddressBook * addressBook() const = 0;
//virtual void setListener(WalletListener *) = 0;
public native void setDefaultMixin(int mixin);
public native boolean setUserNote(String txid, String note);
@ -421,14 +353,6 @@ public class Wallet {
public native String getTxKey(String txid);
//virtual std::string signMessage(const std::string &message) = 0;
//virtual bool verifySignedMessage(const std::string &message, const std::string &addres, const std::string &signature) const = 0;
//virtual bool parse_uri(const std::string &uri, std::string &address, std::string &payment_id, uint64_t &tvAmount, std::string &tx_description, std::string &recipient_name, std::vector<std::string> &unknown_parameters, std::string &error) = 0;
//virtual bool rescanSpent() = 0;
private static final String NEW_ACCOUNT_NAME = "Untitled account"; // src/wallet/wallet2.cpp:941
public void addAccount() {
addAccount(NEW_ACCOUNT_NAME);
}
@ -439,6 +363,16 @@ public class Wallet {
return getAccountLabel(accountIndex);
}
//virtual std::string signMessage(const std::string &message) = 0;
//virtual bool verifySignedMessage(const std::string &message, const std::string &addres, const std::string &signature) const = 0;
//virtual bool parse_uri(const std::string &uri, std::string &address, std::string &payment_id, uint64_t &tvAmount, std::string &tx_description, std::string &recipient_name, std::vector<std::string> &unknown_parameters, std::string &error) = 0;
//virtual bool rescanSpent() = 0;
public void setAccountLabel(String label) {
setAccountLabel(accountIndex, label);
}
public String getAccountLabel(int accountIndex) {
String label = getSubaddressLabel(accountIndex, 0);
if (label.equals(NEW_ACCOUNT_NAME)) {
@ -456,10 +390,6 @@ public class Wallet {
public native String getSubaddressLabel(int accountIndex, int addressIndex);
public void setAccountLabel(String label) {
setAccountLabel(accountIndex, label);
}
public void setAccountLabel(int accountIndex, String label) {
setSubaddressLabel(accountIndex, 0, label);
}
@ -504,4 +434,66 @@ public class Wallet {
private native int getDeviceTypeJ();
@RequiredArgsConstructor
@Getter
public enum Device {
Device_Undefined(0, 0),
Device_Software(50, 200),
Device_Ledger(5, 20);
private final int accountLookahead;
private final int subaddressLookahead;
}
public enum StatusEnum {
Status_Ok,
Status_Error,
Status_Critical
}
public enum ConnectionStatus {
ConnectionStatus_Disconnected,
ConnectionStatus_Connected,
ConnectionStatus_WrongVersion
}
static public class Status {
final private StatusEnum status;
final private String errorString;
@Nullable
private ConnectionStatus connectionStatus; // optional
Status(int status, String errorString) {
this.status = StatusEnum.values()[status];
this.errorString = errorString;
}
public StatusEnum getStatus() {
return status;
}
public String getErrorString() {
return errorString;
}
@Nullable
public ConnectionStatus getConnectionStatus() {
return connectionStatus;
}
public void setConnectionStatus(@Nullable ConnectionStatus connectionStatus) {
this.connectionStatus = connectionStatus;
}
public boolean isOk() {
return (getStatus() == StatusEnum.Status_Ok)
&& ((getConnectionStatus() == null) ||
(getConnectionStatus() == ConnectionStatus.ConnectionStatus_Connected));
}
@Override
@NonNull
public String toString() {
return "Wallet.Status: " + status + "/" + errorString + "/" + connectionStatus;
}
}
}

View File

@ -17,7 +17,6 @@
package com.m2049r.xmrwallet.model;
import com.m2049r.xmrwallet.data.Node;
import com.m2049r.xmrwallet.ledger.Ledger;
import com.m2049r.xmrwallet.util.RestoreHeight;
import java.io.File;
@ -31,12 +30,26 @@ import timber.log.Timber;
public class WalletManager {
//TODO: maybe put these in an enum like in monero core - but why?
static public int LOGLEVEL_SILENT = -1;
static public int LOGLEVEL_WARN = 0;
static public int LOGLEVEL_INFO = 1;
static public int LOGLEVEL_DEBUG = 2;
static public int LOGLEVEL_TRACE = 3;
static public int LOGLEVEL_MAX = 4;
// no need to keep a reference to the REAL WalletManager (we get it every tvTime we need it)
private static WalletManager Instance = null;
static {
System.loadLibrary("monerujo");
}
// no need to keep a reference to the REAL WalletManager (we get it every tvTime we need it)
private static WalletManager Instance = null;
private final NetworkType networkType = NetworkType.NetworkType_Mainnet;
private Wallet managedWallet = null;
private String daemonAddress = null;
private String daemonUsername = "";
private String daemonPassword = "";
private String proxy = "";
public static synchronized WalletManager getInstance() {
if (WalletManager.Instance == null) {
@ -46,10 +59,6 @@ public class WalletManager {
return WalletManager.Instance;
}
public String addressPrefix() {
return addressPrefix(getNetworkType());
}
static public String addressPrefix(NetworkType networkType) {
switch (networkType) {
case NetworkType_Testnet:
@ -63,7 +72,23 @@ public class WalletManager {
}
}
private Wallet managedWallet = null;
static public native void initLogger(String argv0, String defaultLogBaseName);
static public native void setLogLevel(int level);
static public native void logDebug(String category, String message);
static public native void logInfo(String category, String message);
static public native void logWarning(String category, String message);
static public native void logError(String category, String message);
static public native String moneroVersion();
public String addressPrefix() {
return addressPrefix(getNetworkType());
}
public Wallet getWallet() {
return managedWallet;
@ -108,6 +133,8 @@ public class WalletManager {
return wallet;
}
//public native List<String> findWallets(String path); // this does not work - some error in boost
private native long createWalletJ(String path, String password, String language, int networkType);
public Wallet openAccount(String path, int accountIndex, String password) {
@ -117,6 +144,8 @@ public class WalletManager {
return wallet;
}
//TODO virtual bool checkPayment(const std::string &address, const std::string &txid, const std::string &txkey, const std::string &daemon_address, uint64_t &received, uint64_t &height, std::string &error) const = 0;
public Wallet openWallet(String path, String password) {
long walletHandle = openWalletJ(path, password, getNetworkType().getValue());
Wallet wallet = new Wallet(walletHandle);
@ -163,7 +192,7 @@ public class WalletManager {
String deviceName) {
long walletHandle = createWalletFromDeviceJ(aFile.getAbsolutePath(), password,
getNetworkType().getValue(), deviceName, restoreHeight,
Ledger.SUBADDRESS_LOOKAHEAD);
"5:20");
Wallet wallet = new Wallet(walletHandle);
manageWallet(wallet);
return wallet;
@ -175,7 +204,6 @@ public class WalletManager {
long restoreHeight,
String subaddressLookahead);
public native boolean closeJ(Wallet wallet);
public boolean close(Wallet wallet) {
@ -208,25 +236,6 @@ public class WalletManager {
private native int queryWalletDeviceJ(String keys_file_name, String password);
//public native List<String> findWallets(String path); // this does not work - some error in boost
public class WalletInfo implements Comparable<WalletInfo> {
@Getter
final private File path;
@Getter
final private String name;
public WalletInfo(File wallet) {
path = wallet.getParentFile();
name = wallet.getName();
}
@Override
public int compareTo(WalletInfo another) {
return name.toLowerCase().compareTo(another.name.toLowerCase());
}
}
public List<WalletInfo> findWallets(File path) {
List<WalletInfo> wallets = new ArrayList<>();
Timber.d("Scanning: %s", path.getAbsolutePath());
@ -243,11 +252,6 @@ public class WalletManager {
return wallets;
}
//TODO virtual bool checkPayment(const std::string &address, const std::string &txid, const std::string &txkey, const std::string &daemon_address, uint64_t &received, uint64_t &height, std::string &error) const = 0;
private String daemonAddress = null;
private final NetworkType networkType = NetworkType.NetworkType_Mainnet;
public NetworkType getNetworkType() {
return networkType;
}
@ -279,20 +283,18 @@ public class WalletManager {
private native void setDaemonAddressJ(String address);
private String daemonUsername = "";
public String getDaemonUsername() {
return daemonUsername;
}
private String daemonPassword = "";
public String getDaemonPassword() {
return daemonPassword;
}
public native int getDaemonVersion();
//TODO static std::tuple<bool, std::string, std::string, std::string, std::string> checkUpdates(const std::string &software, const std::string &subdir);
public native long getBlockchainHeight();
public native long getBlockchainTargetHeight();
@ -311,8 +313,6 @@ public class WalletManager {
public native String resolveOpenAlias(String address, boolean dnssec_valid);
private String proxy = "";
public String getProxy() {
return proxy;
}
@ -324,27 +324,20 @@ public class WalletManager {
public native boolean setProxyJ(String address);
//TODO static std::tuple<bool, std::string, std::string, std::string, std::string> checkUpdates(const std::string &software, const std::string &subdir);
public class WalletInfo implements Comparable<WalletInfo> {
@Getter
final private File path;
@Getter
final private String name;
static public native void initLogger(String argv0, String defaultLogBaseName);
//TODO: maybe put these in an enum like in monero core - but why?
static public int LOGLEVEL_SILENT = -1;
static public int LOGLEVEL_WARN = 0;
static public int LOGLEVEL_INFO = 1;
static public int LOGLEVEL_DEBUG = 2;
static public int LOGLEVEL_TRACE = 3;
static public int LOGLEVEL_MAX = 4;
static public native void setLogLevel(int level);
static public native void logDebug(String category, String message);
static public native void logInfo(String category, String message);
static public native void logWarning(String category, String message);
static public native void logError(String category, String message);
static public native String moneroVersion();
public WalletInfo(File wallet) {
path = wallet.getParentFile();
name = wallet.getName();
}
@Override
public int compareTo(WalletInfo another) {
return name.toLowerCase().compareTo(another.name.toLowerCase());
}
}
}

View File

@ -5,16 +5,8 @@ import com.m2049r.xmrwallet.model.TransactionInfo;
import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.model.WalletManager;
import java.util.ArrayList;
import java.util.HashMap;
public class AddressService extends ServiceBase {
public static AddressService instance = null;
public static AddressService getInstance() {
return instance;
}
private int latestAddressIndex = 1;
public AddressService(MoneroHandlerThread thread) {
@ -22,6 +14,10 @@ public class AddressService extends ServiceBase {
instance = this;
}
public static AddressService getInstance() {
return instance;
}
public void refreshAddresses() {
for (TransactionInfo info : HistoryService.getInstance().getHistory()) {
if (info.addressIndex >= latestAddressIndex) {

View File

@ -3,26 +3,23 @@ package com.m2049r.xmrwallet.service;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import com.m2049r.xmrwallet.MainActivity;
import com.m2049r.xmrwallet.model.WalletManager;
public class BalanceService extends ServiceBase {
public static BalanceService instance = null;
public static BalanceService getInstance() {
return instance;
}
private final MutableLiveData<Long> _balance = new MutableLiveData<>(0L);
public LiveData<Long> balance = _balance;
private final MutableLiveData<Long> _lockedBalance = new MutableLiveData<>(0L);
public LiveData<Long> balance = _balance;
public LiveData<Long> lockedBalance = _lockedBalance;
public BalanceService(MoneroHandlerThread thread) {
super(thread);
instance = this;
}
public static BalanceService getInstance() {
return instance;
}
public void refreshBalance() {
_balance.postValue(getUnlockedBalanceRaw());
_lockedBalance.postValue(getLockedBalanceRaw());

View File

@ -3,26 +3,23 @@ package com.m2049r.xmrwallet.service;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import com.m2049r.xmrwallet.MainActivity;
import com.m2049r.xmrwallet.model.WalletManager;
public class BlockchainService extends ServiceBase {
public static BlockchainService instance = null;
public static BlockchainService getInstance() {
return instance;
}
private long daemonHeight = 0;
private long lastDaemonHeightUpdateTimeMs = 0;
private final MutableLiveData<Long> _currentHeight = new MutableLiveData<>(0L);
public LiveData<Long> height = _currentHeight;
private long daemonHeight = 0;
private long lastDaemonHeightUpdateTimeMs = 0;
public BlockchainService(MoneroHandlerThread thread) {
super(thread);
instance = this;
}
public static BlockchainService getInstance() {
return instance;
}
public void refreshBlockchain() {
_currentHeight.postValue(getCurrentHeight());
}

View File

@ -2,25 +2,25 @@ package com.m2049r.xmrwallet.service;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import com.m2049r.xmrwallet.model.TransactionInfo;
import com.m2049r.xmrwallet.model.WalletManager;
import java.util.List;
public class HistoryService extends ServiceBase {
public static HistoryService instance = null;
public static HistoryService getInstance() {
return instance;
}
private final MutableLiveData<List<TransactionInfo>> _history = new MutableLiveData<>();
public LiveData<List<TransactionInfo>> history = _history;
public HistoryService(MoneroHandlerThread thread) {
super(thread);
instance = this;
}
public static HistoryService getInstance() {
return instance;
}
public void refreshHistory() {
_history.postValue(getHistory());
}

View File

@ -28,8 +28,6 @@ import com.m2049r.xmrwallet.model.WalletListener;
import com.m2049r.xmrwallet.model.WalletManager;
import com.m2049r.xmrwallet.util.Constants;
import java.io.File;
/**
* Handy class for starting a new thread that has a looper. The looper can then be
@ -37,10 +35,11 @@ import java.io.File;
* The started Thread has a stck size of STACK_SIZE (=5MB)
*/
public class MoneroHandlerThread extends Thread implements WalletListener {
private Listener listener = null;
// from src/cryptonote_config.h
static public final long THREAD_STACK_SIZE = 5 * 1024 * 1024;
private Wallet wallet;
int triesLeft = 5;
private Listener listener = null;
private final Wallet wallet;
public MoneroHandlerThread(String name, Listener listener, Wallet wallet) {
super(null, null, name, THREAD_STACK_SIZE);
@ -93,8 +92,6 @@ public class MoneroHandlerThread extends Thread implements WalletListener {
refresh();
}
int triesLeft = 5;
@Override
public void refreshed() {
Wallet.ConnectionStatus status = wallet.getFullStatus().getConnectionStatus();
@ -129,6 +126,7 @@ public class MoneroHandlerThread extends Thread implements WalletListener {
public interface Listener {
void onRefresh();
void onConnectionFail();
}
}

View File

@ -3,18 +3,17 @@ package com.m2049r.xmrwallet.service;
import android.content.Context;
import android.content.SharedPreferences;
import com.m2049r.xmrwallet.MainActivity;
import com.m2049r.xmrwallet.MoneroApplication;
public class PrefService extends ServiceBase {
public static SharedPreferences instance = null;
public static SharedPreferences getInstance() {
return instance;
}
public PrefService(MoneroApplication application) {
super(null);
instance = application.getSharedPreferences(application.getApplicationInfo().packageName, Context.MODE_PRIVATE);
}
public static SharedPreferences getInstance() {
return instance;
}
}

View File

@ -1,21 +1,19 @@
package com.m2049r.xmrwallet.service;
import com.m2049r.xmrwallet.MainActivity;
import com.m2049r.xmrwallet.livedata.SingleLiveEvent;
import com.m2049r.xmrwallet.model.PendingTransaction;
public class TxService extends ServiceBase {
public static TxService instance = null;
public static TxService getInstance() {
return instance;
}
public TxService(MoneroHandlerThread thread) {
super(thread);
instance = this;
}
public static TxService getInstance() {
return instance;
}
public PendingTransaction createTx(String address, String amount, boolean sendAll, PendingTransaction.Priority feePriority) {
return this.getThread().createTx(address, amount, sendAll, feePriority);
}

View File

@ -1,68 +0,0 @@
/*
* Copyright (c) 2018 m2049r
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.xmrwallet.util;
import java.math.BigInteger;
public class CrazyPassEncoder {
static final String BASE = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ";
static final int PW_CHARS = 52;
// this takes a 32 byte buffer and converts it to 52 alphnumeric characters
// separated by blanks every 4 characters = 13 groups of 4
// always (padding by Xs if need be
static public String encode(byte[] data) {
if (data.length != 32) throw new IllegalArgumentException("data[] is not 32 bytes long");
BigInteger rest = new BigInteger(1, data);
BigInteger remainder;
final StringBuilder result = new StringBuilder();
final BigInteger base = BigInteger.valueOf(BASE.length());
int i = 0;
do {
if ((i > 0) && (i % 4 == 0)) result.append(' ');
i++;
remainder = rest.remainder(base);
rest = rest.divide(base);
result.append(BASE.charAt(remainder.intValue()));
} while (!BigInteger.ZERO.equals(rest));
// pad it
while (i < PW_CHARS) {
if ((i > 0) && (i % 4 == 0)) result.append(' ');
result.append('2');
i++;
}
return result.toString();
}
static public String reformat(String password) {
// maybe this is a CrAzYpass without blanks? or lowercase letters
String noBlanks = password.toUpperCase().replaceAll(" ", "");
if (noBlanks.length() == PW_CHARS) { // looks like a CrAzYpass
// insert blanks every 4 characters
StringBuilder sb = new StringBuilder();
for (int i = 0; i < PW_CHARS; i++) {
if ((i > 0) && (i % 4 == 0)) sb.append(' ');
char c = noBlanks.charAt(i);
if (BASE.indexOf(c) < 0) return null; // invalid character found
sb.append(c);
}
return sb.toString();
} else {
return null; // not a CrAzYpass
}
}
}

View File

@ -31,10 +31,6 @@ public enum DayNightMode {
this.nightMode = nightMode;
}
public int getNightMode() {
return nightMode;
}
static public DayNightMode getValue(int nightMode) {
for (DayNightMode mode : DayNightMode.values()) {
if (mode.nightMode == nightMode)
@ -42,4 +38,8 @@ public enum DayNightMode {
}
return UNKNOWN;
}
public int getNightMode() {
return nightMode;
}
}

View File

@ -30,10 +30,6 @@ import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.VectorDrawable;
import android.os.AsyncTask;
import android.os.StrictMode;
import android.system.ErrnoException;
import android.system.Os;
import android.view.View;
import android.view.WindowManager;
import android.view.animation.Animation;
@ -42,12 +38,9 @@ import android.view.inputmethod.InputMethodManager;
import android.widget.Toast;
import androidx.activity.result.ActivityResultLauncher;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import com.m2049r.xmrwallet.BuildConfig;
import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.data.Crypto;
import com.m2049r.xmrwallet.model.WalletManager;
import java.io.File;
@ -67,17 +60,18 @@ import timber.log.Timber;
public class Helper {
static public final String NOCRAZYPASS_FLAGFILE = ".nocrazypass";
static public final String BASE_CRYPTO = Crypto.XMR.getSymbol();
static public final int XMR_DECIMALS = 12;
static public final long ONE_XMR = Math.round(Math.pow(10, Helper.XMR_DECIMALS));
static public final boolean SHOW_EXCHANGERATES = true;
static public boolean ALLOW_SHIFT = false;
static public final int PERMISSIONS_REQUEST_CAMERA = 7;
static final int HTTP_TIMEOUT = 5000;
static private final String WALLET_DIR = "wallets";
static private final String MONERO_DIR = "monero";
private final static char[] HexArray = "0123456789ABCDEF".toCharArray();
static public boolean ALLOW_SHIFT = false;
static public int DISPLAY_DIGITS_INFO = 5;
static private Animation ShakeAnimation;
static public File getWalletRoot(Context context) {
return getStorage(context, WALLET_DIR);
@ -97,8 +91,6 @@ public class Helper {
return dir;
}
static public final int PERMISSIONS_REQUEST_CAMERA = 7;
static public boolean getCameraPermission(Activity context) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
if (context.checkSelfPermission(Manifest.permission.CAMERA)
@ -227,8 +219,6 @@ public class Helper {
return bitmap;
}
static final int HTTP_TIMEOUT = 5000;
static public String getUrl(String httpsUrl) {
HttpsURLConnection urlConnection = null;
try {
@ -284,8 +274,6 @@ public class Helper {
return null;
}
static private Animation ShakeAnimation;
static public Animation getShakeAnimation(Context context) {
if (ShakeAnimation == null) {
synchronized (Helper.class) {
@ -297,8 +285,6 @@ public class Helper {
return ShakeAnimation;
}
private final static char[] HexArray = "0123456789ABCDEF".toCharArray();
public static String bytesToHex(byte[] data) {
if ((data != null) && (data.length > 0))
return String.format("%0" + (data.length * 2) + "X", new BigInteger(1, data));

View File

@ -82,16 +82,6 @@ public class KeyStoreHelper {
}
}
static public class BrokenPasswordStoreException extends Exception {
BrokenPasswordStoreException() {
super();
}
BrokenPasswordStoreException(Throwable cause) {
super(cause);
}
}
/**
* Creates a public and private key and stores it using the Android Key
* Store, so that only this application will be able to access the keys.
@ -267,4 +257,14 @@ public class KeyStoreHelper {
String WALLET_PASS_PREFS_NAME = "wallet";
String WALLET_PASS_KEY_PREFIX = "walletKey-";
}
static public class BrokenPasswordStoreException extends Exception {
BrokenPasswordStoreException() {
super();
}
BrokenPasswordStoreException(Throwable cause) {
super(cause);
}
}
}

View File

@ -24,6 +24,8 @@ import timber.log.Timber;
@RequiredArgsConstructor
public class LegacyStorageHelper {
private static final Pattern WALLET_PATTERN = Pattern.compile("^(.+) \\(([0-9]+)\\).keys$");
private static final String MIGRATED_KEY = "migrated_legacy_storage";
final private File srcDir;
final private File dstDir;
@ -49,6 +51,70 @@ public class LegacyStorageHelper {
}
}
private static boolean isExternalStorageWritable() {
String state = Environment.getExternalStorageState();
return Environment.MEDIA_MOUNTED.equals(state);
}
private static File getWalletRoot() {
if (!isExternalStorageWritable())
throw new IllegalStateException();
// wallet folder for legacy (pre-Q) installations
final String FLAVOR_SUFFIX =
(BuildConfig.FLAVOR.startsWith("prod") ? "" : "." + BuildConfig.FLAVOR)
+ (BuildConfig.DEBUG ? "-debug" : "");
final String WALLET_DIR = "monerujo" + FLAVOR_SUFFIX;
File dir = new File(Environment.getExternalStorageDirectory(), WALLET_DIR);
if (!dir.exists() || !dir.isDirectory())
throw new IllegalStateException();
return dir;
}
private static boolean hasReadPermission(Context context) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
return context.checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_DENIED;
} else {
return true;
}
}
private static String getUniqueName(File root, String name) {
if (!(new File(root, name + ".keys")).exists()) // <name> does not exist => it's ok to use
return name;
File[] wallets = root.listFiles(
(dir, filename) -> {
Matcher m = WALLET_PATTERN.matcher(filename);
if (m.find())
return m.group(1).equals(name);
else return false;
});
if (wallets.length == 0) return name + " (1)";
int maxIndex = 0;
for (File wallet : wallets) {
try {
final Matcher m = WALLET_PATTERN.matcher(wallet.getName());
if (!m.find())
throw new IllegalStateException("this must match as it did before");
final int index = Integer.parseInt(m.group(2));
if (index > maxIndex) maxIndex = index;
} catch (NumberFormatException ex) {
// this cannot happen & we can ignore it if it does
}
}
return name + " (" + (maxIndex + 1) + ")";
}
public static boolean isStorageMigrated(Context context) {
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(MIGRATED_KEY, false);
}
public static void setStorageMigrated(Context context) {
PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(MIGRATED_KEY, true).apply();
}
public void migrate() {
String addressPrefix = WalletManager.getInstance().addressPrefix();
File[] wallets = srcDir.listFiles((dir, filename) -> filename.endsWith(".keys"));
@ -99,72 +165,4 @@ public class LegacyStorageHelper {
inChannel.transferTo(0, inChannel.size(), outChannel);
}
}
private static boolean isExternalStorageWritable() {
String state = Environment.getExternalStorageState();
return Environment.MEDIA_MOUNTED.equals(state);
}
private static File getWalletRoot() {
if (!isExternalStorageWritable())
throw new IllegalStateException();
// wallet folder for legacy (pre-Q) installations
final String FLAVOR_SUFFIX =
(BuildConfig.FLAVOR.startsWith("prod") ? "" : "." + BuildConfig.FLAVOR)
+ (BuildConfig.DEBUG ? "-debug" : "");
final String WALLET_DIR = "monerujo" + FLAVOR_SUFFIX;
File dir = new File(Environment.getExternalStorageDirectory(), WALLET_DIR);
if (!dir.exists() || !dir.isDirectory())
throw new IllegalStateException();
return dir;
}
private static boolean hasReadPermission(Context context) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
return context.checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_DENIED;
} else {
return true;
}
}
private static final Pattern WALLET_PATTERN = Pattern.compile("^(.+) \\(([0-9]+)\\).keys$");
private static String getUniqueName(File root, String name) {
if (!(new File(root, name + ".keys")).exists()) // <name> does not exist => it's ok to use
return name;
File[] wallets = root.listFiles(
(dir, filename) -> {
Matcher m = WALLET_PATTERN.matcher(filename);
if (m.find())
return m.group(1).equals(name);
else return false;
});
if (wallets.length == 0) return name + " (1)";
int maxIndex = 0;
for (File wallet : wallets) {
try {
final Matcher m = WALLET_PATTERN.matcher(wallet.getName());
if (!m.find())
throw new IllegalStateException("this must match as it did before");
final int index = Integer.parseInt(m.group(2));
if (index > maxIndex) maxIndex = index;
} catch (NumberFormatException ex) {
// this cannot happen & we can ignore it if it does
}
}
return name + " (" + (maxIndex + 1) + ")";
}
private static final String MIGRATED_KEY = "migrated_legacy_storage";
public static boolean isStorageMigrated(Context context) {
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(MIGRATED_KEY, false);
}
public static void setStorageMigrated(Context context) {
PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(MIGRATED_KEY, true).apply();
}
}

View File

@ -28,11 +28,11 @@ import java.util.concurrent.atomic.AtomicInteger;
public class MoneroThreadPoolExecutor {
public static final Executor MONERO_THREAD_POOL_EXECUTOR;
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
private static final int KEEP_ALIVE_SECONDS = 30;
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1);
@ -40,12 +40,9 @@ public class MoneroThreadPoolExecutor {
return new Thread(null, r, "MoneroTask #" + mCount.getAndIncrement(), MoneroHandlerThread.THREAD_STACK_SIZE);
}
};
private static final BlockingQueue<Runnable> sPoolWorkQueue =
new LinkedBlockingQueue<>(128);
public static final Executor MONERO_THREAD_POOL_EXECUTOR;
static {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,

View File

@ -1,393 +0,0 @@
/*
* Copyright (c) 2021 m2049r
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.xmrwallet.util;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import com.burgstaller.okhttp.AuthenticationCacheInterceptor;
import com.burgstaller.okhttp.CachingAuthenticatorDecorator;
import com.burgstaller.okhttp.digest.CachingAuthenticator;
import com.burgstaller.okhttp.digest.Credentials;
import com.burgstaller.okhttp.digest.DigestAuthenticator;
import org.json.JSONObject;
import java.io.IOException;
import java.net.Proxy;
import java.util.ArrayList;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import info.guardianproject.netcipher.client.StrongOkHttpClientBuilder;
import info.guardianproject.netcipher.proxy.OrbotHelper;
import info.guardianproject.netcipher.proxy.SignatureUtils;
import info.guardianproject.netcipher.proxy.StatusCallback;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.HttpUrl;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.RequestBody;
import okhttp3.Response;
import timber.log.Timber;
@RequiredArgsConstructor
public class NetCipherHelper implements StatusCallback {
public static final String USER_AGENT = "Monerujo/1.0";
public static final int HTTP_TIMEOUT = 1000; //ms
public static final int TOR_TIMEOUT_CONNECT = 5000; //ms
public static final int TOR_TIMEOUT = 2000; //ms
public interface OnStatusChangedListener {
void connected();
void disconnected();
void notInstalled();
void notEnabled();
}
final private Context context;
final private OrbotHelper orbot;
@SuppressLint("StaticFieldLeak")
private static NetCipherHelper Instance;
public static void createInstance(Context context) {
if (Instance == null) {
synchronized (NetCipherHelper.class) {
if (Instance == null) {
final Context applicationContext = context.getApplicationContext();
Instance = new NetCipherHelper(applicationContext, OrbotHelper.get(context).statusTimeout(5000));
}
}
}
}
public static NetCipherHelper getInstance() {
if (Instance == null) throw new IllegalStateException("NetCipherHelper is null");
return Instance;
}
private OkHttpClient client;
private void createTorClient(Intent statusIntent) {
String orbotStatus = statusIntent.getStringExtra(OrbotHelper.EXTRA_STATUS);
if (orbotStatus == null) throw new IllegalStateException("status is null");
if (!orbotStatus.equals(OrbotHelper.STATUS_ON))
throw new IllegalStateException("Orbot is not ON");
try {
final OkHttpClient.Builder okBuilder = new OkHttpClient.Builder()
.connectTimeout(TOR_TIMEOUT_CONNECT, TimeUnit.MILLISECONDS)
.writeTimeout(TOR_TIMEOUT, TimeUnit.MILLISECONDS)
.readTimeout(TOR_TIMEOUT, TimeUnit.MILLISECONDS);
client = new StrongOkHttpClientBuilder(context)
.withSocksProxy()
.applyTo(okBuilder, statusIntent)
.build();
Helper.ALLOW_SHIFT = false; // no shifting with Tor
} catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
private void createClearnetClient() {
try {
client = new OkHttpClient.Builder()
.connectTimeout(HTTP_TIMEOUT, TimeUnit.MILLISECONDS)
.writeTimeout(HTTP_TIMEOUT, TimeUnit.MILLISECONDS)
.readTimeout(HTTP_TIMEOUT, TimeUnit.MILLISECONDS)
.build();
Helper.ALLOW_SHIFT = true;
} catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
private OnStatusChangedListener onStatusChangedListener;
public static void deregister() {
getInstance().onStatusChangedListener = null;
}
public static void register(OnStatusChangedListener listener) {
final NetCipherHelper me = getInstance();
me.onStatusChangedListener = listener;
// NOT_INSTALLED is dealt with through the callbacks
me.orbot.removeStatusCallback(me) // make sure we are registered just once
.addStatusCallback(me);
// deal with org.torproject.android.intent.action.STATUS = STARTS_DISABLED
me.context.registerReceiver(orbotStatusReceiver, new IntentFilter(OrbotHelper.ACTION_STATUS));
me.startTor();
}
// for StatusCallback
public enum Status {
STARTING,
ENABLED,
STOPPING,
DISABLED,
NOT_INSTALLED,
NOT_ENABLED,
UNKNOWN
}
private Status status = Status.UNKNOWN;
@Override
public void onStarting() {
Timber.d("onStarting");
status = Status.STARTING;
}
@Override
public void onEnabled(Intent statusIntent) {
Timber.d("onEnabled");
if (getTorPref() != Status.ENABLED) return; // do we want Tor?
createTorClient(statusIntent);
status = Status.ENABLED;
if (onStatusChangedListener != null) {
new Thread(() -> onStatusChangedListener.connected()).start();
}
}
@Override
public void onStopping() {
Timber.d("onStopping");
status = Status.STOPPING;
}
@Override
public void onDisabled() {
Timber.d("onDisabled");
createClearnetClient();
status = Status.DISABLED;
if (onStatusChangedListener != null) {
new Thread(() -> onStatusChangedListener.disconnected()).start();
}
}
@Override
public void onStatusTimeout() {
Timber.d("onStatusTimeout");
createClearnetClient();
// (timeout does not not change the status)
if (onStatusChangedListener != null) {
new Thread(() -> onStatusChangedListener.disconnected()).start();
}
orbotInit = false; // do init() next time we try to open Tor
}
@Override
public void onNotYetInstalled() {
Timber.d("onNotYetInstalled");
// never mind then
orbot.removeStatusCallback(this);
createClearnetClient();
status = Status.NOT_INSTALLED;
if (onStatusChangedListener != null) {
new Thread(() -> onStatusChangedListener.notInstalled()).start();
}
}
// user has not enabled background Orbot starts
public void onNotEnabled() {
Timber.d("onNotEnabled");
// keep the callback in case they turn it on manually
setTorPref(Status.DISABLED);
createClearnetClient();
status = Status.NOT_ENABLED;
if (onStatusChangedListener != null) {
new Thread(() -> onStatusChangedListener.notEnabled()).start();
}
}
static public Status getStatus() {
return getInstance().status;
}
public void toggle() {
switch (getStatus()) {
case ENABLED:
onDisabled();
setTorPref(Status.DISABLED);
break;
case DISABLED:
setTorPref(Status.ENABLED);
startTor();
break;
}
}
private boolean orbotInit = false;
private void startTor() {
if (!isOrbotInstalled()) {
onNotYetInstalled();
} else if (getTorPref() == Status.DISABLED) {
onDisabled();
} else if (!orbotInit) {
orbotInit = orbot.init();
} else {
orbot.requestStart(context);
}
}
// extracted from OrbotHelper
private boolean isOrbotInstalled() {
ArrayList<String> hashes = new ArrayList<>();
// Tor Project signing key
hashes.add("A4:54:B8:7A:18:47:A8:9E:D7:F5:E7:0F:BA:6B:BA:96:F3:EF:29:C2:6E:09:81:20:4F:E3:47:BF:23:1D:FD:5B");
// f-droid.org signing key
hashes.add("A7:02:07:92:4F:61:FF:09:37:1D:54:84:14:5C:4B:EE:77:2C:55:C1:9E:EE:23:2F:57:70:E1:82:71:F7:CB:AE");
return null != SignatureUtils.validateBroadcastIntent(context,
OrbotHelper.getOrbotStartIntent(context),
hashes, false);
}
static public boolean hasClient() {
return getInstance().client != null;
}
static public boolean isTor() {
return getStatus() == Status.ENABLED;
}
static public String getProxy() {
if (!isTor()) return "";
final Proxy proxy = getInstance().client.proxy();
if (proxy == null) return "";
return proxy.address().toString().substring(1);
}
@ToString
static public class Request {
final HttpUrl url;
final String json;
final String username;
final String password;
public Request(final HttpUrl url, final String json, final String username, final String password) {
this.url = url;
this.json = json;
this.username = username;
this.password = password;
}
public Request(final HttpUrl url, final JSONObject json) {
this(url, json == null ? null : json.toString(), null, null);
}
public Request(final HttpUrl url) {
this(url, null, null, null);
}
public void enqueue(Callback callback) {
newCall().enqueue(callback);
}
public Response execute() throws IOException {
return newCall().execute();
}
private Call newCall() {
return getClient().newCall(getRequest());
}
private OkHttpClient getClient() {
if (mockClient != null) return mockClient; // Unit-test mode
final OkHttpClient client = getInstance().client;
if ((username != null) && (!username.isEmpty())) {
final DigestAuthenticator authenticator = new DigestAuthenticator(new Credentials(username, password));
final Map<String, CachingAuthenticator> authCache = new ConcurrentHashMap<>();
return client.newBuilder()
.authenticator(new CachingAuthenticatorDecorator(authenticator, authCache))
.addInterceptor(new AuthenticationCacheInterceptor(authCache))
.build();
// TODO: maybe cache & reuse the client for these credentials?
} else {
return client;
}
}
private okhttp3.Request getRequest() {
final okhttp3.Request.Builder builder =
new okhttp3.Request.Builder()
.url(url)
.header("User-Agent", USER_AGENT);
if (json != null) {
builder.post(RequestBody.create(json, MediaType.parse("application/json")));
} else {
builder.get();
}
return builder.build();
}
// for unit tests only
static public OkHttpClient mockClient = null;
}
private static final String PREFS_NAME = "tor";
private static final String PREFS_STATUS = "status";
private Status currentPref = Status.UNKNOWN;
private Status getTorPref() {
if (currentPref != Status.UNKNOWN) return currentPref;
currentPref = Status.valueOf(context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
.getString(PREFS_STATUS, "DISABLED"));
return currentPref;
}
private void setTorPref(Status status) {
if (getTorPref() == status) return; // no change
context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
.edit()
.putString(PREFS_STATUS, status.name())
.apply();
currentPref = status;
}
private static final BroadcastReceiver orbotStatusReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Timber.d("%s/%s", intent.getAction(), intent.getStringExtra(OrbotHelper.EXTRA_STATUS));
if (OrbotHelper.ACTION_STATUS.equals(intent.getAction())) {
if (OrbotHelper.STATUS_STARTS_DISABLED.equals(intent.getStringExtra(OrbotHelper.EXTRA_STATUS))) {
getInstance().onNotEnabled();
}
}
}
};
public void installOrbot(Activity host) {
host.startActivity(OrbotHelper.getOrbotInstallIntent(context));
}
}

View File

@ -17,12 +17,9 @@
package com.m2049r.xmrwallet.util;
import android.annotation.SuppressLint;
import android.content.Context;
import android.preference.PreferenceManager;
import androidx.appcompat.app.AppCompatDelegate;
import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.service.PrefService;
public class NightmodeHelper {

View File

@ -1,53 +0,0 @@
/*
* Copyright (c) 2018 m2049r
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.xmrwallet.util;
import com.m2049r.xmrwallet.data.NodeInfo;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import timber.log.Timber;
public class NodePinger {
static final public int NUM_THREADS = 10;
static final public long MAX_TIME = 5L; // seconds
public interface Listener {
void publish(NodeInfo node);
}
static public void execute(Collection<NodeInfo> nodes, final Listener listener) {
final ExecutorService exeService = Executors.newFixedThreadPool(NUM_THREADS);
List<Callable<Boolean>> taskList = new ArrayList<>();
for (NodeInfo node : nodes) {
taskList.add(() -> node.testRpcService(listener));
}
try {
exeService.invokeAll(taskList, MAX_TIME, TimeUnit.SECONDS);
} catch (InterruptedException ex) {
Timber.w(ex);
}
exeService.shutdownNow();
}
}

View File

@ -1,245 +0,0 @@
/*
* Copyright (c) 2018 m2049r
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Specs from https://openalias.org/
package com.m2049r.xmrwallet.util;
import android.os.AsyncTask;
import com.m2049r.xmrwallet.data.BarcodeData;
import com.m2049r.xmrwallet.data.Crypto;
import org.jitsi.dnssec.validator.ValidatingResolver;
import org.xbill.DNS.DClass;
import org.xbill.DNS.Flags;
import org.xbill.DNS.Message;
import org.xbill.DNS.Name;
import org.xbill.DNS.RRset;
import org.xbill.DNS.Rcode;
import org.xbill.DNS.Record;
import org.xbill.DNS.Section;
import org.xbill.DNS.SimpleResolver;
import org.xbill.DNS.TXTRecord;
import org.xbill.DNS.Type;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import timber.log.Timber;
public class OpenAliasHelper {
public static final String OA1_SCHEME = "oa1:";
public static final String OA1_ASSET = "asset";
public static final String OA1_ADDRESS = "recipient_address";
public static final String OA1_NAME = "recipient_name";
public static final String OA1_DESCRIPTION = "tx_description";
public static final String OA1_AMOUNT = "tx_amount";
public static final int DNS_LOOKUP_TIMEOUT = 2500; // ms
public static void resolve(String name, OnResolvedListener resolvedListener) {
new DnsTxtResolver(resolvedListener).execute(name);
}
public static Map<String, String> parse(String oaString) {
return new OpenAliasParser(oaString).parse();
}
public interface OnResolvedListener {
void onResolved(Map<Crypto, BarcodeData> dataMap);
void onFailure();
}
private static class DnsTxtResolver extends AsyncTask<String, Void, Boolean> {
List<String> txts = new ArrayList<>();
boolean dnssec = false;
private final OnResolvedListener resolvedListener;
private DnsTxtResolver(OnResolvedListener resolvedListener) {
this.resolvedListener = resolvedListener;
}
// trust anchor of the root zone
// http://data.iana.org/root-anchors/root-anchors.xml
final String ROOT =
". IN DS 19036 8 2 49AAC11D7B6F6446702E54A1607371607A1A41855200FD2CE1CDDE32F24E8FB5\n" +
". IN DS 20326 8 2 E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D";
final String[] DNSSEC_SERVERS = {
"4.2.2.1", // Level3
"4.2.2.2", // Level3
"4.2.2.6", // Level3
"1.1.1.1", // cloudflare
"9.9.9.9", // quad9
"8.8.4.4", // google
"8.8.8.8" // google
};
@Override
protected Boolean doInBackground(String... args) {
//main();
if (args.length != 1) return false;
String name = args[0];
if ((name == null) || (name.isEmpty()))
return false; //pointless trying to lookup nothing
Timber.d("Resolving %s", name);
try {
SimpleResolver sr = new SimpleResolver(DNSSEC_SERVERS[new Random().nextInt(DNSSEC_SERVERS.length)]);
ValidatingResolver vr = new ValidatingResolver(sr);
vr.setTimeout(0, DNS_LOOKUP_TIMEOUT);
vr.loadTrustAnchors(new ByteArrayInputStream(ROOT.getBytes(StandardCharsets.US_ASCII)));
Record qr = Record.newRecord(Name.fromConstantString(name + "."), Type.TXT, DClass.IN);
Message response = vr.send(Message.newQuery(qr));
final int rcode = response.getRcode();
if (rcode != Rcode.NOERROR) {
Timber.i("Rcode: %s", Rcode.string(rcode));
for (RRset set : response.getSectionRRsets(Section.ADDITIONAL)) {
if (set.getName().equals(Name.root) && set.getType() == Type.TXT
&& set.getDClass() == ValidatingResolver.VALIDATION_REASON_QCLASS) {
Timber.i("Reason: %s", ((TXTRecord) set.first()).getStrings().get(0));
}
}
return false;
} else {
dnssec = response.getHeader().getFlag(Flags.AD);
for (Record record : response.getSectionArray(Section.ANSWER)) {
if (record.getType() == Type.TXT) {
txts.addAll(((TXTRecord) record).getStrings());
}
}
}
} catch (IOException | IllegalArgumentException ex) {
return false;
}
return true;
}
@Override
public void onPostExecute(Boolean success) {
if (resolvedListener != null)
if (success) {
Map<Crypto, BarcodeData> dataMap = new HashMap<>();
for (String txt : txts) {
BarcodeData bc = BarcodeData.parseOpenAlias(txt, dnssec);
if (bc != null) {
if (!dataMap.containsKey(bc.asset)) {
dataMap.put(bc.asset, bc);
}
}
}
resolvedListener.onResolved(dataMap);
} else {
resolvedListener.onFailure();
}
}
}
private static class OpenAliasParser {
int currentPos = 0;
final String oaString;
StringBuilder sb = new StringBuilder();
OpenAliasParser(String oaString) {
this.oaString = oaString;
}
Map<String, String> parse() {
if ((oaString == null) || !oaString.startsWith(OA1_SCHEME)) return null;
if (oaString.charAt(oaString.length() - 1) != ';') return null;
Map<String, String> oaAttributes = new HashMap<>();
final int assetEnd = oaString.indexOf(' ');
if (assetEnd > 20) return null; // random sanity check
String asset = oaString.substring(OA1_SCHEME.length(), assetEnd);
oaAttributes.put(OA1_ASSET, asset);
boolean inQuote = false;
boolean inKey = true;
String key = null;
for (currentPos = assetEnd; currentPos < oaString.length() - 1; currentPos++) {
char c = currentChar();
if (inKey) {
if ((sb.length() == 0) && Character.isWhitespace(c)) continue;
if ((c == '\\') || (c == ';')) return null;
if (c == '=') {
key = sb.toString();
if (oaAttributes.containsKey(key)) return null; // no duplicate keys allowed
sb.setLength(0);
inKey = false;
} else {
sb.append(c);
}
continue;
}
// now we are in the value
if ((sb.length() == 0) && (c == '"')) {
inQuote = true;
continue;
}
if ((!inQuote || ((sb.length() > 0) && (c == '"'))) && (nextChar() == ';')) {
if (!inQuote) appendCurrentEscapedChar();
oaAttributes.put(key, sb.toString());
sb.setLength(0);
currentPos++; // skip the next ;
inQuote = false;
inKey = true;
key = null;
continue;
}
appendCurrentEscapedChar();
}
if (inQuote) return null;
if (key != null) {
oaAttributes.put(key, sb.toString());
}
return oaAttributes;
}
char currentChar() {
return oaString.charAt(currentPos);
}
char nextChar() throws IndexOutOfBoundsException {
int pos = currentPos;
char c = oaString.charAt(pos);
if (c == '\\') {
pos++;
}
return oaString.charAt(pos + 1);
}
void appendCurrentEscapedChar() throws IndexOutOfBoundsException {
char c = oaString.charAt(currentPos);
if (c == '\\') {
c = oaString.charAt(++currentPos);
}
sb.append(c);
}
}
}

View File

@ -29,18 +29,6 @@ public class RestoreHeight {
static final int DIFFICULTY_TARGET = 120; // seconds
static private RestoreHeight Singleton = null;
static public RestoreHeight getInstance() {
if (Singleton == null) {
synchronized (RestoreHeight.class) {
if (Singleton == null) {
Singleton = new RestoreHeight();
}
}
}
return Singleton;
}
private final Map<String, Long> blockheight = new HashMap<>();
RestoreHeight() {
@ -141,6 +129,17 @@ public class RestoreHeight {
blockheight.put("2022-03-01", 2569711L);
}
static public RestoreHeight getInstance() {
if (Singleton == null) {
synchronized (RestoreHeight.class) {
if (Singleton == null) {
Singleton = new RestoreHeight();
}
}
}
return Singleton;
}
public long getHeight(String date) {
SimpleDateFormat parser = new SimpleDateFormat("yyyy-MM-dd");
parser.setTimeZone(TimeZone.getTimeZone("UTC"));

View File

@ -1,64 +0,0 @@
package com.m2049r.xmrwallet.util;
import android.content.Context;
import android.net.Uri;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
public class ZipBackup {
final private Context context;
final private String walletName;
private ZipOutputStream zip;
public void writeTo(Uri zipUri) throws IOException {
if (zip != null)
throw new IllegalStateException("zip already initialized");
try {
zip = new ZipOutputStream(context.getContentResolver().openOutputStream(zipUri));
final File walletRoot = Helper.getWalletRoot(context);
addFile(new File(walletRoot, walletName + ".keys"));
addFile(new File(walletRoot, walletName));
zip.close();
} finally {
if (zip != null) zip.close();
}
}
private void addFile(File file) throws IOException {
if (!file.exists()) return; // ignore missing files (e.g. the cache file might not exist)
ZipEntry entry = new ZipEntry(file.getName());
zip.putNextEntry(entry);
writeFile(file);
zip.closeEntry();
}
private void writeFile(File source) throws IOException {
try (InputStream is = new FileInputStream(source)) {
byte[] buffer = new byte[8192];
int length;
while ((length = is.read(buffer)) > 0) {
zip.write(buffer, 0, length);
}
}
}
private static final SimpleDateFormat DATETIME_FORMATTER =
new SimpleDateFormat("yyyy-MM-dd'T'HH-mm-ss");
public String getBackupName() {
return walletName + " " + DATETIME_FORMATTER.format(new Date());
}
}

View File

@ -1,139 +0,0 @@
package com.m2049r.xmrwallet.util;
import android.content.Context;
import android.net.Uri;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import lombok.RequiredArgsConstructor;
import timber.log.Timber;
@RequiredArgsConstructor
public class ZipRestore {
final private Context context;
final private Uri zipUri;
private File walletRoot;
private ZipInputStream zip;
public boolean restore() throws IOException {
walletRoot = Helper.getWalletRoot(context);
String walletName = testArchive();
if (walletName == null) return false;
walletName = getUniqueName(walletName);
if (zip != null)
throw new IllegalStateException("zip already initialized");
try {
zip = new ZipInputStream(context.getContentResolver().openInputStream(zipUri));
for (ZipEntry entry = zip.getNextEntry(); entry != null; zip.closeEntry(), entry = zip.getNextEntry()) {
File destination;
final String name = entry.getName();
if (name.endsWith(".keys")) {
destination = new File(walletRoot, walletName + ".keys");
} else if (name.endsWith(".address.txt")) {
destination = new File(walletRoot, walletName + ".address.txt");
} else {
destination = new File(walletRoot, walletName);
}
writeFile(destination);
}
} finally {
if (zip != null) zip.close();
}
return true;
}
private void writeFile(File destination) throws IOException {
try (OutputStream os = new FileOutputStream(destination)) {
byte[] buffer = new byte[8192];
int length;
while ((length = zip.read(buffer)) > 0) {
os.write(buffer, 0, length);
}
}
}
// test the archive to contain files we expect & return the name of the contained wallet or null
private String testArchive() {
String walletName = null;
boolean keys = false;
ZipInputStream zipStream = null;
try {
zipStream = new ZipInputStream(context.getContentResolver().openInputStream(zipUri));
for (ZipEntry entry = zipStream.getNextEntry(); entry != null;
zipStream.closeEntry(), entry = zipStream.getNextEntry()) {
if (entry.isDirectory())
return null;
final String name = entry.getName();
if ((new File(name)).getParentFile() != null)
return null;
if (walletName == null) {
if (name.endsWith(".keys")) {
walletName = name.substring(0, name.length() - ".keys".length());
keys = true; // we have they keys
} else if (name.endsWith(".address.txt")) {
walletName = name.substring(0, name.length() - ".address.txt".length());
} else {
walletName = name;
}
} else { // we have a wallet name
if (name.endsWith(".keys")) {
if (!name.equals(walletName + ".keys")) return null;
keys = true; // we have they keys
} else if (name.endsWith(".address.txt")) {
if (!name.equals(walletName + ".address.txt")) return null;
} else if (!name.equals(walletName)) return null;
}
}
} catch (IOException ex) {
return null;
} finally {
try {
if (zipStream != null) zipStream.close();
} catch (IOException ex) {
Timber.w(ex);
}
}
// we need the keys at least
if (keys) return walletName;
else return null;
}
final static Pattern WALLET_PATTERN = Pattern.compile("^(.+) \\(([0-9]+)\\).keys$");
private String getUniqueName(String name) {
if (!(new File(walletRoot, name + ".keys")).exists()) // <name> does not exist => it's ok to use
return name;
File[] wallets = walletRoot.listFiles(
(dir, filename) -> {
Matcher m = WALLET_PATTERN.matcher(filename);
if (m.find())
return m.group(1).equals(name);
else return false;
});
if (wallets.length == 0) return name + " (1)";
int maxIndex = 0;
for (File wallet : wallets) {
try {
final Matcher m = WALLET_PATTERN.matcher(wallet.getName());
m.find();
final int index = Integer.parseInt(m.group(2));
if (index > maxIndex) maxIndex = index;
} catch (NumberFormatException ex) {
// this cannot happen & we can ignore it if it does
}
}
return name + " (" + (maxIndex + 1) + ")";
}
}

View File

@ -1,81 +0,0 @@
/*
* Based on
* https://stackoverflow.com/a/19943894
*
* Curve parameters from
* https://en.bitcoin.it/wiki/Secp256k1
*
* Copyright (c) 2019 m2049r
* Copyright (c) 2013 ChiaraHsieh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.xmrwallet.util.ledger;
import java.math.BigInteger;
import java.security.spec.ECPoint;
public class ECsecp256k1 {
static private final BigInteger TWO = new BigInteger("2");
static private final BigInteger THREE = new BigInteger("3");
static public final BigInteger p = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", 16);
static public final BigInteger a = new BigInteger("0000000000000000000000000000000000000000000000000000000000000000", 16);
static public final BigInteger b = new BigInteger("0000000000000000000000000000000000000000000000000000000000000007", 16);
static public final BigInteger n = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 16);
static public final ECPoint G = new ECPoint(
new BigInteger("79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798", 16),
new BigInteger("483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8", 16));
public static ECPoint scalmult(BigInteger kin, ECPoint P) {
ECPoint R = ECPoint.POINT_INFINITY, S = P;
BigInteger k = kin.mod(n); // not necessary b/c that's how curves work
int length = k.bitLength();
byte[] binarray = new byte[length];
for (int i = 0; i <= length - 1; i++) {
binarray[i] = k.mod(TWO).byteValue();
k = k.divide(TWO);
}
for (int i = length - 1; i >= 0; i--) {
// i should start at length-1 not -2 because the MSB of binary may not be 1
R = doublePoint(R);
if (binarray[i] == 1)
R = addPoint(R, S);
}
return R;
}
public static ECPoint addPoint(ECPoint r, ECPoint s) {
if (r.equals(s))
return doublePoint(r);
else if (r.equals(ECPoint.POINT_INFINITY))
return s;
else if (s.equals(ECPoint.POINT_INFINITY))
return r;
BigInteger slope = (r.getAffineY().subtract(s.getAffineY()))
.multiply(r.getAffineX().subtract(s.getAffineX()).modInverse(p));
BigInteger Xout = (slope.modPow(TWO, p).subtract(r.getAffineX())).subtract(s.getAffineX()).mod(p);
BigInteger Yout = s.getAffineY().negate().add(slope.multiply(s.getAffineX().subtract(Xout))).mod(p);
return new ECPoint(Xout, Yout);
}
public static ECPoint doublePoint(ECPoint r) {
if (r.equals(ECPoint.POINT_INFINITY))
return r;
BigInteger slope = (r.getAffineX().pow(2)).multiply(THREE).add(a)
.multiply((r.getAffineY().multiply(TWO)).modInverse(p));
BigInteger Xout = slope.pow(2).subtract(r.getAffineX().multiply(TWO)).mod(p);
BigInteger Yout = (r.getAffineY().negate()).add(slope.multiply(r.getAffineX().subtract(Xout))).mod(p);
return new ECPoint(Xout, Yout);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,51 +0,0 @@
package com.m2049r.xmrwallet.util.validator;
import lombok.Getter;
public enum BitcoinAddressType {
BTC(Type.BTC, Type.BTC_BECH32_PREFIX),
LTC(Type.LTC, Type.LTC_BECH32_PREFIX),
DASH(Type.DASH, null),
DOGE(Type.DOGE, null);
@Getter
private final byte[] production;
@Getter
private final byte[] testnet;
@Getter
private final String productionBech32Prefix;
@Getter
private final String testnetBech32Prefix;
public boolean hasBech32() {
return productionBech32Prefix != null;
}
public String getBech32Prefix(boolean testnet) {
return testnet ? testnetBech32Prefix : productionBech32Prefix;
}
BitcoinAddressType(byte[][] types, String[] bech32Prefix) {
production = types[0];
testnet = types[1];
if (bech32Prefix != null) {
productionBech32Prefix = bech32Prefix[0];
testnetBech32Prefix = bech32Prefix[1];
} else {
productionBech32Prefix = null;
testnetBech32Prefix = null;
}
}
// Java is silly and doesn't allow array initializers in the construction
private static class Type {
private static final byte[][] BTC = {{0x00, 0x05}, {0x6f, (byte) 0xc4}};
private static final String[] BTC_BECH32_PREFIX = {"bc", "tb"};
private static final byte[][] LTC = {{0x30, 0x05, 0x32}, {0x6f, (byte) 0xc4, 0x3a}};
private static final String[] LTC_BECH32_PREFIX = {"ltc", "tltc"};
private static final byte[][] DASH = {{0x4c, 0x10}, {(byte) 0x8c, 0x13}};
private static final byte[][] DOGE = {{0x1e, 0x16}, {0x71, (byte) 0xc4}};
}
}

View File

@ -1,218 +0,0 @@
/*
* Copyright (c) 2017 m2049r er al.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.xmrwallet.util.validator;
// mostly based on https://rosettacode.org/wiki/Bitcoin/address_validation#Java
import com.m2049r.xmrwallet.data.Crypto;
import com.m2049r.xmrwallet.model.NetworkType;
import com.m2049r.xmrwallet.model.WalletManager;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
public class BitcoinAddressValidator {
private static final String ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
public static Crypto validate(String address) {
for (BitcoinAddressType type : BitcoinAddressType.values()) {
if (validate(address, type))
return Crypto.valueOf(type.name());
}
return null;
}
// just for tests
public static boolean validateBTC(String addrress, boolean testnet) {
return validate(addrress, BitcoinAddressType.BTC, testnet);
}
public static boolean validate(String addrress, BitcoinAddressType type, boolean testnet) {
if (validate(addrress, testnet ? type.getTestnet() : type.getProduction()))
return true;
if (type.hasBech32())
return validateBech32Segwit(addrress, type, testnet);
else
return false;
}
public static boolean validate(String addrress, BitcoinAddressType type) {
final boolean testnet = WalletManager.getInstance().getNetworkType() != NetworkType.NetworkType_Mainnet;
return validate(addrress, type, testnet);
}
public static boolean validate(String addrress, byte[] addressTypes) {
if (addrress.length() < 26 || addrress.length() > 35)
return false;
byte[] decoded = decodeBase58To25Bytes(addrress);
if (decoded == null)
return false;
int v = decoded[0] & 0xFF;
boolean nok = true;
for (byte b : addressTypes) {
nok = nok && (v != (b & 0xFF));
}
if (nok) return false;
byte[] hash1 = sha256(Arrays.copyOfRange(decoded, 0, 21));
byte[] hash2 = sha256(hash1);
return Arrays.equals(Arrays.copyOfRange(hash2, 0, 4), Arrays.copyOfRange(decoded, 21, 25));
}
private static byte[] decodeBase58To25Bytes(String input) {
BigInteger num = BigInteger.ZERO;
for (char t : input.toCharArray()) {
int p = ALPHABET.indexOf(t);
if (p == -1)
return null;
num = num.multiply(BigInteger.valueOf(58)).add(BigInteger.valueOf(p));
}
byte[] result = new byte[25];
byte[] numBytes = num.toByteArray();
if (num.bitLength() > 200) return null;
if (num.bitLength() == 200) {
System.arraycopy(numBytes, 1, result, 0, 25);
} else {
System.arraycopy(numBytes, 0, result, result.length - numBytes.length, numBytes.length);
}
return result;
}
private static byte[] sha256(byte[] data) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(data);
return md.digest();
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException(e);
}
}
//
// validate Bech32 segwit
// see https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki for spec
//
private static final String DATA_CHARS = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
public static boolean validateBech32Segwit(String bech32, BitcoinAddressType type, boolean testnet) {
if (!bech32.equals(bech32.toLowerCase()) && !bech32.equals(bech32.toUpperCase())) {
return false; // mixing upper and lower case not allowed
}
bech32 = bech32.toLowerCase();
if (!bech32.startsWith(type.getBech32Prefix(testnet))) return false;
final int hrpLength = type.getBech32Prefix(testnet).length();
if ((bech32.length() < (12 + hrpLength)) || (bech32.length() > (72 + hrpLength)))
return false;
int mod = (bech32.length() - hrpLength) % 8;
if ((mod == 6) || (mod == 1) || (mod == 3)) return false;
int sep = -1;
final byte[] bytes = bech32.getBytes(StandardCharsets.US_ASCII);
for (int i = 0; i < bytes.length; i++) {
if ((bytes[i] < 33) || (bytes[i] > 126)) {
return false;
}
if (bytes[i] == 49) sep = i; // 49 := '1' in ASCII
}
if (sep != hrpLength) return false;
if (sep > bytes.length - 7) {
return false; // min 6 bytes data
}
if (bytes.length < 8) { // hrp{min}=1 + sep=1 + data{min}=6 := 8
return false; // too short
}
if (bytes.length > 90) {
return false; // too long
}
final byte[] hrp = Arrays.copyOfRange(bytes, 0, sep);
final byte[] data = Arrays.copyOfRange(bytes, sep + 1, bytes.length);
for (int i = 0; i < data.length; i++) {
int b = DATA_CHARS.indexOf(data[i]);
if (b < 0) return false; // invalid character
data[i] = (byte) b;
}
if (!validateBech32Data(data)) return false;
return verifyChecksum(hrp, data);
}
private static int polymod(byte[] values) {
final int[] GEN = {0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3};
int chk = 1;
for (byte v : values) {
byte b = (byte) (chk >> 25);
chk = ((chk & 0x1ffffff) << 5) ^ v;
for (int i = 0; i < 5; i++) {
chk ^= ((b >> i) & 1) == 1 ? GEN[i] : 0;
}
}
return chk;
}
private static byte[] hrpExpand(byte[] hrp) {
final byte[] expanded = new byte[(2 * hrp.length) + 1];
int i = 0;
for (byte b : hrp) {
expanded[i++] = (byte) (b >> 5);
}
expanded[i++] = 0;
for (byte b : hrp) {
expanded[i++] = (byte) (b & 0x1f);
}
return expanded;
}
private static boolean verifyChecksum(byte[] hrp, byte[] data) {
final byte[] hrpExpanded = hrpExpand(hrp);
final byte[] values = new byte[hrpExpanded.length + data.length];
System.arraycopy(hrpExpanded, 0, values, 0, hrpExpanded.length);
System.arraycopy(data, 0, values, hrpExpanded.length, data.length);
return (polymod(values) == 1);
}
private static boolean validateBech32Data(final byte[] data) {
if ((data[0] < 0) || (data[0] > 16)) return false; // witness version
final int programLength = data.length - 1 - 6; // 1-byte version at beginning & 6-byte checksum at end
// since we are coming from our own decoder, we don't need to verify data is 5-bit bytes
final int convertedSize = programLength * 5 / 8;
final int remainderSize = programLength * 5 % 8;
if ((convertedSize < 2) || (convertedSize > 40)) return false;
if ((data[0] == 0) && (convertedSize != 20) && (convertedSize != 32)) return false;
if (remainderSize >= 5) return false;
// ignore checksum at end and get last byte of program
return (data[data.length - 1 - 6] & ((1 << remainderSize) - 1)) == 0;
}
}

View File

@ -1,64 +0,0 @@
/*
* Copyright (c) 2017 m2049r er al.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.xmrwallet.util.validator;
// mostly based on https://github.com/ognus/wallet-address-validator/blob/master/src/ethereum_validator.js
import com.theromus.sha.Keccak;
import com.theromus.sha.Parameters;
import java.nio.charset.StandardCharsets;
import java.util.regex.Pattern;
public class EthAddressValidator {
static private final Pattern ETH_ADDRESS = Pattern.compile("^0x[0-9a-fA-F]{40}$");
static private final Pattern ETH_ALLLOWER = Pattern.compile("^0x[0-9a-f]{40}$");
static private final Pattern ETH_ALLUPPER = Pattern.compile("^0x[0-9A-F]{40}$");
public static boolean validate(String address) {
// Check if it has the basic requirements of an address
if (!ETH_ADDRESS.matcher(address).matches())
return false;
// If it's all small caps or all all caps, return true
if (ETH_ALLLOWER.matcher(address).matches() || ETH_ALLUPPER.matcher(address).matches()) {
return true;
}
// Otherwise check each case
return validateChecksum(address);
}
private static boolean validateChecksum(String address) {
// Check each case
address = address.substring(2); // strip 0x
Keccak keccak = new Keccak();
final byte[] addressHash = keccak.getHash(
address.toLowerCase().getBytes(StandardCharsets.US_ASCII),
Parameters.KECCAK_256);
for (int i = 0; i < 40; i++) {
boolean upper = (addressHash[i / 2] & ((i % 2) == 0 ? 128 : 8)) != 0;
char c = address.charAt(i);
if (Character.isAlphabetic(c)) {
if (Character.isUpperCase(c) && !upper) return false;
if (Character.isLowerCase(c) && upper) return false;
}
}
return true;
}
}

View File

@ -1,170 +0,0 @@
package com.theromus.sha;
import static com.theromus.utils.HexUtils.leftRotate64;
import static com.theromus.utils.HexUtils.convertToUint;
import static com.theromus.utils.HexUtils.convertFromLittleEndianTo64;
import static com.theromus.utils.HexUtils.convertFrom64ToLittleEndian;
import static java.lang.Math.min;
import static java.lang.System.arraycopy;
import static java.util.Arrays.fill;
import java.io.ByteArrayOutputStream;
import java.math.BigInteger;
/**
* Keccak implementation.
*
* @author romus
*/
public class Keccak {
private static BigInteger BIT_64 = new BigInteger("18446744073709551615");
/**
* Do hash.
*
* @param message input data
* @param parameter keccak param
* @return byte-array result
*/
public byte[] getHash(final byte[] message, final Parameters parameter) {
int[] uState = new int[200];
int[] uMessage = convertToUint(message);
int rateInBytes = parameter.getRate() / 8;
int blockSize = 0;
int inputOffset = 0;
// Absorbing phase
while (inputOffset < uMessage.length) {
blockSize = min(uMessage.length - inputOffset, rateInBytes);
for (int i = 0; i < blockSize; i++) {
uState[i] = uState[i] ^ uMessage[i + inputOffset];
}
inputOffset = inputOffset + blockSize;
if (blockSize == rateInBytes) {
doKeccakf(uState);
blockSize = 0;
}
}
// Padding phase
uState[blockSize] = uState[blockSize] ^ parameter.getD();
if ((parameter.getD() & 0x80) != 0 && blockSize == (rateInBytes - 1)) {
doKeccakf(uState);
}
uState[rateInBytes - 1] = uState[rateInBytes - 1] ^ 0x80;
doKeccakf(uState);
// Squeezing phase
ByteArrayOutputStream byteResults = new ByteArrayOutputStream();
int tOutputLen = parameter.getOutputLen() / 8;
while (tOutputLen > 0) {
blockSize = min(tOutputLen, rateInBytes);
for (int i = 0; i < blockSize; i++) {
byteResults.write((byte) uState[i]);
}
tOutputLen -= blockSize;
if (tOutputLen > 0) {
doKeccakf(uState);
}
}
return byteResults.toByteArray();
}
private void doKeccakf(final int[] uState) {
BigInteger[][] lState = new BigInteger[5][5];
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 5; j++) {
int[] data = new int[8];
arraycopy(uState, 8 * (i + 5 * j), data, 0, data.length);
lState[i][j] = convertFromLittleEndianTo64(data);
}
}
roundB(lState);
fill(uState, 0);
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 5; j++) {
int[] data = convertFrom64ToLittleEndian(lState[i][j]);
arraycopy(data, 0, uState, 8 * (i + 5 * j), data.length);
}
}
}
/**
* Permutation on the given state.
*
* @param state state
*/
private void roundB(final BigInteger[][] state) {
int LFSRstate = 1;
for (int round = 0; round < 24; round++) {
BigInteger[] C = new BigInteger[5];
BigInteger[] D = new BigInteger[5];
// θ step
for (int i = 0; i < 5; i++) {
C[i] = state[i][0].xor(state[i][1]).xor(state[i][2]).xor(state[i][3]).xor(state[i][4]);
}
for (int i = 0; i < 5; i++) {
D[i] = C[(i + 4) % 5].xor(leftRotate64(C[(i + 1) % 5], 1));
}
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 5; j++) {
state[i][j] = state[i][j].xor(D[i]);
}
}
//ρ and π steps
int x = 1, y = 0;
BigInteger current = state[x][y];
for (int i = 0; i < 24; i++) {
int tX = x;
x = y;
y = (2 * tX + 3 * y) % 5;
BigInteger shiftValue = current;
current = state[x][y];
state[x][y] = leftRotate64(shiftValue, (i + 1) * (i + 2) / 2);
}
//χ step
for (int j = 0; j < 5; j++) {
BigInteger[] t = new BigInteger[5];
for (int i = 0; i < 5; i++) {
t[i] = state[i][j];
}
for (int i = 0; i < 5; i++) {
// ~t[(i + 1) % 5]
BigInteger invertVal = t[(i + 1) % 5].xor(BIT_64);
// t[i] ^ ((~t[(i + 1) % 5]) & t[(i + 2) % 5])
state[i][j] = t[i].xor(invertVal.and(t[(i + 2) % 5]));
}
}
//ι step
for (int i = 0; i < 7; i++) {
LFSRstate = ((LFSRstate << 1) ^ ((LFSRstate >> 7) * 0x71)) % 256;
// pow(2, i) - 1
int bitPosition = (1 << i) - 1;
if ((LFSRstate & 2) != 0) {
state[0][0] = state[0][0].xor(new BigInteger("1").shiftLeft(bitPosition));
}
}
}
}
}

View File

@ -1,51 +0,0 @@
package com.theromus.sha;
/**
* The parameters defining the standard FIPS 202.
*
* @author romus
*/
public enum Parameters {
KECCAK_224 (1152, 0x01, 224),
KECCAK_256 (1088, 0x01, 256),
KECCAK_384 (832, 0x01, 384),
KECCAK_512 (576, 0x01, 512),
SHA3_224 (1152, 0x06, 224),
SHA3_256 (1088, 0x06, 256),
SHA3_384 (832, 0x06, 384),
SHA3_512 (576, 0x06, 512),
SHAKE128 (1344, 0x1F, 256),
SHAKE256 (1088, 0x1F, 512);
private final int rate;
/**
* Delimited suffix.
*/
public final int d;
/**
* Output length (bits).
*/
public final int outputLen;
Parameters(int rate, int d, int outputLen) {
this.rate = rate;
this.d = d;
this.outputLen = outputLen;
}
public int getRate() {
return rate;
}
public int getD() {
return d;
}
public int getOutputLen() {
return outputLen;
}
}

View File

@ -1,97 +0,0 @@
package com.theromus.utils;
import java.io.ByteArrayOutputStream;
import java.math.BigInteger;
/**
* Hex-utils.
*
* @author romus
*/
public class HexUtils {
private static final byte[] ENCODE_BYTE_TABLE = {
(byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', (byte) '6', (byte) '7',
(byte) '8', (byte) '9', (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f'
};
/**
* Convert byte array to unsigned array.
*
* @param data byte array
* @return unsigned array
*/
public static int[] convertToUint(final byte[] data) {
int[] converted = new int[data.length];
for (int i = 0; i < data.length; i++) {
converted[i] = data[i] & 0xFF;
}
return converted;
}
/**
* Convert LE to 64-bit value (unsigned long).
*
* @param data data
* @return 64-bit value (unsigned long)
*/
public static BigInteger convertFromLittleEndianTo64(final int[] data) {
BigInteger uLong = new BigInteger("0");
for (int i = 0; i < 8; i++) {
uLong = uLong.add(new BigInteger(Integer.toString(data[i])).shiftLeft(8 * i));
}
return uLong;
}
/**
* Convert 64-bit (unsigned long) value to LE.
*
* @param uLong 64-bit value (unsigned long)
* @return LE
*/
public static int[] convertFrom64ToLittleEndian(final BigInteger uLong) {
int[] data = new int[8];
BigInteger mod256 = new BigInteger("256");
for (int i = 0; i < 8; i++) {
data[i] = uLong.shiftRight((8 * i)).mod(mod256).intValue();
}
return data;
}
/**
* Bitwise rotate left.
*
* @param value unsigned long value
* @param rotate rotate left
* @return result
*/
public static BigInteger leftRotate64(final BigInteger value, final int rotate) {
BigInteger lp = value.shiftRight(64 - (rotate % 64));
BigInteger rp = value.shiftLeft(rotate % 64);
return lp.add(rp).mod(new BigInteger("18446744073709551616"));
}
/**
* Convert bytes to string.
*
* @param data bytes array
* @return string
*/
public static String convertBytesToString(final byte[] data) {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
for (int i = 0; i < data.length; i++) {
int uVal = data[i] & 0xFF;
buffer.write(ENCODE_BYTE_TABLE[(uVal >>> 4)]);
buffer.write(ENCODE_BYTE_TABLE[uVal & 0xF]);
}
return new String(buffer.toByteArray());
}
}

View File

@ -1,115 +0,0 @@
/*
* Copyright 2012-2016 Nathan Freitas
* Copyright 2015 str4d
* Portions Copyright (c) 2016 CommonsWare, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package info.guardianproject.netcipher.client;
import android.content.Context;
import android.content.Intent;
import javax.net.ssl.SSLSocketFactory;
import okhttp3.OkHttpClient;
import okhttp3.Request;
/**
* Creates an OkHttpClient using NetCipher configuration. Use
* build() if you have no other OkHttpClient configuration
* that you need to perform. Or, use applyTo() to augment an
* existing OkHttpClient.Builder with NetCipher.
*/
public class StrongOkHttpClientBuilder extends
StrongBuilderBase<StrongOkHttpClientBuilder, OkHttpClient> {
/**
* Creates a StrongOkHttpClientBuilder using the strongest set
* of options for security. Use this if the strongest set of
* options is what you want; otherwise, create a
* builder via the constructor and configure it as you see fit.
*
* @param context any Context will do
* @return a configured StrongOkHttpClientBuilder
* @throws Exception
*/
static public StrongOkHttpClientBuilder forMaxSecurity(Context context)
throws Exception {
return(new StrongOkHttpClientBuilder(context)
.withBestProxy());
}
/**
* Creates a builder instance.
*
* @param context any Context will do; builder will hold onto
* Application context
*/
public StrongOkHttpClientBuilder(Context context) {
super(context);
}
/**
* Copy constructor.
*
* @param original builder to clone
*/
public StrongOkHttpClientBuilder(StrongOkHttpClientBuilder original) {
super(original);
}
/**
* OkHttp3 does not support SOCKS proxies:
* https://github.com/square/okhttp/issues/2315
*
* @return false
*/
@Override
public boolean supportsSocksProxy() {
return(true);
}
/**
* {@inheritDoc}
*/
@Override
public OkHttpClient build(Intent status) {
return(applyTo(new OkHttpClient.Builder(), status).build());
}
/**
* Adds NetCipher configuration to an existing OkHttpClient.Builder,
* in case you have additional configuration that you wish to
* perform.
*
* @param builder a new or partially-configured OkHttpClient.Builder
* @return the same builder
*/
public OkHttpClient.Builder applyTo(OkHttpClient.Builder builder, Intent status) {
SSLSocketFactory factory=buildSocketFactory();
if (factory!=null) {
builder.sslSocketFactory(factory);
}
return(builder
.proxy(buildProxy(status)));
}
@Override
protected String get(Intent status, OkHttpClient connection,
String url) throws Exception {
Request request=new Request.Builder().url(url).build();
return(connection.newCall(request).execute().body().string());
}
}

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Color when the row is selected -->
<item android:state_enabled="true" android:drawable="@drawable/button_bg_enabled" />
<item android:drawable="@drawable/button_bg_enabled" android:state_enabled="true" />
<!-- Standard background color -->
<item android:drawable="@drawable/button_bg_disabled" />
</selector>

View File

@ -4,8 +4,6 @@
<padding
android:left="8dp"
android:right="8dp" />
<solid
android:color="@color/button_disabled_bg_color"/>
<corners
android:radius="8dp"/>
<solid android:color="@color/button_disabled_bg_color" />
<corners android:radius="8dp" />
</shape>

View File

@ -4,8 +4,6 @@
<padding
android:left="8dp"
android:right="8dp" />
<solid
android:color="@color/oled_colorSecondary"/>
<corners
android:radius="8dp"/>
<solid android:color="@color/oled_colorSecondary" />
<corners android:radius="8dp" />
</shape>

View File

@ -6,8 +6,6 @@
android:left="12dp"
android:right="12dp"
android:top="12dp" />
<solid
android:color="@color/edittext_bg_color"/>
<corners
android:radius="8dp"/>
<solid android:color="@color/edittext_bg_color" />
<corners android:radius="8dp" />
</shape>

View File

@ -1,17 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="oval" android:padding="10dp">
<shape
android:padding="10dp"
android:shape="oval">
<padding
android:top="2dp"
android:bottom="2dp"
android:left="2dp"
android:right="2dp"/>
android:right="2dp"
android:top="2dp" />
<stroke
android:color="#fff"
android:width="1dp"/>
<solid
android:width="1dp"
android:color="#fff" />
<solid android:color="#fff" />
</shape>
</item>
<item android:drawable="@drawable/ic_monero" />

View File

@ -2,17 +2,14 @@
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/background">
<shape>
<solid
android:color="@android:color/white" />
<solid android:color="@android:color/white" />
</shape>
</item>
<item
android:id="@android:id/progress">
<item android:id="@android:id/progress">
<clip>
<shape>
<solid
android:color="@color/oled_colorSecondary" />
<solid android:color="@color/oled_colorSecondary" />
</shape>
</clip>
</item>

View File

@ -1,19 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:defaultNavHost="true"
app:navGraph="@navigation/main_nav" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -23,14 +23,14 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginEnd="24dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="24dp"
android:textSize="24sp"
android:textStyle="bold"
tools:text="100.000000000000 XMR"
app:layout_constraintEnd_toStartOf="@id/settings_imageview"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
app:layout_constraintTop_toTopOf="parent"
tools:text="100.000000000000 XMR" />
<TextView
android:id="@+id/balance_locked_textview"
@ -38,79 +38,80 @@
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginEnd="24dp"
tools:text="+ 100.000000000000 confirming"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/settings_imageview" />
app:layout_constraintTop_toBottomOf="@id/settings_imageview"
tools:text="+ 100.000000000000 confirming" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/transaction_history_recyclerview"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="16dp"
android:clipToPadding="false"
android:paddingStart="24dp"
android:paddingEnd="24dp"
android:paddingBottom="128dp"
android:clipToPadding="false"
app:layout_constraintTop_toBottomOf="@id/balance_locked_textview"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/balance_locked_textview"
app:layout_constraintVertical_bias="0.0" />
<ImageView
android:layout_width="match_parent"
android:layout_height="0dp"
android:src="@drawable/gradient_bottom"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/receive_send_buttons_layout"
app:layout_constraintBottom_toBottomOf="parent"/>
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/receive_send_buttons_layout" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/receive_send_buttons_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="24dp"
android:paddingTop="128dp"
android:paddingEnd="24dp"
android:paddingBottom="16dp"
android:paddingTop="128dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<Button
android:id="@+id/receive_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="@drawable/button_bg"
android:layout_marginEnd="4dp"
android:background="@drawable/button_bg"
android:text="@string/receive"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/send_button"
app:layout_constraintBottom_toBottomOf="parent"/>
app:layout_constraintStart_toStartOf="parent" />
<Button
android:id="@+id/send_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="@drawable/button_bg"
android:layout_marginStart="4dp"
android:background="@drawable/button_bg"
android:text="@string/send"
app:layout_constraintStart_toEndOf="@id/receive_button"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
app:layout_constraintStart_toEndOf="@id/receive_button" />
</androidx.constraintlayout.widget.ConstraintLayout>
<ImageView
android:id="@+id/settings_imageview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_settings"
android:layout_marginEnd="24dp"
android:padding="8dp"
android:minHeight="24dp"
android:minWidth="24dp"
app:layout_constraintTop_toTopOf="@id/balance_unlocked_textview"
android:minHeight="24dp"
android:padding="8dp"
android:src="@drawable/ic_settings"
app:layout_constraintBottom_toBottomOf="@id/balance_unlocked_textview"
app:layout_constraintEnd_toEndOf="parent"/>
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/balance_unlocked_textview" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -4,20 +4,22 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".fragment.settings.SettingsFragment"
android:padding="24dp">
android:padding="24dp"
tools:context=".fragment.settings.SettingsFragment">
<TextView
android:id="@+id/create_wallet_textview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/create_wallet"
android:layout_marginBottom="32dp"
android:text="@string/create_wallet"
android:textSize="32sp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toTopOf="@id/wallet_password_edittext"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/wallet_password_edittext"/>
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/wallet_password_edittext"
android:layout_width="0dp"
@ -26,31 +28,34 @@
android:background="@drawable/edittext_bg"
android:hint="@string/password_optional"
android:inputType="textPassword"
app:layout_constraintTop_toBottomOf="@id/create_wallet_textview"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@id/advanced_settings_dropdown_textview"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/create_wallet_textview"
tools:visibility="visible" />
<TextView
android:id="@+id/advanced_settings_dropdown_textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/more_options"
android:layout_marginBottom="8dp"
android:text="@string/more_options"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@id/wallet_seed_edittext"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/wallet_password_edittext"
app:layout_constraintBottom_toTopOf="@id/wallet_seed_edittext"/>
app:layout_constraintTop_toBottomOf="@id/wallet_password_edittext" />
<ImageView
android:id="@+id/advanced_settings_chevron_imageview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_keyboard_arrow_down"
app:layout_constraintBottom_toBottomOf="@id/advanced_settings_dropdown_textview"
app:layout_constraintStart_toEndOf="@id/advanced_settings_dropdown_textview"
app:layout_constraintTop_toTopOf="@id/advanced_settings_dropdown_textview"
app:layout_constraintBottom_toBottomOf="@id/advanced_settings_dropdown_textview"/>
app:layout_constraintTop_toTopOf="@id/advanced_settings_dropdown_textview" />
<EditText
android:id="@+id/wallet_seed_edittext"
android:layout_width="0dp"
@ -59,24 +64,26 @@
android:background="@drawable/edittext_bg"
android:hint="@string/recovery_phrase_optional"
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@id/advanced_settings_dropdown_textview"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@id/wallet_restore_height_edittext"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/advanced_settings_dropdown_textview"
tools:visibility="visible" />
<EditText
android:id="@+id/wallet_restore_height_edittext"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:hint="@string/restore_height_optional"
android:background="@drawable/edittext_bg"
android:hint="@string/restore_height_optional"
android:inputType="number"
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@id/wallet_seed_edittext"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@id/create_wallet_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/wallet_seed_edittext"
tools:visibility="visible" />
<Button
android:id="@+id/create_wallet_button"
android:layout_width="match_parent"
@ -85,6 +92,6 @@
android:background="@drawable/button_bg"
android:text="@string/create_wallet"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/wallet_restore_height_edittext"
app:layout_constraintStart_toStartOf="parent"/>
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/wallet_restore_height_edittext" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -4,8 +4,9 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".fragment.settings.SettingsFragment"
android:padding="24dp">
android:padding="24dp"
tools:context=".fragment.settings.SettingsFragment">
<TextView
android:id="@+id/settings_textview"
android:layout_width="match_parent"
@ -13,49 +14,49 @@
android:text="@string/settings"
android:textSize="32sp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toTopOf="@id/wallet_settings_textview"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/wallet_settings_textview"/>
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/wallet_settings_textview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:text="@string/wallet"
android:textSize="24sp"
android:textStyle="bold"
android:layout_marginTop="32dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toTopOf="@id/display_seed_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/settings_textview"
app:layout_constraintBottom_toTopOf="@id/display_seed_button"/>
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/settings_textview" />
<Button
android:id="@+id/display_seed_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/display_recovery_phrase"
android:layout_marginTop="16dp"
android:background="@drawable/button_bg"
android:text="@string/display_recovery_phrase"
app:layout_constraintBottom_toTopOf="@id/appearance_settings_textview"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@id/appearance_settings_textview"
app:layout_constraintTop_toBottomOf="@id/wallet_settings_textview" />
<TextView
android:id="@+id/appearance_settings_textview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:text="@string/appearance"
android:textSize="24sp"
android:textStyle="bold"
android:layout_marginTop="32dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toTopOf="@id/day_night_switch"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/display_seed_button"
app:layout_constraintBottom_toTopOf="@id/day_night_switch"/>
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/display_seed_button" />
<TextView
android:id="@+id/day_night_textview"
@ -63,10 +64,10 @@
android:layout_height="wrap_content"
android:text="@string/night_mode"
android:textSize="16sp"
app:layout_constraintTop_toTopOf="@id/day_night_switch"
app:layout_constraintBottom_toBottomOf="@id/day_night_switch"
app:layout_constraintEnd_toStartOf="@id/day_night_switch"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/day_night_switch"/>
app:layout_constraintTop_toTopOf="@id/day_night_switch" />
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/day_night_switch"
@ -74,21 +75,21 @@
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:layout_constraintBottom_toTopOf="@id/network_settings_textview"
app:layout_constraintTop_toBottomOf="@id/appearance_settings_textview"
app:layout_constraintEnd_toEndOf="parent"/>
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/appearance_settings_textview" />
<TextView
android:id="@+id/network_settings_textview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:text="@string/network"
android:textSize="24sp"
android:textStyle="bold"
android:layout_marginTop="32dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toTopOf="@id/tor_switch"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/day_night_switch"
app:layout_constraintBottom_toTopOf="@id/tor_switch"/>
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/day_night_switch" />
<TextView
android:id="@+id/tor_textview"
@ -96,16 +97,16 @@
android:layout_height="wrap_content"
android:text="@string/tor_switch_label"
android:textSize="16sp"
app:layout_constraintTop_toTopOf="@id/tor_switch"
app:layout_constraintBottom_toBottomOf="@id/tor_switch"
app:layout_constraintEnd_toStartOf="@id/tor_switch"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/tor_switch"/>
app:layout_constraintTop_toTopOf="@id/tor_switch" />
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/tor_switch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:layout_constraintTop_toBottomOf="@id/network_settings_textview"
app:layout_constraintEnd_toEndOf="parent"/>
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/network_settings_textview" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -21,23 +21,24 @@
android:textAlignment="center"
android:textSize="16sp"
android:textStyle="bold"
tools:text="INFORMATION"
app:layout_constraintEnd_toStartOf="@id/copy_information_imagebutton"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintTop_toTopOf="parent"
tools:text="INFORMATION" />
<ImageButton
android:id="@+id/copy_information_imagebutton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:padding="8dp"
android:minHeight="24dp"
android:minWidth="24dp"
android:minHeight="24dp"
android:padding="8dp"
android:src="@drawable/ic_content_copy_24dp"
app:layout_constraintTop_toTopOf="@id/information_textview"
app:layout_constraintBottom_toBottomOf="@id/information_textview"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/information_textview"
app:layout_constraintEnd_toEndOf="parent"/>
app:layout_constraintTop_toTopOf="@id/information_textview" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>

View File

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
@ -12,44 +11,48 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/enter_password_textview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/enter_password"
android:layout_marginBottom="32dp"
android:text="@string/enter_password"
android:textSize="32sp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toTopOf="@id/wallet_password_edittext"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/wallet_password_edittext"/>
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/wallet_password_edittext"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="32dp"
android:background="@drawable/edittext_bg"
android:hint="@string/password"
android:inputType="textPassword"
android:background="@drawable/edittext_bg"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toTopOf="@id/unlock_wallet_button"
app:layout_constraintEnd_toStartOf="@id/paste_password_imagebutton"
app:layout_constraintTop_toBottomOf="@id/enter_password_textview"
app:layout_constraintBottom_toTopOf="@id/unlock_wallet_button"/>
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/enter_password_textview" />
<ImageButton
android:id="@+id/paste_password_imagebutton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:src="@drawable/ic_content_paste_24dp"
android:layout_marginStart="8dp"
android:background="@android:color/transparent"
android:minWidth="24dp"
android:minHeight="24dp"
android:padding="8dp"
app:layout_constraintStart_toEndOf="@id/wallet_password_edittext"
app:layout_constraintTop_toTopOf="@id/wallet_password_edittext"
android:src="@drawable/ic_content_paste_24dp"
app:layout_constraintBottom_toBottomOf="@id/wallet_password_edittext"
app:layout_constraintEnd_toEndOf="parent"/>
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/wallet_password_edittext"
app:layout_constraintTop_toTopOf="@id/wallet_password_edittext" />
<Button
android:id="@+id/unlock_wallet_button"
android:layout_width="match_parent"
@ -57,8 +60,8 @@
android:layout_marginTop="32dp"
android:background="@drawable/button_bg"
android:text="@string/unlock"
app:layout_constraintTop_toBottomOf="@id/wallet_password_edittext"
app:layout_constraintStart_toStartOf="parent"/>
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/wallet_password_edittext" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -12,27 +12,29 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/recv_monero_textview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/recv_monero"
android:layout_marginBottom="32dp"
android:text="@string/recv_monero"
android:textSize="32sp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toTopOf="@id/monero_qr_imageview"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/monero_qr_imageview"/>
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/monero_qr_imageview"
android:layout_width="256dp"
android:layout_height="256dp"
android:layout_marginTop="16dp"
android:src="@drawable/ic_fingerprint"
app:layout_constraintTop_toBottomOf="@id/recv_monero_textview"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"/>
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/recv_monero_textview" />
<ImageView
android:id="@+id/monero_logo_imageview"
@ -41,8 +43,8 @@
android:src="@drawable/ic_monero_qr"
app:layout_constraintBottom_toBottomOf="@id/monero_qr_imageview"
app:layout_constraintEnd_toEndOf="@id/monero_qr_imageview"
app:layout_constraintTop_toTopOf="@id/monero_qr_imageview"
app:layout_constraintStart_toStartOf="@id/monero_qr_imageview"/>
app:layout_constraintStart_toStartOf="@id/monero_qr_imageview"
app:layout_constraintTop_toTopOf="@id/monero_qr_imageview" />
<TextView
android:id="@+id/address_textview"
@ -53,23 +55,24 @@
android:textAlignment="center"
android:textSize="16sp"
android:textStyle="bold"
tools:text="ADDRESS"
app:layout_constraintEnd_toStartOf="@id/copy_address_imagebutton"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/monero_qr_imageview" />
app:layout_constraintTop_toBottomOf="@id/monero_qr_imageview"
tools:text="ADDRESS" />
<ImageButton
android:id="@+id/copy_address_imagebutton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:padding="8dp"
android:minHeight="24dp"
android:minWidth="24dp"
android:minHeight="24dp"
android:padding="8dp"
android:src="@drawable/ic_content_copy_24dp"
app:layout_constraintTop_toTopOf="@id/address_textview"
app:layout_constraintBottom_toBottomOf="@id/address_textview"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/address_textview"
app:layout_constraintEnd_toEndOf="parent"/>
app:layout_constraintTop_toTopOf="@id/address_textview" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>

View File

@ -17,14 +17,14 @@
android:id="@+id/send_monero_textview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/send_monero"
android:layout_marginBottom="32dp"
android:text="@string/send_monero"
android:textSize="32sp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toTopOf="@id/address_edittext"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/address_edittext"/>
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/address_edittext"
@ -32,24 +32,24 @@
android:layout_height="wrap_content"
android:layout_marginBottom="32dp"
android:background="@drawable/edittext_bg"
android:singleLine="true"
android:ellipsize="middle"
android:hint="@string/address"
android:singleLine="true"
app:layout_constraintBottom_toTopOf="@id/amount_edittext"
app:layout_constraintEnd_toStartOf="@id/paste_address_imagebutton"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/send_monero_textview"
app:layout_constraintEnd_toStartOf="@id/paste_address_imagebutton"
app:layout_constraintBottom_toTopOf="@id/amount_edittext"
tools:visibility="gone" />
<ImageButton
android:id="@+id/paste_address_imagebutton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:background="@android:color/transparent"
android:minWidth="24dp"
android:minHeight="24dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:padding="8dp"
android:src="@drawable/ic_content_paste_24dp"
app:layout_constraintBottom_toBottomOf="@id/address_edittext"
@ -58,6 +58,7 @@
app:layout_constraintTop_toTopOf="@id/address_edittext"
tools:ignore="SpeakableTextPresentCheck"
tools:visibility="gone" />
<ImageButton
android:id="@+id/scan_address_imagebutton"
android:layout_width="wrap_content"
@ -73,6 +74,7 @@
app:layout_constraintTop_toTopOf="@id/address_edittext"
tools:ignore="SpeakableTextPresentCheck"
tools:visibility="gone" />
<EditText
android:id="@+id/amount_edittext"
android:layout_width="0dp"
@ -81,10 +83,11 @@
android:background="@drawable/edittext_bg"
android:hint="@string/amount"
android:inputType="numberDecimal"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/send_max_button"
app:layout_constraintBottom_toTopOf="@id/create_tx_button"
app:layout_constraintEnd_toStartOf="@id/send_max_button"
app:layout_constraintStart_toStartOf="parent"
tools:visibility="gone" />
<TextView
android:id="@+id/sending_all_textview"
android:layout_width="0dp"
@ -94,22 +97,24 @@
android:text="@string/sending_all"
android:textStyle="bold"
android:visibility="gone"
app:layout_constraintTop_toTopOf="@id/amount_edittext"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/send_max_button"
app:layout_constraintBottom_toBottomOf="@id/amount_edittext"
app:layout_constraintEnd_toStartOf="@id/send_max_button"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/amount_edittext"
tools:visibility="gone" />
<Button
android:id="@+id/send_max_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/button_bg"
android:text="@string/send_max"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/amount_edittext"
app:layout_constraintBottom_toBottomOf="@id/amount_edittext"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/amount_edittext"
app:layout_constraintTop_toTopOf="@id/amount_edittext"
tools:visibility="gone" />
<Button
android:id="@+id/create_tx_button"
android:layout_width="match_parent"
@ -117,58 +122,57 @@
android:layout_marginTop="32dp"
android:background="@drawable/button_bg"
android:text="@string/create"
app:layout_constraintTop_toBottomOf="@id/amount_edittext"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/amount_edittext"
tools:visibility="gone" />
<!-- SEND LAYOUT -->
<TextView
android:id="@+id/address_pending_textview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/tx_address_text"
android:layout_marginTop="32dp"
android:ellipsize="middle"
android:singleLine="true"
android:text="@string/tx_address_text"
android:textSize="16sp"
android:textStyle="bold"
android:visibility="gone"
android:singleLine="true"
android:ellipsize="middle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/send_monero_textview"
app:layout_constraintBottom_toTopOf="@id/amount_pending_textview"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/send_monero_textview"
tools:visibility="visible" />
<TextView
android:id="@+id/amount_pending_textview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/tx_amount_text"
android:layout_marginTop="32dp"
android:text="@string/tx_amount_text"
android:textSize="16sp"
android:textStyle="bold"
android:visibility="gone"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/address_pending_textview"
app:layout_constraintBottom_toTopOf="@id/fee_textview"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/address_pending_textview"
tools:visibility="visible" />
<TextView
android:id="@+id/fee_textview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/tx_fee_text"
android:layout_marginTop="32dp"
android:text="@string/tx_fee_text"
android:textSize="16sp"
android:textStyle="bold"
android:visibility="gone"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/amount_pending_textview"
app:layout_constraintBottom_toTopOf="@id/send_tx_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/amount_pending_textview"
tools:visibility="visible" />
<Button
@ -179,8 +183,8 @@
android:background="@drawable/button_bg"
android:text="@string/send"
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@id/fee_textview"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/fee_textview"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -10,10 +10,10 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingTop="12dp"
android:paddingBottom="12dp"
android:paddingLeft="8dp"
android:paddingRight="8dp">
android:paddingTop="12dp"
android:paddingRight="8dp"
android:paddingBottom="12dp">
<LinearLayout
android:layout_width="0dp"

View File

@ -28,12 +28,10 @@
android:id="@+id/settings_fragment"
android:name="com.m2049r.xmrwallet.fragment.settings.SettingsFragment"
android:label="fragment_send_amount"
tools:layout="@layout/fragment_settings">
</fragment>
tools:layout="@layout/fragment_settings"></fragment>
<fragment
android:id="@+id/onboarding_fragment"
android:name="com.m2049r.xmrwallet.fragment.onboarding.OnboardingFragment"
android:label="fragment_onboarding"
tools:layout="@layout/fragment_settings">
</fragment>
tools:layout="@layout/fragment_settings"></fragment>
</navigation>

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="MyMaterialTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
<item name="windowNoTitle">true</item>
<item name="windowActionBar">false</item>

View File

@ -22,8 +22,6 @@
<string name="node_updated_hours">Last Block: %1$d hours ago</string>
<string name="node_updated_days">Last Block: %1$d days ago</string>
<!-- order must be the same as in com.m2049r.xmrwallet.data.Crypto-->
<string name="tx_details_notes" translatable="false">&lt;span style=\"background-color: #%1$s; color: #%2$s;\"&gt;&nbsp;%3$s&nbsp;&lt;/span&gt;%4$s</string>
<string name="menu_restore">Import wallet</string>

View File

@ -43,7 +43,9 @@
<style name="BottomSheetDialog_Rounded" parent="MyMaterialThemeOled">
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowIsFloating">false</item>
<item name="android:windowAnimationStyle">@style/Animation.MaterialComponents.BottomSheetDialog</item>
<item name="android:windowAnimationStyle">
@style/Animation.MaterialComponents.BottomSheetDialog
</item>
<item name="enableEdgeToEdge">true</item>
<item name="paddingBottomSystemWindowInsets">true</item>
<item name="paddingLeftSystemWindowInsets">true</item>

View File

@ -1,301 +0,0 @@
/*
* Copyright (c) 2017 m2049r et al.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.xmrwallet.service.exchange.ecb;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeApi;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeCallback;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeException;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeRate;
import com.m2049r.xmrwallet.util.NetCipherHelper;
import net.jodah.concurrentunit.Waiter;
import org.json.JSONException;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.concurrent.TimeoutException;
import okhttp3.OkHttpClient;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
import static org.junit.Assert.assertEquals;
public class ExchangeRateTest {
private MockWebServer mockWebServer;
private ExchangeApi exchangeApi;
private Waiter waiter;
@Mock
ExchangeCallback mockExchangeCallback;
@Before
public void setUp() throws Exception {
mockWebServer = new MockWebServer();
mockWebServer.start();
waiter = new Waiter();
MockitoAnnotations.initMocks(this);
NetCipherHelper.Request.mockClient = new OkHttpClient();
exchangeApi = new ExchangeApiImpl( mockWebServer.url("/"));
}
@After
public void tearDown() throws Exception {
mockWebServer.shutdown();
}
@Test
public void queryExchangeRate_shouldBeGetMethod()
throws InterruptedException, TimeoutException {
exchangeApi.queryExchangeRate("EUR", "USD", mockExchangeCallback);
RecordedRequest request = mockWebServer.takeRequest();
assertEquals("GET", request.getMethod());
}
@Test
public void queryExchangeRate_shouldBeEUR()
throws InterruptedException, TimeoutException {
exchangeApi.queryExchangeRate("CHF", "USD", new ExchangeCallback() {
@Override
public void onSuccess(final ExchangeRate exchangeRate) {
waiter.fail();
waiter.resume();
}
@Override
public void onError(final Exception e) {
waiter.assertTrue(e instanceof IllegalArgumentException);
waiter.resume();
}
});
waiter.await();
}
@Test
public void queryExchangeRate_shouldBeOneForEur()
throws InterruptedException, TimeoutException {
exchangeApi.queryExchangeRate("EUR", "EUR", new ExchangeCallback() {
@Override
public void onSuccess(final ExchangeRate exchangeRate) {
waiter.assertEquals(1.0, exchangeRate.getRate());
waiter.resume();
}
@Override
public void onError(final Exception e) {
waiter.fail();
waiter.resume();
}
});
waiter.await();
}
@Test
public void queryExchangeRate_wasSuccessfulShouldRespondWithUsdRate()
throws InterruptedException, JSONException, TimeoutException {
final String base = "EUR";
final String quote = "USD";
final double rate = 1.1043;
MockResponse jsonMockResponse = new MockResponse().setBody(createMockExchangeRateResponse());
mockWebServer.enqueue(jsonMockResponse);
exchangeApi.queryExchangeRate(base, quote, new ExchangeCallback() {
@Override
public void onSuccess(final ExchangeRate exchangeRate) {
waiter.assertEquals(base, exchangeRate.getBaseCurrency());
waiter.assertEquals(quote, exchangeRate.getQuoteCurrency());
waiter.assertEquals(rate, exchangeRate.getRate());
waiter.resume();
}
@Override
public void onError(final Exception e) {
waiter.fail(e);
waiter.resume();
}
});
waiter.await();
}
@Test
public void queryExchangeRate_wasSuccessfulShouldRespondWithAudRate()
throws InterruptedException, JSONException, TimeoutException {
final String base = "EUR";
final String quote = "AUD";
final double rate = 1.6246;
MockResponse jsonMockResponse = new MockResponse().setBody(createMockExchangeRateResponse());
mockWebServer.enqueue(jsonMockResponse);
exchangeApi.queryExchangeRate(base, quote, new ExchangeCallback() {
@Override
public void onSuccess(final ExchangeRate exchangeRate) {
waiter.assertEquals(base, exchangeRate.getBaseCurrency());
waiter.assertEquals(quote, exchangeRate.getQuoteCurrency());
waiter.assertEquals(rate, exchangeRate.getRate());
waiter.resume();
}
@Override
public void onError(final Exception e) {
waiter.fail(e);
waiter.resume();
}
});
waiter.await();
}
@Test
public void queryExchangeRate_wasSuccessfulShouldRespondWithZarRate()
throws InterruptedException, JSONException, TimeoutException {
final String base = "EUR";
final String quote = "ZAR";
final double rate = 16.3978;
MockResponse jsonMockResponse = new MockResponse().setBody(createMockExchangeRateResponse());
mockWebServer.enqueue(jsonMockResponse);
exchangeApi.queryExchangeRate(base, quote, new ExchangeCallback() {
@Override
public void onSuccess(final ExchangeRate exchangeRate) {
waiter.assertEquals(base, exchangeRate.getBaseCurrency());
waiter.assertEquals(quote, exchangeRate.getQuoteCurrency());
waiter.assertEquals(rate, exchangeRate.getRate());
waiter.resume();
}
@Override
public void onError(final Exception e) {
waiter.fail(e);
waiter.resume();
}
});
waiter.await();
}
@Test
public void queryExchangeRate_wasNotSuccessfulShouldCallOnError()
throws InterruptedException, JSONException, TimeoutException {
mockWebServer.enqueue(new MockResponse().setResponseCode(500));
exchangeApi.queryExchangeRate("EUR", "USD", new ExchangeCallback() {
@Override
public void onSuccess(final ExchangeRate exchangeRate) {
waiter.fail();
waiter.resume();
}
@Override
public void onError(final Exception e) {
waiter.assertTrue(e instanceof ExchangeException);
waiter.assertTrue(((ExchangeException) e).getCode() == 500);
waiter.resume();
}
});
waiter.await();
}
@Test
public void queryExchangeRate_unknownAssetShouldCallOnError()
throws InterruptedException, JSONException, TimeoutException {
MockResponse jsonMockResponse = new MockResponse().setBody(createMockExchangeRateResponse());
mockWebServer.enqueue(jsonMockResponse);
exchangeApi.queryExchangeRate("EUR", "ABC", new ExchangeCallback() {
@Override
public void onSuccess(final ExchangeRate exchangeRate) {
waiter.fail();
waiter.resume();
}
@Override
public void onError(final Exception e) {
waiter.assertTrue(e instanceof ExchangeException);
ExchangeException ex = (ExchangeException) e;
waiter.assertTrue(ex.getCode() == 404);
waiter.assertEquals(ex.getErrorMsg(), "Currency not supported: ABC");
waiter.resume();
}
});
waiter.await();
}
static public String createMockExchangeRateResponse() {
return "<gesmes:Envelope xmlns:gesmes=\"http://www.gesmes.org/xml/2002-08-01\" xmlns=\"http://www.ecb.int/vocabulary/2002-08-01/eurofxref\"><script xmlns=\"\"/>\n" +
"\t<gesmes:subject>Reference rates</gesmes:subject>\n" +
"\t<gesmes:Sender>\n" +
"\t\t<gesmes:name>European Central Bank</gesmes:name>\n" +
"\t</gesmes:Sender>\n" +
"\t<Cube>\n" +
"\t\t<Cube time=\"2019-10-11\">\n" +
"\t\t\t<Cube currency=\"USD\" rate=\"1.1043\"/>\n" +
"\t\t\t<Cube currency=\"JPY\" rate=\"119.75\"/>\n" +
"\t\t\t<Cube currency=\"BGN\" rate=\"1.9558\"/>\n" +
"\t\t\t<Cube currency=\"CZK\" rate=\"25.807\"/>\n" +
"\t\t\t<Cube currency=\"DKK\" rate=\"7.4688\"/>\n" +
"\t\t\t<Cube currency=\"GBP\" rate=\"0.87518\"/>\n" +
"\t\t\t<Cube currency=\"HUF\" rate=\"331.71\"/>\n" +
"\t\t\t<Cube currency=\"PLN\" rate=\"4.3057\"/>\n" +
"\t\t\t<Cube currency=\"RON\" rate=\"4.7573\"/>\n" +
"\t\t\t<Cube currency=\"SEK\" rate=\"10.8448\"/>\n" +
"\t\t\t<Cube currency=\"CHF\" rate=\"1.1025\"/>\n" +
"\t\t\t<Cube currency=\"ISK\" rate=\"137.70\"/>\n" +
"\t\t\t<Cube currency=\"NOK\" rate=\"10.0375\"/>\n" +
"\t\t\t<Cube currency=\"HRK\" rate=\"7.4280\"/>\n" +
"\t\t\t<Cube currency=\"RUB\" rate=\"70.8034\"/>\n" +
"\t\t\t<Cube currency=\"TRY\" rate=\"6.4713\"/>\n" +
"\t\t\t<Cube currency=\"AUD\" rate=\"1.6246\"/>\n" +
"\t\t\t<Cube currency=\"BRL\" rate=\"4.5291\"/>\n" +
"\t\t\t<Cube currency=\"CAD\" rate=\"1.4679\"/>\n" +
"\t\t\t<Cube currency=\"CNY\" rate=\"7.8417\"/>\n" +
"\t\t\t<Cube currency=\"HKD\" rate=\"8.6614\"/>\n" +
"\t\t\t<Cube currency=\"IDR\" rate=\"15601.55\"/>\n" +
"\t\t\t<Cube currency=\"ILS\" rate=\"3.8673\"/>\n" +
"\t\t\t<Cube currency=\"INR\" rate=\"78.4875\"/>\n" +
"\t\t\t<Cube currency=\"KRW\" rate=\"1308.61\"/>\n" +
"\t\t\t<Cube currency=\"MXN\" rate=\"21.3965\"/>\n" +
"\t\t\t<Cube currency=\"MYR\" rate=\"4.6220\"/>\n" +
"\t\t\t<Cube currency=\"NZD\" rate=\"1.7419\"/>\n" +
"\t\t\t<Cube currency=\"PHP\" rate=\"56.927\"/>\n" +
"\t\t\t<Cube currency=\"SGD\" rate=\"1.5177\"/>\n" +
"\t\t\t<Cube currency=\"THB\" rate=\"33.642\"/>\n" +
"\t\t\t<Cube currency=\"ZAR\" rate=\"16.3978\"/>\n" +
"\t\t</Cube>\n" +
"\t</Cube>\n" +
"</gesmes:Envelope>";
}
}

View File

@ -1,186 +0,0 @@
/*
* Copyright (c) 2017 m2049r et al.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.xmrwallet.service.exchange.kraken;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeApi;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeCallback;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeException;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeRate;
import com.m2049r.xmrwallet.util.NetCipherHelper;
import net.jodah.concurrentunit.Waiter;
import org.json.JSONException;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.concurrent.TimeoutException;
import okhttp3.OkHttpClient;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
import static org.junit.Assert.assertEquals;
public class ExchangeRateTest {
private MockWebServer mockWebServer;
private ExchangeApi exchangeApi;
private Waiter waiter;
@Mock
ExchangeCallback mockExchangeCallback;
@Before
public void setUp() throws Exception {
mockWebServer = new MockWebServer();
mockWebServer.start();
waiter = new Waiter();
MockitoAnnotations.initMocks(this);
NetCipherHelper.Request.mockClient = new OkHttpClient();
exchangeApi = new ExchangeApiImpl(mockWebServer.url("/"));
}
@After
public void tearDown() throws Exception {
mockWebServer.shutdown();
}
@Test
public void queryExchangeRate_shouldBeGetMethod()
throws InterruptedException, TimeoutException {
exchangeApi.queryExchangeRate("XMR", "USD", mockExchangeCallback);
RecordedRequest request = mockWebServer.takeRequest();
assertEquals("GET", request.getMethod());
}
@Test
public void queryExchangeRate_shouldHavePairInUrl()
throws InterruptedException, TimeoutException {
exchangeApi.queryExchangeRate("XMR", "USD", mockExchangeCallback);
RecordedRequest request = mockWebServer.takeRequest();
assertEquals("/?pair=XMRUSD", request.getPath());
}
@Test
public void queryExchangeRate_wasSuccessfulShouldRespondWithRate()
throws InterruptedException, JSONException, TimeoutException {
final String base = "XMR";
final String quote = "USD";
final double rate = 100;
MockResponse jsonMockResponse = new MockResponse().setBody(
createMockExchangeRateResponse(base, quote, rate));
mockWebServer.enqueue(jsonMockResponse);
exchangeApi.queryExchangeRate(base, quote, new ExchangeCallback() {
@Override
public void onSuccess(final ExchangeRate exchangeRate) {
waiter.assertEquals(exchangeRate.getBaseCurrency(), base);
waiter.assertEquals(exchangeRate.getQuoteCurrency(), quote);
waiter.assertEquals(exchangeRate.getRate(), rate);
waiter.resume();
}
@Override
public void onError(final Exception e) {
waiter.fail(e);
waiter.resume();
}
});
waiter.await();
}
@Test
public void queryExchangeRate_wasNotSuccessfulShouldCallOnError()
throws InterruptedException, JSONException, TimeoutException {
mockWebServer.enqueue(new MockResponse().setResponseCode(500));
exchangeApi.queryExchangeRate("XMR", "USD", new ExchangeCallback() {
@Override
public void onSuccess(final ExchangeRate exchangeRate) {
waiter.fail();
waiter.resume();
}
@Override
public void onError(final Exception e) {
waiter.assertTrue(e instanceof ExchangeException);
waiter.assertTrue(((ExchangeException) e).getCode() == 500);
waiter.resume();
}
});
waiter.await();
}
@Test
public void queryExchangeRate_unknownAssetShouldCallOnError()
throws InterruptedException, JSONException, TimeoutException {
mockWebServer.enqueue(new MockResponse().
setResponseCode(200).
setBody("{\"error\":[\"EQuery:Unknown asset pair\"]}"));
exchangeApi.queryExchangeRate("XMR", "ABC", new ExchangeCallback() {
@Override
public void onSuccess(final ExchangeRate exchangeRate) {
waiter.fail();
waiter.resume();
}
@Override
public void onError(final Exception e) {
waiter.assertTrue(e instanceof ExchangeException);
ExchangeException ex = (ExchangeException) e;
waiter.assertTrue(ex.getCode() == 200);
waiter.assertEquals(ex.getErrorMsg(), "EQuery:Unknown asset pair");
waiter.resume();
}
});
waiter.await();
}
static public String createMockExchangeRateResponse(final String base, final String quote, final double rate) {
return "{\n" +
" \"error\":[],\n" +
" \"result\":{\n" +
" \"X" + base + "Z" + quote + "\":{\n" +
" \"a\":[\"" + rate + "\",\"322\",\"322.000\"],\n" +
" \"b\":[\"" + rate + "\",\"76\",\"76.000\"],\n" +
" \"c\":[\"" + rate + "\",\"2.90000000\"],\n" +
" \"v\":[\"4559.03962053\",\"5231.33235586\"],\n" +
" \"p\":[\"" + rate + "\",\"" + rate + "\"],\n" +
" \"t\":[801,1014],\n" +
" \"l\":[\"" + (rate * 0.8) + "\",\"" + rate + "\"],\n" +
" \"h\":[\"" + (rate * 1.2) + "\",\"" + rate + "\"],\n" +
" \"o\":\"" + rate + "\"\n" +
" }\n" +
" }\n" +
"}";
}
}

View File

@ -1,213 +0,0 @@
/*
* Copyright (c) 2017 m2049r et al.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.xmrwallet.service.shift.sideshift;
import static org.junit.Assert.assertEquals;
import com.m2049r.xmrwallet.service.shift.ShiftCallback;
import com.m2049r.xmrwallet.service.shift.ShiftException;
import com.m2049r.xmrwallet.service.shift.sideshift.api.CreateOrder;
import com.m2049r.xmrwallet.service.shift.sideshift.api.SideShiftApi;
import com.m2049r.xmrwallet.service.shift.sideshift.network.SideShiftApiImpl;
import com.m2049r.xmrwallet.util.NetCipherHelper;
import com.m2049r.xmrwallet.util.ServiceHelper;
import net.jodah.concurrentunit.Waiter;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.concurrent.TimeoutException;
import okhttp3.OkHttpClient;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
public class SideShiftApiCreateOrderTest {
private MockWebServer mockWebServer;
private SideShiftApi xmrToApi;
private Waiter waiter;
@Mock
ShiftCallback<CreateOrder> mockOrderXmrToCallback;
@Before
public void setUp() throws Exception {
mockWebServer = new MockWebServer();
mockWebServer.start();
waiter = new Waiter();
MockitoAnnotations.initMocks(this);
NetCipherHelper.Request.mockClient = new OkHttpClient();
xmrToApi = new SideShiftApiImpl(mockWebServer.url("/"));
ServiceHelper.ASSET = "btc"; // all tests run with BTC
}
@After
public void tearDown() throws Exception {
mockWebServer.shutdown();
ServiceHelper.ASSET = null;
}
@Test
public void createOrder_shouldBePostMethod()
throws InterruptedException {
xmrToApi.createOrder("01234567-89ab-cdef-0123-456789abcdef", "19y91nJyzXsLEuR7Nj9pc3o5SeHNc8A9RW", mockOrderXmrToCallback);
RecordedRequest request = mockWebServer.takeRequest();
assertEquals("POST", request.getMethod());
}
@Test
public void createOrder_shouldBeContentTypeJson()
throws InterruptedException {
xmrToApi.createOrder("01234567-89ab-cdef-0123-456789abcdef", "19y91nJyzXsLEuR7Nj9pc3o5SeHNc8A9RW", mockOrderXmrToCallback);
RecordedRequest request = mockWebServer.takeRequest();
assertEquals("application/json; charset=utf-8", request.getHeader("Content-Type"));
}
@Test
public void createOrder_shouldContainValidBody()
throws InterruptedException {
final String validBody = "{\"settleAddress\":\"19y91nJyzXsLEuR7Nj9pc3o5SeHNc8A9RW\",\"type\":\"fixed\",\"quoteId\":\"01234567-89ab-cdef-0123-456789abcdef\"}";
xmrToApi.createOrder("01234567-89ab-cdef-0123-456789abcdef", "19y91nJyzXsLEuR7Nj9pc3o5SeHNc8A9RW", mockOrderXmrToCallback);
RecordedRequest request = mockWebServer.takeRequest();
String body = request.getBody().readUtf8();
assertEquals(validBody, body);
}
@Test
public void createOrder_wasSuccessfulShouldRespondWithOrder()
throws TimeoutException, InterruptedException {
final double btcAmount = 1.23456789;
final String btcAddress = "19y91nJyzXsLEuR7Nj9pc3o5SeHNc8A9RW";
final double xmrAmount = 0.6;
final String quoteId = "01234567-89ab-cdef-0123-456789abcdef";
final String orderId = "09090909090909090911";
MockResponse jsonMockResponse = new MockResponse().setBody(
createMockCreateOrderResponse(btcAmount, btcAddress, xmrAmount, quoteId, orderId));
mockWebServer.enqueue(jsonMockResponse);
xmrToApi.createOrder(quoteId, btcAddress, new ShiftCallback<CreateOrder>() {
@Override
public void onSuccess(final CreateOrder order) {
waiter.assertEquals(order.getBtcAmount(), btcAmount);
waiter.assertEquals(order.getBtcAddress(), btcAddress);
waiter.assertEquals(order.getQuoteId(), quoteId);
waiter.assertEquals(order.getOrderId(), orderId);
waiter.resume();
}
@Override
public void onError(final Exception e) {
waiter.fail(e);
waiter.resume();
}
});
waiter.await();
}
@Test
public void createOrder_wasNotSuccessfulShouldCallOnError()
throws TimeoutException, InterruptedException {
mockWebServer.enqueue(new MockResponse().setResponseCode(500));
xmrToApi.createOrder("01234567-89ab-cdef-0123-456789abcdef", "19y91nJyzXsLEuR7Nj9pc3o5SeHNc8A9RW", new ShiftCallback<CreateOrder>() {
@Override
public void onSuccess(final CreateOrder order) {
waiter.fail();
waiter.resume();
}
@Override
public void onError(final Exception e) {
waiter.assertTrue(e instanceof ShiftException);
waiter.assertTrue(((ShiftException) e).getCode() == 500);
waiter.resume();
}
});
waiter.await();
}
@Test
public void createOrder_malformedAddressShouldCallOnError()
throws TimeoutException, InterruptedException {
mockWebServer.enqueue(new MockResponse().
setResponseCode(500).
setBody("{\"error\":{\"message\":\"Invalid settleDestination\"}}"));
xmrToApi.createOrder("01234567-89ab-cdef-0123-456789abcdef", "xxx", new ShiftCallback<CreateOrder>() {
@Override
public void onSuccess(final CreateOrder order) {
waiter.fail();
waiter.resume();
}
@Override
public void onError(final Exception e) {
waiter.assertTrue(e instanceof ShiftException);
ShiftException xmrEx = (ShiftException) e;
waiter.assertTrue(xmrEx.getCode() == 500);
waiter.assertNotNull(xmrEx.getError());
waiter.assertEquals(xmrEx.getError().getErrorMsg(), "Invalid settleDestination");
waiter.resume();
}
});
waiter.await();
}
private String createMockCreateOrderResponse(final double btcAmount, final String btcAddress,
final double xmrAmount,
final String quoteId, final String orderId) {
return "{\"createdAt\":\"1612705584613\"," +
"\"createdAtISO\":\"2021-02-07T13:46:24.613Z\"," +
"\"expiresAt\":\"1612706453080\"," +
"\"expiresAtISO\":\"2021-02-07T14:00:53.080Z\"," +
"\"depositAddress\":{" +
"\"address\":\"4Bh68jCUZGHbVu45zCVvtcMYesHuduwgajoQcdYRjUQcY6MNa8qd67vTfSNWdtrc33dDECzbPCJeQ8HbiopdeM7Ej8MBVLCYz6cVqy6utz\"," +
"\"paymentId\":\"dbe876f0374db1ff\"," +
"\"integratedAddress\":\"4Bh68jCUZGHbVu45zCVvtcMYesHuduwgajoQcdYRjUQcY6MNa8qd67vTfSNWdtrc33dDECzbPCJeQ8HbiopdeM7Ej8MBVLCYz6cVqy6utz\"" +
"}," +
"\"depositMethodId\":\"xmr\"," +
"\"id\":\"" + orderId + "\"," +
"\"orderId\":\"" + orderId + "\"," +
"\"settleAddress\":{" +
"\"address\":\"" + btcAddress + "\"" +
"}," +
"\"settleMethodId\":\"btc\"," +
"\"depositMax\":\"" + xmrAmount + "\"," +
"\"depositMin\":\"" + xmrAmount + "\"," +
"\"quoteId\":\"" + quoteId + "\"," +
"\"settleAmount\":\"" + btcAmount + "\"," +
"\"depositAmount\":\"" + xmrAmount + "\"," +
"\"deposits\":[]}";
}
}

View File

@ -1,169 +0,0 @@
/*
* Copyright (c) 2017 m2049r et al.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.xmrwallet.service.shift.sideshift;
import com.m2049r.xmrwallet.service.shift.ShiftException;
import com.m2049r.xmrwallet.service.shift.sideshift.api.QueryOrderParameters;
import com.m2049r.xmrwallet.service.shift.sideshift.api.SideShiftApi;
import com.m2049r.xmrwallet.service.shift.ShiftCallback;
import com.m2049r.xmrwallet.service.shift.sideshift.network.SideShiftApiImpl;
import com.m2049r.xmrwallet.util.NetCipherHelper;
import net.jodah.concurrentunit.Waiter;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.concurrent.TimeoutException;
import okhttp3.OkHttpClient;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
import static org.junit.Assert.assertEquals;
public class SideShiftApiOrderParameterTest {
private MockWebServer mockWebServer;
private SideShiftApi xmrToApi;
private Waiter waiter;
@Mock
ShiftCallback<QueryOrderParameters> mockParametersXmrToCallback;
@Before
public void setUp() throws Exception {
mockWebServer = new MockWebServer();
mockWebServer.start();
waiter = new Waiter();
MockitoAnnotations.initMocks(this);
NetCipherHelper.Request.mockClient = new OkHttpClient();
xmrToApi = new SideShiftApiImpl(mockWebServer.url("/"));
}
@After
public void tearDown() throws Exception {
mockWebServer.shutdown();
}
@Test
public void orderParameter_shouldBeGetMethod()
throws InterruptedException {
xmrToApi.queryOrderParameters(mockParametersXmrToCallback);
RecordedRequest request = mockWebServer.takeRequest();
assertEquals("GET", request.getMethod());
}
@Test
public void orderParameter_wasSuccessfulShouldRespondWithParameters()
throws TimeoutException, InterruptedException {
final double rate = 0.015537;
final double upperLimit = 20.0;
final double lowerLimit = 0.001;
MockResponse jsonMockResponse = new MockResponse().setBody(
createMockOrderParameterResponse(rate, upperLimit, lowerLimit));
mockWebServer.enqueue(jsonMockResponse);
xmrToApi.queryOrderParameters(new ShiftCallback<QueryOrderParameters>() {
@Override
public void onSuccess(final QueryOrderParameters orderParameter) {
waiter.assertEquals(orderParameter.getLowerLimit(), lowerLimit);
waiter.assertEquals(orderParameter.getUpperLimit(), upperLimit);
waiter.assertEquals(orderParameter.getPrice(), rate);
waiter.resume();
}
@Override
public void onError(final Exception e) {
waiter.fail(e);
waiter.resume();
}
});
waiter.await();
}
@Test
public void orderParameter_wasNotSuccessfulShouldCallOnError()
throws TimeoutException, InterruptedException {
mockWebServer.enqueue(new MockResponse().setResponseCode(500));
xmrToApi.queryOrderParameters(new ShiftCallback<QueryOrderParameters>() {
@Override
public void onSuccess(final QueryOrderParameters orderParameter) {
waiter.fail();
waiter.resume();
}
@Override
public void onError(final Exception e) {
waiter.assertTrue(e instanceof ShiftException);
waiter.assertTrue(((ShiftException) e).getCode() == 500);
waiter.resume();
}
});
waiter.await();
}
@Test
public void orderParameter_SettleMethodInvalidShouldCallOnError()
throws TimeoutException, InterruptedException {
mockWebServer.enqueue(new MockResponse().
setResponseCode(500).
setBody("{\"error\":{\"message\":\"Settle method not found\"}}"));
xmrToApi.queryOrderParameters(new ShiftCallback<QueryOrderParameters>() {
@Override
public void onSuccess(final QueryOrderParameters orderParameter) {
waiter.fail();
waiter.resume();
}
@Override
public void onError(final Exception e) {
waiter.assertTrue(e instanceof ShiftException);
ShiftException xmrEx = (ShiftException) e;
waiter.assertTrue(xmrEx.getCode() == 500);
waiter.assertNotNull(xmrEx.getError());
waiter.assertEquals(xmrEx.getError().getErrorMsg(), "Settle method not found");
waiter.resume();
}
});
waiter.await();
}
private String createMockOrderParameterResponse(
final double rate,
final double upperLimit,
final double lowerLimit) {
return "{\n" +
" \"rate\": \"" + rate + "\",\n" +
" \"min\": \"" + lowerLimit + "\",\n" +
" \"max\": \"" + upperLimit + "\"\n" +
"}";
}
}

View File

@ -1,192 +0,0 @@
/*
* Copyright (c) 2017 m2049r et al.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.xmrwallet.service.shift.sideshift;
import static org.junit.Assert.assertEquals;
import com.m2049r.xmrwallet.service.shift.ShiftCallback;
import com.m2049r.xmrwallet.service.shift.ShiftException;
import com.m2049r.xmrwallet.service.shift.sideshift.api.QueryOrderStatus;
import com.m2049r.xmrwallet.service.shift.sideshift.api.SideShiftApi;
import com.m2049r.xmrwallet.service.shift.sideshift.network.SideShiftApiImpl;
import com.m2049r.xmrwallet.util.NetCipherHelper;
import net.jodah.concurrentunit.Waiter;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.concurrent.TimeoutException;
import okhttp3.OkHttpClient;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
public class SideShiftApiQueryOrderStatusTest {
private MockWebServer mockWebServer;
private SideShiftApi xmrToApi;
private Waiter waiter;
@Mock
ShiftCallback<QueryOrderStatus> mockQueryXmrToCallback;
@Before
public void setUp() throws Exception {
mockWebServer = new MockWebServer();
mockWebServer.start();
waiter = new Waiter();
MockitoAnnotations.initMocks(this);
NetCipherHelper.Request.mockClient = new OkHttpClient();
xmrToApi = new SideShiftApiImpl(mockWebServer.url("/"));
}
@After
public void tearDown() throws Exception {
mockWebServer.shutdown();
}
@Test
public void orderStatus_shouldBePostMethod()
throws InterruptedException {
xmrToApi.queryOrderStatus("09090909090909090911", mockQueryXmrToCallback);
RecordedRequest request = mockWebServer.takeRequest();
assertEquals("GET", request.getMethod());
}
@Test
public void orderStatus_wasSuccessfulShouldRespondWithOrder()
throws TimeoutException, InterruptedException {
final String state = "settled";
final String orderId = "09090909090909090911";
MockResponse jsonMockResponse = new MockResponse().setBody(
createMockQueryOrderResponse(
state,
orderId));
mockWebServer.enqueue(jsonMockResponse);
xmrToApi.queryOrderStatus(orderId, new ShiftCallback<QueryOrderStatus>() {
@Override
public void onSuccess(final QueryOrderStatus orderStatus) {
waiter.assertEquals(orderStatus.getOrderId(), orderId);
waiter.assertEquals(orderStatus.getState(), QueryOrderStatus.State.SETTLED);
waiter.resume();
}
@Override
public void onError(final Exception e) {
waiter.fail(e);
waiter.resume();
}
});
waiter.await();
}
@Test
public void orderStatus_wasNotSuccessfulShouldCallOnError()
throws TimeoutException, InterruptedException {
mockWebServer.enqueue(new MockResponse().setResponseCode(500));
xmrToApi.queryOrderStatus("09090909090909090911", new ShiftCallback<QueryOrderStatus>() {
@Override
public void onSuccess(final QueryOrderStatus orderStatus) {
waiter.fail();
waiter.resume();
}
@Override
public void onError(final Exception e) {
waiter.assertTrue(e instanceof ShiftException);
waiter.assertTrue(((ShiftException) e).getCode() == 500);
waiter.resume();
}
});
waiter.await();
}
@Test
public void orderStatus_orderNotFoundShouldCallOnError()
throws TimeoutException, InterruptedException {
mockWebServer.enqueue(new MockResponse().
setResponseCode(500).
setBody("{\"error\":{\"message\":\"Order not found\"}}"));
xmrToApi.queryOrderStatus("09090909090909090911", new ShiftCallback<QueryOrderStatus>() {
@Override
public void onSuccess(final QueryOrderStatus orderStatus) {
waiter.fail();
waiter.resume();
}
@Override
public void onError(final Exception e) {
waiter.assertTrue(e instanceof ShiftException);
ShiftException xmrEx = (ShiftException) e;
waiter.assertTrue(xmrEx.getCode() == 500);
waiter.assertNotNull(xmrEx.getError());
waiter.assertEquals(xmrEx.getError().getErrorMsg(), "Order not found");
waiter.resume();
}
});
waiter.await();
}
private String createMockQueryOrderResponse(
final String state,
final String orderId) {
return "{\"createdAt\":\"1612700947550\"," +
"\"createdAtISO\":\"2021-02-07T12:29:07.550Z\"," +
"\"expiresAt\":\"1612701817682\"," +
"\"expiresAtISO\":\"2021-02-07T12:43:37.682Z\"," +
"\"depositAddress\":" +
"{\"address\":\"4Bh68jCUZGHbVu45zCVvtcMYesHuduwgajoQcdYRjUQcY6MNa8qd67vTfSNWdtrc33dDECzbPCJeQ8HbiopdeM7Ej2DVsSohug9QxMJnN2\",\"paymentId\":\"3a151908242c6ed4\",\"integratedAddress\":\"4Bh68jCUZGHbVu45zCVvtcMYesHuduwgajoQcdYRjUQcY6MNa8qd67vTfSNWdtrc33dDECzbPCJeQ8HbiopdeM7Ej2DVsSohug9QxMJnN2\"}," +
"\"depositMethodId\":\"xmr\"," +
"\"id\":\"" + orderId + "\"," +
"\"orderId\":\"" + orderId + "\"," +
"\"settleAddress\":{\"address\":\"19y91nJyzXsLEuR7Nj9pc3o5SeHNc8A9RW\"}," +
"\"settleMethodId\":\"btc\"," +
"\"depositMax\":\"0.01\",\"depositMin\":\"0.01\"," +
"\"quoteId\":\"01234567-89ab-cdef-0123-456789abcdef\"," +
"\"settleAmount\":\"0.008108\"," +
"\"depositAmount\":\"0.01\"," +
"\"deposits\":[" +
"{\"createdAt\":\"1612701112634\",\"createdAtISO\":\"2021-02-07T12:31:52.634Z\"," +
"\"depositAmount\":\"0.01\"," +
"\"depositTx\":{\"type\":\"monero\",\"txHash\":\"a0b674f6033f5f5398dacea9dddedf8d12e35f46c29dfeaf5fac724d7c678fb7\",\"transferIndex\":0}," +
"\"depositId\":\"3e700e108fb31a4b1f7f\"," +
"\"id\":\"3e700e108fb31a4b1f7f\"," +
"\"status\":\"" + state + "\"," +
"\"refundAddress\":null,\"refundTx\":null," +
"\"settleAmount\":\"0.008108\"," +
"\"settleRate\":\"0.810756\"," +
"\"settleTx\":{\"type\":\"bitcoin\",\"txHash\":\"7bd5d0c3daac6a087ddf81411c8135fae55078334780debe47df775d596d4561\"}," +
"\"orderId\":\"" + orderId + "\"" +
"}" + // deposits[0]
"]" + // deposits
"}";
}
}

View File

@ -1,198 +0,0 @@
/*
* Copyright (c) 2017 m2049r et al.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.xmrwallet.service.shift.sideshift;
import com.m2049r.xmrwallet.service.shift.ShiftCallback;
import com.m2049r.xmrwallet.service.shift.ShiftError;
import com.m2049r.xmrwallet.service.shift.ShiftException;
import com.m2049r.xmrwallet.service.shift.sideshift.api.RequestQuote;
import com.m2049r.xmrwallet.service.shift.sideshift.api.SideShiftApi;
import com.m2049r.xmrwallet.service.shift.sideshift.network.SideShiftApiImpl;
import com.m2049r.xmrwallet.util.NetCipherHelper;
import com.m2049r.xmrwallet.util.ServiceHelper;
import net.jodah.concurrentunit.Waiter;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.concurrent.TimeoutException;
import okhttp3.OkHttpClient;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
import static org.junit.Assert.assertEquals;
public class SideShiftApiRequestQuoteTest {
private MockWebServer mockWebServer;
private SideShiftApi xmrToApi;
private Waiter waiter;
@Mock
ShiftCallback<RequestQuote> mockXmrToCallback;
@Before
public void setUp() throws Exception {
mockWebServer = new MockWebServer();
mockWebServer.start();
waiter = new Waiter();
MockitoAnnotations.initMocks(this);
NetCipherHelper.Request.mockClient = new OkHttpClient();
xmrToApi = new SideShiftApiImpl(mockWebServer.url("/"));
ServiceHelper.ASSET="btc"; // all tests run with BTC
}
@After
public void tearDown() throws Exception {
mockWebServer.shutdown();
ServiceHelper.ASSET = null;
}
@Test
public void requestQuote_shouldBePostMethod()
throws InterruptedException {
xmrToApi.requestQuote(1.01, mockXmrToCallback);
RecordedRequest request = mockWebServer.takeRequest();
assertEquals("POST", request.getMethod());
}
@Test
public void requestQuote_shouldBeContentTypeJson()
throws InterruptedException {
xmrToApi.requestQuote(1.01, mockXmrToCallback);
RecordedRequest request = mockWebServer.takeRequest();
assertEquals("application/json; charset=utf-8", request.getHeader("Content-Type"));
}
@Test
public void requestQuote_shouldContainValidBody() throws InterruptedException {
final String validBody = "{\"settleAmount\":\"1.01\",\"settleMethod\":\"btc\",\"depositMethod\":\"xmr\"}";
xmrToApi.requestQuote(1.01, mockXmrToCallback);
RecordedRequest request = mockWebServer.takeRequest();
String body = request.getBody().readUtf8();
assertEquals(validBody, body);
}
@Test
public void requestQuote_wasSuccessfulShouldRespondWithQuote()
throws TimeoutException, InterruptedException {
final double btcAmount = 1.01;
final double rate = 0.00397838;
final String uuid = "66fc0749-f320-4361-b0fb-7873576cba67";
MockResponse jsonMockResponse = new MockResponse().setBody(
createMockRequestQuoteResponse(btcAmount, rate, uuid));
mockWebServer.enqueue(jsonMockResponse);
xmrToApi.requestQuote(btcAmount, new ShiftCallback<RequestQuote>() {
@Override
public void onSuccess(final RequestQuote quote) {
waiter.assertEquals(quote.getXmrAmount(), btcAmount / rate);
waiter.assertEquals(quote.getBtcAmount(), btcAmount);
waiter.assertEquals(quote.getId(), uuid);
waiter.resume();
}
@Override
public void onError(final Exception e) {
waiter.fail(e);
waiter.resume();
}
});
waiter.await();
}
@Test
public void requestQuote_wasNotSuccessfulShouldCallOnError()
throws TimeoutException, InterruptedException {
mockWebServer.enqueue(new MockResponse().setResponseCode(500));
xmrToApi.requestQuote(1.01, new ShiftCallback<RequestQuote>() {
@Override
public void onSuccess(final RequestQuote quote) {
waiter.fail();
waiter.resume();
}
@Override
public void onError(final Exception e) {
waiter.assertTrue(e instanceof ShiftException);
waiter.assertTrue(((ShiftException) e).getCode() == 500);
waiter.resume();
}
});
waiter.await();
}
@Test
public void requestQuote_AmountTooHighShouldCallOnError()
throws TimeoutException, InterruptedException {
mockWebServer.enqueue(new MockResponse().
setResponseCode(500).
setBody("{\"error\":{\"message\":\"Amount too high\"}}"));
xmrToApi.requestQuote(1000, new ShiftCallback<RequestQuote>() {
@Override
public void onSuccess(final RequestQuote quote) {
waiter.fail();
waiter.resume();
}
@Override
public void onError(final Exception e) {
waiter.assertTrue(e instanceof ShiftException);
ShiftException xmrEx = (ShiftException) e;
waiter.assertTrue(xmrEx.getCode() == 500);
waiter.assertNotNull(xmrEx.getError());
waiter.assertEquals(xmrEx.getError().getErrorType(), ShiftError.Error.SERVICE);
waiter.assertEquals(xmrEx.getError().getErrorMsg(), "Amount too high");
waiter.resume();
}
});
waiter.await();
}
private String createMockRequestQuoteResponse(final double btcAmount, final double rate,
final String uuid) {
return "{\n" +
"\"createdAt\":\"2021-02-04T13:09:14.484Z\",\n" +
"\"settleAmount\":\"" + btcAmount + "\",\n" +
"\"depositMethod\":\"xmr\",\n" +
"\"expiresAt\":\"2021-02-04T13:24:14.484Z\",\n" +
"\"id\":\"" + uuid + "\",\n" +
"\"rate\":\"" + rate + "\",\n" +
"\"depositAmount\":\"" + (btcAmount / rate) + "\",\n" +
"\"settleMethod\":\"btc\"\n" +
"}";
}
}

View File

@ -1,168 +0,0 @@
/*
* Copyright (c) 2018 m2049r
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.xmrwallet.util;
import com.m2049r.xmrwallet.data.BarcodeData;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import java.util.Map;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
public class OpenAliasHelperTest {
private final static String MONERUJO = "oa1:xmr recipient_address=4AdkPJoxn7JCvAby9szgnt93MSEwdnxdhaASxbTBm6x5dCwmsDep2UYN4FhStDn5i11nsJbpU7oj59ahg8gXb1Mg3viqCuk; recipient_name=Monerujo Development; tx_description=Donation to Monerujo Core Team;";
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void asset() {
Map<String, String> attrs = OpenAliasHelper.parse(MONERUJO);
assertNotNull(attrs);
assertTrue("xmr".equals(attrs.get(OpenAliasHelper.OA1_ASSET)));
}
@Test
public void quotedSemicolon() {
Map<String, String> attrs = OpenAliasHelper.parse("oa1:xmr abc=\";\";def=99;");
assertNotNull(attrs);
assertTrue(attrs.get("abc").equals(";"));
assertTrue(attrs.get("def").equals("99"));
}
@Test
public void space() {
Map<String, String> attrs = OpenAliasHelper.parse("oa1:xmr abc=\\ ;def=99;");
assertNotNull(attrs);
assertTrue(attrs.get("abc").equals(" "));
assertTrue(attrs.get("def").equals("99"));
}
@Test
public void quotaedSpace() {
Map<String, String> attrs = OpenAliasHelper.parse("oa1:xmr abc=\" \";def=99;");
assertNotNull(attrs);
assertTrue(attrs.get("abc").equals(" "));
assertTrue(attrs.get("def").equals("99"));
}
@Test
public void quotes() {
Map<String, String> attrs;
attrs = OpenAliasHelper.parse("oa1:xmr abc=\"def\";");
assertNotNull(attrs);
assertTrue(attrs.get("abc").equals("def"));
}
@Test
public void simple() {
Map<String, String> attrs;
attrs = OpenAliasHelper.parse("oa1:xmr abc=def;");
assertNotNull(attrs);
assertTrue(attrs.get("abc").equals("def"));
}
@Test
public void duplex() {
Map<String, String> attrs;
attrs = OpenAliasHelper.parse("oa1:xmr abc=def;ghi=jkl;");
assertNotNull(attrs);
assertTrue(attrs.get("abc").equals("def"));
assertTrue(attrs.get("ghi").equals("jkl"));
}
@Test
public void duplexQ() {
Map<String, String> attrs;
attrs = OpenAliasHelper.parse("oa1:xmr abc=def;ghi=jkl;");
assertNotNull(attrs);
assertTrue(attrs.get("abc").equals("def"));
assertTrue(attrs.get("ghi").equals("jkl"));
}
@Test
public void simple_unterminated() {
Map<String, String> attrs;
attrs = OpenAliasHelper.parse("oa1:xmr abc=def;ghi=jkl");
assertNull(attrs);
}
@Test
public void unterminatedQuotes() {
Map<String, String> attrs;
attrs = OpenAliasHelper.parse("oa1:xmr abc=\"def;ghi=jkl;");
assertNull(attrs);
}
@Test
public void quoteEnd() {
Map<String, String> attrs;
attrs = OpenAliasHelper.parse("oa1:xmr abc=def\";ghi=jkl;");
assertNotNull(attrs);
assertTrue(attrs.get("abc").equals("def\""));
assertTrue(attrs.get("ghi").equals("jkl"));
}
@Test
public void quoteMiddle() {
Map<String, String> attrs;
attrs = OpenAliasHelper.parse("oa1:xmr abc=d\"ef;ghi=jkl;");
assertNotNull(attrs);
assertTrue(attrs.get("abc").equals("d\"ef"));
assertTrue(attrs.get("ghi").equals("jkl"));
}
@Test
public void quoteMultiple() {
Map<String, String> attrs;
attrs = OpenAliasHelper.parse("oa1:xmr abc=d\"ef\";ghi=jkl;");
assertNotNull(attrs);
assertTrue(attrs.get("abc").equals("d\"ef\""));
assertTrue(attrs.get("ghi").equals("jkl"));
}
@Test
public void quoteMalformedValue() {
Map<String, String> attrs;
attrs = OpenAliasHelper.parse("oa1:xmr abc=d\"e;f\";ghi=jkl;");
assertNull(attrs);
}
@Test
public void quotedSemicolon2() {
Map<String, String> attrs;
attrs = OpenAliasHelper.parse("oa1:xmr abc=\"d;ef\";ghi=jkl;");
assertNotNull(attrs);
assertTrue(attrs.get("abc").equals("d;ef"));
assertTrue(attrs.get("ghi").equals("jkl"));
}
@Test
public void quotedQuote() {
Map<String, String> attrs;
attrs = OpenAliasHelper.parse("oa1:xmr abc=\"d\"ef\";ghi=jkl;");
assertNotNull(attrs);
assertTrue(attrs.get("abc").equals("d\"ef"));
assertTrue(attrs.get("ghi").equals("jkl"));
}
}

View File

@ -1,104 +0,0 @@
/*
* Copyright (c) 2019 m2049r
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.xmrwallet.util.ledger;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
public class MoneroTest {
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void aRealTest() {
String ledgerMnemonic = "weird cloth shiver soda music slight system slender daughter magic design story gospel bulk teach between spice kangaroo inside satoshi convince load morning income";
String ledgerPassphrase = "";
String monero_mnemonic = "maverick aimless laptop eating vibrate sensible bugs dreams " +
"journal sincerely renting obtains boss mullet rustled cuddled " +
"goblet nightly jailed hamburger getting benches haggled hesitate laptop";
String test_monero = Monero.convert(ledgerMnemonic, ledgerPassphrase);
assertTrue(monero_mnemonic.equals(test_monero));
}
@Test
public void bRealTest() {
String ledgerMnemonic = "weird cloth shiver soda music slight system slender daughter magic design story gospel bulk teach between spice kangaroo inside satoshi convince load morning income";
String ledgerPassphrase = "secret";
String monero_mnemonic = "surfer hemlock afraid huddle mostly yanks revamp pairing " +
"northern yodel obliged vials azure huddle mowing melting " +
"ruthless subtly civilian midst playful vats nabbing nowhere mowing";
String test_monero = Monero.convert(ledgerMnemonic, ledgerPassphrase);
assertTrue(monero_mnemonic.equals(test_monero));
}
@Test
public void aTest() {
String ledgerMnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
String ledgerPassphrase = "";
String monero_mnemonic = "tavern judge beyond bifocals deepest mural onward dummy " +
"eagle diode gained vacation rally cause firm idled " +
"jerseys moat vigilant upload bobsled jobs cunning doing jobs";
String test_monero = Monero.convert(ledgerMnemonic, ledgerPassphrase);
assertTrue(monero_mnemonic.equals(test_monero));
}
@Test
public void bTest() {
String ledgerMnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
String ledgerPassphrase = "xyz";
String monero_mnemonic = "gambit observant swiftly metro hoax pheasants agile oozed " +
"fibula nuns picked stellar nibs cause gained phase " +
"lettuce tomorrow pierce awakened pistons pheasants sorry tedious gambit";
String test_monero = Monero.convert(ledgerMnemonic, ledgerPassphrase);
assertTrue(monero_mnemonic.equals(test_monero));
}
@Test
public void whitespaceTest() {
String ledgerMnemonic = " abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
String ledgerPassphrase = "xyz";
String monero_mnemonic = "gambit observant swiftly metro hoax pheasants agile oozed " +
"fibula nuns picked stellar nibs cause gained phase " +
"lettuce tomorrow pierce awakened pistons pheasants sorry tedious gambit";
String test_monero = Monero.convert(ledgerMnemonic, ledgerPassphrase);
assertTrue(monero_mnemonic.equals(test_monero));
}
@Test
public void caseTest() {
String ledgerMnemonic = "Abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
String ledgerPassphrase = "xyz";
String monero_mnemonic = "gambit observant swiftly metro hoax pheasants agile oozed " +
"fibula nuns picked stellar nibs cause gained phase " +
"lettuce tomorrow pierce awakened pistons pheasants sorry tedious gambit";
String test_monero = Monero.convert(ledgerMnemonic, ledgerPassphrase);
assertTrue(monero_mnemonic.equals(test_monero));
}
@Test
public void nullTest() {
String ledgerMnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
String ledgerPassphrase = "xyz";
String test_monero = Monero.convert(ledgerMnemonic, ledgerPassphrase);
assertNull(test_monero);
}
}

Some files were not shown because too many files have changed in this diff Show More