mirror of
https://github.com/m2049r/xmrwallet
synced 2025-09-10 22:10:49 +02:00
Compare commits
131 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
028057a672 | ||
![]() |
909ff8ca5e | ||
![]() |
ffd61e4495 | ||
![]() |
003dee382e | ||
![]() |
be0498c67d | ||
![]() |
06227a4a83 | ||
![]() |
4409087bd0 | ||
![]() |
965e52d8a5 | ||
![]() |
beba0f497b | ||
![]() |
9d1827ff0d | ||
![]() |
c04b192753 | ||
![]() |
888b5edaec | ||
![]() |
5ee5a81926 | ||
![]() |
c4e361a873 | ||
![]() |
dba6cb057e | ||
![]() |
9e1167c5b9 | ||
![]() |
12546a1ade | ||
![]() |
e9313bc235 | ||
![]() |
7e9bf84640 | ||
![]() |
94f87a5193 | ||
![]() |
8a8fc5ec9e | ||
![]() |
036d4ebf6c | ||
![]() |
c6d4de8599 | ||
![]() |
2ef7f8571c | ||
![]() |
d2612a26e5 | ||
![]() |
7e14572756 | ||
![]() |
6f840dcacf | ||
![]() |
36b389cd0f | ||
![]() |
ed2b95ea37 | ||
![]() |
8d41d1d03e | ||
![]() |
e2e9da3437 | ||
![]() |
7cd07d1988 | ||
![]() |
f5ae0525e3 | ||
![]() |
b32ed8caf2 | ||
![]() |
e0c2bfc4c4 | ||
![]() |
2986dfeaa7 | ||
![]() |
acb7398dca | ||
![]() |
c8322f5a83 | ||
![]() |
973472e0ef | ||
![]() |
2dad55e498 | ||
![]() |
8e82bd4cc8 | ||
![]() |
38a825d580 | ||
![]() |
be04185481 | ||
![]() |
5bfb920979 | ||
![]() |
29583fa40d | ||
![]() |
dc86f0469e | ||
![]() |
9e48f2bdcb | ||
![]() |
b71c260323 | ||
![]() |
46dbc0659b | ||
![]() |
85622b3958 | ||
![]() |
5a63481c09 | ||
![]() |
1cb558d78e | ||
![]() |
942c80e38b | ||
![]() |
24ec848f0f | ||
![]() |
0d1b1da5f3 | ||
![]() |
0840d2f350 | ||
![]() |
c7d933ea9d | ||
![]() |
c9b1800309 | ||
![]() |
e7b0b5999e | ||
![]() |
47e1871693 | ||
![]() |
900eab70c8 | ||
![]() |
b11357f379 | ||
![]() |
eba0156a6d | ||
![]() |
bf64d8bd89 | ||
![]() |
2d74281b31 | ||
![]() |
668cefb357 | ||
![]() |
1f5061df38 | ||
![]() |
51445f5941 | ||
![]() |
8c01ec36e8 | ||
![]() |
3cf84c599d | ||
![]() |
ead8564688 | ||
![]() |
b71b3badd8 | ||
![]() |
5ad46e2f54 | ||
![]() |
9d6895b60f | ||
![]() |
a8755ee0da | ||
![]() |
f186bc9d4f | ||
![]() |
9cb961a368 | ||
![]() |
aa78541b6f | ||
![]() |
eba6a78004 | ||
![]() |
8587eab41c | ||
![]() |
4271c743c7 | ||
![]() |
5b60987692 | ||
![]() |
e1bd04c945 | ||
![]() |
ef29db62c4 | ||
![]() |
82ce12e27c | ||
![]() |
4211687981 | ||
![]() |
d398416a3d | ||
![]() |
1844d84be8 | ||
![]() |
5101b7da5e | ||
![]() |
3e90f2e22e | ||
![]() |
f01a8eac5d | ||
![]() |
0c13338dc0 | ||
![]() |
78d6fef3e4 | ||
![]() |
cbddcdc92d | ||
![]() |
4289aaac77 | ||
![]() |
fd15da9c84 | ||
![]() |
c3357087d5 | ||
![]() |
47e0bc86fd | ||
![]() |
dbd45ea4f5 | ||
![]() |
90c3e6ef8b | ||
![]() |
bc6a816462 | ||
![]() |
274a1a25f1 | ||
![]() |
9e3a2b102e | ||
![]() |
647ae9a565 | ||
![]() |
2bbd20855f | ||
![]() |
b8fc5f910a | ||
![]() |
01700cf780 | ||
![]() |
2fa6286b0e | ||
![]() |
0ac1680e75 | ||
![]() |
bd9a98e0d8 | ||
![]() |
8a5121e792 | ||
![]() |
5e6d3f3032 | ||
![]() |
65991ff554 | ||
![]() |
b0629e46e8 | ||
![]() |
45ec3198a0 | ||
![]() |
9b66c466f2 | ||
![]() |
d257e183ad | ||
![]() |
10d8e441fe | ||
![]() |
9f9bc4793d | ||
![]() |
8b28e3ea1e | ||
![]() |
e394394538 | ||
![]() |
7424ef07f7 | ||
![]() |
f5ce6ec824 | ||
![]() |
4215a8bf9e | ||
![]() |
8b016f93dc | ||
![]() |
b239a5094b | ||
![]() |
6a2de36578 | ||
![]() |
5aded68c53 | ||
![]() |
d78a2be120 | ||
![]() |
e6c7800911 | ||
![]() |
cbbe079f67 |
7
.gitignore
vendored
7
.gitignore
vendored
@@ -6,4 +6,11 @@
|
|||||||
/captures
|
/captures
|
||||||
.externalNativeBuild
|
.externalNativeBuild
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
/app/build
|
||||||
/app/release
|
/app/release
|
||||||
|
/app/alpha
|
||||||
|
/app/prod
|
||||||
|
/app/alphaMainnet
|
||||||
|
/app/prodMainnet
|
||||||
|
/app/alphaStagenet
|
||||||
|
/app/prodStagenet
|
||||||
|
1
.idea/gradle.xml
generated
1
.idea/gradle.xml
generated
@@ -3,6 +3,7 @@
|
|||||||
<component name="GradleSettings">
|
<component name="GradleSettings">
|
||||||
<option name="linkedExternalProjectsSettings">
|
<option name="linkedExternalProjectsSettings">
|
||||||
<GradleProjectSettings>
|
<GradleProjectSettings>
|
||||||
|
<option name="disableWrapperSourceDistributionNotification" value="true" />
|
||||||
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
||||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||||
<option name="modules">
|
<option name="modules">
|
||||||
|
13
README.md
13
README.md
@@ -22,12 +22,8 @@ Help us translate Monerujo! You can find instructions [On Taiga](https://taiga.g
|
|||||||
You may lose all your Moneroj if you use this App. Be cautious when spending on the mainnet.
|
You may lose all your Moneroj if you use this App. Be cautious when spending on the mainnet.
|
||||||
|
|
||||||
### Random Notes
|
### Random Notes
|
||||||
- Based off monero v0.11.1.0
|
- works on the mainnet & stagenet
|
||||||
- currently only android32 (runs on 64-bit as well)
|
|
||||||
- works on the testnet & mainnet
|
|
||||||
- sync is slow due to 32-bit architecture
|
|
||||||
- use your own daemon - it's easy
|
- use your own daemon - it's easy
|
||||||
- screen stays on until first sync is complete
|
|
||||||
- Monerujo means "Monero Wallet" according to https://www.reddit.com/r/Monero/comments/3exy7t/esperanto_corner/
|
- Monerujo means "Monero Wallet" according to https://www.reddit.com/r/Monero/comments/3exy7t/esperanto_corner/
|
||||||
|
|
||||||
### TODO
|
### TODO
|
||||||
@@ -43,13 +39,8 @@ of the "real" testnet. After creating a new wallet, make a **new** one by recov
|
|||||||
The official monero client shows the same behaviour.
|
The official monero client shows the same behaviour.
|
||||||
|
|
||||||
### HOW TO BUILD
|
### HOW TO BUILD
|
||||||
No need to build. Binaries are included:
|
|
||||||
|
|
||||||
- openssl-1.0.2l
|
See [the instructions](doc/BUILDING-external-libs.md)
|
||||||
- monero-v0.12
|
|
||||||
- boost_1_58_0
|
|
||||||
|
|
||||||
If you want to build them yourself (recommended) check out [the instructions](doc/BUILDING-external-libs.md)
|
|
||||||
|
|
||||||
Then, fire up Android Studio and build the APK.
|
Then, fire up Android Studio and build the APK.
|
||||||
|
|
||||||
|
3
app/.gitignore
vendored
3
app/.gitignore
vendored
@@ -1,3 +0,0 @@
|
|||||||
.externalNativeBuild
|
|
||||||
build
|
|
||||||
app.iml
|
|
@@ -7,13 +7,21 @@ add_library( monerujo
|
|||||||
|
|
||||||
set(EXTERNAL_LIBS_DIR ${CMAKE_SOURCE_DIR}/../external-libs)
|
set(EXTERNAL_LIBS_DIR ${CMAKE_SOURCE_DIR}/../external-libs)
|
||||||
|
|
||||||
|
############
|
||||||
|
# libsodium
|
||||||
|
############
|
||||||
|
|
||||||
|
add_library(sodium STATIC IMPORTED)
|
||||||
|
set_target_properties(sodium PROPERTIES IMPORTED_LOCATION
|
||||||
|
${EXTERNAL_LIBS_DIR}/libsodium/lib/${ANDROID_ABI}/libsodium.a)
|
||||||
|
|
||||||
############
|
############
|
||||||
# OpenSSL
|
# OpenSSL
|
||||||
############
|
############
|
||||||
|
|
||||||
add_library(crypto STATIC IMPORTED)
|
add_library(crypto STATIC IMPORTED)
|
||||||
set_target_properties(crypto PROPERTIES IMPORTED_LOCATION
|
set_target_properties(crypto PROPERTIES IMPORTED_LOCATION
|
||||||
${EXTERNAL_LIBS_DIR}/openssl/lib/${ANDROID_ABI}/libcrypto.a)
|
${EXTERNAL_LIBS_DIR}/openssl/lib/${ANDROID_ABI}/libcrypto.a)
|
||||||
|
|
||||||
add_library(ssl STATIC IMPORTED)
|
add_library(ssl STATIC IMPORTED)
|
||||||
set_target_properties(ssl PROPERTIES IMPORTED_LOCATION
|
set_target_properties(ssl PROPERTIES IMPORTED_LOCATION
|
||||||
@@ -184,5 +192,7 @@ target_link_libraries( monerujo
|
|||||||
ssl
|
ssl
|
||||||
crypto
|
crypto
|
||||||
|
|
||||||
|
sodium
|
||||||
|
|
||||||
${log-lib}
|
${log-lib}
|
||||||
)
|
)
|
||||||
|
@@ -1,14 +1,15 @@
|
|||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 25
|
compileSdkVersion 28
|
||||||
buildToolsVersion '27.0.3'
|
buildToolsVersion '28.0.3'
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "com.m2049r.xmrwallet"
|
applicationId "com.m2049r.xmrwallet"
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 25
|
targetSdkVersion 28
|
||||||
versionCode 113
|
versionCode 164
|
||||||
versionName "1.6.3 'Nano S'"
|
versionName "1.10.14 'Node-O-matiC'"
|
||||||
|
|
||||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
cmake {
|
cmake {
|
||||||
@@ -18,6 +19,26 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
flavorDimensions 'type', 'net'
|
||||||
|
productFlavors {
|
||||||
|
mainnet {
|
||||||
|
dimension 'net'
|
||||||
|
}
|
||||||
|
stagenet {
|
||||||
|
dimension 'net'
|
||||||
|
applicationIdSuffix '.stage'
|
||||||
|
versionNameSuffix ' (stage)'
|
||||||
|
}
|
||||||
|
alpha {
|
||||||
|
dimension 'type'
|
||||||
|
applicationIdSuffix '.alpha'
|
||||||
|
versionNameSuffix ' (alpha)'
|
||||||
|
}
|
||||||
|
prod {
|
||||||
|
dimension 'type'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
minifyEnabled false
|
minifyEnabled false
|
||||||
@@ -27,6 +48,7 @@ android {
|
|||||||
applicationIdSuffix ".debug"
|
applicationIdSuffix ".debug"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
cmake {
|
cmake {
|
||||||
path "CMakeLists.txt"
|
path "CMakeLists.txt"
|
||||||
@@ -61,30 +83,42 @@ android {
|
|||||||
output ->
|
output ->
|
||||||
def abiName = output.getFilter(com.android.build.OutputFile.ABI)
|
def abiName = output.getFilter(com.android.build.OutputFile.ABI)
|
||||||
output.versionCodeOverride = abiCodes.get(abiName, 0) + 10 * variant.versionCode
|
output.versionCodeOverride = abiCodes.get(abiName, 0) + 10 * variant.versionCode
|
||||||
|
//def flavor = output.getFilter(flavor)
|
||||||
|
|
||||||
if (abiName == null) abiName = "universal"
|
if (abiName == null) abiName = "universal"
|
||||||
def v = "${variant.versionName}".replaceFirst(" .*\$", "").replace(".", "x")
|
def v = "${variant.versionName}".replaceFirst(" '.*' ?", "")
|
||||||
|
.replace(".", "x")
|
||||||
|
.replace("(", "-")
|
||||||
|
.replace(")", "")
|
||||||
outputFileName = "$rootProject.ext.apkName-" + v + "_" + abiName + ".apk"
|
outputFileName = "$rootProject.ext.apkName-" + v + "_" + abiName + ".apk"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'com.android.support:appcompat-v7:25.4.0'
|
implementation "com.android.support:appcompat-v7:$rootProject.ext.supportVersion"
|
||||||
implementation 'com.android.support:design:25.4.0'
|
implementation "com.android.support:design:$rootProject.ext.supportVersion"
|
||||||
implementation 'com.android.support:support-v4:25.4.0'
|
implementation "com.android.support:support-v4:$rootProject.ext.supportVersion"
|
||||||
implementation 'com.android.support:recyclerview-v7:25.4.0'
|
implementation "com.android.support:recyclerview-v7:$rootProject.ext.supportVersion"
|
||||||
implementation 'com.android.support:cardview-v7:25.4.0'
|
implementation "com.android.support:cardview-v7:$rootProject.ext.supportVersion"
|
||||||
|
implementation "com.android.support:swiperefreshlayout:$rootProject.ext.supportVersion"
|
||||||
implementation 'me.dm7.barcodescanner:zxing:1.9.8'
|
implementation 'me.dm7.barcodescanner:zxing:1.9.8'
|
||||||
|
|
||||||
implementation "com.squareup.okhttp3:okhttp:$rootProject.ext.okHttpVersion"
|
implementation "com.squareup.okhttp3:okhttp:$rootProject.ext.okHttpVersion"
|
||||||
|
implementation "com.burgstaller:okhttp-digest:1.18"
|
||||||
implementation "com.jakewharton.timber:timber:$rootProject.ext.timberVersion"
|
implementation "com.jakewharton.timber:timber:$rootProject.ext.timberVersion"
|
||||||
|
|
||||||
implementation 'com.nulab-inc:zxcvbn:1.2.3'
|
implementation 'com.nulab-inc:zxcvbn:1.2.3'
|
||||||
|
|
||||||
|
implementation 'dnsjava:dnsjava:2.1.8'
|
||||||
|
implementation 'org.jitsi:dnssecjava:1.1.3'
|
||||||
|
implementation 'org.slf4j:slf4j-nop:1.7.25'
|
||||||
|
implementation 'com.github.brnunes:swipeablerecyclerview:1.0.2'
|
||||||
|
|
||||||
testImplementation "junit:junit:$rootProject.ext.junitVersion"
|
testImplementation "junit:junit:$rootProject.ext.junitVersion"
|
||||||
testImplementation "org.mockito:mockito-all:$rootProject.ext.mockitoVersion"
|
testImplementation "org.mockito:mockito-all:$rootProject.ext.mockitoVersion"
|
||||||
testImplementation "com.squareup.okhttp3:mockwebserver:$rootProject.ext.okHttpVersion"
|
testImplementation "com.squareup.okhttp3:mockwebserver:$rootProject.ext.okHttpVersion"
|
||||||
testImplementation 'org.json:json:20140107'
|
testImplementation 'org.json:json:20180813'
|
||||||
testImplementation 'net.jodah:concurrentunit:0.4.2'
|
testImplementation 'net.jodah:concurrentunit:0.4.4'
|
||||||
|
|
||||||
}
|
}
|
||||||
|
BIN
app/src/alpha/ic_launcher-web.png
Normal file
BIN
app/src/alpha/ic_launcher-web.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 244 KiB |
BIN
app/src/alpha/res/mipmap-hdpi/ic_launcher.png
Normal file
BIN
app/src/alpha/res/mipmap-hdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.0 KiB |
BIN
app/src/alpha/res/mipmap-mdpi/ic_launcher.png
Normal file
BIN
app/src/alpha/res/mipmap-mdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.4 KiB |
BIN
app/src/alpha/res/mipmap-xhdpi/ic_launcher.png
Normal file
BIN
app/src/alpha/res/mipmap-xhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
BIN
app/src/alpha/res/mipmap-xxhdpi/ic_launcher.png
Normal file
BIN
app/src/alpha/res/mipmap-xxhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 28 KiB |
BIN
app/src/alpha/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
BIN
app/src/alpha/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 47 KiB |
@@ -1,4 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<string name="app_name" translatable="false">monerujo - Debug</string>
|
|
||||||
</resources>
|
|
@@ -7,8 +7,10 @@
|
|||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
<uses-permission android:name="android.permission.CAMERA" />
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
|
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
||||||
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
||||||
<uses-permission android:name="android.permission.NFC" />
|
<uses-permission android:name="android.permission.NFC" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".XmrWalletApplication"
|
android:name=".XmrWalletApplication"
|
||||||
@@ -16,7 +18,8 @@
|
|||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/MyMaterialTheme">
|
android:theme="@style/MyMaterialTheme"
|
||||||
|
android:usesCleartextTraffic="true">
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".WalletActivity"
|
android:name=".WalletActivity"
|
||||||
|
829
app/src/main/assets/licenses.html
Normal file
829
app/src/main/assets/licenses.html
Normal file
File diff suppressed because it is too large
Load Diff
88
app/src/main/cpp/device_io_monerujo.hpp
Normal file
88
app/src/main/cpp/device_io_monerujo.hpp
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
// Copyright (c) 2017-2018, The Monero Project
|
||||||
|
//
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without modification, are
|
||||||
|
// permitted provided that the following conditions are met:
|
||||||
|
//
|
||||||
|
// 1. Redistributions of source code must retain the above copyright notice, this list of
|
||||||
|
// conditions and the following disclaimer.
|
||||||
|
//
|
||||||
|
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||||
|
// of conditions and the following disclaimer in the documentation and/or other
|
||||||
|
// materials provided with the distribution.
|
||||||
|
//
|
||||||
|
// 3. Neither the name of the copyright holder nor the names of its contributors may be
|
||||||
|
// used to endorse or promote products derived from this software without specific
|
||||||
|
// prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
|
||||||
|
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||||
|
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
||||||
|
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||||
|
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
//
|
||||||
|
|
||||||
|
#if defined(HAVE_MONERUJO)
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief LedgerFind - find Ledger Device and return it's name
|
||||||
|
* @param buffer - buffer for name of found device
|
||||||
|
* @param len - length of buffer
|
||||||
|
* @return 0 - success
|
||||||
|
* -1 - no device connected / found
|
||||||
|
* -2 - JVM not found
|
||||||
|
*/
|
||||||
|
int LedgerFind(char *buffer, size_t len);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief LedgerExchange - exchange data with Ledger Device
|
||||||
|
* @param command - buffer for data to send
|
||||||
|
* @param cmd_len - length of send to send
|
||||||
|
* @param response - buffer for received data
|
||||||
|
* @param max_resp_len - size of receive buffer
|
||||||
|
*
|
||||||
|
* @return length of received data in response or -1 if error
|
||||||
|
*/
|
||||||
|
int LedgerExchange(unsigned char *command, unsigned int cmd_len, unsigned char *response, unsigned int max_resp_len);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "device_io.hpp"
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace hw {
|
||||||
|
namespace io {
|
||||||
|
class device_io_monerujo: device_io {
|
||||||
|
public:
|
||||||
|
device_io_monerujo() {};
|
||||||
|
~device_io_monerujo() {};
|
||||||
|
|
||||||
|
void init() {};
|
||||||
|
void release() {};
|
||||||
|
|
||||||
|
void connect(void *params) {};
|
||||||
|
void disconnect() {};
|
||||||
|
bool connected() const {return true;}; // monerujo is always connected before it gets here
|
||||||
|
|
||||||
|
// returns number of bytes read or -1 on error
|
||||||
|
int exchange(unsigned char *command, unsigned int cmd_len, unsigned char *response, unsigned int max_resp_len) {
|
||||||
|
return LedgerExchange(command, cmd_len, response, max_resp_len);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //#if defined(HAVE_MONERUJO)
|
File diff suppressed because it is too large
Load Diff
@@ -1,46 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2018 m2049r
|
|
||||||
* <p>
|
|
||||||
* 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
|
|
||||||
* <p>
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
* <p>
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef XMRWALLET_LEDGER_H
|
|
||||||
#define XMRWALLET_LEDGER_H
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C"
|
|
||||||
{
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define SCARD_S_SUCCESS ((LONG)0x00000000) /**< No error was encountered. */
|
|
||||||
#define SCARD_E_INSUFFICIENT_BUFFER ((LONG)0x80100008) /**< The data buffer to receive returned data is too small for the returned data. */
|
|
||||||
#define SCARD_E_NO_READERS_AVAILABLE ((LONG)0x8010002E) /**< Cannot find a smart card reader. */
|
|
||||||
|
|
||||||
typedef long LONG;
|
|
||||||
typedef unsigned long DWORD;
|
|
||||||
typedef DWORD *LPDWORD;
|
|
||||||
typedef unsigned char BYTE;
|
|
||||||
typedef BYTE *LPBYTE;
|
|
||||||
typedef const BYTE *LPCBYTE;
|
|
||||||
|
|
||||||
typedef char CHAR;
|
|
||||||
typedef CHAR *LPSTR;
|
|
||||||
|
|
||||||
int LedgerFind(char *buffer, size_t len);
|
|
||||||
LONG LedgerExchange(LPCBYTE pbSendBuffer, DWORD cbSendLength, LPBYTE pbRecvBuffer, LPDWORD pcbRecvLength);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif //XMRWALLET_LEDGER_H
|
|
@@ -23,9 +23,9 @@ package com.btchip.comm;
|
|||||||
import com.btchip.BTChipException;
|
import com.btchip.BTChipException;
|
||||||
|
|
||||||
public interface BTChipTransport {
|
public interface BTChipTransport {
|
||||||
public byte[] exchange(byte[] command);
|
byte[] exchange(byte[] command);
|
||||||
|
|
||||||
public void close();
|
void close();
|
||||||
|
|
||||||
public void setDebug(boolean debugFlag);
|
void setDebug(boolean debugFlag);
|
||||||
}
|
}
|
||||||
|
145
app/src/main/java/com/m2049r/levin/data/Bucket.java
Normal file
145
app/src/main/java/com/m2049r/levin/data/Bucket.java
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
/*
|
||||||
|
* 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();
|
||||||
|
}
|
||||||
|
}
|
125
app/src/main/java/com/m2049r/levin/data/Section.java
Normal file
125
app/src/main/java/com/m2049r/levin/data/Section.java
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
/*
|
||||||
|
* 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
196
app/src/main/java/com/m2049r/levin/scanner/Dispatcher.java
Normal file
196
app/src/main/java/com/m2049r/levin/scanner/Dispatcher.java
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
/*
|
||||||
|
* 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.setName();
|
||||||
|
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) {
|
||||||
|
node.clear();
|
||||||
|
if (node.isFavourite()) {
|
||||||
|
rpcNodes.add(node);
|
||||||
|
if (listener != null) listener.onGet(node);
|
||||||
|
}
|
||||||
|
retrievePeer(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
23
app/src/main/java/com/m2049r/levin/scanner/LevinPeer.java
Normal file
23
app/src/main/java/com/m2049r/levin/scanner/LevinPeer.java
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
231
app/src/main/java/com/m2049r/levin/scanner/PeerRetriever.java
Normal file
231
app/src/main/java/com/m2049r/levin/scanner/PeerRetriever.java
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
}
|
42
app/src/main/java/com/m2049r/levin/util/HexHelper.java
Normal file
42
app/src/main/java/com/m2049r/levin/util/HexHelper.java
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
184
app/src/main/java/com/m2049r/levin/util/LevinReader.java
Normal file
184
app/src/main/java/com/m2049r/levin/util/LevinReader.java
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
/*
|
||||||
|
* 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(int firstByte, int bytes) throws IOException {
|
||||||
|
long result = firstByte;
|
||||||
|
for (int i = 0; i < bytes; i++) {
|
||||||
|
result = result + (in.readUnsignedByte() << 8);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
98
app/src/main/java/com/m2049r/levin/util/LevinWriter.java
Normal file
98
app/src/main/java/com/m2049r/levin/util/LevinWriter.java
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
/*
|
||||||
|
* 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -50,19 +50,19 @@ public class BaseActivity extends SecureActivity implements GenerateReviewFragme
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void showProgressDialog(int msgId) {
|
public void showProgressDialog(int msgId) {
|
||||||
showProgressDialog(msgId, 0);
|
showProgressDialog(msgId, 250); // don't show dialog for fast operations
|
||||||
}
|
}
|
||||||
|
|
||||||
public void showProgressDialog(int msgId, long delay) {
|
public void showProgressDialog(int msgId, long delayMillis) {
|
||||||
dismissProgressDialog(); // just in case
|
dismissProgressDialog(); // just in case
|
||||||
progressDialog = new SimpleProgressDialog(BaseActivity.this, msgId);
|
progressDialog = new SimpleProgressDialog(BaseActivity.this, msgId);
|
||||||
if (delay > 0) {
|
if (delayMillis > 0) {
|
||||||
Handler handler = new Handler();
|
Handler handler = new Handler();
|
||||||
handler.postDelayed(new Runnable() {
|
handler.postDelayed(new Runnable() {
|
||||||
public void run() {
|
public void run() {
|
||||||
if (progressDialog != null) progressDialog.show();
|
if (progressDialog != null) progressDialog.show();
|
||||||
}
|
}
|
||||||
}, delay);
|
}, delayMillis);
|
||||||
} else {
|
} else {
|
||||||
progressDialog.show();
|
progressDialog.show();
|
||||||
}
|
}
|
||||||
|
@@ -85,15 +85,15 @@ public class GenerateFragment extends Fragment {
|
|||||||
|
|
||||||
View view = inflater.inflate(R.layout.fragment_generate, container, false);
|
View view = inflater.inflate(R.layout.fragment_generate, container, false);
|
||||||
|
|
||||||
etWalletName = (TextInputLayout) view.findViewById(R.id.etWalletName);
|
etWalletName = view.findViewById(R.id.etWalletName);
|
||||||
etWalletPassword = (TextInputLayout) view.findViewById(R.id.etWalletPassword);
|
etWalletPassword = view.findViewById(R.id.etWalletPassword);
|
||||||
llFingerprintAuth = (LinearLayout) view.findViewById(R.id.llFingerprintAuth);
|
llFingerprintAuth = view.findViewById(R.id.llFingerprintAuth);
|
||||||
etWalletMnemonic = (TextInputLayout) view.findViewById(R.id.etWalletMnemonic);
|
etWalletMnemonic = view.findViewById(R.id.etWalletMnemonic);
|
||||||
etWalletAddress = (TextInputLayout) view.findViewById(R.id.etWalletAddress);
|
etWalletAddress = view.findViewById(R.id.etWalletAddress);
|
||||||
etWalletViewKey = (TextInputLayout) view.findViewById(R.id.etWalletViewKey);
|
etWalletViewKey = view.findViewById(R.id.etWalletViewKey);
|
||||||
etWalletSpendKey = (TextInputLayout) view.findViewById(R.id.etWalletSpendKey);
|
etWalletSpendKey = view.findViewById(R.id.etWalletSpendKey);
|
||||||
etWalletRestoreHeight = (TextInputLayout) view.findViewById(R.id.etWalletRestoreHeight);
|
etWalletRestoreHeight = view.findViewById(R.id.etWalletRestoreHeight);
|
||||||
bGenerate = (Button) view.findViewById(R.id.bGenerate);
|
bGenerate = view.findViewById(R.id.bGenerate);
|
||||||
|
|
||||||
etWalletMnemonic.getEditText().setRawInputType(InputType.TYPE_CLASS_TEXT);
|
etWalletMnemonic.getEditText().setRawInputType(InputType.TYPE_CLASS_TEXT);
|
||||||
etWalletAddress.getEditText().setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
etWalletAddress.getEditText().setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||||
@@ -145,7 +145,8 @@ public class GenerateFragment extends Fragment {
|
|||||||
|
|
||||||
etWalletName.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
etWalletName.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
||||||
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
||||||
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_NEXT)) {
|
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|
||||||
|
|| (actionId == EditorInfo.IME_ACTION_NEXT)) {
|
||||||
if (checkName()) {
|
if (checkName()) {
|
||||||
etWalletPassword.requestFocus();
|
etWalletPassword.requestFocus();
|
||||||
} // otherwise ignore
|
} // otherwise ignore
|
||||||
@@ -183,7 +184,8 @@ public class GenerateFragment extends Fragment {
|
|||||||
etWalletPassword.getEditText().setImeOptions(EditorInfo.IME_ACTION_DONE);
|
etWalletPassword.getEditText().setImeOptions(EditorInfo.IME_ACTION_DONE);
|
||||||
etWalletPassword.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
etWalletPassword.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
||||||
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
||||||
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_DONE)) {
|
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|
||||||
|
|| (actionId == EditorInfo.IME_ACTION_DONE)) {
|
||||||
Helper.hideKeyboard(getActivity());
|
Helper.hideKeyboard(getActivity());
|
||||||
generateWallet();
|
generateWallet();
|
||||||
return true;
|
return true;
|
||||||
@@ -195,7 +197,8 @@ public class GenerateFragment extends Fragment {
|
|||||||
etWalletPassword.getEditText().setImeOptions(EditorInfo.IME_ACTION_DONE);
|
etWalletPassword.getEditText().setImeOptions(EditorInfo.IME_ACTION_DONE);
|
||||||
etWalletPassword.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
etWalletPassword.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
||||||
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
||||||
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_DONE)) {
|
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|
||||||
|
|| (actionId == EditorInfo.IME_ACTION_DONE)) {
|
||||||
etWalletRestoreHeight.requestFocus();
|
etWalletRestoreHeight.requestFocus();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -205,7 +208,8 @@ public class GenerateFragment extends Fragment {
|
|||||||
} else if (type.equals(TYPE_SEED)) {
|
} else if (type.equals(TYPE_SEED)) {
|
||||||
etWalletPassword.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
etWalletPassword.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
||||||
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
||||||
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_NEXT)) {
|
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|
||||||
|
|| (actionId == EditorInfo.IME_ACTION_NEXT)) {
|
||||||
etWalletMnemonic.requestFocus();
|
etWalletMnemonic.requestFocus();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -215,7 +219,8 @@ public class GenerateFragment extends Fragment {
|
|||||||
etWalletMnemonic.setVisibility(View.VISIBLE);
|
etWalletMnemonic.setVisibility(View.VISIBLE);
|
||||||
etWalletMnemonic.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
etWalletMnemonic.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
||||||
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
||||||
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_NEXT)) {
|
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|
||||||
|
|| (actionId == EditorInfo.IME_ACTION_NEXT)) {
|
||||||
if (checkMnemonic()) {
|
if (checkMnemonic()) {
|
||||||
etWalletRestoreHeight.requestFocus();
|
etWalletRestoreHeight.requestFocus();
|
||||||
}
|
}
|
||||||
@@ -227,7 +232,8 @@ public class GenerateFragment extends Fragment {
|
|||||||
} else if (type.equals(TYPE_KEY) || type.equals(TYPE_VIEWONLY)) {
|
} else if (type.equals(TYPE_KEY) || type.equals(TYPE_VIEWONLY)) {
|
||||||
etWalletPassword.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
etWalletPassword.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
||||||
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
||||||
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_NEXT)) {
|
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|
||||||
|
|| (actionId == EditorInfo.IME_ACTION_NEXT)) {
|
||||||
etWalletAddress.requestFocus();
|
etWalletAddress.requestFocus();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -239,7 +245,8 @@ public class GenerateFragment extends Fragment {
|
|||||||
|
|
||||||
{
|
{
|
||||||
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
||||||
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_NEXT)) {
|
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|
||||||
|
|| (actionId == EditorInfo.IME_ACTION_NEXT)) {
|
||||||
if (checkAddress()) {
|
if (checkAddress()) {
|
||||||
etWalletViewKey.requestFocus();
|
etWalletViewKey.requestFocus();
|
||||||
}
|
}
|
||||||
@@ -251,7 +258,8 @@ public class GenerateFragment extends Fragment {
|
|||||||
etWalletViewKey.setVisibility(View.VISIBLE);
|
etWalletViewKey.setVisibility(View.VISIBLE);
|
||||||
etWalletViewKey.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
etWalletViewKey.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
||||||
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
||||||
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_NEXT)) {
|
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|
||||||
|
|| (actionId == EditorInfo.IME_ACTION_NEXT)) {
|
||||||
if (checkViewKey()) {
|
if (checkViewKey()) {
|
||||||
if (type.equals(TYPE_KEY)) {
|
if (type.equals(TYPE_KEY)) {
|
||||||
etWalletSpendKey.requestFocus();
|
etWalletSpendKey.requestFocus();
|
||||||
@@ -271,7 +279,8 @@ public class GenerateFragment extends Fragment {
|
|||||||
|
|
||||||
{
|
{
|
||||||
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
||||||
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_NEXT)) {
|
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|
||||||
|
|| (actionId == EditorInfo.IME_ACTION_NEXT)) {
|
||||||
if (checkSpendKey()) {
|
if (checkSpendKey()) {
|
||||||
etWalletRestoreHeight.requestFocus();
|
etWalletRestoreHeight.requestFocus();
|
||||||
}
|
}
|
||||||
@@ -285,7 +294,8 @@ public class GenerateFragment extends Fragment {
|
|||||||
etWalletRestoreHeight.setVisibility(View.VISIBLE);
|
etWalletRestoreHeight.setVisibility(View.VISIBLE);
|
||||||
etWalletRestoreHeight.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
etWalletRestoreHeight.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
||||||
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
||||||
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_DONE)) {
|
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|
||||||
|
|| (actionId == EditorInfo.IME_ACTION_DONE)) {
|
||||||
Helper.hideKeyboard(getActivity());
|
Helper.hideKeyboard(getActivity());
|
||||||
generateWallet();
|
generateWallet();
|
||||||
return true;
|
return true;
|
||||||
|
@@ -91,22 +91,22 @@ public class GenerateReviewFragment extends Fragment {
|
|||||||
|
|
||||||
View view = inflater.inflate(R.layout.fragment_review, container, false);
|
View view = inflater.inflate(R.layout.fragment_review, container, false);
|
||||||
|
|
||||||
scrollview = (ScrollView) view.findViewById(R.id.scrollview);
|
scrollview = view.findViewById(R.id.scrollview);
|
||||||
pbProgress = (ProgressBar) view.findViewById(R.id.pbProgress);
|
pbProgress = view.findViewById(R.id.pbProgress);
|
||||||
tvWalletPassword = (TextView) view.findViewById(R.id.tvWalletPassword);
|
tvWalletPassword = view.findViewById(R.id.tvWalletPassword);
|
||||||
tvWalletAddress = (TextView) view.findViewById(R.id.tvWalletAddress);
|
tvWalletAddress = view.findViewById(R.id.tvWalletAddress);
|
||||||
tvWalletViewKey = (TextView) view.findViewById(R.id.tvWalletViewKey);
|
tvWalletViewKey = view.findViewById(R.id.tvWalletViewKey);
|
||||||
tvWalletSpendKey = (TextView) view.findViewById(R.id.tvWalletSpendKey);
|
tvWalletSpendKey = view.findViewById(R.id.tvWalletSpendKey);
|
||||||
tvWalletMnemonic = (TextView) view.findViewById(R.id.tvWalletMnemonic);
|
tvWalletMnemonic = view.findViewById(R.id.tvWalletMnemonic);
|
||||||
bCopyAddress = (ImageButton) view.findViewById(R.id.bCopyAddress);
|
bCopyAddress = view.findViewById(R.id.bCopyAddress);
|
||||||
bAdvancedInfo = (Button) view.findViewById(R.id.bAdvancedInfo);
|
bAdvancedInfo = view.findViewById(R.id.bAdvancedInfo);
|
||||||
llAdvancedInfo = (LinearLayout) view.findViewById(R.id.llAdvancedInfo);
|
llAdvancedInfo = view.findViewById(R.id.llAdvancedInfo);
|
||||||
llPassword = (LinearLayout) view.findViewById(R.id.llPassword);
|
llPassword = view.findViewById(R.id.llPassword);
|
||||||
llMnemonic = (LinearLayout) view.findViewById(R.id.llMnemonic);
|
llMnemonic = view.findViewById(R.id.llMnemonic);
|
||||||
llSpendKey = (LinearLayout) view.findViewById(R.id.llSpendKey);
|
llSpendKey = view.findViewById(R.id.llSpendKey);
|
||||||
llViewKey = (LinearLayout) view.findViewById(R.id.llViewKey);
|
llViewKey = view.findViewById(R.id.llViewKey);
|
||||||
|
|
||||||
bAccept = (Button) view.findViewById(R.id.bAccept);
|
bAccept = view.findViewById(R.id.bAccept);
|
||||||
|
|
||||||
boolean allowCopy = WalletManager.getInstance().getNetworkType() != NetworkType.NetworkType_Mainnet;
|
boolean allowCopy = WalletManager.getInstance().getNetworkType() != NetworkType.NetworkType_Mainnet;
|
||||||
tvWalletMnemonic.setTextIsSelectable(allowCopy);
|
tvWalletMnemonic.setTextIsSelectable(allowCopy);
|
||||||
@@ -200,7 +200,8 @@ public class GenerateReviewFragment extends Fragment {
|
|||||||
super.onPreExecute();
|
super.onPreExecute();
|
||||||
showProgress();
|
showProgress();
|
||||||
if ((walletPath != null)
|
if ((walletPath != null)
|
||||||
&& (WalletManager.getInstance().queryWalletHardware(walletPath + ".keys", getPassword()) == 1)
|
&& (WalletManager.getInstance().queryWalletDevice(walletPath + ".keys", getPassword())
|
||||||
|
== Wallet.Device.Device_Ledger)
|
||||||
&& (progressCallback != null)) {
|
&& (progressCallback != null)) {
|
||||||
progressCallback.showLedgerProgressDialog(LedgerProgressDialog.TYPE_RESTORE);
|
progressCallback.showLedgerProgressDialog(LedgerProgressDialog.TYPE_RESTORE);
|
||||||
dialogOpened = true;
|
dialogOpened = true;
|
||||||
@@ -231,10 +232,15 @@ public class GenerateReviewFragment extends Fragment {
|
|||||||
|
|
||||||
address = wallet.getAddress();
|
address = wallet.getAddress();
|
||||||
seed = wallet.getSeed();
|
seed = wallet.getSeed();
|
||||||
if (wallet.isKeyOnDevice()) {
|
switch (wallet.getDeviceType()) {
|
||||||
viewKey = Ledger.Key();
|
case Device_Ledger:
|
||||||
} else {
|
viewKey = Ledger.Key();
|
||||||
viewKey = wallet.getSecretViewKey();
|
break;
|
||||||
|
case Device_Software:
|
||||||
|
viewKey = wallet.getSecretViewKey();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException("Hardware backing not supported. At all!");
|
||||||
}
|
}
|
||||||
spendKey = isWatchOnly ? getActivity().getString(R.string.label_watchonly) : wallet.getSecretSpendKey();
|
spendKey = isWatchOnly ? getActivity().getString(R.string.label_watchonly) : wallet.getSecretSpendKey();
|
||||||
isWatchOnly = wallet.isWatchOnly();
|
isWatchOnly = wallet.isWatchOnly();
|
||||||
@@ -475,13 +481,13 @@ public class GenerateReviewFragment extends Fragment {
|
|||||||
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getActivity());
|
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getActivity());
|
||||||
alertDialogBuilder.setView(promptsView);
|
alertDialogBuilder.setView(promptsView);
|
||||||
|
|
||||||
final TextInputLayout etPasswordA = (TextInputLayout) promptsView.findViewById(R.id.etWalletPasswordA);
|
final TextInputLayout etPasswordA = promptsView.findViewById(R.id.etWalletPasswordA);
|
||||||
etPasswordA.setHint(getString(R.string.prompt_changepw, walletName));
|
etPasswordA.setHint(getString(R.string.prompt_changepw, walletName));
|
||||||
|
|
||||||
final TextInputLayout etPasswordB = (TextInputLayout) promptsView.findViewById(R.id.etWalletPasswordB);
|
final TextInputLayout etPasswordB = promptsView.findViewById(R.id.etWalletPasswordB);
|
||||||
etPasswordB.setHint(getString(R.string.prompt_changepwB, walletName));
|
etPasswordB.setHint(getString(R.string.prompt_changepwB, walletName));
|
||||||
|
|
||||||
LinearLayout llFingerprintAuth = (LinearLayout) promptsView.findViewById(R.id.llFingerprintAuth);
|
LinearLayout llFingerprintAuth = promptsView.findViewById(R.id.llFingerprintAuth);
|
||||||
final Switch swFingerprintAllowed = (Switch) llFingerprintAuth.getChildAt(0);
|
final Switch swFingerprintAllowed = (Switch) llFingerprintAuth.getChildAt(0);
|
||||||
if (FingerprintHelper.isDeviceSupported(getActivity())) {
|
if (FingerprintHelper.isDeviceSupported(getActivity())) {
|
||||||
llFingerprintAuth.setVisibility(View.VISIBLE);
|
llFingerprintAuth.setVisibility(View.VISIBLE);
|
||||||
@@ -594,7 +600,8 @@ public class GenerateReviewFragment extends Fragment {
|
|||||||
// accept keyboard "ok"
|
// accept keyboard "ok"
|
||||||
etPasswordB.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
etPasswordB.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
||||||
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
||||||
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_DONE)) {
|
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|
||||||
|
|| (actionId == EditorInfo.IME_ACTION_DONE)) {
|
||||||
String newPasswordA = etPasswordA.getEditText().getText().toString();
|
String newPasswordA = etPasswordA.getEditText().getText().toString();
|
||||||
String newPasswordB = etPasswordB.getEditText().getText().toString();
|
String newPasswordB = etPasswordB.getEditText().getText().toString();
|
||||||
// disallow empty passwords
|
// disallow empty passwords
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user