mirror of
https://gitlab.futo.org/videostreaming/grayjay.git
synced 2026-05-16 13:02:39 +02:00
Compare commits
142 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6166392515 | |||
| 49d0dead7d | |||
| 6f004830ff | |||
| e2e5e36bad | |||
| f267d264d3 | |||
| be1a77bfd7 | |||
| 41a980e826 | |||
| 09c09f3d64 | |||
| 2404399ec5 | |||
| b45d4c0557 | |||
| a41b138d3c | |||
| 1e46949dd6 | |||
| 3ed2c1ba5d | |||
| 809b99c9c9 | |||
| 4d3acdb5fb | |||
| ca9e321ef2 | |||
| da27517fcf | |||
| 192df0a3b8 | |||
| a965003a9d | |||
| 9ea26c821f | |||
| 14b699485a | |||
| 1684edc43f | |||
| 580c4418b9 | |||
| 4a65fc2358 | |||
| 71ba131fb3 | |||
| 9693b50719 | |||
| 102e2c54bb | |||
| e989590c08 | |||
| 6cee33b449 | |||
| f32498a444 | |||
| c85f71b601 | |||
| 196e55899e | |||
| ebec45076d | |||
| 561d9ae987 | |||
| 8950bd94cb | |||
| f416f197bc | |||
| 65afe5a0e6 | |||
| 4b5d347413 | |||
| 4dcc2dd0ca | |||
| 2a7a332160 | |||
| 27ee1eabda | |||
| 0034665965 | |||
| a69692be18 | |||
| dc76152166 | |||
| d7f3ae696c | |||
| 71f5449d34 | |||
| 0e64fa8d4c | |||
| 73b048d4c5 | |||
| 1c05b39861 | |||
| 7cfa6c163f | |||
| 2d4af2e867 | |||
| 1eeaffc442 | |||
| 82125b33ed | |||
| 42cbbc28fd | |||
| a7cbb0e93c | |||
| fde6148ece | |||
| df1661d75a | |||
| f938f79a35 | |||
| 333f00235b | |||
| c06475bfb3 | |||
| d1a54d0cf3 | |||
| 349437c06b | |||
| 1b03c83c84 | |||
| bb749aacf1 | |||
| 3a41b89e52 | |||
| 70cbc77381 | |||
| 3a99f5dfaa | |||
| f24435ecf4 | |||
| 4a708e316a | |||
| c2b47c998d | |||
| 534f7b3134 | |||
| d5d2692317 | |||
| dc9cc7b00f | |||
| 965e74c7e2 | |||
| 096ba54eb1 | |||
| f4e38f9e50 | |||
| c0d9409176 | |||
| 7d1f565749 | |||
| dfec4ada3b | |||
| cd695cf265 | |||
| 47ff2e0c38 | |||
| db7c09291f | |||
| 01f10c49ba | |||
| 1ff0692a72 | |||
| 116e6099d5 | |||
| 18ccaadc5b | |||
| 8f6eac7ca2 | |||
| f4610d0df5 | |||
| bf1a6b7d0a | |||
| b3fd05e62e | |||
| f7ce365618 | |||
| 77a558dbe5 | |||
| cc0c400b28 | |||
| 2bcd59cbfa | |||
| 5139acc7f1 | |||
| 1564433e02 | |||
| 1339beb7cd | |||
| cd9698ea48 | |||
| c8f8e4c5eb | |||
| 0b4ab46563 | |||
| ea1ac86134 | |||
| 790331e798 | |||
| f5d9b2ba41 | |||
| 7f26ac00b1 | |||
| fcbab10434 | |||
| c4061cc6ac | |||
| 12ac4d6b6f | |||
| 3d06e62cd4 | |||
| d7d23e1048 | |||
| 1fe9b70176 | |||
| a9cf8dd71a | |||
| 3299261db3 | |||
| e465ec8278 | |||
| d0e4a0aa1f | |||
| 74efec3235 | |||
| 13516087f2 | |||
| 0a0c16524a | |||
| 9b843a155e | |||
| cb085acbff | |||
| c3d7df166b | |||
| d312062125 | |||
| e2453192aa | |||
| 68eb0cc8f2 | |||
| cb9cecfa5d | |||
| 0f4e4a7d97 | |||
| f20a708b36 | |||
| 8c4e511883 | |||
| a4a3b8d664 | |||
| bf6530ea81 | |||
| 4a80c2aab1 | |||
| 527bbfe43f | |||
| d8e1edb60b | |||
| 245b5f74c0 | |||
| e9a1f63415 | |||
| ec370dd94b | |||
| e39d862ef3 | |||
| 7b065654aa | |||
| 918b2bbe96 | |||
| e529a3d34d | |||
| 5475778d67 | |||
| c6a3ff0a53 | |||
| da6eef905c |
+16
-2
@@ -49,9 +49,23 @@ We encourage developers to write their own plugins. Please refer to the "Getting
|
||||
|
||||
## Contributing to Core
|
||||
|
||||
**We are currently not accepting contributions to the core.**
|
||||
|
||||
The core is currently licensed under the FUTO Temporary License (FTL). The licensing and ownership of contributions to the core are complex topics that we are still working on. We'll update these guidelines when we have more clarity.
|
||||
### License
|
||||
|
||||
The core is currently licensed under the [Source First License 1.1](./LICENSE.md). All contributors have to sign FUTO Individual Contributor License Agreement before contributions can be accepted. You can read more about it at [https://cla.futo.org/](https://cla.futo.org/).
|
||||
|
||||
### How to Contribute
|
||||
|
||||
1. Fork the core repository.
|
||||
2. Clone your fork.
|
||||
3. Make your changes.
|
||||
4. Commit and push your changes.
|
||||
5. Open a pull request.
|
||||
|
||||
### Guidelines
|
||||
|
||||
- Ensure your code adheres to the existing style.
|
||||
- Include documentation and unit tests (where applicable).
|
||||
|
||||
---
|
||||
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
# Grayjay Core License 1.0
|
||||
# Source First License 1.1
|
||||
|
||||
## Acceptance
|
||||
By using the software, you agree to all of the terms and conditions below.
|
||||
@@ -16,7 +16,7 @@ Notwithstanding the above, you may not remove or obscure any functionality in th
|
||||
You may not alter, remove, or obscure any licensing, copyright, or other notices of the Licensor in the software. Any use of the Licensor’s trademarks is subject to applicable law.
|
||||
|
||||
## Patents
|
||||
If you make any written claim that the software infringes or contributes to infringement of any patent, your patent license for the software granted under these terms ends immediately. If your company makes such a claim, your patent license ends immediately for work on behalf of your company.
|
||||
If you make any written claim that the software infringes or contributes to infringement of any patent, your license for the software granted under these terms ends immediately. If your company makes such a claim, your license ends immediately for work on behalf of your company.
|
||||
|
||||
## Notices
|
||||
You must ensure that anyone who gets a copy of any part of the software from you also gets a copy of these terms. If you modify the software, you must include in any modified copies of the software a prominent notice stating that you have modified the software, such as but not limited to, a statement in a readme file or an in-application about section.
|
||||
|
||||
@@ -9,8 +9,8 @@ technologies that frustrate centralization and industry consolidation.
|
||||
|
||||
<table border="0">
|
||||
<tr>
|
||||
<td><b style="font-size:30px"><img src="images/video.jpg" height="700" /></b></td>
|
||||
<td><b style="font-size:30px"><img src="images/video-details.jpg" height="700" /></b></td>
|
||||
<td><b style="font-size:30px"><img src="images/video.png" height="700" /></b></td>
|
||||
<td><b style="font-size:30px"><img src="images/video-details.png" height="700" /></b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Video</td>
|
||||
@@ -24,12 +24,10 @@ The FUTO media app is a player that exposes multiple video websites as sources i
|
||||
|
||||
<table border="0">
|
||||
<tr>
|
||||
<td><b style="font-size:30px"><img src="images/sources.jpg" height="700" /></b></td>
|
||||
<td><b style="font-size:30px"><img src="images/sources-disabled.jpg" height="700" /></b></td>
|
||||
<td><b style="font-size:30px"><img src="images/source.png" height="700" /></b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Sources (all enabled)</td>
|
||||
<td>Sources (one disabled)</td>
|
||||
<td>Sources</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@@ -38,7 +36,7 @@ Additional sources can also be installed. These sources are JavaScript sources,
|
||||
<table border="0">
|
||||
<tr>
|
||||
<td><b style="font-size:30px"><img src="images/source-install.png" height="700" /></b></td>
|
||||
<td><b style="font-size:30px"><img src="images/source-settings.jpg" height="700" /></b></td>
|
||||
<td><b style="font-size:30px"><img src="images/source-settings.png" height="700" /></b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Install a new source</td>
|
||||
@@ -54,8 +52,8 @@ When a user enters a search term into the search bar, the query is posted to th
|
||||
|
||||
<table border="0">
|
||||
<tr>
|
||||
<td><b style="font-size:30px"><img src="images/search-list.jpg" height="700" /></b></td>
|
||||
<td><b style="font-size:30px"><img src="images/search-preview.jpg" height="700" /></b></td>
|
||||
<td><b style="font-size:30px"><img src="images/search-list.png" height="700" /></b></td>
|
||||
<td><b style="font-size:30px"><img src="images/search-preview.png" height="700" /></b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Search (list)</td>
|
||||
@@ -71,7 +69,7 @@ Creators are able to configure their profile using NeoPass.
|
||||
|
||||
<table border="0">
|
||||
<tr>
|
||||
<td><b style="font-size:30px"><img src="images/channel.jpg" height="700" /></b></td>
|
||||
<td><b style="font-size:30px"><img src="images/channel.png" height="700" /></b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Channel</td>
|
||||
@@ -112,7 +110,7 @@ The app offers a lot of settings customizing how the app looks and feels. An exa
|
||||
|
||||
<table border="0">
|
||||
<tr>
|
||||
<td><b style="font-size:30px"><img src="images/settings.jpg" height="700" /></b></td>
|
||||
<td><b style="font-size:30px"><img src="images/settings.png" height="700" /></b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Settings</td>
|
||||
@@ -125,8 +123,8 @@ Playlists allow you to make a collection of videos that you can create and custo
|
||||
|
||||
<table border="0">
|
||||
<tr>
|
||||
<td><b style="font-size:30px"><img src="images/playlists.jpg" height="700" /></b></td>
|
||||
<td><b style="font-size:30px"><img src="images/playlist.jpg" height="700" /></b></td>
|
||||
<td><b style="font-size:30px"><img src="images/playlists.png" height="700" /></b></td>
|
||||
<td><b style="font-size:30px"><img src="images/playlist.png" height="700" /></b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Playlists</td>
|
||||
@@ -142,7 +140,7 @@ Both individual videos and playlists can be downloaded for local, offline playba
|
||||
|
||||
<table border="0">
|
||||
<tr>
|
||||
<td><b style="font-size:30px"><img src="images/downloads.jpg" height="700" /></b></td>
|
||||
<td><b style="font-size:30px"><img src="images/downloads.png" height="700" /></b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Downloads</td>
|
||||
@@ -157,7 +155,7 @@ For more information about casting please click [here](./docs/casting.md).
|
||||
|
||||
<table border="0">
|
||||
<tr>
|
||||
<td><b style="font-size:30px"><img src="images/casting.jpg" height="700" /></b></td>
|
||||
<td><b style="font-size:30px"><img src="images/casting.png" height="700" /></b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Casting</td>
|
||||
@@ -182,6 +180,12 @@ In the future we hope to offer users the choice of their desired recommendation
|
||||
|
||||
1. Download a copy of the repository.
|
||||
2. Open the project in Android Studio: Once the repository is cloned, you can open it in Android Studio by selecting "Open an Existing Project" from the welcome screen and navigating to the directory where you cloned the repository.
|
||||
3. Open the terminal in Android Studio by clicking on the terminal icon on bottom left and run the following command:
|
||||
|
||||
```sh
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
|
||||
3. Build the project: With the project open in Android Studio, you can build it by selecting "Build > Make Project" from the main menu. This will compile the code and generate an APK file that you can install on your device or emulator.
|
||||
4. Run the project: To run the project, select "Run > Run 'app'" from the main menu. This will launch the app on your device or emulator, allowing you to test it and make any necessary changes.
|
||||
|
||||
@@ -199,7 +203,6 @@ Create a tag on the master branch, incrementing the last version number by 1 (fo
|
||||
|
||||
Click on the CI/CD tab, you should now see the tests and build are in progress. If the build succeeds the last step will become available. The last step is a manual action which can be triggered by clicking the run button on the action. This action will deploy the build to all users using the app through auto-update.
|
||||
|
||||
|
||||
## Documentation
|
||||
|
||||
The documentation can be found [here](https://gitlab.futo.org/videostreaming/documents/-/wikis/API-Overview).
|
||||
|
||||
@@ -51,7 +51,6 @@
|
||||
android:name=".activities.MainActivity"
|
||||
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
|
||||
android:exported="true"
|
||||
android:screenOrientation="sensorPortrait"
|
||||
android:theme="@style/Theme.FutoVideo.NoActionBar"
|
||||
android:launchMode="singleTask"
|
||||
android:resizeableActivity="true"
|
||||
@@ -146,11 +145,9 @@
|
||||
<data android:scheme="polycentric" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".activities.TestActivity"
|
||||
android:theme="@style/Theme.FutoVideo.NoActionBar" />
|
||||
|
||||
<activity
|
||||
android:name=".activities.SettingsActivity"
|
||||
android:screenOrientation="sensorPortrait"
|
||||
@@ -173,7 +170,6 @@
|
||||
android:theme="@style/Theme.FutoVideo.NoActionBar" />
|
||||
<activity
|
||||
android:name=".activities.AddSourceActivity"
|
||||
android:screenOrientation="sensorPortrait"
|
||||
android:exported="true"
|
||||
android:theme="@style/Theme.FutoVideo.NoActionBar">
|
||||
<intent-filter>
|
||||
@@ -217,7 +213,6 @@
|
||||
android:name=".activities.ManageTabsActivity"
|
||||
android:screenOrientation="sensorPortrait"
|
||||
android:theme="@style/Theme.FutoVideo.NoActionBar" />
|
||||
|
||||
<activity
|
||||
android:name=".activities.QRCaptureActivity"
|
||||
android:screenOrientation="sensorPortrait"
|
||||
@@ -226,5 +221,17 @@
|
||||
android:name=".activities.FCastGuideActivity"
|
||||
android:screenOrientation="sensorPortrait"
|
||||
android:theme="@style/Theme.FutoVideo.NoActionBar" />
|
||||
<activity
|
||||
android:name=".activities.SyncHomeActivity"
|
||||
android:screenOrientation="sensorPortrait"
|
||||
android:theme="@style/Theme.FutoVideo.NoActionBar" />
|
||||
<activity
|
||||
android:name=".activities.SyncPairActivity"
|
||||
android:screenOrientation="sensorPortrait"
|
||||
android:theme="@style/Theme.FutoVideo.NoActionBar" />
|
||||
<activity
|
||||
android:name=".activities.SyncShowPairingCodeActivity"
|
||||
android:screenOrientation="sensorPortrait"
|
||||
android:theme="@style/Theme.FutoVideo.NoActionBar" />
|
||||
</application>
|
||||
</manifest>
|
||||
@@ -367,6 +367,16 @@ class VideoUrlSource {
|
||||
this.requestModifier = obj.requestModifier;
|
||||
}
|
||||
}
|
||||
class VideoUrlWidevineSource extends VideoUrlSource {
|
||||
constructor(obj) {
|
||||
super(obj);
|
||||
this.plugin_type = "VideoUrlWidevineSource";
|
||||
|
||||
this.licenseUri = obj.licenseUri;
|
||||
if(obj.getLicenseRequestExecutor)
|
||||
this.getLicenseRequestExecutor = obj.getLicenseRequestExecutor;
|
||||
}
|
||||
}
|
||||
class VideoUrlRangeSource extends VideoUrlSource {
|
||||
constructor(obj) {
|
||||
super(obj);
|
||||
@@ -399,8 +409,26 @@ class AudioUrlWidevineSource extends AudioUrlSource {
|
||||
super(obj);
|
||||
this.plugin_type = "AudioUrlWidevineSource";
|
||||
|
||||
this.bearerToken = obj.bearerToken;
|
||||
this.licenseUri = obj.licenseUri;
|
||||
if(obj.getLicenseRequestExecutor)
|
||||
this.getLicenseRequestExecutor = obj.getLicenseRequestExecutor;
|
||||
|
||||
// deprecated api conversion
|
||||
if(obj.bearerToken) {
|
||||
this.getLicenseRequestExecutor = () => {
|
||||
return {
|
||||
executeRequest: (url, _headers, _method, license_request_data) => {
|
||||
return http.POST(
|
||||
url,
|
||||
license_request_data,
|
||||
{ Authorization: `Bearer ${obj.bearerToken}` },
|
||||
false,
|
||||
true
|
||||
).body
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
class AudioUrlRangeSource extends AudioUrlSource {
|
||||
@@ -443,6 +471,16 @@ class DashSource {
|
||||
this.requestModifier = obj.requestModifier;
|
||||
}
|
||||
}
|
||||
class DashWidevineSource extends DashSource {
|
||||
constructor(obj) {
|
||||
super(obj);
|
||||
this.plugin_type = "DashWidevineSource";
|
||||
|
||||
this.licenseUri = obj.licenseUri;
|
||||
if(obj.getLicenseRequestExecutor)
|
||||
this.getLicenseRequestExecutor = obj.getLicenseRequestExecutor;
|
||||
}
|
||||
}
|
||||
class DashManifestRawSource {
|
||||
constructor(obj) {
|
||||
obj = obj ?? {};
|
||||
|
||||
@@ -1,122 +0,0 @@
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.hardware.Sensor
|
||||
import android.hardware.SensorEvent
|
||||
import android.hardware.SensorEventListener
|
||||
import android.hardware.SensorManager
|
||||
import com.futo.platformplayer.constructs.Event1
|
||||
import com.futo.platformplayer.logging.Logger
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
||||
class AdvancedOrientationListener(private val activity: Activity, private val lifecycleScope: CoroutineScope) {
|
||||
private val sensorManager: SensorManager = activity.getSystemService(Context.SENSOR_SERVICE) as SensorManager
|
||||
private val accelerometer: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
|
||||
private val magnetometer: Sensor? = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD)
|
||||
|
||||
private var lastOrientation: Int = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
|
||||
private var lastStableOrientation: Int = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
|
||||
private var lastOrientationChangeTime = 0L
|
||||
private val debounceTime = 200L
|
||||
private val stabilityThresholdTime = 800L
|
||||
private var deviceAspectRatio: Float = 1.0f
|
||||
|
||||
private val gravity = FloatArray(3)
|
||||
private val geomagnetic = FloatArray(3)
|
||||
private val rotationMatrix = FloatArray(9)
|
||||
private val orientationAngles = FloatArray(3)
|
||||
|
||||
val onOrientationChanged = Event1<Int>()
|
||||
|
||||
private val sensorListener = object : SensorEventListener {
|
||||
override fun onSensorChanged(event: SensorEvent) {
|
||||
when (event.sensor.type) {
|
||||
Sensor.TYPE_ACCELEROMETER -> {
|
||||
System.arraycopy(event.values, 0, gravity, 0, gravity.size)
|
||||
}
|
||||
Sensor.TYPE_MAGNETIC_FIELD -> {
|
||||
System.arraycopy(event.values, 0, geomagnetic, 0, geomagnetic.size)
|
||||
}
|
||||
}
|
||||
|
||||
if (gravity.isNotEmpty() && geomagnetic.isNotEmpty()) {
|
||||
val success = SensorManager.getRotationMatrix(rotationMatrix, null, gravity, geomagnetic)
|
||||
if (success) {
|
||||
SensorManager.getOrientation(rotationMatrix, orientationAngles)
|
||||
|
||||
val azimuth = Math.toDegrees(orientationAngles[0].toDouble()).toFloat()
|
||||
val pitch = Math.toDegrees(orientationAngles[1].toDouble()).toFloat()
|
||||
val roll = Math.toDegrees(orientationAngles[2].toDouble()).toFloat()
|
||||
|
||||
val newOrientation = when {
|
||||
roll in -155f .. -15f && isWithinThreshold(pitch, 0f, 30.0) -> {
|
||||
ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
|
||||
}
|
||||
roll in 15f .. 155f && isWithinThreshold(pitch, 0f, 30.0) -> {
|
||||
ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
|
||||
}
|
||||
isWithinThreshold(pitch, -90f, 30.0 * deviceAspectRatio) && roll in -15f .. 15f -> {
|
||||
ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
|
||||
}
|
||||
isWithinThreshold(pitch, 90f, 30.0 * deviceAspectRatio) && roll in -15f .. 15f -> {
|
||||
ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT
|
||||
}
|
||||
else -> lastOrientation
|
||||
}
|
||||
|
||||
//Logger.i("AdvancedOrientationListener", "newOrientation = ${newOrientation}, roll = ${roll}, pitch = ${pitch}, azimuth = ${azimuth}")
|
||||
|
||||
if (newOrientation != lastStableOrientation) {
|
||||
val currentTime = System.currentTimeMillis()
|
||||
if (currentTime - lastOrientationChangeTime > debounceTime) {
|
||||
lastOrientationChangeTime = currentTime
|
||||
lastStableOrientation = newOrientation
|
||||
|
||||
lifecycleScope.launch(Dispatchers.Main) {
|
||||
try {
|
||||
delay(stabilityThresholdTime)
|
||||
if (newOrientation == lastStableOrientation) {
|
||||
lastOrientation = newOrientation
|
||||
onOrientationChanged.emit(newOrientation)
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
Logger.i(TAG, "Failed to trigger onOrientationChanged", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {}
|
||||
}
|
||||
|
||||
private fun isWithinThreshold(value: Float, target: Float, threshold: Double): Boolean {
|
||||
return Math.abs(value - target) <= threshold
|
||||
}
|
||||
|
||||
init {
|
||||
sensorManager.registerListener(sensorListener, accelerometer, SensorManager.SENSOR_DELAY_GAME)
|
||||
sensorManager.registerListener(sensorListener, magnetometer, SensorManager.SENSOR_DELAY_GAME)
|
||||
|
||||
val metrics = activity.resources.displayMetrics
|
||||
deviceAspectRatio = (metrics.heightPixels.toFloat() / metrics.widthPixels.toFloat())
|
||||
if (deviceAspectRatio == 0.0f)
|
||||
deviceAspectRatio = 1.0f
|
||||
|
||||
lastOrientation = activity.resources.configuration.orientation
|
||||
}
|
||||
|
||||
fun stopListening() {
|
||||
sensorManager.unregisterListener(sensorListener)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = "AdvancedOrientationListener"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
package com.futo.platformplayer
|
||||
|
||||
import com.google.common.base.Preconditions
|
||||
import com.google.common.io.ByteStreams
|
||||
import com.google.common.primitives.Ints
|
||||
import com.google.common.primitives.Longs
|
||||
import java.io.DataInput
|
||||
import java.io.DataInputStream
|
||||
import java.io.EOFException
|
||||
import java.io.FilterInputStream
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
|
||||
class LittleEndianDataInputStream
|
||||
/**
|
||||
* Creates a `LittleEndianDataInputStream` that wraps the given stream.
|
||||
*
|
||||
* @param in the stream to delegate to
|
||||
*/
|
||||
(`in`: InputStream?) : FilterInputStream(Preconditions.checkNotNull(`in`)), DataInput {
|
||||
/** This method will throw an [UnsupportedOperationException]. */
|
||||
override fun readLine(): String {
|
||||
throw UnsupportedOperationException("readLine is not supported")
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun readFully(b: ByteArray) {
|
||||
ByteStreams.readFully(this, b)
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun readFully(b: ByteArray, off: Int, len: Int) {
|
||||
ByteStreams.readFully(this, b, off, len)
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun skipBytes(n: Int): Int {
|
||||
return `in`.skip(n.toLong()).toInt()
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun readUnsignedByte(): Int {
|
||||
val b1 = `in`.read()
|
||||
if (0 > b1) {
|
||||
throw EOFException()
|
||||
}
|
||||
|
||||
return b1
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an unsigned `short` as specified by [DataInputStream.readUnsignedShort],
|
||||
* except using little-endian byte order.
|
||||
*
|
||||
* @return the next two bytes of the input stream, interpreted as an unsigned 16-bit integer in
|
||||
* little-endian byte order
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
@Throws(IOException::class)
|
||||
override fun readUnsignedShort(): Int {
|
||||
val b1 = readAndCheckByte()
|
||||
val b2 = readAndCheckByte()
|
||||
|
||||
return Ints.fromBytes(0.toByte(), 0.toByte(), b2, b1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an integer as specified by [DataInputStream.readInt], except using little-endian
|
||||
* byte order.
|
||||
*
|
||||
* @return the next four bytes of the input stream, interpreted as an `int` in little-endian
|
||||
* byte order
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
@Throws(IOException::class)
|
||||
override fun readInt(): Int {
|
||||
val b1 = readAndCheckByte()
|
||||
val b2 = readAndCheckByte()
|
||||
val b3 = readAndCheckByte()
|
||||
val b4 = readAndCheckByte()
|
||||
|
||||
return Ints.fromBytes(b4, b3, b2, b1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a `long` as specified by [DataInputStream.readLong], except using
|
||||
* little-endian byte order.
|
||||
*
|
||||
* @return the next eight bytes of the input stream, interpreted as a `long` in
|
||||
* little-endian byte order
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
@Throws(IOException::class)
|
||||
override fun readLong(): Long {
|
||||
val b1 = readAndCheckByte()
|
||||
val b2 = readAndCheckByte()
|
||||
val b3 = readAndCheckByte()
|
||||
val b4 = readAndCheckByte()
|
||||
val b5 = readAndCheckByte()
|
||||
val b6 = readAndCheckByte()
|
||||
val b7 = readAndCheckByte()
|
||||
val b8 = readAndCheckByte()
|
||||
|
||||
return Longs.fromBytes(b8, b7, b6, b5, b4, b3, b2, b1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a `float` as specified by [DataInputStream.readFloat], except using
|
||||
* little-endian byte order.
|
||||
*
|
||||
* @return the next four bytes of the input stream, interpreted as a `float` in
|
||||
* little-endian byte order
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
@Throws(IOException::class)
|
||||
override fun readFloat(): Float {
|
||||
return java.lang.Float.intBitsToFloat(readInt())
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a `double` as specified by [DataInputStream.readDouble], except using
|
||||
* little-endian byte order.
|
||||
*
|
||||
* @return the next eight bytes of the input stream, interpreted as a `double` in
|
||||
* little-endian byte order
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
@Throws(IOException::class)
|
||||
override fun readDouble(): Double {
|
||||
return java.lang.Double.longBitsToDouble(readLong())
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun readUTF(): String {
|
||||
return DataInputStream(`in`).readUTF()
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a `short` as specified by [DataInputStream.readShort], except using
|
||||
* little-endian byte order.
|
||||
*
|
||||
* @return the next two bytes of the input stream, interpreted as a `short` in little-endian
|
||||
* byte order.
|
||||
* @throws IOException if an I/O error occurs.
|
||||
*/
|
||||
@Throws(IOException::class)
|
||||
override fun readShort(): Short {
|
||||
return readUnsignedShort().toShort()
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a char as specified by [DataInputStream.readChar], except using little-endian
|
||||
* byte order.
|
||||
*
|
||||
* @return the next two bytes of the input stream, interpreted as a `char` in little-endian
|
||||
* byte order
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
@Throws(IOException::class)
|
||||
override fun readChar(): Char {
|
||||
return readUnsignedShort().toChar()
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun readByte(): Byte {
|
||||
return readUnsignedByte().toByte()
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun readBoolean(): Boolean {
|
||||
return readUnsignedByte() != 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a byte from the input stream checking that the end of file (EOF) has not been
|
||||
* encountered.
|
||||
*
|
||||
* @return byte read from input
|
||||
* @throws IOException if an error is encountered while reading
|
||||
* @throws EOFException if the end of file (EOF) is encountered.
|
||||
*/
|
||||
@Throws(IOException::class, EOFException::class)
|
||||
private fun readAndCheckByte(): Byte {
|
||||
val b1 = `in`.read()
|
||||
|
||||
if (-1 == b1) {
|
||||
throw EOFException()
|
||||
}
|
||||
|
||||
return b1.toByte()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
package com.futo.platformplayer
|
||||
|
||||
import com.google.common.base.Preconditions
|
||||
import com.google.common.primitives.Longs
|
||||
import java.io.*
|
||||
|
||||
class LittleEndianDataOutputStream
|
||||
/**
|
||||
* Creates a `LittleEndianDataOutputStream` that wraps the given stream.
|
||||
*
|
||||
* @param out the stream to delegate to
|
||||
*/
|
||||
(out: OutputStream?) : FilterOutputStream(DataOutputStream(Preconditions.checkNotNull(out))),
|
||||
DataOutput {
|
||||
@Throws(IOException::class)
|
||||
override fun write(b: ByteArray, off: Int, len: Int) {
|
||||
// Override slow FilterOutputStream impl
|
||||
out.write(b, off, len)
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun writeBoolean(v: Boolean) {
|
||||
(out as DataOutputStream).writeBoolean(v)
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun writeByte(v: Int) {
|
||||
(out as DataOutputStream).writeByte(v)
|
||||
}
|
||||
|
||||
@Deprecated(
|
||||
"""The semantics of {@code writeBytes(String s)} are considered dangerous. Please use
|
||||
{@link #writeUTF(String s)}, {@link #writeChars(String s)} or another write method instead."""
|
||||
)
|
||||
@Throws(
|
||||
IOException::class
|
||||
)
|
||||
override fun writeBytes(s: String) {
|
||||
(out as DataOutputStream).writeBytes(s)
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a char as specified by [DataOutputStream.writeChar], except using
|
||||
* little-endian byte order.
|
||||
*
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
@Throws(IOException::class)
|
||||
override fun writeChar(v: Int) {
|
||||
writeShort(v)
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a `String` as specified by [DataOutputStream.writeChars], except
|
||||
* each character is written using little-endian byte order.
|
||||
*
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
@Throws(IOException::class)
|
||||
override fun writeChars(s: String) {
|
||||
for (i in 0 until s.length) {
|
||||
writeChar(s[i].code)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a `double` as specified by [DataOutputStream.writeDouble], except
|
||||
* using little-endian byte order.
|
||||
*
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
@Throws(IOException::class)
|
||||
override fun writeDouble(v: Double) {
|
||||
writeLong(java.lang.Double.doubleToLongBits(v))
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a `float` as specified by [DataOutputStream.writeFloat], except using
|
||||
* little-endian byte order.
|
||||
*
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
@Throws(IOException::class)
|
||||
override fun writeFloat(v: Float) {
|
||||
writeInt(java.lang.Float.floatToIntBits(v))
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes an `int` as specified by [DataOutputStream.writeInt], except using
|
||||
* little-endian byte order.
|
||||
*
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
@Throws(IOException::class)
|
||||
override fun writeInt(v: Int) {
|
||||
val bytes = byteArrayOf(
|
||||
(0xFF and v).toByte(),
|
||||
(0xFF and (v shr 8)).toByte(),
|
||||
(0xFF and (v shr 16)).toByte(),
|
||||
(0xFF and (v shr 24)).toByte()
|
||||
)
|
||||
out.write(bytes)
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a `long` as specified by [DataOutputStream.writeLong], except using
|
||||
* little-endian byte order.
|
||||
*
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
@Throws(IOException::class)
|
||||
override fun writeLong(v: Long) {
|
||||
val bytes = Longs.toByteArray(java.lang.Long.reverseBytes(v))
|
||||
write(bytes, 0, bytes.size)
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a `short` as specified by [DataOutputStream.writeShort], except using
|
||||
* little-endian byte order.
|
||||
*
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
@Throws(IOException::class)
|
||||
override fun writeShort(v: Int) {
|
||||
val bytes = byteArrayOf(
|
||||
(0xFF and v).toByte(),
|
||||
(0xFF and (v shr 8)).toByte()
|
||||
)
|
||||
out.write(bytes)
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun writeUTF(str: String) {
|
||||
(out as DataOutputStream).writeUTF(str)
|
||||
}
|
||||
|
||||
// Overriding close() because FilterOutputStream's close() method pre-JDK8 has bad behavior:
|
||||
// it silently ignores any exception thrown by flush(). Instead, just close the delegate stream.
|
||||
// It should flush itself if necessary.
|
||||
@Throws(IOException::class)
|
||||
override fun close() {
|
||||
out.close()
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,8 @@ package com.futo.platformplayer
|
||||
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Context
|
||||
import android.content.Context.POWER_SERVICE
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.PowerManager
|
||||
import android.webkit.CookieManager
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.futo.platformplayer.activities.MainActivity
|
||||
@@ -14,6 +11,7 @@ import com.futo.platformplayer.activities.ManageTabsActivity
|
||||
import com.futo.platformplayer.activities.PolycentricHomeActivity
|
||||
import com.futo.platformplayer.activities.PolycentricProfileActivity
|
||||
import com.futo.platformplayer.activities.SettingsActivity
|
||||
import com.futo.platformplayer.activities.SyncHomeActivity
|
||||
import com.futo.platformplayer.api.http.ManagedHttpClient
|
||||
import com.futo.platformplayer.constructs.Event0
|
||||
import com.futo.platformplayer.fragment.mainactivity.bottombar.MenuBottomBarFragment
|
||||
@@ -26,7 +24,6 @@ import com.futo.platformplayer.states.StateBackup
|
||||
import com.futo.platformplayer.states.StateCache
|
||||
import com.futo.platformplayer.states.StateMeta
|
||||
import com.futo.platformplayer.states.StatePayment
|
||||
import com.futo.platformplayer.states.StatePlayer
|
||||
import com.futo.platformplayer.states.StatePolycentric
|
||||
import com.futo.platformplayer.states.StateUpdate
|
||||
import com.futo.platformplayer.stores.FragmentedStorage
|
||||
@@ -36,9 +33,7 @@ import com.futo.platformplayer.views.fields.DropdownFieldOptionsId
|
||||
import com.futo.platformplayer.views.fields.FieldForm
|
||||
import com.futo.platformplayer.views.fields.FormField
|
||||
import com.futo.platformplayer.views.fields.FormFieldButton
|
||||
import com.futo.platformplayer.views.fields.FormFieldWarning
|
||||
import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuItem
|
||||
import com.stripe.android.customersheet.injection.CustomerSheetViewModelModule_Companion_ContextFactory.context
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
@@ -63,6 +58,15 @@ class Settings : FragmentedStorageFileJson() {
|
||||
@Transient
|
||||
val onTabsChanged = Event0();
|
||||
|
||||
@FormField(R.string.sync_grayjay, FieldForm.BUTTON, R.string.sync_grayjay_description, -8)
|
||||
@FormFieldButton(R.drawable.ic_update)
|
||||
fun syncGrayjay() {
|
||||
SettingsActivity.getActivity()?.let {
|
||||
it.startActivity(Intent(it, SyncHomeActivity::class.java))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@FormField(R.string.manage_polycentric_identity, FieldForm.BUTTON, R.string.manage_your_polycentric_identity, -7)
|
||||
@FormFieldButton(R.drawable.ic_person)
|
||||
fun managePolycentricIdentity() {
|
||||
@@ -140,7 +144,6 @@ class Settings : FragmentedStorageFileJson() {
|
||||
fun import() {
|
||||
val act = SettingsActivity.getActivity() ?: return;
|
||||
val intent = MainActivity.getImportOptionsIntent(act);
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK;
|
||||
act.startActivity(intent);
|
||||
}
|
||||
|
||||
@@ -417,8 +420,6 @@ class Settings : FragmentedStorageFileJson() {
|
||||
@DropdownFieldOptionsId(R.array.system_enabled_disabled_array)
|
||||
var autoRotate: Int = 2;
|
||||
|
||||
fun isAutoRotate() = (autoRotate == 1 && !StatePlayer.instance.rotationLock) || (autoRotate == 2 && StateApp.instance.getCurrentSystemAutoRotate() && !StatePlayer.instance.rotationLock);
|
||||
|
||||
@FormField(R.string.background_behavior, FieldForm.DROPDOWN, -1, 7)
|
||||
@DropdownFieldOptionsId(R.array.player_background_behavior)
|
||||
var backgroundPlay: Int = 2;
|
||||
@@ -471,14 +472,22 @@ class Settings : FragmentedStorageFileJson() {
|
||||
@FormField(R.string.full_screen_portrait, FieldForm.TOGGLE, R.string.allow_full_screen_portrait, 13)
|
||||
var fullscreenPortrait: Boolean = false;
|
||||
|
||||
@FormField(R.string.reverse_portrait, FieldForm.TOGGLE, R.string.reverse_portrait_description, 14)
|
||||
var reversePortrait: Boolean = false;
|
||||
|
||||
@FormField(R.string.prefer_webm, FieldForm.TOGGLE, R.string.prefer_webm_description, 14)
|
||||
@FormField(R.string.prefer_webm, FieldForm.TOGGLE, R.string.prefer_webm_description, 18)
|
||||
var preferWebmVideo: Boolean = false;
|
||||
@FormField(R.string.prefer_webm_audio, FieldForm.TOGGLE, R.string.prefer_webm_audio_description, 15)
|
||||
@FormField(R.string.prefer_webm_audio, FieldForm.TOGGLE, R.string.prefer_webm_audio_description, 19)
|
||||
var preferWebmAudio: Boolean = false;
|
||||
|
||||
@FormField(R.string.allow_under_cutout, FieldForm.TOGGLE, R.string.allow_under_cutout_description, 16)
|
||||
@FormField(R.string.allow_under_cutout, FieldForm.TOGGLE, R.string.allow_under_cutout_description, 20)
|
||||
var allowVideoToGoUnderCutout: Boolean = true;
|
||||
|
||||
@FormField(R.string.autoplay, FieldForm.TOGGLE, R.string.autoplay, 21)
|
||||
var autoplay: Boolean = false;
|
||||
|
||||
@FormField(R.string.delete_watchlist_on_finish, FieldForm.TOGGLE, R.string.delete_watchlist_on_finish_description, 22)
|
||||
var deleteFromWatchLaterAuto: Boolean = true;
|
||||
}
|
||||
|
||||
@FormField(R.string.comments, "group", R.string.comments_description, 6)
|
||||
@@ -494,6 +503,9 @@ class Settings : FragmentedStorageFileJson() {
|
||||
@FormField(R.string.default_recommendations, FieldForm.TOGGLE, R.string.default_recommendations_description, 0)
|
||||
var recommendationsDefault: Boolean = false;
|
||||
|
||||
@FormField(R.string.hide_recommendations, FieldForm.TOGGLE, R.string.hide_recommendations_description, 0)
|
||||
var hideRecommendations: Boolean = false;
|
||||
|
||||
@FormField(R.string.bad_reputation_comments_fading, FieldForm.TOGGLE, R.string.bad_reputation_comments_fading_description, 0)
|
||||
var badReputationCommentsFading: Boolean = true;
|
||||
|
||||
@@ -545,7 +557,7 @@ class Settings : FragmentedStorageFileJson() {
|
||||
class Browsing {
|
||||
@FormField(R.string.enable_video_cache, FieldForm.TOGGLE, R.string.cache_to_quickly_load_previously_fetched_videos, 0)
|
||||
@Serializable(with = FlexibleBooleanSerializer::class)
|
||||
var videoCache: Boolean = true;
|
||||
var videoCache: Boolean = false; //Temporary default disabled to prevent ui freeze?
|
||||
}
|
||||
|
||||
@FormField(R.string.casting, "group", R.string.configure_casting, 9)
|
||||
@@ -833,10 +845,14 @@ class Settings : FragmentedStorageFileJson() {
|
||||
|
||||
@FormField(R.string.clear_payment, FieldForm.BUTTON, R.string.deletes_license_keys_from_app, 2)
|
||||
fun clearPayment() {
|
||||
StatePayment.instance.clearLicenses();
|
||||
SettingsActivity.getActivity()?.let {
|
||||
UIDialogs.toast(it, it.getString(R.string.licenses_cleared_might_require_app_restart));
|
||||
it.reloadSettings();
|
||||
SettingsActivity.getActivity()?.let { context ->
|
||||
UIDialogs.showConfirmationDialog(context, "Are you sure you want to delete your license?", {
|
||||
StatePayment.instance.clearLicenses();
|
||||
SettingsActivity.getActivity()?.let {
|
||||
UIDialogs.toast(it, it.getString(R.string.licenses_cleared_might_require_app_restart));
|
||||
it.reloadSettings();
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -845,12 +861,14 @@ class Settings : FragmentedStorageFileJson() {
|
||||
var other = Other();
|
||||
@Serializable
|
||||
class Other {
|
||||
@FormField(R.string.bypass_rotation_prevention, FieldForm.TOGGLE, R.string.bypass_rotation_prevention_description, 1)
|
||||
@FormFieldWarning(R.string.bypass_rotation_prevention_warning)
|
||||
var bypassRotationPrevention: Boolean = false;
|
||||
@FormField(R.string.playlist_delete_confirmation, FieldForm.TOGGLE, R.string.playlist_delete_confirmation_description, 2)
|
||||
var playlistDeleteConfirmation: Boolean = true;
|
||||
|
||||
@FormField(R.string.enable_polycentric, FieldForm.TOGGLE, R.string.can_be_disabled_when_you_are_experiencing_issues, 1)
|
||||
@FormField(R.string.enable_polycentric, FieldForm.TOGGLE, R.string.can_be_disabled_when_you_are_experiencing_issues, 3)
|
||||
var polycentricEnabled: Boolean = true;
|
||||
|
||||
@FormField(R.string.polycentric_local_cache, FieldForm.TOGGLE, R.string.polycentric_local_cache_description, 4)
|
||||
var polycentricLocalCache: Boolean = true;
|
||||
}
|
||||
|
||||
@FormField(R.string.gesture_controls, FieldForm.GROUP, -1, 19)
|
||||
@@ -882,7 +900,24 @@ class Settings : FragmentedStorageFileJson() {
|
||||
var pan: Boolean = true;
|
||||
}
|
||||
|
||||
@FormField(R.string.info, FieldForm.GROUP, -1, 20)
|
||||
@FormField(R.string.synchronization, FieldForm.GROUP, -1, 20)
|
||||
var synchronization = Synchronization();
|
||||
@Serializable
|
||||
class Synchronization {
|
||||
@FormField(R.string.enabled, FieldForm.TOGGLE, R.string.enabled_description, 1)
|
||||
var enabled: Boolean = true;
|
||||
|
||||
@FormField(R.string.broadcast, FieldForm.TOGGLE, R.string.broadcast_description, 1)
|
||||
var broadcast: Boolean = false;
|
||||
|
||||
@FormField(R.string.connect_discovered, FieldForm.TOGGLE, R.string.connect_discovered_description, 2)
|
||||
var connectDiscovered: Boolean = true;
|
||||
|
||||
@FormField(R.string.connect_last, FieldForm.TOGGLE, R.string.connect_last_description, 3)
|
||||
var connectLast: Boolean = true;
|
||||
}
|
||||
|
||||
@FormField(R.string.info, FieldForm.GROUP, -1, 21)
|
||||
var info = Info();
|
||||
@Serializable
|
||||
class Info {
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
package com.futo.platformplayer
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.hardware.SensorManager
|
||||
import android.view.OrientationEventListener
|
||||
import com.futo.platformplayer.constructs.Event1
|
||||
import com.futo.platformplayer.logging.Logger
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class SimpleOrientationListener(
|
||||
private val activity: Activity,
|
||||
private val lifecycleScope: CoroutineScope
|
||||
) {
|
||||
private var lastOrientation: Int = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
|
||||
private var lastStableOrientation: Int = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
|
||||
private val stabilityThresholdTime = 500L
|
||||
|
||||
val onOrientationChanged = Event1<Int>()
|
||||
|
||||
private val orientationListener = object : OrientationEventListener(activity, SensorManager.SENSOR_DELAY_UI) {
|
||||
override fun onOrientationChanged(orientation: Int) {
|
||||
val newOrientation = when {
|
||||
orientation in 45..134 -> ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
|
||||
orientation in 135..224 -> ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT
|
||||
orientation in 225..314 -> ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
|
||||
orientation in 315..360 || orientation in 0..44 -> ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
|
||||
else -> lastOrientation
|
||||
}
|
||||
|
||||
if (newOrientation != lastStableOrientation) {
|
||||
lastStableOrientation = newOrientation
|
||||
|
||||
lifecycleScope.launch(Dispatchers.Main) {
|
||||
try {
|
||||
delay(stabilityThresholdTime)
|
||||
if (newOrientation == lastStableOrientation) {
|
||||
lastOrientation = newOrientation
|
||||
onOrientationChanged.emit(newOrientation)
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
Logger.i(TAG, "Failed to trigger onOrientationChanged", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
orientationListener.enable()
|
||||
lastOrientation = activity.resources.configuration.orientation
|
||||
}
|
||||
|
||||
fun stopListening() {
|
||||
orientationListener.disable()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = "SimpleOrientationListener"
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Color
|
||||
import android.net.Uri
|
||||
import android.text.Layout
|
||||
import android.text.method.ScrollingMovementMethod
|
||||
import android.util.TypedValue
|
||||
import android.view.Gravity
|
||||
@@ -198,7 +199,6 @@ class UIDialogs {
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
|
||||
fun showDialog(context: Context, icon: Int, text: String, textDetails: String? = null, code: String? = null, defaultCloseAction: Int, vararg actions: Action) {
|
||||
val builder = AlertDialog.Builder(context);
|
||||
val view = LayoutInflater.from(context).inflate(R.layout.dialog_multi_button, null);
|
||||
@@ -214,18 +214,19 @@ class UIDialogs {
|
||||
this.text = text;
|
||||
};
|
||||
view.findViewById<TextView>(R.id.dialog_text_details).apply {
|
||||
if(textDetails == null)
|
||||
if (textDetails == null)
|
||||
this.visibility = View.GONE;
|
||||
else
|
||||
else {
|
||||
this.text = textDetails;
|
||||
}
|
||||
};
|
||||
view.findViewById<TextView>(R.id.dialog_text_code).apply {
|
||||
if(code == null)
|
||||
this.visibility = View.GONE;
|
||||
if (code == null) this.visibility = View.GONE;
|
||||
else {
|
||||
this.text = code;
|
||||
this.movementMethod = ScrollingMovementMethod.getInstance();
|
||||
this.visibility = View.VISIBLE;
|
||||
this.textAlignment = View.TEXT_ALIGNMENT_VIEW_START
|
||||
}
|
||||
};
|
||||
view.findViewById<LinearLayout>(R.id.dialog_buttons).apply {
|
||||
@@ -348,6 +349,13 @@ class UIDialogs {
|
||||
showDialog(context, R.drawable.ic_error, text, null, null, 0, cancelButtonAction, confirmButtonAction)
|
||||
}
|
||||
|
||||
fun showConfirmationDialog(context: Context, text: String, action: () -> Unit, cancelAction: (() -> Unit)? = null, doNotAskAgainAction: (() -> Unit)? = null) {
|
||||
val confirmButtonAction = Action(context.getString(R.string.confirm), action, ActionStyle.PRIMARY)
|
||||
val cancelButtonAction = Action(context.getString(R.string.cancel), cancelAction ?: {}, ActionStyle.ACCENT)
|
||||
val doNotAskAgain = Action(context.getString(R.string.do_not_ask_again), doNotAskAgainAction ?: {}, ActionStyle.NONE)
|
||||
showDialog(context, R.drawable.ic_error, text, null, null, 0, doNotAskAgain, cancelButtonAction, confirmButtonAction)
|
||||
}
|
||||
|
||||
fun showUpdateAvailableDialog(context: Context, lastVersion: Int, hideExceptionButtons: Boolean = false) {
|
||||
val dialog = AutoUpdateDialog(context);
|
||||
registerDialogOpened(dialog);
|
||||
|
||||
@@ -25,6 +25,7 @@ import com.futo.platformplayer.api.media.models.subtitles.ISubtitleSource
|
||||
import com.futo.platformplayer.api.media.models.video.IPlatformVideo
|
||||
import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails
|
||||
import com.futo.platformplayer.api.media.models.video.SerializedPlatformVideo
|
||||
import com.futo.platformplayer.api.media.platforms.js.JSClient
|
||||
import com.futo.platformplayer.api.media.platforms.js.models.sources.JSDashManifestRawAudioSource
|
||||
import com.futo.platformplayer.api.media.platforms.js.models.sources.JSDashManifestRawSource
|
||||
import com.futo.platformplayer.downloads.VideoLocal
|
||||
@@ -879,6 +880,12 @@ class UISlideOverlays {
|
||||
val items = arrayListOf<View>();
|
||||
val lastUpdated = StatePlaylists.instance.getLastUpdatedPlaylist();
|
||||
|
||||
val isLimited = video?.url != null && StatePlatform.instance.getContentClientOrNull(video!!.url)?.let {
|
||||
if (it is JSClient)
|
||||
return@let it.config.reduceFunctionsInLimitedVersion && BuildConfig.IS_PLAYSTORE_BUILD
|
||||
else false;
|
||||
} ?: false;
|
||||
|
||||
if (lastUpdated != null) {
|
||||
items.add(
|
||||
SlideUpMenuGroup(container.context, container.context.getString(R.string.recently_used_playlist), "recentlyusedplaylist",
|
||||
@@ -899,17 +906,18 @@ class UISlideOverlays {
|
||||
val watchLater = StatePlaylists.instance.getWatchLater();
|
||||
items.add(SlideUpMenuGroup(container.context, container.context.getString(R.string.actions), "actions",
|
||||
(listOf(
|
||||
SlideUpMenuItem(
|
||||
container.context,
|
||||
R.drawable.ic_download,
|
||||
container.context.getString(R.string.download),
|
||||
container.context.getString(R.string.download_the_video),
|
||||
tag = "download",
|
||||
call = {
|
||||
showDownloadVideoOverlay(video, container, true);
|
||||
},
|
||||
invokeParent = false
|
||||
),
|
||||
if(!isLimited)
|
||||
SlideUpMenuItem(
|
||||
container.context,
|
||||
R.drawable.ic_download,
|
||||
container.context.getString(R.string.download),
|
||||
container.context.getString(R.string.download_the_video),
|
||||
tag = "download",
|
||||
call = {
|
||||
showDownloadVideoOverlay(video, container, true);
|
||||
},
|
||||
invokeParent = false
|
||||
) else null,
|
||||
SlideUpMenuItem(
|
||||
container.context,
|
||||
R.drawable.ic_share,
|
||||
@@ -936,7 +944,7 @@ class UISlideOverlays {
|
||||
StateMeta.instance.addHiddenCreator(video.author.url);
|
||||
UIDialogs.toast(container.context, "[${video.author.name}] hidden, you may need to reload home");
|
||||
}))
|
||||
+ actions)
|
||||
+ actions).filterNotNull()
|
||||
));
|
||||
items.add(
|
||||
SlideUpMenuGroup(container.context, container.context.getString(R.string.add_to), "addto",
|
||||
@@ -951,7 +959,7 @@ class UISlideOverlays {
|
||||
"${container.context.getString(R.string.add_to)} " + StatePlayer.TYPE_WATCHLATER + "",
|
||||
"${watchLater.size} " + container.context.getString(R.string.videos),
|
||||
tag = "watch later",
|
||||
call = { StatePlaylists.instance.addToWatchLater(SerializedPlatformVideo.fromVideo(video)); }),
|
||||
call = { StatePlaylists.instance.addToWatchLater(SerializedPlatformVideo.fromVideo(video), true); }),
|
||||
SlideUpMenuItem(container.context,
|
||||
R.drawable.ic_history,
|
||||
container.context.getString(R.string.add_to_history),
|
||||
@@ -1032,16 +1040,8 @@ class UISlideOverlays {
|
||||
StatePlayer.TYPE_WATCHLATER,
|
||||
"${watchLater.size} " + container.context.getString(R.string.videos),
|
||||
tag = "watch later",
|
||||
call = { StatePlaylists.instance.addToWatchLater(SerializedPlatformVideo.fromVideo(video)); }),
|
||||
SlideUpMenuItem(
|
||||
container.context,
|
||||
R.drawable.ic_download,
|
||||
container.context.getString(R.string.download),
|
||||
container.context.getString(R.string.download_the_video),
|
||||
tag = container.context.getString(R.string.download),
|
||||
call = { showDownloadVideoOverlay(video, container, true); },
|
||||
invokeParent = false
|
||||
))
|
||||
call = { StatePlaylists.instance.addToWatchLater(SerializedPlatformVideo.fromVideo(video), true); }),
|
||||
)
|
||||
);
|
||||
|
||||
val playlistItems = arrayListOf<SlideUpMenuItem>();
|
||||
|
||||
@@ -26,10 +26,13 @@ import com.futo.platformplayer.engine.V8Plugin
|
||||
import com.futo.platformplayer.logging.Logger
|
||||
import com.futo.platformplayer.models.PlatformVideoWithTime
|
||||
import com.futo.platformplayer.others.PlatformLinkMovementMethod
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
import java.util.*
|
||||
import java.util.concurrent.ThreadLocalRandom
|
||||
|
||||
@@ -230,4 +233,49 @@ fun String.decodeUnicode(): String {
|
||||
i++
|
||||
}
|
||||
return sb.toString()
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> smartMerge(targetArr: List<T>, toMerge: List<T>) : List<T>{
|
||||
val missingToMerge = toMerge.filter { !targetArr.contains(it) }.toList();
|
||||
val newArrResult = targetArr.toMutableList();
|
||||
|
||||
for(missing in missingToMerge) {
|
||||
val newIndex = findNewIndex(toMerge, newArrResult, missing);
|
||||
newArrResult.add(newIndex, missing);
|
||||
}
|
||||
|
||||
return newArrResult;
|
||||
}
|
||||
fun <T> findNewIndex(originalArr: List<T>, newArr: List<T>, item: T): Int{
|
||||
var originalIndex = originalArr.indexOf(item);
|
||||
var newIndex = -1;
|
||||
|
||||
for(i in originalIndex-1 downTo 0) {
|
||||
val previousItem = originalArr[i];
|
||||
val indexInNewArr = newArr.indexOfFirst { it == previousItem };
|
||||
if(indexInNewArr >= 0) {
|
||||
newIndex = indexInNewArr + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(newIndex < 0) {
|
||||
for(i in originalIndex+1 until originalArr.size) {
|
||||
val previousItem = originalArr[i];
|
||||
val indexInNewArr = newArr.indexOfFirst { it == previousItem };
|
||||
if(indexInNewArr >= 0) {
|
||||
newIndex = indexInNewArr - 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(newIndex < 0)
|
||||
return originalArr.size;
|
||||
else
|
||||
return newIndex;
|
||||
}
|
||||
|
||||
fun ByteBuffer.toUtf8String(): String {
|
||||
val remainingBytes = ByteArray(remaining())
|
||||
get(remainingBytes)
|
||||
return String(remainingBytes, Charsets.UTF_8)
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.Configuration
|
||||
import android.net.Uri
|
||||
import android.net.wifi.WifiManager
|
||||
import android.os.Bundle
|
||||
import android.os.StrictMode
|
||||
import android.os.StrictMode.VmPolicy
|
||||
@@ -29,10 +28,12 @@ import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentContainerView
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import com.futo.platformplayer.BuildConfig
|
||||
import com.futo.platformplayer.R
|
||||
import com.futo.platformplayer.Settings
|
||||
import com.futo.platformplayer.UIDialogs
|
||||
import com.futo.platformplayer.api.http.ManagedHttpClient
|
||||
import com.futo.platformplayer.casting.StateCasting
|
||||
import com.futo.platformplayer.constructs.Event1
|
||||
import com.futo.platformplayer.fragment.mainactivity.bottombar.MenuBottomBarFragment
|
||||
@@ -70,6 +71,7 @@ import com.futo.platformplayer.fragment.mainactivity.topbar.NavigationTopBarFrag
|
||||
import com.futo.platformplayer.fragment.mainactivity.topbar.SearchTopBarFragment
|
||||
import com.futo.platformplayer.logging.Logger
|
||||
import com.futo.platformplayer.models.ImportCache
|
||||
import com.futo.platformplayer.models.UrlVideoWithTime
|
||||
import com.futo.platformplayer.setNavigationBarColorAndIcons
|
||||
import com.futo.platformplayer.states.StateApp
|
||||
import com.futo.platformplayer.states.StateBackup
|
||||
@@ -80,6 +82,7 @@ import com.futo.platformplayer.states.StatePlayer
|
||||
import com.futo.platformplayer.states.StatePlaylists
|
||||
import com.futo.platformplayer.states.StateSubscriptions
|
||||
import com.futo.platformplayer.stores.FragmentedStorage
|
||||
import com.futo.platformplayer.stores.StringStorage
|
||||
import com.futo.platformplayer.stores.SubscriptionStorage
|
||||
import com.futo.platformplayer.stores.v2.ManagedStore
|
||||
import com.futo.platformplayer.views.ToastView
|
||||
@@ -87,11 +90,14 @@ import com.futo.polycentric.core.ApiMethods
|
||||
import com.google.gson.JsonParser
|
||||
import com.google.zxing.integration.android.IntentIntegrator
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.io.File
|
||||
import java.io.PrintWriter
|
||||
@@ -101,7 +107,6 @@ import java.util.LinkedList
|
||||
import java.util.Queue
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
|
||||
|
||||
class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||
|
||||
//TODO: Move to dimensions
|
||||
@@ -109,7 +114,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||
private val HEIGHT_VIDEO_MINIMIZED_DP = 60f;
|
||||
|
||||
//Containers
|
||||
lateinit var rootView : MotionLayout;
|
||||
lateinit var rootView: MotionLayout;
|
||||
|
||||
private lateinit var _overlayContainer: FrameLayout;
|
||||
private lateinit var _toastView: ToastView;
|
||||
@@ -166,11 +171,11 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||
lateinit var _fragVideoDetail: VideoDetailFragment;
|
||||
|
||||
//State
|
||||
private val _queue : Queue<Pair<MainFragment, Any?>> = LinkedList();
|
||||
lateinit var fragCurrent : MainFragment private set;
|
||||
private val _queue: Queue<Pair<MainFragment, Any?>> = LinkedList();
|
||||
lateinit var fragCurrent: MainFragment private set;
|
||||
private var _parameterCurrent: Any? = null;
|
||||
|
||||
var fragBeforeOverlay : MainFragment? = null; private set;
|
||||
var fragBeforeOverlay: MainFragment? = null; private set;
|
||||
|
||||
val onNavigated = Event1<MainFragment>();
|
||||
|
||||
@@ -216,15 +221,15 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||
Logger.e("Application", "Uncaught", excp);
|
||||
|
||||
//Resolve invocation chains
|
||||
while(excp is InvocationTargetException || excp is java.lang.RuntimeException) {
|
||||
while (excp is InvocationTargetException || excp is java.lang.RuntimeException) {
|
||||
val before = excp;
|
||||
|
||||
if(excp is InvocationTargetException)
|
||||
if (excp is InvocationTargetException)
|
||||
excp = excp.targetException ?: excp.cause ?: excp;
|
||||
else if(excp is java.lang.RuntimeException)
|
||||
else if (excp is java.lang.RuntimeException)
|
||||
excp = excp.cause ?: excp;
|
||||
|
||||
if(excp == before)
|
||||
if (excp == before)
|
||||
break;
|
||||
}
|
||||
writer.write((excp.message ?: "Empty error") + "\n\n");
|
||||
@@ -246,6 +251,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||
super.attachBaseContext(StateApp.instance.getLocaleContext(newBase))
|
||||
}
|
||||
|
||||
@UnstableApi
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
Logger.i(TAG, "MainActivity Starting");
|
||||
StateApp.instance.setGlobalContext(this, lifecycleScope);
|
||||
@@ -255,7 +261,8 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||
setContentView(R.layout.activity_main);
|
||||
setNavigationBarColorAndIcons();
|
||||
if (Settings.instance.playback.allowVideoToGoUnderCutout)
|
||||
window.attributes.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
|
||||
window.attributes.layoutInDisplayCutoutMode =
|
||||
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
|
||||
|
||||
runBlocking {
|
||||
StatePlatform.instance.updateAvailableClients(this@MainActivity);
|
||||
@@ -329,10 +336,12 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||
updateSegmentPaddings();
|
||||
};
|
||||
_fragVideoDetail.onTransitioning.subscribe {
|
||||
if(it || _fragVideoDetail.state != VideoDetailFragment.State.MINIMIZED)
|
||||
_fragContainerOverlay.elevation = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 15f, resources.displayMetrics);
|
||||
if (it || _fragVideoDetail.state != VideoDetailFragment.State.MINIMIZED)
|
||||
_fragContainerOverlay.elevation =
|
||||
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 15f, resources.displayMetrics);
|
||||
else
|
||||
_fragContainerOverlay.elevation = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5f, resources.displayMetrics);
|
||||
_fragContainerOverlay.elevation =
|
||||
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5f, resources.displayMetrics);
|
||||
}
|
||||
|
||||
_fragVideoDetail.onCloseEvent.subscribe {
|
||||
@@ -349,40 +358,39 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||
_buttonIncognito.alpha = 0f;
|
||||
StateApp.instance.privateModeChanged.subscribe {
|
||||
//Messing with visibility causes some issues with layout ordering?
|
||||
if(it) {
|
||||
if (it) {
|
||||
_buttonIncognito.elevation = 99f;
|
||||
_buttonIncognito.alpha = 1f;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
_buttonIncognito.elevation = -99f;
|
||||
_buttonIncognito.alpha = 0f;
|
||||
}
|
||||
}
|
||||
_buttonIncognito.setOnClickListener {
|
||||
if(!StateApp.instance.privateMode)
|
||||
if (!StateApp.instance.privateMode)
|
||||
return@setOnClickListener;
|
||||
UIDialogs.showDialog(this, R.drawable.ic_disabled_visible_purple, "Disable Privacy Mode",
|
||||
UIDialogs.showDialog(
|
||||
this, R.drawable.ic_disabled_visible_purple, "Disable Privacy Mode",
|
||||
"Do you want to disable privacy mode? New videos will be tracked again.", null, 0,
|
||||
UIDialogs.Action("Cancel", {
|
||||
StateApp.instance.setPrivacyMode(true);
|
||||
}, UIDialogs.ActionStyle.NONE),
|
||||
UIDialogs.Action("Disable", {
|
||||
StateApp.instance.setPrivacyMode(false);
|
||||
}, UIDialogs.ActionStyle.DANGEROUS));
|
||||
}, UIDialogs.ActionStyle.DANGEROUS)
|
||||
);
|
||||
};
|
||||
_fragVideoDetail.onFullscreenChanged.subscribe {
|
||||
Logger.i(TAG, "onFullscreenChanged ${it}");
|
||||
|
||||
if(it) {
|
||||
if (it) {
|
||||
_buttonIncognito.elevation = -99f;
|
||||
_buttonIncognito.alpha = 0f;
|
||||
}
|
||||
else {
|
||||
if(StateApp.instance.privateMode) {
|
||||
} else {
|
||||
if (StateApp.instance.privateMode) {
|
||||
_buttonIncognito.elevation = 99f;
|
||||
_buttonIncognito.alpha = 1f;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
_buttonIncognito.elevation = -99f;
|
||||
_buttonIncognito.alpha = 0f;
|
||||
}
|
||||
@@ -395,7 +403,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||
return@subscribe;
|
||||
}
|
||||
|
||||
if(_fragVideoDetail.state == VideoDetailFragment.State.CLOSED) {
|
||||
if (_fragVideoDetail.state == VideoDetailFragment.State.CLOSED) {
|
||||
if (fragCurrent !is VideoDetailFragment) {
|
||||
val toPlay = StatePlayer.instance.getCurrentQueueItem();
|
||||
navigate(_fragVideoDetail, toPlay);
|
||||
@@ -443,11 +451,12 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||
_fragSubGroupList.topBar = _fragTopBarAdd;
|
||||
|
||||
_fragBrowser.topBar = _fragTopBarNavigation;
|
||||
|
||||
|
||||
fragCurrent = _fragMainHome;
|
||||
|
||||
val defaultTab = Settings.instance.tabs.mapNotNull {
|
||||
val buttonDefinition = MenuBottomBarFragment.buttonDefinitions.firstOrNull { bd -> it.id == bd.id };
|
||||
val buttonDefinition =
|
||||
MenuBottomBarFragment.buttonDefinitions.firstOrNull { bd -> it.id == bd.id };
|
||||
if (buttonDefinition == null) {
|
||||
return@mapNotNull null;
|
||||
} else {
|
||||
@@ -506,7 +515,11 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||
|
||||
//startActivity(Intent(this, TestActivity::class.java));
|
||||
|
||||
val sharedPreferences = getSharedPreferences("GrayjayFirstBoot", Context.MODE_PRIVATE)
|
||||
// updates the requestedOrientation based on user settings
|
||||
_fragVideoDetail.updateOrientation()
|
||||
|
||||
val sharedPreferences =
|
||||
getSharedPreferences("GrayjayFirstBoot", Context.MODE_PRIVATE)
|
||||
val isFirstBoot = sharedPreferences.getBoolean("IsFirstBoot", true)
|
||||
if (isFirstBoot) {
|
||||
UIDialogs.showConfirmationDialog(this, getString(R.string.do_you_want_to_see_the_tutorials_you_can_find_them_at_any_time_through_the_more_button), {
|
||||
@@ -515,6 +528,64 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||
|
||||
sharedPreferences.edit().putBoolean("IsFirstBoot", false).apply()
|
||||
}
|
||||
|
||||
val submissionStatus = FragmentedStorage.get<StringStorage>("subscriptionSubmissionStatus")
|
||||
|
||||
val numSubscriptions = StateSubscriptions.instance.getSubscriptionCount()
|
||||
|
||||
val subscriptionsThreshold = 20
|
||||
|
||||
if (
|
||||
submissionStatus.value == ""
|
||||
&& StateApp.instance.getCurrentNetworkState() != StateApp.NetworkState.DISCONNECTED
|
||||
&& numSubscriptions >= subscriptionsThreshold
|
||||
) {
|
||||
|
||||
UIDialogs.showDialog(
|
||||
this,
|
||||
R.drawable.ic_internet,
|
||||
getString(R.string.contribute_personal_subscriptions_list),
|
||||
getString(R.string.contribute_personal_subscriptions_list_description),
|
||||
null,
|
||||
0,
|
||||
UIDialogs.Action("Cancel", {
|
||||
submissionStatus.setAndSave("dismissed")
|
||||
}, UIDialogs.ActionStyle.NONE),
|
||||
UIDialogs.Action("Upload", {
|
||||
submissionStatus.setAndSave("submitted")
|
||||
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
@Serializable
|
||||
data class CreatorInfo(val pluginId: String, val url: String)
|
||||
|
||||
val subscriptions =
|
||||
StateSubscriptions.instance.getSubscriptions().map { original ->
|
||||
CreatorInfo(
|
||||
pluginId = original.channel.id.pluginId ?: "",
|
||||
url = original.channel.url
|
||||
)
|
||||
}
|
||||
|
||||
val json = Json.encodeToString(subscriptions)
|
||||
|
||||
val url = "https://data.grayjay.app/donate-subscription-list"
|
||||
val client = ManagedHttpClient();
|
||||
val headers = hashMapOf(
|
||||
"Content-Type" to "application/json"
|
||||
)
|
||||
try {
|
||||
val response = client.post(url, json, headers)
|
||||
// if it failed retry one time
|
||||
if (!response.isOk) {
|
||||
client.post(url, json, headers)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Logger.i(TAG, "Failed to submit subscription list.", e)
|
||||
}
|
||||
}
|
||||
}, UIDialogs.ActionStyle.PRIMARY)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -579,39 +650,45 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||
}
|
||||
|
||||
private fun handleIntent(intent: Intent?) {
|
||||
if(intent == null)
|
||||
if (intent == null)
|
||||
return;
|
||||
Logger.i(TAG, "handleIntent started by " + intent.action);
|
||||
|
||||
|
||||
var targetData: String? = null;
|
||||
|
||||
when(intent.action) {
|
||||
when (intent.action) {
|
||||
Intent.ACTION_SEND -> {
|
||||
targetData = intent.getStringExtra(Intent.EXTRA_STREAM) ?: intent.getStringExtra(Intent.EXTRA_TEXT);
|
||||
targetData = intent.getStringExtra(Intent.EXTRA_STREAM)
|
||||
?: intent.getStringExtra(Intent.EXTRA_TEXT);
|
||||
Logger.i(TAG, "Share Received: " + targetData);
|
||||
}
|
||||
|
||||
Intent.ACTION_VIEW -> {
|
||||
targetData = intent.dataString
|
||||
|
||||
if(!targetData.isNullOrEmpty()) {
|
||||
if (!targetData.isNullOrEmpty()) {
|
||||
Logger.i(TAG, "View Received: " + targetData);
|
||||
}
|
||||
}
|
||||
|
||||
"VIDEO" -> {
|
||||
val url = intent.getStringExtra("VIDEO");
|
||||
navigate(_fragVideoDetail, url);
|
||||
}
|
||||
|
||||
"IMPORT_OPTIONS" -> {
|
||||
UIDialogs.showImportOptionsDialog(this);
|
||||
}
|
||||
|
||||
"ACTION" -> {
|
||||
val action = intent.getStringExtra("ACTION");
|
||||
StateDeveloper.instance.testState = "TestPlayback";
|
||||
StateDeveloper.instance.testPlayback();
|
||||
}
|
||||
|
||||
"TAB" -> {
|
||||
when(intent.getStringExtra("TAB")){
|
||||
when (intent.getStringExtra("TAB")) {
|
||||
"Sources" -> {
|
||||
runBlocking {
|
||||
StatePlatform.instance.updateAvailableClients(this@MainActivity, true) //Ideally this is not needed..
|
||||
@@ -622,7 +699,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||
navigate(_fragBrowser, BrowserFragment.NavigateOptions("https://plugins.grayjay.app/phone.html", mapOf(
|
||||
Pair("grayjay") { req ->
|
||||
StateApp.instance.contextOrNull?.let {
|
||||
if(it is MainActivity) {
|
||||
if (it is MainActivity) {
|
||||
runBlocking {
|
||||
it.handleUrlAll(req.url.toString());
|
||||
}
|
||||
@@ -641,8 +718,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||
handleUrlAll(targetData)
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(ex: Throwable) {
|
||||
} catch (ex: Throwable) {
|
||||
UIDialogs.showGeneralErrorDialog(this, getString(R.string.failed_to_handle_file), ex);
|
||||
}
|
||||
}
|
||||
@@ -651,35 +727,31 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||
val uri = Uri.parse(url)
|
||||
when (uri.scheme) {
|
||||
"grayjay" -> {
|
||||
if(url.startsWith("grayjay://license/")) {
|
||||
if(StatePayment.instance.setPaymentLicenseUrl(url))
|
||||
{
|
||||
if (url.startsWith("grayjay://license/")) {
|
||||
if (StatePayment.instance.setPaymentLicenseUrl(url)) {
|
||||
UIDialogs.showDialogOk(this, R.drawable.ic_check, getString(R.string.your_license_key_has_been_set_an_app_restart_might_be_required));
|
||||
|
||||
if(fragCurrent is BuyFragment)
|
||||
if (fragCurrent is BuyFragment)
|
||||
closeSegment(fragCurrent);
|
||||
}
|
||||
else
|
||||
} else
|
||||
UIDialogs.toast(getString(R.string.invalid_license_format));
|
||||
|
||||
}
|
||||
else if(url.startsWith("grayjay://plugin/")) {
|
||||
} else if (url.startsWith("grayjay://plugin/")) {
|
||||
val intent = Intent(this, AddSourceActivity::class.java).apply {
|
||||
data = Uri.parse(url.substring("grayjay://plugin/".length));
|
||||
};
|
||||
startActivity(intent);
|
||||
}
|
||||
else if(url.startsWith("grayjay://video/")) {
|
||||
} else if (url.startsWith("grayjay://video/")) {
|
||||
val videoUrl = url.substring("grayjay://video/".length);
|
||||
navigate(_fragVideoDetail, videoUrl);
|
||||
}
|
||||
else if(url.startsWith("grayjay://channel/")) {
|
||||
} else if (url.startsWith("grayjay://channel/")) {
|
||||
val channelUrl = url.substring("grayjay://channel/".length);
|
||||
navigate(_fragMainChannel, channelUrl);
|
||||
}
|
||||
}
|
||||
|
||||
"content" -> {
|
||||
if(!handleContent(url, intent.type)) {
|
||||
if (!handleContent(url, intent.type)) {
|
||||
UIDialogs.showSingleButtonDialog(
|
||||
this,
|
||||
R.drawable.ic_play,
|
||||
@@ -688,8 +760,9 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||
{ });
|
||||
}
|
||||
}
|
||||
|
||||
"file" -> {
|
||||
if(!handleFile(url)) {
|
||||
if (!handleFile(url)) {
|
||||
UIDialogs.showSingleButtonDialog(
|
||||
this,
|
||||
R.drawable.ic_play,
|
||||
@@ -698,8 +771,9 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||
{ });
|
||||
}
|
||||
}
|
||||
|
||||
"polycentric" -> {
|
||||
if(!handlePolycentric(url)) {
|
||||
if (!handlePolycentric(url)) {
|
||||
UIDialogs.showSingleButtonDialog(
|
||||
this,
|
||||
R.drawable.ic_play,
|
||||
@@ -708,8 +782,9 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||
{ });
|
||||
}
|
||||
}
|
||||
|
||||
"fcast" -> {
|
||||
if(!handleFCast(url)) {
|
||||
if (!handleFCast(url)) {
|
||||
UIDialogs.showSingleButtonDialog(
|
||||
this,
|
||||
R.drawable.ic_cast,
|
||||
@@ -718,6 +793,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||
{ });
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
if (!handleUrl(url)) {
|
||||
UIDialogs.showSingleButtonDialog(
|
||||
@@ -731,7 +807,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun handleUrl(url: String): Boolean {
|
||||
suspend fun handleUrl(url: String, position: Int = 0): Boolean {
|
||||
Logger.i(TAG, "handleUrl(url=$url)")
|
||||
|
||||
return withContext(Dispatchers.IO) {
|
||||
@@ -739,7 +815,10 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||
if (StatePlatform.instance.hasEnabledVideoClient(url)) {
|
||||
Logger.i(TAG, "handleUrl(url=$url) found video client");
|
||||
lifecycleScope.launch(Dispatchers.Main) {
|
||||
navigate(_fragVideoDetail, url);
|
||||
if (position > 0)
|
||||
navigate(_fragVideoDetail, UrlVideoWithTime(url, position.toLong(), true));
|
||||
else
|
||||
navigate(_fragVideoDetail, url);
|
||||
|
||||
_fragVideoDetail.maximizeVideoDetail(true);
|
||||
}
|
||||
@@ -755,7 +834,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||
} else if (StatePlatform.instance.hasEnabledPlaylistClient(url)) {
|
||||
Logger.i(TAG, "handleUrl(url=$url) found playlist client");
|
||||
lifecycleScope.launch(Dispatchers.Main) {
|
||||
navigate(_fragMainPlaylist, url);
|
||||
navigate(_fragMainRemotePlaylist, url);
|
||||
delay(100);
|
||||
_fragVideoDetail.minimizeVideoDetail();
|
||||
};
|
||||
@@ -764,24 +843,25 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||
return@withContext false;
|
||||
}
|
||||
}
|
||||
|
||||
fun handleContent(file: String, mime: String? = null): Boolean {
|
||||
Logger.i(TAG, "handleContent(url=$file)");
|
||||
|
||||
val data = readSharedContent(file);
|
||||
if(file.lowercase().endsWith(".json") || mime == "application/json") {
|
||||
if (file.lowercase().endsWith(".json") || mime == "application/json") {
|
||||
var recon = String(data);
|
||||
if(!recon.trim().startsWith("["))
|
||||
if (!recon.trim().startsWith("["))
|
||||
return handleUnknownJson(recon);
|
||||
|
||||
var reconLines = Json.decodeFromString<List<String>>(recon);
|
||||
val cacheStr = reconLines.find { it.startsWith("__CACHE:") }?.substring("__CACHE:".length);
|
||||
val cacheStr =
|
||||
reconLines.find { it.startsWith("__CACHE:") }?.substring("__CACHE:".length);
|
||||
reconLines = reconLines.filter { !it.startsWith("__CACHE:") }; //TODO: constant prefix
|
||||
var cache: ImportCache? = null;
|
||||
try {
|
||||
if(cacheStr != null)
|
||||
if (cacheStr != null)
|
||||
cache = Json.decodeFromString(cacheStr);
|
||||
}
|
||||
catch(ex: Throwable) {
|
||||
} catch (ex: Throwable) {
|
||||
Logger.e(TAG, "Failed to deserialize cache");
|
||||
}
|
||||
|
||||
@@ -790,32 +870,31 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||
Logger.i(TAG, "Opened shared playlist reconstruction\n${recon}");
|
||||
handleReconstruction(recon, cache);
|
||||
return true;
|
||||
}
|
||||
else if(file.lowercase().endsWith(".zip") || mime == "application/zip") {
|
||||
} else if (file.lowercase().endsWith(".zip") || mime == "application/zip") {
|
||||
StateBackup.importZipBytes(this, lifecycleScope, data);
|
||||
return true;
|
||||
}
|
||||
else if(file.lowercase().endsWith(".txt") || mime == "text/plain") {
|
||||
} else if (file.lowercase().endsWith(".txt") || mime == "text/plain") {
|
||||
return handleUnknownText(String(data));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fun handleFile(file: String): Boolean {
|
||||
Logger.i(TAG, "handleFile(url=$file)");
|
||||
if(file.lowercase().endsWith(".json")) {
|
||||
if (file.lowercase().endsWith(".json")) {
|
||||
var recon = String(readSharedFile(file));
|
||||
if(!recon.startsWith("["))
|
||||
if (!recon.startsWith("["))
|
||||
return handleUnknownJson(recon);
|
||||
|
||||
var reconLines = Json.decodeFromString<List<String>>(recon);
|
||||
val cacheStr = reconLines.find { it.startsWith("__CACHE:") }?.substring("__CACHE:".length);
|
||||
val cacheStr =
|
||||
reconLines.find { it.startsWith("__CACHE:") }?.substring("__CACHE:".length);
|
||||
reconLines = reconLines.filter { !it.startsWith("__CACHE:") }; //TODO: constant prefix
|
||||
var cache: ImportCache? = null;
|
||||
try {
|
||||
if(cacheStr != null)
|
||||
if (cacheStr != null)
|
||||
cache = Json.decodeFromString(cacheStr);
|
||||
}
|
||||
catch(ex: Throwable) {
|
||||
} catch (ex: Throwable) {
|
||||
Logger.e(TAG, "Failed to deserialize cache");
|
||||
}
|
||||
recon = reconLines.joinToString("\n");
|
||||
@@ -823,19 +902,18 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||
Logger.i(TAG, "Opened shared playlist reconstruction\n${recon}");
|
||||
handleReconstruction(recon, cache);
|
||||
return true;
|
||||
}
|
||||
else if(file.lowercase().endsWith(".zip")) {
|
||||
} else if (file.lowercase().endsWith(".zip")) {
|
||||
StateBackup.importZipBytes(this, lifecycleScope, readSharedFile(file));
|
||||
return true;
|
||||
}
|
||||
else if(file.lowercase().endsWith(".txt")) {
|
||||
} else if (file.lowercase().endsWith(".txt")) {
|
||||
return handleUnknownText(String(readSharedFile(file)));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fun handleReconstruction(recon: String, cache: ImportCache? = null) {
|
||||
val type = ManagedStore.getReconstructionIdentifier(recon);
|
||||
val store: ManagedStore<*> = when(type) {
|
||||
val store: ManagedStore<*> = when (type) {
|
||||
"Playlist" -> StatePlaylists.instance.playlistStore
|
||||
else -> {
|
||||
UIDialogs.toast(getString(R.string.unknown_reconstruction_type) + " ${type}", false);
|
||||
@@ -843,13 +921,15 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||
};
|
||||
};
|
||||
|
||||
val name = when(type) {
|
||||
"Playlist" -> recon.split("\n").filter { !it.startsWith(ManagedStore.RECONSTRUCTION_HEADER_OPERATOR) }.firstOrNull() ?: type;
|
||||
val name = when (type) {
|
||||
"Playlist" -> recon.split("\n")
|
||||
.filter { !it.startsWith(ManagedStore.RECONSTRUCTION_HEADER_OPERATOR) }
|
||||
.firstOrNull() ?: type;
|
||||
else -> type
|
||||
}
|
||||
|
||||
|
||||
if(!type.isNullOrEmpty()) {
|
||||
if (!type.isNullOrEmpty()) {
|
||||
UIDialogs.showImportDialog(this, store, name, listOf(recon), cache) {
|
||||
|
||||
}
|
||||
@@ -858,18 +938,18 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||
|
||||
fun handleUnknownText(text: String): Boolean {
|
||||
try {
|
||||
if(text.startsWith("@/Subscription") || text.startsWith("Subscriptions")) {
|
||||
if (text.startsWith("@/Subscription") || text.startsWith("Subscriptions")) {
|
||||
val lines = text.split("\n").map { it.trim() }.drop(1).filter { it.isNotEmpty() };
|
||||
navigate(_fragImportSubscriptions, lines);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch(ex: Throwable) {
|
||||
} catch (ex: Throwable) {
|
||||
Logger.e(TAG, ex.message, ex);
|
||||
UIDialogs.showGeneralErrorDialog(this, getString(R.string.failed_to_parse_text_file), ex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
fun handleUnknownJson(json: String): Boolean {
|
||||
|
||||
val context = this;
|
||||
@@ -881,8 +961,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||
return false;//throw IllegalArgumentException("Invalid NewPipe json structure found");
|
||||
|
||||
StateBackup.importNewPipeSubs(this, newPipeSubsParsed);
|
||||
}
|
||||
catch(ex: Exception) {
|
||||
} catch (ex: Exception) {
|
||||
Logger.e(TAG, ex.message, ex);
|
||||
UIDialogs.showGeneralErrorDialog(context, getString(R.string.failed_to_parse_newpipe_subscriptions), ex);
|
||||
}
|
||||
@@ -928,7 +1007,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||
|
||||
private fun readSharedFile(filePath: String): ByteArray {
|
||||
val dataFile = File(filePath);
|
||||
if(!dataFile.exists())
|
||||
if (!dataFile.exists())
|
||||
throw IllegalArgumentException("Opened file does not exist or not permitted");
|
||||
val data = dataFile.readBytes();
|
||||
return data;
|
||||
@@ -937,13 +1016,13 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||
override fun onBackPressed() {
|
||||
Logger.i(TAG, "onBackPressed")
|
||||
|
||||
if(_fragBotBarMenu.onBackPressed())
|
||||
if (_fragBotBarMenu.onBackPressed())
|
||||
return;
|
||||
|
||||
if(_fragVideoDetail.state == VideoDetailFragment.State.MAXIMIZED && _fragVideoDetail.onBackPressed())
|
||||
if (_fragVideoDetail.state == VideoDetailFragment.State.MAXIMIZED && _fragVideoDetail.onBackPressed())
|
||||
return;
|
||||
|
||||
if(!fragCurrent.onBackPressed())
|
||||
if (!fragCurrent.onBackPressed())
|
||||
closeSegment();
|
||||
}
|
||||
|
||||
@@ -951,7 +1030,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||
super.onUserLeaveHint();
|
||||
Logger.i(TAG, "onUserLeaveHint")
|
||||
|
||||
if(_fragVideoDetail.state == VideoDetailFragment.State.MAXIMIZED || _fragVideoDetail.state == VideoDetailFragment.State.MINIMIZED)
|
||||
if (_fragVideoDetail.state == VideoDetailFragment.State.MAXIMIZED || _fragVideoDetail.state == VideoDetailFragment.State.MINIMIZED)
|
||||
_fragVideoDetail.onUserLeaveHint();
|
||||
}
|
||||
|
||||
@@ -987,12 +1066,12 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||
fun navigate(segment: MainFragment, parameter: Any? = null, withHistory: Boolean = true, isBack: Boolean = false) {
|
||||
Logger.i(TAG, "Navigate to $segment (parameter=$parameter, withHistory=$withHistory, isBack=$isBack)")
|
||||
|
||||
if(segment != fragCurrent) {
|
||||
|
||||
if(segment is VideoDetailFragment) {
|
||||
if(_fragContainerVideoDetail.visibility != View.VISIBLE)
|
||||
if (segment != fragCurrent) {
|
||||
|
||||
if (segment is VideoDetailFragment) {
|
||||
if (_fragContainerVideoDetail.visibility != View.VISIBLE)
|
||||
_fragContainerVideoDetail.visibility = View.VISIBLE;
|
||||
when(segment.state) {
|
||||
when (segment.state) {
|
||||
VideoDetailFragment.State.MINIMIZED -> segment.maximizeVideoDetail()
|
||||
VideoDetailFragment.State.CLOSED -> segment.maximizeVideoDetail()
|
||||
else -> {}
|
||||
@@ -1000,11 +1079,10 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||
segment.onShown(parameter, isBack);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
fragCurrent.onHide();
|
||||
|
||||
if(segment.isMainView) {
|
||||
if (segment.isMainView) {
|
||||
var transaction = supportFragmentManager.beginTransaction();
|
||||
if (segment.topBar != null) {
|
||||
if (segment.topBar != fragCurrent.topBar) {
|
||||
@@ -1013,8 +1091,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||
.replace(R.id.fragment_top_bar, segment.topBar as Fragment);
|
||||
fragCurrent.topBar?.onHide();
|
||||
}
|
||||
}
|
||||
else if(fragCurrent.topBar != null)
|
||||
} else if (fragCurrent.topBar != null)
|
||||
transaction.hide(fragCurrent.topBar as Fragment);
|
||||
|
||||
transaction = transaction.replace(R.id.fragment_main, segment);
|
||||
@@ -1022,25 +1099,24 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||
if (segment.hasBottomBar) {
|
||||
if (!fragCurrent.hasBottomBar)
|
||||
transaction = transaction.show(_fragBotBarMenu);
|
||||
}
|
||||
else {
|
||||
if(fragCurrent.hasBottomBar)
|
||||
} else {
|
||||
if (fragCurrent.hasBottomBar)
|
||||
transaction = transaction.hide(_fragBotBarMenu);
|
||||
}
|
||||
transaction.commitNow();
|
||||
} else {
|
||||
|
||||
if(!segment.hasBottomBar) {
|
||||
if (!segment.hasBottomBar) {
|
||||
supportFragmentManager.beginTransaction()
|
||||
.hide(_fragBotBarMenu)
|
||||
.commitNow();
|
||||
}
|
||||
}
|
||||
|
||||
if(fragCurrent.isHistory && withHistory && _queue.lastOrNull() != fragCurrent)
|
||||
if (fragCurrent.isHistory && withHistory && _queue.lastOrNull() != fragCurrent)
|
||||
_queue.add(Pair(fragCurrent, _parameterCurrent));
|
||||
|
||||
if(segment.isOverlay && !fragCurrent.isOverlay && withHistory)// && fragCurrent.isHistory)
|
||||
if (segment.isOverlay && !fragCurrent.isOverlay && withHistory)// && fragCurrent.isHistory)
|
||||
fragBeforeOverlay = fragCurrent;
|
||||
|
||||
|
||||
@@ -1058,12 +1134,12 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||
* If called with a non-null fragment, it will only close if the current fragment is the provided one
|
||||
*/
|
||||
fun closeSegment(fragment: MainFragment? = null) {
|
||||
if(fragment is VideoDetailFragment) {
|
||||
if (fragment is VideoDetailFragment) {
|
||||
fragment.onHide();
|
||||
return;
|
||||
}
|
||||
|
||||
if((fragment?.isOverlay ?: false) && fragBeforeOverlay != null) {
|
||||
if ((fragment?.isOverlay ?: false) && fragBeforeOverlay != null) {
|
||||
navigate(fragBeforeOverlay!!, null, false, true);
|
||||
} else {
|
||||
val last = _queue.lastOrNull();
|
||||
@@ -1085,8 +1161,8 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||
/**
|
||||
* Provides the fragment instance for the provided fragment class
|
||||
*/
|
||||
inline fun <reified T : Fragment> getFragment() : T {
|
||||
return when(T::class) {
|
||||
inline fun <reified T : Fragment> getFragment(): T {
|
||||
return when (T::class) {
|
||||
HomeFragment::class -> _fragMainHome as T;
|
||||
TutorialFragment::class -> _fragMainTutorial as T;
|
||||
ContentSearchResultsFragment::class -> _fragMainVideoSearchResults as T;
|
||||
@@ -1123,15 +1199,21 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||
|
||||
private fun updateSegmentPaddings() {
|
||||
var paddingBottom = 0f;
|
||||
if(fragCurrent.hasBottomBar)
|
||||
if (fragCurrent.hasBottomBar)
|
||||
paddingBottom += HEIGHT_MENU_DP;
|
||||
|
||||
_fragContainerOverlay.setPadding(0,0,0, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, paddingBottom - HEIGHT_MENU_DP, resources.displayMetrics).toInt());
|
||||
_fragContainerOverlay.setPadding(
|
||||
0, 0, 0, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, paddingBottom - HEIGHT_MENU_DP, resources.displayMetrics)
|
||||
.toInt()
|
||||
);
|
||||
|
||||
if(_fragVideoDetail.state == VideoDetailFragment.State.MINIMIZED)
|
||||
if (_fragVideoDetail.state == VideoDetailFragment.State.MINIMIZED)
|
||||
paddingBottom += HEIGHT_VIDEO_MINIMIZED_DP;
|
||||
|
||||
_fragContainerMain.setPadding(0,0,0, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, paddingBottom, resources.displayMetrics).toInt());
|
||||
_fragContainerMain.setPadding(
|
||||
0, 0, 0, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, paddingBottom, resources.displayMetrics)
|
||||
.toInt()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1147,14 +1229,18 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||
ContextCompat.checkSelfPermission(this, notifPermission) == PackageManager.PERMISSION_GRANTED -> {
|
||||
|
||||
}
|
||||
|
||||
ActivityCompat.shouldShowRequestPermissionRationale(this, notifPermission) -> {
|
||||
UIDialogs.showDialog(this, R.drawable.ic_notifications, "Notifications Required",
|
||||
UIDialogs.showDialog(
|
||||
this, R.drawable.ic_notifications, "Notifications Required",
|
||||
reason, null, 0,
|
||||
UIDialogs.Action("Cancel", {}),
|
||||
UIDialogs.Action("Enable", {
|
||||
requestPermissionLauncher.launch(notifPermission);
|
||||
}, UIDialogs.ActionStyle.PRIMARY));
|
||||
}, UIDialogs.ActionStyle.PRIMARY)
|
||||
);
|
||||
}
|
||||
|
||||
else -> {
|
||||
requestPermissionLauncher.launch(notifPermission);
|
||||
}
|
||||
@@ -1166,15 +1252,16 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||
fun showAppToast(toast: ToastView.Toast) {
|
||||
synchronized(_toastQueue) {
|
||||
_toastQueue.add(toast);
|
||||
if(_toastJob?.isActive != true)
|
||||
if (_toastJob?.isActive != true)
|
||||
_toastJob = lifecycleScope.launch(Dispatchers.Default) {
|
||||
launchAppToastJob();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun launchAppToastJob() {
|
||||
Logger.i(TAG, "Starting appToast loop");
|
||||
while(!_toastQueue.isEmpty()) {
|
||||
while (!_toastQueue.isEmpty()) {
|
||||
val toast = _toastQueue.poll() ?: continue;
|
||||
Logger.i(TAG, "Showing next toast (${toast.msg})");
|
||||
|
||||
@@ -1187,7 +1274,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||
_toastView.setToastAnimated(toast);
|
||||
}
|
||||
}
|
||||
if(toast.long)
|
||||
if (toast.long)
|
||||
delay(5000);
|
||||
else
|
||||
delay(3000);
|
||||
@@ -1201,18 +1288,19 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||
|
||||
|
||||
//TODO: Only calls last handler due to missing request codes on ActivityResultLaunchers.
|
||||
private var resultLauncherMap = mutableMapOf<Int, (ActivityResult)->Unit>();
|
||||
private var resultLauncherMap = mutableMapOf<Int, (ActivityResult) -> Unit>();
|
||||
private var requestCode: Int? = -1;
|
||||
private val resultLauncher: ActivityResultLauncher<Intent> = registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()) {
|
||||
result: ActivityResult ->
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) { result: ActivityResult ->
|
||||
val handler = synchronized(resultLauncherMap) {
|
||||
resultLauncherMap.remove(requestCode);
|
||||
}
|
||||
if(handler != null)
|
||||
if (handler != null)
|
||||
handler(result);
|
||||
};
|
||||
override fun launchForResult(intent: Intent, code: Int, handler: (ActivityResult)->Unit) {
|
||||
|
||||
override fun launchForResult(intent: Intent, code: Int, handler: (ActivityResult) -> Unit) {
|
||||
synchronized(resultLauncherMap) {
|
||||
resultLauncherMap[code] = handler;
|
||||
}
|
||||
@@ -1223,32 +1311,34 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
||||
companion object {
|
||||
private val TAG = "MainActivity"
|
||||
|
||||
fun getTabIntent(context: Context, tab: String) : Intent {
|
||||
fun getTabIntent(context: Context, tab: String): Intent {
|
||||
val sourcesIntent = Intent(context, MainActivity::class.java);
|
||||
sourcesIntent.action = "TAB";
|
||||
sourcesIntent.putExtra("TAB", tab);
|
||||
sourcesIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
sourcesIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
return sourcesIntent;
|
||||
}
|
||||
fun getVideoIntent(context: Context, videoUrl: String) : Intent {
|
||||
|
||||
fun getVideoIntent(context: Context, videoUrl: String): Intent {
|
||||
val sourcesIntent = Intent(context, MainActivity::class.java);
|
||||
sourcesIntent.action = "VIDEO";
|
||||
sourcesIntent.putExtra("VIDEO", videoUrl);
|
||||
sourcesIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
sourcesIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
return sourcesIntent;
|
||||
}
|
||||
fun getActionIntent(context: Context, action: String) : Intent {
|
||||
|
||||
fun getActionIntent(context: Context, action: String): Intent {
|
||||
val sourcesIntent = Intent(context, MainActivity::class.java);
|
||||
sourcesIntent.action = "ACTION";
|
||||
sourcesIntent.putExtra("ACTION", action);
|
||||
sourcesIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
sourcesIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
return sourcesIntent;
|
||||
}
|
||||
|
||||
fun getImportOptionsIntent(context: Context): Intent {
|
||||
val sourcesIntent = Intent(context, MainActivity::class.java);
|
||||
sourcesIntent.action = "IMPORT_OPTIONS";
|
||||
sourcesIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
sourcesIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
return sourcesIntent;
|
||||
}
|
||||
}
|
||||
|
||||
+39
-21
@@ -3,6 +3,7 @@ package com.futo.platformplayer.activities
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.EditText
|
||||
import android.widget.ImageButton
|
||||
import android.widget.LinearLayout
|
||||
@@ -17,6 +18,7 @@ import com.futo.platformplayer.polycentric.PolycentricStorage
|
||||
import com.futo.platformplayer.setNavigationBarColorAndIcons
|
||||
import com.futo.platformplayer.states.StateApp
|
||||
import com.futo.platformplayer.states.StatePolycentric
|
||||
import com.futo.platformplayer.views.LoaderView
|
||||
import com.futo.polycentric.core.ProcessHandle
|
||||
import com.futo.polycentric.core.Store
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -27,6 +29,7 @@ class PolycentricCreateProfileActivity : AppCompatActivity() {
|
||||
private lateinit var _buttonHelp: ImageButton;
|
||||
private lateinit var _profileName: EditText;
|
||||
private lateinit var _buttonCreate: LinearLayout;
|
||||
private lateinit var _loader: LoaderView;
|
||||
private val TAG = "PolycentricCreateProfileActivity";
|
||||
|
||||
private var _creating = false;
|
||||
@@ -43,6 +46,7 @@ class PolycentricCreateProfileActivity : AppCompatActivity() {
|
||||
_buttonHelp = findViewById(R.id.button_help);
|
||||
_profileName = findViewById(R.id.edit_profile_name);
|
||||
_buttonCreate = findViewById(R.id.button_create_profile);
|
||||
_loader = findViewById(R.id.loader);
|
||||
findViewById<ImageButton>(R.id.button_back).setOnClickListener {
|
||||
finish();
|
||||
};
|
||||
@@ -65,35 +69,49 @@ class PolycentricCreateProfileActivity : AppCompatActivity() {
|
||||
return@setOnClickListener;
|
||||
}
|
||||
|
||||
_profileName.isEnabled = false;
|
||||
_buttonCreate.visibility = View.GONE;
|
||||
_loader.start();
|
||||
_loader.visibility = View.VISIBLE;
|
||||
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
val processHandle: ProcessHandle;
|
||||
|
||||
try {
|
||||
processHandle = ProcessHandle.create();
|
||||
Store.instance.addProcessSecret(processHandle.processSecret);
|
||||
|
||||
try {
|
||||
PolycentricStorage.instance.addProcessSecret(processHandle.processSecret)
|
||||
processHandle = ProcessHandle.create();
|
||||
Store.instance.addProcessSecret(processHandle.processSecret);
|
||||
|
||||
try {
|
||||
PolycentricStorage.instance.addProcessSecret(processHandle.processSecret)
|
||||
} catch (e: Throwable) {
|
||||
Logger.e(TAG, "Failed to save process secret to secret storage.", e)
|
||||
}
|
||||
|
||||
processHandle.addServer(PolycentricCache.SERVER);
|
||||
processHandle.setUsername(username);
|
||||
StatePolycentric.instance.setProcessHandle(processHandle);
|
||||
} catch (e: Throwable) {
|
||||
Logger.e(TAG, "Failed to save process secret to secret storage.", e)
|
||||
Logger.e(TAG, getString(R.string.failed_to_create_profile), e);
|
||||
return@launch;
|
||||
} finally {
|
||||
_creating = false;
|
||||
}
|
||||
|
||||
processHandle.addServer(PolycentricCache.SERVER);
|
||||
processHandle.setUsername(username);
|
||||
StatePolycentric.instance.setProcessHandle(processHandle);
|
||||
} catch (e: Throwable) {
|
||||
Logger.e(TAG, getString(R.string.failed_to_create_profile), e);
|
||||
return@launch;
|
||||
} finally {
|
||||
_creating = false;
|
||||
try {
|
||||
Logger.i(TAG, "Started backfill");
|
||||
processHandle.fullyBackfillServersAnnounceExceptions();
|
||||
Logger.i(TAG, "Finished backfill");
|
||||
} catch (e: Throwable) {
|
||||
Logger.e(TAG, getString(R.string.failed_to_fully_backfill_servers), e);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
Logger.i(TAG, "Started backfill");
|
||||
processHandle.fullyBackfillServersAnnounceExceptions();
|
||||
Logger.i(TAG, "Finished backfill");
|
||||
} catch (e: Throwable) {
|
||||
Logger.e(TAG, getString(R.string.failed_to_fully_backfill_servers), e);
|
||||
finally {
|
||||
withContext(Dispatchers.Main) {
|
||||
_profileName.isEnabled = true;
|
||||
_buttonCreate.visibility = View.VISIBLE;
|
||||
_loader.stop();
|
||||
_loader.visibility = View.GONE;
|
||||
}
|
||||
}
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
|
||||
@@ -8,6 +8,7 @@ import android.os.Bundle
|
||||
import android.util.TypedValue
|
||||
import android.widget.ImageButton
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.ScrollView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.request.target.CustomTarget
|
||||
@@ -28,6 +29,7 @@ class PolycentricHomeActivity : AppCompatActivity() {
|
||||
private lateinit var _buttonNewProfile: BigButton;
|
||||
private lateinit var _buttonImportProfile: BigButton;
|
||||
private lateinit var _layoutButtons: LinearLayout;
|
||||
private lateinit var _scroll: ScrollView;
|
||||
|
||||
override fun attachBaseContext(newBase: Context?) {
|
||||
super.attachBaseContext(StateApp.instance.getLocaleContext(newBase))
|
||||
@@ -42,6 +44,7 @@ class PolycentricHomeActivity : AppCompatActivity() {
|
||||
_buttonNewProfile = findViewById(R.id.button_new_profile);
|
||||
_buttonImportProfile = findViewById(R.id.button_import_profile);
|
||||
_layoutButtons = findViewById(R.id.layout_buttons);
|
||||
_scroll = findViewById(R.id.scroll);
|
||||
findViewById<ImageButton>(R.id.button_back).setOnClickListener {
|
||||
finish();
|
||||
};
|
||||
@@ -78,6 +81,7 @@ class PolycentricHomeActivity : AppCompatActivity() {
|
||||
|
||||
_layoutButtons.addView(profileButton, 0);
|
||||
}
|
||||
_scroll.invalidate();
|
||||
|
||||
_buttonHelp.setOnClickListener {
|
||||
startActivity(Intent(this, PolycentricWhyActivity::class.java));
|
||||
|
||||
@@ -25,6 +25,7 @@ import com.futo.platformplayer.fullyBackfillServersAnnounceExceptions
|
||||
import com.futo.platformplayer.images.GlideHelper.Companion.crossfade
|
||||
import com.futo.platformplayer.logging.Logger
|
||||
import com.futo.platformplayer.polycentric.PolycentricCache
|
||||
import com.futo.platformplayer.polycentric.PolycentricStorage
|
||||
import com.futo.platformplayer.selectBestImage
|
||||
import com.futo.platformplayer.setNavigationBarColorAndIcons
|
||||
import com.futo.platformplayer.states.StateApp
|
||||
@@ -33,6 +34,7 @@ import com.futo.platformplayer.views.buttons.BigButton
|
||||
import com.futo.platformplayer.views.overlays.LoaderOverlay
|
||||
import com.futo.polycentric.core.Store
|
||||
import com.futo.polycentric.core.SystemState
|
||||
import com.futo.polycentric.core.systemToURLInfoSystemLinkUrl
|
||||
import com.futo.polycentric.core.toBase64Url
|
||||
import com.futo.polycentric.core.toURLInfoSystemLinkUrl
|
||||
import com.github.dhaval2404.imagepicker.ImagePicker
|
||||
@@ -47,6 +49,7 @@ class PolycentricProfileActivity : AppCompatActivity() {
|
||||
private lateinit var _buttonHelp: ImageButton;
|
||||
private lateinit var _editName: EditText;
|
||||
private lateinit var _buttonExport: BigButton;
|
||||
private lateinit var _buttonOpenHarborProfile: BigButton;
|
||||
private lateinit var _buttonLogout: BigButton;
|
||||
private lateinit var _buttonDelete: BigButton;
|
||||
private lateinit var _username: String;
|
||||
@@ -68,10 +71,14 @@ class PolycentricProfileActivity : AppCompatActivity() {
|
||||
_imagePolycentric = findViewById(R.id.image_polycentric);
|
||||
_editName = findViewById(R.id.edit_profile_name);
|
||||
_buttonExport = findViewById(R.id.button_export);
|
||||
_buttonOpenHarborProfile = findViewById(R.id.button_open_harbor_profile);
|
||||
_buttonLogout = findViewById(R.id.button_logout);
|
||||
_buttonDelete = findViewById(R.id.button_delete);
|
||||
_loaderOverlay = findViewById(R.id.loader_overlay);
|
||||
_textSystem = findViewById(R.id.text_system)
|
||||
findViewById<TextView>(R.id.text_cta2).setOnClickListener {
|
||||
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("https://harbor.social")))
|
||||
}
|
||||
findViewById<ImageButton>(R.id.button_back).setOnClickListener {
|
||||
saveIfRequired();
|
||||
finish();
|
||||
@@ -92,6 +99,16 @@ class PolycentricProfileActivity : AppCompatActivity() {
|
||||
startActivity(Intent(this, PolycentricBackupActivity::class.java));
|
||||
};
|
||||
|
||||
_buttonOpenHarborProfile.onClick.subscribe {
|
||||
val processHandle = StatePolycentric.instance.processHandle!!;
|
||||
processHandle?.let {
|
||||
val systemState = SystemState.fromStorageTypeSystemState(Store.instance.getSystemState(it.system));
|
||||
val url = it.system.systemToURLInfoSystemLinkUrl(systemState.servers.asIterable());
|
||||
val navUrl = "https://harbor.social/" + url.substring("polycentric://".length)
|
||||
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(navUrl)))
|
||||
}
|
||||
}
|
||||
|
||||
_buttonLogout.onClick.subscribe {
|
||||
StatePolycentric.instance.setProcessHandle(null);
|
||||
startActivity(Intent(this, PolycentricHomeActivity::class.java));
|
||||
@@ -108,6 +125,7 @@ class PolycentricProfileActivity : AppCompatActivity() {
|
||||
|
||||
StatePolycentric.instance.setProcessHandle(null);
|
||||
Store.instance.removeProcessSecret(processHandle.system);
|
||||
PolycentricStorage.instance.removeProcessSecret(processHandle.system);
|
||||
startActivity(Intent(this, PolycentricHomeActivity::class.java));
|
||||
finish();
|
||||
});
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
package com.futo.platformplayer.activities
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.ImageButton
|
||||
import android.widget.LinearLayout
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.futo.platformplayer.R
|
||||
import com.futo.platformplayer.setNavigationBarColorAndIcons
|
||||
import com.futo.platformplayer.states.StateApp
|
||||
import com.futo.platformplayer.states.StateSync
|
||||
import com.futo.platformplayer.sync.internal.LinkType
|
||||
import com.futo.platformplayer.sync.internal.SyncSession
|
||||
import com.futo.platformplayer.views.sync.SyncDeviceView
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class SyncHomeActivity : AppCompatActivity() {
|
||||
private lateinit var _layoutDevices: LinearLayout
|
||||
private lateinit var _layoutEmpty: LinearLayout
|
||||
private val _viewMap: MutableMap<String, SyncDeviceView> = mutableMapOf()
|
||||
|
||||
override fun attachBaseContext(newBase: Context?) {
|
||||
super.attachBaseContext(StateApp.instance.getLocaleContext(newBase))
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_sync_home)
|
||||
setNavigationBarColorAndIcons()
|
||||
|
||||
_layoutDevices = findViewById(R.id.layout_devices)
|
||||
_layoutEmpty = findViewById(R.id.layout_empty)
|
||||
|
||||
findViewById<ImageButton>(R.id.button_back).setOnClickListener {
|
||||
finish()
|
||||
}
|
||||
|
||||
findViewById<LinearLayout>(R.id.button_link_new_device).setOnClickListener {
|
||||
startActivity(Intent(this@SyncHomeActivity, SyncPairActivity::class.java))
|
||||
}
|
||||
|
||||
findViewById<LinearLayout>(R.id.button_show_pairing_code).setOnClickListener {
|
||||
startActivity(Intent(this@SyncHomeActivity, SyncShowPairingCodeActivity::class.java))
|
||||
}
|
||||
|
||||
initializeDevices()
|
||||
|
||||
StateSync.instance.deviceUpdatedOrAdded.subscribe(this) { publicKey, session ->
|
||||
lifecycleScope.launch(Dispatchers.Main) {
|
||||
val view = _viewMap[publicKey]
|
||||
if (!session.isAuthorized) {
|
||||
if (view != null) {
|
||||
_layoutDevices.removeView(view)
|
||||
_viewMap.remove(publicKey)
|
||||
}
|
||||
return@launch
|
||||
}
|
||||
|
||||
if (view == null) {
|
||||
val syncDeviceView = SyncDeviceView(this@SyncHomeActivity)
|
||||
syncDeviceView.onRemove.subscribe {
|
||||
StateApp.instance.scopeOrNull?.launch {
|
||||
StateSync.instance.delete(publicKey)
|
||||
}
|
||||
}
|
||||
val v = updateDeviceView(syncDeviceView, publicKey, session)
|
||||
_layoutDevices.addView(v, 0)
|
||||
_viewMap[publicKey] = v
|
||||
} else {
|
||||
updateDeviceView(view, publicKey, session)
|
||||
}
|
||||
|
||||
updateEmptyVisibility()
|
||||
}
|
||||
}
|
||||
|
||||
StateSync.instance.deviceRemoved.subscribe(this) {
|
||||
lifecycleScope.launch(Dispatchers.Main) {
|
||||
val view = _viewMap[it]
|
||||
if (view != null) {
|
||||
_layoutDevices.removeView(view)
|
||||
_viewMap.remove(it)
|
||||
}
|
||||
|
||||
updateEmptyVisibility()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
|
||||
StateSync.instance.deviceUpdatedOrAdded.remove(this)
|
||||
StateSync.instance.deviceRemoved.remove(this)
|
||||
}
|
||||
|
||||
private fun updateDeviceView(syncDeviceView: SyncDeviceView, publicKey: String, session: SyncSession?): SyncDeviceView {
|
||||
val connected = session?.connected ?: false
|
||||
syncDeviceView.setLinkType(if (connected) LinkType.Local else LinkType.None)
|
||||
.setName(publicKey)
|
||||
.setStatus(if (connected) "Connected" else "Disconnected")
|
||||
return syncDeviceView
|
||||
}
|
||||
|
||||
private fun updateEmptyVisibility() {
|
||||
if (_viewMap.isNotEmpty()) {
|
||||
_layoutEmpty.visibility = View.GONE
|
||||
} else {
|
||||
_layoutEmpty.visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
private fun initializeDevices() {
|
||||
_layoutDevices.removeAllViews()
|
||||
|
||||
for (publicKey in StateSync.instance.getAll()) {
|
||||
val syncDeviceView = SyncDeviceView(this)
|
||||
syncDeviceView.onRemove.subscribe {
|
||||
StateApp.instance.scopeOrNull?.launch {
|
||||
StateSync.instance.delete(publicKey)
|
||||
}
|
||||
}
|
||||
val view = updateDeviceView(syncDeviceView, publicKey, StateSync.instance.getSession(publicKey))
|
||||
_layoutDevices.addView(view)
|
||||
_viewMap[publicKey] = view
|
||||
}
|
||||
|
||||
updateEmptyVisibility()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "SyncHomeActivity"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
package com.futo.platformplayer.activities
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.util.Base64
|
||||
import android.view.View
|
||||
import android.widget.EditText
|
||||
import android.widget.ImageButton
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.futo.platformplayer.R
|
||||
import com.futo.platformplayer.logging.Logger
|
||||
import com.futo.platformplayer.setNavigationBarColorAndIcons
|
||||
import com.futo.platformplayer.states.StateApp
|
||||
import com.futo.platformplayer.states.StateSync
|
||||
import com.futo.platformplayer.sync.internal.SyncDeviceInfo
|
||||
import com.google.zxing.integration.android.IntentIntegrator
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
class SyncPairActivity : AppCompatActivity() {
|
||||
private lateinit var _editCode: EditText
|
||||
|
||||
private lateinit var _layoutPairing: LinearLayout
|
||||
private lateinit var _textPairingStatus: TextView
|
||||
|
||||
private lateinit var _layoutPairingSuccess: LinearLayout
|
||||
|
||||
private lateinit var _layoutPairingError: LinearLayout
|
||||
private lateinit var _textError: TextView
|
||||
|
||||
private val _qrCodeResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||
val scanResult = IntentIntegrator.parseActivityResult(result.resultCode, result.data)
|
||||
scanResult?.let {
|
||||
if (it.contents != null) {
|
||||
_editCode.text.clear()
|
||||
_editCode.text.append(it.contents)
|
||||
pair(it.contents)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun attachBaseContext(newBase: Context?) {
|
||||
super.attachBaseContext(StateApp.instance.getLocaleContext(newBase))
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_sync_pair)
|
||||
setNavigationBarColorAndIcons()
|
||||
|
||||
_editCode = findViewById(R.id.edit_code)
|
||||
_layoutPairing = findViewById(R.id.layout_pairing)
|
||||
_textPairingStatus = findViewById(R.id.text_pairing_status)
|
||||
_layoutPairingSuccess = findViewById(R.id.layout_pairing_success)
|
||||
_layoutPairingError = findViewById(R.id.layout_pairing_error)
|
||||
_textError = findViewById(R.id.text_error)
|
||||
|
||||
findViewById<ImageButton>(R.id.button_back).setOnClickListener {
|
||||
finish()
|
||||
}
|
||||
|
||||
findViewById<LinearLayout>(R.id.button_scan_qr).setOnClickListener {
|
||||
val integrator = IntentIntegrator(this)
|
||||
integrator.setDesiredBarcodeFormats(IntentIntegrator.QR_CODE)
|
||||
integrator.setPrompt(getString(R.string.scan_a_qr_code))
|
||||
integrator.setOrientationLocked(true);
|
||||
integrator.setCameraId(0)
|
||||
integrator.setBeepEnabled(false)
|
||||
integrator.setBarcodeImageEnabled(true)
|
||||
integrator.setCaptureActivity(QRCaptureActivity::class.java);
|
||||
_qrCodeResultLauncher.launch(integrator.createScanIntent())
|
||||
}
|
||||
|
||||
findViewById<LinearLayout>(R.id.button_link_new_device).setOnClickListener {
|
||||
pair(_editCode.text.toString())
|
||||
}
|
||||
|
||||
_layoutPairingSuccess.setOnClickListener {
|
||||
_layoutPairingSuccess.visibility = View.GONE
|
||||
}
|
||||
_layoutPairingError.setOnClickListener {
|
||||
_layoutPairingError.visibility = View.GONE
|
||||
}
|
||||
_layoutPairingSuccess.visibility = View.GONE
|
||||
_layoutPairingError.visibility = View.GONE
|
||||
}
|
||||
|
||||
fun pair(url: String) {
|
||||
try {
|
||||
_layoutPairing.visibility = View.VISIBLE
|
||||
_textPairingStatus.text = "Parsing text..."
|
||||
|
||||
if (!url.startsWith("grayjay://sync/")) {
|
||||
throw Exception("Not a valid URL: $url")
|
||||
}
|
||||
|
||||
val deviceInfo: SyncDeviceInfo = Json.decodeFromString<SyncDeviceInfo>(Base64.decode(url.substring("grayjay://sync/".length), Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP).decodeToString())
|
||||
if (StateSync.instance.isAuthorized(deviceInfo.publicKey)) {
|
||||
throw Exception("This device is already paired")
|
||||
}
|
||||
|
||||
_textPairingStatus.text = "Connecting..."
|
||||
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
StateSync.instance.connect(deviceInfo) { session, complete, message ->
|
||||
lifecycleScope.launch(Dispatchers.Main) {
|
||||
if (complete) {
|
||||
_layoutPairingSuccess.visibility = View.VISIBLE
|
||||
_layoutPairing.visibility = View.GONE
|
||||
} else {
|
||||
_textPairingStatus.text = message
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
withContext(Dispatchers.Main) {
|
||||
_layoutPairingError.visibility = View.VISIBLE
|
||||
_textError.text = e.message
|
||||
_layoutPairing.visibility = View.GONE
|
||||
Logger.e(TAG, "Failed to pair", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch(e: Throwable) {
|
||||
_layoutPairingError.visibility = View.VISIBLE
|
||||
_textError.text = e.message
|
||||
_layoutPairing.visibility = View.GONE
|
||||
Logger.e(TAG, "Failed to pair", e)
|
||||
} finally {
|
||||
_layoutPairing.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "SyncPairActivity"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
package com.futo.platformplayer.activities
|
||||
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
import android.util.Base64
|
||||
import android.util.TypedValue
|
||||
import android.view.View
|
||||
import android.widget.ImageButton
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.futo.platformplayer.R
|
||||
import com.futo.platformplayer.UIDialogs
|
||||
import com.futo.platformplayer.logging.Logger
|
||||
import com.futo.platformplayer.setNavigationBarColorAndIcons
|
||||
import com.futo.platformplayer.states.StateApp
|
||||
import com.futo.platformplayer.states.StateSync
|
||||
import com.futo.platformplayer.sync.internal.SyncDeviceInfo
|
||||
import com.google.zxing.BarcodeFormat
|
||||
import com.google.zxing.MultiFormatWriter
|
||||
import com.google.zxing.common.BitMatrix
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.net.NetworkInterface
|
||||
|
||||
class SyncShowPairingCodeActivity : AppCompatActivity() {
|
||||
private lateinit var _textCode: TextView
|
||||
private lateinit var _imageQR: ImageView
|
||||
private lateinit var _textQR: TextView
|
||||
private var _code: String? = null
|
||||
|
||||
override fun attachBaseContext(newBase: Context?) {
|
||||
super.attachBaseContext(StateApp.instance.getLocaleContext(newBase))
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
activity = null
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
activity = this
|
||||
|
||||
setContentView(R.layout.activity_sync_show_pairing_code)
|
||||
setNavigationBarColorAndIcons()
|
||||
|
||||
_textCode = findViewById(R.id.text_code)
|
||||
_imageQR = findViewById(R.id.image_qr)
|
||||
_textQR = findViewById(R.id.text_scan_qr)
|
||||
|
||||
findViewById<ImageButton>(R.id.button_back).setOnClickListener {
|
||||
finish()
|
||||
}
|
||||
|
||||
findViewById<LinearLayout>(R.id.button_copy).setOnClickListener {
|
||||
val code = _code ?: return@setOnClickListener
|
||||
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager;
|
||||
val clip = ClipData.newPlainText(getString(R.string.copied_text), code);
|
||||
clipboard.setPrimaryClip(clip);
|
||||
UIDialogs.toast(this, "Copied to clipboard")
|
||||
}
|
||||
|
||||
val ips = getIPs()
|
||||
val selfDeviceInfo = SyncDeviceInfo(StateSync.instance.publicKey!!, ips.toTypedArray(), StateSync.PORT)
|
||||
val json = Json.encodeToString(selfDeviceInfo)
|
||||
val base64 = Base64.encodeToString(json.toByteArray(), Base64.URL_SAFE or Base64.NO_PADDING or Base64.NO_WRAP)
|
||||
val url = "grayjay://sync/${base64}"
|
||||
setCode(url)
|
||||
}
|
||||
|
||||
fun setCode(code: String?) {
|
||||
_code = code
|
||||
|
||||
_textCode.text = code
|
||||
|
||||
if (code == null) {
|
||||
_imageQR.visibility = View.INVISIBLE
|
||||
_textQR.visibility = View.INVISIBLE
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
val dimension = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 200f, resources.displayMetrics).toInt()
|
||||
val qrCodeBitmap = generateQRCode(code, dimension, dimension)
|
||||
_imageQR.setImageBitmap(qrCodeBitmap)
|
||||
_imageQR.visibility = View.VISIBLE
|
||||
_textQR.visibility = View.VISIBLE
|
||||
} catch (e: Exception) {
|
||||
Logger.e(TAG, getString(R.string.failed_to_generate_qr_code), e)
|
||||
_imageQR.visibility = View.INVISIBLE
|
||||
_textQR.visibility = View.INVISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateQRCode(content: String, width: Int, height: Int): Bitmap {
|
||||
val bitMatrix = MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, width, height);
|
||||
return bitMatrixToBitmap(bitMatrix);
|
||||
}
|
||||
|
||||
private fun bitMatrixToBitmap(matrix: BitMatrix): Bitmap {
|
||||
val width = matrix.width;
|
||||
val height = matrix.height;
|
||||
val bmp = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
|
||||
|
||||
for (x in 0 until width) {
|
||||
for (y in 0 until height) {
|
||||
bmp.setPixel(x, y, if (matrix[x, y]) Color.BLACK else Color.WHITE);
|
||||
}
|
||||
}
|
||||
return bmp;
|
||||
}
|
||||
|
||||
private fun getIPs(): List<String> {
|
||||
val ips = arrayListOf<String>()
|
||||
for (intf in NetworkInterface.getNetworkInterfaces()) {
|
||||
for (addr in intf.inetAddresses) {
|
||||
if (addr.isLoopbackAddress) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (addr.address.size != 4) {
|
||||
continue
|
||||
}
|
||||
|
||||
addr.hostAddress?.let { ips.add(it) }
|
||||
}
|
||||
}
|
||||
return ips
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "SyncShowPairingCodeActivity"
|
||||
var activity: SyncShowPairingCodeActivity? = null
|
||||
private set
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,6 @@ import kotlinx.serialization.json.Json
|
||||
|
||||
class Serializer {
|
||||
companion object {
|
||||
val json = Json { ignoreUnknownKeys = true; encodeDefaults = true; };
|
||||
val json = Json { ignoreUnknownKeys = true; encodeDefaults = true; coerceInputValues = true };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.futo.platformplayer.api.media.models.comments
|
||||
|
||||
import com.futo.platformplayer.api.media.IPlatformClient
|
||||
import com.futo.platformplayer.api.media.models.PlatformAuthorLink
|
||||
import com.futo.platformplayer.api.media.models.ratings.IRating
|
||||
import com.futo.platformplayer.api.media.models.ratings.RatingLikes
|
||||
import com.futo.platformplayer.api.media.models.ratings.RatingType
|
||||
import com.futo.platformplayer.api.media.structures.IPager
|
||||
import com.futo.platformplayer.logging.Logger
|
||||
import kotlinx.coroutines.Deferred
|
||||
import java.time.OffsetDateTime
|
||||
|
||||
class LazyComment: IPlatformComment {
|
||||
private var _commentDeferred: Deferred<IPlatformComment>;
|
||||
private var _commentLoaded: IPlatformComment? = null;
|
||||
private var _commentException: Throwable? = null;
|
||||
|
||||
override val contextUrl: String
|
||||
get() = _commentLoaded?.contextUrl ?: "";
|
||||
override val author: PlatformAuthorLink
|
||||
get() = _commentLoaded?.author ?: PlatformAuthorLink.UNKNOWN;
|
||||
override val message: String
|
||||
get() = _commentLoaded?.message ?: "";
|
||||
override val rating: IRating
|
||||
get() = _commentLoaded?.rating ?: RatingLikes(0);
|
||||
override val date: OffsetDateTime?
|
||||
get() = _commentLoaded?.date ?: OffsetDateTime.MIN;
|
||||
override val replyCount: Int?
|
||||
get() = _commentLoaded?.replyCount ?: 0;
|
||||
|
||||
val isAvailable: Boolean get() = _commentLoaded != null;
|
||||
|
||||
private var _uiHandler: ((LazyComment)->Unit)? = null;
|
||||
|
||||
constructor(commentDeferred: Deferred<IPlatformComment>) {
|
||||
_commentDeferred = commentDeferred;
|
||||
_commentDeferred.invokeOnCompletion {
|
||||
if(it == null) {
|
||||
_commentLoaded = commentDeferred.getCompleted();
|
||||
Logger.i("LazyComment", "Resolved comment");
|
||||
}
|
||||
else {
|
||||
_commentException = it;
|
||||
Logger.e("LazyComment", "Resolving comment failed: ${it.message}", it);
|
||||
}
|
||||
|
||||
_uiHandler?.invoke(this);
|
||||
}
|
||||
}
|
||||
|
||||
fun getUnderlyingComment(): IPlatformComment? {
|
||||
return _commentLoaded;
|
||||
}
|
||||
|
||||
fun setUIHandler(handler: (LazyComment)->Unit){
|
||||
_uiHandler = handler;
|
||||
}
|
||||
|
||||
override fun getReplies(client: IPlatformClient): IPager<IPlatformComment>? {
|
||||
return _commentLoaded?.getReplies(client);
|
||||
}
|
||||
|
||||
}
|
||||
+1
-4
@@ -1,6 +1,3 @@
|
||||
package com.futo.platformplayer.api.media.models.streams.sources
|
||||
|
||||
interface IAudioUrlWidevineSource : IAudioUrlSource {
|
||||
val bearerToken: String
|
||||
val licenseUri: String
|
||||
}
|
||||
interface IAudioUrlWidevineSource : IAudioUrlSource, IWidevineSource
|
||||
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package com.futo.platformplayer.api.media.models.streams.sources
|
||||
|
||||
interface IDashManifestWidevineSource : IWidevineSource {
|
||||
val url: String
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
package com.futo.platformplayer.api.media.models.streams.sources
|
||||
|
||||
interface IVideoUrlWidevineSource : IVideoUrlSource, IWidevineSource
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package com.futo.platformplayer.api.media.models.streams.sources
|
||||
|
||||
import com.futo.platformplayer.api.media.platforms.js.models.JSRequestExecutor
|
||||
|
||||
interface IWidevineSource {
|
||||
val licenseUri: String
|
||||
val hasLicenseRequestExecutor: Boolean
|
||||
fun getLicenseRequestExecutor(): JSRequestExecutor?
|
||||
}
|
||||
+2
-2
@@ -19,9 +19,9 @@ open class SerializedPlatformVideo(
|
||||
override val thumbnails: Thumbnails,
|
||||
override val author: PlatformAuthorLink,
|
||||
@kotlinx.serialization.Serializable(with = OffsetDateTimeNullableSerializer::class)
|
||||
override val datetime: OffsetDateTime?,
|
||||
override val datetime: OffsetDateTime? = null,
|
||||
override val url: String,
|
||||
override val shareUrl: String,
|
||||
override val shareUrl: String = "",
|
||||
|
||||
override val duration: Long,
|
||||
override val viewCount: Long,
|
||||
|
||||
@@ -237,7 +237,8 @@ open class JSClient : IPlatformClient {
|
||||
hasGetLiveChatWindow = plugin.executeBoolean("!!source.getLiveChatWindow") ?: false,
|
||||
hasGetContentChapters = plugin.executeBoolean("!!source.getContentChapters") ?: false,
|
||||
hasPeekChannelContents = plugin.executeBoolean("!!source.peekChannelContents") ?: false,
|
||||
hasGetChannelPlaylists = plugin.executeBoolean("!!source.getChannelPlaylists") ?: false
|
||||
hasGetChannelPlaylists = plugin.executeBoolean("!!source.getChannelPlaylists") ?: false,
|
||||
hasGetContentRecommendations = plugin.executeBoolean("!!source.getContentRecommendations") ?: false
|
||||
);
|
||||
|
||||
try {
|
||||
|
||||
+2
-1
@@ -50,7 +50,8 @@ class SourcePluginConfig(
|
||||
var primaryClaimFieldType: Int? = null,
|
||||
var developerSubmitUrl: String? = null,
|
||||
var allowAllHttpHeaderAccess: Boolean = false,
|
||||
var maxDownloadParallelism: Int = 0
|
||||
var maxDownloadParallelism: Int = 0,
|
||||
var reduceFunctionsInLimitedVersion: Boolean = false,
|
||||
) : IV8PluginConfig {
|
||||
|
||||
val absoluteIconUrl: String? get() = resolveAbsoluteUrl(iconUrl, sourceUrl);
|
||||
|
||||
+1
-1
@@ -38,7 +38,7 @@ class JSHttpClient : ManagedHttpClient {
|
||||
|
||||
constructor(jsClient: JSClient?, auth: SourceAuth? = null, captcha: SourceCaptchaData? = null, config: SourcePluginConfig? = null) : super(
|
||||
//Temporary ugly solution for DevPortal proxy support
|
||||
(if(jsClient?.config?.id == StateDeveloper.DEV_ID && StateDeveloper.instance.devProxy != null)
|
||||
(if((jsClient?.config?.id == StateDeveloper.DEV_ID || jsClient == null) && StateDeveloper.instance.devProxy != null)
|
||||
OkHttpClient.Builder().proxy(Proxy(Proxy.Type.HTTP,
|
||||
InetSocketAddress(StateDeveloper.instance.devProxy!!.url, StateDeveloper.instance.devProxy!!.port)
|
||||
))
|
||||
|
||||
@@ -71,6 +71,8 @@ abstract class JSPager<T> : IPager<T> {
|
||||
|
||||
warnIfMainThread("JSPager.getResults");
|
||||
val items = pager.getOrThrow<V8ValueArray>(config, "results", "JSPager");
|
||||
if(items.v8Runtime.isDead || items.v8Runtime.isClosed)
|
||||
throw IllegalStateException("Runtime closed");
|
||||
val newResults = items.toArray()
|
||||
.map { convertResult(it as V8ValueObject) }
|
||||
.toList();
|
||||
|
||||
+2
-1
@@ -10,6 +10,7 @@ import com.futo.platformplayer.api.media.structures.IPager
|
||||
import com.futo.platformplayer.api.media.structures.ReusablePager
|
||||
import com.futo.platformplayer.getOrThrow
|
||||
import com.futo.platformplayer.models.Playlist
|
||||
import java.util.UUID
|
||||
|
||||
class JSPlaylistDetails: JSPlaylist, IPlatformPlaylistDetails {
|
||||
override val contents: IPager<IPlatformVideo>;
|
||||
@@ -37,6 +38,6 @@ class JSPlaylistDetails: JSPlaylist, IPlatformPlaylistDetails {
|
||||
onProgress?.invoke(videos.size);
|
||||
}
|
||||
|
||||
return Playlist(id.toString(), name, videos.map { SerializedPlatformVideo.fromVideo(it)});
|
||||
return Playlist(UUID.randomUUID().toString(), name, videos.map { SerializedPlatformVideo.fromVideo(it)});
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -42,7 +42,7 @@ class JSRequestExecutor {
|
||||
|
||||
//TODO: Executor properties?
|
||||
@Throws(ScriptException::class)
|
||||
open fun executeRequest(url: String, headers: Map<String, String>): ByteArray {
|
||||
open fun executeRequest(method: String, url: String, body: ByteArray?, headers: Map<String, String>): ByteArray {
|
||||
if (_executor.isClosed)
|
||||
throw IllegalStateException("Executor object is closed");
|
||||
|
||||
@@ -53,7 +53,7 @@ class JSRequestExecutor {
|
||||
"[${_config.name}] JSRequestExecutor",
|
||||
"builder.modifyRequest()"
|
||||
) {
|
||||
_executor.invoke("executeRequest", url, headers);
|
||||
_executor.invoke("executeRequest", url, headers, method, body);
|
||||
} as V8Value;
|
||||
}
|
||||
else V8Plugin.catchScriptErrors<Any>(
|
||||
@@ -61,7 +61,7 @@ class JSRequestExecutor {
|
||||
"[${_config.name}] JSRequestExecutor",
|
||||
"builder.modifyRequest()"
|
||||
) {
|
||||
_executor.invoke("executeRequest", url, headers);
|
||||
_executor.invoke("executeRequest", url, headers, method, body);
|
||||
} as V8Value;
|
||||
|
||||
try {
|
||||
|
||||
+20
-3
@@ -3,22 +3,39 @@ package com.futo.platformplayer.api.media.platforms.js.models.sources
|
||||
import com.caoccao.javet.values.reference.V8ValueObject
|
||||
import com.futo.platformplayer.api.media.models.streams.sources.IAudioUrlWidevineSource
|
||||
import com.futo.platformplayer.api.media.platforms.js.JSClient
|
||||
import com.futo.platformplayer.api.media.platforms.js.models.JSRequestExecutor
|
||||
import com.futo.platformplayer.engine.V8Plugin
|
||||
import com.futo.platformplayer.getOrThrow
|
||||
|
||||
class JSAudioUrlWidevineSource : JSAudioUrlSource, IAudioUrlWidevineSource {
|
||||
override val bearerToken: String
|
||||
override val licenseUri: String
|
||||
override val hasLicenseRequestExecutor: Boolean
|
||||
|
||||
@Suppress("ConvertSecondaryConstructorToPrimary")
|
||||
constructor(plugin: JSClient, obj: V8ValueObject) : super(plugin, obj) {
|
||||
val contextName = "JSAudioUrlWidevineSource"
|
||||
val config = plugin.config
|
||||
bearerToken = _obj.getOrThrow(config, "bearerToken", contextName)
|
||||
|
||||
licenseUri = _obj.getOrThrow(config, "licenseUri", contextName)
|
||||
hasLicenseRequestExecutor = obj.has("getLicenseRequestExecutor")
|
||||
}
|
||||
|
||||
override fun getLicenseRequestExecutor(): JSRequestExecutor? {
|
||||
if (!hasLicenseRequestExecutor || _obj.isClosed)
|
||||
return null
|
||||
|
||||
val result = V8Plugin.catchScriptErrors<Any>(_config, "[${_config.name}] JSAudioUrlWidevineSource", "obj.getLicenseRequestExecutor()") {
|
||||
_obj.invoke("getLicenseRequestExecutor", arrayOf<Any>())
|
||||
}
|
||||
|
||||
if (result !is V8ValueObject)
|
||||
return null
|
||||
|
||||
return JSRequestExecutor(_plugin, result)
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
val url = getAudioUrl()
|
||||
return "(name=$name, container=$container, bitrate=$bitrate, codec=$codec, url=$url, language=$language, duration=$duration, bearerToken=$bearerToken, licenseUri=$licenseUri)"
|
||||
return "(name=$name, container=$container, bitrate=$bitrate, codec=$codec, url=$url, language=$language, duration=$duration, hasLicenseRequestExecutor=${hasLicenseRequestExecutor}, licenseUri=$licenseUri)"
|
||||
}
|
||||
}
|
||||
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package com.futo.platformplayer.api.media.platforms.js.models.sources
|
||||
|
||||
import com.caoccao.javet.values.reference.V8ValueObject
|
||||
import com.futo.platformplayer.api.media.models.streams.sources.IDashManifestSource
|
||||
import com.futo.platformplayer.api.media.models.streams.sources.IDashManifestWidevineSource
|
||||
import com.futo.platformplayer.api.media.models.streams.sources.IVideoUrlSource
|
||||
import com.futo.platformplayer.api.media.platforms.js.JSClient
|
||||
import com.futo.platformplayer.api.media.platforms.js.models.JSRequestExecutor
|
||||
import com.futo.platformplayer.engine.V8Plugin
|
||||
import com.futo.platformplayer.getOrNull
|
||||
import com.futo.platformplayer.getOrThrow
|
||||
|
||||
class JSDashManifestWidevineSource : IVideoUrlSource, IDashManifestSource,
|
||||
IDashManifestWidevineSource, JSSource {
|
||||
override val width: Int = 0
|
||||
override val height: Int = 0
|
||||
override val container: String = "application/dash+xml"
|
||||
override val codec: String = "Dash"
|
||||
override val name: String
|
||||
override val bitrate: Int? = null
|
||||
override val url: String
|
||||
override val duration: Long
|
||||
|
||||
override var priority: Boolean = false
|
||||
|
||||
override val licenseUri: String
|
||||
override val hasLicenseRequestExecutor: Boolean
|
||||
|
||||
@Suppress("ConvertSecondaryConstructorToPrimary")
|
||||
constructor(plugin: JSClient, obj: V8ValueObject) : super(TYPE_DASH, plugin, obj) {
|
||||
val contextName = "DashWidevineSource"
|
||||
val config = plugin.config
|
||||
name = _obj.getOrThrow(config, "name", contextName)
|
||||
url = _obj.getOrThrow(config, "url", contextName)
|
||||
duration = _obj.getOrThrow(config, "duration", contextName)
|
||||
|
||||
priority = obj.getOrNull(config, "priority", contextName) ?: false
|
||||
|
||||
licenseUri = _obj.getOrThrow(config, "licenseUri", contextName)
|
||||
hasLicenseRequestExecutor = obj.has("getLicenseRequestExecutor")
|
||||
}
|
||||
|
||||
override fun getLicenseRequestExecutor(): JSRequestExecutor? {
|
||||
if (!hasLicenseRequestExecutor || _obj.isClosed)
|
||||
return null
|
||||
|
||||
val result = V8Plugin.catchScriptErrors<Any>(_config, "[${_config.name}] JSDashManifestWidevineSource", "obj.getLicenseRequestExecutor()") {
|
||||
_obj.invoke("getLicenseRequestExecutor", arrayOf<Any>())
|
||||
}
|
||||
|
||||
if (result !is V8ValueObject)
|
||||
return null
|
||||
|
||||
return JSRequestExecutor(_plugin, result)
|
||||
}
|
||||
|
||||
override fun getVideoUrl(): String {
|
||||
return url
|
||||
}
|
||||
}
|
||||
+4
@@ -98,18 +98,22 @@ abstract class JSSource {
|
||||
const val TYPE_AUDIO_WITH_METADATA = "AudioUrlRangeSource";
|
||||
const val TYPE_VIDEO_WITH_METADATA = "VideoUrlRangeSource";
|
||||
const val TYPE_DASH = "DashSource";
|
||||
const val TYPE_DASH_WIDEVINE = "DashWidevineSource";
|
||||
const val TYPE_DASH_RAW = "DashRawSource";
|
||||
const val TYPE_DASH_RAW_AUDIO = "DashRawAudioSource";
|
||||
const val TYPE_HLS = "HLSSource";
|
||||
const val TYPE_AUDIOURL_WIDEVINE = "AudioUrlWidevineSource"
|
||||
const val TYPE_VIDEOURL_WIDEVINE = "VideoUrlWidevineSource"
|
||||
|
||||
fun fromV8VideoNullable(plugin: JSClient, obj: V8Value?) : IVideoSource? = obj.orNull { fromV8Video(plugin, it as V8ValueObject) };
|
||||
fun fromV8Video(plugin: JSClient, obj: V8ValueObject) : IVideoSource? {
|
||||
val type = obj.getString("plugin_type");
|
||||
return when(type) {
|
||||
TYPE_VIDEOURL -> JSVideoUrlSource(plugin, obj);
|
||||
TYPE_VIDEOURL_WIDEVINE -> JSVideoUrlWidevineSource(plugin, obj);
|
||||
TYPE_VIDEO_WITH_METADATA -> JSVideoUrlRangeSource(plugin, obj);
|
||||
TYPE_HLS -> fromV8HLS(plugin, obj);
|
||||
TYPE_DASH_WIDEVINE -> JSDashManifestWidevineSource(plugin, obj)
|
||||
TYPE_DASH -> fromV8Dash(plugin, obj);
|
||||
TYPE_DASH_RAW -> fromV8DashRaw(plugin, obj);
|
||||
else -> {
|
||||
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
package com.futo.platformplayer.api.media.platforms.js.models.sources
|
||||
|
||||
import com.caoccao.javet.values.reference.V8ValueObject
|
||||
import com.futo.platformplayer.api.media.models.streams.sources.IVideoUrlWidevineSource
|
||||
import com.futo.platformplayer.api.media.platforms.js.JSClient
|
||||
import com.futo.platformplayer.api.media.platforms.js.models.JSRequestExecutor
|
||||
import com.futo.platformplayer.engine.V8Plugin
|
||||
import com.futo.platformplayer.getOrThrow
|
||||
|
||||
class JSVideoUrlWidevineSource : JSVideoUrlSource, IVideoUrlWidevineSource {
|
||||
override val licenseUri: String
|
||||
override val hasLicenseRequestExecutor: Boolean
|
||||
|
||||
@Suppress("ConvertSecondaryConstructorToPrimary")
|
||||
constructor(plugin: JSClient, obj: V8ValueObject) : super(plugin, obj) {
|
||||
val contextName = "JSAudioUrlWidevineSource"
|
||||
val config = plugin.config
|
||||
|
||||
licenseUri = _obj.getOrThrow(config, "licenseUri", contextName)
|
||||
hasLicenseRequestExecutor = obj.has("getLicenseRequestExecutor")
|
||||
}
|
||||
|
||||
override fun getLicenseRequestExecutor(): JSRequestExecutor? {
|
||||
if (!hasLicenseRequestExecutor || _obj.isClosed)
|
||||
return null
|
||||
|
||||
val result = V8Plugin.catchScriptErrors<Any>(_config, "[${_config.name}] JSAudioUrlWidevineSource", "obj.getLicenseRequestExecutor()") {
|
||||
_obj.invoke("getLicenseRequestExecutor", arrayOf<Any>())
|
||||
}
|
||||
|
||||
if (result !is V8ValueObject)
|
||||
return null
|
||||
|
||||
return JSRequestExecutor(_plugin, result)
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
val url = getVideoUrl()
|
||||
return "(width=$width, height=$height, container=$container, codec=$codec, name=$name, bitrate=$bitrate, duration=$duration, url=$url, hasLicenseRequestExecutor=$hasLicenseRequestExecutor, licenseUri=$licenseUri)"
|
||||
}
|
||||
}
|
||||
@@ -88,7 +88,8 @@ class DashBuilder : XMLBuilder {
|
||||
fun withRepresentationOnDemand(id: String, subtitleSource: ISubtitleSource, subtitleUrl: String) {
|
||||
withRepresentation(id, mapOf(
|
||||
Pair("mimeType", subtitleSource.format ?: "text/vtt"),
|
||||
Pair("startWithSAP", "1"),
|
||||
Pair("default", "true"),
|
||||
Pair("lang", "en"),
|
||||
Pair("bandwidth", "1000")
|
||||
)) {
|
||||
it.withBaseURL(subtitleUrl)
|
||||
@@ -151,7 +152,7 @@ class DashBuilder : XMLBuilder {
|
||||
)
|
||||
) {
|
||||
//TODO: Verify if & really should be replaced like this?
|
||||
it.withRepresentationOnDemand("1", subtitleSource, subtitleUrl.replace("&", "&"))
|
||||
it.withRepresentationOnDemand("caption_en", subtitleSource, subtitleUrl.replace("&", "&"))
|
||||
}
|
||||
}
|
||||
//Video
|
||||
@@ -164,7 +165,7 @@ class DashBuilder : XMLBuilder {
|
||||
Pair("subsegmentStartsWithSAP", "1")
|
||||
)
|
||||
) {
|
||||
it.withRepresentationOnDemand("1", vidSource, vidUrl.replace("&", "&"));
|
||||
it.withRepresentationOnDemand("2", vidSource, vidUrl.replace("&", "&"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1245,7 +1245,7 @@ class StateCasting {
|
||||
|
||||
val videoExecutor = _videoExecutor;
|
||||
if (videoExecutor != null) {
|
||||
val data = videoExecutor.executeRequest(originalUrl, httpContext.headers)
|
||||
val data = videoExecutor.executeRequest("GET", originalUrl, null, httpContext.headers)
|
||||
httpContext.respondBytes(200, HttpHeaders().apply {
|
||||
put("Content-Type", mediaType)
|
||||
}, data);
|
||||
@@ -1263,7 +1263,7 @@ class StateCasting {
|
||||
|
||||
val audioExecutor = _audioExecutor;
|
||||
if (audioExecutor != null) {
|
||||
val data = audioExecutor.executeRequest(originalUrl, httpContext.headers)
|
||||
val data = audioExecutor.executeRequest("GET", originalUrl, null, httpContext.headers)
|
||||
httpContext.respondBytes(200, HttpHeaders().apply {
|
||||
put("Content-Type", mediaType)
|
||||
}, data);
|
||||
|
||||
@@ -22,6 +22,7 @@ class AutomaticBackupDialog(context: Context) : AlertDialog(context) {
|
||||
private lateinit var _buttonCancel: ImageButton;
|
||||
|
||||
private lateinit var _editPassword: EditText;
|
||||
private lateinit var _editPassword2: EditText;
|
||||
|
||||
private lateinit var _inputMethodManager: InputMethodManager;
|
||||
|
||||
@@ -34,6 +35,7 @@ class AutomaticBackupDialog(context: Context) : AlertDialog(context) {
|
||||
_buttonStop = findViewById(R.id.button_stop);
|
||||
_buttonStart = findViewById(R.id.button_start);
|
||||
_editPassword = findViewById(R.id.edit_password);
|
||||
_editPassword2 = findViewById(R.id.edit_password2);
|
||||
|
||||
_inputMethodManager = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager;
|
||||
|
||||
@@ -52,6 +54,13 @@ class AutomaticBackupDialog(context: Context) : AlertDialog(context) {
|
||||
}
|
||||
|
||||
_buttonStart.setOnClickListener {
|
||||
val p1 = _editPassword.text.toString();
|
||||
val p2 = _editPassword2.text.toString();
|
||||
if(!(p1?.equals(p2) ?: false)) {
|
||||
UIDialogs.toast(context, "Password fields do not match, confirm that you typed it correctly.");
|
||||
return@setOnClickListener;
|
||||
}
|
||||
|
||||
val pbytes = _editPassword.text.toString().toByteArray();
|
||||
if(pbytes.size < 4 || pbytes.size > 32) {
|
||||
UIDialogs.toast(context, "Password needs to be atleast 4 bytes long and smaller than 32 bytes", false);
|
||||
|
||||
@@ -663,7 +663,7 @@ class VideoDownload {
|
||||
val url = foundTemplateUrl.replace("\$Number\$", indexCounter.toString());
|
||||
|
||||
val data = if(executor != null)
|
||||
executor.executeRequest(url, mapOf());
|
||||
executor.executeRequest("GET", url, null, mapOf());
|
||||
else {
|
||||
val resp = client.get(url, mutableMapOf());
|
||||
if(!resp.isOk)
|
||||
|
||||
@@ -133,6 +133,10 @@ class ChannelAboutFragment : Fragment, IChannelTabFragment {
|
||||
Logger.w(TAG, "Failed to parse claim=$c", e)
|
||||
}
|
||||
}
|
||||
if(!map.containsKey("Harbor"))
|
||||
this.context?.let {
|
||||
map.set("Harbor", polycentricProfile.getHarborUrl(it));
|
||||
}
|
||||
|
||||
if (map.isNotEmpty())
|
||||
setLinks(map, if (polycentricProfile.systemState.username.isNotBlank()) polycentricProfile.systemState.username else _lastChannel?.name ?: "")
|
||||
|
||||
+46
-24
@@ -7,6 +7,7 @@ import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.res.Configuration
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
@@ -34,7 +35,7 @@ import kotlin.math.roundToInt
|
||||
class MenuBottomBarFragment : MainActivityFragment() {
|
||||
private var _view: MenuBottomBarView? = null;
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
val view = MenuBottomBarView(this, inflater);
|
||||
_view = view;
|
||||
return view;
|
||||
@@ -56,7 +57,13 @@ class MenuBottomBarFragment : MainActivityFragment() {
|
||||
return _view?.onBackPressed() ?: false;
|
||||
}
|
||||
|
||||
@SuppressLint("ViewConstructor")
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
|
||||
_view?.updateAllButtonVisibility()
|
||||
}
|
||||
|
||||
@SuppressLint("ViewConstructor")
|
||||
class MenuBottomBarView : LinearLayout {
|
||||
private val _fragment: MenuBottomBarFragment;
|
||||
private val _inflater: LayoutInflater;
|
||||
@@ -76,7 +83,7 @@ class MenuBottomBarFragment : MainActivityFragment() {
|
||||
private var _buttonsVisible = 0;
|
||||
private var _subscriptionsVisible = true;
|
||||
|
||||
var currentButtonDefinitions: List<ButtonDefinition>? = null;
|
||||
private var currentButtonDefinitions: List<ButtonDefinition>? = null;
|
||||
|
||||
constructor(fragment: MenuBottomBarFragment, inflater: LayoutInflater) : super(inflater.context) {
|
||||
_fragment = fragment;
|
||||
@@ -132,7 +139,7 @@ class MenuBottomBarFragment : MainActivityFragment() {
|
||||
val staggerFactor = 3.0f
|
||||
|
||||
if (visible) {
|
||||
moreOverlay.visibility = LinearLayout.VISIBLE
|
||||
moreOverlay.visibility = VISIBLE
|
||||
val animations = arrayListOf<Animator>()
|
||||
animations.add(ObjectAnimator.ofFloat(moreOverlayBackground, "alpha", 0.0f, 1.0f).setDuration(duration))
|
||||
|
||||
@@ -161,7 +168,7 @@ class MenuBottomBarFragment : MainActivityFragment() {
|
||||
animatorSet.doOnEnd {
|
||||
_moreVisibleAnimating = false
|
||||
_moreVisible = false
|
||||
moreOverlay.visibility = LinearLayout.INVISIBLE
|
||||
moreOverlay.visibility = INVISIBLE
|
||||
}
|
||||
animatorSet.playTogether(animations)
|
||||
animatorSet.start()
|
||||
@@ -178,7 +185,7 @@ class MenuBottomBarFragment : MainActivityFragment() {
|
||||
_layoutBottomBarButtons.removeAllViews();
|
||||
|
||||
_layoutBottomBarButtons.addView(Space(context).apply {
|
||||
layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f)
|
||||
layoutParams = LayoutParams(0, LayoutParams.WRAP_CONTENT, 1f)
|
||||
})
|
||||
|
||||
for ((index, button) in buttons.withIndex()) {
|
||||
@@ -192,7 +199,7 @@ class MenuBottomBarFragment : MainActivityFragment() {
|
||||
_layoutBottomBarButtons.addView(menuButton)
|
||||
if (index < buttonDefinitions.size - 1) {
|
||||
_layoutBottomBarButtons.addView(Space(context).apply {
|
||||
layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f)
|
||||
layoutParams = LayoutParams(0, LayoutParams.WRAP_CONTENT, 1f)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -200,7 +207,7 @@ class MenuBottomBarFragment : MainActivityFragment() {
|
||||
}
|
||||
|
||||
_layoutBottomBarButtons.addView(Space(context).apply {
|
||||
layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f)
|
||||
layoutParams = LayoutParams(0, LayoutParams.WRAP_CONTENT, 1f)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -209,26 +216,30 @@ class MenuBottomBarFragment : MainActivityFragment() {
|
||||
_moreButtons.clear();
|
||||
_layoutMoreButtons.removeAllViews();
|
||||
|
||||
var insertedButtons = 0;
|
||||
//Force buy to be on top for more buttons
|
||||
val buyIndex = buttons.indexOfFirst { b -> b.id == 98 };
|
||||
if (buyIndex != -1) {
|
||||
val button = buttons[buyIndex]
|
||||
buttons.removeAt(buyIndex)
|
||||
buttons.add(0, button)
|
||||
insertedButtons++;
|
||||
}
|
||||
//Force faq to be second
|
||||
val faqIndex = buttons.indexOfFirst { b -> b.id == 97 };
|
||||
if (faqIndex != -1) {
|
||||
val button = buttons[faqIndex]
|
||||
buttons.removeAt(faqIndex)
|
||||
buttons.add(if (buttons.size == 1) 1 else 0, button)
|
||||
buttons.add(if (insertedButtons == 1) 1 else 0, button)
|
||||
insertedButtons++;
|
||||
}
|
||||
//Force privacy to be third
|
||||
val privacyIndex = buttons.indexOfFirst { b -> b.id == 96 };
|
||||
if (privacyIndex != -1) {
|
||||
val button = buttons[privacyIndex]
|
||||
buttons.removeAt(privacyIndex)
|
||||
buttons.add(if (buttons.size == 2) 2 else 1, button)
|
||||
buttons.add(if (insertedButtons == 2) 2 else (if(insertedButtons == 1) 1 else 0), button)
|
||||
insertedButtons++;
|
||||
}
|
||||
|
||||
for (data in buttons) {
|
||||
@@ -251,9 +262,20 @@ class MenuBottomBarFragment : MainActivityFragment() {
|
||||
button.updateActive(_fragment);
|
||||
}
|
||||
|
||||
override fun onConfigurationChanged(newConfig: Configuration?) {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
|
||||
updateAllButtonVisibility()
|
||||
}
|
||||
|
||||
fun updateAllButtonVisibility() {
|
||||
// if the more fly-out menu is open the we should close it
|
||||
if(_moreVisible) {
|
||||
setMoreVisible(false)
|
||||
}
|
||||
|
||||
val defs = currentButtonDefinitions?.toMutableList() ?: return
|
||||
val metrics = StateApp.instance.displayMetrics ?: resources.displayMetrics;
|
||||
val metrics = resources.displayMetrics
|
||||
_buttonsVisible = floor(metrics.widthPixels.toDouble() / 65.dp(resources).toDouble()).roundToInt();
|
||||
if (_buttonsVisible >= defs.size) {
|
||||
updateBottomMenuButtons(defs.toMutableList(), false);
|
||||
@@ -310,19 +332,6 @@ class MenuBottomBarFragment : MainActivityFragment() {
|
||||
if (!StatePayment.instance.hasPaid) {
|
||||
newCurrentButtonDefinitions.add(ButtonDefinition(98, R.drawable.ic_paid, R.drawable.ic_paid_filled, R.string.buy, canToggle = false, { it.currentMain is BuyFragment }, { it.navigate<BuyFragment>() }))
|
||||
}
|
||||
newCurrentButtonDefinitions.add(ButtonDefinition(97, R.drawable.ic_quiz, R.drawable.ic_quiz_fill, R.string.faq, canToggle = false, { false }, {
|
||||
it.navigate<BrowserFragment>(Settings.URL_FAQ);
|
||||
}))
|
||||
newCurrentButtonDefinitions.add(ButtonDefinition(96, R.drawable.ic_disabled_visible, R.drawable.ic_disabled_visible, R.string.privacy_mode, canToggle = false, { false }, {
|
||||
UIDialogs.showDialog(context, R.drawable.ic_disabled_visible_purple, "Privacy Mode",
|
||||
"All requests will be processed anonymously (unauthenticated), playback and history tracking will be disabled.\n\nTap the icon to disable.", null, 0,
|
||||
UIDialogs.Action("Cancel", {
|
||||
StateApp.instance.setPrivacyMode(false);
|
||||
}, UIDialogs.ActionStyle.NONE),
|
||||
UIDialogs.Action("Enable", {
|
||||
StateApp.instance.setPrivacyMode(true);
|
||||
}, UIDialogs.ActionStyle.PRIMARY));
|
||||
}))
|
||||
|
||||
//Add conditional buttons here, when you add a conditional button, be sure to add the register and unregister events for when the button needs to be updated
|
||||
|
||||
@@ -395,6 +404,19 @@ class MenuBottomBarFragment : MainActivityFragment() {
|
||||
if (c is Activity) {
|
||||
c.overridePendingTransition(R.anim.slide_in_up, R.anim.slide_darken);
|
||||
}
|
||||
}),
|
||||
ButtonDefinition(96, R.drawable.ic_disabled_visible, R.drawable.ic_disabled_visible, R.string.privacy_mode, canToggle = true, { false }, {
|
||||
UIDialogs.showDialog(it.context ?: return@ButtonDefinition, R.drawable.ic_disabled_visible_purple, "Privacy Mode",
|
||||
"All requests will be processed anonymously (any logins will be disabled except for the personalized home page), local playback and history tracking will also be disabled.\n\nTap the icon to disable.", null, 0,
|
||||
UIDialogs.Action("Cancel", {
|
||||
StateApp.instance.setPrivacyMode(false);
|
||||
}, UIDialogs.ActionStyle.NONE),
|
||||
UIDialogs.Action("Enable", {
|
||||
StateApp.instance.setPrivacyMode(true);
|
||||
}, UIDialogs.ActionStyle.PRIMARY));
|
||||
}),
|
||||
ButtonDefinition(97, R.drawable.ic_quiz, R.drawable.ic_quiz_fill, R.string.faq, canToggle = true, { false }, {
|
||||
it.navigate<BrowserFragment>(Settings.URL_FAQ);
|
||||
})
|
||||
//96 is reserved for privacy button
|
||||
//98 is reserved for buy button
|
||||
|
||||
@@ -10,6 +10,7 @@ import android.widget.TextView
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.futo.futopay.PaymentConfigurations
|
||||
import com.futo.futopay.PaymentManager
|
||||
import com.futo.futopay.formatMoney
|
||||
import com.futo.platformplayer.BuildConfig
|
||||
import com.futo.platformplayer.R
|
||||
import com.futo.platformplayer.UIDialogs
|
||||
@@ -94,9 +95,8 @@ class BuyFragment : MainFragment() {
|
||||
|
||||
if(currency != null && prices.containsKey(currency.id)) {
|
||||
val price = prices[currency.id]!!;
|
||||
val priceDecimal = (price.toDouble() / 100);
|
||||
withContext(Dispatchers.Main) {
|
||||
_buttonBuyText.text = currency.symbol + String.format("%.2f", priceDecimal) + context.getString(R.string.plus_tax);
|
||||
_buttonBuyText.text = formatMoney(country.id, currency.id, price) + context.getString(R.string.plus_tax);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+14
-6
@@ -1,7 +1,10 @@
|
||||
package com.futo.platformplayer.fragment.mainactivity.main
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.drawable.Animatable
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
@@ -12,6 +15,7 @@ import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.widget.AppCompatImageView
|
||||
import androidx.core.content.ContextCompat.startActivity
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import com.bumptech.glide.Glide
|
||||
@@ -52,7 +56,9 @@ import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuOverlay
|
||||
import com.futo.platformplayer.views.subscriptions.SubscribeButton
|
||||
import com.futo.polycentric.core.OwnedClaim
|
||||
import com.futo.polycentric.core.PublicKey
|
||||
import com.futo.polycentric.core.Store
|
||||
import com.futo.polycentric.core.SystemState
|
||||
import com.futo.polycentric.core.systemToURLInfoSystemLinkUrl
|
||||
import com.futo.polycentric.core.toURLInfoSystemLinkUrl
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
@@ -64,7 +70,13 @@ import kotlinx.serialization.Serializable
|
||||
@Serializable
|
||||
data class PolycentricProfile(
|
||||
val system: PublicKey, val systemState: SystemState, val ownedClaims: List<OwnedClaim>
|
||||
)
|
||||
) {
|
||||
fun getHarborUrl(context: Context): String{
|
||||
val systemState = SystemState.fromStorageTypeSystemState(Store.instance.getSystemState(system));
|
||||
val url = system.systemToURLInfoSystemLinkUrl(systemState.servers.asIterable());
|
||||
return "https://harbor.social/" + url.substring("polycentric://".length);
|
||||
}
|
||||
}
|
||||
|
||||
class ChannelFragment : MainFragment() {
|
||||
override val isMainView: Boolean = true
|
||||
@@ -225,11 +237,7 @@ class ChannelFragment : MainFragment() {
|
||||
}
|
||||
adapter.onAddToWatchLaterClicked.subscribe { content ->
|
||||
if (content is IPlatformVideo) {
|
||||
StatePlaylists.instance.addToWatchLater(
|
||||
SerializedPlatformVideo.fromVideo(
|
||||
content
|
||||
)
|
||||
)
|
||||
StatePlaylists.instance.addToWatchLater(SerializedPlatformVideo.fromVideo(content), true)
|
||||
UIDialogs.toast("Added to watch later\n[${content.name}]")
|
||||
}
|
||||
}
|
||||
|
||||
+26
-30
@@ -4,7 +4,7 @@ import android.content.Context
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.LinearLayout
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.futo.platformplayer.R
|
||||
import com.futo.platformplayer.Settings
|
||||
@@ -45,9 +45,7 @@ abstract class ContentFeedView<TFragment> : FeedView<TFragment, IPlatformContent
|
||||
private var _videoOptionsOverlay: SlideUpMenuOverlay? = null;
|
||||
protected open val shouldShowTimeBar: Boolean get() = true
|
||||
|
||||
constructor(fragment: TFragment, inflater: LayoutInflater, cachedRecyclerData: RecyclerData<InsertedViewAdapterWithLoader<ContentPreviewViewHolder>, LinearLayoutManager, IPager<IPlatformContent>, IPlatformContent, IPlatformContent, InsertedViewHolder<ContentPreviewViewHolder>>? = null) : super(fragment, inflater, cachedRecyclerData) {
|
||||
|
||||
}
|
||||
constructor(fragment: TFragment, inflater: LayoutInflater, cachedRecyclerData: RecyclerData<InsertedViewAdapterWithLoader<ContentPreviewViewHolder>, GridLayoutManager, IPager<IPlatformContent>, IPlatformContent, IPlatformContent, InsertedViewHolder<ContentPreviewViewHolder>>? = null) : super(fragment, inflater, cachedRecyclerData)
|
||||
|
||||
override fun filterResults(results: List<IPlatformContent>): List<IPlatformContent> {
|
||||
return results;
|
||||
@@ -55,16 +53,10 @@ abstract class ContentFeedView<TFragment> : FeedView<TFragment, IPlatformContent
|
||||
|
||||
override fun createAdapter(recyclerResults: RecyclerView, context: Context, dataset: ArrayList<IPlatformContent>): InsertedViewAdapterWithLoader<ContentPreviewViewHolder> {
|
||||
val player = StatePlayer.instance.getThumbnailPlayerOrCreate(context);
|
||||
player.modifyState("ThumbnailPlayer", { state -> state.muted = true });
|
||||
player.modifyState("ThumbnailPlayer") { state -> state.muted = true };
|
||||
_exoPlayer = player;
|
||||
|
||||
val v = LinearLayout(context).apply {
|
||||
layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
|
||||
orientation = LinearLayout.VERTICAL;
|
||||
};
|
||||
headerView = v;
|
||||
|
||||
return PreviewContentListAdapter(context, feedStyle, dataset, player, _previewsEnabled, arrayListOf(v), arrayListOf(), shouldShowTimeBar).apply {
|
||||
return PreviewContentListAdapter(context, feedStyle, dataset, player, _previewsEnabled, arrayListOf(), arrayListOf(), shouldShowTimeBar).apply {
|
||||
attachAdapterEvents(this);
|
||||
}
|
||||
}
|
||||
@@ -89,7 +81,7 @@ abstract class ContentFeedView<TFragment> : FeedView<TFragment, IPlatformContent
|
||||
};
|
||||
adapter.onAddToWatchLaterClicked.subscribe(this) {
|
||||
if(it is IPlatformVideo) {
|
||||
StatePlaylists.instance.addToWatchLater(SerializedPlatformVideo.fromVideo(it));
|
||||
StatePlaylists.instance.addToWatchLater(SerializedPlatformVideo.fromVideo(it), true);
|
||||
UIDialogs.toast("Added to watch later\n[${it.name}]");
|
||||
}
|
||||
};
|
||||
@@ -142,7 +134,10 @@ abstract class ContentFeedView<TFragment> : FeedView<TFragment, IPlatformContent
|
||||
val newQueue = listOf(content) + recyclerData.results
|
||||
.filterIsInstance<IPlatformVideo>()
|
||||
.filter { it != content };
|
||||
StatePlayer.instance.setQueue(newQueue, StatePlayer.TYPE_QUEUE, "Feed Queue", true, false);
|
||||
StatePlayer.instance.setQueue(newQueue, StatePlayer.TYPE_QUEUE, "Feed Queue",
|
||||
focus = true,
|
||||
shuffle = false
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -160,21 +155,22 @@ abstract class ContentFeedView<TFragment> : FeedView<TFragment, IPlatformContent
|
||||
adapter.onLongPress.remove(this);
|
||||
}
|
||||
|
||||
override fun onRestoreCachedData(cachedData: RecyclerData<InsertedViewAdapterWithLoader<ContentPreviewViewHolder>, LinearLayoutManager, IPager<IPlatformContent>, IPlatformContent, IPlatformContent, InsertedViewHolder<ContentPreviewViewHolder>>) {
|
||||
override fun onRestoreCachedData(cachedData: RecyclerData<InsertedViewAdapterWithLoader<ContentPreviewViewHolder>, GridLayoutManager, IPager<IPlatformContent>, IPlatformContent, IPlatformContent, InsertedViewHolder<ContentPreviewViewHolder>>) {
|
||||
super.onRestoreCachedData(cachedData)
|
||||
val v = LinearLayout(context).apply {
|
||||
layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
|
||||
orientation = LinearLayout.VERTICAL;
|
||||
};
|
||||
headerView = v;
|
||||
cachedData.adapter.viewsToPrepend.add(v);
|
||||
|
||||
(cachedData.adapter as PreviewContentListAdapter?)?.let { attachAdapterEvents(it) };
|
||||
}
|
||||
|
||||
override fun createLayoutManager(recyclerResults: RecyclerView, context: Context): LinearLayoutManager {
|
||||
val llmResults = LinearLayoutManager(context);
|
||||
llmResults.orientation = LinearLayoutManager.VERTICAL;
|
||||
return llmResults;
|
||||
override fun createLayoutManager(
|
||||
recyclerResults: RecyclerView,
|
||||
context: Context
|
||||
): GridLayoutManager {
|
||||
val glmResults =
|
||||
GridLayoutManager(
|
||||
context,
|
||||
(resources.configuration.screenWidthDp / resources.getDimension(R.dimen.landscape_threshold)).toInt() + 1
|
||||
);
|
||||
return glmResults
|
||||
}
|
||||
|
||||
override fun onScrollStateChanged(newState: Int) {
|
||||
@@ -217,11 +213,11 @@ abstract class ContentFeedView<TFragment> : FeedView<TFragment, IPlatformContent
|
||||
}
|
||||
|
||||
private fun playPreview() {
|
||||
if(feedStyle == FeedStyle.THUMBNAIL)
|
||||
if(feedStyle == FeedStyle.THUMBNAIL || recyclerData.layoutManager.spanCount > 1)
|
||||
return;
|
||||
|
||||
val firstVisible = recyclerData.layoutManager.findFirstVisibleItemPosition();
|
||||
val lastVisible = recyclerData.layoutManager.findLastVisibleItemPosition();
|
||||
val firstVisible = recyclerData.layoutManager.findFirstVisibleItemPosition()
|
||||
val lastVisible = recyclerData.layoutManager.findLastVisibleItemPosition()
|
||||
val itemsVisible = lastVisible - firstVisible + 1;
|
||||
val autoPlayIndex = (firstVisible + floor(itemsVisible / 2.0 + 0.49).toInt()).coerceAtLeast(0).coerceAtMost((recyclerData.results.size - 1));
|
||||
|
||||
@@ -241,7 +237,7 @@ abstract class ContentFeedView<TFragment> : FeedView<TFragment, IPlatformContent
|
||||
(recyclerData.adapter as PreviewContentListAdapter?)?.preview(viewHolder.childViewHolder)
|
||||
}
|
||||
|
||||
fun stopVideo() {
|
||||
private fun stopVideo() {
|
||||
//TODO: Is this still necessary?
|
||||
(recyclerData.adapter as PreviewContentListAdapter?)?.stopPreview();
|
||||
}
|
||||
@@ -269,6 +265,6 @@ abstract class ContentFeedView<TFragment> : FeedView<TFragment, IPlatformContent
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = "ContentFeedView";
|
||||
private const val TAG = "ContentFeedView";
|
||||
}
|
||||
}
|
||||
+21
-14
@@ -3,13 +3,9 @@ package com.futo.platformplayer.fragment.mainactivity.main
|
||||
import android.content.Context
|
||||
import android.util.TypedValue
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup.MarginLayoutParams
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.futo.platformplayer.*
|
||||
import com.futo.platformplayer.api.media.models.PlatformAuthorLink
|
||||
import com.futo.platformplayer.api.media.models.contents.IPlatformContent
|
||||
import com.futo.platformplayer.api.media.structures.*
|
||||
import com.futo.platformplayer.views.FeedStyle
|
||||
import com.futo.platformplayer.views.adapters.*
|
||||
@@ -18,9 +14,7 @@ import com.futo.platformplayer.views.adapters.viewholders.CreatorViewHolder
|
||||
abstract class CreatorFeedView<TFragment> : FeedView<TFragment, PlatformAuthorLink, PlatformAuthorLink, IPager<PlatformAuthorLink>, CreatorViewHolder> where TFragment : MainFragment {
|
||||
override val feedStyle: FeedStyle = FeedStyle.THUMBNAIL; //R.layout.list_creator;
|
||||
|
||||
constructor(fragment: TFragment, inflater: LayoutInflater) : super(fragment, inflater) {
|
||||
|
||||
}
|
||||
constructor(fragment: TFragment, inflater: LayoutInflater) : super(fragment, inflater)
|
||||
|
||||
override fun createAdapter(recyclerResults: RecyclerView, context: Context, dataset: ArrayList<PlatformAuthorLink>): InsertedViewAdapterWithLoader<CreatorViewHolder> {
|
||||
return InsertedViewAdapterWithLoader(context, arrayListOf(), arrayListOf(),
|
||||
@@ -34,18 +28,31 @@ abstract class CreatorFeedView<TFragment> : FeedView<TFragment, PlatformAuthorLi
|
||||
);
|
||||
}
|
||||
|
||||
override fun createLayoutManager(recyclerResults: RecyclerView, context: Context): LinearLayoutManager {
|
||||
val glmResults = GridLayoutManager(context, 2);
|
||||
glmResults.orientation = LinearLayoutManager.VERTICAL;
|
||||
/*
|
||||
* An empty override to remove the inherited span count update functionality
|
||||
*/
|
||||
override fun updateSpanCount(){
|
||||
|
||||
}
|
||||
|
||||
override fun createLayoutManager(
|
||||
recyclerResults: RecyclerView,
|
||||
context: Context
|
||||
): GridLayoutManager {
|
||||
val glmResults = GridLayoutManager(context, 2)
|
||||
|
||||
_swipeRefresh.layoutParams = (_swipeRefresh.layoutParams as MarginLayoutParams?)?.apply {
|
||||
rightMargin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8.0f, context.resources.displayMetrics).toInt();
|
||||
};
|
||||
rightMargin = TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP,
|
||||
8.0f,
|
||||
context.resources.displayMetrics
|
||||
).toInt()
|
||||
}
|
||||
|
||||
return glmResults;
|
||||
return glmResults
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = "CreatorFeedView";
|
||||
private const val TAG = "CreatorFeedView";
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,14 @@
|
||||
package com.futo.platformplayer.fragment.mainactivity.main
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.Color
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.*
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.RecyclerView.LayoutManager
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
@@ -33,7 +34,7 @@ abstract class FeedView<TFragment, TResult, TConverted, TPager, TViewHolder> : L
|
||||
protected val _recyclerResults: RecyclerView;
|
||||
protected val _overlayContainer: FrameLayout;
|
||||
protected val _swipeRefresh: SwipeRefreshLayout;
|
||||
private val _progress_bar: ProgressBar;
|
||||
private val _progressBar: ProgressBar;
|
||||
private val _spinnerSortBy: Spinner;
|
||||
private val _containerSortBy: LinearLayout;
|
||||
private val _tagsView: TagsView;
|
||||
@@ -44,7 +45,7 @@ abstract class FeedView<TFragment, TResult, TConverted, TPager, TViewHolder> : L
|
||||
|
||||
private var _loading: Boolean = true;
|
||||
|
||||
private val _pager_lock = Object();
|
||||
private val _pagerLock = Object();
|
||||
private var _cache: ItemCache<TResult>? = null;
|
||||
|
||||
open val visibleThreshold = 15;
|
||||
@@ -58,21 +59,21 @@ abstract class FeedView<TFragment, TResult, TConverted, TPager, TViewHolder> : L
|
||||
private var _activeTags: List<String>? = null;
|
||||
|
||||
private var _nextPageHandler: TaskHandler<TPager, List<TResult>>;
|
||||
val recyclerData: RecyclerData<InsertedViewAdapterWithLoader<TViewHolder>, LinearLayoutManager, TPager, TResult, TConverted, InsertedViewHolder<TViewHolder>>;
|
||||
val recyclerData: RecyclerData<InsertedViewAdapterWithLoader<TViewHolder>, GridLayoutManager, TPager, TResult, TConverted, InsertedViewHolder<TViewHolder>>;
|
||||
|
||||
val fragment: TFragment;
|
||||
|
||||
private val _scrollListener: RecyclerView.OnScrollListener;
|
||||
private var _automaticNextPageCounter = 0;
|
||||
|
||||
constructor(fragment: TFragment, inflater: LayoutInflater, cachedRecyclerData: RecyclerData<InsertedViewAdapterWithLoader<TViewHolder>, LinearLayoutManager, TPager, TResult, TConverted, InsertedViewHolder<TViewHolder>>? = null) : super(inflater.context) {
|
||||
constructor(fragment: TFragment, inflater: LayoutInflater, cachedRecyclerData: RecyclerData<InsertedViewAdapterWithLoader<TViewHolder>, GridLayoutManager, TPager, TResult, TConverted, InsertedViewHolder<TViewHolder>>? = null) : super(inflater.context) {
|
||||
this.fragment = fragment;
|
||||
inflater.inflate(R.layout.fragment_feed, this);
|
||||
|
||||
_textCentered = findViewById(R.id.text_centered);
|
||||
_emptyPagerContainer = findViewById(R.id.empty_pager_container);
|
||||
_progress_bar = findViewById(R.id.progress_bar);
|
||||
_progress_bar.inactiveColor = Color.TRANSPARENT;
|
||||
_progressBar = findViewById(R.id.progress_bar);
|
||||
_progressBar.inactiveColor = Color.TRANSPARENT;
|
||||
|
||||
_swipeRefresh = findViewById(R.id.swipe_refresh);
|
||||
val recyclerResults: RecyclerView = findViewById(R.id.list_results);
|
||||
@@ -158,7 +159,7 @@ abstract class FeedView<TFragment, TResult, TConverted, TPager, TViewHolder> : L
|
||||
super.onScrolled(recyclerView, dx, dy);
|
||||
|
||||
val visibleItemCount = _recyclerResults.childCount;
|
||||
val firstVisibleItem = recyclerData.layoutManager.findFirstVisibleItemPosition();
|
||||
val firstVisibleItem = recyclerData.layoutManager.findFirstVisibleItemPosition()
|
||||
//Logger.i(TAG, "onScrolled loadNextPage visibleItemCount=$visibleItemCount firstVisibleItem=$visibleItemCount")
|
||||
|
||||
if (!_loading && firstVisibleItem + visibleItemCount + visibleThreshold >= recyclerData.results.size && firstVisibleItem > 0) {
|
||||
@@ -179,14 +180,13 @@ abstract class FeedView<TFragment, TResult, TConverted, TPager, TViewHolder> : L
|
||||
if (firstVisibleItemPosition != RecyclerView.NO_POSITION) {
|
||||
val firstVisibleView = layoutManager.findViewByPosition(firstVisibleItemPosition)
|
||||
val itemHeight = firstVisibleView?.height ?: 0
|
||||
val occupiedSpace = recyclerData.results.size * itemHeight
|
||||
val occupiedSpace = recyclerData.results.size / recyclerData.layoutManager.spanCount * itemHeight
|
||||
val recyclerViewHeight = _recyclerResults.height
|
||||
Logger.i(TAG, "ensureEnoughContentVisible loadNextPage occupiedSpace=$occupiedSpace recyclerViewHeight=$recyclerViewHeight")
|
||||
occupiedSpace >= recyclerViewHeight
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
||||
}
|
||||
Logger.i(TAG, "ensureEnoughContentVisible loadNextPage canScroll=$canScroll _automaticNextPageCounter=$_automaticNextPageCounter")
|
||||
if (!canScroll || filteredResults.isEmpty()) {
|
||||
@@ -226,7 +226,19 @@ abstract class FeedView<TFragment, TResult, TConverted, TPager, TViewHolder> : L
|
||||
}
|
||||
}
|
||||
|
||||
open fun updateSpanCount() {
|
||||
recyclerData.layoutManager.spanCount = (resources.configuration.screenWidthDp / resources.getDimension(R.dimen.landscape_threshold)).toInt() + 1
|
||||
}
|
||||
|
||||
override fun onConfigurationChanged(newConfig: Configuration?) {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
|
||||
updateSpanCount()
|
||||
}
|
||||
|
||||
fun onResume() {
|
||||
updateSpanCount()
|
||||
|
||||
//Reload the pager if the plugin was killed
|
||||
val pager = recyclerData.pager;
|
||||
if((pager is MultiPager<*> && pager.findPager { it is JSPager<*> && !it.isAvailable } != null) ||
|
||||
@@ -252,7 +264,7 @@ abstract class FeedView<TFragment, TResult, TConverted, TPager, TViewHolder> : L
|
||||
protected open fun setActiveTags(activeTags: List<String>?) {
|
||||
_activeTags = activeTags;
|
||||
|
||||
if (activeTags != null && activeTags.isNotEmpty()) {
|
||||
if (!activeTags.isNullOrEmpty()) {
|
||||
_tagsView.setTags(activeTags);
|
||||
_tagsView.visibility = View.VISIBLE;
|
||||
} else {
|
||||
@@ -262,7 +274,7 @@ abstract class FeedView<TFragment, TResult, TConverted, TPager, TViewHolder> : L
|
||||
protected open fun setSortByOptions(options: List<String>?) {
|
||||
_sortByOptions = options;
|
||||
|
||||
if (options != null && options.isNotEmpty()) {
|
||||
if (!options.isNullOrEmpty()) {
|
||||
val allOptions = arrayListOf<String>();
|
||||
allOptions.add("Default");
|
||||
allOptions.addAll(options);
|
||||
@@ -277,19 +289,19 @@ abstract class FeedView<TFragment, TResult, TConverted, TPager, TViewHolder> : L
|
||||
}
|
||||
}
|
||||
protected abstract fun createAdapter(recyclerResults: RecyclerView, context: Context, dataset: ArrayList<TConverted>): InsertedViewAdapterWithLoader<TViewHolder>;
|
||||
protected abstract fun createLayoutManager(recyclerResults: RecyclerView, context: Context): LinearLayoutManager;
|
||||
protected open fun onRestoreCachedData(cachedData: RecyclerData<InsertedViewAdapterWithLoader<TViewHolder>, LinearLayoutManager, TPager, TResult, TConverted, InsertedViewHolder<TViewHolder>>) {}
|
||||
protected abstract fun createLayoutManager(recyclerResults: RecyclerView, context: Context): GridLayoutManager;
|
||||
protected open fun onRestoreCachedData(cachedData: RecyclerData<InsertedViewAdapterWithLoader<TViewHolder>, GridLayoutManager, TPager, TResult, TConverted, InsertedViewHolder<TViewHolder>>) {}
|
||||
|
||||
protected fun setProgress(fin: Int, total: Int) {
|
||||
val progress = (fin.toFloat() / total);
|
||||
_progress_bar.progress = progress;
|
||||
_progressBar.progress = progress;
|
||||
if(progress > 0 && progress < 1)
|
||||
{
|
||||
if(_progress_bar.height == 0)
|
||||
_progress_bar.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 5);
|
||||
if(_progressBar.height == 0)
|
||||
_progressBar.layoutParams = LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 5);
|
||||
}
|
||||
else if(_progress_bar.height > 0) {
|
||||
_progress_bar.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0);
|
||||
else if(_progressBar.height > 0) {
|
||||
_progressBar.layoutParams = LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -345,7 +357,7 @@ abstract class FeedView<TFragment, TResult, TConverted, TPager, TViewHolder> : L
|
||||
//insertPagerResults(_cache!!.cachePager.getResults(), false);
|
||||
}
|
||||
fun setPager(pager: TPager, cache: ItemCache<TResult>? = null) {
|
||||
synchronized(_pager_lock) {
|
||||
synchronized(_pagerLock) {
|
||||
detachParentPagerEvents();
|
||||
detachPagerEvents();
|
||||
|
||||
@@ -425,7 +437,7 @@ abstract class FeedView<TFragment, TResult, TConverted, TPager, TViewHolder> : L
|
||||
val p = recyclerData.pager;
|
||||
if(p is IReplacerPager<*>) {
|
||||
p.onReplaced.subscribe(this) { _, newItem ->
|
||||
synchronized(_pager_lock) {
|
||||
synchronized(_pagerLock) {
|
||||
val filtered = filterResults(listOf(newItem as TResult));
|
||||
if(filtered.isEmpty())
|
||||
return@subscribe;
|
||||
@@ -443,7 +455,7 @@ abstract class FeedView<TFragment, TResult, TConverted, TPager, TViewHolder> : L
|
||||
|
||||
var _lastNextPage = false;
|
||||
private fun loadNextPage() {
|
||||
synchronized(_pager_lock) {
|
||||
synchronized(_pagerLock) {
|
||||
val pager: TPager = recyclerData.pager ?: return;
|
||||
val hasMorePages = pager.hasMorePages();
|
||||
Logger.i(TAG, "loadNextPage() hasMorePages=$hasMorePages, page size=${pager.getResults().size}");
|
||||
@@ -468,7 +480,7 @@ abstract class FeedView<TFragment, TResult, TConverted, TPager, TViewHolder> : L
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = "FeedView";
|
||||
private const val TAG = "FeedView";
|
||||
}
|
||||
|
||||
abstract class ItemCache<TResult>(val cachePager: IPager<TResult>) {
|
||||
|
||||
+15
-19
@@ -6,7 +6,7 @@ import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import com.futo.platformplayer.*
|
||||
import com.futo.platformplayer.activities.MainActivity
|
||||
import com.futo.platformplayer.api.media.models.contents.IPlatformContent
|
||||
@@ -18,13 +18,9 @@ import com.futo.platformplayer.engine.exceptions.ScriptCaptchaRequiredException
|
||||
import com.futo.platformplayer.engine.exceptions.ScriptExecutionException
|
||||
import com.futo.platformplayer.engine.exceptions.ScriptImplementationException
|
||||
import com.futo.platformplayer.logging.Logger
|
||||
import com.futo.platformplayer.models.SearchType
|
||||
import com.futo.platformplayer.states.AnnouncementType
|
||||
import com.futo.platformplayer.states.StateAnnouncement
|
||||
import com.futo.platformplayer.states.StateApp
|
||||
import com.futo.platformplayer.states.StateMeta
|
||||
import com.futo.platformplayer.states.StatePlatform
|
||||
import com.futo.platformplayer.states.StateSubscriptions
|
||||
import com.futo.platformplayer.views.FeedStyle
|
||||
import com.futo.platformplayer.views.NoResultsView
|
||||
import com.futo.platformplayer.views.adapters.ContentPreviewViewHolder
|
||||
@@ -32,11 +28,8 @@ import com.futo.platformplayer.views.adapters.InsertedViewAdapterWithLoader
|
||||
import com.futo.platformplayer.views.adapters.InsertedViewHolder
|
||||
import com.futo.platformplayer.views.announcements.AnnouncementView
|
||||
import com.futo.platformplayer.views.buttons.BigButton
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.time.OffsetDateTime
|
||||
import java.util.UUID
|
||||
|
||||
class HomeFragment : MainFragment() {
|
||||
override val isMainView : Boolean = true;
|
||||
@@ -44,7 +37,7 @@ class HomeFragment : MainFragment() {
|
||||
override val hasBottomBar: Boolean get() = true;
|
||||
|
||||
private var _view: HomeView? = null;
|
||||
private var _cachedRecyclerData: FeedView.RecyclerData<InsertedViewAdapterWithLoader<ContentPreviewViewHolder>, LinearLayoutManager, IPager<IPlatformContent>, IPlatformContent, IPlatformContent, InsertedViewHolder<ContentPreviewViewHolder>>? = null;
|
||||
private var _cachedRecyclerData: FeedView.RecyclerData<InsertedViewAdapterWithLoader<ContentPreviewViewHolder>, GridLayoutManager, IPager<IPlatformContent>, IPlatformContent, IPlatformContent, InsertedViewHolder<ContentPreviewViewHolder>>? = null;
|
||||
|
||||
fun reloadFeed() {
|
||||
_view?.reloadFeed()
|
||||
@@ -101,15 +94,19 @@ class HomeFragment : MainFragment() {
|
||||
class HomeView : ContentFeedView<HomeFragment> {
|
||||
override val feedStyle: FeedStyle get() = Settings.instance.home.getHomeFeedStyle();
|
||||
|
||||
private var _announcementsView: AnnouncementView;
|
||||
private var _announcementsView: AnnouncementView = AnnouncementView(context, null).apply {
|
||||
if(!this.isClosed()) {
|
||||
recyclerData.adapter.viewsToPrepend.add(this)
|
||||
this.onClose.subscribe {
|
||||
recyclerData.adapter.viewsToPrepend.remove(this)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private val _taskGetPager: TaskHandler<Boolean, IPager<IPlatformContent>>;
|
||||
override val shouldShowTimeBar: Boolean get() = Settings.instance.home.progressBar
|
||||
|
||||
constructor(fragment: HomeFragment, inflater: LayoutInflater, cachedRecyclerData: RecyclerData<InsertedViewAdapterWithLoader<ContentPreviewViewHolder>, LinearLayoutManager, IPager<IPlatformContent>, IPlatformContent, IPlatformContent, InsertedViewHolder<ContentPreviewViewHolder>>? = null) : super(fragment, inflater, cachedRecyclerData) {
|
||||
_announcementsView = AnnouncementView(context, null).apply {
|
||||
headerView.addView(this);
|
||||
};
|
||||
constructor(fragment: HomeFragment, inflater: LayoutInflater, cachedRecyclerData: RecyclerData<InsertedViewAdapterWithLoader<ContentPreviewViewHolder>, GridLayoutManager, IPager<IPlatformContent>, IPlatformContent, IPlatformContent, InsertedViewHolder<ContentPreviewViewHolder>>? = null) : super(fragment, inflater, cachedRecyclerData) {
|
||||
|
||||
_taskGetPager = TaskHandler<Boolean, IPager<IPlatformContent>>({ fragment.lifecycleScope }, {
|
||||
StatePlatform.instance.getHomeRefresh(fragment.lifecycleScope)
|
||||
@@ -174,7 +171,7 @@ class HomeFragment : MainFragment() {
|
||||
loadResults();
|
||||
}
|
||||
|
||||
override fun getEmptyPagerView(): View? {
|
||||
override fun getEmptyPagerView(): View {
|
||||
val dp10 = 10.dp(resources);
|
||||
val dp30 = 30.dp(resources);
|
||||
|
||||
@@ -206,8 +203,7 @@ class HomeFragment : MainFragment() {
|
||||
listOf(BigButton(context, "Sources", "Go to the sources tab", R.drawable.ic_creators) {
|
||||
fragment.navigate<SourcesFragment>();
|
||||
}.withMargin(dp10, dp30))
|
||||
);
|
||||
return null;
|
||||
)
|
||||
}
|
||||
|
||||
override fun reload() {
|
||||
@@ -227,7 +223,7 @@ class HomeFragment : MainFragment() {
|
||||
//StateAnnouncement.instance.registerAnnouncement(UUID.randomUUID().toString(), context.getString(R.string.no_home_available), context.getString(R.string.no_home_page_is_available_please_check_if_you_are_connected_to_the_internet_and_refresh), AnnouncementType.SESSION);
|
||||
}
|
||||
|
||||
Logger.i(TAG, "Got new home pager ${pager}");
|
||||
Logger.i(TAG, "Got new home pager $pager");
|
||||
finishRefreshLayoutLoader();
|
||||
setLoading(false);
|
||||
setPager(pager);
|
||||
@@ -237,7 +233,7 @@ class HomeFragment : MainFragment() {
|
||||
}
|
||||
|
||||
companion object {
|
||||
val TAG = "HomeFragment";
|
||||
const val TAG = "HomeFragment";
|
||||
|
||||
fun newInstance() = HomeFragment().apply {}
|
||||
}
|
||||
|
||||
+5
-5
@@ -70,7 +70,7 @@ class PlaylistFragment : MainFragment() {
|
||||
private var _editPlaylistOverlay: SlideUpMenuOverlay? = null;
|
||||
private var _url: String? = null;
|
||||
|
||||
private val _taskLoadPlaylist: TaskHandler<String, IPlatformPlaylistDetails>;
|
||||
private val _taskLoadPlaylist: TaskHandler<String, Playlist>;
|
||||
|
||||
constructor(fragment: PlaylistFragment, inflater: LayoutInflater) : super(inflater) {
|
||||
_fragment = fragment;
|
||||
@@ -137,16 +137,16 @@ class PlaylistFragment : MainFragment() {
|
||||
);
|
||||
};
|
||||
|
||||
_taskLoadPlaylist = TaskHandler<String, IPlatformPlaylistDetails>(
|
||||
_taskLoadPlaylist = TaskHandler<String, Playlist>(
|
||||
StateApp.instance.scopeGetter,
|
||||
{
|
||||
return@TaskHandler StatePlatform.instance.getPlaylist(it);
|
||||
return@TaskHandler StatePlatform.instance.getPlaylist(it).toPlaylist();
|
||||
})
|
||||
.success {
|
||||
setName(it.name);
|
||||
//TODO: Implement support for pagination
|
||||
setVideos(it.toPlaylist().videos, false);
|
||||
setVideoCount(it.videoCount);
|
||||
setVideos(it.videos, false);
|
||||
setVideoCount(it.videos.size);
|
||||
setLoading(false);
|
||||
}
|
||||
.exception<Throwable> {
|
||||
|
||||
+6
-1
@@ -12,6 +12,7 @@ import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.futo.platformplayer.R
|
||||
@@ -23,6 +24,8 @@ import com.futo.platformplayer.states.StatePlaylists
|
||||
import com.futo.platformplayer.views.adapters.*
|
||||
import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuOverlay
|
||||
import com.google.android.material.appbar.AppBarLayout
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
||||
class PlaylistsFragment : MainFragment() {
|
||||
@@ -119,7 +122,9 @@ class PlaylistsFragment : MainFragment() {
|
||||
|
||||
findViewById<TextView>(R.id.text_view_all).setOnClickListener { _fragment.navigate<WatchLaterFragment>(context.getString(R.string.watch_later)); };
|
||||
StatePlaylists.instance.onWatchLaterChanged.subscribe(this) {
|
||||
updateWatchLater();
|
||||
fragment.lifecycleScope.launch(Dispatchers.Main) {
|
||||
updateWatchLater();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+27
-7
@@ -397,23 +397,43 @@ class SourceDetailFragment : MainFragment() {
|
||||
UIDialogs.Action("Cancel", {}, UIDialogs.ActionStyle.NONE),
|
||||
UIDialogs.Action("Login", {
|
||||
LoginActivity.showLogin(StateApp.instance.context, config) {
|
||||
StatePlugins.instance.setPluginAuth(config.id, it);
|
||||
reloadSource(config.id);
|
||||
try {
|
||||
StatePlugins.instance.setPluginAuth(config.id, it);
|
||||
reloadSource(config.id);
|
||||
} catch (e: Throwable) {
|
||||
StateApp.instance.scopeOrNull?.launch(Dispatchers.Main) {
|
||||
context?.let { c -> UIDialogs.showGeneralErrorDialog(c, "Failed to set plugin authentication (loginSource, loginWarning)", e) }
|
||||
}
|
||||
Logger.e(TAG, "Failed to set plugin authentication (loginSource, loginWarning)", e)
|
||||
}
|
||||
};
|
||||
}, UIDialogs.ActionStyle.PRIMARY))
|
||||
}
|
||||
else
|
||||
LoginActivity.showLogin(StateApp.instance.context, config) {
|
||||
StatePlugins.instance.setPluginAuth(config.id, it);
|
||||
reloadSource(config.id);
|
||||
try {
|
||||
StatePlugins.instance.setPluginAuth(config.id, it);
|
||||
reloadSource(config.id);
|
||||
} catch (e: Throwable) {
|
||||
StateApp.instance.scopeOrNull?.launch(Dispatchers.Main) {
|
||||
context?.let { c -> UIDialogs.showGeneralErrorDialog(c, "Failed to set plugin authentication (loginSource)", e) }
|
||||
}
|
||||
Logger.e(TAG, "Failed to set plugin authentication (loginSource)", e)
|
||||
}
|
||||
};
|
||||
}
|
||||
private fun logoutSource(clear: Boolean = true) {
|
||||
val config = _config ?: return;
|
||||
|
||||
StatePlugins.instance.setPluginAuth(config.id, null);
|
||||
reloadSource(config.id);
|
||||
|
||||
try {
|
||||
StatePlugins.instance.setPluginAuth(config.id, null);
|
||||
reloadSource(config.id);
|
||||
} catch (e: Throwable) {
|
||||
StateApp.instance.scopeOrNull?.launch(Dispatchers.Main) {
|
||||
context?.let { c -> UIDialogs.showGeneralErrorDialog(c, "Failed to clear plugin authentication", e) }
|
||||
}
|
||||
Logger.e(TAG, "Failed to clear plugin authentication", e)
|
||||
}
|
||||
|
||||
//TODO: Maybe add a dialog option..
|
||||
if(Settings.instance.plugins.clearCookiesOnLogout && clear) {
|
||||
|
||||
+6
-4
@@ -180,7 +180,7 @@ class SubscriptionGroupFragment : MainFragment() {
|
||||
UIDialogs.showDialog(context, R.drawable.ic_trash, "Delete Group", "Are you sure you want to this group?\n[${g.name}]?", null, 0,
|
||||
UIDialogs.Action("Cancel", {}),
|
||||
UIDialogs.Action("Delete", {
|
||||
StateSubscriptionGroups.instance.deleteSubscriptionGroup(g.id);
|
||||
StateSubscriptionGroups.instance.deleteSubscriptionGroup(g.id, true);
|
||||
_didDelete = true;
|
||||
fragment.close(true);
|
||||
}, UIDialogs.ActionStyle.DANGEROUS))
|
||||
@@ -253,7 +253,7 @@ class SubscriptionGroupFragment : MainFragment() {
|
||||
if(g.urls.isEmpty() && g.image == null) {
|
||||
//Obtain image
|
||||
for(sub in it) {
|
||||
val sub = StateSubscriptions.instance.getSubscription(sub);
|
||||
val sub = StateSubscriptions.instance.getSubscription(sub) ?: StateSubscriptions.instance.getSubscriptionOther(sub);
|
||||
if(sub != null && sub.channel.thumbnail != null) {
|
||||
g.image = ImageVariable.fromUrl(sub.channel.thumbnail!!);
|
||||
g.image?.setImageView(_imageGroup);
|
||||
@@ -308,8 +308,10 @@ class SubscriptionGroupFragment : MainFragment() {
|
||||
|
||||
if(group != null) {
|
||||
val urls = group.urls.toList();
|
||||
val subs = StateSubscriptions.instance.getSubscriptions().map { it.channel }
|
||||
_enabledCreators.addAll(subs.filter { urls.contains(it.url) });
|
||||
val subs = urls.map {
|
||||
(StateSubscriptions.instance.getSubscription(it) ?: StateSubscriptions.instance.getSubscriptionOther(it))?.channel
|
||||
}.filterNotNull();
|
||||
_enabledCreators.addAll(subs);
|
||||
}
|
||||
updateMeta();
|
||||
filterCreators();
|
||||
|
||||
+14
-4
@@ -14,6 +14,7 @@ import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.futo.platformplayer.R
|
||||
import com.futo.platformplayer.UIDialogs
|
||||
import com.futo.platformplayer.UISlideOverlays
|
||||
import com.futo.platformplayer.activities.AddSourceOptionsActivity
|
||||
import com.futo.platformplayer.fragment.mainactivity.topbar.AddTopBarFragment
|
||||
@@ -57,10 +58,19 @@ class SubscriptionGroupListFragment : MainFragment() {
|
||||
|
||||
};
|
||||
it.onDelete.subscribe { group ->
|
||||
val loc = _subs.indexOf(group);
|
||||
_subs.remove(group);
|
||||
_list?.adapter?.notifyItemRangeRemoved(loc);
|
||||
StateSubscriptionGroups.instance.deleteSubscriptionGroup(group.id);
|
||||
context?.let { context ->
|
||||
UIDialogs.showDialog(context, R.drawable.ic_trash, "Delete Group", "Are you sure you want to this group?\n[${group.name}]?", null, 0,
|
||||
UIDialogs.Action("Cancel", {}),
|
||||
UIDialogs.Action("Delete", {
|
||||
StateSubscriptionGroups.instance.deleteSubscriptionGroup(group.id, true);
|
||||
|
||||
val loc = _subs.indexOf(group);
|
||||
_subs.remove(group);
|
||||
_list?.adapter?.notifyItemRangeRemoved(loc);
|
||||
StateSubscriptionGroups.instance.deleteSubscriptionGroup(group.id, true);
|
||||
|
||||
}, UIDialogs.ActionStyle.DANGEROUS));
|
||||
}
|
||||
};
|
||||
it.onDragDrop.subscribe {
|
||||
_touchHelper?.startDrag(it);
|
||||
|
||||
+19
-17
@@ -5,12 +5,10 @@ import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.LinearLayout
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import com.futo.platformplayer.*
|
||||
import com.futo.platformplayer.activities.MainActivity
|
||||
import com.futo.platformplayer.api.media.IPlatformClient
|
||||
import com.futo.platformplayer.api.media.models.contents.ContentType
|
||||
import com.futo.platformplayer.api.media.models.contents.IPlatformContent
|
||||
import com.futo.platformplayer.api.media.models.video.IPlatformVideo
|
||||
@@ -27,6 +25,7 @@ import com.futo.platformplayer.states.StateApp
|
||||
import com.futo.platformplayer.states.StateCache
|
||||
import com.futo.platformplayer.states.StateHistory
|
||||
import com.futo.platformplayer.states.StatePlatform
|
||||
import com.futo.platformplayer.states.StatePlugins
|
||||
import com.futo.platformplayer.states.StateSubscriptions
|
||||
import com.futo.platformplayer.stores.FragmentedStorage
|
||||
import com.futo.platformplayer.stores.FragmentedStorageFileJson
|
||||
@@ -46,7 +45,6 @@ import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.nio.channels.Channel
|
||||
import java.time.OffsetDateTime
|
||||
import kotlin.system.measureTimeMillis
|
||||
|
||||
@@ -57,7 +55,7 @@ class SubscriptionsFeedFragment : MainFragment() {
|
||||
|
||||
private var _view: SubscriptionsFeedView? = null;
|
||||
private var _group: SubscriptionGroup? = null;
|
||||
private var _cachedRecyclerData: FeedView.RecyclerData<InsertedViewAdapterWithLoader<ContentPreviewViewHolder>, LinearLayoutManager, IPager<IPlatformContent>, IPlatformContent, IPlatformContent, InsertedViewHolder<ContentPreviewViewHolder>>? = null;
|
||||
private var _cachedRecyclerData: FeedView.RecyclerData<InsertedViewAdapterWithLoader<ContentPreviewViewHolder>, GridLayoutManager, IPager<IPlatformContent>, IPlatformContent, IPlatformContent, InsertedViewHolder<ContentPreviewViewHolder>>? = null;
|
||||
|
||||
override fun onShownWithView(parameter: Any?, isBack: Boolean) {
|
||||
super.onShownWithView(parameter, isBack);
|
||||
@@ -110,7 +108,7 @@ class SubscriptionsFeedFragment : MainFragment() {
|
||||
|
||||
var subGroup: SubscriptionGroup? = null;
|
||||
|
||||
constructor(fragment: SubscriptionsFeedFragment, inflater: LayoutInflater, cachedRecyclerData: RecyclerData<InsertedViewAdapterWithLoader<ContentPreviewViewHolder>, LinearLayoutManager, IPager<IPlatformContent>, IPlatformContent, IPlatformContent, InsertedViewHolder<ContentPreviewViewHolder>>? = null) : super(fragment, inflater, cachedRecyclerData) {
|
||||
constructor(fragment: SubscriptionsFeedFragment, inflater: LayoutInflater, cachedRecyclerData: RecyclerData<InsertedViewAdapterWithLoader<ContentPreviewViewHolder>, GridLayoutManager, IPager<IPlatformContent>, IPlatformContent, IPlatformContent, InsertedViewHolder<ContentPreviewViewHolder>>? = null) : super(fragment, inflater, cachedRecyclerData) {
|
||||
Logger.i(TAG, "SubscriptionsFeedFragment constructor()");
|
||||
StateSubscriptions.instance.global.onUpdateProgress.subscribe(this) { progress, total ->
|
||||
};
|
||||
@@ -151,16 +149,19 @@ class SubscriptionsFeedFragment : MainFragment() {
|
||||
val homeTab = Settings.instance.tabs.find { it.id == 0 };
|
||||
val isHomeEnabled = homeTab?.enabled == true;
|
||||
if (announcementsView != null && isHomeEnabled) {
|
||||
headerView.removeView(announcementsView);
|
||||
_announcementsView = null;
|
||||
recyclerData.adapter.viewsToPrepend.remove(announcementsView)
|
||||
_announcementsView = null
|
||||
}
|
||||
|
||||
if (announcementsView == null && !isHomeEnabled) {
|
||||
val c = context;
|
||||
if (c != null) {
|
||||
_announcementsView = AnnouncementView(c, null).apply {
|
||||
headerView.addView(this)
|
||||
};
|
||||
recyclerData.adapter.viewsToPrepend.add(this)
|
||||
this.onClose.subscribe {
|
||||
recyclerData.adapter.viewsToPrepend.remove(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,7 +215,7 @@ class SubscriptionsFeedFragment : MainFragment() {
|
||||
val subRequestCounts = StateSubscriptions.instance.getSubscriptionRequestCount(group);
|
||||
val reqCountStr = subRequestCounts.map { " ${it.key.config.name}: ${it.value}/${it.key.getSubscriptionRateLimit()}" }.joinToString("\n");
|
||||
val rateLimitPlugins = subRequestCounts.filter { clientCount -> clientCount.key.getSubscriptionRateLimit()?.let { rateLimit -> clientCount.value > rateLimit } == true }
|
||||
Logger.w(TAG, "Trying to refreshing subscriptions with requests:\n" + reqCountStr);
|
||||
Logger.w(TAG, "Trying to refreshing subscriptions with requests:\n$reqCountStr");
|
||||
if(rateLimitPlugins.any())
|
||||
throw RateLimitException(rateLimitPlugins.map { it.key.id });
|
||||
}
|
||||
@@ -276,7 +277,7 @@ class SubscriptionsFeedFragment : MainFragment() {
|
||||
|
||||
private fun initializeToolbarContent() {
|
||||
_subscriptionBar = SubscriptionBar(context).apply {
|
||||
layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
|
||||
layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
|
||||
};
|
||||
_subscriptionBar?.onClickChannel?.subscribe { c -> fragment.navigate<ChannelFragment>(c); };
|
||||
_subscriptionBar?.onToggleGroup?.subscribe { g ->
|
||||
@@ -364,6 +365,7 @@ class SubscriptionsFeedFragment : MainFragment() {
|
||||
}
|
||||
|
||||
override fun reload() {
|
||||
StatePlugins.instance.clearUpdating(); //Fallback in case it doesnt clear, UI should be blocked.
|
||||
loadResults(true);
|
||||
}
|
||||
|
||||
@@ -395,7 +397,7 @@ class SubscriptionsFeedFragment : MainFragment() {
|
||||
_taskGetPager.run(withRefetch);
|
||||
}
|
||||
|
||||
override fun onRestoreCachedData(cachedData: RecyclerData<InsertedViewAdapterWithLoader<ContentPreviewViewHolder>, LinearLayoutManager, IPager<IPlatformContent>, IPlatformContent, IPlatformContent, InsertedViewHolder<ContentPreviewViewHolder>>) {
|
||||
override fun onRestoreCachedData(cachedData: RecyclerData<InsertedViewAdapterWithLoader<ContentPreviewViewHolder>, GridLayoutManager, IPager<IPlatformContent>, IPlatformContent, IPlatformContent, InsertedViewHolder<ContentPreviewViewHolder>>) {
|
||||
super.onRestoreCachedData(cachedData);
|
||||
setEmptyPager(cachedData.results.isEmpty());
|
||||
}
|
||||
@@ -450,7 +452,7 @@ class SubscriptionsFeedFragment : MainFragment() {
|
||||
if (toShow is PluginException)
|
||||
UIDialogs.appToast(ToastView.Toast(
|
||||
toShow.message +
|
||||
(if(channel != null) "\nChannel: " + channel else ""), false, null,
|
||||
(if(channel != null) "\nChannel: $channel" else ""), false, null,
|
||||
"Plugin ${toShow.config.name} failed")
|
||||
);
|
||||
else
|
||||
@@ -461,14 +463,14 @@ class SubscriptionsFeedFragment : MainFragment() {
|
||||
val failedChannels = exs.filterIsInstance<ChannelException>().map { it.channelNameOrUrl }.distinct().toList();
|
||||
val failedPlugins = exs.filter { it is PluginException || (it is ChannelException && it.cause is PluginException) }
|
||||
.map { if(it is ChannelException) (it.cause as PluginException) else if(it is PluginException) it else null }
|
||||
.filter { it != null }
|
||||
.filterNotNull()
|
||||
.distinctBy { it?.config?.name }
|
||||
.map { it!! }
|
||||
.toList();
|
||||
for(distinctPluginFail in failedPlugins)
|
||||
UIDialogs.appToast(context.getString(R.string.plugin_pluginname_failed_message).replace("{pluginName}", distinctPluginFail.config.name).replace("{message}", distinctPluginFail.message ?: ""));
|
||||
if(failedChannels.isNotEmpty())
|
||||
UIDialogs.appToast(ToastView.Toast(failedChannels.take(3).map { "- ${it}" }.joinToString("\n") +
|
||||
UIDialogs.appToast(ToastView.Toast(failedChannels.take(3).map { "- $it" }.joinToString("\n") +
|
||||
(if(failedChannels.size >= 3) "\nAnd ${failedChannels.size - 3} more" else ""), false, null, "Failed Channels"));
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
@@ -480,7 +482,7 @@ class SubscriptionsFeedFragment : MainFragment() {
|
||||
}
|
||||
|
||||
companion object {
|
||||
val TAG = "SubscriptionsFeedFragment";
|
||||
const val TAG = "SubscriptionsFeedFragment";
|
||||
|
||||
fun newInstance() = SubscriptionsFeedFragment().apply {}
|
||||
}
|
||||
|
||||
+12
-3
@@ -6,6 +6,7 @@ import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.ScrollView
|
||||
import android.widget.TextView
|
||||
import com.futo.platformplayer.*
|
||||
import com.futo.platformplayer.api.media.IPlatformClient
|
||||
@@ -58,7 +59,15 @@ class TutorialFragment : MainFragment() {
|
||||
}
|
||||
|
||||
@SuppressLint("ViewConstructor")
|
||||
class TutorialView : LinearLayout {
|
||||
class TutorialView(fragment: TutorialFragment, inflater: LayoutInflater) :
|
||||
ScrollView(inflater.context) {
|
||||
init {
|
||||
addView(TutorialContainer(fragment, inflater))
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("ViewConstructor")
|
||||
class TutorialContainer : LinearLayout {
|
||||
val fragment: TutorialFragment
|
||||
|
||||
constructor(fragment: TutorialFragment, inflater: LayoutInflater) : super(inflater.context) {
|
||||
@@ -150,7 +159,7 @@ class TutorialFragment : MainFragment() {
|
||||
}
|
||||
|
||||
companion object {
|
||||
val TAG = "HomeFragment";
|
||||
const val TAG = "HomeFragment";
|
||||
|
||||
fun newInstance() = TutorialFragment().apply {}
|
||||
val initialSetupVideos = listOf(
|
||||
@@ -200,7 +209,7 @@ class TutorialFragment : MainFragment() {
|
||||
TutorialVideo(
|
||||
uuid = "94d36959-e3fc-4c24-a988-89147067a179",
|
||||
name = "Casting",
|
||||
description = "Learn about casting in Grayjay. How do I show video on my TV?",
|
||||
description = "Learn about casting in Grayjay. How do I show video on my TV?\nhttps://fcast.org/",
|
||||
thumbnailUrl = "https://releases.grayjay.app/tutorials/how-to-cast.jpg",
|
||||
videoUrl = "https://releases.grayjay.app/tutorials/how-to-cast.mp4",
|
||||
duration = 79
|
||||
|
||||
+108
-89
@@ -1,11 +1,10 @@
|
||||
package com.futo.platformplayer.fragment.mainactivity.main
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.content.res.Configuration
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
@@ -14,10 +13,9 @@ import android.view.WindowInsetsController
|
||||
import android.view.WindowManager
|
||||
import androidx.constraintlayout.motion.widget.MotionLayout
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import com.futo.platformplayer.R
|
||||
import com.futo.platformplayer.Settings
|
||||
import com.futo.platformplayer.SimpleOrientationListener
|
||||
import com.futo.platformplayer.UIDialogs
|
||||
import com.futo.platformplayer.activities.SettingsActivity
|
||||
import com.futo.platformplayer.api.media.models.video.IPlatformVideo
|
||||
@@ -25,14 +23,14 @@ import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails
|
||||
import com.futo.platformplayer.casting.StateCasting
|
||||
import com.futo.platformplayer.constructs.Event0
|
||||
import com.futo.platformplayer.constructs.Event1
|
||||
import com.futo.platformplayer.listeners.AutoRotateChangeListener
|
||||
import com.futo.platformplayer.logging.Logger
|
||||
import com.futo.platformplayer.models.PlatformVideoWithTime
|
||||
import com.futo.platformplayer.models.UrlVideoWithTime
|
||||
import com.futo.platformplayer.states.StatePlayer
|
||||
import com.futo.platformplayer.views.containers.SingleViewTouchableMotionLayout
|
||||
import kotlin.math.min
|
||||
|
||||
|
||||
@UnstableApi
|
||||
class VideoDetailFragment : MainFragment {
|
||||
override val isMainView : Boolean = false;
|
||||
override val hasBottomBar: Boolean = true;
|
||||
@@ -43,11 +41,13 @@ class VideoDetailFragment : MainFragment {
|
||||
|
||||
private var _viewDetail : VideoDetailView? = null;
|
||||
private var _view : SingleViewTouchableMotionLayout? = null;
|
||||
private lateinit var _autoRotateChangeListener: AutoRotateChangeListener
|
||||
private lateinit var _orientationListener: SimpleOrientationListener
|
||||
private var _currentOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
|
||||
|
||||
var isFullscreen : Boolean = false;
|
||||
/**
|
||||
* whether the view is in the process of switching from full-screen maximized to minimized
|
||||
* this is used to detect that the app is skipping the non full-screen maximized state
|
||||
*/
|
||||
var isMinimizingFromFullScreen : Boolean = false;
|
||||
val onFullscreenChanged = Event1<Boolean>();
|
||||
var isTransitioning : Boolean = false
|
||||
private set;
|
||||
@@ -77,8 +77,7 @@ class VideoDetailFragment : MainFragment {
|
||||
private var _leavingPiP = false;
|
||||
|
||||
//region Fragment
|
||||
constructor() : super() {
|
||||
}
|
||||
constructor() : super()
|
||||
|
||||
fun nextVideo() {
|
||||
_viewDetail?.nextVideo(true, true, true);
|
||||
@@ -88,45 +87,105 @@ class VideoDetailFragment : MainFragment {
|
||||
_viewDetail?.prevVideo(true);
|
||||
}
|
||||
|
||||
private fun onStateChanged(state: VideoDetailFragment.State) {
|
||||
private fun isSmallWindow(): Boolean {
|
||||
return min(
|
||||
resources.configuration.screenWidthDp,
|
||||
resources.configuration.screenHeightDp
|
||||
) < resources.getDimension(R.dimen.landscape_threshold)
|
||||
}
|
||||
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
|
||||
val isLandscapeVideo: Boolean = _viewDetail?.isLandscapeVideo() ?: false
|
||||
|
||||
val isSmallWindow = isSmallWindow()
|
||||
|
||||
if (
|
||||
isSmallWindow
|
||||
&& newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE
|
||||
&& !isFullscreen
|
||||
&& state == State.MAXIMIZED
|
||||
) {
|
||||
_viewDetail?.setFullscreen(true)
|
||||
} else if (
|
||||
isSmallWindow
|
||||
&& isFullscreen
|
||||
&& !Settings.instance.playback.fullscreenPortrait
|
||||
&& newConfig.orientation == Configuration.ORIENTATION_PORTRAIT
|
||||
&& isLandscapeVideo
|
||||
) {
|
||||
_viewDetail?.setFullscreen(false)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onStateChanged(state: State) {
|
||||
if (
|
||||
isSmallWindow()
|
||||
&& state == State.MAXIMIZED
|
||||
&& !isFullscreen
|
||||
&& resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
|
||||
) {
|
||||
_viewDetail?.setFullscreen(true)
|
||||
}
|
||||
|
||||
updateOrientation()
|
||||
}
|
||||
|
||||
private fun updateOrientation() {
|
||||
val a = activity ?: return
|
||||
val isMaximized = state == State.MAXIMIZED
|
||||
val isFullScreenPortraitAllowed = Settings.instance.playback.fullscreenPortrait;
|
||||
val bypassRotationPrevention = Settings.instance.other.bypassRotationPrevention;
|
||||
val currentRequestedOrientation = a.requestedOrientation
|
||||
val currentOrientation = if (_currentOrientation == -1) currentRequestedOrientation else _currentOrientation
|
||||
val isAutoRotate = Settings.instance.playback.isAutoRotate()
|
||||
val isFs = isFullscreen
|
||||
|
||||
if (isFs && isMaximized) {
|
||||
if (isFullScreenPortraitAllowed) {
|
||||
if (isAutoRotate) {
|
||||
a.requestedOrientation = currentOrientation
|
||||
}
|
||||
} else if (currentOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE || currentOrientation == ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE) {
|
||||
if (isAutoRotate) {
|
||||
a.requestedOrientation = currentOrientation
|
||||
}
|
||||
} else {
|
||||
a.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
|
||||
}
|
||||
} else if (bypassRotationPrevention) {
|
||||
if (isAutoRotate) {
|
||||
a.requestedOrientation = currentOrientation
|
||||
}
|
||||
} else if (currentOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || currentOrientation == ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT) {
|
||||
if (isAutoRotate) {
|
||||
a.requestedOrientation = currentOrientation
|
||||
}
|
||||
} else {
|
||||
a.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
|
||||
private fun onVideoChanged(videoWidth : Int, videoHeight: Int) {
|
||||
if (
|
||||
isSmallWindow()
|
||||
&& state == State.MAXIMIZED
|
||||
&& !isFullscreen
|
||||
&& videoHeight > videoWidth
|
||||
) {
|
||||
_viewDetail?.setFullscreen(true)
|
||||
}
|
||||
}
|
||||
|
||||
Log.i(TAG, "updateOrientation (isFs = ${isFs}, currentOrientation = ${currentOrientation}, currentRequestedOrientation = ${currentRequestedOrientation}, isMaximized = ${isMaximized}, isAutoRotate = ${isAutoRotate}, isFullScreenPortraitAllowed = ${isFullScreenPortraitAllowed}) resulted in requested orientation ${activity?.requestedOrientation}");
|
||||
@SuppressLint("SourceLockedOrientationActivity")
|
||||
fun updateOrientation() {
|
||||
val a = activity ?: return
|
||||
val isFullScreenPortraitAllowed = Settings.instance.playback.fullscreenPortrait
|
||||
val isReversePortraitAllowed = Settings.instance.playback.reversePortrait
|
||||
val rotationLock = StatePlayer.instance.rotationLock
|
||||
|
||||
val isLandscapeVideo: Boolean = _viewDetail?.isLandscapeVideo() ?: false
|
||||
|
||||
val isSmallWindow = isSmallWindow()
|
||||
|
||||
// For small windows if the device isn't landscape right now and full screen portrait isn't allowed then we should force landscape
|
||||
if (isSmallWindow && isFullscreen && !isFullScreenPortraitAllowed && resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT && !rotationLock && isLandscapeVideo) {
|
||||
a.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
|
||||
}
|
||||
// For small windows if the device isn't in a portrait orientation and we're in the maximized state then we should force portrait
|
||||
else if (isSmallWindow && !isMinimizingFromFullScreen && !isFullscreen && state == State.MAXIMIZED && resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||
a.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT
|
||||
} else if (rotationLock) {
|
||||
a.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED
|
||||
} else {
|
||||
when (Settings.instance.playback.autoRotate) {
|
||||
0 -> {
|
||||
a.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED
|
||||
}
|
||||
|
||||
1 -> {
|
||||
a.requestedOrientation = if (isReversePortraitAllowed) {
|
||||
ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR
|
||||
} else {
|
||||
ActivityInfo.SCREEN_ORIENTATION_SENSOR
|
||||
}
|
||||
}
|
||||
|
||||
2 -> {
|
||||
a.requestedOrientation = if (isReversePortraitAllowed) {
|
||||
ActivityInfo.SCREEN_ORIENTATION_FULL_USER
|
||||
} else {
|
||||
ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onShownWithView(parameter: Any?, isBack: Boolean) {
|
||||
@@ -168,10 +227,6 @@ class VideoDetailFragment : MainFragment {
|
||||
return true;
|
||||
}
|
||||
|
||||
override fun onHide() {
|
||||
super.onHide();
|
||||
}
|
||||
|
||||
fun preventPictureInPicture() {
|
||||
Logger.i(TAG, "preventPictureInPicture() preventPictureInPicture = true");
|
||||
_viewDetail?.preventPictureInPicture = true;
|
||||
@@ -211,7 +266,9 @@ class VideoDetailFragment : MainFragment {
|
||||
_viewDetail = _view!!.findViewById<VideoDetailView>(R.id.fragview_videodetail).also {
|
||||
it.applyFragment(this);
|
||||
it.onFullscreenChanged.subscribe(::onFullscreenChanged);
|
||||
it.onVideoChanged.subscribe(::onVideoChanged)
|
||||
it.onMinimize.subscribe {
|
||||
isMinimizingFromFullScreen = true
|
||||
_view!!.transitionToStart();
|
||||
};
|
||||
it.onClose.subscribe {
|
||||
@@ -248,6 +305,7 @@ class VideoDetailFragment : MainFragment {
|
||||
|
||||
if (state != State.MINIMIZED && progress < 0.1) {
|
||||
state = State.MINIMIZED;
|
||||
isMinimizingFromFullScreen = false
|
||||
onMinimize.emit();
|
||||
}
|
||||
else if (state != State.MAXIMIZED && progress > 0.9) {
|
||||
@@ -286,13 +344,6 @@ class VideoDetailFragment : MainFragment {
|
||||
minimizeVideoDetail();
|
||||
}
|
||||
|
||||
_autoRotateChangeListener = AutoRotateChangeListener(requireContext(), Handler()) { _ ->
|
||||
if (updateAutoFullscreen()) {
|
||||
return@AutoRotateChangeListener
|
||||
}
|
||||
updateOrientation()
|
||||
}
|
||||
|
||||
_loadUrlOnCreate?.let { _viewDetail?.setVideo(it.url, it.timeSeconds, it.playWhenReady) };
|
||||
maximizeVideoDetail();
|
||||
|
||||
@@ -301,40 +352,11 @@ class VideoDetailFragment : MainFragment {
|
||||
}
|
||||
|
||||
StatePlayer.instance.onRotationLockChanged.subscribe(this) {
|
||||
if (updateAutoFullscreen()) {
|
||||
return@subscribe
|
||||
}
|
||||
updateOrientation()
|
||||
}
|
||||
|
||||
_orientationListener = SimpleOrientationListener(requireActivity(), lifecycleScope)
|
||||
_orientationListener.onOrientationChanged.subscribe {
|
||||
_currentOrientation = it
|
||||
Logger.i(TAG, "Current orientation changed (_currentOrientation = ${_currentOrientation})")
|
||||
|
||||
if (updateAutoFullscreen()) {
|
||||
return@subscribe
|
||||
}
|
||||
updateOrientation()
|
||||
}
|
||||
return _view!!;
|
||||
}
|
||||
|
||||
private fun updateAutoFullscreen(): Boolean {
|
||||
if (Settings.instance.playback.isAutoRotate()) {
|
||||
if (state == State.MAXIMIZED && !isFullscreen && (_currentOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE || _currentOrientation == ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE)) {
|
||||
_viewDetail?.setFullscreen(true)
|
||||
return true
|
||||
}
|
||||
|
||||
if (state == State.MAXIMIZED && isFullscreen && !Settings.instance.playback.fullscreenPortrait && (_currentOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || _currentOrientation == ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT)) {
|
||||
_viewDetail?.setFullscreen(false)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun onUserLeaveHint() {
|
||||
val viewDetail = _viewDetail;
|
||||
Logger.i(TAG, "onUserLeaveHint preventPictureInPicture=${viewDetail?.preventPictureInPicture} isCasting=${StateCasting.instance.isCasting} isBackgroundPictureInPicture=${Settings.instance.playback.isBackgroundPictureInPicture()} allowBackground=${viewDetail?.allowBackground}");
|
||||
@@ -423,15 +445,12 @@ class VideoDetailFragment : MainFragment {
|
||||
if(shouldStop) {
|
||||
_viewDetail?.onStop();
|
||||
StateCasting.instance.onStop();
|
||||
Logger.v(TAG, "called onStop() shouldStop: $shouldStop");
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyMainView() {
|
||||
super.onDestroyMainView();
|
||||
Logger.v(TAG, "onDestroyMainView");
|
||||
_autoRotateChangeListener?.unregister()
|
||||
_orientationListener.stopListening()
|
||||
|
||||
SettingsActivity.settingsActivityClosed.remove(this)
|
||||
StatePlayer.instance.onRotationLockChanged.remove(this)
|
||||
@@ -512,7 +531,7 @@ class VideoDetailFragment : MainFragment {
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = "VideoDetailFragment";
|
||||
private const val TAG = "VideoDetailFragment";
|
||||
|
||||
fun newInstance() = VideoDetailFragment().apply {}
|
||||
}
|
||||
|
||||
+372
-166
@@ -4,6 +4,7 @@ import android.app.PictureInPictureParams
|
||||
import android.app.RemoteAction
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.res.Configuration
|
||||
import android.content.res.Resources
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Rect
|
||||
@@ -40,6 +41,7 @@ import androidx.media3.ui.TimeBar
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.request.target.CustomTarget
|
||||
import com.bumptech.glide.request.transition.Transition
|
||||
import com.futo.platformplayer.BuildConfig
|
||||
import com.futo.platformplayer.R
|
||||
import com.futo.platformplayer.Settings
|
||||
import com.futo.platformplayer.UIDialogs
|
||||
@@ -72,6 +74,7 @@ import com.futo.platformplayer.api.media.models.subtitles.ISubtitleSource
|
||||
import com.futo.platformplayer.api.media.models.video.IPlatformVideo
|
||||
import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails
|
||||
import com.futo.platformplayer.api.media.models.video.SerializedPlatformVideo
|
||||
import com.futo.platformplayer.api.media.platforms.js.JSClient
|
||||
import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig
|
||||
import com.futo.platformplayer.api.media.platforms.js.models.JSVideoDetails
|
||||
import com.futo.platformplayer.api.media.structures.IPager
|
||||
@@ -79,6 +82,7 @@ import com.futo.platformplayer.casting.CastConnectionState
|
||||
import com.futo.platformplayer.casting.StateCasting
|
||||
import com.futo.platformplayer.constructs.Event0
|
||||
import com.futo.platformplayer.constructs.Event1
|
||||
import com.futo.platformplayer.constructs.Event2
|
||||
import com.futo.platformplayer.constructs.TaskHandler
|
||||
import com.futo.platformplayer.downloads.VideoLocal
|
||||
import com.futo.platformplayer.dp
|
||||
@@ -111,9 +115,12 @@ import com.futo.platformplayer.states.StatePlaylists
|
||||
import com.futo.platformplayer.states.StatePlugins
|
||||
import com.futo.platformplayer.states.StatePolycentric
|
||||
import com.futo.platformplayer.states.StateSubscriptions
|
||||
import com.futo.platformplayer.states.StateSync
|
||||
import com.futo.platformplayer.stores.FragmentedStorage
|
||||
import com.futo.platformplayer.stores.StringArrayStorage
|
||||
import com.futo.platformplayer.stores.db.types.DBHistory
|
||||
import com.futo.platformplayer.sync.internal.GJSyncOpcodes
|
||||
import com.futo.platformplayer.sync.models.SendToDevicePackage
|
||||
import com.futo.platformplayer.toHumanBitrate
|
||||
import com.futo.platformplayer.toHumanBytesSize
|
||||
import com.futo.platformplayer.toHumanNowDiffString
|
||||
@@ -154,20 +161,20 @@ import com.futo.polycentric.core.Opinion
|
||||
import com.futo.polycentric.core.toURLInfoSystemLinkUrl
|
||||
import com.google.protobuf.ByteString
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.Dispatcher
|
||||
import org.w3c.dom.Text
|
||||
import userpackage.Protocol
|
||||
import java.time.OffsetDateTime
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.max
|
||||
import kotlin.math.roundToLong
|
||||
|
||||
@androidx.media3.common.util.UnstableApi
|
||||
@UnstableApi
|
||||
class VideoDetailView : ConstraintLayout {
|
||||
private val TAG = "VideoDetailView"
|
||||
|
||||
@@ -180,7 +187,7 @@ class VideoDetailView : ConstraintLayout {
|
||||
private var _searchVideo: IPlatformVideo? = null;
|
||||
var video: IPlatformVideoDetails? = null
|
||||
private set;
|
||||
var videoLocal: VideoLocal? = null;
|
||||
private var videoLocal: VideoLocal? = null;
|
||||
private var _playbackTracker: IPlaybackTracker? = null;
|
||||
private var _historyIndex: DBHistory.Index? = null;
|
||||
|
||||
@@ -195,7 +202,7 @@ class VideoDetailView : ConstraintLayout {
|
||||
private val _timeBar: TimeBar;
|
||||
private var _upNext: UpNextView;
|
||||
|
||||
val rootView: ConstraintLayout;
|
||||
private val rootView: ConstraintLayout;
|
||||
|
||||
private val _title: TextView;
|
||||
private val _subTitle: TextView;
|
||||
@@ -284,12 +291,13 @@ class VideoDetailView : ConstraintLayout {
|
||||
|
||||
var isPlaying: Boolean = false
|
||||
private set;
|
||||
var lastPositionMilliseconds: Long = 0
|
||||
private var lastPositionMilliseconds: Long = 0
|
||||
private set;
|
||||
private var _historicalPosition: Long = 0;
|
||||
private var _commentsCount = 0;
|
||||
private var _polycentricProfile: PolycentricCache.CachedPolycentricProfile? = null;
|
||||
private var _slideUpOverlay: SlideUpMenuOverlay? = null;
|
||||
private var _autoplayVideo: IPlatformVideo? = null
|
||||
|
||||
//Events
|
||||
val onMinimize = Event0();
|
||||
@@ -298,6 +306,7 @@ class VideoDetailView : ConstraintLayout {
|
||||
val onFullscreenChanged = Event1<Boolean>();
|
||||
val onEnterPictureInPicture = Event0();
|
||||
val onPlayChanged = Event1<Boolean>();
|
||||
val onVideoChanged = Event2<Int, Int>()
|
||||
|
||||
var allowBackground : Boolean = false
|
||||
private set;
|
||||
@@ -523,12 +532,14 @@ class VideoDetailView : ConstraintLayout {
|
||||
_cast.onChapterChanged.subscribe(onChapterChanged);
|
||||
|
||||
_cast.onMinimizeClick.subscribe {
|
||||
_player.setFullScreen(false);
|
||||
onMinimize.emit();
|
||||
// emit minimize before toggling fullscreen so we know that the full screen toggle is happening during a minimize operation
|
||||
onMinimize.emit()
|
||||
_player.setFullScreen(false)
|
||||
};
|
||||
_player.onMinimize.subscribe {
|
||||
_player.setFullScreen(false);
|
||||
onMinimize.emit();
|
||||
// emit minimize before toggling fullscreen so we know that the full screen toggle is happening during a minimize operation
|
||||
onMinimize.emit()
|
||||
_player.setFullScreen(false)
|
||||
};
|
||||
|
||||
_player.onTimeBarChanged.subscribe { position, _ ->
|
||||
@@ -636,6 +647,27 @@ class VideoDetailView : ConstraintLayout {
|
||||
StatePlayer.instance.onVideoChanging.subscribe(this) {
|
||||
setVideoOverview(it);
|
||||
};
|
||||
|
||||
var hadDevice = false;
|
||||
StateSync.instance.deviceUpdatedOrAdded.subscribe(this) { id, session ->
|
||||
val hasDevice = StateSync.instance.hasAtLeastOneOnlineDevice();
|
||||
if(hasDevice != hadDevice) {
|
||||
hadDevice = hasDevice;
|
||||
fragment.lifecycleScope.launch(Dispatchers.Main) {
|
||||
updateMoreButtons();
|
||||
}
|
||||
}
|
||||
};
|
||||
StateSync.instance.deviceRemoved.subscribe(this) { id ->
|
||||
val hasDevice = StateSync.instance.hasAtLeastOneOnlineDevice();
|
||||
if(hasDevice != hadDevice) {
|
||||
hadDevice = hasDevice;
|
||||
fragment.lifecycleScope.launch(Dispatchers.Main) {
|
||||
updateMoreButtons();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MediaControlReceiver.onLowerVolumeReceived.subscribe(this) { handleLowerVolume() };
|
||||
MediaControlReceiver.onPlayReceived.subscribe(this) { handlePlay() };
|
||||
MediaControlReceiver.onPauseReceived.subscribe(this) { handlePause() };
|
||||
@@ -696,7 +728,8 @@ class VideoDetailView : ConstraintLayout {
|
||||
|
||||
if (c is PolycentricPlatformComment) {
|
||||
var parentComment: PolycentricPlatformComment = c;
|
||||
_container_content_replies.load(if (_tabIndex!! == 0) false else true, metadata, c.contextUrl, c.reference, c,
|
||||
_container_content_replies.load(
|
||||
_tabIndex!! != 0, metadata, c.contextUrl, c.reference, c,
|
||||
{ StatePolycentric.instance.getCommentPager(c.contextUrl, c.reference) },
|
||||
{
|
||||
val newComment = parentComment.cloneWithUpdatedReplyCount((parentComment.replyCount ?: 0) + 1);
|
||||
@@ -704,12 +737,13 @@ class VideoDetailView : ConstraintLayout {
|
||||
parentComment = newComment;
|
||||
});
|
||||
} else {
|
||||
_container_content_replies.load(if (_tabIndex!! == 0) false else true, metadata, null, null, c, { StatePlatform.instance.getSubComments(c) });
|
||||
_container_content_replies.load(_tabIndex!! != 0, metadata, null, null, c, { StatePlatform.instance.getSubComments(c) });
|
||||
}
|
||||
switchContentView(_container_content_replies);
|
||||
};
|
||||
|
||||
onClose.subscribe {
|
||||
checkAndRemoveWatchLater();
|
||||
_lastVideoSource = null;
|
||||
_lastAudioSource = null;
|
||||
_lastSubtitleSource = null;
|
||||
@@ -720,6 +754,17 @@ class VideoDetailView : ConstraintLayout {
|
||||
fragment.activity?.window?.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
};
|
||||
|
||||
StatePlayer.instance.autoplayChanged.subscribe(this) {
|
||||
if (it) {
|
||||
val url = _url
|
||||
val autoPlayVideo = _autoplayVideo
|
||||
if (url != null && autoPlayVideo == null) {
|
||||
_taskLoadRecommendations.cancel()
|
||||
_taskLoadRecommendations.run(url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_layoutResume.setOnClickListener {
|
||||
handleSeek(_historicalPosition * 1000);
|
||||
|
||||
@@ -807,6 +852,11 @@ class VideoDetailView : ConstraintLayout {
|
||||
}
|
||||
|
||||
fun updateMoreButtons() {
|
||||
val isLimitedVersion = video?.url != null && StatePlatform.instance.getContentClientOrNull(video!!.url)?.let {
|
||||
if (it is JSClient)
|
||||
return@let it.config.reduceFunctionsInLimitedVersion && BuildConfig.IS_PLAYSTORE_BUILD
|
||||
else false;
|
||||
} ?: false;
|
||||
val buttons = listOf(RoundButton(context, R.drawable.ic_add, context.getString(R.string.add), TAG_ADD) {
|
||||
(video ?: _searchVideo)?.let {
|
||||
_slideUpOverlay = UISlideOverlays.showAddToOverlay(it, _overlayContainer) {
|
||||
@@ -826,38 +876,44 @@ class VideoDetailView : ConstraintLayout {
|
||||
}
|
||||
_slideUpOverlay?.hide();
|
||||
} else null,
|
||||
RoundButton(context, R.drawable.ic_screen_share, context.getString(R.string.background), TAG_BACKGROUND) {
|
||||
if(!allowBackground) {
|
||||
_player.switchToAudioMode();
|
||||
allowBackground = true;
|
||||
it.text.text = resources.getString(R.string.background_revert);
|
||||
if(!isLimitedVersion)
|
||||
RoundButton(context, R.drawable.ic_screen_share, context.getString(R.string.background), TAG_BACKGROUND) {
|
||||
if(!allowBackground) {
|
||||
_player.switchToAudioMode();
|
||||
allowBackground = true;
|
||||
it.text.text = resources.getString(R.string.background_revert);
|
||||
}
|
||||
else {
|
||||
_player.switchToVideoMode();
|
||||
allowBackground = false;
|
||||
it.text.text = resources.getString(R.string.background);
|
||||
}
|
||||
_slideUpOverlay?.hide();
|
||||
}
|
||||
else {
|
||||
_player.switchToVideoMode();
|
||||
allowBackground = false;
|
||||
it.text.text = resources.getString(R.string.background);
|
||||
else null,
|
||||
if(!isLimitedVersion)
|
||||
RoundButton(context, R.drawable.ic_download, context.getString(R.string.download), TAG_DOWNLOAD) {
|
||||
video?.let {
|
||||
_slideUpOverlay = UISlideOverlays.showDownloadVideoOverlay(it, _overlayContainer, context.contentResolver);
|
||||
};
|
||||
}
|
||||
_slideUpOverlay?.hide();
|
||||
},
|
||||
RoundButton(context, R.drawable.ic_download, context.getString(R.string.download), TAG_DOWNLOAD) {
|
||||
video?.let {
|
||||
_slideUpOverlay = UISlideOverlays.showDownloadVideoOverlay(it, _overlayContainer, context.contentResolver);
|
||||
};
|
||||
},
|
||||
RoundButton(context, R.drawable.ic_share, context.getString(R.string.share), TAG_SHARE) {
|
||||
video?.let {
|
||||
Logger.i(TAG, "Share preventPictureInPicture = true");
|
||||
preventPictureInPicture = true;
|
||||
shareVideo();
|
||||
};
|
||||
_slideUpOverlay?.hide();
|
||||
},
|
||||
RoundButton(context, R.drawable.ic_screen_share, context.getString(R.string.overlay), TAG_OVERLAY) {
|
||||
this.startPictureInPicture();
|
||||
fragment.forcePictureInPicture();
|
||||
//PiPActivity.startPiP(context);
|
||||
_slideUpOverlay?.hide();
|
||||
},
|
||||
else null,
|
||||
RoundButton(context, R.drawable.ic_share, context.getString(R.string.share), TAG_SHARE) {
|
||||
video?.let {
|
||||
Logger.i(TAG, "Share preventPictureInPicture = true");
|
||||
preventPictureInPicture = true;
|
||||
shareVideo();
|
||||
};
|
||||
_slideUpOverlay?.hide();
|
||||
},
|
||||
if(!isLimitedVersion)
|
||||
RoundButton(context, R.drawable.ic_screen_share, context.getString(R.string.overlay), TAG_OVERLAY) {
|
||||
this.startPictureInPicture();
|
||||
fragment.forcePictureInPicture();
|
||||
//PiPActivity.startPiP(context);
|
||||
_slideUpOverlay?.hide();
|
||||
}
|
||||
else null,
|
||||
RoundButton(context, R.drawable.ic_export, context.getString(R.string.page), TAG_OPEN) {
|
||||
video?.let {
|
||||
val url = video?.shareUrl ?: _searchVideo?.shareUrl ?: _url;
|
||||
@@ -866,6 +922,22 @@ class VideoDetailView : ConstraintLayout {
|
||||
};
|
||||
_slideUpOverlay?.hide();
|
||||
},
|
||||
if(StateSync.instance.hasAtLeastOneOnlineDevice()) {
|
||||
RoundButton(context, R.drawable.ic_device, context.getString(R.string.send_to_device), TAG_SEND_TO_DEVICE) {
|
||||
val devices = StateSync.instance.getSessions();
|
||||
val videoToSend = video ?: return@RoundButton;
|
||||
if(devices.size > 1) {
|
||||
//not implemented
|
||||
}
|
||||
else if(devices.size == 1){
|
||||
val device = devices.first();
|
||||
UIDialogs.showConfirmationDialog(context, "Would you like to open\n[${videoToSend.name}]\non ${device.remotePublicKey}" , {
|
||||
fragment.lifecycleScope.launch(Dispatchers.IO) {
|
||||
device.sendJsonData(GJSyncOpcodes.sendToDevices, SendToDevicePackage(videoToSend.url, (lastPositionMilliseconds/1000).toInt()));
|
||||
}
|
||||
})
|
||||
}
|
||||
}} else null,
|
||||
RoundButton(context, R.drawable.ic_refresh, context.getString(R.string.reload), "Reload") {
|
||||
reloadVideo();
|
||||
_slideUpOverlay?.hide();
|
||||
@@ -875,7 +947,7 @@ class VideoDetailView : ConstraintLayout {
|
||||
else {
|
||||
val selectedButtons = _buttonPinStore.getAllValues()
|
||||
.map { x-> buttons.find { it.tagRef == x } }
|
||||
.filter { it != null }
|
||||
.filterNotNull()
|
||||
.map { it!! };
|
||||
_buttonPins.setButtons(*(selectedButtons +
|
||||
buttons.filter { !selectedButtons.contains(it) } +
|
||||
@@ -1006,12 +1078,15 @@ class VideoDetailView : ConstraintLayout {
|
||||
_container_content_queue.cleanup();
|
||||
_container_content_description.cleanup();
|
||||
_container_content_support.cleanup();
|
||||
StatePlayer.instance.autoplayChanged.remove(this)
|
||||
StateCasting.instance.onActiveDevicePlayChanged.remove(this);
|
||||
StateCasting.instance.onActiveDeviceTimeChanged.remove(this);
|
||||
StateCasting.instance.onActiveDeviceConnectionStateChanged.remove(this);
|
||||
StateApp.instance.preventPictureInPicture.remove(this);
|
||||
StatePlayer.instance.onQueueChanged.remove(this);
|
||||
StatePlayer.instance.onVideoChanging.remove(this);
|
||||
StateSync.instance.deviceUpdatedOrAdded.remove(this);
|
||||
StateSync.instance.deviceRemoved.remove(this);
|
||||
MediaControlReceiver.onLowerVolumeReceived.remove(this);
|
||||
MediaControlReceiver.onPlayReceived.remove(this);
|
||||
MediaControlReceiver.onPauseReceived.remove(this);
|
||||
@@ -1102,6 +1177,8 @@ class VideoDetailView : ConstraintLayout {
|
||||
this.video = null;
|
||||
cleanupPlaybackTracker();
|
||||
_searchVideo = video;
|
||||
_autoplayVideo = null
|
||||
Logger.i(TAG, "Autoplay video cleared (setVideoOverview)")
|
||||
_videoResumePositionMilliseconds = resumeSeconds * 1000;
|
||||
setLastPositionMilliseconds(_videoResumePositionMilliseconds, false);
|
||||
_addCommentView.setContext(null, null);
|
||||
@@ -1186,13 +1263,16 @@ class VideoDetailView : ConstraintLayout {
|
||||
|
||||
switchContentView(_container_content_main);
|
||||
}
|
||||
//@OptIn(ExperimentalCoroutinesApi::class)
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
fun setVideoDetails(videoDetail: IPlatformVideoDetails, newVideo: Boolean = false) {
|
||||
Logger.i(TAG, "setVideoDetails (${videoDetail.name})")
|
||||
_didTriggerDatasourceErrroCount = 0;
|
||||
_didTriggerDatasourceError = false;
|
||||
_autoplayVideo = null
|
||||
Logger.i(TAG, "Autoplay video cleared (setVideoDetails)")
|
||||
|
||||
if(newVideo && this.video?.url == videoDetail.url)
|
||||
if (newVideo && this.video?.url == videoDetail.url)
|
||||
return;
|
||||
|
||||
if (newVideo) {
|
||||
@@ -1201,8 +1281,13 @@ class VideoDetailView : ConstraintLayout {
|
||||
_lastSubtitleSource = null;
|
||||
}
|
||||
|
||||
if(videoDetail.datetime != null && videoDetail.datetime!! > OffsetDateTime.now())
|
||||
UIDialogs.toast(context, context.getString(R.string.planned_in) + " ${videoDetail.datetime?.toHumanNowDiffString(true)}")
|
||||
if (videoDetail.datetime != null && videoDetail.datetime!! > OffsetDateTime.now())
|
||||
UIDialogs.toast(
|
||||
context,
|
||||
context.getString(R.string.planned_in) + " ${
|
||||
videoDetail.datetime?.toHumanNowDiffString(true)
|
||||
}"
|
||||
)
|
||||
|
||||
if (!videoDetail.isLive) {
|
||||
_player.setPlaybackRate(Settings.instance.playback.getDefaultPlaybackSpeed());
|
||||
@@ -1211,26 +1296,25 @@ class VideoDetailView : ConstraintLayout {
|
||||
val videoLocal: VideoLocal?;
|
||||
val video: IPlatformVideoDetails?;
|
||||
|
||||
if(videoDetail is VideoLocal) {
|
||||
if (videoDetail is VideoLocal) {
|
||||
videoLocal = videoDetail;
|
||||
video = videoDetail;
|
||||
this.video = video;
|
||||
val videoTask = StatePlatform.instance.getContentDetails(videoDetail.url);
|
||||
videoTask.invokeOnCompletion { ex ->
|
||||
if(ex != null) {
|
||||
if (ex != null) {
|
||||
Logger.e(TAG, "Failed to fetch live video for offline video", ex);
|
||||
return@invokeOnCompletion;
|
||||
}
|
||||
val result = videoTask.getCompleted();
|
||||
if(this.video == videoDetail && result is IPlatformVideoDetails) {
|
||||
if (this.video == videoDetail && result is IPlatformVideoDetails) {
|
||||
this.video = result;
|
||||
fragment.lifecycleScope.launch(Dispatchers.Main) {
|
||||
updateQualitySourcesOverlay(result, videoLocal);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
else { //TODO: Update cached video if it exists with video
|
||||
} else { //TODO: Update cached video if it exists with video
|
||||
videoLocal = StateDownloads.instance.getCachedVideo(videoDetail.id);
|
||||
video = videoDetail;
|
||||
}
|
||||
@@ -1238,7 +1322,16 @@ class VideoDetailView : ConstraintLayout {
|
||||
this.video = video;
|
||||
cleanupPlaybackTracker();
|
||||
|
||||
if(video is JSVideoDetails) {
|
||||
if (video.video.videoSources.isNotEmpty()) {
|
||||
onVideoChanged.emit(
|
||||
video.video.videoSources[0].width,
|
||||
video.video.videoSources[0].height
|
||||
)
|
||||
} else {
|
||||
onVideoChanged.emit(0, 0)
|
||||
}
|
||||
|
||||
if (video is JSVideoDetails) {
|
||||
val me = this;
|
||||
fragment.lifecycleScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
@@ -1246,8 +1339,7 @@ class VideoDetailView : ConstraintLayout {
|
||||
val chapters = null ?: StatePlatform.instance.getContentChapters(video.url);
|
||||
_player.setChapters(chapters);
|
||||
_cast.setChapters(chapters);
|
||||
}
|
||||
catch(ex: Throwable) {
|
||||
} catch (ex: Throwable) {
|
||||
Logger.e(TAG, "Failed to get chapters", ex);
|
||||
_player.setChapters(null);
|
||||
_cast.setChapters(null);
|
||||
@@ -1257,7 +1349,7 @@ class VideoDetailView : ConstraintLayout {
|
||||
}*/
|
||||
}
|
||||
try {
|
||||
if(!StateApp.instance.privateMode) {
|
||||
if (!StateApp.instance.privateMode) {
|
||||
val stopwatch = com.futo.platformplayer.debug.Stopwatch()
|
||||
var tracker = video.getPlaybackTracker()
|
||||
Logger.i(TAG, "video.getPlaybackTracker took ${stopwatch.elapsedMs}ms")
|
||||
@@ -1273,17 +1365,20 @@ class VideoDetailView : ConstraintLayout {
|
||||
|
||||
if (me.video == video)
|
||||
me._playbackTracker = tracker;
|
||||
}
|
||||
else if(me.video == video)
|
||||
} else if (me.video == video)
|
||||
me._playbackTracker = null;
|
||||
}
|
||||
catch(ex: Throwable) {
|
||||
} catch (ex: Throwable) {
|
||||
Logger.e(TAG, "Playback tracker failed", ex);
|
||||
if(me.video?.isLive == true) withContext(Dispatchers.Main) {
|
||||
|
||||
if(me.video?.isLive == true || ex.message?.contains("Unable to resolve host") == true) withContext(Dispatchers.Main) {
|
||||
UIDialogs.toast(context, context.getString(R.string.failed_to_get_playback_tracker));
|
||||
};
|
||||
else withContext(Dispatchers.Main) {
|
||||
UIDialogs.showGeneralErrorDialog(context, context.getString(R.string.failed_to_get_playback_tracker), ex);
|
||||
UIDialogs.showGeneralErrorDialog(
|
||||
context,
|
||||
context.getString(R.string.failed_to_get_playback_tracker),
|
||||
ex
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1297,13 +1392,16 @@ class VideoDetailView : ConstraintLayout {
|
||||
if (video is TutorialFragment.TutorialVideo) {
|
||||
setTabIndex(0, true)
|
||||
} else {
|
||||
if (Settings.instance.comments.recommendationsDefault) {
|
||||
setTabIndex(2)
|
||||
if (Settings.instance.comments.recommendationsDefault && !Settings.instance.comments.hideRecommendations) {
|
||||
setTabIndex(2, true)
|
||||
} else {
|
||||
when(Settings.instance.comments.defaultCommentSection) {
|
||||
0 -> if(Settings.instance.other.polycentricEnabled) setTabIndex(0) else setTabIndex(1);
|
||||
1 -> setTabIndex(1);
|
||||
2 -> setTabIndex(StateMeta.instance.getLastCommentSection())
|
||||
when (Settings.instance.comments.defaultCommentSection) {
|
||||
0 -> if (Settings.instance.other.polycentricEnabled) setTabIndex(
|
||||
0,
|
||||
true
|
||||
) else setTabIndex(1, true);
|
||||
1 -> setTabIndex(1, true);
|
||||
2 -> setTabIndex(StateMeta.instance.getLastCommentSection(), true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1311,9 +1409,16 @@ class VideoDetailView : ConstraintLayout {
|
||||
//UI
|
||||
_title.text = video.name;
|
||||
_channelName.text = video.author.name;
|
||||
if(video.author.subscribers != null) {
|
||||
_channelMeta.text = if((video.author.subscribers ?: 0) > 0) video.author.subscribers!!.toHumanNumber() + " " + context.getString(R.string.subscribers) else "";
|
||||
(_channelName.layoutParams as MarginLayoutParams).setMargins(0, (DP_5 * -1).toInt(), 0, 0);
|
||||
if (video.author.subscribers != null) {
|
||||
_channelMeta.text = if ((video.author.subscribers
|
||||
?: 0) > 0
|
||||
) video.author.subscribers!!.toHumanNumber() + " " + context.getString(R.string.subscribers) else "";
|
||||
(_channelName.layoutParams as MarginLayoutParams).setMargins(
|
||||
0,
|
||||
(DP_5 * -1).toInt(),
|
||||
0,
|
||||
0
|
||||
);
|
||||
} else {
|
||||
_channelMeta.text = "";
|
||||
(_channelName.layoutParams as MarginLayoutParams).setMargins(0, (DP_2).toInt(), 0, 0);
|
||||
@@ -1321,7 +1426,7 @@ class VideoDetailView : ConstraintLayout {
|
||||
|
||||
|
||||
video.author.let {
|
||||
if(it is PlatformAuthorMembershipLink && !it.membershipUrl.isNullOrEmpty())
|
||||
if (it is PlatformAuthorMembershipLink && !it.membershipUrl.isNullOrEmpty())
|
||||
_monetization.setPlatformMembership(video.id.pluginId, it.membershipUrl);
|
||||
else
|
||||
_monetization.setPlatformMembership(null, null);
|
||||
@@ -1334,7 +1439,9 @@ class VideoDetailView : ConstraintLayout {
|
||||
setDescription(video.description.fixHtmlLinks());
|
||||
_creatorThumbnail.setThumbnail(video.author.thumbnail, false);
|
||||
|
||||
val cachedPolycentricProfile = PolycentricCache.instance.getCachedProfile(video.author.url, true);
|
||||
|
||||
val cachedPolycentricProfile =
|
||||
PolycentricCache.instance.getCachedProfile(video.author.url, true);
|
||||
if (cachedPolycentricProfile != null) {
|
||||
setPolycentricProfile(cachedPolycentricProfile, animate = false);
|
||||
} else {
|
||||
@@ -1343,13 +1450,19 @@ class VideoDetailView : ConstraintLayout {
|
||||
}
|
||||
|
||||
_platform.setPlatformFromClientID(video.id.pluginId);
|
||||
val subTitleSegments : ArrayList<String> = ArrayList();
|
||||
if(video.viewCount > 0)
|
||||
subTitleSegments.add("${video.viewCount.toHumanNumber()} ${if(video.isLive) context.getString(R.string.watching_now) else context.getString(R.string.views)}");
|
||||
if(video.datetime != null) {
|
||||
val subTitleSegments: ArrayList<String> = ArrayList();
|
||||
if (video.viewCount > 0)
|
||||
subTitleSegments.add(
|
||||
"${video.viewCount.toHumanNumber()} ${
|
||||
if (video.isLive) context.getString(
|
||||
R.string.watching_now
|
||||
) else context.getString(R.string.views)
|
||||
}"
|
||||
);
|
||||
if (video.datetime != null) {
|
||||
val diff = video.datetime?.getNowDiffSeconds() ?: 0;
|
||||
val ago = video.datetime?.toHumanNowDiffString(true)
|
||||
if(diff >= 0)
|
||||
if (diff >= 0)
|
||||
subTitleSegments.add("${ago} ago");
|
||||
else
|
||||
subTitleSegments.add("available in ${ago}");
|
||||
@@ -1362,20 +1475,27 @@ class VideoDetailView : ConstraintLayout {
|
||||
|
||||
fragment.lifecycleScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
val queryReferencesResponse = ApiMethods.getQueryReferences(PolycentricCache.SERVER, ref, null,null,
|
||||
val queryReferencesResponse = ApiMethods.getQueryReferences(
|
||||
PolycentricCache.SERVER, ref, null, null,
|
||||
arrayListOf(
|
||||
Protocol.QueryReferencesRequestCountLWWElementReferences.newBuilder().setFromType(ContentType.OPINION.value).setValue(
|
||||
ByteString.copyFrom(Opinion.like.data)).build(),
|
||||
Protocol.QueryReferencesRequestCountLWWElementReferences.newBuilder().setFromType(ContentType.OPINION.value).setValue(
|
||||
ByteString.copyFrom(Opinion.dislike.data)).build()
|
||||
Protocol.QueryReferencesRequestCountLWWElementReferences.newBuilder()
|
||||
.setFromType(ContentType.OPINION.value).setValue(
|
||||
ByteString.copyFrom(Opinion.like.data)
|
||||
).build(),
|
||||
Protocol.QueryReferencesRequestCountLWWElementReferences.newBuilder()
|
||||
.setFromType(ContentType.OPINION.value).setValue(
|
||||
ByteString.copyFrom(Opinion.dislike.data)
|
||||
).build()
|
||||
),
|
||||
extraByteReferences = listOfNotNull(extraBytesRef)
|
||||
);
|
||||
|
||||
val likes = queryReferencesResponse.countsList[0];
|
||||
val dislikes = queryReferencesResponse.countsList[1];
|
||||
val hasLiked = StatePolycentric.instance.hasLiked(ref.toByteArray())/* || extraBytesRef?.let { StatePolycentric.instance.hasLiked(it) } ?: false*/;
|
||||
val hasDisliked = StatePolycentric.instance.hasDisliked(ref.toByteArray())/* || extraBytesRef?.let { StatePolycentric.instance.hasDisliked(it) } ?: false*/;
|
||||
val hasLiked =
|
||||
StatePolycentric.instance.hasLiked(ref.toByteArray())/* || extraBytesRef?.let { StatePolycentric.instance.hasLiked(it) } ?: false*/;
|
||||
val hasDisliked =
|
||||
StatePolycentric.instance.hasDisliked(ref.toByteArray())/* || extraBytesRef?.let { StatePolycentric.instance.hasDisliked(it) } ?: false*/;
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
_rating.visibility = View.VISIBLE;
|
||||
@@ -1399,7 +1519,11 @@ class VideoDetailView : ConstraintLayout {
|
||||
}
|
||||
}
|
||||
|
||||
StatePolycentric.instance.updateLikeMap(ref, args.hasLiked, args.hasDisliked)
|
||||
StatePolycentric.instance.updateLikeMap(
|
||||
ref,
|
||||
args.hasLiked,
|
||||
args.hasDisliked
|
||||
)
|
||||
};
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
@@ -1421,6 +1545,7 @@ class VideoDetailView : ConstraintLayout {
|
||||
_textDislikes.visibility = View.VISIBLE;
|
||||
_textDislikes.text = r.dislikes.toHumanNumber();
|
||||
}
|
||||
|
||||
is RatingLikes -> {
|
||||
val r = video.rating as RatingLikes;
|
||||
_layoutRating.visibility = View.VISIBLE;
|
||||
@@ -1432,6 +1557,7 @@ class VideoDetailView : ConstraintLayout {
|
||||
_imageDislikeIcon.visibility = View.GONE;
|
||||
_textDislikes.visibility = View.GONE;
|
||||
}
|
||||
|
||||
else -> {
|
||||
_layoutRating.visibility = View.GONE;
|
||||
}
|
||||
@@ -1443,6 +1569,7 @@ class VideoDetailView : ConstraintLayout {
|
||||
|
||||
setLoading(false);
|
||||
|
||||
|
||||
//Set Mediasource
|
||||
|
||||
val toResume = _videoResumePositionMilliseconds;
|
||||
@@ -1459,9 +1586,22 @@ class VideoDetailView : ConstraintLayout {
|
||||
val historyItem = getHistoryIndex(videoDetail) ?: return@launch;
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
_historicalPosition = StateHistory.instance.updateHistoryPosition(video, historyItem,false, (toResume.toFloat() / 1000.0f).toLong());
|
||||
Logger.i(TAG, "Historical position: $_historicalPosition, last position: $lastPositionMilliseconds");
|
||||
if (_historicalPosition > 60 && video.duration - _historicalPosition > 5 && Math.abs(_historicalPosition - lastPositionMilliseconds / 1000) > 5.0) {
|
||||
_historicalPosition = StateHistory.instance.updateHistoryPosition(
|
||||
video,
|
||||
historyItem,
|
||||
false,
|
||||
(toResume.toFloat() / 1000.0f).toLong(),
|
||||
null,
|
||||
true
|
||||
);
|
||||
Logger.i(
|
||||
TAG,
|
||||
"Historical position: $_historicalPosition, last position: $lastPositionMilliseconds"
|
||||
);
|
||||
if (_historicalPosition > 60 && video.duration - _historicalPosition > 5 && Math.abs(
|
||||
_historicalPosition - lastPositionMilliseconds / 1000
|
||||
) > 5.0
|
||||
) {
|
||||
_layoutResume.visibility = View.VISIBLE;
|
||||
_textResume.text = "Resume at ${_historicalPosition.toHumanTime(false)}";
|
||||
|
||||
@@ -1487,12 +1627,13 @@ class VideoDetailView : ConstraintLayout {
|
||||
|
||||
_liveChat?.stop();
|
||||
_liveChat = null;
|
||||
if(video.isLive && video.live != null) {
|
||||
if (video.isLive && video.live != null) {
|
||||
loadLiveChat(video);
|
||||
}
|
||||
if(video.isLive && video.live == null && !video.video.videoSources.any())
|
||||
if (video.isLive && video.live == null && !video.video.videoSources.any())
|
||||
startLiveTry(video);
|
||||
|
||||
|
||||
_player.updateNextPrevious();
|
||||
updateMoreButtons();
|
||||
|
||||
@@ -1509,6 +1650,11 @@ class VideoDetailView : ConstraintLayout {
|
||||
_layoutRating.visibility = View.VISIBLE
|
||||
_layoutChangeBottomSection.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
if (StatePlayer.instance.autoplay) {
|
||||
_taskLoadRecommendations.cancel()
|
||||
_taskLoadRecommendations.run(videoDetail.url)
|
||||
}
|
||||
}
|
||||
fun loadLiveChat(video: IPlatformVideoDetails) {
|
||||
_liveChat?.stop();
|
||||
@@ -1618,8 +1764,7 @@ class VideoDetailView : ConstraintLayout {
|
||||
});
|
||||
else
|
||||
_player.setArtwork(null);
|
||||
|
||||
_player.setSource(videoSource, audioSource, _playWhenReady, false);
|
||||
_player.setSource(videoSource, audioSource, _playWhenReady, false, resume = resumePositionMs > 0);
|
||||
if(subtitleSource != null)
|
||||
_player.swapSubtitles(fragment.lifecycleScope, subtitleSource);
|
||||
_player.seekTo(resumePositionMs);
|
||||
@@ -1769,6 +1914,8 @@ class VideoDetailView : ConstraintLayout {
|
||||
|
||||
fun prevVideo(withoutRemoval: Boolean = false) {
|
||||
Logger.i(TAG, "prevVideo")
|
||||
checkAndRemoveWatchLater();
|
||||
|
||||
val next = StatePlayer.instance.prevQueueItem(withoutRemoval || _player.duration < 100 || (_player.position.toFloat() / _player.duration) < 0.9);
|
||||
if(next != null) {
|
||||
setVideoOverview(next, true, 0, true);
|
||||
@@ -1777,7 +1924,18 @@ class VideoDetailView : ConstraintLayout {
|
||||
|
||||
fun nextVideo(forceLoop: Boolean = false, withoutRemoval: Boolean = false, bypassVideoLoop: Boolean = false): Boolean {
|
||||
Logger.i(TAG, "nextVideo")
|
||||
checkAndRemoveWatchLater();
|
||||
|
||||
var next = StatePlayer.instance.nextQueueItem(withoutRemoval || _player.duration < 100 || (_player.position.toFloat() / _player.duration) < 0.9, bypassVideoLoop);
|
||||
val autoplayVideo = _autoplayVideo
|
||||
if (next == null && autoplayVideo != null && StatePlayer.instance.autoplay) {
|
||||
Logger.i(TAG, "Found autoplay video!")
|
||||
StatePlayer.instance.setAutoplayed(autoplayVideo.url)
|
||||
next = autoplayVideo
|
||||
}
|
||||
_autoplayVideo = null
|
||||
Logger.i(TAG, "Autoplay video cleared (nextVideo)");
|
||||
|
||||
if(next == null && forceLoop)
|
||||
next = StatePlayer.instance.restartQueue();
|
||||
if(next != null) {
|
||||
@@ -1789,6 +1947,20 @@ class VideoDetailView : ConstraintLayout {
|
||||
return false;
|
||||
}
|
||||
|
||||
fun checkAndRemoveWatchLater(){
|
||||
val watchCurrent = video ?: videoLocal ?: _searchVideo;
|
||||
if(Settings.instance.playback.deleteFromWatchLaterAuto) {
|
||||
if(watchCurrent?.duration != null &&
|
||||
watchCurrent.duration > 0 &&
|
||||
(lastPositionMilliseconds / 1000) > watchCurrent.duration * 0.7) {
|
||||
if(!watchCurrent.url.isNullOrEmpty()) {
|
||||
StatePlaylists.instance.removeFromWatchLater(watchCurrent.url);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//Quality Selector data
|
||||
private fun updateQualityFormatsOverlay(liveStreamVideoFormats : List<Format>?, liveStreamAudioFormats : List<Format>?) {
|
||||
val v = video ?: return;
|
||||
@@ -1839,7 +2011,7 @@ class VideoDetailView : ConstraintLayout {
|
||||
?.map { x -> VideoHelper.selectBestVideoSource(videoSources.filter { x == it.height * it.width }, -1, FutoVideoPlayerBase.PREFERED_VIDEO_CONTAINERS) }
|
||||
?.plus(videoSources.filter { it is IHLSManifestSource || it is IDashManifestSource }))
|
||||
?.distinct()
|
||||
?.filter { it != null }
|
||||
?.filterNotNull()
|
||||
?.toList() ?: listOf() else videoSources?.toList() ?: listOf()
|
||||
val bestAudioContainer = audioSources?.let { VideoHelper.selectBestAudioSource(it, FutoVideoPlayerBase.PREFERED_AUDIO_CONTAINERS)?.container };
|
||||
val bestAudioSources = if(doDedup) audioSources
|
||||
@@ -2140,7 +2312,7 @@ class VideoDetailView : ConstraintLayout {
|
||||
cleanupPlaybackTracker();
|
||||
|
||||
val url = _url;
|
||||
if (url != null && url.isNotBlank()) {
|
||||
if (!url.isNullOrBlank()) {
|
||||
setLoading(true);
|
||||
_taskLoadVideo.run(url);
|
||||
}
|
||||
@@ -2152,7 +2324,7 @@ class VideoDetailView : ConstraintLayout {
|
||||
if(fullscreen) {
|
||||
_layoutPlayerContainer.setPadding(0, 0, 0, 0);
|
||||
|
||||
val lp = _container_content.layoutParams as ConstraintLayout.LayoutParams;
|
||||
val lp = _container_content.layoutParams as LayoutParams;
|
||||
lp.topMargin = 0;
|
||||
_container_content.layoutParams = lp;
|
||||
|
||||
@@ -2165,7 +2337,7 @@ class VideoDetailView : ConstraintLayout {
|
||||
else {
|
||||
_layoutPlayerContainer.setPadding(0, 0, 0, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 6.0f, Resources.getSystem().displayMetrics).toInt());
|
||||
|
||||
val lp = _container_content.layoutParams as ConstraintLayout.LayoutParams;
|
||||
val lp = _container_content.layoutParams as LayoutParams;
|
||||
lp.topMargin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, -18.0f, Resources.getSystem().displayMetrics).toInt();
|
||||
_container_content.layoutParams = lp;
|
||||
|
||||
@@ -2206,9 +2378,20 @@ class VideoDetailView : ConstraintLayout {
|
||||
}
|
||||
}
|
||||
|
||||
fun isLandscapeVideo(): Boolean? {
|
||||
var videoSourceWidth = _player.exoPlayer?.player?.videoSize?.width
|
||||
var videoSourceHeight = _player.exoPlayer?.player?.videoSize?.height
|
||||
|
||||
return if (videoSourceWidth == null || videoSourceHeight == null || videoSourceWidth == 0 || videoSourceHeight == 0){
|
||||
null
|
||||
} else{
|
||||
videoSourceWidth >= videoSourceHeight
|
||||
}
|
||||
}
|
||||
|
||||
fun setFullscreen(fullscreen : Boolean) {
|
||||
Logger.i(TAG, "setFullscreen(fullscreen=$fullscreen)")
|
||||
_player.setFullScreen(fullscreen);
|
||||
_player.setFullScreen(fullscreen)
|
||||
}
|
||||
private fun setLoading(isLoading : Boolean) {
|
||||
if(isLoading){
|
||||
@@ -2302,6 +2485,9 @@ class VideoDetailView : ConstraintLayout {
|
||||
return
|
||||
}
|
||||
|
||||
val recommendationsHidden = Settings.instance.comments.hideRecommendations
|
||||
_buttonRecommended.visibility = if (recommendationsHidden) View.GONE else View.VISIBLE
|
||||
|
||||
_taskLoadRecommendations.cancel()
|
||||
_tabIndex = index
|
||||
_buttonRecommended.setTextColor(resources.getColor(if (index == 2) R.color.white else R.color.gray_ac))
|
||||
@@ -2318,7 +2504,7 @@ class VideoDetailView : ConstraintLayout {
|
||||
_layoutRecommended.visibility = View.GONE
|
||||
fetchPolycentricComments()
|
||||
} else if (index == 1) {
|
||||
_addCommentView.visibility = View.VISIBLE
|
||||
_addCommentView.visibility = View.GONE
|
||||
_layoutRecommended.visibility = View.GONE
|
||||
fetchComments()
|
||||
} else if (index == 2) {
|
||||
@@ -2326,60 +2512,57 @@ class VideoDetailView : ConstraintLayout {
|
||||
_layoutRecommended.visibility = View.VISIBLE
|
||||
_commentsList.clear()
|
||||
|
||||
val url = _url
|
||||
if (url != null) {
|
||||
_layoutRecommended.addView(LoaderView(context).apply {
|
||||
layoutParams = LinearLayout.LayoutParams(60.dp(resources), 60.dp(resources))
|
||||
start()
|
||||
})
|
||||
_taskLoadRecommendations.run(url)
|
||||
} else {
|
||||
_layoutRecommended.addView(TextView(context).apply {
|
||||
layoutParams = LinearLayout.LayoutParams(60.dp(resources), 60.dp(resources))
|
||||
textSize = 12.0f
|
||||
text = "No recommendations found"
|
||||
})
|
||||
}
|
||||
_layoutRecommended.addView(LoaderView(context).apply {
|
||||
layoutParams = LinearLayout.LayoutParams(60.dp(resources), 60.dp(resources))
|
||||
start()
|
||||
})
|
||||
_taskLoadRecommendations.run(null)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setRecommendations(pager: IPager<IPlatformContent>?, message: String? = null) {
|
||||
_layoutRecommended.removeAllViews()
|
||||
if (pager == null) {
|
||||
_layoutRecommended.addView(TextView(context).apply {
|
||||
layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT).apply {
|
||||
setMargins(20.dp(resources), 20.dp(resources), 20.dp(resources), 20.dp(resources))
|
||||
}
|
||||
textAlignment = TEXT_ALIGNMENT_CENTER
|
||||
textSize = 14.0f
|
||||
text = message
|
||||
})
|
||||
return
|
||||
private fun setRecommendations(results: List<IPlatformVideo>?, message: String? = null) {
|
||||
if (results != null && StatePlayer.instance.autoplay) {
|
||||
_autoplayVideo = results.firstOrNull { !StatePlayer.instance.wasAutoplayed(it.url) }
|
||||
Logger.i(TAG, "Autoplay video set (url = ${_autoplayVideo?.url})")
|
||||
}
|
||||
|
||||
val results = pager.getResults().filter { it is IPlatformVideo }
|
||||
for (result in results) {
|
||||
_layoutRecommended.addView(PreviewVideoView(context, FeedStyle.THUMBNAIL, null, false).apply {
|
||||
layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT)
|
||||
bind(result)
|
||||
|
||||
hideAddTo()
|
||||
|
||||
onVideoClicked.subscribe { video, _ ->
|
||||
fragment.navigate<VideoDetailFragment>(video).maximizeVideoDetail()
|
||||
}
|
||||
|
||||
onChannelClicked.subscribe {
|
||||
fragment.navigate<ChannelFragment>(it)
|
||||
}
|
||||
|
||||
onAddToWatchLaterClicked.subscribe(this) {
|
||||
if(it is IPlatformVideo) {
|
||||
StatePlaylists.instance.addToWatchLater(SerializedPlatformVideo.fromVideo(it));
|
||||
UIDialogs.toast("Added to watch later\n[${it.name}]");
|
||||
if (_tabIndex == 2) {
|
||||
_layoutRecommended.removeAllViews()
|
||||
if (results == null || results.isEmpty()) {
|
||||
_layoutRecommended.addView(TextView(context).apply {
|
||||
layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT).apply {
|
||||
setMargins(20.dp(resources), 20.dp(resources), 20.dp(resources), 20.dp(resources))
|
||||
}
|
||||
}
|
||||
})
|
||||
textAlignment = TEXT_ALIGNMENT_CENTER
|
||||
textSize = 14.0f
|
||||
text = message
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
for (result in results) {
|
||||
_layoutRecommended.addView(PreviewVideoView(context, FeedStyle.THUMBNAIL, null, false).apply {
|
||||
layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT)
|
||||
bind(result)
|
||||
|
||||
hideAddTo()
|
||||
|
||||
onVideoClicked.subscribe { video, _ ->
|
||||
fragment.navigate<VideoDetailFragment>(video).maximizeVideoDetail()
|
||||
}
|
||||
|
||||
onChannelClicked.subscribe {
|
||||
fragment.navigate<ChannelFragment>(it)
|
||||
}
|
||||
|
||||
onAddToWatchLaterClicked.subscribe(this) {
|
||||
if(it is IPlatformVideo) {
|
||||
StatePlaylists.instance.addToWatchLater(SerializedPlatformVideo.fromVideo(it), true);
|
||||
UIDialogs.toast("Added to watch later\n[${it.name}]");
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2399,7 +2582,8 @@ class VideoDetailView : ConstraintLayout {
|
||||
_overlayContainer.removeAllViews();
|
||||
_overlay_quality_selector?.hide();
|
||||
|
||||
_player.fillHeight();
|
||||
_player.setFullScreen(true)
|
||||
_player.fillHeight(false)
|
||||
_layoutPlayerContainer.setPadding(0, 0, 0, 0);
|
||||
}
|
||||
fun handleLeavePictureInPicture() {
|
||||
@@ -2466,7 +2650,7 @@ class VideoDetailView : ConstraintLayout {
|
||||
if (v !is TutorialFragment.TutorialVideo) {
|
||||
fragment.lifecycleScope.launch(Dispatchers.IO) {
|
||||
val history = getHistoryIndex(v) ?: return@launch;
|
||||
StateHistory.instance.updateHistoryPosition(v, history, true, (positionMilliseconds.toFloat() / 1000.0f).toLong());
|
||||
StateHistory.instance.updateHistoryPosition(v, history, true, (positionMilliseconds.toFloat() / 1000.0f).toLong(), null, true);
|
||||
}
|
||||
}
|
||||
_lastPositionSaveTime = currentTime;
|
||||
@@ -2535,7 +2719,7 @@ class VideoDetailView : ConstraintLayout {
|
||||
else {
|
||||
if(_player.layoutParams.height == WRAP_CONTENT) {
|
||||
_player.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT);
|
||||
_player.fillHeight();
|
||||
_player.fillHeight(true)
|
||||
_cast.layoutParams = _cast.layoutParams.apply {
|
||||
(this as MarginLayoutParams).bottomMargin = 0;
|
||||
};
|
||||
@@ -2608,13 +2792,24 @@ class VideoDetailView : ConstraintLayout {
|
||||
if(_minimize_controls.isClickable != clickable)
|
||||
_minimize_controls.isClickable = clickable;
|
||||
}
|
||||
fun setVideoMinimize(value : Float) {
|
||||
val padRight = (resources.displayMetrics.widthPixels * 0.70 * value).toInt();
|
||||
_player.setPadding(0, _player.paddingTop, padRight, 0);
|
||||
_cast.setPadding(0, _cast.paddingTop, padRight, 0);
|
||||
|
||||
override fun onConfigurationChanged(newConfig: Configuration?) {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
if (fragment.state == VideoDetailFragment.State.MINIMIZED) {
|
||||
_player.fillHeight(true)
|
||||
} else if (!fragment.isFullscreen) {
|
||||
_player.fitHeight()
|
||||
}
|
||||
}
|
||||
fun setTopPadding(value : Float) {
|
||||
_player.setPadding(0, value.toInt(), _player.paddingRight, 0);
|
||||
|
||||
fun setVideoMinimize(value : Float) {
|
||||
val padRight = (resources.displayMetrics.widthPixels * 0.70 * value).toInt()
|
||||
_player.setPadding(0, _player.paddingTop, padRight, 0)
|
||||
_cast.setPadding(0, _cast.paddingTop, padRight, 0)
|
||||
}
|
||||
|
||||
fun setTopPadding(value: Float) {
|
||||
_player.setPadding(_player.paddingLeft, value.toInt(), _player.paddingRight, 0)
|
||||
}
|
||||
|
||||
//Tasks
|
||||
@@ -2657,7 +2852,7 @@ class VideoDetailView : ConstraintLayout {
|
||||
}
|
||||
if(!didLogin)
|
||||
UIDialogs.showDialogOk(context, R.drawable.ic_error_pred, "Failed to login");
|
||||
}));
|
||||
}, UIDialogs.ActionStyle.PRIMARY));
|
||||
}
|
||||
.exception<ContentNotAvailableYetException> {
|
||||
Logger.w(TAG, "exception<ContentNotAvailableYetException>", it)
|
||||
@@ -2717,18 +2912,28 @@ class VideoDetailView : ConstraintLayout {
|
||||
.exception<Throwable> {
|
||||
Logger.w(ChannelFragment.TAG, "Failed to load video.", it);
|
||||
|
||||
handleErrorOrCall {
|
||||
_retryCount = 0;
|
||||
_retryJob?.cancel();
|
||||
_retryJob = null;
|
||||
_liveTryJob?.cancel();
|
||||
_liveTryJob = null;
|
||||
UIDialogs.showGeneralRetryErrorDialog(context, context.getString(R.string.failed_to_load_video), it, ::fetchVideo, null, fragment);
|
||||
if(!(it.message?.contains("Unable to resolve host") ?: false && nextVideo())){
|
||||
handleErrorOrCall {
|
||||
_retryCount = 0;
|
||||
_retryJob?.cancel();
|
||||
_retryJob = null;
|
||||
_liveTryJob?.cancel();
|
||||
_liveTryJob = null;
|
||||
UIDialogs.showGeneralRetryErrorDialog(context, context.getString(R.string.failed_to_load_video), it, ::fetchVideo, null, fragment);
|
||||
}
|
||||
}
|
||||
} else TaskHandler(IPlatformVideoDetails::class.java, {fragment.lifecycleScope});
|
||||
|
||||
private val _taskLoadRecommendations = TaskHandler<String, IPager<IPlatformContent>?>(StateApp.instance.scopeGetter, { video?.getContentRecommendations(StatePlatform.instance.getContentClient(it)) })
|
||||
.success { setRecommendations(it, "No recommendations found") }
|
||||
private val _taskLoadRecommendations = TaskHandler<String?, IPager<IPlatformContent>?>(StateApp.instance.scopeGetter, {
|
||||
video?.let { v ->
|
||||
if (v is VideoLocal) {
|
||||
StatePlatform.instance.getContentRecommendations(v.url)
|
||||
} else {
|
||||
video?.getContentRecommendations(StatePlatform.instance.getContentClient(v.url))
|
||||
}
|
||||
}
|
||||
})
|
||||
.success { setRecommendations(it?.getResults()?.filter { it is IPlatformVideo }?.map { it as IPlatformVideo }, "No recommendations found") }
|
||||
.exception<Throwable> {
|
||||
setRecommendations(null, it.message)
|
||||
Logger.w(TAG, "Failed to load recommendations.", it);
|
||||
@@ -2821,6 +3026,7 @@ class VideoDetailView : ConstraintLayout {
|
||||
const val TAG_OVERLAY = "overlay";
|
||||
const val TAG_LIVECHAT = "livechat";
|
||||
const val TAG_OPEN = "open";
|
||||
const val TAG_SEND_TO_DEVICE = "send_to_device";
|
||||
const val TAG_MORE = "MORE";
|
||||
|
||||
private val _buttonPinStore = FragmentedStorage.get<StringArrayStorage>("videoPinnedButtons");
|
||||
|
||||
+2
-2
@@ -96,11 +96,11 @@ class WatchLaterFragment : MainFragment() {
|
||||
}
|
||||
|
||||
override fun onVideoOrderChanged(videos: List<IPlatformVideo>) {
|
||||
StatePlaylists.instance.updateWatchLater(ArrayList(videos.map { it as SerializedPlatformVideo }));
|
||||
StatePlaylists.instance.updateWatchLater(ArrayList(videos.map { it as SerializedPlatformVideo }), true);
|
||||
}
|
||||
override fun onVideoRemoved(video: IPlatformVideo) {
|
||||
if (video is SerializedPlatformVideo) {
|
||||
StatePlaylists.instance.removeFromWatchLater(video);
|
||||
StatePlaylists.instance.removeFromWatchLater(video, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+16
-12
@@ -26,6 +26,7 @@ import com.futo.platformplayer.stores.FragmentedStorage
|
||||
import com.futo.platformplayer.stores.SearchHistoryStorage
|
||||
|
||||
class SearchTopBarFragment : TopFragment() {
|
||||
@Suppress("PrivatePropertyName")
|
||||
private val TAG = "SearchTopBarFragment"
|
||||
|
||||
private var _editSearch: EditText? = null;
|
||||
@@ -191,29 +192,32 @@ class SearchTopBarFragment : TopFragment() {
|
||||
}
|
||||
|
||||
private fun onDone() {
|
||||
val editSearch = _editSearch;
|
||||
val editSearch = _editSearch
|
||||
if (editSearch != null) {
|
||||
val text = editSearch.text.toString();
|
||||
if (text.length < 3) {
|
||||
UIDialogs.toast(getString(R.string.please_use_at_least_3_characters));
|
||||
return;
|
||||
val text = editSearch.text.toString()
|
||||
if (text.isEmpty()) {
|
||||
UIDialogs.toast(getString(R.string.please_use_at_least_1_character))
|
||||
return
|
||||
}
|
||||
|
||||
editSearch.clearFocus();
|
||||
_inputMethodManager?.hideSoftInputFromWindow(editSearch.windowToken, 0);
|
||||
editSearch.clearFocus()
|
||||
_inputMethodManager?.hideSoftInputFromWindow(editSearch.windowToken, 0)
|
||||
|
||||
if (Settings.instance.search.searchHistory) {
|
||||
val storage = FragmentedStorage.get<SearchHistoryStorage>();
|
||||
storage.add(text);
|
||||
val storage = FragmentedStorage.get<SearchHistoryStorage>()
|
||||
storage.add(text)
|
||||
}
|
||||
|
||||
if (_searchType == SearchType.CREATOR) {
|
||||
onSearch.emit(text);
|
||||
onSearch.emit(text)
|
||||
} else {
|
||||
onSearch.emit(text);
|
||||
onSearch.emit(text)
|
||||
}
|
||||
} else {
|
||||
Logger.w(TAG, "Unexpected condition happened where done is edit search is null but done is triggered.");
|
||||
Logger.w(
|
||||
TAG,
|
||||
"Unexpected condition happened where done is edit search is null but done is triggered."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -191,21 +191,21 @@ class VideoHelper {
|
||||
}
|
||||
|
||||
|
||||
fun estimateSourceSize(source: IVideoSource?): Int {
|
||||
fun estimateSourceSize(source: IVideoSource?): Long {
|
||||
if(source == null) return 0;
|
||||
if(source is IVideoSource) {
|
||||
if(source.bitrate ?: 0 <= 0 || source.duration.toInt() == 0)
|
||||
return 0;
|
||||
return (source.duration / 8).toInt() * source.bitrate!!;
|
||||
return (source.duration / 8) * source.bitrate!!;
|
||||
}
|
||||
else return 0;
|
||||
}
|
||||
fun estimateSourceSize(source: IAudioSource?): Int {
|
||||
fun estimateSourceSize(source: IAudioSource?): Long {
|
||||
if(source == null) return 0;
|
||||
if(source is IAudioSource) {
|
||||
if(source.bitrate <= 0 || source.duration?.toInt() ?: 0 == 0)
|
||||
return 0;
|
||||
return (source.duration!! / 8).toInt() * source.bitrate;
|
||||
return (source.duration!! / 8) * source.bitrate;
|
||||
}
|
||||
else return 0;
|
||||
}
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
package com.futo.platformplayer.listeners
|
||||
|
||||
import android.content.Context
|
||||
import android.database.ContentObserver
|
||||
import android.os.Handler
|
||||
import android.provider.Settings
|
||||
|
||||
class AutoRotateObserver(handler: Handler, private val onChangeCallback: () -> Unit) : ContentObserver(handler) {
|
||||
override fun onChange(selfChange: Boolean) {
|
||||
super.onChange(selfChange)
|
||||
onChangeCallback()
|
||||
}
|
||||
}
|
||||
|
||||
class AutoRotateChangeListener(context: Context, handler: Handler, private val onAutoRotateChanged: (Boolean) -> Unit) {
|
||||
|
||||
private val contentResolver = context.contentResolver
|
||||
private val autoRotateObserver = AutoRotateObserver(handler) {
|
||||
val isAutoRotateEnabled = isAutoRotateEnabled()
|
||||
onAutoRotateChanged(isAutoRotateEnabled)
|
||||
}
|
||||
|
||||
init {
|
||||
contentResolver.registerContentObserver(
|
||||
Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION),
|
||||
false,
|
||||
autoRotateObserver
|
||||
)
|
||||
}
|
||||
|
||||
fun unregister() {
|
||||
contentResolver.unregisterContentObserver(autoRotateObserver)
|
||||
}
|
||||
|
||||
private fun isAutoRotateEnabled(): Boolean {
|
||||
return Settings.System.getInt(
|
||||
contentResolver,
|
||||
Settings.System.ACCELEROMETER_ROTATION,
|
||||
0
|
||||
) == 1
|
||||
}
|
||||
}
|
||||
@@ -74,7 +74,7 @@ class DnsWriter {
|
||||
namePositions[nameAtOffset] = nameStartPos
|
||||
}
|
||||
}
|
||||
write(0.toByte()) // End of domain name
|
||||
write(0.toByte())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,10 @@ class MDNSListener {
|
||||
}
|
||||
|
||||
fun start() {
|
||||
if (_started) throw Exception("Already running.")
|
||||
if (_started) {
|
||||
Logger.i(TAG, "Already started.")
|
||||
return
|
||||
}
|
||||
_started = true
|
||||
|
||||
_scope = CoroutineScope(Dispatchers.IO);
|
||||
|
||||
@@ -37,7 +37,10 @@ class ServiceDiscoverer(names: Array<String>, private val _onServicesUpdated: (L
|
||||
}
|
||||
|
||||
fun start() {
|
||||
if (_started) throw Exception("Already running.")
|
||||
if (_started) {
|
||||
Logger.i(TAG, "Already started.")
|
||||
return
|
||||
}
|
||||
_started = true
|
||||
|
||||
val listener = MDNSListener()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.futo.platformplayer.mdns
|
||||
|
||||
import com.futo.platformplayer.logging.Logger
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
@@ -67,7 +68,7 @@ class ServiceRecordAggregator {
|
||||
_currentServices.addAll(newServices)
|
||||
}
|
||||
|
||||
onServicesUpdated?.invoke(_currentServices)
|
||||
onServicesUpdated?.invoke(_currentServices.toList())
|
||||
delay(5000)
|
||||
}
|
||||
}
|
||||
@@ -91,43 +92,42 @@ class ServiceRecordAggregator {
|
||||
|
||||
/*val builder = StringBuilder()
|
||||
builder.appendLine("Received records:")
|
||||
srvRecords.forEach { builder.appendLine(" ${it.first.name} ${it.first.type} ${it.first.clazz} TTL ${it.first.timeToLive}: (Port: ${it.second.port}, Target: ${it.second.target}, Priority: ${it.second.priority}, Weight: ${it.second.weight})") }
|
||||
ptrRecords.forEach { builder.appendLine(" ${it.first.name} ${it.first.type} ${it.first.clazz} TTL ${it.first.timeToLive}: ${it.second.domainName}") }
|
||||
txtRecords.forEach { builder.appendLine(" ${it.first.name} ${it.first.type} ${it.first.clazz} TTL ${it.first.timeToLive}: ${it.second.texts.joinToString(", ")}") }
|
||||
aRecords.forEach { builder.appendLine(" ${it.first.name} ${it.first.type} ${it.first.clazz} TTL ${it.first.timeToLive}: ${it.second.address}") }
|
||||
aaaaRecords.forEach { builder.appendLine(" ${it.first.name} ${it.first.type} ${it.first.clazz} TTL ${it.first.timeToLive}: ${it.second.address}") }
|
||||
synchronized(lockObject) {
|
||||
// Save to file if necessary
|
||||
}*/
|
||||
srvRecords.forEach { builder.appendLine("SRV ${it.first.name} ${it.first.type} ${it.first.clazz} TTL ${it.first.timeToLive}: (Port: ${it.second.port}, Target: ${it.second.target}, Priority: ${it.second.priority}, Weight: ${it.second.weight})") }
|
||||
ptrRecords.forEach { builder.appendLine("PTR ${it.first.name} ${it.first.type} ${it.first.clazz} TTL ${it.first.timeToLive}: ${it.second.domainName}") }
|
||||
txtRecords.forEach { builder.appendLine("TXT ${it.first.name} ${it.first.type} ${it.first.clazz} TTL ${it.first.timeToLive}: ${it.second.texts.joinToString(", ")}") }
|
||||
aRecords.forEach { builder.appendLine("A ${it.first.name} ${it.first.type} ${it.first.clazz} TTL ${it.first.timeToLive}: ${it.second.address}") }
|
||||
aaaaRecords.forEach { builder.appendLine("AAAA ${it.first.name} ${it.first.type} ${it.first.clazz} TTL ${it.first.timeToLive}: ${it.second.address}") }
|
||||
Logger.i(TAG, "$builder")*/
|
||||
|
||||
val currentServices: MutableList<DnsService>
|
||||
ptrRecords.forEach { record ->
|
||||
val cachedPtrRecord = _cachedPtrRecords.getOrPut(record.first.name) { mutableListOf() }
|
||||
val newPtrRecord = CachedDnsPtrRecord(Date(System.currentTimeMillis() + record.first.timeToLive.toLong() * 1000L), record.second.domainName)
|
||||
cachedPtrRecord.replaceOrAdd(newPtrRecord) { it.target == record.second.domainName }
|
||||
}
|
||||
|
||||
aRecords.forEach { aRecord ->
|
||||
val cachedARecord = _cachedAddressRecords.getOrPut(aRecord.first.name) { mutableListOf() }
|
||||
val newARecord = CachedDnsAddressRecord(Date(System.currentTimeMillis() + aRecord.first.timeToLive.toLong() * 1000L), aRecord.second.address)
|
||||
cachedARecord.replaceOrAdd(newARecord) { it.address == newARecord.address }
|
||||
}
|
||||
|
||||
aaaaRecords.forEach { aaaaRecord ->
|
||||
val cachedAaaaRecord = _cachedAddressRecords.getOrPut(aaaaRecord.first.name) { mutableListOf() }
|
||||
val newAaaaRecord = CachedDnsAddressRecord(Date(System.currentTimeMillis() + aaaaRecord.first.timeToLive.toLong() * 1000L), aaaaRecord.second.address)
|
||||
cachedAaaaRecord.replaceOrAdd(newAaaaRecord) { it.address == newAaaaRecord.address }
|
||||
}
|
||||
|
||||
txtRecords.forEach { txtRecord ->
|
||||
_cachedTxtRecords[txtRecord.first.name] = CachedDnsTxtRecord(Date(System.currentTimeMillis() + txtRecord.first.timeToLive.toLong() * 1000L), txtRecord.second.texts)
|
||||
}
|
||||
|
||||
srvRecords.forEach { srvRecord ->
|
||||
_cachedSrvRecords[srvRecord.first.name] = CachedDnsSrvRecord(Date(System.currentTimeMillis() + srvRecord.first.timeToLive.toLong() * 1000L), srvRecord.second)
|
||||
}
|
||||
|
||||
//TODO: Maybe this can be debounced?
|
||||
synchronized(this._currentServices) {
|
||||
ptrRecords.forEach { record ->
|
||||
val cachedPtrRecord = _cachedPtrRecords.getOrPut(record.first.name) { mutableListOf() }
|
||||
val newPtrRecord = CachedDnsPtrRecord(Date(System.currentTimeMillis() + record.first.timeToLive.toLong() * 1000L), record.second.domainName)
|
||||
cachedPtrRecord.replaceOrAdd(newPtrRecord) { it.target == record.second.domainName }
|
||||
|
||||
aRecords.forEach { aRecord ->
|
||||
val cachedARecord = _cachedAddressRecords.getOrPut(aRecord.first.name) { mutableListOf() }
|
||||
val newARecord = CachedDnsAddressRecord(Date(System.currentTimeMillis() + aRecord.first.timeToLive.toLong() * 1000L), aRecord.second.address)
|
||||
cachedARecord.replaceOrAdd(newARecord) { it.address == newARecord.address }
|
||||
}
|
||||
|
||||
aaaaRecords.forEach { aaaaRecord ->
|
||||
val cachedAaaaRecord = _cachedAddressRecords.getOrPut(aaaaRecord.first.name) { mutableListOf() }
|
||||
val newAaaaRecord = CachedDnsAddressRecord(Date(System.currentTimeMillis() + aaaaRecord.first.timeToLive.toLong() * 1000L), aaaaRecord.second.address)
|
||||
cachedAaaaRecord.replaceOrAdd(newAaaaRecord) { it.address == newAaaaRecord.address }
|
||||
}
|
||||
}
|
||||
|
||||
txtRecords.forEach { txtRecord ->
|
||||
_cachedTxtRecords[txtRecord.first.name] = CachedDnsTxtRecord(Date(System.currentTimeMillis() + txtRecord.first.timeToLive.toLong() * 1000L), txtRecord.second.texts)
|
||||
}
|
||||
|
||||
srvRecords.forEach { srvRecord ->
|
||||
_cachedSrvRecords[srvRecord.first.name] = CachedDnsSrvRecord(Date(System.currentTimeMillis() + srvRecord.first.timeToLive.toLong() * 1000L), srvRecord.second)
|
||||
}
|
||||
|
||||
currentServices = getCurrentServices()
|
||||
this._currentServices.clear()
|
||||
this._currentServices.addAll(currentServices)
|
||||
@@ -216,4 +216,8 @@ class ServiceRecordAggregator {
|
||||
add(newElement)
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
private const val TAG = "ServiceRecordAggregator"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,9 @@ import java.time.OffsetDateTime
|
||||
class Subscription {
|
||||
var channel: SerializedChannel;
|
||||
|
||||
@kotlinx.serialization.Serializable(with = OffsetDateTimeSerializer::class)
|
||||
var creationTime: OffsetDateTime = OffsetDateTime.MIN;
|
||||
|
||||
//Settings
|
||||
var doNotifications: Boolean = false;
|
||||
var doFetchLive: Boolean = false;
|
||||
@@ -55,6 +58,8 @@ class Subscription {
|
||||
|
||||
constructor(channel : SerializedChannel) {
|
||||
this.channel = channel;
|
||||
if(this.creationTime == OffsetDateTime.MIN)
|
||||
this.creationTime = OffsetDateTime.now();
|
||||
}
|
||||
|
||||
fun isChannel(url: String): Boolean {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.futo.platformplayer.models
|
||||
|
||||
import com.futo.platformplayer.serializers.OffsetDateTimeSerializer
|
||||
import java.time.OffsetDateTime
|
||||
import java.util.UUID
|
||||
|
||||
@kotlinx.serialization.Serializable
|
||||
@@ -10,6 +12,11 @@ open class SubscriptionGroup {
|
||||
var urls: MutableList<String> = mutableListOf();
|
||||
var priority: Int = 99;
|
||||
|
||||
@kotlinx.serialization.Serializable(with = OffsetDateTimeSerializer::class)
|
||||
var lastChange : OffsetDateTime = OffsetDateTime.MIN;
|
||||
@kotlinx.serialization.Serializable(with = OffsetDateTimeSerializer::class)
|
||||
var creationTime : OffsetDateTime = OffsetDateTime.now();
|
||||
|
||||
constructor(name: String) {
|
||||
this.name = name;
|
||||
}
|
||||
@@ -19,6 +26,8 @@ open class SubscriptionGroup {
|
||||
this.image = parent.image;
|
||||
this.urls = parent.urls;
|
||||
this.priority = parent.priority;
|
||||
this.lastChange = parent.lastChange;
|
||||
this.creationTime = parent.creationTime;
|
||||
}
|
||||
|
||||
class Selectable(parent: SubscriptionGroup, isSelected: Boolean = false): SubscriptionGroup(parent) {
|
||||
|
||||
@@ -0,0 +1,243 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Southern Storm Software, Pty Ltd.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included
|
||||
* in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.futo.platformplayer.noise.crypto;
|
||||
|
||||
import java.security.DigestException;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Arrays;
|
||||
|
||||
import com.futo.platformplayer.noise.protocol.Destroyable;
|
||||
|
||||
/**
|
||||
* Fallback implementation of BLAKE2b for the Noise library.
|
||||
*
|
||||
* This implementation only supports message digesting with an output
|
||||
* length of 64 bytes and a limit of 2^64 - 1 bytes of input.
|
||||
* Keyed hashing and variable-length digests are not supported.
|
||||
*/
|
||||
public class Blake2bMessageDigest extends MessageDigest implements Destroyable {
|
||||
|
||||
private long[] h;
|
||||
private byte[] block;
|
||||
private long[] m;
|
||||
private long[] v;
|
||||
private long length;
|
||||
private int posn;
|
||||
|
||||
/**
|
||||
* Constructs a new BLAKE2b message digest object.
|
||||
*/
|
||||
public Blake2bMessageDigest() {
|
||||
super("BLAKE2B-512");
|
||||
h = new long [8];
|
||||
block = new byte [128];
|
||||
m = new long [16];
|
||||
v = new long [16];
|
||||
engineReset();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] engineDigest() {
|
||||
byte[] digest = new byte [64];
|
||||
try {
|
||||
engineDigest(digest, 0, 64);
|
||||
} catch (DigestException e) {
|
||||
// Shouldn't happen, but just in case.
|
||||
Arrays.fill(digest, (byte)0);
|
||||
}
|
||||
return digest;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int engineDigest(byte[] buf, int offset, int len) throws DigestException
|
||||
{
|
||||
if (len < 64)
|
||||
throw new DigestException("Invalid digest length for BLAKE2b");
|
||||
Arrays.fill(block, posn, 128, (byte)0);
|
||||
transform(-1);
|
||||
for (int index = 0; index < 8; ++index) {
|
||||
long value = h[index];
|
||||
buf[offset++] = (byte)value;
|
||||
buf[offset++] = (byte)(value >> 8);
|
||||
buf[offset++] = (byte)(value >> 16);
|
||||
buf[offset++] = (byte)(value >> 24);
|
||||
buf[offset++] = (byte)(value >> 32);
|
||||
buf[offset++] = (byte)(value >> 40);
|
||||
buf[offset++] = (byte)(value >> 48);
|
||||
buf[offset++] = (byte)(value >> 56);
|
||||
}
|
||||
return 32;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int engineGetDigestLength() {
|
||||
return 64;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void engineReset() {
|
||||
h[0] = 0x6a09e667f3bcc908L ^ 0x01010040;
|
||||
h[1] = 0xbb67ae8584caa73bL;
|
||||
h[2] = 0x3c6ef372fe94f82bL;
|
||||
h[3] = 0xa54ff53a5f1d36f1L;
|
||||
h[4] = 0x510e527fade682d1L;
|
||||
h[5] = 0x9b05688c2b3e6c1fL;
|
||||
h[6] = 0x1f83d9abfb41bd6bL;
|
||||
h[7] = 0x5be0cd19137e2179L;
|
||||
length = 0;
|
||||
posn = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void engineUpdate(byte input) {
|
||||
if (posn >= 128) {
|
||||
transform(0);
|
||||
posn = 0;
|
||||
}
|
||||
block[posn++] = input;
|
||||
++length;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void engineUpdate(byte[] input, int offset, int len) {
|
||||
while (len > 0) {
|
||||
if (posn >= 128) {
|
||||
transform(0);
|
||||
posn = 0;
|
||||
}
|
||||
int temp = (128 - posn);
|
||||
if (temp > len)
|
||||
temp = len;
|
||||
System.arraycopy(input, offset, block, posn, temp);
|
||||
posn += temp;
|
||||
length += temp;
|
||||
offset += temp;
|
||||
len -= temp;
|
||||
}
|
||||
}
|
||||
|
||||
// Permutation on the message input state for BLAKE2b.
|
||||
static final byte[][] sigma = {
|
||||
{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
|
||||
{14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3},
|
||||
{11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4},
|
||||
{ 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8},
|
||||
{ 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13},
|
||||
{ 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9},
|
||||
{12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11},
|
||||
{13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10},
|
||||
{ 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5},
|
||||
{10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13 , 0},
|
||||
{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
|
||||
{14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3},
|
||||
};
|
||||
|
||||
private void transform(long f0)
|
||||
{
|
||||
int index;
|
||||
int offset;
|
||||
|
||||
// Unpack the input block from little-endian into host-endian.
|
||||
for (index = 0, offset = 0; index < 16; ++index, offset += 8) {
|
||||
m[index] = (block[offset] & 0xFFL) |
|
||||
((block[offset + 1] & 0xFFL) << 8) |
|
||||
((block[offset + 2] & 0xFFL) << 16) |
|
||||
((block[offset + 3] & 0xFFL) << 24) |
|
||||
((block[offset + 4] & 0xFFL) << 32) |
|
||||
((block[offset + 5] & 0xFFL) << 40) |
|
||||
((block[offset + 6] & 0xFFL) << 48) |
|
||||
((block[offset + 7] & 0xFFL) << 56);
|
||||
}
|
||||
|
||||
// Format the block to be hashed.
|
||||
for (index = 0; index < 8; ++index)
|
||||
v[index] = h[index];
|
||||
v[8] = 0x6a09e667f3bcc908L;
|
||||
v[9] = 0xbb67ae8584caa73bL;
|
||||
v[10] = 0x3c6ef372fe94f82bL;
|
||||
v[11] = 0xa54ff53a5f1d36f1L;
|
||||
v[12] = 0x510e527fade682d1L ^ length;
|
||||
v[13] = 0x9b05688c2b3e6c1fL;
|
||||
v[14] = 0x1f83d9abfb41bd6bL ^ f0;
|
||||
v[15] = 0x5be0cd19137e2179L;
|
||||
|
||||
// Perform the 12 BLAKE2b rounds.
|
||||
for (index = 0; index < 12; ++index) {
|
||||
// Column round.
|
||||
quarterRound(0, 4, 8, 12, 0, index);
|
||||
quarterRound(1, 5, 9, 13, 1, index);
|
||||
quarterRound(2, 6, 10, 14, 2, index);
|
||||
quarterRound(3, 7, 11, 15, 3, index);
|
||||
|
||||
// Diagonal round.
|
||||
quarterRound(0, 5, 10, 15, 4, index);
|
||||
quarterRound(1, 6, 11, 12, 5, index);
|
||||
quarterRound(2, 7, 8, 13, 6, index);
|
||||
quarterRound(3, 4, 9, 14, 7, index);
|
||||
}
|
||||
|
||||
// Combine the new and old hash values.
|
||||
for (index = 0; index < 8; ++index)
|
||||
h[index] ^= (v[index] ^ v[index + 8]);
|
||||
}
|
||||
|
||||
private static long rightRotate32(long v)
|
||||
{
|
||||
return v << 32 | (v >>> 32);
|
||||
}
|
||||
|
||||
private static long rightRotate24(long v)
|
||||
{
|
||||
return v << 40 | (v >>> 24);
|
||||
}
|
||||
|
||||
private static long rightRotate16(long v)
|
||||
{
|
||||
return v << 48 | (v >>> 16);
|
||||
}
|
||||
|
||||
private static long rightRotate63(long v)
|
||||
{
|
||||
return v << 1 | (v >>> 63);
|
||||
}
|
||||
|
||||
private void quarterRound(int a, int b, int c, int d, int i, int row)
|
||||
{
|
||||
v[a] += v[b] + m[sigma[row][2 * i]];
|
||||
v[d] = rightRotate32(v[d] ^ v[a]);
|
||||
v[c] += v[d];
|
||||
v[b] = rightRotate24(v[b] ^ v[c]);
|
||||
v[a] += v[b] + m[sigma[row][2 * i + 1]];
|
||||
v[d] = rightRotate16(v[d] ^ v[a]);
|
||||
v[c] += v[d];
|
||||
v[b] = rightRotate63(v[b] ^ v[c]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
Arrays.fill(h, (long)0);
|
||||
Arrays.fill(block, (byte)0);
|
||||
Arrays.fill(m, (long)0);
|
||||
Arrays.fill(v, (long)0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Southern Storm Software, Pty Ltd.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included
|
||||
* in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.futo.platformplayer.noise.crypto;
|
||||
|
||||
import java.security.DigestException;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Arrays;
|
||||
|
||||
import com.futo.platformplayer.noise.protocol.Destroyable;
|
||||
|
||||
/**
|
||||
* Fallback implementation of BLAKE2s for the Noise library.
|
||||
*
|
||||
* This implementation only supports message digesting with an output
|
||||
* length of 32 bytes. Keyed hashing and variable-length digests are
|
||||
* not supported.
|
||||
*/
|
||||
public class Blake2sMessageDigest extends MessageDigest implements Destroyable {
|
||||
|
||||
private int[] h;
|
||||
private byte[] block;
|
||||
private int[] m;
|
||||
private int[] v;
|
||||
private long length;
|
||||
private int posn;
|
||||
|
||||
/**
|
||||
* Constructs a new BLAKE2s message digest object.
|
||||
*/
|
||||
public Blake2sMessageDigest() {
|
||||
super("BLAKE2S-256");
|
||||
h = new int [8];
|
||||
block = new byte [64];
|
||||
m = new int [16];
|
||||
v = new int [16];
|
||||
engineReset();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] engineDigest() {
|
||||
byte[] digest = new byte [32];
|
||||
try {
|
||||
engineDigest(digest, 0, 32);
|
||||
} catch (DigestException e) {
|
||||
// Shouldn't happen, but just in case.
|
||||
Arrays.fill(digest, (byte)0);
|
||||
}
|
||||
return digest;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int engineDigest(byte[] buf, int offset, int len) throws DigestException
|
||||
{
|
||||
if (len < 32)
|
||||
throw new DigestException("Invalid digest length for BLAKE2s");
|
||||
Arrays.fill(block, posn, 64, (byte)0);
|
||||
transform(-1);
|
||||
for (int index = 0; index < 8; ++index) {
|
||||
int value = h[index];
|
||||
buf[offset++] = (byte)value;
|
||||
buf[offset++] = (byte)(value >> 8);
|
||||
buf[offset++] = (byte)(value >> 16);
|
||||
buf[offset++] = (byte)(value >> 24);
|
||||
}
|
||||
return 32;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int engineGetDigestLength() {
|
||||
return 32;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void engineReset() {
|
||||
h[0] = 0x6A09E667 ^ 0x01010020;
|
||||
h[1] = 0xBB67AE85;
|
||||
h[2] = 0x3C6EF372;
|
||||
h[3] = 0xA54FF53A;
|
||||
h[4] = 0x510E527F;
|
||||
h[5] = 0x9B05688C;
|
||||
h[6] = 0x1F83D9AB;
|
||||
h[7] = 0x5BE0CD19;
|
||||
length = 0;
|
||||
posn = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void engineUpdate(byte input) {
|
||||
if (posn >= 64) {
|
||||
transform(0);
|
||||
posn = 0;
|
||||
}
|
||||
block[posn++] = input;
|
||||
++length;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void engineUpdate(byte[] input, int offset, int len) {
|
||||
while (len > 0) {
|
||||
if (posn >= 64) {
|
||||
transform(0);
|
||||
posn = 0;
|
||||
}
|
||||
int temp = (64 - posn);
|
||||
if (temp > len)
|
||||
temp = len;
|
||||
System.arraycopy(input, offset, block, posn, temp);
|
||||
posn += temp;
|
||||
length += temp;
|
||||
offset += temp;
|
||||
len -= temp;
|
||||
}
|
||||
}
|
||||
|
||||
// Permutation on the message input state for BLAKE2s.
|
||||
static final byte[][] sigma = {
|
||||
{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
|
||||
{14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3},
|
||||
{11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4},
|
||||
{ 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8},
|
||||
{ 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13},
|
||||
{ 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9},
|
||||
{12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11},
|
||||
{13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10},
|
||||
{ 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5},
|
||||
{10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13 , 0}
|
||||
};
|
||||
|
||||
private void transform(int f0)
|
||||
{
|
||||
int index;
|
||||
int offset;
|
||||
|
||||
// Unpack the input block from little-endian into host-endian.
|
||||
for (index = 0, offset = 0; index < 16; ++index, offset += 4) {
|
||||
m[index] = (block[offset] & 0xFF) |
|
||||
((block[offset + 1] & 0xFF) << 8) |
|
||||
((block[offset + 2] & 0xFF) << 16) |
|
||||
((block[offset + 3] & 0xFF) << 24);
|
||||
}
|
||||
|
||||
// Format the block to be hashed.
|
||||
for (index = 0; index < 8; ++index)
|
||||
v[index] = h[index];
|
||||
v[8] = 0x6A09E667;
|
||||
v[9] = 0xBB67AE85;
|
||||
v[10] = 0x3C6EF372;
|
||||
v[11] = 0xA54FF53A;
|
||||
v[12] = 0x510E527F ^ (int)length;
|
||||
v[13] = 0x9B05688C ^ (int)(length >> 32);
|
||||
v[14] = 0x1F83D9AB ^ f0;
|
||||
v[15] = 0x5BE0CD19;
|
||||
|
||||
// Perform the 10 BLAKE2s rounds.
|
||||
for (index = 0; index < 10; ++index) {
|
||||
// Column round.
|
||||
quarterRound(0, 4, 8, 12, 0, index);
|
||||
quarterRound(1, 5, 9, 13, 1, index);
|
||||
quarterRound(2, 6, 10, 14, 2, index);
|
||||
quarterRound(3, 7, 11, 15, 3, index);
|
||||
|
||||
// Diagonal round.
|
||||
quarterRound(0, 5, 10, 15, 4, index);
|
||||
quarterRound(1, 6, 11, 12, 5, index);
|
||||
quarterRound(2, 7, 8, 13, 6, index);
|
||||
quarterRound(3, 4, 9, 14, 7, index);
|
||||
}
|
||||
|
||||
// Combine the new and old hash values.
|
||||
for (index = 0; index < 8; ++index)
|
||||
h[index] ^= (v[index] ^ v[index + 8]);
|
||||
}
|
||||
|
||||
private static int rightRotate16(int v)
|
||||
{
|
||||
return v << 16 | (v >>> 16);
|
||||
}
|
||||
|
||||
private static int rightRotate12(int v)
|
||||
{
|
||||
return v << 20 | (v >>> 12);
|
||||
}
|
||||
|
||||
private static int rightRotate8(int v)
|
||||
{
|
||||
return v << 24 | (v >>> 8);
|
||||
}
|
||||
|
||||
private static int rightRotate7(int v)
|
||||
{
|
||||
return v << 25 | (v >>> 7);
|
||||
}
|
||||
|
||||
private void quarterRound(int a, int b, int c, int d, int i, int row)
|
||||
{
|
||||
v[a] += v[b] + m[sigma[row][2 * i]];
|
||||
v[d] = rightRotate16(v[d] ^ v[a]);
|
||||
v[c] += v[d];
|
||||
v[b] = rightRotate12(v[b] ^ v[c]);
|
||||
v[a] += v[b] + m[sigma[row][2 * i + 1]];
|
||||
v[d] = rightRotate8(v[d] ^ v[a]);
|
||||
v[c] += v[d];
|
||||
v[b] = rightRotate7(v[b] ^ v[c]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
Arrays.fill(h, (int)0);
|
||||
Arrays.fill(block, (byte)0);
|
||||
Arrays.fill(m, (int)0);
|
||||
Arrays.fill(v, (int)0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Southern Storm Software, Pty Ltd.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included
|
||||
* in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.futo.platformplayer.noise.crypto;
|
||||
|
||||
/**
|
||||
* Implementation of the ChaCha20 core hash transformation.
|
||||
*/
|
||||
public final class ChaChaCore {
|
||||
|
||||
private ChaChaCore() {}
|
||||
|
||||
/**
|
||||
* Hashes an input block with ChaCha20.
|
||||
*
|
||||
* @param output The output block, which must contain at least 16
|
||||
* elements and must not overlap with the input.
|
||||
* @param input The input block, which must contain at least 16
|
||||
* elements.
|
||||
*/
|
||||
public static void hash(int[] output, int[] input)
|
||||
{
|
||||
int index;
|
||||
|
||||
// Copy the input to the output to start with.
|
||||
for (index = 0; index < 16; ++index)
|
||||
output[index] = input[index];
|
||||
|
||||
// Perform the 20 ChaCha rounds in groups of two.
|
||||
for (index = 0; index < 20; index += 2) {
|
||||
// Column round.
|
||||
quarterRound(output, 0, 4, 8, 12);
|
||||
quarterRound(output, 1, 5, 9, 13);
|
||||
quarterRound(output, 2, 6, 10, 14);
|
||||
quarterRound(output, 3, 7, 11, 15);
|
||||
|
||||
// Diagonal round.
|
||||
quarterRound(output, 0, 5, 10, 15);
|
||||
quarterRound(output, 1, 6, 11, 12);
|
||||
quarterRound(output, 2, 7, 8, 13);
|
||||
quarterRound(output, 3, 4, 9, 14);
|
||||
}
|
||||
|
||||
// Add the input block to the output.
|
||||
for (index = 0; index < 16; ++index)
|
||||
output[index] += input[index];
|
||||
}
|
||||
|
||||
private static int char4(char c1, char c2, char c3, char c4)
|
||||
{
|
||||
return (((int)c1) & 0xFF) | ((((int)c2) & 0xFF) << 8) | ((((int)c3) & 0xFF) << 16) | ((((int)c4) & 0xFF) << 24);
|
||||
}
|
||||
|
||||
private static int fromLittleEndian(byte[] key, int offset)
|
||||
{
|
||||
return (key[offset] & 0xFF) | ((key[offset + 1] & 0xFF) << 8) | ((key[offset + 2] & 0xFF) << 16) | ((key[offset + 3] & 0xFF) << 24);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a ChaCha20 block with a 128-bit key.
|
||||
*
|
||||
* @param output The output block, which must consist of at
|
||||
* least 16 words.
|
||||
* @param key The buffer containing the key.
|
||||
* @param offset Offset of the key in the buffer.
|
||||
*/
|
||||
public static void initKey128(int[] output, byte[] key, int offset)
|
||||
{
|
||||
output[0] = char4('e', 'x', 'p', 'a');
|
||||
output[1] = char4('n', 'd', ' ', '1');
|
||||
output[2] = char4('6', '-', 'b', 'y');
|
||||
output[3] = char4('t', 'e', ' ', 'k');
|
||||
output[4] = fromLittleEndian(key, offset);
|
||||
output[5] = fromLittleEndian(key, offset + 4);
|
||||
output[6] = fromLittleEndian(key, offset + 8);
|
||||
output[7] = fromLittleEndian(key, offset + 12);
|
||||
output[8] = output[4];
|
||||
output[9] = output[5];
|
||||
output[10] = output[6];
|
||||
output[11] = output[7];
|
||||
output[12] = 0;
|
||||
output[13] = 0;
|
||||
output[14] = 0;
|
||||
output[15] = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a ChaCha20 block with a 256-bit key.
|
||||
*
|
||||
* @param output The output block, which must consist of at
|
||||
* least 16 words.
|
||||
* @param key The buffer containing the key.
|
||||
* @param offset Offset of the key in the buffer.
|
||||
*/
|
||||
public static void initKey256(int[] output, byte[] key, int offset)
|
||||
{
|
||||
output[0] = char4('e', 'x', 'p', 'a');
|
||||
output[1] = char4('n', 'd', ' ', '3');
|
||||
output[2] = char4('2', '-', 'b', 'y');
|
||||
output[3] = char4('t', 'e', ' ', 'k');
|
||||
output[4] = fromLittleEndian(key, offset);
|
||||
output[5] = fromLittleEndian(key, offset + 4);
|
||||
output[6] = fromLittleEndian(key, offset + 8);
|
||||
output[7] = fromLittleEndian(key, offset + 12);
|
||||
output[8] = fromLittleEndian(key, offset + 16);
|
||||
output[9] = fromLittleEndian(key, offset + 20);
|
||||
output[10] = fromLittleEndian(key, offset + 24);
|
||||
output[11] = fromLittleEndian(key, offset + 28);
|
||||
output[12] = 0;
|
||||
output[13] = 0;
|
||||
output[14] = 0;
|
||||
output[15] = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the 64-bit initialization vector in a ChaCha20 block.
|
||||
*
|
||||
* @param output The output block, which must consist of at
|
||||
* least 16 words and must have been initialized by initKey256()
|
||||
* or initKey128().
|
||||
* @param iv The 64-bit initialization vector value.
|
||||
*
|
||||
* The counter portion of the output block is set to zero.
|
||||
*/
|
||||
public static void initIV(int[] output, long iv)
|
||||
{
|
||||
output[12] = 0;
|
||||
output[13] = 0;
|
||||
output[14] = (int)iv;
|
||||
output[15] = (int)(iv >> 32);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the 64-bit initialization vector and counter in a ChaCha20 block.
|
||||
*
|
||||
* @param output The output block, which must consist of at
|
||||
* least 16 words and must have been initialized by initKey256()
|
||||
* or initKey128().
|
||||
* @param iv The 64-bit initialization vector value.
|
||||
* @param counter The 64-bit counter value.
|
||||
*/
|
||||
public static void initIV(int[] output, long iv, long counter)
|
||||
{
|
||||
output[12] = (int)counter;
|
||||
output[13] = (int)(counter >> 32);
|
||||
output[14] = (int)iv;
|
||||
output[15] = (int)(iv >> 32);
|
||||
}
|
||||
|
||||
private static int leftRotate16(int v)
|
||||
{
|
||||
return v << 16 | (v >>> 16);
|
||||
}
|
||||
|
||||
private static int leftRotate12(int v)
|
||||
{
|
||||
return v << 12 | (v >>> 20);
|
||||
}
|
||||
|
||||
private static int leftRotate8(int v)
|
||||
{
|
||||
return v << 8 | (v >>> 24);
|
||||
}
|
||||
|
||||
private static int leftRotate7(int v)
|
||||
{
|
||||
return v << 7 | (v >>> 25);
|
||||
}
|
||||
|
||||
private static void quarterRound(int[] v, int a, int b, int c, int d)
|
||||
{
|
||||
v[a] += v[b];
|
||||
v[d] = leftRotate16(v[d] ^ v[a]);
|
||||
v[c] += v[d];
|
||||
v[b] = leftRotate12(v[b] ^ v[c]);
|
||||
v[a] += v[b];
|
||||
v[d] = leftRotate8(v[d] ^ v[a]);
|
||||
v[c] += v[d];
|
||||
v[b] = leftRotate7(v[b] ^ v[c]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,531 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Southern Storm Software, Pty Ltd.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included
|
||||
* in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.futo.platformplayer.noise.crypto;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Implementation of the Curve25519 elliptic curve algorithm.
|
||||
*
|
||||
* This implementation is based on that from arduinolibs:
|
||||
* https://github.com/rweather/arduinolibs
|
||||
*
|
||||
* Differences in this version are due to using 26-bit limbs for the
|
||||
* representation instead of the 8/16/32-bit limbs in the original.
|
||||
*
|
||||
* References: http://cr.yp.to/ecdh.html, RFC 7748
|
||||
*/
|
||||
public final class Curve25519 {
|
||||
|
||||
// Numbers modulo 2^255 - 19 are broken up into ten 26-bit words.
|
||||
private static final int NUM_LIMBS_255BIT = 10;
|
||||
private static final int NUM_LIMBS_510BIT = 20;
|
||||
private int[] x_1;
|
||||
private int[] x_2;
|
||||
private int[] x_3;
|
||||
private int[] z_2;
|
||||
private int[] z_3;
|
||||
private int[] A;
|
||||
private int[] B;
|
||||
private int[] C;
|
||||
private int[] D;
|
||||
private int[] E;
|
||||
private int[] AA;
|
||||
private int[] BB;
|
||||
private int[] DA;
|
||||
private int[] CB;
|
||||
private long[] t1;
|
||||
private int[] t2;
|
||||
|
||||
/**
|
||||
* Constructs the temporary state holder for Curve25519 evaluation.
|
||||
*/
|
||||
private Curve25519()
|
||||
{
|
||||
// Allocate memory for all of the temporary variables we will need.
|
||||
x_1 = new int [NUM_LIMBS_255BIT];
|
||||
x_2 = new int [NUM_LIMBS_255BIT];
|
||||
x_3 = new int [NUM_LIMBS_255BIT];
|
||||
z_2 = new int [NUM_LIMBS_255BIT];
|
||||
z_3 = new int [NUM_LIMBS_255BIT];
|
||||
A = new int [NUM_LIMBS_255BIT];
|
||||
B = new int [NUM_LIMBS_255BIT];
|
||||
C = new int [NUM_LIMBS_255BIT];
|
||||
D = new int [NUM_LIMBS_255BIT];
|
||||
E = new int [NUM_LIMBS_255BIT];
|
||||
AA = new int [NUM_LIMBS_255BIT];
|
||||
BB = new int [NUM_LIMBS_255BIT];
|
||||
DA = new int [NUM_LIMBS_255BIT];
|
||||
CB = new int [NUM_LIMBS_255BIT];
|
||||
t1 = new long [NUM_LIMBS_510BIT];
|
||||
t2 = new int [NUM_LIMBS_510BIT];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Destroy all sensitive data in this object.
|
||||
*/
|
||||
private void destroy() {
|
||||
// Destroy all temporary variables.
|
||||
Arrays.fill(x_1, 0);
|
||||
Arrays.fill(x_2, 0);
|
||||
Arrays.fill(x_3, 0);
|
||||
Arrays.fill(z_2, 0);
|
||||
Arrays.fill(z_3, 0);
|
||||
Arrays.fill(A, 0);
|
||||
Arrays.fill(B, 0);
|
||||
Arrays.fill(C, 0);
|
||||
Arrays.fill(D, 0);
|
||||
Arrays.fill(E, 0);
|
||||
Arrays.fill(AA, 0);
|
||||
Arrays.fill(BB, 0);
|
||||
Arrays.fill(DA, 0);
|
||||
Arrays.fill(CB, 0);
|
||||
Arrays.fill(t1, 0L);
|
||||
Arrays.fill(t2, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduces a number modulo 2^255 - 19 where it is known that the
|
||||
* number can be reduced with only 1 trial subtraction.
|
||||
*
|
||||
* @param x The number to reduce, and the result.
|
||||
*/
|
||||
private void reduceQuick(int[] x)
|
||||
{
|
||||
int index, carry;
|
||||
|
||||
// Perform a trial subtraction of (2^255 - 19) from "x" which is
|
||||
// equivalent to adding 19 and subtracting 2^255. We add 19 here;
|
||||
// the subtraction of 2^255 occurs in the next step.
|
||||
carry = 19;
|
||||
for (index = 0; index < NUM_LIMBS_255BIT; ++index) {
|
||||
carry += x[index];
|
||||
t2[index] = carry & 0x03FFFFFF;
|
||||
carry >>= 26;
|
||||
}
|
||||
|
||||
// If there was a borrow, then the original "x" is the correct answer.
|
||||
// If there was no borrow, then "t2" is the correct answer. Select the
|
||||
// correct answer but do it in a way that instruction timing will not
|
||||
// reveal which value was selected. Borrow will occur if bit 21 of
|
||||
// "t2" is zero. Turn the bit into a selection mask.
|
||||
int mask = -((t2[NUM_LIMBS_255BIT - 1] >> 21) & 0x01);
|
||||
int nmask = ~mask;
|
||||
t2[NUM_LIMBS_255BIT - 1] &= 0x001FFFFF;
|
||||
for (index = 0; index < NUM_LIMBS_255BIT; ++index)
|
||||
x[index] = (x[index] & nmask) | (t2[index] & mask);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduce a number modulo 2^255 - 19.
|
||||
*
|
||||
* @param result The result.
|
||||
* @param x The value to be reduced. This array will be
|
||||
* modified during the reduction.
|
||||
* @param size The number of limbs in the high order half of x.
|
||||
*/
|
||||
private void reduce(int[] result, int[] x, int size)
|
||||
{
|
||||
int index, limb, carry;
|
||||
|
||||
// Calculate (x mod 2^255) + ((x / 2^255) * 19) which will
|
||||
// either produce the answer we want or it will produce a
|
||||
// value of the form "answer + j * (2^255 - 19)". There are
|
||||
// 5 left-over bits in the top-most limb of the bottom half.
|
||||
carry = 0;
|
||||
limb = x[NUM_LIMBS_255BIT - 1] >> 21;
|
||||
x[NUM_LIMBS_255BIT - 1] &= 0x001FFFFF;
|
||||
for (index = 0; index < size; ++index) {
|
||||
limb += x[NUM_LIMBS_255BIT + index] << 5;
|
||||
carry += (limb & 0x03FFFFFF) * 19 + x[index];
|
||||
x[index] = carry & 0x03FFFFFF;
|
||||
limb >>= 26;
|
||||
carry >>= 26;
|
||||
}
|
||||
if (size < NUM_LIMBS_255BIT) {
|
||||
// The high order half of the number is short; e.g. for mulA24().
|
||||
// Propagate the carry through the rest of the low order part.
|
||||
for (index = size; index < NUM_LIMBS_255BIT; ++index) {
|
||||
carry += x[index];
|
||||
x[index] = carry & 0x03FFFFFF;
|
||||
carry >>= 26;
|
||||
}
|
||||
}
|
||||
|
||||
// The "j" value may still be too large due to the final carry-out.
|
||||
// We must repeat the reduction. If we already have the answer,
|
||||
// then this won't do any harm but we must still do the calculation
|
||||
// to preserve the overall timing. The "j" value will be between
|
||||
// 0 and 19, which means that the carry we care about is in the
|
||||
// top 5 bits of the highest limb of the bottom half.
|
||||
carry = (x[NUM_LIMBS_255BIT - 1] >> 21) * 19;
|
||||
x[NUM_LIMBS_255BIT - 1] &= 0x001FFFFF;
|
||||
for (index = 0; index < NUM_LIMBS_255BIT; ++index) {
|
||||
carry += x[index];
|
||||
result[index] = carry & 0x03FFFFFF;
|
||||
carry >>= 26;
|
||||
}
|
||||
|
||||
// At this point "x" will either be the answer or it will be the
|
||||
// answer plus (2^255 - 19). Perform a trial subtraction to
|
||||
// complete the reduction process.
|
||||
reduceQuick(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Multiplies two numbers modulo 2^255 - 19.
|
||||
*
|
||||
* @param result The result.
|
||||
* @param x The first number to multiply.
|
||||
* @param y The second number to multiply.
|
||||
*/
|
||||
private void mul(int[] result, int[] x, int[] y)
|
||||
{
|
||||
int i, j;
|
||||
|
||||
// Multiply the two numbers to create the intermediate result.
|
||||
long v = x[0];
|
||||
for (i = 0; i < NUM_LIMBS_255BIT; ++i) {
|
||||
t1[i] = v * y[i];
|
||||
}
|
||||
for (i = 1; i < NUM_LIMBS_255BIT; ++i) {
|
||||
v = x[i];
|
||||
for (j = 0; j < (NUM_LIMBS_255BIT - 1); ++j) {
|
||||
t1[i + j] += v * y[j];
|
||||
}
|
||||
t1[i + NUM_LIMBS_255BIT - 1] = v * y[NUM_LIMBS_255BIT - 1];
|
||||
}
|
||||
|
||||
// Propagate carries and convert back into 26-bit words.
|
||||
v = t1[0];
|
||||
t2[0] = ((int)v) & 0x03FFFFFF;
|
||||
for (i = 1; i < NUM_LIMBS_510BIT; ++i) {
|
||||
v = (v >> 26) + t1[i];
|
||||
t2[i] = ((int)v) & 0x03FFFFFF;
|
||||
}
|
||||
|
||||
// Reduce the result modulo 2^255 - 19.
|
||||
reduce(result, t2, NUM_LIMBS_255BIT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Squares a number modulo 2^255 - 19.
|
||||
*
|
||||
* @param result The result.
|
||||
* @param x The number to square.
|
||||
*/
|
||||
private void square(int[] result, int[] x)
|
||||
{
|
||||
mul(result, x, x);
|
||||
}
|
||||
|
||||
/**
|
||||
* Multiplies a number by the a24 constant, modulo 2^255 - 19.
|
||||
*
|
||||
* @param result The result.
|
||||
* @param x The number to multiply by a24.
|
||||
*/
|
||||
private void mulA24(int[] result, int[] x)
|
||||
{
|
||||
long a24 = 121665;
|
||||
long carry = 0;
|
||||
int index;
|
||||
for (index = 0; index < NUM_LIMBS_255BIT; ++index) {
|
||||
carry += a24 * x[index];
|
||||
t2[index] = ((int)carry) & 0x03FFFFFF;
|
||||
carry >>= 26;
|
||||
}
|
||||
t2[NUM_LIMBS_255BIT] = ((int)carry) & 0x03FFFFFF;
|
||||
reduce(result, t2, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds two numbers modulo 2^255 - 19.
|
||||
*
|
||||
* @param result The result.
|
||||
* @param x The first number to add.
|
||||
* @param y The second number to add.
|
||||
*/
|
||||
private void add(int[] result, int[] x, int[] y)
|
||||
{
|
||||
int index, carry;
|
||||
carry = x[0] + y[0];
|
||||
result[0] = carry & 0x03FFFFFF;
|
||||
for (index = 1; index < NUM_LIMBS_255BIT; ++index) {
|
||||
carry = (carry >> 26) + x[index] + y[index];
|
||||
result[index] = carry & 0x03FFFFFF;
|
||||
}
|
||||
reduceQuick(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Subtracts two numbers modulo 2^255 - 19.
|
||||
*
|
||||
* @param result The result.
|
||||
* @param x The first number to subtract.
|
||||
* @param y The second number to subtract.
|
||||
*/
|
||||
private void sub(int[] result, int[] x, int[] y)
|
||||
{
|
||||
int index, borrow;
|
||||
|
||||
// Subtract y from x to generate the intermediate result.
|
||||
borrow = 0;
|
||||
for (index = 0; index < NUM_LIMBS_255BIT; ++index) {
|
||||
borrow = x[index] - y[index] - ((borrow >> 26) & 0x01);
|
||||
result[index] = borrow & 0x03FFFFFF;
|
||||
}
|
||||
|
||||
// If we had a borrow, then the result has gone negative and we
|
||||
// have to add 2^255 - 19 to the result to make it positive again.
|
||||
// The top bits of "borrow" will be all 1's if there is a borrow
|
||||
// or it will be all 0's if there was no borrow. Easiest is to
|
||||
// conditionally subtract 19 and then mask off the high bits.
|
||||
borrow = result[0] - ((-((borrow >> 26) & 0x01)) & 19);
|
||||
result[0] = borrow & 0x03FFFFFF;
|
||||
for (index = 1; index < NUM_LIMBS_255BIT; ++index) {
|
||||
borrow = result[index] - ((borrow >> 26) & 0x01);
|
||||
result[index] = borrow & 0x03FFFFFF;
|
||||
}
|
||||
result[NUM_LIMBS_255BIT - 1] &= 0x001FFFFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Conditional swap of two values.
|
||||
*
|
||||
* @param select Set to 1 to swap, 0 to leave as-is.
|
||||
* @param x The first value.
|
||||
* @param y The second value.
|
||||
*/
|
||||
private static void cswap(int select, int[] x, int[] y)
|
||||
{
|
||||
int dummy;
|
||||
select = -select;
|
||||
for (int index = 0; index < NUM_LIMBS_255BIT; ++index) {
|
||||
dummy = select & (x[index] ^ y[index]);
|
||||
x[index] ^= dummy;
|
||||
y[index] ^= dummy;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Raise x to the power of (2^250 - 1).
|
||||
*
|
||||
* @param result The result. Must not overlap with x.
|
||||
* @param x The argument.
|
||||
*/
|
||||
private void pow250(int[] result, int[] x)
|
||||
{
|
||||
int i, j;
|
||||
|
||||
// The big-endian hexadecimal expansion of (2^250 - 1) is:
|
||||
// 03FFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF
|
||||
//
|
||||
// The naive implementation needs to do 2 multiplications per 1 bit and
|
||||
// 1 multiplication per 0 bit. We can improve upon this by creating a
|
||||
// pattern 0000000001 ... 0000000001. If we square and multiply the
|
||||
// pattern by itself we can turn the pattern into the partial results
|
||||
// 0000000011 ... 0000000011, 0000000111 ... 0000000111, etc.
|
||||
// This averages out to about 1.1 multiplications per 1 bit instead of 2.
|
||||
|
||||
// Build a pattern of 250 bits in length of repeated copies of 0000000001.
|
||||
square(A, x);
|
||||
for (j = 0; j < 9; ++j)
|
||||
square(A, A);
|
||||
mul(result, A, x);
|
||||
for (i = 0; i < 23; ++i) {
|
||||
for (j = 0; j < 10; ++j)
|
||||
square(A, A);
|
||||
mul(result, result, A);
|
||||
}
|
||||
|
||||
// Multiply bit-shifted versions of the 0000000001 pattern into
|
||||
// the result to "fill in" the gaps in the pattern.
|
||||
square(A, result);
|
||||
mul(result, result, A);
|
||||
for (j = 0; j < 8; ++j) {
|
||||
square(A, A);
|
||||
mul(result, result, A);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the reciprocal of a number modulo 2^255 - 19.
|
||||
*
|
||||
* @param result The result. Must not overlap with x.
|
||||
* @param x The argument.
|
||||
*/
|
||||
private void recip(int[] result, int[] x)
|
||||
{
|
||||
// The reciprocal is the same as x ^ (p - 2) where p = 2^255 - 19.
|
||||
// The big-endian hexadecimal expansion of (p - 2) is:
|
||||
// 7FFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFEB
|
||||
// Start with the 250 upper bits of the expansion of (p - 2).
|
||||
pow250(result, x);
|
||||
|
||||
// Deal with the 5 lowest bits of (p - 2), 01011, from highest to lowest.
|
||||
square(result, result);
|
||||
square(result, result);
|
||||
mul(result, result, x);
|
||||
square(result, result);
|
||||
square(result, result);
|
||||
mul(result, result, x);
|
||||
square(result, result);
|
||||
mul(result, result, x);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates the curve for every bit in a secret key.
|
||||
*
|
||||
* @param s The 32-byte secret key.
|
||||
*/
|
||||
private void evalCurve(byte[] s)
|
||||
{
|
||||
int sposn = 31;
|
||||
int sbit = 6;
|
||||
int svalue = s[sposn] | 0x40;
|
||||
int swap = 0;
|
||||
int select;
|
||||
|
||||
// Iterate over all 255 bits of "s" from the highest to the lowest.
|
||||
// We ignore the high bit of the 256-bit representation of "s".
|
||||
for (;;) {
|
||||
// Conditional swaps on entry to this bit but only if we
|
||||
// didn't swap on the previous bit.
|
||||
select = (svalue >> sbit) & 0x01;
|
||||
swap ^= select;
|
||||
cswap(swap, x_2, x_3);
|
||||
cswap(swap, z_2, z_3);
|
||||
swap = select;
|
||||
|
||||
// Evaluate the curve.
|
||||
add(A, x_2, z_2); // A = x_2 + z_2
|
||||
square(AA, A); // AA = A^2
|
||||
sub(B, x_2, z_2); // B = x_2 - z_2
|
||||
square(BB, B); // BB = B^2
|
||||
sub(E, AA, BB); // E = AA - BB
|
||||
add(C, x_3, z_3); // C = x_3 + z_3
|
||||
sub(D, x_3, z_3); // D = x_3 - z_3
|
||||
mul(DA, D, A); // DA = D * A
|
||||
mul(CB, C, B); // CB = C * B
|
||||
add(x_3, DA, CB); // x_3 = (DA + CB)^2
|
||||
square(x_3, x_3);
|
||||
sub(z_3, DA, CB); // z_3 = x_1 * (DA - CB)^2
|
||||
square(z_3, z_3);
|
||||
mul(z_3, z_3, x_1);
|
||||
mul(x_2, AA, BB); // x_2 = AA * BB
|
||||
mulA24(z_2, E); // z_2 = E * (AA + a24 * E)
|
||||
add(z_2, z_2, AA);
|
||||
mul(z_2, z_2, E);
|
||||
|
||||
// Move onto the next lower bit of "s".
|
||||
if (sbit > 0) {
|
||||
--sbit;
|
||||
} else if (sposn == 0) {
|
||||
break;
|
||||
} else if (sposn == 1) {
|
||||
--sposn;
|
||||
svalue = s[sposn] & 0xF8;
|
||||
sbit = 7;
|
||||
} else {
|
||||
--sposn;
|
||||
svalue = s[sposn];
|
||||
sbit = 7;
|
||||
}
|
||||
}
|
||||
|
||||
// Final conditional swaps.
|
||||
cswap(swap, x_2, x_3);
|
||||
cswap(swap, z_2, z_3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates the Curve25519 curve.
|
||||
*
|
||||
* @param result Buffer to place the result of the evaluation into.
|
||||
* @param offset Offset into the result buffer.
|
||||
* @param privateKey The private key to use in the evaluation.
|
||||
* @param publicKey The public key to use in the evaluation, or null
|
||||
* if the base point of the curve should be used.
|
||||
*/
|
||||
public static void eval(byte[] result, int offset, byte[] privateKey, byte[] publicKey)
|
||||
{
|
||||
Curve25519 state = new Curve25519();
|
||||
try {
|
||||
// Unpack the public key value. If null, use 9 as the base point.
|
||||
Arrays.fill(state.x_1, 0);
|
||||
if (publicKey != null) {
|
||||
// Convert the input value from little-endian into 26-bit limbs.
|
||||
for (int index = 0; index < 32; ++index) {
|
||||
int bit = (index * 8) % 26;
|
||||
int word = (index * 8) / 26;
|
||||
int value = publicKey[index] & 0xFF;
|
||||
if (bit <= (26 - 8)) {
|
||||
state.x_1[word] |= value << bit;
|
||||
} else {
|
||||
state.x_1[word] |= value << bit;
|
||||
state.x_1[word] &= 0x03FFFFFF;
|
||||
state.x_1[word + 1] |= value >> (26 - bit);
|
||||
}
|
||||
}
|
||||
|
||||
// Just in case, we reduce the number modulo 2^255 - 19 to
|
||||
// make sure that it is in range of the field before we start.
|
||||
// This eliminates values between 2^255 - 19 and 2^256 - 1.
|
||||
state.reduceQuick(state.x_1);
|
||||
state.reduceQuick(state.x_1);
|
||||
} else {
|
||||
state.x_1[0] = 9;
|
||||
}
|
||||
|
||||
// Initialize the other temporary variables.
|
||||
Arrays.fill(state.x_2, 0); // x_2 = 1
|
||||
state.x_2[0] = 1;
|
||||
Arrays.fill(state.z_2, 0); // z_2 = 0
|
||||
System.arraycopy(state.x_1, 0, state.x_3, 0, state.x_1.length); // x_3 = x_1
|
||||
Arrays.fill(state.z_3, 0); // z_3 = 1
|
||||
state.z_3[0] = 1;
|
||||
|
||||
// Evaluate the curve for every bit of the private key.
|
||||
state.evalCurve(privateKey);
|
||||
|
||||
// Compute x_2 * (z_2 ^ (p - 2)) where p = 2^255 - 19.
|
||||
state.recip(state.z_3, state.z_2);
|
||||
state.mul(state.x_2, state.x_2, state.z_3);
|
||||
|
||||
// Convert x_2 into little-endian in the result buffer.
|
||||
for (int index = 0; index < 32; ++index) {
|
||||
int bit = (index * 8) % 26;
|
||||
int word = (index * 8) / 26;
|
||||
if (bit <= (26 - 8))
|
||||
result[offset + index] = (byte)(state.x_2[word] >> bit);
|
||||
else
|
||||
result[offset + index] = (byte)((state.x_2[word] >> bit) | (state.x_2[word + 1] << (26 - bit)));
|
||||
}
|
||||
} finally {
|
||||
// Clean up all temporary state before we exit.
|
||||
state.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,622 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Southern Storm Software, Pty Ltd.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included
|
||||
* in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
|
||||
Portions of this code were extracted from the p448/arch_32 field
|
||||
arithmetic implementation in Ed448-Goldilocks and converted from
|
||||
C into Java. The LICENSE.txt file for the imported code follows:
|
||||
|
||||
----
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2011 Stanford University.
|
||||
Copyright (c) 2014 Cryptography Research, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
----
|
||||
|
||||
*/
|
||||
|
||||
package com.futo.platformplayer.noise.crypto;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Implementation of the Curve448 elliptic curve algorithm.
|
||||
*
|
||||
* Reference: RFC 7748
|
||||
*/
|
||||
public final class Curve448 {
|
||||
|
||||
// Numbers modulo 2^448 - 2^224 - 1 are broken up into sixteen 28-bit words.
|
||||
private int[] x_1;
|
||||
private int[] x_2;
|
||||
private int[] x_3;
|
||||
private int[] z_2;
|
||||
private int[] z_3;
|
||||
private int[] A;
|
||||
private int[] B;
|
||||
private int[] C;
|
||||
private int[] D;
|
||||
private int[] E;
|
||||
private int[] AA;
|
||||
private int[] BB;
|
||||
private int[] DA;
|
||||
private int[] CB;
|
||||
private int[] aa;
|
||||
private int[] bb;
|
||||
|
||||
/**
|
||||
* Constructs the temporary state holder for Curve448 evaluation.
|
||||
*/
|
||||
private Curve448()
|
||||
{
|
||||
// Allocate memory for all of the temporary variables we will need.
|
||||
x_1 = new int [16];
|
||||
x_2 = new int [16];
|
||||
x_3 = new int [16];
|
||||
z_2 = new int [16];
|
||||
z_3 = new int [16];
|
||||
A = new int [16];
|
||||
B = new int [16];
|
||||
C = new int [16];
|
||||
D = new int [16];
|
||||
E = new int [16];
|
||||
AA = new int [16];
|
||||
BB = new int [16];
|
||||
DA = new int [16];
|
||||
CB = new int [16];
|
||||
aa = new int [8];
|
||||
bb = new int [8];
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy all sensitive data in this object.
|
||||
*/
|
||||
private void destroy() {
|
||||
// Destroy all temporary variables.
|
||||
Arrays.fill(x_1, 0);
|
||||
Arrays.fill(x_2, 0);
|
||||
Arrays.fill(x_3, 0);
|
||||
Arrays.fill(z_2, 0);
|
||||
Arrays.fill(z_3, 0);
|
||||
Arrays.fill(A, 0);
|
||||
Arrays.fill(B, 0);
|
||||
Arrays.fill(C, 0);
|
||||
Arrays.fill(D, 0);
|
||||
Arrays.fill(E, 0);
|
||||
Arrays.fill(AA, 0);
|
||||
Arrays.fill(BB, 0);
|
||||
Arrays.fill(DA, 0);
|
||||
Arrays.fill(CB, 0);
|
||||
Arrays.fill(aa, 0);
|
||||
Arrays.fill(bb, 0);
|
||||
}
|
||||
|
||||
/* Beginning of code imported from Ed448-Goldilocks */
|
||||
|
||||
private static long widemul_32(int a, int b)
|
||||
{
|
||||
return ((long)a) * b;
|
||||
}
|
||||
|
||||
// p448_mul()
|
||||
private void mul(int[] c, int[] a, int[] b)
|
||||
{
|
||||
long accum0 = 0, accum1 = 0, accum2 = 0;
|
||||
int mask = (1<<28) - 1;
|
||||
|
||||
int i,j;
|
||||
for (i=0; i<8; i++) {
|
||||
aa[i] = a[i] + a[i+8];
|
||||
bb[i] = b[i] + b[i+8];
|
||||
}
|
||||
|
||||
for (j=0; j<8; j++) {
|
||||
accum2 = 0;
|
||||
|
||||
for (i=0; i<=j; i++) {
|
||||
accum2 += widemul_32(a[j-i],b[i]);
|
||||
accum1 += widemul_32(aa[j-i],bb[i]);
|
||||
accum0 += widemul_32(a[8+j-i], b[8+i]);
|
||||
}
|
||||
|
||||
accum1 -= accum2;
|
||||
accum0 += accum2;
|
||||
accum2 = 0;
|
||||
|
||||
for (; i<8; i++) {
|
||||
accum0 -= widemul_32(a[8+j-i], b[i]);
|
||||
accum2 += widemul_32(aa[8+j-i], bb[i]);
|
||||
accum1 += widemul_32(a[16+j-i], b[8+i]);
|
||||
}
|
||||
|
||||
accum1 += accum2;
|
||||
accum0 += accum2;
|
||||
|
||||
c[j] = ((int)(accum0)) & mask;
|
||||
c[j+8] = ((int)(accum1)) & mask;
|
||||
|
||||
accum0 >>>= 28;
|
||||
accum1 >>>= 28;
|
||||
}
|
||||
|
||||
accum0 += accum1;
|
||||
accum0 += c[8];
|
||||
accum1 += c[0];
|
||||
c[8] = ((int)(accum0)) & mask;
|
||||
c[0] = ((int)(accum1)) & mask;
|
||||
|
||||
accum0 >>>= 28;
|
||||
accum1 >>>= 28;
|
||||
c[9] += ((int)(accum0));
|
||||
c[1] += ((int)(accum1));
|
||||
}
|
||||
|
||||
// p448_mulw()
|
||||
private static void mulw(int[] c, int[] a, long b)
|
||||
{
|
||||
int bhi = (int)(b>>28), blo = ((int)b) & ((1<<28)-1);
|
||||
|
||||
long accum0, accum8;
|
||||
int mask = (1<<28) - 1;
|
||||
|
||||
int i;
|
||||
|
||||
accum0 = widemul_32(blo, a[0]);
|
||||
accum8 = widemul_32(blo, a[8]);
|
||||
accum0 += widemul_32(bhi, a[15]);
|
||||
accum8 += widemul_32(bhi, a[15] + a[7]);
|
||||
|
||||
c[0] = ((int)accum0) & mask; accum0 >>>= 28;
|
||||
c[8] = ((int)accum8) & mask; accum8 >>>= 28;
|
||||
|
||||
for (i=1; i<8; i++) {
|
||||
accum0 += widemul_32(blo, a[i]);
|
||||
accum8 += widemul_32(blo, a[i+8]);
|
||||
|
||||
accum0 += widemul_32(bhi, a[i-1]);
|
||||
accum8 += widemul_32(bhi, a[i+7]);
|
||||
|
||||
c[i] = ((int)accum0) & mask; accum0 >>>= 28;
|
||||
c[i+8] = ((int)accum8) & mask; accum8 >>>= 28;
|
||||
}
|
||||
|
||||
accum0 += accum8 + c[8];
|
||||
c[8] = ((int)accum0) & mask;
|
||||
c[9] += accum0 >>> 28;
|
||||
|
||||
accum8 += c[0];
|
||||
c[0] = ((int)accum8) & mask;
|
||||
c[1] += accum8 >>> 28;
|
||||
}
|
||||
|
||||
// p448_weak_reduce
|
||||
private static void weak_reduce(int[] a)
|
||||
{
|
||||
int mask = (1<<28) - 1;
|
||||
int tmp = a[15] >>> 28;
|
||||
int i;
|
||||
a[8] += tmp;
|
||||
for (i=15; i>0; i--) {
|
||||
a[i] = (a[i] & mask) + (a[i-1]>>>28);
|
||||
}
|
||||
a[0] = (a[0] & mask) + tmp;
|
||||
}
|
||||
|
||||
// p448_strong_reduce
|
||||
private static void strong_reduce(int[] a)
|
||||
{
|
||||
int mask = (1<<28) - 1;
|
||||
|
||||
/* first, clear high */
|
||||
a[8] += a[15]>>>28;
|
||||
a[0] += a[15]>>>28;
|
||||
a[15] &= mask;
|
||||
|
||||
/* now the total is less than 2^448 - 2^(448-56) + 2^(448-56+8) < 2p */
|
||||
|
||||
/* compute total_value - p. No need to reduce mod p. */
|
||||
|
||||
long scarry = 0;
|
||||
int i;
|
||||
for (i=0; i<16; i++) {
|
||||
scarry = scarry + (a[i] & 0xFFFFFFFFL) - ((i==8)?mask-1:mask);
|
||||
a[i] = (int)(scarry & mask);
|
||||
scarry >>= 28;
|
||||
}
|
||||
|
||||
/* uncommon case: it was >= p, so now scarry = 0 and this = x
|
||||
* common case: it was < p, so now scarry = -1 and this = x - p + 2^448
|
||||
* so let's add back in p. will carry back off the top for 2^448.
|
||||
*/
|
||||
|
||||
int scarry_mask = (int)(scarry & mask);
|
||||
long carry = 0;
|
||||
|
||||
/* add it back */
|
||||
for (i=0; i<16; i++) {
|
||||
carry = carry + (a[i] & 0xFFFFFFFFL) + ((i==8)?(scarry_mask&~1):scarry_mask);
|
||||
a[i] = (int)(carry & mask);
|
||||
carry >>>= 28;
|
||||
}
|
||||
}
|
||||
|
||||
// field_add()
|
||||
private static void add(int[] out, int[] a, int[] b)
|
||||
{
|
||||
for (int i = 0; i < 16; ++i)
|
||||
out[i] = a[i] + b[i];
|
||||
weak_reduce(out);
|
||||
}
|
||||
|
||||
// field_sub()
|
||||
private static void sub(int[] out, int[] a, int[] b)
|
||||
{
|
||||
int i;
|
||||
|
||||
// p448_sub_RAW(out, a, b)
|
||||
for (i = 0; i < 16; ++i)
|
||||
out[i] = a[i] - b[i];
|
||||
|
||||
// p448_bias(out, 2)
|
||||
int co1 = ((1 << 28) - 1) * 2;
|
||||
int co2 = co1 - 2;
|
||||
for (i = 0; i < 16; ++i) {
|
||||
if (i != 8)
|
||||
out[i] += co1;
|
||||
else
|
||||
out[i] += co2;
|
||||
}
|
||||
|
||||
weak_reduce(out);
|
||||
}
|
||||
|
||||
// p448_serialize()
|
||||
private static void serialize(byte[] serial, int offset, int[] x)
|
||||
{
|
||||
int i,j;
|
||||
for (i=0; i<8; i++) {
|
||||
long limb = x[2*i] + (((long)x[2*i+1])<<28);
|
||||
for (j=0; j<7; j++) {
|
||||
serial[offset+7*i+j] = (byte)limb;
|
||||
limb >>= 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int is_zero(int x)
|
||||
{
|
||||
long xx = x & 0xFFFFFFFFL;
|
||||
xx--;
|
||||
return (int)(xx >> 32);
|
||||
}
|
||||
|
||||
// p448_deserialize()
|
||||
private static int deserialize(int[] x, byte[] serial, int offset)
|
||||
{
|
||||
int i,j;
|
||||
for (i=0; i<8; i++) {
|
||||
long out = 0;
|
||||
for (j=0; j<7; j++) {
|
||||
out |= (serial[offset+7*i+j] & 0xFFL)<<(8*j);
|
||||
}
|
||||
x[2*i] = ((int)out) & ((1<<28)-1);
|
||||
x[2*i+1] = (int)(out >>> 28);
|
||||
}
|
||||
|
||||
/* Check for reduction.
|
||||
*
|
||||
* The idea is to create a variable ge which is all ones (rather, 56 ones)
|
||||
* if and only if the low $i$ words of $x$ are >= those of p.
|
||||
*
|
||||
* Remember p = little_endian(1111,1111,1111,1111,1110,1111,1111,1111)
|
||||
*/
|
||||
int ge = -1, mask = (1<<28)-1;
|
||||
for (i=0; i<8; i++) {
|
||||
ge &= x[i];
|
||||
}
|
||||
|
||||
/* At this point, ge = 1111 iff bottom are all 1111. Now propagate if 1110, or set if 1111 */
|
||||
ge = (ge & (x[8] + 1)) | is_zero(x[8] ^ mask);
|
||||
|
||||
/* Propagate the rest */
|
||||
for (i=9; i<16; i++) {
|
||||
ge &= x[i];
|
||||
}
|
||||
|
||||
return ~is_zero(ge ^ mask);
|
||||
}
|
||||
|
||||
/* End of code imported from Ed448-Goldilocks */
|
||||
|
||||
/**
|
||||
* Squares a number modulo 2^448 - 2^224 - 1.
|
||||
*
|
||||
* @param result The result.
|
||||
* @param x The number to square.
|
||||
*/
|
||||
private void square(int[] result, int[] x)
|
||||
{
|
||||
mul(result, x, x);
|
||||
}
|
||||
|
||||
/**
|
||||
* Conditional swap of two values.
|
||||
*
|
||||
* @param select Set to 1 to swap, 0 to leave as-is.
|
||||
* @param x The first value.
|
||||
* @param y The second value.
|
||||
*/
|
||||
private static void cswap(int select, int[] x, int[] y)
|
||||
{
|
||||
int dummy;
|
||||
select = -select;
|
||||
for (int index = 0; index < 16; ++index) {
|
||||
dummy = select & (x[index] ^ y[index]);
|
||||
x[index] ^= dummy;
|
||||
y[index] ^= dummy;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the reciprocal of a number modulo 2^448 - 2^224 - 1.
|
||||
*
|
||||
* @param result The result. Must not overlap with z_2.
|
||||
* @param z_2 The argument.
|
||||
*/
|
||||
private void recip(int[] result, int[] z_2)
|
||||
{
|
||||
int posn;
|
||||
|
||||
/* Compute z_2 ^ (p - 2)
|
||||
|
||||
The value p - 2 is: FF...FEFF...FD, which from highest to lowest is
|
||||
223 one bits, followed by a zero bit, followed by 222 one bits,
|
||||
followed by another zero bit, and a final one bit.
|
||||
|
||||
The naive implementation that squares for every bit and multiplies
|
||||
for every 1 bit requires 893 multiplications. The following can
|
||||
do the same operation in 483 multiplications. The basic idea is to
|
||||
create bit patterns and then "shift" them into position. We start
|
||||
with a 4 bit pattern 1111, which we can square 4 times to get
|
||||
11110000 and then multiply by the 1111 pattern to get 11111111.
|
||||
We then repeat that to turn 11111111 into 1111111111111111, etc.
|
||||
*/
|
||||
square(B, z_2); /* Set A to a 4 bit pattern */
|
||||
mul(A, B, z_2);
|
||||
square(B, A);
|
||||
mul(A, B, z_2);
|
||||
square(B, A);
|
||||
mul(A, B, z_2);
|
||||
square(B, A); /* Set C to a 6 bit pattern */
|
||||
mul(C, B, z_2);
|
||||
square(B, C);
|
||||
mul(C, B, z_2);
|
||||
square(B, C); /* Set A to a 8 bit pattern */
|
||||
mul(A, B, z_2);
|
||||
square(B, A);
|
||||
mul(A, B, z_2);
|
||||
square(E, A); /* Set E to a 16 bit pattern */
|
||||
square(B, E);
|
||||
for (posn = 1; posn < 4; ++posn) {
|
||||
square(E, B);
|
||||
square(B, E);
|
||||
}
|
||||
mul(E, B, A);
|
||||
square(AA, E); /* Set AA to a 32 bit pattern */
|
||||
square(B, AA);
|
||||
for (posn = 1; posn < 8; ++posn) {
|
||||
square(AA, B);
|
||||
square(B, AA);
|
||||
}
|
||||
mul(AA, B, E);
|
||||
square(BB, AA); /* Set BB to a 64 bit pattern */
|
||||
square(B, BB);
|
||||
for (posn = 1; posn < 16; ++posn) {
|
||||
square(BB, B);
|
||||
square(B, BB);
|
||||
}
|
||||
mul(BB, B, AA);
|
||||
square(DA, BB); /* Set DA to a 128 bit pattern */
|
||||
square(B, DA);
|
||||
for (posn = 1; posn < 32; ++posn) {
|
||||
square(DA, B);
|
||||
square(B, DA);
|
||||
}
|
||||
mul(DA, B, BB);
|
||||
square(CB, DA); /* Set CB to a 192 bit pattern */
|
||||
square(B, CB); /* 192 = 128 + 64 */
|
||||
for (posn = 1; posn < 32; ++posn) {
|
||||
square(CB, B);
|
||||
square(B, CB);
|
||||
}
|
||||
mul(CB, B, BB);
|
||||
square(DA, CB); /* Set DA to a 208 bit pattern */
|
||||
square(B, DA); /* 208 = 128 + 64 + 16 */
|
||||
for (posn = 1; posn < 8; ++posn) {
|
||||
square(DA, B);
|
||||
square(B, DA);
|
||||
}
|
||||
mul(DA, B, E);
|
||||
square(CB, DA); /* Set CB to a 216 bit pattern */
|
||||
square(B, CB); /* 216 = 128 + 64 + 16 + 8 */
|
||||
for (posn = 1; posn < 4; ++posn) {
|
||||
square(CB, B);
|
||||
square(B, CB);
|
||||
}
|
||||
mul(CB, B, A);
|
||||
square(DA, CB); /* Set DA to a 222 bit pattern */
|
||||
square(B, DA); /* 222 = 128 + 64 + 16 + 8 + 6 */
|
||||
for (posn = 1; posn < 3; ++posn) {
|
||||
square(DA, B);
|
||||
square(B, DA);
|
||||
}
|
||||
mul(DA, B, C);
|
||||
square(CB, DA); /* Set CB to a 224 bit pattern */
|
||||
mul(B, CB, z_2); /* CB = DA|1|0 */
|
||||
square(CB, B);
|
||||
square(BB, CB); /* Set BB to a 446 bit pattern */
|
||||
square(B, BB); /* BB = DA|1|0|DA */
|
||||
for (posn = 1; posn < 111; ++posn) {
|
||||
square(BB, B);
|
||||
square(B, BB);
|
||||
}
|
||||
mul(BB, B, DA);
|
||||
square(B, BB); /* Set result to a 448 bit pattern */
|
||||
square(BB, B); /* result = DA|1|0|DA|01 */
|
||||
mul(result, BB, z_2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates the curve for every bit in a secret key.
|
||||
*
|
||||
* @param s The 56-byte secret key.
|
||||
*/
|
||||
private void evalCurve(byte[] s)
|
||||
{
|
||||
int sposn = 55;
|
||||
int sbit = 7;
|
||||
int svalue = s[sposn] | 0x80;
|
||||
int swap = 0;
|
||||
int select;
|
||||
|
||||
// Iterate over all 448 bits of "s" from the highest to the lowest.
|
||||
for (;;) {
|
||||
// Conditional swaps on entry to this bit but only if we
|
||||
// didn't swap on the previous bit.
|
||||
select = (svalue >> sbit) & 0x01;
|
||||
swap ^= select;
|
||||
cswap(swap, x_2, x_3);
|
||||
cswap(swap, z_2, z_3);
|
||||
swap = select;
|
||||
|
||||
// Evaluate the curve.
|
||||
add(A, x_2, z_2); // A = x_2 + z_2
|
||||
square(AA, A); // AA = A^2
|
||||
sub(B, x_2, z_2); // B = x_2 - z_2
|
||||
square(BB, B); // BB = B^2
|
||||
sub(E, AA, BB); // E = AA - BB
|
||||
add(C, x_3, z_3); // C = x_3 + z_3
|
||||
sub(D, x_3, z_3); // D = x_3 - z_3
|
||||
mul(DA, D, A); // DA = D * A
|
||||
mul(CB, C, B); // CB = C * B
|
||||
add(z_2, DA, CB); // x_3 = (DA + CB)^2
|
||||
square(x_3, z_2);
|
||||
sub(z_2, DA, CB); // z_3 = x_1 * (DA - CB)^2
|
||||
square(x_2, z_2);
|
||||
mul(z_3, x_1, x_2);
|
||||
mul(x_2, AA, BB); // x_2 = AA * BB
|
||||
mulw(z_2, E, 39081); // z_2 = E * (AA + a24 * E)
|
||||
add(A, AA, z_2);
|
||||
mul(z_2, E, A);
|
||||
|
||||
// Move onto the next lower bit of "s".
|
||||
if (sbit > 0) {
|
||||
--sbit;
|
||||
} else if (sposn == 0) {
|
||||
break;
|
||||
} else if (sposn == 1) {
|
||||
--sposn;
|
||||
svalue = s[sposn] & 0xFC;
|
||||
sbit = 7;
|
||||
} else {
|
||||
--sposn;
|
||||
svalue = s[sposn];
|
||||
sbit = 7;
|
||||
}
|
||||
}
|
||||
|
||||
// Final conditional swaps.
|
||||
cswap(swap, x_2, x_3);
|
||||
cswap(swap, z_2, z_3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates the Curve448 curve.
|
||||
*
|
||||
* @param result Buffer to place the result of the evaluation into.
|
||||
* @param offset Offset into the result buffer.
|
||||
* @param privateKey The private key to use in the evaluation.
|
||||
* @param publicKey The public key to use in the evaluation, or null
|
||||
* if the base point of the curve should be used.
|
||||
* @return Returns true if the curve evaluation was successful,
|
||||
* false if the publicKey value is out of range.
|
||||
*/
|
||||
public static boolean eval(byte[] result, int offset, byte[] privateKey, byte[] publicKey)
|
||||
{
|
||||
Curve448 state = new Curve448();
|
||||
int success = -1;
|
||||
try {
|
||||
// Unpack the public key value. If null, use 5 as the base point.
|
||||
Arrays.fill(state.x_1, 0);
|
||||
if (publicKey != null) {
|
||||
// Convert the input value from little-endian into 28-bit limbs.
|
||||
// It is possible that the public key is out of range. If so,
|
||||
// delay reporting that state until the function completes.
|
||||
success = deserialize(state.x_1, publicKey, 0);
|
||||
} else {
|
||||
state.x_1[0] = 5;
|
||||
}
|
||||
|
||||
// Initialize the other temporary variables.
|
||||
Arrays.fill(state.x_2, 0); // x_2 = 1
|
||||
state.x_2[0] = 1;
|
||||
Arrays.fill(state.z_2, 0); // z_2 = 0
|
||||
System.arraycopy(state.x_1, 0, state.x_3, 0, state.x_1.length); // x_3 = x_1
|
||||
Arrays.fill(state.z_3, 0); // z_3 = 1
|
||||
state.z_3[0] = 1;
|
||||
|
||||
// Evaluate the curve for every bit of the private key.
|
||||
state.evalCurve(privateKey);
|
||||
|
||||
// Compute x_2 * (z_2 ^ (p - 2)) where p = 2^448 - 2^224 - 1.
|
||||
state.recip(state.z_3, state.z_2);
|
||||
state.mul(state.x_1, state.x_2, state.z_3);
|
||||
|
||||
// Convert x_2 into little-endian in the result buffer.
|
||||
strong_reduce(state.x_1);
|
||||
serialize(result, offset, state.x_1);
|
||||
} finally {
|
||||
// Clean up all temporary state before we exit.
|
||||
state.destroy();
|
||||
}
|
||||
return (success & 0x01) != 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Southern Storm Software, Pty Ltd.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included
|
||||
* in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.futo.platformplayer.noise.crypto;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import com.futo.platformplayer.noise.protocol.Destroyable;
|
||||
|
||||
/**
|
||||
* Implementation of the GHASH primitive for GCM.
|
||||
*/
|
||||
public final class GHASH implements Destroyable {
|
||||
|
||||
private long[] H;
|
||||
private byte[] Y;
|
||||
int posn;
|
||||
|
||||
/**
|
||||
* Constructs a new GHASH object.
|
||||
*/
|
||||
public GHASH()
|
||||
{
|
||||
H = new long [2];
|
||||
Y = new byte [16];
|
||||
posn = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets this GHASH object with a new key.
|
||||
*
|
||||
* @param key The key, which must contain at least 16 bytes.
|
||||
* @param offset The offset of the first key byte.
|
||||
*/
|
||||
public void reset(byte[] key, int offset)
|
||||
{
|
||||
H[0] = readBigEndian(key, offset);
|
||||
H[1] = readBigEndian(key, offset + 8);
|
||||
Arrays.fill(Y, (byte)0);
|
||||
posn = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the GHASH object but retains the previous key.
|
||||
*/
|
||||
public void reset()
|
||||
{
|
||||
Arrays.fill(Y, (byte)0);
|
||||
posn = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates this GHASH object with more data.
|
||||
*
|
||||
* @param data Buffer containing the data.
|
||||
* @param offset Offset of the first data byte in the buffer.
|
||||
* @param length The number of bytes from the buffer to hash.
|
||||
*/
|
||||
public void update(byte[] data, int offset, int length)
|
||||
{
|
||||
while (length > 0) {
|
||||
int size = 16 - posn;
|
||||
if (size > length)
|
||||
size = length;
|
||||
for (int index = 0; index < size; ++index)
|
||||
Y[posn + index] ^= data[offset + index];
|
||||
posn += size;
|
||||
length -= size;
|
||||
offset += size;
|
||||
if (posn == 16) {
|
||||
GF128_mul(Y, H);
|
||||
posn = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finishes the GHASH process and returns the tag.
|
||||
*
|
||||
* @param tag Buffer to receive the tag.
|
||||
* @param offset Offset of the first byte of the tag.
|
||||
* @param length The length of the tag, which must be less
|
||||
* than or equal to 16.
|
||||
*/
|
||||
public void finish(byte[] tag, int offset, int length)
|
||||
{
|
||||
pad();
|
||||
System.arraycopy(Y, 0, tag, offset, length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pads the input to a 16-byte boundary.
|
||||
*/
|
||||
public void pad()
|
||||
{
|
||||
if (posn != 0) {
|
||||
// Padding involves XOR'ing the rest of state->Y with zeroes,
|
||||
// which does nothing. Immediately process the next chunk.
|
||||
GF128_mul(Y, H);
|
||||
posn = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pads the input to a 16-byte boundary and then adds a block
|
||||
* containing the AD and data lengths.
|
||||
*
|
||||
* @param adLen Length of the associated data in bytes.
|
||||
* @param dataLen Length of the data in bytes.
|
||||
*/
|
||||
public void pad(long adLen, long dataLen)
|
||||
{
|
||||
byte[] temp = new byte [16];
|
||||
try {
|
||||
pad();
|
||||
writeBigEndian(temp, 0, adLen * 8);
|
||||
writeBigEndian(temp, 8, dataLen * 8);
|
||||
update(temp, 0, 16);
|
||||
} finally {
|
||||
Arrays.fill(temp, (byte)0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
Arrays.fill(H, 0L);
|
||||
Arrays.fill(Y, (byte)0);
|
||||
}
|
||||
|
||||
private static long readBigEndian(byte[] buf, int offset)
|
||||
{
|
||||
return ((buf[offset] & 0xFFL) << 56) |
|
||||
((buf[offset + 1] & 0xFFL) << 48) |
|
||||
((buf[offset + 2] & 0xFFL) << 40) |
|
||||
((buf[offset + 3] & 0xFFL) << 32) |
|
||||
((buf[offset + 4] & 0xFFL) << 24) |
|
||||
((buf[offset + 5] & 0xFFL) << 16) |
|
||||
((buf[offset + 6] & 0xFFL) << 8) |
|
||||
(buf[offset + 7] & 0xFFL);
|
||||
}
|
||||
|
||||
private static void writeBigEndian(byte[] buf, int offset, long value)
|
||||
{
|
||||
buf[offset] = (byte)(value >> 56);
|
||||
buf[offset + 1] = (byte)(value >> 48);
|
||||
buf[offset + 2] = (byte)(value >> 40);
|
||||
buf[offset + 3] = (byte)(value >> 32);
|
||||
buf[offset + 4] = (byte)(value >> 24);
|
||||
buf[offset + 5] = (byte)(value >> 16);
|
||||
buf[offset + 6] = (byte)(value >> 8);
|
||||
buf[offset + 7] = (byte)value;
|
||||
}
|
||||
|
||||
private static void GF128_mul(byte[] Y, long[] H)
|
||||
{
|
||||
long Z0 = 0; // Z = 0
|
||||
long Z1 = 0;
|
||||
long V0 = H[0]; // V = H
|
||||
long V1 = H[1];
|
||||
|
||||
// Multiply Z by V for the set bits in Y, starting at the top.
|
||||
// This is a very simple bit by bit version that may not be very
|
||||
// fast but it should be resistant to cache timing attacks.
|
||||
for (int posn = 0; posn < 16; ++posn) {
|
||||
int value = Y[posn] & 0xFF;
|
||||
for (int bit = 7; bit >= 0; --bit) {
|
||||
// Extract the high bit of "value" and turn it into a mask.
|
||||
long mask = -((long)((value >> bit) & 0x01));
|
||||
|
||||
// XOR V with Z if the bit is 1.
|
||||
Z0 ^= (V0 & mask);
|
||||
Z1 ^= (V1 & mask);
|
||||
|
||||
// Rotate V right by 1 bit.
|
||||
mask = ((~(V1 & 0x01)) + 1) & 0xE100000000000000L;
|
||||
V1 = (V1 >>> 1) | (V0 << 63);
|
||||
V0 = (V0 >>> 1) ^ mask;
|
||||
}
|
||||
}
|
||||
|
||||
// We have finished the block so copy Z into Y and byte-swap.
|
||||
writeBigEndian(Y, 0, Z0);
|
||||
writeBigEndian(Y, 8, Z1);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,982 @@
|
||||
/*
|
||||
* Based on the public domain C reference code for New Hope.
|
||||
* This Java version is also placed into the public domain.
|
||||
*
|
||||
* Original authors: Erdem Alkim, Léo Ducas, Thomas Pöppelmann, Peter Schwabe
|
||||
* Java port: Rhys Weatherley
|
||||
*/
|
||||
|
||||
package com.futo.platformplayer.noise.crypto;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* New Hope key exchange algorithm, "torref" variant.
|
||||
*
|
||||
* This version of New Hope implements the alternative constant-time
|
||||
* method for generating the public "a" value for anonymity networks
|
||||
* like Tor. It is not binary-compatible with the standard New Hope
|
||||
* implementation in the NewHope class.
|
||||
*
|
||||
* Reference: https://cryptojedi.org/papers/newhope-20160803.pdf
|
||||
*
|
||||
* @see NewHope
|
||||
*/
|
||||
public class NewHopeTor extends NewHope {
|
||||
|
||||
public NewHopeTor() {}
|
||||
|
||||
@Override
|
||||
protected void uniform(char[] coeffs, byte[] seed)
|
||||
{
|
||||
long[] state = new long [25];
|
||||
int nblocks=16;
|
||||
byte[] buf = new byte [SHAKE128_RATE*nblocks];
|
||||
char[] x = new char [buf.length / 2];
|
||||
|
||||
try {
|
||||
shake128_absorb(state, seed, 0, SEEDBYTES);
|
||||
do
|
||||
{
|
||||
shake128_squeezeblocks(buf, 0, nblocks, state);
|
||||
for (int i = buf.length - 2; i >= 0; i -= 2)
|
||||
{
|
||||
x[i / 2] = (char)((buf[i] & 0xff) | ((buf[i+1] & 0xff) << 8));
|
||||
}
|
||||
}
|
||||
while (discardtopoly(coeffs, x));
|
||||
} finally {
|
||||
Arrays.fill(state, 0);
|
||||
Arrays.fill(buf, (byte)0);
|
||||
Arrays.fill(x, (char)0);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean discardtopoly(char[] coeffs, char[] x)
|
||||
{
|
||||
int i, r=0;
|
||||
|
||||
for(i=0;i<16;i++)
|
||||
batcher84(x, i);
|
||||
|
||||
// Check whether we're safe:
|
||||
for(i=1008;i<1024;i++)
|
||||
r |= 61444 - x[i];
|
||||
if((r >>= 31) != 0) return true;
|
||||
|
||||
// If we are, copy coefficients to polynomial:
|
||||
for(i=0;i<PARAM_N;i++)
|
||||
coeffs[i] = x[i];
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void batcher84(char[] x, int offset)
|
||||
{
|
||||
int c, t;
|
||||
c = 61444 - x[offset + 0]; t = (x[offset + 0] ^ x[offset + 16]) & (c >> 31); x[offset + 0] ^= t; x[offset + 16] ^= t;
|
||||
c = 61444 - x[offset + 32]; t = (x[offset + 32] ^ x[offset + 48]) & (c >> 31); x[offset + 32] ^= t; x[offset + 48] ^= t;
|
||||
c = 61444 - x[offset + 0]; t = (x[offset + 0] ^ x[offset + 32]) & (c >> 31); x[offset + 0] ^= t; x[offset + 32] ^= t;
|
||||
c = 61444 - x[offset + 16]; t = (x[offset + 16] ^ x[offset + 48]) & (c >> 31); x[offset + 16] ^= t; x[offset + 48] ^= t;
|
||||
c = 61444 - x[offset + 16]; t = (x[offset + 16] ^ x[offset + 32]) & (c >> 31); x[offset + 16] ^= t; x[offset + 32] ^= t;
|
||||
c = 61444 - x[offset + 64]; t = (x[offset + 64] ^ x[offset + 80]) & (c >> 31); x[offset + 64] ^= t; x[offset + 80] ^= t;
|
||||
c = 61444 - x[offset + 96]; t = (x[offset + 96] ^ x[offset + 112]) & (c >> 31); x[offset + 96] ^= t; x[offset + 112] ^= t;
|
||||
c = 61444 - x[offset + 64]; t = (x[offset + 64] ^ x[offset + 96]) & (c >> 31); x[offset + 64] ^= t; x[offset + 96] ^= t;
|
||||
c = 61444 - x[offset + 80]; t = (x[offset + 80] ^ x[offset + 112]) & (c >> 31); x[offset + 80] ^= t; x[offset + 112] ^= t;
|
||||
c = 61444 - x[offset + 80]; t = (x[offset + 80] ^ x[offset + 96]) & (c >> 31); x[offset + 80] ^= t; x[offset + 96] ^= t;
|
||||
c = 61444 - x[offset + 0]; t = (x[offset + 0] ^ x[offset + 64]) & (c >> 31); x[offset + 0] ^= t; x[offset + 64] ^= t;
|
||||
c = 61444 - x[offset + 32]; t = (x[offset + 32] ^ x[offset + 96]) & (c >> 31); x[offset + 32] ^= t; x[offset + 96] ^= t;
|
||||
c = 61444 - x[offset + 32]; t = (x[offset + 32] ^ x[offset + 64]) & (c >> 31); x[offset + 32] ^= t; x[offset + 64] ^= t;
|
||||
c = 61444 - x[offset + 16]; t = (x[offset + 16] ^ x[offset + 80]) & (c >> 31); x[offset + 16] ^= t; x[offset + 80] ^= t;
|
||||
c = 61444 - x[offset + 48]; t = (x[offset + 48] ^ x[offset + 112]) & (c >> 31); x[offset + 48] ^= t; x[offset + 112] ^= t;
|
||||
c = 61444 - x[offset + 48]; t = (x[offset + 48] ^ x[offset + 80]) & (c >> 31); x[offset + 48] ^= t; x[offset + 80] ^= t;
|
||||
c = 61444 - x[offset + 16]; t = (x[offset + 16] ^ x[offset + 32]) & (c >> 31); x[offset + 16] ^= t; x[offset + 32] ^= t;
|
||||
c = 61444 - x[offset + 48]; t = (x[offset + 48] ^ x[offset + 64]) & (c >> 31); x[offset + 48] ^= t; x[offset + 64] ^= t;
|
||||
c = 61444 - x[offset + 80]; t = (x[offset + 80] ^ x[offset + 96]) & (c >> 31); x[offset + 80] ^= t; x[offset + 96] ^= t;
|
||||
c = 61444 - x[offset + 128]; t = (x[offset + 128] ^ x[offset + 144]) & (c >> 31); x[offset + 128] ^= t; x[offset + 144] ^= t;
|
||||
c = 61444 - x[offset + 160]; t = (x[offset + 160] ^ x[offset + 176]) & (c >> 31); x[offset + 160] ^= t; x[offset + 176] ^= t;
|
||||
c = 61444 - x[offset + 128]; t = (x[offset + 128] ^ x[offset + 160]) & (c >> 31); x[offset + 128] ^= t; x[offset + 160] ^= t;
|
||||
c = 61444 - x[offset + 144]; t = (x[offset + 144] ^ x[offset + 176]) & (c >> 31); x[offset + 144] ^= t; x[offset + 176] ^= t;
|
||||
c = 61444 - x[offset + 144]; t = (x[offset + 144] ^ x[offset + 160]) & (c >> 31); x[offset + 144] ^= t; x[offset + 160] ^= t;
|
||||
c = 61444 - x[offset + 192]; t = (x[offset + 192] ^ x[offset + 208]) & (c >> 31); x[offset + 192] ^= t; x[offset + 208] ^= t;
|
||||
c = 61444 - x[offset + 224]; t = (x[offset + 224] ^ x[offset + 240]) & (c >> 31); x[offset + 224] ^= t; x[offset + 240] ^= t;
|
||||
c = 61444 - x[offset + 192]; t = (x[offset + 192] ^ x[offset + 224]) & (c >> 31); x[offset + 192] ^= t; x[offset + 224] ^= t;
|
||||
c = 61444 - x[offset + 208]; t = (x[offset + 208] ^ x[offset + 240]) & (c >> 31); x[offset + 208] ^= t; x[offset + 240] ^= t;
|
||||
c = 61444 - x[offset + 208]; t = (x[offset + 208] ^ x[offset + 224]) & (c >> 31); x[offset + 208] ^= t; x[offset + 224] ^= t;
|
||||
c = 61444 - x[offset + 128]; t = (x[offset + 128] ^ x[offset + 192]) & (c >> 31); x[offset + 128] ^= t; x[offset + 192] ^= t;
|
||||
c = 61444 - x[offset + 160]; t = (x[offset + 160] ^ x[offset + 224]) & (c >> 31); x[offset + 160] ^= t; x[offset + 224] ^= t;
|
||||
c = 61444 - x[offset + 160]; t = (x[offset + 160] ^ x[offset + 192]) & (c >> 31); x[offset + 160] ^= t; x[offset + 192] ^= t;
|
||||
c = 61444 - x[offset + 144]; t = (x[offset + 144] ^ x[offset + 208]) & (c >> 31); x[offset + 144] ^= t; x[offset + 208] ^= t;
|
||||
c = 61444 - x[offset + 176]; t = (x[offset + 176] ^ x[offset + 240]) & (c >> 31); x[offset + 176] ^= t; x[offset + 240] ^= t;
|
||||
c = 61444 - x[offset + 176]; t = (x[offset + 176] ^ x[offset + 208]) & (c >> 31); x[offset + 176] ^= t; x[offset + 208] ^= t;
|
||||
c = 61444 - x[offset + 144]; t = (x[offset + 144] ^ x[offset + 160]) & (c >> 31); x[offset + 144] ^= t; x[offset + 160] ^= t;
|
||||
c = 61444 - x[offset + 176]; t = (x[offset + 176] ^ x[offset + 192]) & (c >> 31); x[offset + 176] ^= t; x[offset + 192] ^= t;
|
||||
c = 61444 - x[offset + 208]; t = (x[offset + 208] ^ x[offset + 224]) & (c >> 31); x[offset + 208] ^= t; x[offset + 224] ^= t;
|
||||
c = 61444 - x[offset + 0]; t = (x[offset + 0] ^ x[offset + 128]) & (c >> 31); x[offset + 0] ^= t; x[offset + 128] ^= t;
|
||||
c = 61444 - x[offset + 64]; t = (x[offset + 64] ^ x[offset + 192]) & (c >> 31); x[offset + 64] ^= t; x[offset + 192] ^= t;
|
||||
c = 61444 - x[offset + 64]; t = (x[offset + 64] ^ x[offset + 128]) & (c >> 31); x[offset + 64] ^= t; x[offset + 128] ^= t;
|
||||
c = 61444 - x[offset + 32]; t = (x[offset + 32] ^ x[offset + 160]) & (c >> 31); x[offset + 32] ^= t; x[offset + 160] ^= t;
|
||||
c = 61444 - x[offset + 96]; t = (x[offset + 96] ^ x[offset + 224]) & (c >> 31); x[offset + 96] ^= t; x[offset + 224] ^= t;
|
||||
c = 61444 - x[offset + 96]; t = (x[offset + 96] ^ x[offset + 160]) & (c >> 31); x[offset + 96] ^= t; x[offset + 160] ^= t;
|
||||
c = 61444 - x[offset + 32]; t = (x[offset + 32] ^ x[offset + 64]) & (c >> 31); x[offset + 32] ^= t; x[offset + 64] ^= t;
|
||||
c = 61444 - x[offset + 96]; t = (x[offset + 96] ^ x[offset + 128]) & (c >> 31); x[offset + 96] ^= t; x[offset + 128] ^= t;
|
||||
c = 61444 - x[offset + 160]; t = (x[offset + 160] ^ x[offset + 192]) & (c >> 31); x[offset + 160] ^= t; x[offset + 192] ^= t;
|
||||
c = 61444 - x[offset + 16]; t = (x[offset + 16] ^ x[offset + 144]) & (c >> 31); x[offset + 16] ^= t; x[offset + 144] ^= t;
|
||||
c = 61444 - x[offset + 80]; t = (x[offset + 80] ^ x[offset + 208]) & (c >> 31); x[offset + 80] ^= t; x[offset + 208] ^= t;
|
||||
c = 61444 - x[offset + 80]; t = (x[offset + 80] ^ x[offset + 144]) & (c >> 31); x[offset + 80] ^= t; x[offset + 144] ^= t;
|
||||
c = 61444 - x[offset + 48]; t = (x[offset + 48] ^ x[offset + 176]) & (c >> 31); x[offset + 48] ^= t; x[offset + 176] ^= t;
|
||||
c = 61444 - x[offset + 112]; t = (x[offset + 112] ^ x[offset + 240]) & (c >> 31); x[offset + 112] ^= t; x[offset + 240] ^= t;
|
||||
c = 61444 - x[offset + 112]; t = (x[offset + 112] ^ x[offset + 176]) & (c >> 31); x[offset + 112] ^= t; x[offset + 176] ^= t;
|
||||
c = 61444 - x[offset + 48]; t = (x[offset + 48] ^ x[offset + 80]) & (c >> 31); x[offset + 48] ^= t; x[offset + 80] ^= t;
|
||||
c = 61444 - x[offset + 112]; t = (x[offset + 112] ^ x[offset + 144]) & (c >> 31); x[offset + 112] ^= t; x[offset + 144] ^= t;
|
||||
c = 61444 - x[offset + 176]; t = (x[offset + 176] ^ x[offset + 208]) & (c >> 31); x[offset + 176] ^= t; x[offset + 208] ^= t;
|
||||
c = 61444 - x[offset + 16]; t = (x[offset + 16] ^ x[offset + 32]) & (c >> 31); x[offset + 16] ^= t; x[offset + 32] ^= t;
|
||||
c = 61444 - x[offset + 48]; t = (x[offset + 48] ^ x[offset + 64]) & (c >> 31); x[offset + 48] ^= t; x[offset + 64] ^= t;
|
||||
c = 61444 - x[offset + 80]; t = (x[offset + 80] ^ x[offset + 96]) & (c >> 31); x[offset + 80] ^= t; x[offset + 96] ^= t;
|
||||
c = 61444 - x[offset + 112]; t = (x[offset + 112] ^ x[offset + 128]) & (c >> 31); x[offset + 112] ^= t; x[offset + 128] ^= t;
|
||||
c = 61444 - x[offset + 144]; t = (x[offset + 144] ^ x[offset + 160]) & (c >> 31); x[offset + 144] ^= t; x[offset + 160] ^= t;
|
||||
c = 61444 - x[offset + 176]; t = (x[offset + 176] ^ x[offset + 192]) & (c >> 31); x[offset + 176] ^= t; x[offset + 192] ^= t;
|
||||
c = 61444 - x[offset + 208]; t = (x[offset + 208] ^ x[offset + 224]) & (c >> 31); x[offset + 208] ^= t; x[offset + 224] ^= t;
|
||||
c = 61444 - x[offset + 256]; t = (x[offset + 256] ^ x[offset + 272]) & (c >> 31); x[offset + 256] ^= t; x[offset + 272] ^= t;
|
||||
c = 61444 - x[offset + 288]; t = (x[offset + 288] ^ x[offset + 304]) & (c >> 31); x[offset + 288] ^= t; x[offset + 304] ^= t;
|
||||
c = 61444 - x[offset + 256]; t = (x[offset + 256] ^ x[offset + 288]) & (c >> 31); x[offset + 256] ^= t; x[offset + 288] ^= t;
|
||||
c = 61444 - x[offset + 272]; t = (x[offset + 272] ^ x[offset + 304]) & (c >> 31); x[offset + 272] ^= t; x[offset + 304] ^= t;
|
||||
c = 61444 - x[offset + 272]; t = (x[offset + 272] ^ x[offset + 288]) & (c >> 31); x[offset + 272] ^= t; x[offset + 288] ^= t;
|
||||
c = 61444 - x[offset + 320]; t = (x[offset + 320] ^ x[offset + 336]) & (c >> 31); x[offset + 320] ^= t; x[offset + 336] ^= t;
|
||||
c = 61444 - x[offset + 352]; t = (x[offset + 352] ^ x[offset + 368]) & (c >> 31); x[offset + 352] ^= t; x[offset + 368] ^= t;
|
||||
c = 61444 - x[offset + 320]; t = (x[offset + 320] ^ x[offset + 352]) & (c >> 31); x[offset + 320] ^= t; x[offset + 352] ^= t;
|
||||
c = 61444 - x[offset + 336]; t = (x[offset + 336] ^ x[offset + 368]) & (c >> 31); x[offset + 336] ^= t; x[offset + 368] ^= t;
|
||||
c = 61444 - x[offset + 336]; t = (x[offset + 336] ^ x[offset + 352]) & (c >> 31); x[offset + 336] ^= t; x[offset + 352] ^= t;
|
||||
c = 61444 - x[offset + 256]; t = (x[offset + 256] ^ x[offset + 320]) & (c >> 31); x[offset + 256] ^= t; x[offset + 320] ^= t;
|
||||
c = 61444 - x[offset + 288]; t = (x[offset + 288] ^ x[offset + 352]) & (c >> 31); x[offset + 288] ^= t; x[offset + 352] ^= t;
|
||||
c = 61444 - x[offset + 288]; t = (x[offset + 288] ^ x[offset + 320]) & (c >> 31); x[offset + 288] ^= t; x[offset + 320] ^= t;
|
||||
c = 61444 - x[offset + 272]; t = (x[offset + 272] ^ x[offset + 336]) & (c >> 31); x[offset + 272] ^= t; x[offset + 336] ^= t;
|
||||
c = 61444 - x[offset + 304]; t = (x[offset + 304] ^ x[offset + 368]) & (c >> 31); x[offset + 304] ^= t; x[offset + 368] ^= t;
|
||||
c = 61444 - x[offset + 304]; t = (x[offset + 304] ^ x[offset + 336]) & (c >> 31); x[offset + 304] ^= t; x[offset + 336] ^= t;
|
||||
c = 61444 - x[offset + 272]; t = (x[offset + 272] ^ x[offset + 288]) & (c >> 31); x[offset + 272] ^= t; x[offset + 288] ^= t;
|
||||
c = 61444 - x[offset + 304]; t = (x[offset + 304] ^ x[offset + 320]) & (c >> 31); x[offset + 304] ^= t; x[offset + 320] ^= t;
|
||||
c = 61444 - x[offset + 336]; t = (x[offset + 336] ^ x[offset + 352]) & (c >> 31); x[offset + 336] ^= t; x[offset + 352] ^= t;
|
||||
c = 61444 - x[offset + 384]; t = (x[offset + 384] ^ x[offset + 400]) & (c >> 31); x[offset + 384] ^= t; x[offset + 400] ^= t;
|
||||
c = 61444 - x[offset + 416]; t = (x[offset + 416] ^ x[offset + 432]) & (c >> 31); x[offset + 416] ^= t; x[offset + 432] ^= t;
|
||||
c = 61444 - x[offset + 384]; t = (x[offset + 384] ^ x[offset + 416]) & (c >> 31); x[offset + 384] ^= t; x[offset + 416] ^= t;
|
||||
c = 61444 - x[offset + 400]; t = (x[offset + 400] ^ x[offset + 432]) & (c >> 31); x[offset + 400] ^= t; x[offset + 432] ^= t;
|
||||
c = 61444 - x[offset + 400]; t = (x[offset + 400] ^ x[offset + 416]) & (c >> 31); x[offset + 400] ^= t; x[offset + 416] ^= t;
|
||||
c = 61444 - x[offset + 448]; t = (x[offset + 448] ^ x[offset + 464]) & (c >> 31); x[offset + 448] ^= t; x[offset + 464] ^= t;
|
||||
c = 61444 - x[offset + 480]; t = (x[offset + 480] ^ x[offset + 496]) & (c >> 31); x[offset + 480] ^= t; x[offset + 496] ^= t;
|
||||
c = 61444 - x[offset + 448]; t = (x[offset + 448] ^ x[offset + 480]) & (c >> 31); x[offset + 448] ^= t; x[offset + 480] ^= t;
|
||||
c = 61444 - x[offset + 464]; t = (x[offset + 464] ^ x[offset + 496]) & (c >> 31); x[offset + 464] ^= t; x[offset + 496] ^= t;
|
||||
c = 61444 - x[offset + 464]; t = (x[offset + 464] ^ x[offset + 480]) & (c >> 31); x[offset + 464] ^= t; x[offset + 480] ^= t;
|
||||
c = 61444 - x[offset + 384]; t = (x[offset + 384] ^ x[offset + 448]) & (c >> 31); x[offset + 384] ^= t; x[offset + 448] ^= t;
|
||||
c = 61444 - x[offset + 416]; t = (x[offset + 416] ^ x[offset + 480]) & (c >> 31); x[offset + 416] ^= t; x[offset + 480] ^= t;
|
||||
c = 61444 - x[offset + 416]; t = (x[offset + 416] ^ x[offset + 448]) & (c >> 31); x[offset + 416] ^= t; x[offset + 448] ^= t;
|
||||
c = 61444 - x[offset + 400]; t = (x[offset + 400] ^ x[offset + 464]) & (c >> 31); x[offset + 400] ^= t; x[offset + 464] ^= t;
|
||||
c = 61444 - x[offset + 432]; t = (x[offset + 432] ^ x[offset + 496]) & (c >> 31); x[offset + 432] ^= t; x[offset + 496] ^= t;
|
||||
c = 61444 - x[offset + 432]; t = (x[offset + 432] ^ x[offset + 464]) & (c >> 31); x[offset + 432] ^= t; x[offset + 464] ^= t;
|
||||
c = 61444 - x[offset + 400]; t = (x[offset + 400] ^ x[offset + 416]) & (c >> 31); x[offset + 400] ^= t; x[offset + 416] ^= t;
|
||||
c = 61444 - x[offset + 432]; t = (x[offset + 432] ^ x[offset + 448]) & (c >> 31); x[offset + 432] ^= t; x[offset + 448] ^= t;
|
||||
c = 61444 - x[offset + 464]; t = (x[offset + 464] ^ x[offset + 480]) & (c >> 31); x[offset + 464] ^= t; x[offset + 480] ^= t;
|
||||
c = 61444 - x[offset + 256]; t = (x[offset + 256] ^ x[offset + 384]) & (c >> 31); x[offset + 256] ^= t; x[offset + 384] ^= t;
|
||||
c = 61444 - x[offset + 320]; t = (x[offset + 320] ^ x[offset + 448]) & (c >> 31); x[offset + 320] ^= t; x[offset + 448] ^= t;
|
||||
c = 61444 - x[offset + 320]; t = (x[offset + 320] ^ x[offset + 384]) & (c >> 31); x[offset + 320] ^= t; x[offset + 384] ^= t;
|
||||
c = 61444 - x[offset + 288]; t = (x[offset + 288] ^ x[offset + 416]) & (c >> 31); x[offset + 288] ^= t; x[offset + 416] ^= t;
|
||||
c = 61444 - x[offset + 352]; t = (x[offset + 352] ^ x[offset + 480]) & (c >> 31); x[offset + 352] ^= t; x[offset + 480] ^= t;
|
||||
c = 61444 - x[offset + 352]; t = (x[offset + 352] ^ x[offset + 416]) & (c >> 31); x[offset + 352] ^= t; x[offset + 416] ^= t;
|
||||
c = 61444 - x[offset + 288]; t = (x[offset + 288] ^ x[offset + 320]) & (c >> 31); x[offset + 288] ^= t; x[offset + 320] ^= t;
|
||||
c = 61444 - x[offset + 352]; t = (x[offset + 352] ^ x[offset + 384]) & (c >> 31); x[offset + 352] ^= t; x[offset + 384] ^= t;
|
||||
c = 61444 - x[offset + 416]; t = (x[offset + 416] ^ x[offset + 448]) & (c >> 31); x[offset + 416] ^= t; x[offset + 448] ^= t;
|
||||
c = 61444 - x[offset + 272]; t = (x[offset + 272] ^ x[offset + 400]) & (c >> 31); x[offset + 272] ^= t; x[offset + 400] ^= t;
|
||||
c = 61444 - x[offset + 336]; t = (x[offset + 336] ^ x[offset + 464]) & (c >> 31); x[offset + 336] ^= t; x[offset + 464] ^= t;
|
||||
c = 61444 - x[offset + 336]; t = (x[offset + 336] ^ x[offset + 400]) & (c >> 31); x[offset + 336] ^= t; x[offset + 400] ^= t;
|
||||
c = 61444 - x[offset + 304]; t = (x[offset + 304] ^ x[offset + 432]) & (c >> 31); x[offset + 304] ^= t; x[offset + 432] ^= t;
|
||||
c = 61444 - x[offset + 368]; t = (x[offset + 368] ^ x[offset + 496]) & (c >> 31); x[offset + 368] ^= t; x[offset + 496] ^= t;
|
||||
c = 61444 - x[offset + 368]; t = (x[offset + 368] ^ x[offset + 432]) & (c >> 31); x[offset + 368] ^= t; x[offset + 432] ^= t;
|
||||
c = 61444 - x[offset + 304]; t = (x[offset + 304] ^ x[offset + 336]) & (c >> 31); x[offset + 304] ^= t; x[offset + 336] ^= t;
|
||||
c = 61444 - x[offset + 368]; t = (x[offset + 368] ^ x[offset + 400]) & (c >> 31); x[offset + 368] ^= t; x[offset + 400] ^= t;
|
||||
c = 61444 - x[offset + 432]; t = (x[offset + 432] ^ x[offset + 464]) & (c >> 31); x[offset + 432] ^= t; x[offset + 464] ^= t;
|
||||
c = 61444 - x[offset + 272]; t = (x[offset + 272] ^ x[offset + 288]) & (c >> 31); x[offset + 272] ^= t; x[offset + 288] ^= t;
|
||||
c = 61444 - x[offset + 304]; t = (x[offset + 304] ^ x[offset + 320]) & (c >> 31); x[offset + 304] ^= t; x[offset + 320] ^= t;
|
||||
c = 61444 - x[offset + 336]; t = (x[offset + 336] ^ x[offset + 352]) & (c >> 31); x[offset + 336] ^= t; x[offset + 352] ^= t;
|
||||
c = 61444 - x[offset + 368]; t = (x[offset + 368] ^ x[offset + 384]) & (c >> 31); x[offset + 368] ^= t; x[offset + 384] ^= t;
|
||||
c = 61444 - x[offset + 400]; t = (x[offset + 400] ^ x[offset + 416]) & (c >> 31); x[offset + 400] ^= t; x[offset + 416] ^= t;
|
||||
c = 61444 - x[offset + 432]; t = (x[offset + 432] ^ x[offset + 448]) & (c >> 31); x[offset + 432] ^= t; x[offset + 448] ^= t;
|
||||
c = 61444 - x[offset + 464]; t = (x[offset + 464] ^ x[offset + 480]) & (c >> 31); x[offset + 464] ^= t; x[offset + 480] ^= t;
|
||||
c = 61444 - x[offset + 0]; t = (x[offset + 0] ^ x[offset + 256]) & (c >> 31); x[offset + 0] ^= t; x[offset + 256] ^= t;
|
||||
c = 61444 - x[offset + 128]; t = (x[offset + 128] ^ x[offset + 384]) & (c >> 31); x[offset + 128] ^= t; x[offset + 384] ^= t;
|
||||
c = 61444 - x[offset + 128]; t = (x[offset + 128] ^ x[offset + 256]) & (c >> 31); x[offset + 128] ^= t; x[offset + 256] ^= t;
|
||||
c = 61444 - x[offset + 64]; t = (x[offset + 64] ^ x[offset + 320]) & (c >> 31); x[offset + 64] ^= t; x[offset + 320] ^= t;
|
||||
c = 61444 - x[offset + 192]; t = (x[offset + 192] ^ x[offset + 448]) & (c >> 31); x[offset + 192] ^= t; x[offset + 448] ^= t;
|
||||
c = 61444 - x[offset + 192]; t = (x[offset + 192] ^ x[offset + 320]) & (c >> 31); x[offset + 192] ^= t; x[offset + 320] ^= t;
|
||||
c = 61444 - x[offset + 64]; t = (x[offset + 64] ^ x[offset + 128]) & (c >> 31); x[offset + 64] ^= t; x[offset + 128] ^= t;
|
||||
c = 61444 - x[offset + 192]; t = (x[offset + 192] ^ x[offset + 256]) & (c >> 31); x[offset + 192] ^= t; x[offset + 256] ^= t;
|
||||
c = 61444 - x[offset + 320]; t = (x[offset + 320] ^ x[offset + 384]) & (c >> 31); x[offset + 320] ^= t; x[offset + 384] ^= t;
|
||||
c = 61444 - x[offset + 32]; t = (x[offset + 32] ^ x[offset + 288]) & (c >> 31); x[offset + 32] ^= t; x[offset + 288] ^= t;
|
||||
c = 61444 - x[offset + 160]; t = (x[offset + 160] ^ x[offset + 416]) & (c >> 31); x[offset + 160] ^= t; x[offset + 416] ^= t;
|
||||
c = 61444 - x[offset + 160]; t = (x[offset + 160] ^ x[offset + 288]) & (c >> 31); x[offset + 160] ^= t; x[offset + 288] ^= t;
|
||||
c = 61444 - x[offset + 96]; t = (x[offset + 96] ^ x[offset + 352]) & (c >> 31); x[offset + 96] ^= t; x[offset + 352] ^= t;
|
||||
c = 61444 - x[offset + 224]; t = (x[offset + 224] ^ x[offset + 480]) & (c >> 31); x[offset + 224] ^= t; x[offset + 480] ^= t;
|
||||
c = 61444 - x[offset + 224]; t = (x[offset + 224] ^ x[offset + 352]) & (c >> 31); x[offset + 224] ^= t; x[offset + 352] ^= t;
|
||||
c = 61444 - x[offset + 96]; t = (x[offset + 96] ^ x[offset + 160]) & (c >> 31); x[offset + 96] ^= t; x[offset + 160] ^= t;
|
||||
c = 61444 - x[offset + 224]; t = (x[offset + 224] ^ x[offset + 288]) & (c >> 31); x[offset + 224] ^= t; x[offset + 288] ^= t;
|
||||
c = 61444 - x[offset + 352]; t = (x[offset + 352] ^ x[offset + 416]) & (c >> 31); x[offset + 352] ^= t; x[offset + 416] ^= t;
|
||||
c = 61444 - x[offset + 32]; t = (x[offset + 32] ^ x[offset + 64]) & (c >> 31); x[offset + 32] ^= t; x[offset + 64] ^= t;
|
||||
c = 61444 - x[offset + 96]; t = (x[offset + 96] ^ x[offset + 128]) & (c >> 31); x[offset + 96] ^= t; x[offset + 128] ^= t;
|
||||
c = 61444 - x[offset + 160]; t = (x[offset + 160] ^ x[offset + 192]) & (c >> 31); x[offset + 160] ^= t; x[offset + 192] ^= t;
|
||||
c = 61444 - x[offset + 224]; t = (x[offset + 224] ^ x[offset + 256]) & (c >> 31); x[offset + 224] ^= t; x[offset + 256] ^= t;
|
||||
c = 61444 - x[offset + 288]; t = (x[offset + 288] ^ x[offset + 320]) & (c >> 31); x[offset + 288] ^= t; x[offset + 320] ^= t;
|
||||
c = 61444 - x[offset + 352]; t = (x[offset + 352] ^ x[offset + 384]) & (c >> 31); x[offset + 352] ^= t; x[offset + 384] ^= t;
|
||||
c = 61444 - x[offset + 416]; t = (x[offset + 416] ^ x[offset + 448]) & (c >> 31); x[offset + 416] ^= t; x[offset + 448] ^= t;
|
||||
c = 61444 - x[offset + 16]; t = (x[offset + 16] ^ x[offset + 272]) & (c >> 31); x[offset + 16] ^= t; x[offset + 272] ^= t;
|
||||
c = 61444 - x[offset + 144]; t = (x[offset + 144] ^ x[offset + 400]) & (c >> 31); x[offset + 144] ^= t; x[offset + 400] ^= t;
|
||||
c = 61444 - x[offset + 144]; t = (x[offset + 144] ^ x[offset + 272]) & (c >> 31); x[offset + 144] ^= t; x[offset + 272] ^= t;
|
||||
c = 61444 - x[offset + 80]; t = (x[offset + 80] ^ x[offset + 336]) & (c >> 31); x[offset + 80] ^= t; x[offset + 336] ^= t;
|
||||
c = 61444 - x[offset + 208]; t = (x[offset + 208] ^ x[offset + 464]) & (c >> 31); x[offset + 208] ^= t; x[offset + 464] ^= t;
|
||||
c = 61444 - x[offset + 208]; t = (x[offset + 208] ^ x[offset + 336]) & (c >> 31); x[offset + 208] ^= t; x[offset + 336] ^= t;
|
||||
c = 61444 - x[offset + 80]; t = (x[offset + 80] ^ x[offset + 144]) & (c >> 31); x[offset + 80] ^= t; x[offset + 144] ^= t;
|
||||
c = 61444 - x[offset + 208]; t = (x[offset + 208] ^ x[offset + 272]) & (c >> 31); x[offset + 208] ^= t; x[offset + 272] ^= t;
|
||||
c = 61444 - x[offset + 336]; t = (x[offset + 336] ^ x[offset + 400]) & (c >> 31); x[offset + 336] ^= t; x[offset + 400] ^= t;
|
||||
c = 61444 - x[offset + 48]; t = (x[offset + 48] ^ x[offset + 304]) & (c >> 31); x[offset + 48] ^= t; x[offset + 304] ^= t;
|
||||
c = 61444 - x[offset + 176]; t = (x[offset + 176] ^ x[offset + 432]) & (c >> 31); x[offset + 176] ^= t; x[offset + 432] ^= t;
|
||||
c = 61444 - x[offset + 176]; t = (x[offset + 176] ^ x[offset + 304]) & (c >> 31); x[offset + 176] ^= t; x[offset + 304] ^= t;
|
||||
c = 61444 - x[offset + 112]; t = (x[offset + 112] ^ x[offset + 368]) & (c >> 31); x[offset + 112] ^= t; x[offset + 368] ^= t;
|
||||
c = 61444 - x[offset + 240]; t = (x[offset + 240] ^ x[offset + 496]) & (c >> 31); x[offset + 240] ^= t; x[offset + 496] ^= t;
|
||||
c = 61444 - x[offset + 240]; t = (x[offset + 240] ^ x[offset + 368]) & (c >> 31); x[offset + 240] ^= t; x[offset + 368] ^= t;
|
||||
c = 61444 - x[offset + 112]; t = (x[offset + 112] ^ x[offset + 176]) & (c >> 31); x[offset + 112] ^= t; x[offset + 176] ^= t;
|
||||
c = 61444 - x[offset + 240]; t = (x[offset + 240] ^ x[offset + 304]) & (c >> 31); x[offset + 240] ^= t; x[offset + 304] ^= t;
|
||||
c = 61444 - x[offset + 368]; t = (x[offset + 368] ^ x[offset + 432]) & (c >> 31); x[offset + 368] ^= t; x[offset + 432] ^= t;
|
||||
c = 61444 - x[offset + 48]; t = (x[offset + 48] ^ x[offset + 80]) & (c >> 31); x[offset + 48] ^= t; x[offset + 80] ^= t;
|
||||
c = 61444 - x[offset + 112]; t = (x[offset + 112] ^ x[offset + 144]) & (c >> 31); x[offset + 112] ^= t; x[offset + 144] ^= t;
|
||||
c = 61444 - x[offset + 176]; t = (x[offset + 176] ^ x[offset + 208]) & (c >> 31); x[offset + 176] ^= t; x[offset + 208] ^= t;
|
||||
c = 61444 - x[offset + 240]; t = (x[offset + 240] ^ x[offset + 272]) & (c >> 31); x[offset + 240] ^= t; x[offset + 272] ^= t;
|
||||
c = 61444 - x[offset + 304]; t = (x[offset + 304] ^ x[offset + 336]) & (c >> 31); x[offset + 304] ^= t; x[offset + 336] ^= t;
|
||||
c = 61444 - x[offset + 368]; t = (x[offset + 368] ^ x[offset + 400]) & (c >> 31); x[offset + 368] ^= t; x[offset + 400] ^= t;
|
||||
c = 61444 - x[offset + 432]; t = (x[offset + 432] ^ x[offset + 464]) & (c >> 31); x[offset + 432] ^= t; x[offset + 464] ^= t;
|
||||
c = 61444 - x[offset + 16]; t = (x[offset + 16] ^ x[offset + 32]) & (c >> 31); x[offset + 16] ^= t; x[offset + 32] ^= t;
|
||||
c = 61444 - x[offset + 48]; t = (x[offset + 48] ^ x[offset + 64]) & (c >> 31); x[offset + 48] ^= t; x[offset + 64] ^= t;
|
||||
c = 61444 - x[offset + 80]; t = (x[offset + 80] ^ x[offset + 96]) & (c >> 31); x[offset + 80] ^= t; x[offset + 96] ^= t;
|
||||
c = 61444 - x[offset + 112]; t = (x[offset + 112] ^ x[offset + 128]) & (c >> 31); x[offset + 112] ^= t; x[offset + 128] ^= t;
|
||||
c = 61444 - x[offset + 144]; t = (x[offset + 144] ^ x[offset + 160]) & (c >> 31); x[offset + 144] ^= t; x[offset + 160] ^= t;
|
||||
c = 61444 - x[offset + 176]; t = (x[offset + 176] ^ x[offset + 192]) & (c >> 31); x[offset + 176] ^= t; x[offset + 192] ^= t;
|
||||
c = 61444 - x[offset + 208]; t = (x[offset + 208] ^ x[offset + 224]) & (c >> 31); x[offset + 208] ^= t; x[offset + 224] ^= t;
|
||||
c = 61444 - x[offset + 240]; t = (x[offset + 240] ^ x[offset + 256]) & (c >> 31); x[offset + 240] ^= t; x[offset + 256] ^= t;
|
||||
c = 61444 - x[offset + 272]; t = (x[offset + 272] ^ x[offset + 288]) & (c >> 31); x[offset + 272] ^= t; x[offset + 288] ^= t;
|
||||
c = 61444 - x[offset + 304]; t = (x[offset + 304] ^ x[offset + 320]) & (c >> 31); x[offset + 304] ^= t; x[offset + 320] ^= t;
|
||||
c = 61444 - x[offset + 336]; t = (x[offset + 336] ^ x[offset + 352]) & (c >> 31); x[offset + 336] ^= t; x[offset + 352] ^= t;
|
||||
c = 61444 - x[offset + 368]; t = (x[offset + 368] ^ x[offset + 384]) & (c >> 31); x[offset + 368] ^= t; x[offset + 384] ^= t;
|
||||
c = 61444 - x[offset + 400]; t = (x[offset + 400] ^ x[offset + 416]) & (c >> 31); x[offset + 400] ^= t; x[offset + 416] ^= t;
|
||||
c = 61444 - x[offset + 432]; t = (x[offset + 432] ^ x[offset + 448]) & (c >> 31); x[offset + 432] ^= t; x[offset + 448] ^= t;
|
||||
c = 61444 - x[offset + 464]; t = (x[offset + 464] ^ x[offset + 480]) & (c >> 31); x[offset + 464] ^= t; x[offset + 480] ^= t;
|
||||
c = 61444 - x[offset + 512]; t = (x[offset + 512] ^ x[offset + 528]) & (c >> 31); x[offset + 512] ^= t; x[offset + 528] ^= t;
|
||||
c = 61444 - x[offset + 544]; t = (x[offset + 544] ^ x[offset + 560]) & (c >> 31); x[offset + 544] ^= t; x[offset + 560] ^= t;
|
||||
c = 61444 - x[offset + 512]; t = (x[offset + 512] ^ x[offset + 544]) & (c >> 31); x[offset + 512] ^= t; x[offset + 544] ^= t;
|
||||
c = 61444 - x[offset + 528]; t = (x[offset + 528] ^ x[offset + 560]) & (c >> 31); x[offset + 528] ^= t; x[offset + 560] ^= t;
|
||||
c = 61444 - x[offset + 528]; t = (x[offset + 528] ^ x[offset + 544]) & (c >> 31); x[offset + 528] ^= t; x[offset + 544] ^= t;
|
||||
c = 61444 - x[offset + 576]; t = (x[offset + 576] ^ x[offset + 592]) & (c >> 31); x[offset + 576] ^= t; x[offset + 592] ^= t;
|
||||
c = 61444 - x[offset + 608]; t = (x[offset + 608] ^ x[offset + 624]) & (c >> 31); x[offset + 608] ^= t; x[offset + 624] ^= t;
|
||||
c = 61444 - x[offset + 576]; t = (x[offset + 576] ^ x[offset + 608]) & (c >> 31); x[offset + 576] ^= t; x[offset + 608] ^= t;
|
||||
c = 61444 - x[offset + 592]; t = (x[offset + 592] ^ x[offset + 624]) & (c >> 31); x[offset + 592] ^= t; x[offset + 624] ^= t;
|
||||
c = 61444 - x[offset + 592]; t = (x[offset + 592] ^ x[offset + 608]) & (c >> 31); x[offset + 592] ^= t; x[offset + 608] ^= t;
|
||||
c = 61444 - x[offset + 512]; t = (x[offset + 512] ^ x[offset + 576]) & (c >> 31); x[offset + 512] ^= t; x[offset + 576] ^= t;
|
||||
c = 61444 - x[offset + 544]; t = (x[offset + 544] ^ x[offset + 608]) & (c >> 31); x[offset + 544] ^= t; x[offset + 608] ^= t;
|
||||
c = 61444 - x[offset + 544]; t = (x[offset + 544] ^ x[offset + 576]) & (c >> 31); x[offset + 544] ^= t; x[offset + 576] ^= t;
|
||||
c = 61444 - x[offset + 528]; t = (x[offset + 528] ^ x[offset + 592]) & (c >> 31); x[offset + 528] ^= t; x[offset + 592] ^= t;
|
||||
c = 61444 - x[offset + 560]; t = (x[offset + 560] ^ x[offset + 624]) & (c >> 31); x[offset + 560] ^= t; x[offset + 624] ^= t;
|
||||
c = 61444 - x[offset + 560]; t = (x[offset + 560] ^ x[offset + 592]) & (c >> 31); x[offset + 560] ^= t; x[offset + 592] ^= t;
|
||||
c = 61444 - x[offset + 528]; t = (x[offset + 528] ^ x[offset + 544]) & (c >> 31); x[offset + 528] ^= t; x[offset + 544] ^= t;
|
||||
c = 61444 - x[offset + 560]; t = (x[offset + 560] ^ x[offset + 576]) & (c >> 31); x[offset + 560] ^= t; x[offset + 576] ^= t;
|
||||
c = 61444 - x[offset + 592]; t = (x[offset + 592] ^ x[offset + 608]) & (c >> 31); x[offset + 592] ^= t; x[offset + 608] ^= t;
|
||||
c = 61444 - x[offset + 640]; t = (x[offset + 640] ^ x[offset + 656]) & (c >> 31); x[offset + 640] ^= t; x[offset + 656] ^= t;
|
||||
c = 61444 - x[offset + 672]; t = (x[offset + 672] ^ x[offset + 688]) & (c >> 31); x[offset + 672] ^= t; x[offset + 688] ^= t;
|
||||
c = 61444 - x[offset + 640]; t = (x[offset + 640] ^ x[offset + 672]) & (c >> 31); x[offset + 640] ^= t; x[offset + 672] ^= t;
|
||||
c = 61444 - x[offset + 656]; t = (x[offset + 656] ^ x[offset + 688]) & (c >> 31); x[offset + 656] ^= t; x[offset + 688] ^= t;
|
||||
c = 61444 - x[offset + 656]; t = (x[offset + 656] ^ x[offset + 672]) & (c >> 31); x[offset + 656] ^= t; x[offset + 672] ^= t;
|
||||
c = 61444 - x[offset + 704]; t = (x[offset + 704] ^ x[offset + 720]) & (c >> 31); x[offset + 704] ^= t; x[offset + 720] ^= t;
|
||||
c = 61444 - x[offset + 736]; t = (x[offset + 736] ^ x[offset + 752]) & (c >> 31); x[offset + 736] ^= t; x[offset + 752] ^= t;
|
||||
c = 61444 - x[offset + 704]; t = (x[offset + 704] ^ x[offset + 736]) & (c >> 31); x[offset + 704] ^= t; x[offset + 736] ^= t;
|
||||
c = 61444 - x[offset + 720]; t = (x[offset + 720] ^ x[offset + 752]) & (c >> 31); x[offset + 720] ^= t; x[offset + 752] ^= t;
|
||||
c = 61444 - x[offset + 720]; t = (x[offset + 720] ^ x[offset + 736]) & (c >> 31); x[offset + 720] ^= t; x[offset + 736] ^= t;
|
||||
c = 61444 - x[offset + 640]; t = (x[offset + 640] ^ x[offset + 704]) & (c >> 31); x[offset + 640] ^= t; x[offset + 704] ^= t;
|
||||
c = 61444 - x[offset + 672]; t = (x[offset + 672] ^ x[offset + 736]) & (c >> 31); x[offset + 672] ^= t; x[offset + 736] ^= t;
|
||||
c = 61444 - x[offset + 672]; t = (x[offset + 672] ^ x[offset + 704]) & (c >> 31); x[offset + 672] ^= t; x[offset + 704] ^= t;
|
||||
c = 61444 - x[offset + 656]; t = (x[offset + 656] ^ x[offset + 720]) & (c >> 31); x[offset + 656] ^= t; x[offset + 720] ^= t;
|
||||
c = 61444 - x[offset + 688]; t = (x[offset + 688] ^ x[offset + 752]) & (c >> 31); x[offset + 688] ^= t; x[offset + 752] ^= t;
|
||||
c = 61444 - x[offset + 688]; t = (x[offset + 688] ^ x[offset + 720]) & (c >> 31); x[offset + 688] ^= t; x[offset + 720] ^= t;
|
||||
c = 61444 - x[offset + 656]; t = (x[offset + 656] ^ x[offset + 672]) & (c >> 31); x[offset + 656] ^= t; x[offset + 672] ^= t;
|
||||
c = 61444 - x[offset + 688]; t = (x[offset + 688] ^ x[offset + 704]) & (c >> 31); x[offset + 688] ^= t; x[offset + 704] ^= t;
|
||||
c = 61444 - x[offset + 720]; t = (x[offset + 720] ^ x[offset + 736]) & (c >> 31); x[offset + 720] ^= t; x[offset + 736] ^= t;
|
||||
c = 61444 - x[offset + 512]; t = (x[offset + 512] ^ x[offset + 640]) & (c >> 31); x[offset + 512] ^= t; x[offset + 640] ^= t;
|
||||
c = 61444 - x[offset + 576]; t = (x[offset + 576] ^ x[offset + 704]) & (c >> 31); x[offset + 576] ^= t; x[offset + 704] ^= t;
|
||||
c = 61444 - x[offset + 576]; t = (x[offset + 576] ^ x[offset + 640]) & (c >> 31); x[offset + 576] ^= t; x[offset + 640] ^= t;
|
||||
c = 61444 - x[offset + 544]; t = (x[offset + 544] ^ x[offset + 672]) & (c >> 31); x[offset + 544] ^= t; x[offset + 672] ^= t;
|
||||
c = 61444 - x[offset + 608]; t = (x[offset + 608] ^ x[offset + 736]) & (c >> 31); x[offset + 608] ^= t; x[offset + 736] ^= t;
|
||||
c = 61444 - x[offset + 608]; t = (x[offset + 608] ^ x[offset + 672]) & (c >> 31); x[offset + 608] ^= t; x[offset + 672] ^= t;
|
||||
c = 61444 - x[offset + 544]; t = (x[offset + 544] ^ x[offset + 576]) & (c >> 31); x[offset + 544] ^= t; x[offset + 576] ^= t;
|
||||
c = 61444 - x[offset + 608]; t = (x[offset + 608] ^ x[offset + 640]) & (c >> 31); x[offset + 608] ^= t; x[offset + 640] ^= t;
|
||||
c = 61444 - x[offset + 672]; t = (x[offset + 672] ^ x[offset + 704]) & (c >> 31); x[offset + 672] ^= t; x[offset + 704] ^= t;
|
||||
c = 61444 - x[offset + 528]; t = (x[offset + 528] ^ x[offset + 656]) & (c >> 31); x[offset + 528] ^= t; x[offset + 656] ^= t;
|
||||
c = 61444 - x[offset + 592]; t = (x[offset + 592] ^ x[offset + 720]) & (c >> 31); x[offset + 592] ^= t; x[offset + 720] ^= t;
|
||||
c = 61444 - x[offset + 592]; t = (x[offset + 592] ^ x[offset + 656]) & (c >> 31); x[offset + 592] ^= t; x[offset + 656] ^= t;
|
||||
c = 61444 - x[offset + 560]; t = (x[offset + 560] ^ x[offset + 688]) & (c >> 31); x[offset + 560] ^= t; x[offset + 688] ^= t;
|
||||
c = 61444 - x[offset + 624]; t = (x[offset + 624] ^ x[offset + 752]) & (c >> 31); x[offset + 624] ^= t; x[offset + 752] ^= t;
|
||||
c = 61444 - x[offset + 624]; t = (x[offset + 624] ^ x[offset + 688]) & (c >> 31); x[offset + 624] ^= t; x[offset + 688] ^= t;
|
||||
c = 61444 - x[offset + 560]; t = (x[offset + 560] ^ x[offset + 592]) & (c >> 31); x[offset + 560] ^= t; x[offset + 592] ^= t;
|
||||
c = 61444 - x[offset + 624]; t = (x[offset + 624] ^ x[offset + 656]) & (c >> 31); x[offset + 624] ^= t; x[offset + 656] ^= t;
|
||||
c = 61444 - x[offset + 688]; t = (x[offset + 688] ^ x[offset + 720]) & (c >> 31); x[offset + 688] ^= t; x[offset + 720] ^= t;
|
||||
c = 61444 - x[offset + 528]; t = (x[offset + 528] ^ x[offset + 544]) & (c >> 31); x[offset + 528] ^= t; x[offset + 544] ^= t;
|
||||
c = 61444 - x[offset + 560]; t = (x[offset + 560] ^ x[offset + 576]) & (c >> 31); x[offset + 560] ^= t; x[offset + 576] ^= t;
|
||||
c = 61444 - x[offset + 592]; t = (x[offset + 592] ^ x[offset + 608]) & (c >> 31); x[offset + 592] ^= t; x[offset + 608] ^= t;
|
||||
c = 61444 - x[offset + 624]; t = (x[offset + 624] ^ x[offset + 640]) & (c >> 31); x[offset + 624] ^= t; x[offset + 640] ^= t;
|
||||
c = 61444 - x[offset + 656]; t = (x[offset + 656] ^ x[offset + 672]) & (c >> 31); x[offset + 656] ^= t; x[offset + 672] ^= t;
|
||||
c = 61444 - x[offset + 688]; t = (x[offset + 688] ^ x[offset + 704]) & (c >> 31); x[offset + 688] ^= t; x[offset + 704] ^= t;
|
||||
c = 61444 - x[offset + 720]; t = (x[offset + 720] ^ x[offset + 736]) & (c >> 31); x[offset + 720] ^= t; x[offset + 736] ^= t;
|
||||
c = 61444 - x[offset + 768]; t = (x[offset + 768] ^ x[offset + 784]) & (c >> 31); x[offset + 768] ^= t; x[offset + 784] ^= t;
|
||||
c = 61444 - x[offset + 800]; t = (x[offset + 800] ^ x[offset + 816]) & (c >> 31); x[offset + 800] ^= t; x[offset + 816] ^= t;
|
||||
c = 61444 - x[offset + 768]; t = (x[offset + 768] ^ x[offset + 800]) & (c >> 31); x[offset + 768] ^= t; x[offset + 800] ^= t;
|
||||
c = 61444 - x[offset + 784]; t = (x[offset + 784] ^ x[offset + 816]) & (c >> 31); x[offset + 784] ^= t; x[offset + 816] ^= t;
|
||||
c = 61444 - x[offset + 784]; t = (x[offset + 784] ^ x[offset + 800]) & (c >> 31); x[offset + 784] ^= t; x[offset + 800] ^= t;
|
||||
c = 61444 - x[offset + 832]; t = (x[offset + 832] ^ x[offset + 848]) & (c >> 31); x[offset + 832] ^= t; x[offset + 848] ^= t;
|
||||
c = 61444 - x[offset + 864]; t = (x[offset + 864] ^ x[offset + 880]) & (c >> 31); x[offset + 864] ^= t; x[offset + 880] ^= t;
|
||||
c = 61444 - x[offset + 832]; t = (x[offset + 832] ^ x[offset + 864]) & (c >> 31); x[offset + 832] ^= t; x[offset + 864] ^= t;
|
||||
c = 61444 - x[offset + 848]; t = (x[offset + 848] ^ x[offset + 880]) & (c >> 31); x[offset + 848] ^= t; x[offset + 880] ^= t;
|
||||
c = 61444 - x[offset + 848]; t = (x[offset + 848] ^ x[offset + 864]) & (c >> 31); x[offset + 848] ^= t; x[offset + 864] ^= t;
|
||||
c = 61444 - x[offset + 768]; t = (x[offset + 768] ^ x[offset + 832]) & (c >> 31); x[offset + 768] ^= t; x[offset + 832] ^= t;
|
||||
c = 61444 - x[offset + 800]; t = (x[offset + 800] ^ x[offset + 864]) & (c >> 31); x[offset + 800] ^= t; x[offset + 864] ^= t;
|
||||
c = 61444 - x[offset + 800]; t = (x[offset + 800] ^ x[offset + 832]) & (c >> 31); x[offset + 800] ^= t; x[offset + 832] ^= t;
|
||||
c = 61444 - x[offset + 784]; t = (x[offset + 784] ^ x[offset + 848]) & (c >> 31); x[offset + 784] ^= t; x[offset + 848] ^= t;
|
||||
c = 61444 - x[offset + 816]; t = (x[offset + 816] ^ x[offset + 880]) & (c >> 31); x[offset + 816] ^= t; x[offset + 880] ^= t;
|
||||
c = 61444 - x[offset + 816]; t = (x[offset + 816] ^ x[offset + 848]) & (c >> 31); x[offset + 816] ^= t; x[offset + 848] ^= t;
|
||||
c = 61444 - x[offset + 784]; t = (x[offset + 784] ^ x[offset + 800]) & (c >> 31); x[offset + 784] ^= t; x[offset + 800] ^= t;
|
||||
c = 61444 - x[offset + 816]; t = (x[offset + 816] ^ x[offset + 832]) & (c >> 31); x[offset + 816] ^= t; x[offset + 832] ^= t;
|
||||
c = 61444 - x[offset + 848]; t = (x[offset + 848] ^ x[offset + 864]) & (c >> 31); x[offset + 848] ^= t; x[offset + 864] ^= t;
|
||||
c = 61444 - x[offset + 896]; t = (x[offset + 896] ^ x[offset + 912]) & (c >> 31); x[offset + 896] ^= t; x[offset + 912] ^= t;
|
||||
c = 61444 - x[offset + 928]; t = (x[offset + 928] ^ x[offset + 944]) & (c >> 31); x[offset + 928] ^= t; x[offset + 944] ^= t;
|
||||
c = 61444 - x[offset + 896]; t = (x[offset + 896] ^ x[offset + 928]) & (c >> 31); x[offset + 896] ^= t; x[offset + 928] ^= t;
|
||||
c = 61444 - x[offset + 912]; t = (x[offset + 912] ^ x[offset + 944]) & (c >> 31); x[offset + 912] ^= t; x[offset + 944] ^= t;
|
||||
c = 61444 - x[offset + 912]; t = (x[offset + 912] ^ x[offset + 928]) & (c >> 31); x[offset + 912] ^= t; x[offset + 928] ^= t;
|
||||
c = 61444 - x[offset + 960]; t = (x[offset + 960] ^ x[offset + 976]) & (c >> 31); x[offset + 960] ^= t; x[offset + 976] ^= t;
|
||||
c = 61444 - x[offset + 992]; t = (x[offset + 992] ^ x[offset + 1008]) & (c >> 31); x[offset + 992] ^= t; x[offset + 1008] ^= t;
|
||||
c = 61444 - x[offset + 960]; t = (x[offset + 960] ^ x[offset + 992]) & (c >> 31); x[offset + 960] ^= t; x[offset + 992] ^= t;
|
||||
c = 61444 - x[offset + 976]; t = (x[offset + 976] ^ x[offset + 1008]) & (c >> 31); x[offset + 976] ^= t; x[offset + 1008] ^= t;
|
||||
c = 61444 - x[offset + 976]; t = (x[offset + 976] ^ x[offset + 992]) & (c >> 31); x[offset + 976] ^= t; x[offset + 992] ^= t;
|
||||
c = 61444 - x[offset + 896]; t = (x[offset + 896] ^ x[offset + 960]) & (c >> 31); x[offset + 896] ^= t; x[offset + 960] ^= t;
|
||||
c = 61444 - x[offset + 928]; t = (x[offset + 928] ^ x[offset + 992]) & (c >> 31); x[offset + 928] ^= t; x[offset + 992] ^= t;
|
||||
c = 61444 - x[offset + 928]; t = (x[offset + 928] ^ x[offset + 960]) & (c >> 31); x[offset + 928] ^= t; x[offset + 960] ^= t;
|
||||
c = 61444 - x[offset + 912]; t = (x[offset + 912] ^ x[offset + 976]) & (c >> 31); x[offset + 912] ^= t; x[offset + 976] ^= t;
|
||||
c = 61444 - x[offset + 944]; t = (x[offset + 944] ^ x[offset + 1008]) & (c >> 31); x[offset + 944] ^= t; x[offset + 1008] ^= t;
|
||||
c = 61444 - x[offset + 944]; t = (x[offset + 944] ^ x[offset + 976]) & (c >> 31); x[offset + 944] ^= t; x[offset + 976] ^= t;
|
||||
c = 61444 - x[offset + 912]; t = (x[offset + 912] ^ x[offset + 928]) & (c >> 31); x[offset + 912] ^= t; x[offset + 928] ^= t;
|
||||
c = 61444 - x[offset + 944]; t = (x[offset + 944] ^ x[offset + 960]) & (c >> 31); x[offset + 944] ^= t; x[offset + 960] ^= t;
|
||||
c = 61444 - x[offset + 976]; t = (x[offset + 976] ^ x[offset + 992]) & (c >> 31); x[offset + 976] ^= t; x[offset + 992] ^= t;
|
||||
c = 61444 - x[offset + 768]; t = (x[offset + 768] ^ x[offset + 896]) & (c >> 31); x[offset + 768] ^= t; x[offset + 896] ^= t;
|
||||
c = 61444 - x[offset + 832]; t = (x[offset + 832] ^ x[offset + 960]) & (c >> 31); x[offset + 832] ^= t; x[offset + 960] ^= t;
|
||||
c = 61444 - x[offset + 832]; t = (x[offset + 832] ^ x[offset + 896]) & (c >> 31); x[offset + 832] ^= t; x[offset + 896] ^= t;
|
||||
c = 61444 - x[offset + 800]; t = (x[offset + 800] ^ x[offset + 928]) & (c >> 31); x[offset + 800] ^= t; x[offset + 928] ^= t;
|
||||
c = 61444 - x[offset + 864]; t = (x[offset + 864] ^ x[offset + 992]) & (c >> 31); x[offset + 864] ^= t; x[offset + 992] ^= t;
|
||||
c = 61444 - x[offset + 864]; t = (x[offset + 864] ^ x[offset + 928]) & (c >> 31); x[offset + 864] ^= t; x[offset + 928] ^= t;
|
||||
c = 61444 - x[offset + 800]; t = (x[offset + 800] ^ x[offset + 832]) & (c >> 31); x[offset + 800] ^= t; x[offset + 832] ^= t;
|
||||
c = 61444 - x[offset + 864]; t = (x[offset + 864] ^ x[offset + 896]) & (c >> 31); x[offset + 864] ^= t; x[offset + 896] ^= t;
|
||||
c = 61444 - x[offset + 928]; t = (x[offset + 928] ^ x[offset + 960]) & (c >> 31); x[offset + 928] ^= t; x[offset + 960] ^= t;
|
||||
c = 61444 - x[offset + 784]; t = (x[offset + 784] ^ x[offset + 912]) & (c >> 31); x[offset + 784] ^= t; x[offset + 912] ^= t;
|
||||
c = 61444 - x[offset + 848]; t = (x[offset + 848] ^ x[offset + 976]) & (c >> 31); x[offset + 848] ^= t; x[offset + 976] ^= t;
|
||||
c = 61444 - x[offset + 848]; t = (x[offset + 848] ^ x[offset + 912]) & (c >> 31); x[offset + 848] ^= t; x[offset + 912] ^= t;
|
||||
c = 61444 - x[offset + 816]; t = (x[offset + 816] ^ x[offset + 944]) & (c >> 31); x[offset + 816] ^= t; x[offset + 944] ^= t;
|
||||
c = 61444 - x[offset + 880]; t = (x[offset + 880] ^ x[offset + 1008]) & (c >> 31); x[offset + 880] ^= t; x[offset + 1008] ^= t;
|
||||
c = 61444 - x[offset + 880]; t = (x[offset + 880] ^ x[offset + 944]) & (c >> 31); x[offset + 880] ^= t; x[offset + 944] ^= t;
|
||||
c = 61444 - x[offset + 816]; t = (x[offset + 816] ^ x[offset + 848]) & (c >> 31); x[offset + 816] ^= t; x[offset + 848] ^= t;
|
||||
c = 61444 - x[offset + 880]; t = (x[offset + 880] ^ x[offset + 912]) & (c >> 31); x[offset + 880] ^= t; x[offset + 912] ^= t;
|
||||
c = 61444 - x[offset + 944]; t = (x[offset + 944] ^ x[offset + 976]) & (c >> 31); x[offset + 944] ^= t; x[offset + 976] ^= t;
|
||||
c = 61444 - x[offset + 784]; t = (x[offset + 784] ^ x[offset + 800]) & (c >> 31); x[offset + 784] ^= t; x[offset + 800] ^= t;
|
||||
c = 61444 - x[offset + 816]; t = (x[offset + 816] ^ x[offset + 832]) & (c >> 31); x[offset + 816] ^= t; x[offset + 832] ^= t;
|
||||
c = 61444 - x[offset + 848]; t = (x[offset + 848] ^ x[offset + 864]) & (c >> 31); x[offset + 848] ^= t; x[offset + 864] ^= t;
|
||||
c = 61444 - x[offset + 880]; t = (x[offset + 880] ^ x[offset + 896]) & (c >> 31); x[offset + 880] ^= t; x[offset + 896] ^= t;
|
||||
c = 61444 - x[offset + 912]; t = (x[offset + 912] ^ x[offset + 928]) & (c >> 31); x[offset + 912] ^= t; x[offset + 928] ^= t;
|
||||
c = 61444 - x[offset + 944]; t = (x[offset + 944] ^ x[offset + 960]) & (c >> 31); x[offset + 944] ^= t; x[offset + 960] ^= t;
|
||||
c = 61444 - x[offset + 976]; t = (x[offset + 976] ^ x[offset + 992]) & (c >> 31); x[offset + 976] ^= t; x[offset + 992] ^= t;
|
||||
c = 61444 - x[offset + 512]; t = (x[offset + 512] ^ x[offset + 768]) & (c >> 31); x[offset + 512] ^= t; x[offset + 768] ^= t;
|
||||
c = 61444 - x[offset + 640]; t = (x[offset + 640] ^ x[offset + 896]) & (c >> 31); x[offset + 640] ^= t; x[offset + 896] ^= t;
|
||||
c = 61444 - x[offset + 640]; t = (x[offset + 640] ^ x[offset + 768]) & (c >> 31); x[offset + 640] ^= t; x[offset + 768] ^= t;
|
||||
c = 61444 - x[offset + 576]; t = (x[offset + 576] ^ x[offset + 832]) & (c >> 31); x[offset + 576] ^= t; x[offset + 832] ^= t;
|
||||
c = 61444 - x[offset + 704]; t = (x[offset + 704] ^ x[offset + 960]) & (c >> 31); x[offset + 704] ^= t; x[offset + 960] ^= t;
|
||||
c = 61444 - x[offset + 704]; t = (x[offset + 704] ^ x[offset + 832]) & (c >> 31); x[offset + 704] ^= t; x[offset + 832] ^= t;
|
||||
c = 61444 - x[offset + 576]; t = (x[offset + 576] ^ x[offset + 640]) & (c >> 31); x[offset + 576] ^= t; x[offset + 640] ^= t;
|
||||
c = 61444 - x[offset + 704]; t = (x[offset + 704] ^ x[offset + 768]) & (c >> 31); x[offset + 704] ^= t; x[offset + 768] ^= t;
|
||||
c = 61444 - x[offset + 832]; t = (x[offset + 832] ^ x[offset + 896]) & (c >> 31); x[offset + 832] ^= t; x[offset + 896] ^= t;
|
||||
c = 61444 - x[offset + 544]; t = (x[offset + 544] ^ x[offset + 800]) & (c >> 31); x[offset + 544] ^= t; x[offset + 800] ^= t;
|
||||
c = 61444 - x[offset + 672]; t = (x[offset + 672] ^ x[offset + 928]) & (c >> 31); x[offset + 672] ^= t; x[offset + 928] ^= t;
|
||||
c = 61444 - x[offset + 672]; t = (x[offset + 672] ^ x[offset + 800]) & (c >> 31); x[offset + 672] ^= t; x[offset + 800] ^= t;
|
||||
c = 61444 - x[offset + 608]; t = (x[offset + 608] ^ x[offset + 864]) & (c >> 31); x[offset + 608] ^= t; x[offset + 864] ^= t;
|
||||
c = 61444 - x[offset + 736]; t = (x[offset + 736] ^ x[offset + 992]) & (c >> 31); x[offset + 736] ^= t; x[offset + 992] ^= t;
|
||||
c = 61444 - x[offset + 736]; t = (x[offset + 736] ^ x[offset + 864]) & (c >> 31); x[offset + 736] ^= t; x[offset + 864] ^= t;
|
||||
c = 61444 - x[offset + 608]; t = (x[offset + 608] ^ x[offset + 672]) & (c >> 31); x[offset + 608] ^= t; x[offset + 672] ^= t;
|
||||
c = 61444 - x[offset + 736]; t = (x[offset + 736] ^ x[offset + 800]) & (c >> 31); x[offset + 736] ^= t; x[offset + 800] ^= t;
|
||||
c = 61444 - x[offset + 864]; t = (x[offset + 864] ^ x[offset + 928]) & (c >> 31); x[offset + 864] ^= t; x[offset + 928] ^= t;
|
||||
c = 61444 - x[offset + 544]; t = (x[offset + 544] ^ x[offset + 576]) & (c >> 31); x[offset + 544] ^= t; x[offset + 576] ^= t;
|
||||
c = 61444 - x[offset + 608]; t = (x[offset + 608] ^ x[offset + 640]) & (c >> 31); x[offset + 608] ^= t; x[offset + 640] ^= t;
|
||||
c = 61444 - x[offset + 672]; t = (x[offset + 672] ^ x[offset + 704]) & (c >> 31); x[offset + 672] ^= t; x[offset + 704] ^= t;
|
||||
c = 61444 - x[offset + 736]; t = (x[offset + 736] ^ x[offset + 768]) & (c >> 31); x[offset + 736] ^= t; x[offset + 768] ^= t;
|
||||
c = 61444 - x[offset + 800]; t = (x[offset + 800] ^ x[offset + 832]) & (c >> 31); x[offset + 800] ^= t; x[offset + 832] ^= t;
|
||||
c = 61444 - x[offset + 864]; t = (x[offset + 864] ^ x[offset + 896]) & (c >> 31); x[offset + 864] ^= t; x[offset + 896] ^= t;
|
||||
c = 61444 - x[offset + 928]; t = (x[offset + 928] ^ x[offset + 960]) & (c >> 31); x[offset + 928] ^= t; x[offset + 960] ^= t;
|
||||
c = 61444 - x[offset + 528]; t = (x[offset + 528] ^ x[offset + 784]) & (c >> 31); x[offset + 528] ^= t; x[offset + 784] ^= t;
|
||||
c = 61444 - x[offset + 656]; t = (x[offset + 656] ^ x[offset + 912]) & (c >> 31); x[offset + 656] ^= t; x[offset + 912] ^= t;
|
||||
c = 61444 - x[offset + 656]; t = (x[offset + 656] ^ x[offset + 784]) & (c >> 31); x[offset + 656] ^= t; x[offset + 784] ^= t;
|
||||
c = 61444 - x[offset + 592]; t = (x[offset + 592] ^ x[offset + 848]) & (c >> 31); x[offset + 592] ^= t; x[offset + 848] ^= t;
|
||||
c = 61444 - x[offset + 720]; t = (x[offset + 720] ^ x[offset + 976]) & (c >> 31); x[offset + 720] ^= t; x[offset + 976] ^= t;
|
||||
c = 61444 - x[offset + 720]; t = (x[offset + 720] ^ x[offset + 848]) & (c >> 31); x[offset + 720] ^= t; x[offset + 848] ^= t;
|
||||
c = 61444 - x[offset + 592]; t = (x[offset + 592] ^ x[offset + 656]) & (c >> 31); x[offset + 592] ^= t; x[offset + 656] ^= t;
|
||||
c = 61444 - x[offset + 720]; t = (x[offset + 720] ^ x[offset + 784]) & (c >> 31); x[offset + 720] ^= t; x[offset + 784] ^= t;
|
||||
c = 61444 - x[offset + 848]; t = (x[offset + 848] ^ x[offset + 912]) & (c >> 31); x[offset + 848] ^= t; x[offset + 912] ^= t;
|
||||
c = 61444 - x[offset + 560]; t = (x[offset + 560] ^ x[offset + 816]) & (c >> 31); x[offset + 560] ^= t; x[offset + 816] ^= t;
|
||||
c = 61444 - x[offset + 688]; t = (x[offset + 688] ^ x[offset + 944]) & (c >> 31); x[offset + 688] ^= t; x[offset + 944] ^= t;
|
||||
c = 61444 - x[offset + 688]; t = (x[offset + 688] ^ x[offset + 816]) & (c >> 31); x[offset + 688] ^= t; x[offset + 816] ^= t;
|
||||
c = 61444 - x[offset + 624]; t = (x[offset + 624] ^ x[offset + 880]) & (c >> 31); x[offset + 624] ^= t; x[offset + 880] ^= t;
|
||||
c = 61444 - x[offset + 752]; t = (x[offset + 752] ^ x[offset + 1008]) & (c >> 31); x[offset + 752] ^= t; x[offset + 1008] ^= t;
|
||||
c = 61444 - x[offset + 752]; t = (x[offset + 752] ^ x[offset + 880]) & (c >> 31); x[offset + 752] ^= t; x[offset + 880] ^= t;
|
||||
c = 61444 - x[offset + 624]; t = (x[offset + 624] ^ x[offset + 688]) & (c >> 31); x[offset + 624] ^= t; x[offset + 688] ^= t;
|
||||
c = 61444 - x[offset + 752]; t = (x[offset + 752] ^ x[offset + 816]) & (c >> 31); x[offset + 752] ^= t; x[offset + 816] ^= t;
|
||||
c = 61444 - x[offset + 880]; t = (x[offset + 880] ^ x[offset + 944]) & (c >> 31); x[offset + 880] ^= t; x[offset + 944] ^= t;
|
||||
c = 61444 - x[offset + 560]; t = (x[offset + 560] ^ x[offset + 592]) & (c >> 31); x[offset + 560] ^= t; x[offset + 592] ^= t;
|
||||
c = 61444 - x[offset + 624]; t = (x[offset + 624] ^ x[offset + 656]) & (c >> 31); x[offset + 624] ^= t; x[offset + 656] ^= t;
|
||||
c = 61444 - x[offset + 688]; t = (x[offset + 688] ^ x[offset + 720]) & (c >> 31); x[offset + 688] ^= t; x[offset + 720] ^= t;
|
||||
c = 61444 - x[offset + 752]; t = (x[offset + 752] ^ x[offset + 784]) & (c >> 31); x[offset + 752] ^= t; x[offset + 784] ^= t;
|
||||
c = 61444 - x[offset + 816]; t = (x[offset + 816] ^ x[offset + 848]) & (c >> 31); x[offset + 816] ^= t; x[offset + 848] ^= t;
|
||||
c = 61444 - x[offset + 880]; t = (x[offset + 880] ^ x[offset + 912]) & (c >> 31); x[offset + 880] ^= t; x[offset + 912] ^= t;
|
||||
c = 61444 - x[offset + 944]; t = (x[offset + 944] ^ x[offset + 976]) & (c >> 31); x[offset + 944] ^= t; x[offset + 976] ^= t;
|
||||
c = 61444 - x[offset + 528]; t = (x[offset + 528] ^ x[offset + 544]) & (c >> 31); x[offset + 528] ^= t; x[offset + 544] ^= t;
|
||||
c = 61444 - x[offset + 560]; t = (x[offset + 560] ^ x[offset + 576]) & (c >> 31); x[offset + 560] ^= t; x[offset + 576] ^= t;
|
||||
c = 61444 - x[offset + 592]; t = (x[offset + 592] ^ x[offset + 608]) & (c >> 31); x[offset + 592] ^= t; x[offset + 608] ^= t;
|
||||
c = 61444 - x[offset + 624]; t = (x[offset + 624] ^ x[offset + 640]) & (c >> 31); x[offset + 624] ^= t; x[offset + 640] ^= t;
|
||||
c = 61444 - x[offset + 656]; t = (x[offset + 656] ^ x[offset + 672]) & (c >> 31); x[offset + 656] ^= t; x[offset + 672] ^= t;
|
||||
c = 61444 - x[offset + 688]; t = (x[offset + 688] ^ x[offset + 704]) & (c >> 31); x[offset + 688] ^= t; x[offset + 704] ^= t;
|
||||
c = 61444 - x[offset + 720]; t = (x[offset + 720] ^ x[offset + 736]) & (c >> 31); x[offset + 720] ^= t; x[offset + 736] ^= t;
|
||||
c = 61444 - x[offset + 752]; t = (x[offset + 752] ^ x[offset + 768]) & (c >> 31); x[offset + 752] ^= t; x[offset + 768] ^= t;
|
||||
c = 61444 - x[offset + 784]; t = (x[offset + 784] ^ x[offset + 800]) & (c >> 31); x[offset + 784] ^= t; x[offset + 800] ^= t;
|
||||
c = 61444 - x[offset + 816]; t = (x[offset + 816] ^ x[offset + 832]) & (c >> 31); x[offset + 816] ^= t; x[offset + 832] ^= t;
|
||||
c = 61444 - x[offset + 848]; t = (x[offset + 848] ^ x[offset + 864]) & (c >> 31); x[offset + 848] ^= t; x[offset + 864] ^= t;
|
||||
c = 61444 - x[offset + 880]; t = (x[offset + 880] ^ x[offset + 896]) & (c >> 31); x[offset + 880] ^= t; x[offset + 896] ^= t;
|
||||
c = 61444 - x[offset + 912]; t = (x[offset + 912] ^ x[offset + 928]) & (c >> 31); x[offset + 912] ^= t; x[offset + 928] ^= t;
|
||||
c = 61444 - x[offset + 944]; t = (x[offset + 944] ^ x[offset + 960]) & (c >> 31); x[offset + 944] ^= t; x[offset + 960] ^= t;
|
||||
c = 61444 - x[offset + 976]; t = (x[offset + 976] ^ x[offset + 992]) & (c >> 31); x[offset + 976] ^= t; x[offset + 992] ^= t;
|
||||
c = 61444 - x[offset + 0]; t = (x[offset + 0] ^ x[offset + 512]) & (c >> 31); x[offset + 0] ^= t; x[offset + 512] ^= t;
|
||||
c = 61444 - x[offset + 256]; t = (x[offset + 256] ^ x[offset + 768]) & (c >> 31); x[offset + 256] ^= t; x[offset + 768] ^= t;
|
||||
c = 61444 - x[offset + 256]; t = (x[offset + 256] ^ x[offset + 512]) & (c >> 31); x[offset + 256] ^= t; x[offset + 512] ^= t;
|
||||
c = 61444 - x[offset + 128]; t = (x[offset + 128] ^ x[offset + 640]) & (c >> 31); x[offset + 128] ^= t; x[offset + 640] ^= t;
|
||||
c = 61444 - x[offset + 384]; t = (x[offset + 384] ^ x[offset + 896]) & (c >> 31); x[offset + 384] ^= t; x[offset + 896] ^= t;
|
||||
c = 61444 - x[offset + 384]; t = (x[offset + 384] ^ x[offset + 640]) & (c >> 31); x[offset + 384] ^= t; x[offset + 640] ^= t;
|
||||
c = 61444 - x[offset + 128]; t = (x[offset + 128] ^ x[offset + 256]) & (c >> 31); x[offset + 128] ^= t; x[offset + 256] ^= t;
|
||||
c = 61444 - x[offset + 384]; t = (x[offset + 384] ^ x[offset + 512]) & (c >> 31); x[offset + 384] ^= t; x[offset + 512] ^= t;
|
||||
c = 61444 - x[offset + 640]; t = (x[offset + 640] ^ x[offset + 768]) & (c >> 31); x[offset + 640] ^= t; x[offset + 768] ^= t;
|
||||
c = 61444 - x[offset + 64]; t = (x[offset + 64] ^ x[offset + 576]) & (c >> 31); x[offset + 64] ^= t; x[offset + 576] ^= t;
|
||||
c = 61444 - x[offset + 320]; t = (x[offset + 320] ^ x[offset + 832]) & (c >> 31); x[offset + 320] ^= t; x[offset + 832] ^= t;
|
||||
c = 61444 - x[offset + 320]; t = (x[offset + 320] ^ x[offset + 576]) & (c >> 31); x[offset + 320] ^= t; x[offset + 576] ^= t;
|
||||
c = 61444 - x[offset + 192]; t = (x[offset + 192] ^ x[offset + 704]) & (c >> 31); x[offset + 192] ^= t; x[offset + 704] ^= t;
|
||||
c = 61444 - x[offset + 448]; t = (x[offset + 448] ^ x[offset + 960]) & (c >> 31); x[offset + 448] ^= t; x[offset + 960] ^= t;
|
||||
c = 61444 - x[offset + 448]; t = (x[offset + 448] ^ x[offset + 704]) & (c >> 31); x[offset + 448] ^= t; x[offset + 704] ^= t;
|
||||
c = 61444 - x[offset + 192]; t = (x[offset + 192] ^ x[offset + 320]) & (c >> 31); x[offset + 192] ^= t; x[offset + 320] ^= t;
|
||||
c = 61444 - x[offset + 448]; t = (x[offset + 448] ^ x[offset + 576]) & (c >> 31); x[offset + 448] ^= t; x[offset + 576] ^= t;
|
||||
c = 61444 - x[offset + 704]; t = (x[offset + 704] ^ x[offset + 832]) & (c >> 31); x[offset + 704] ^= t; x[offset + 832] ^= t;
|
||||
c = 61444 - x[offset + 64]; t = (x[offset + 64] ^ x[offset + 128]) & (c >> 31); x[offset + 64] ^= t; x[offset + 128] ^= t;
|
||||
c = 61444 - x[offset + 192]; t = (x[offset + 192] ^ x[offset + 256]) & (c >> 31); x[offset + 192] ^= t; x[offset + 256] ^= t;
|
||||
c = 61444 - x[offset + 320]; t = (x[offset + 320] ^ x[offset + 384]) & (c >> 31); x[offset + 320] ^= t; x[offset + 384] ^= t;
|
||||
c = 61444 - x[offset + 448]; t = (x[offset + 448] ^ x[offset + 512]) & (c >> 31); x[offset + 448] ^= t; x[offset + 512] ^= t;
|
||||
c = 61444 - x[offset + 576]; t = (x[offset + 576] ^ x[offset + 640]) & (c >> 31); x[offset + 576] ^= t; x[offset + 640] ^= t;
|
||||
c = 61444 - x[offset + 704]; t = (x[offset + 704] ^ x[offset + 768]) & (c >> 31); x[offset + 704] ^= t; x[offset + 768] ^= t;
|
||||
c = 61444 - x[offset + 832]; t = (x[offset + 832] ^ x[offset + 896]) & (c >> 31); x[offset + 832] ^= t; x[offset + 896] ^= t;
|
||||
c = 61444 - x[offset + 32]; t = (x[offset + 32] ^ x[offset + 544]) & (c >> 31); x[offset + 32] ^= t; x[offset + 544] ^= t;
|
||||
c = 61444 - x[offset + 288]; t = (x[offset + 288] ^ x[offset + 800]) & (c >> 31); x[offset + 288] ^= t; x[offset + 800] ^= t;
|
||||
c = 61444 - x[offset + 288]; t = (x[offset + 288] ^ x[offset + 544]) & (c >> 31); x[offset + 288] ^= t; x[offset + 544] ^= t;
|
||||
c = 61444 - x[offset + 160]; t = (x[offset + 160] ^ x[offset + 672]) & (c >> 31); x[offset + 160] ^= t; x[offset + 672] ^= t;
|
||||
c = 61444 - x[offset + 416]; t = (x[offset + 416] ^ x[offset + 928]) & (c >> 31); x[offset + 416] ^= t; x[offset + 928] ^= t;
|
||||
c = 61444 - x[offset + 416]; t = (x[offset + 416] ^ x[offset + 672]) & (c >> 31); x[offset + 416] ^= t; x[offset + 672] ^= t;
|
||||
c = 61444 - x[offset + 160]; t = (x[offset + 160] ^ x[offset + 288]) & (c >> 31); x[offset + 160] ^= t; x[offset + 288] ^= t;
|
||||
c = 61444 - x[offset + 416]; t = (x[offset + 416] ^ x[offset + 544]) & (c >> 31); x[offset + 416] ^= t; x[offset + 544] ^= t;
|
||||
c = 61444 - x[offset + 672]; t = (x[offset + 672] ^ x[offset + 800]) & (c >> 31); x[offset + 672] ^= t; x[offset + 800] ^= t;
|
||||
c = 61444 - x[offset + 96]; t = (x[offset + 96] ^ x[offset + 608]) & (c >> 31); x[offset + 96] ^= t; x[offset + 608] ^= t;
|
||||
c = 61444 - x[offset + 352]; t = (x[offset + 352] ^ x[offset + 864]) & (c >> 31); x[offset + 352] ^= t; x[offset + 864] ^= t;
|
||||
c = 61444 - x[offset + 352]; t = (x[offset + 352] ^ x[offset + 608]) & (c >> 31); x[offset + 352] ^= t; x[offset + 608] ^= t;
|
||||
c = 61444 - x[offset + 224]; t = (x[offset + 224] ^ x[offset + 736]) & (c >> 31); x[offset + 224] ^= t; x[offset + 736] ^= t;
|
||||
c = 61444 - x[offset + 480]; t = (x[offset + 480] ^ x[offset + 992]) & (c >> 31); x[offset + 480] ^= t; x[offset + 992] ^= t;
|
||||
c = 61444 - x[offset + 480]; t = (x[offset + 480] ^ x[offset + 736]) & (c >> 31); x[offset + 480] ^= t; x[offset + 736] ^= t;
|
||||
c = 61444 - x[offset + 224]; t = (x[offset + 224] ^ x[offset + 352]) & (c >> 31); x[offset + 224] ^= t; x[offset + 352] ^= t;
|
||||
c = 61444 - x[offset + 480]; t = (x[offset + 480] ^ x[offset + 608]) & (c >> 31); x[offset + 480] ^= t; x[offset + 608] ^= t;
|
||||
c = 61444 - x[offset + 736]; t = (x[offset + 736] ^ x[offset + 864]) & (c >> 31); x[offset + 736] ^= t; x[offset + 864] ^= t;
|
||||
c = 61444 - x[offset + 96]; t = (x[offset + 96] ^ x[offset + 160]) & (c >> 31); x[offset + 96] ^= t; x[offset + 160] ^= t;
|
||||
c = 61444 - x[offset + 224]; t = (x[offset + 224] ^ x[offset + 288]) & (c >> 31); x[offset + 224] ^= t; x[offset + 288] ^= t;
|
||||
c = 61444 - x[offset + 352]; t = (x[offset + 352] ^ x[offset + 416]) & (c >> 31); x[offset + 352] ^= t; x[offset + 416] ^= t;
|
||||
c = 61444 - x[offset + 480]; t = (x[offset + 480] ^ x[offset + 544]) & (c >> 31); x[offset + 480] ^= t; x[offset + 544] ^= t;
|
||||
c = 61444 - x[offset + 608]; t = (x[offset + 608] ^ x[offset + 672]) & (c >> 31); x[offset + 608] ^= t; x[offset + 672] ^= t;
|
||||
c = 61444 - x[offset + 736]; t = (x[offset + 736] ^ x[offset + 800]) & (c >> 31); x[offset + 736] ^= t; x[offset + 800] ^= t;
|
||||
c = 61444 - x[offset + 864]; t = (x[offset + 864] ^ x[offset + 928]) & (c >> 31); x[offset + 864] ^= t; x[offset + 928] ^= t;
|
||||
c = 61444 - x[offset + 32]; t = (x[offset + 32] ^ x[offset + 64]) & (c >> 31); x[offset + 32] ^= t; x[offset + 64] ^= t;
|
||||
c = 61444 - x[offset + 96]; t = (x[offset + 96] ^ x[offset + 128]) & (c >> 31); x[offset + 96] ^= t; x[offset + 128] ^= t;
|
||||
c = 61444 - x[offset + 160]; t = (x[offset + 160] ^ x[offset + 192]) & (c >> 31); x[offset + 160] ^= t; x[offset + 192] ^= t;
|
||||
c = 61444 - x[offset + 224]; t = (x[offset + 224] ^ x[offset + 256]) & (c >> 31); x[offset + 224] ^= t; x[offset + 256] ^= t;
|
||||
c = 61444 - x[offset + 288]; t = (x[offset + 288] ^ x[offset + 320]) & (c >> 31); x[offset + 288] ^= t; x[offset + 320] ^= t;
|
||||
c = 61444 - x[offset + 352]; t = (x[offset + 352] ^ x[offset + 384]) & (c >> 31); x[offset + 352] ^= t; x[offset + 384] ^= t;
|
||||
c = 61444 - x[offset + 416]; t = (x[offset + 416] ^ x[offset + 448]) & (c >> 31); x[offset + 416] ^= t; x[offset + 448] ^= t;
|
||||
c = 61444 - x[offset + 480]; t = (x[offset + 480] ^ x[offset + 512]) & (c >> 31); x[offset + 480] ^= t; x[offset + 512] ^= t;
|
||||
c = 61444 - x[offset + 544]; t = (x[offset + 544] ^ x[offset + 576]) & (c >> 31); x[offset + 544] ^= t; x[offset + 576] ^= t;
|
||||
c = 61444 - x[offset + 608]; t = (x[offset + 608] ^ x[offset + 640]) & (c >> 31); x[offset + 608] ^= t; x[offset + 640] ^= t;
|
||||
c = 61444 - x[offset + 672]; t = (x[offset + 672] ^ x[offset + 704]) & (c >> 31); x[offset + 672] ^= t; x[offset + 704] ^= t;
|
||||
c = 61444 - x[offset + 736]; t = (x[offset + 736] ^ x[offset + 768]) & (c >> 31); x[offset + 736] ^= t; x[offset + 768] ^= t;
|
||||
c = 61444 - x[offset + 800]; t = (x[offset + 800] ^ x[offset + 832]) & (c >> 31); x[offset + 800] ^= t; x[offset + 832] ^= t;
|
||||
c = 61444 - x[offset + 864]; t = (x[offset + 864] ^ x[offset + 896]) & (c >> 31); x[offset + 864] ^= t; x[offset + 896] ^= t;
|
||||
c = 61444 - x[offset + 928]; t = (x[offset + 928] ^ x[offset + 960]) & (c >> 31); x[offset + 928] ^= t; x[offset + 960] ^= t;
|
||||
c = 61444 - x[offset + 16]; t = (x[offset + 16] ^ x[offset + 528]) & (c >> 31); x[offset + 16] ^= t; x[offset + 528] ^= t;
|
||||
c = 61444 - x[offset + 272]; t = (x[offset + 272] ^ x[offset + 784]) & (c >> 31); x[offset + 272] ^= t; x[offset + 784] ^= t;
|
||||
c = 61444 - x[offset + 272]; t = (x[offset + 272] ^ x[offset + 528]) & (c >> 31); x[offset + 272] ^= t; x[offset + 528] ^= t;
|
||||
c = 61444 - x[offset + 144]; t = (x[offset + 144] ^ x[offset + 656]) & (c >> 31); x[offset + 144] ^= t; x[offset + 656] ^= t;
|
||||
c = 61444 - x[offset + 400]; t = (x[offset + 400] ^ x[offset + 912]) & (c >> 31); x[offset + 400] ^= t; x[offset + 912] ^= t;
|
||||
c = 61444 - x[offset + 400]; t = (x[offset + 400] ^ x[offset + 656]) & (c >> 31); x[offset + 400] ^= t; x[offset + 656] ^= t;
|
||||
c = 61444 - x[offset + 144]; t = (x[offset + 144] ^ x[offset + 272]) & (c >> 31); x[offset + 144] ^= t; x[offset + 272] ^= t;
|
||||
c = 61444 - x[offset + 400]; t = (x[offset + 400] ^ x[offset + 528]) & (c >> 31); x[offset + 400] ^= t; x[offset + 528] ^= t;
|
||||
c = 61444 - x[offset + 656]; t = (x[offset + 656] ^ x[offset + 784]) & (c >> 31); x[offset + 656] ^= t; x[offset + 784] ^= t;
|
||||
c = 61444 - x[offset + 80]; t = (x[offset + 80] ^ x[offset + 592]) & (c >> 31); x[offset + 80] ^= t; x[offset + 592] ^= t;
|
||||
c = 61444 - x[offset + 336]; t = (x[offset + 336] ^ x[offset + 848]) & (c >> 31); x[offset + 336] ^= t; x[offset + 848] ^= t;
|
||||
c = 61444 - x[offset + 336]; t = (x[offset + 336] ^ x[offset + 592]) & (c >> 31); x[offset + 336] ^= t; x[offset + 592] ^= t;
|
||||
c = 61444 - x[offset + 208]; t = (x[offset + 208] ^ x[offset + 720]) & (c >> 31); x[offset + 208] ^= t; x[offset + 720] ^= t;
|
||||
c = 61444 - x[offset + 464]; t = (x[offset + 464] ^ x[offset + 976]) & (c >> 31); x[offset + 464] ^= t; x[offset + 976] ^= t;
|
||||
c = 61444 - x[offset + 464]; t = (x[offset + 464] ^ x[offset + 720]) & (c >> 31); x[offset + 464] ^= t; x[offset + 720] ^= t;
|
||||
c = 61444 - x[offset + 208]; t = (x[offset + 208] ^ x[offset + 336]) & (c >> 31); x[offset + 208] ^= t; x[offset + 336] ^= t;
|
||||
c = 61444 - x[offset + 464]; t = (x[offset + 464] ^ x[offset + 592]) & (c >> 31); x[offset + 464] ^= t; x[offset + 592] ^= t;
|
||||
c = 61444 - x[offset + 720]; t = (x[offset + 720] ^ x[offset + 848]) & (c >> 31); x[offset + 720] ^= t; x[offset + 848] ^= t;
|
||||
c = 61444 - x[offset + 80]; t = (x[offset + 80] ^ x[offset + 144]) & (c >> 31); x[offset + 80] ^= t; x[offset + 144] ^= t;
|
||||
c = 61444 - x[offset + 208]; t = (x[offset + 208] ^ x[offset + 272]) & (c >> 31); x[offset + 208] ^= t; x[offset + 272] ^= t;
|
||||
c = 61444 - x[offset + 336]; t = (x[offset + 336] ^ x[offset + 400]) & (c >> 31); x[offset + 336] ^= t; x[offset + 400] ^= t;
|
||||
c = 61444 - x[offset + 464]; t = (x[offset + 464] ^ x[offset + 528]) & (c >> 31); x[offset + 464] ^= t; x[offset + 528] ^= t;
|
||||
c = 61444 - x[offset + 592]; t = (x[offset + 592] ^ x[offset + 656]) & (c >> 31); x[offset + 592] ^= t; x[offset + 656] ^= t;
|
||||
c = 61444 - x[offset + 720]; t = (x[offset + 720] ^ x[offset + 784]) & (c >> 31); x[offset + 720] ^= t; x[offset + 784] ^= t;
|
||||
c = 61444 - x[offset + 848]; t = (x[offset + 848] ^ x[offset + 912]) & (c >> 31); x[offset + 848] ^= t; x[offset + 912] ^= t;
|
||||
c = 61444 - x[offset + 48]; t = (x[offset + 48] ^ x[offset + 560]) & (c >> 31); x[offset + 48] ^= t; x[offset + 560] ^= t;
|
||||
c = 61444 - x[offset + 304]; t = (x[offset + 304] ^ x[offset + 816]) & (c >> 31); x[offset + 304] ^= t; x[offset + 816] ^= t;
|
||||
c = 61444 - x[offset + 304]; t = (x[offset + 304] ^ x[offset + 560]) & (c >> 31); x[offset + 304] ^= t; x[offset + 560] ^= t;
|
||||
c = 61444 - x[offset + 176]; t = (x[offset + 176] ^ x[offset + 688]) & (c >> 31); x[offset + 176] ^= t; x[offset + 688] ^= t;
|
||||
c = 61444 - x[offset + 432]; t = (x[offset + 432] ^ x[offset + 944]) & (c >> 31); x[offset + 432] ^= t; x[offset + 944] ^= t;
|
||||
c = 61444 - x[offset + 432]; t = (x[offset + 432] ^ x[offset + 688]) & (c >> 31); x[offset + 432] ^= t; x[offset + 688] ^= t;
|
||||
c = 61444 - x[offset + 176]; t = (x[offset + 176] ^ x[offset + 304]) & (c >> 31); x[offset + 176] ^= t; x[offset + 304] ^= t;
|
||||
c = 61444 - x[offset + 432]; t = (x[offset + 432] ^ x[offset + 560]) & (c >> 31); x[offset + 432] ^= t; x[offset + 560] ^= t;
|
||||
c = 61444 - x[offset + 688]; t = (x[offset + 688] ^ x[offset + 816]) & (c >> 31); x[offset + 688] ^= t; x[offset + 816] ^= t;
|
||||
c = 61444 - x[offset + 112]; t = (x[offset + 112] ^ x[offset + 624]) & (c >> 31); x[offset + 112] ^= t; x[offset + 624] ^= t;
|
||||
c = 61444 - x[offset + 368]; t = (x[offset + 368] ^ x[offset + 880]) & (c >> 31); x[offset + 368] ^= t; x[offset + 880] ^= t;
|
||||
c = 61444 - x[offset + 368]; t = (x[offset + 368] ^ x[offset + 624]) & (c >> 31); x[offset + 368] ^= t; x[offset + 624] ^= t;
|
||||
c = 61444 - x[offset + 240]; t = (x[offset + 240] ^ x[offset + 752]) & (c >> 31); x[offset + 240] ^= t; x[offset + 752] ^= t;
|
||||
c = 61444 - x[offset + 496]; t = (x[offset + 496] ^ x[offset + 1008]) & (c >> 31); x[offset + 496] ^= t; x[offset + 1008] ^= t;
|
||||
c = 61444 - x[offset + 496]; t = (x[offset + 496] ^ x[offset + 752]) & (c >> 31); x[offset + 496] ^= t; x[offset + 752] ^= t;
|
||||
c = 61444 - x[offset + 240]; t = (x[offset + 240] ^ x[offset + 368]) & (c >> 31); x[offset + 240] ^= t; x[offset + 368] ^= t;
|
||||
c = 61444 - x[offset + 496]; t = (x[offset + 496] ^ x[offset + 624]) & (c >> 31); x[offset + 496] ^= t; x[offset + 624] ^= t;
|
||||
c = 61444 - x[offset + 752]; t = (x[offset + 752] ^ x[offset + 880]) & (c >> 31); x[offset + 752] ^= t; x[offset + 880] ^= t;
|
||||
c = 61444 - x[offset + 112]; t = (x[offset + 112] ^ x[offset + 176]) & (c >> 31); x[offset + 112] ^= t; x[offset + 176] ^= t;
|
||||
c = 61444 - x[offset + 240]; t = (x[offset + 240] ^ x[offset + 304]) & (c >> 31); x[offset + 240] ^= t; x[offset + 304] ^= t;
|
||||
c = 61444 - x[offset + 368]; t = (x[offset + 368] ^ x[offset + 432]) & (c >> 31); x[offset + 368] ^= t; x[offset + 432] ^= t;
|
||||
c = 61444 - x[offset + 496]; t = (x[offset + 496] ^ x[offset + 560]) & (c >> 31); x[offset + 496] ^= t; x[offset + 560] ^= t;
|
||||
c = 61444 - x[offset + 624]; t = (x[offset + 624] ^ x[offset + 688]) & (c >> 31); x[offset + 624] ^= t; x[offset + 688] ^= t;
|
||||
c = 61444 - x[offset + 752]; t = (x[offset + 752] ^ x[offset + 816]) & (c >> 31); x[offset + 752] ^= t; x[offset + 816] ^= t;
|
||||
c = 61444 - x[offset + 880]; t = (x[offset + 880] ^ x[offset + 944]) & (c >> 31); x[offset + 880] ^= t; x[offset + 944] ^= t;
|
||||
c = 61444 - x[offset + 48]; t = (x[offset + 48] ^ x[offset + 80]) & (c >> 31); x[offset + 48] ^= t; x[offset + 80] ^= t;
|
||||
c = 61444 - x[offset + 112]; t = (x[offset + 112] ^ x[offset + 144]) & (c >> 31); x[offset + 112] ^= t; x[offset + 144] ^= t;
|
||||
c = 61444 - x[offset + 176]; t = (x[offset + 176] ^ x[offset + 208]) & (c >> 31); x[offset + 176] ^= t; x[offset + 208] ^= t;
|
||||
c = 61444 - x[offset + 240]; t = (x[offset + 240] ^ x[offset + 272]) & (c >> 31); x[offset + 240] ^= t; x[offset + 272] ^= t;
|
||||
c = 61444 - x[offset + 304]; t = (x[offset + 304] ^ x[offset + 336]) & (c >> 31); x[offset + 304] ^= t; x[offset + 336] ^= t;
|
||||
c = 61444 - x[offset + 368]; t = (x[offset + 368] ^ x[offset + 400]) & (c >> 31); x[offset + 368] ^= t; x[offset + 400] ^= t;
|
||||
c = 61444 - x[offset + 432]; t = (x[offset + 432] ^ x[offset + 464]) & (c >> 31); x[offset + 432] ^= t; x[offset + 464] ^= t;
|
||||
c = 61444 - x[offset + 496]; t = (x[offset + 496] ^ x[offset + 528]) & (c >> 31); x[offset + 496] ^= t; x[offset + 528] ^= t;
|
||||
c = 61444 - x[offset + 560]; t = (x[offset + 560] ^ x[offset + 592]) & (c >> 31); x[offset + 560] ^= t; x[offset + 592] ^= t;
|
||||
c = 61444 - x[offset + 624]; t = (x[offset + 624] ^ x[offset + 656]) & (c >> 31); x[offset + 624] ^= t; x[offset + 656] ^= t;
|
||||
c = 61444 - x[offset + 688]; t = (x[offset + 688] ^ x[offset + 720]) & (c >> 31); x[offset + 688] ^= t; x[offset + 720] ^= t;
|
||||
c = 61444 - x[offset + 752]; t = (x[offset + 752] ^ x[offset + 784]) & (c >> 31); x[offset + 752] ^= t; x[offset + 784] ^= t;
|
||||
c = 61444 - x[offset + 816]; t = (x[offset + 816] ^ x[offset + 848]) & (c >> 31); x[offset + 816] ^= t; x[offset + 848] ^= t;
|
||||
c = 61444 - x[offset + 880]; t = (x[offset + 880] ^ x[offset + 912]) & (c >> 31); x[offset + 880] ^= t; x[offset + 912] ^= t;
|
||||
c = 61444 - x[offset + 944]; t = (x[offset + 944] ^ x[offset + 976]) & (c >> 31); x[offset + 944] ^= t; x[offset + 976] ^= t;
|
||||
c = 61444 - x[offset + 16]; t = (x[offset + 16] ^ x[offset + 32]) & (c >> 31); x[offset + 16] ^= t; x[offset + 32] ^= t;
|
||||
c = 61444 - x[offset + 48]; t = (x[offset + 48] ^ x[offset + 64]) & (c >> 31); x[offset + 48] ^= t; x[offset + 64] ^= t;
|
||||
c = 61444 - x[offset + 80]; t = (x[offset + 80] ^ x[offset + 96]) & (c >> 31); x[offset + 80] ^= t; x[offset + 96] ^= t;
|
||||
c = 61444 - x[offset + 112]; t = (x[offset + 112] ^ x[offset + 128]) & (c >> 31); x[offset + 112] ^= t; x[offset + 128] ^= t;
|
||||
c = 61444 - x[offset + 144]; t = (x[offset + 144] ^ x[offset + 160]) & (c >> 31); x[offset + 144] ^= t; x[offset + 160] ^= t;
|
||||
c = 61444 - x[offset + 176]; t = (x[offset + 176] ^ x[offset + 192]) & (c >> 31); x[offset + 176] ^= t; x[offset + 192] ^= t;
|
||||
c = 61444 - x[offset + 208]; t = (x[offset + 208] ^ x[offset + 224]) & (c >> 31); x[offset + 208] ^= t; x[offset + 224] ^= t;
|
||||
c = 61444 - x[offset + 240]; t = (x[offset + 240] ^ x[offset + 256]) & (c >> 31); x[offset + 240] ^= t; x[offset + 256] ^= t;
|
||||
c = 61444 - x[offset + 272]; t = (x[offset + 272] ^ x[offset + 288]) & (c >> 31); x[offset + 272] ^= t; x[offset + 288] ^= t;
|
||||
c = 61444 - x[offset + 304]; t = (x[offset + 304] ^ x[offset + 320]) & (c >> 31); x[offset + 304] ^= t; x[offset + 320] ^= t;
|
||||
c = 61444 - x[offset + 336]; t = (x[offset + 336] ^ x[offset + 352]) & (c >> 31); x[offset + 336] ^= t; x[offset + 352] ^= t;
|
||||
c = 61444 - x[offset + 368]; t = (x[offset + 368] ^ x[offset + 384]) & (c >> 31); x[offset + 368] ^= t; x[offset + 384] ^= t;
|
||||
c = 61444 - x[offset + 400]; t = (x[offset + 400] ^ x[offset + 416]) & (c >> 31); x[offset + 400] ^= t; x[offset + 416] ^= t;
|
||||
c = 61444 - x[offset + 432]; t = (x[offset + 432] ^ x[offset + 448]) & (c >> 31); x[offset + 432] ^= t; x[offset + 448] ^= t;
|
||||
c = 61444 - x[offset + 464]; t = (x[offset + 464] ^ x[offset + 480]) & (c >> 31); x[offset + 464] ^= t; x[offset + 480] ^= t;
|
||||
c = 61444 - x[offset + 496]; t = (x[offset + 496] ^ x[offset + 512]) & (c >> 31); x[offset + 496] ^= t; x[offset + 512] ^= t;
|
||||
c = 61444 - x[offset + 528]; t = (x[offset + 528] ^ x[offset + 544]) & (c >> 31); x[offset + 528] ^= t; x[offset + 544] ^= t;
|
||||
c = 61444 - x[offset + 560]; t = (x[offset + 560] ^ x[offset + 576]) & (c >> 31); x[offset + 560] ^= t; x[offset + 576] ^= t;
|
||||
c = 61444 - x[offset + 592]; t = (x[offset + 592] ^ x[offset + 608]) & (c >> 31); x[offset + 592] ^= t; x[offset + 608] ^= t;
|
||||
c = 61444 - x[offset + 624]; t = (x[offset + 624] ^ x[offset + 640]) & (c >> 31); x[offset + 624] ^= t; x[offset + 640] ^= t;
|
||||
c = 61444 - x[offset + 656]; t = (x[offset + 656] ^ x[offset + 672]) & (c >> 31); x[offset + 656] ^= t; x[offset + 672] ^= t;
|
||||
c = 61444 - x[offset + 688]; t = (x[offset + 688] ^ x[offset + 704]) & (c >> 31); x[offset + 688] ^= t; x[offset + 704] ^= t;
|
||||
c = 61444 - x[offset + 720]; t = (x[offset + 720] ^ x[offset + 736]) & (c >> 31); x[offset + 720] ^= t; x[offset + 736] ^= t;
|
||||
c = 61444 - x[offset + 752]; t = (x[offset + 752] ^ x[offset + 768]) & (c >> 31); x[offset + 752] ^= t; x[offset + 768] ^= t;
|
||||
c = 61444 - x[offset + 784]; t = (x[offset + 784] ^ x[offset + 800]) & (c >> 31); x[offset + 784] ^= t; x[offset + 800] ^= t;
|
||||
c = 61444 - x[offset + 816]; t = (x[offset + 816] ^ x[offset + 832]) & (c >> 31); x[offset + 816] ^= t; x[offset + 832] ^= t;
|
||||
c = 61444 - x[offset + 848]; t = (x[offset + 848] ^ x[offset + 864]) & (c >> 31); x[offset + 848] ^= t; x[offset + 864] ^= t;
|
||||
c = 61444 - x[offset + 880]; t = (x[offset + 880] ^ x[offset + 896]) & (c >> 31); x[offset + 880] ^= t; x[offset + 896] ^= t;
|
||||
c = 61444 - x[offset + 912]; t = (x[offset + 912] ^ x[offset + 928]) & (c >> 31); x[offset + 912] ^= t; x[offset + 928] ^= t;
|
||||
c = 61444 - x[offset + 944]; t = (x[offset + 944] ^ x[offset + 960]) & (c >> 31); x[offset + 944] ^= t; x[offset + 960] ^= t;
|
||||
c = 61444 - x[offset + 976]; t = (x[offset + 976] ^ x[offset + 992]) & (c >> 31); x[offset + 976] ^= t; x[offset + 992] ^= t;
|
||||
c = 61444 - x[offset + 1024]; t = (x[offset + 1024] ^ x[offset + 1040]) & (c >> 31); x[offset + 1024] ^= t; x[offset + 1040] ^= t;
|
||||
c = 61444 - x[offset + 1056]; t = (x[offset + 1056] ^ x[offset + 1072]) & (c >> 31); x[offset + 1056] ^= t; x[offset + 1072] ^= t;
|
||||
c = 61444 - x[offset + 1024]; t = (x[offset + 1024] ^ x[offset + 1056]) & (c >> 31); x[offset + 1024] ^= t; x[offset + 1056] ^= t;
|
||||
c = 61444 - x[offset + 1040]; t = (x[offset + 1040] ^ x[offset + 1072]) & (c >> 31); x[offset + 1040] ^= t; x[offset + 1072] ^= t;
|
||||
c = 61444 - x[offset + 1040]; t = (x[offset + 1040] ^ x[offset + 1056]) & (c >> 31); x[offset + 1040] ^= t; x[offset + 1056] ^= t;
|
||||
c = 61444 - x[offset + 1088]; t = (x[offset + 1088] ^ x[offset + 1104]) & (c >> 31); x[offset + 1088] ^= t; x[offset + 1104] ^= t;
|
||||
c = 61444 - x[offset + 1120]; t = (x[offset + 1120] ^ x[offset + 1136]) & (c >> 31); x[offset + 1120] ^= t; x[offset + 1136] ^= t;
|
||||
c = 61444 - x[offset + 1088]; t = (x[offset + 1088] ^ x[offset + 1120]) & (c >> 31); x[offset + 1088] ^= t; x[offset + 1120] ^= t;
|
||||
c = 61444 - x[offset + 1104]; t = (x[offset + 1104] ^ x[offset + 1136]) & (c >> 31); x[offset + 1104] ^= t; x[offset + 1136] ^= t;
|
||||
c = 61444 - x[offset + 1104]; t = (x[offset + 1104] ^ x[offset + 1120]) & (c >> 31); x[offset + 1104] ^= t; x[offset + 1120] ^= t;
|
||||
c = 61444 - x[offset + 1024]; t = (x[offset + 1024] ^ x[offset + 1088]) & (c >> 31); x[offset + 1024] ^= t; x[offset + 1088] ^= t;
|
||||
c = 61444 - x[offset + 1056]; t = (x[offset + 1056] ^ x[offset + 1120]) & (c >> 31); x[offset + 1056] ^= t; x[offset + 1120] ^= t;
|
||||
c = 61444 - x[offset + 1056]; t = (x[offset + 1056] ^ x[offset + 1088]) & (c >> 31); x[offset + 1056] ^= t; x[offset + 1088] ^= t;
|
||||
c = 61444 - x[offset + 1040]; t = (x[offset + 1040] ^ x[offset + 1104]) & (c >> 31); x[offset + 1040] ^= t; x[offset + 1104] ^= t;
|
||||
c = 61444 - x[offset + 1072]; t = (x[offset + 1072] ^ x[offset + 1136]) & (c >> 31); x[offset + 1072] ^= t; x[offset + 1136] ^= t;
|
||||
c = 61444 - x[offset + 1072]; t = (x[offset + 1072] ^ x[offset + 1104]) & (c >> 31); x[offset + 1072] ^= t; x[offset + 1104] ^= t;
|
||||
c = 61444 - x[offset + 1040]; t = (x[offset + 1040] ^ x[offset + 1056]) & (c >> 31); x[offset + 1040] ^= t; x[offset + 1056] ^= t;
|
||||
c = 61444 - x[offset + 1072]; t = (x[offset + 1072] ^ x[offset + 1088]) & (c >> 31); x[offset + 1072] ^= t; x[offset + 1088] ^= t;
|
||||
c = 61444 - x[offset + 1104]; t = (x[offset + 1104] ^ x[offset + 1120]) & (c >> 31); x[offset + 1104] ^= t; x[offset + 1120] ^= t;
|
||||
c = 61444 - x[offset + 1152]; t = (x[offset + 1152] ^ x[offset + 1168]) & (c >> 31); x[offset + 1152] ^= t; x[offset + 1168] ^= t;
|
||||
c = 61444 - x[offset + 1184]; t = (x[offset + 1184] ^ x[offset + 1200]) & (c >> 31); x[offset + 1184] ^= t; x[offset + 1200] ^= t;
|
||||
c = 61444 - x[offset + 1152]; t = (x[offset + 1152] ^ x[offset + 1184]) & (c >> 31); x[offset + 1152] ^= t; x[offset + 1184] ^= t;
|
||||
c = 61444 - x[offset + 1168]; t = (x[offset + 1168] ^ x[offset + 1200]) & (c >> 31); x[offset + 1168] ^= t; x[offset + 1200] ^= t;
|
||||
c = 61444 - x[offset + 1168]; t = (x[offset + 1168] ^ x[offset + 1184]) & (c >> 31); x[offset + 1168] ^= t; x[offset + 1184] ^= t;
|
||||
c = 61444 - x[offset + 1216]; t = (x[offset + 1216] ^ x[offset + 1232]) & (c >> 31); x[offset + 1216] ^= t; x[offset + 1232] ^= t;
|
||||
c = 61444 - x[offset + 1248]; t = (x[offset + 1248] ^ x[offset + 1264]) & (c >> 31); x[offset + 1248] ^= t; x[offset + 1264] ^= t;
|
||||
c = 61444 - x[offset + 1216]; t = (x[offset + 1216] ^ x[offset + 1248]) & (c >> 31); x[offset + 1216] ^= t; x[offset + 1248] ^= t;
|
||||
c = 61444 - x[offset + 1232]; t = (x[offset + 1232] ^ x[offset + 1264]) & (c >> 31); x[offset + 1232] ^= t; x[offset + 1264] ^= t;
|
||||
c = 61444 - x[offset + 1232]; t = (x[offset + 1232] ^ x[offset + 1248]) & (c >> 31); x[offset + 1232] ^= t; x[offset + 1248] ^= t;
|
||||
c = 61444 - x[offset + 1152]; t = (x[offset + 1152] ^ x[offset + 1216]) & (c >> 31); x[offset + 1152] ^= t; x[offset + 1216] ^= t;
|
||||
c = 61444 - x[offset + 1184]; t = (x[offset + 1184] ^ x[offset + 1248]) & (c >> 31); x[offset + 1184] ^= t; x[offset + 1248] ^= t;
|
||||
c = 61444 - x[offset + 1184]; t = (x[offset + 1184] ^ x[offset + 1216]) & (c >> 31); x[offset + 1184] ^= t; x[offset + 1216] ^= t;
|
||||
c = 61444 - x[offset + 1168]; t = (x[offset + 1168] ^ x[offset + 1232]) & (c >> 31); x[offset + 1168] ^= t; x[offset + 1232] ^= t;
|
||||
c = 61444 - x[offset + 1200]; t = (x[offset + 1200] ^ x[offset + 1264]) & (c >> 31); x[offset + 1200] ^= t; x[offset + 1264] ^= t;
|
||||
c = 61444 - x[offset + 1200]; t = (x[offset + 1200] ^ x[offset + 1232]) & (c >> 31); x[offset + 1200] ^= t; x[offset + 1232] ^= t;
|
||||
c = 61444 - x[offset + 1168]; t = (x[offset + 1168] ^ x[offset + 1184]) & (c >> 31); x[offset + 1168] ^= t; x[offset + 1184] ^= t;
|
||||
c = 61444 - x[offset + 1200]; t = (x[offset + 1200] ^ x[offset + 1216]) & (c >> 31); x[offset + 1200] ^= t; x[offset + 1216] ^= t;
|
||||
c = 61444 - x[offset + 1232]; t = (x[offset + 1232] ^ x[offset + 1248]) & (c >> 31); x[offset + 1232] ^= t; x[offset + 1248] ^= t;
|
||||
c = 61444 - x[offset + 1024]; t = (x[offset + 1024] ^ x[offset + 1152]) & (c >> 31); x[offset + 1024] ^= t; x[offset + 1152] ^= t;
|
||||
c = 61444 - x[offset + 1088]; t = (x[offset + 1088] ^ x[offset + 1216]) & (c >> 31); x[offset + 1088] ^= t; x[offset + 1216] ^= t;
|
||||
c = 61444 - x[offset + 1088]; t = (x[offset + 1088] ^ x[offset + 1152]) & (c >> 31); x[offset + 1088] ^= t; x[offset + 1152] ^= t;
|
||||
c = 61444 - x[offset + 1056]; t = (x[offset + 1056] ^ x[offset + 1184]) & (c >> 31); x[offset + 1056] ^= t; x[offset + 1184] ^= t;
|
||||
c = 61444 - x[offset + 1120]; t = (x[offset + 1120] ^ x[offset + 1248]) & (c >> 31); x[offset + 1120] ^= t; x[offset + 1248] ^= t;
|
||||
c = 61444 - x[offset + 1120]; t = (x[offset + 1120] ^ x[offset + 1184]) & (c >> 31); x[offset + 1120] ^= t; x[offset + 1184] ^= t;
|
||||
c = 61444 - x[offset + 1056]; t = (x[offset + 1056] ^ x[offset + 1088]) & (c >> 31); x[offset + 1056] ^= t; x[offset + 1088] ^= t;
|
||||
c = 61444 - x[offset + 1120]; t = (x[offset + 1120] ^ x[offset + 1152]) & (c >> 31); x[offset + 1120] ^= t; x[offset + 1152] ^= t;
|
||||
c = 61444 - x[offset + 1184]; t = (x[offset + 1184] ^ x[offset + 1216]) & (c >> 31); x[offset + 1184] ^= t; x[offset + 1216] ^= t;
|
||||
c = 61444 - x[offset + 1040]; t = (x[offset + 1040] ^ x[offset + 1168]) & (c >> 31); x[offset + 1040] ^= t; x[offset + 1168] ^= t;
|
||||
c = 61444 - x[offset + 1104]; t = (x[offset + 1104] ^ x[offset + 1232]) & (c >> 31); x[offset + 1104] ^= t; x[offset + 1232] ^= t;
|
||||
c = 61444 - x[offset + 1104]; t = (x[offset + 1104] ^ x[offset + 1168]) & (c >> 31); x[offset + 1104] ^= t; x[offset + 1168] ^= t;
|
||||
c = 61444 - x[offset + 1072]; t = (x[offset + 1072] ^ x[offset + 1200]) & (c >> 31); x[offset + 1072] ^= t; x[offset + 1200] ^= t;
|
||||
c = 61444 - x[offset + 1136]; t = (x[offset + 1136] ^ x[offset + 1264]) & (c >> 31); x[offset + 1136] ^= t; x[offset + 1264] ^= t;
|
||||
c = 61444 - x[offset + 1136]; t = (x[offset + 1136] ^ x[offset + 1200]) & (c >> 31); x[offset + 1136] ^= t; x[offset + 1200] ^= t;
|
||||
c = 61444 - x[offset + 1072]; t = (x[offset + 1072] ^ x[offset + 1104]) & (c >> 31); x[offset + 1072] ^= t; x[offset + 1104] ^= t;
|
||||
c = 61444 - x[offset + 1136]; t = (x[offset + 1136] ^ x[offset + 1168]) & (c >> 31); x[offset + 1136] ^= t; x[offset + 1168] ^= t;
|
||||
c = 61444 - x[offset + 1200]; t = (x[offset + 1200] ^ x[offset + 1232]) & (c >> 31); x[offset + 1200] ^= t; x[offset + 1232] ^= t;
|
||||
c = 61444 - x[offset + 1040]; t = (x[offset + 1040] ^ x[offset + 1056]) & (c >> 31); x[offset + 1040] ^= t; x[offset + 1056] ^= t;
|
||||
c = 61444 - x[offset + 1072]; t = (x[offset + 1072] ^ x[offset + 1088]) & (c >> 31); x[offset + 1072] ^= t; x[offset + 1088] ^= t;
|
||||
c = 61444 - x[offset + 1104]; t = (x[offset + 1104] ^ x[offset + 1120]) & (c >> 31); x[offset + 1104] ^= t; x[offset + 1120] ^= t;
|
||||
c = 61444 - x[offset + 1136]; t = (x[offset + 1136] ^ x[offset + 1152]) & (c >> 31); x[offset + 1136] ^= t; x[offset + 1152] ^= t;
|
||||
c = 61444 - x[offset + 1168]; t = (x[offset + 1168] ^ x[offset + 1184]) & (c >> 31); x[offset + 1168] ^= t; x[offset + 1184] ^= t;
|
||||
c = 61444 - x[offset + 1200]; t = (x[offset + 1200] ^ x[offset + 1216]) & (c >> 31); x[offset + 1200] ^= t; x[offset + 1216] ^= t;
|
||||
c = 61444 - x[offset + 1232]; t = (x[offset + 1232] ^ x[offset + 1248]) & (c >> 31); x[offset + 1232] ^= t; x[offset + 1248] ^= t;
|
||||
c = 61444 - x[offset + 1280]; t = (x[offset + 1280] ^ x[offset + 1296]) & (c >> 31); x[offset + 1280] ^= t; x[offset + 1296] ^= t;
|
||||
c = 61444 - x[offset + 1312]; t = (x[offset + 1312] ^ x[offset + 1328]) & (c >> 31); x[offset + 1312] ^= t; x[offset + 1328] ^= t;
|
||||
c = 61444 - x[offset + 1280]; t = (x[offset + 1280] ^ x[offset + 1312]) & (c >> 31); x[offset + 1280] ^= t; x[offset + 1312] ^= t;
|
||||
c = 61444 - x[offset + 1296]; t = (x[offset + 1296] ^ x[offset + 1328]) & (c >> 31); x[offset + 1296] ^= t; x[offset + 1328] ^= t;
|
||||
c = 61444 - x[offset + 1296]; t = (x[offset + 1296] ^ x[offset + 1312]) & (c >> 31); x[offset + 1296] ^= t; x[offset + 1312] ^= t;
|
||||
c = 61444 - x[offset + 1296]; t = (x[offset + 1296] ^ x[offset + 1312]) & (c >> 31); x[offset + 1296] ^= t; x[offset + 1312] ^= t;
|
||||
c = 61444 - x[offset + 1296]; t = (x[offset + 1296] ^ x[offset + 1312]) & (c >> 31); x[offset + 1296] ^= t; x[offset + 1312] ^= t;
|
||||
c = 61444 - x[offset + 1024]; t = (x[offset + 1024] ^ x[offset + 1280]) & (c >> 31); x[offset + 1024] ^= t; x[offset + 1280] ^= t;
|
||||
c = 61444 - x[offset + 1152]; t = (x[offset + 1152] ^ x[offset + 1280]) & (c >> 31); x[offset + 1152] ^= t; x[offset + 1280] ^= t;
|
||||
c = 61444 - x[offset + 1088]; t = (x[offset + 1088] ^ x[offset + 1152]) & (c >> 31); x[offset + 1088] ^= t; x[offset + 1152] ^= t;
|
||||
c = 61444 - x[offset + 1216]; t = (x[offset + 1216] ^ x[offset + 1280]) & (c >> 31); x[offset + 1216] ^= t; x[offset + 1280] ^= t;
|
||||
c = 61444 - x[offset + 1056]; t = (x[offset + 1056] ^ x[offset + 1312]) & (c >> 31); x[offset + 1056] ^= t; x[offset + 1312] ^= t;
|
||||
c = 61444 - x[offset + 1184]; t = (x[offset + 1184] ^ x[offset + 1312]) & (c >> 31); x[offset + 1184] ^= t; x[offset + 1312] ^= t;
|
||||
c = 61444 - x[offset + 1120]; t = (x[offset + 1120] ^ x[offset + 1184]) & (c >> 31); x[offset + 1120] ^= t; x[offset + 1184] ^= t;
|
||||
c = 61444 - x[offset + 1248]; t = (x[offset + 1248] ^ x[offset + 1312]) & (c >> 31); x[offset + 1248] ^= t; x[offset + 1312] ^= t;
|
||||
c = 61444 - x[offset + 1056]; t = (x[offset + 1056] ^ x[offset + 1088]) & (c >> 31); x[offset + 1056] ^= t; x[offset + 1088] ^= t;
|
||||
c = 61444 - x[offset + 1120]; t = (x[offset + 1120] ^ x[offset + 1152]) & (c >> 31); x[offset + 1120] ^= t; x[offset + 1152] ^= t;
|
||||
c = 61444 - x[offset + 1184]; t = (x[offset + 1184] ^ x[offset + 1216]) & (c >> 31); x[offset + 1184] ^= t; x[offset + 1216] ^= t;
|
||||
c = 61444 - x[offset + 1248]; t = (x[offset + 1248] ^ x[offset + 1280]) & (c >> 31); x[offset + 1248] ^= t; x[offset + 1280] ^= t;
|
||||
c = 61444 - x[offset + 1040]; t = (x[offset + 1040] ^ x[offset + 1296]) & (c >> 31); x[offset + 1040] ^= t; x[offset + 1296] ^= t;
|
||||
c = 61444 - x[offset + 1168]; t = (x[offset + 1168] ^ x[offset + 1296]) & (c >> 31); x[offset + 1168] ^= t; x[offset + 1296] ^= t;
|
||||
c = 61444 - x[offset + 1104]; t = (x[offset + 1104] ^ x[offset + 1168]) & (c >> 31); x[offset + 1104] ^= t; x[offset + 1168] ^= t;
|
||||
c = 61444 - x[offset + 1232]; t = (x[offset + 1232] ^ x[offset + 1296]) & (c >> 31); x[offset + 1232] ^= t; x[offset + 1296] ^= t;
|
||||
c = 61444 - x[offset + 1072]; t = (x[offset + 1072] ^ x[offset + 1328]) & (c >> 31); x[offset + 1072] ^= t; x[offset + 1328] ^= t;
|
||||
c = 61444 - x[offset + 1200]; t = (x[offset + 1200] ^ x[offset + 1328]) & (c >> 31); x[offset + 1200] ^= t; x[offset + 1328] ^= t;
|
||||
c = 61444 - x[offset + 1136]; t = (x[offset + 1136] ^ x[offset + 1200]) & (c >> 31); x[offset + 1136] ^= t; x[offset + 1200] ^= t;
|
||||
c = 61444 - x[offset + 1264]; t = (x[offset + 1264] ^ x[offset + 1328]) & (c >> 31); x[offset + 1264] ^= t; x[offset + 1328] ^= t;
|
||||
c = 61444 - x[offset + 1072]; t = (x[offset + 1072] ^ x[offset + 1104]) & (c >> 31); x[offset + 1072] ^= t; x[offset + 1104] ^= t;
|
||||
c = 61444 - x[offset + 1136]; t = (x[offset + 1136] ^ x[offset + 1168]) & (c >> 31); x[offset + 1136] ^= t; x[offset + 1168] ^= t;
|
||||
c = 61444 - x[offset + 1200]; t = (x[offset + 1200] ^ x[offset + 1232]) & (c >> 31); x[offset + 1200] ^= t; x[offset + 1232] ^= t;
|
||||
c = 61444 - x[offset + 1264]; t = (x[offset + 1264] ^ x[offset + 1296]) & (c >> 31); x[offset + 1264] ^= t; x[offset + 1296] ^= t;
|
||||
c = 61444 - x[offset + 1040]; t = (x[offset + 1040] ^ x[offset + 1056]) & (c >> 31); x[offset + 1040] ^= t; x[offset + 1056] ^= t;
|
||||
c = 61444 - x[offset + 1072]; t = (x[offset + 1072] ^ x[offset + 1088]) & (c >> 31); x[offset + 1072] ^= t; x[offset + 1088] ^= t;
|
||||
c = 61444 - x[offset + 1104]; t = (x[offset + 1104] ^ x[offset + 1120]) & (c >> 31); x[offset + 1104] ^= t; x[offset + 1120] ^= t;
|
||||
c = 61444 - x[offset + 1136]; t = (x[offset + 1136] ^ x[offset + 1152]) & (c >> 31); x[offset + 1136] ^= t; x[offset + 1152] ^= t;
|
||||
c = 61444 - x[offset + 1168]; t = (x[offset + 1168] ^ x[offset + 1184]) & (c >> 31); x[offset + 1168] ^= t; x[offset + 1184] ^= t;
|
||||
c = 61444 - x[offset + 1200]; t = (x[offset + 1200] ^ x[offset + 1216]) & (c >> 31); x[offset + 1200] ^= t; x[offset + 1216] ^= t;
|
||||
c = 61444 - x[offset + 1232]; t = (x[offset + 1232] ^ x[offset + 1248]) & (c >> 31); x[offset + 1232] ^= t; x[offset + 1248] ^= t;
|
||||
c = 61444 - x[offset + 1264]; t = (x[offset + 1264] ^ x[offset + 1280]) & (c >> 31); x[offset + 1264] ^= t; x[offset + 1280] ^= t;
|
||||
c = 61444 - x[offset + 1296]; t = (x[offset + 1296] ^ x[offset + 1312]) & (c >> 31); x[offset + 1296] ^= t; x[offset + 1312] ^= t;
|
||||
c = 61444 - x[offset + 1152]; t = (x[offset + 1152] ^ x[offset + 1280]) & (c >> 31); x[offset + 1152] ^= t; x[offset + 1280] ^= t;
|
||||
c = 61444 - x[offset + 1088]; t = (x[offset + 1088] ^ x[offset + 1152]) & (c >> 31); x[offset + 1088] ^= t; x[offset + 1152] ^= t;
|
||||
c = 61444 - x[offset + 1216]; t = (x[offset + 1216] ^ x[offset + 1280]) & (c >> 31); x[offset + 1216] ^= t; x[offset + 1280] ^= t;
|
||||
c = 61444 - x[offset + 1184]; t = (x[offset + 1184] ^ x[offset + 1312]) & (c >> 31); x[offset + 1184] ^= t; x[offset + 1312] ^= t;
|
||||
c = 61444 - x[offset + 1120]; t = (x[offset + 1120] ^ x[offset + 1184]) & (c >> 31); x[offset + 1120] ^= t; x[offset + 1184] ^= t;
|
||||
c = 61444 - x[offset + 1248]; t = (x[offset + 1248] ^ x[offset + 1312]) & (c >> 31); x[offset + 1248] ^= t; x[offset + 1312] ^= t;
|
||||
c = 61444 - x[offset + 1056]; t = (x[offset + 1056] ^ x[offset + 1088]) & (c >> 31); x[offset + 1056] ^= t; x[offset + 1088] ^= t;
|
||||
c = 61444 - x[offset + 1120]; t = (x[offset + 1120] ^ x[offset + 1152]) & (c >> 31); x[offset + 1120] ^= t; x[offset + 1152] ^= t;
|
||||
c = 61444 - x[offset + 1184]; t = (x[offset + 1184] ^ x[offset + 1216]) & (c >> 31); x[offset + 1184] ^= t; x[offset + 1216] ^= t;
|
||||
c = 61444 - x[offset + 1248]; t = (x[offset + 1248] ^ x[offset + 1280]) & (c >> 31); x[offset + 1248] ^= t; x[offset + 1280] ^= t;
|
||||
c = 61444 - x[offset + 1168]; t = (x[offset + 1168] ^ x[offset + 1296]) & (c >> 31); x[offset + 1168] ^= t; x[offset + 1296] ^= t;
|
||||
c = 61444 - x[offset + 1104]; t = (x[offset + 1104] ^ x[offset + 1168]) & (c >> 31); x[offset + 1104] ^= t; x[offset + 1168] ^= t;
|
||||
c = 61444 - x[offset + 1232]; t = (x[offset + 1232] ^ x[offset + 1296]) & (c >> 31); x[offset + 1232] ^= t; x[offset + 1296] ^= t;
|
||||
c = 61444 - x[offset + 1200]; t = (x[offset + 1200] ^ x[offset + 1328]) & (c >> 31); x[offset + 1200] ^= t; x[offset + 1328] ^= t;
|
||||
c = 61444 - x[offset + 1136]; t = (x[offset + 1136] ^ x[offset + 1200]) & (c >> 31); x[offset + 1136] ^= t; x[offset + 1200] ^= t;
|
||||
c = 61444 - x[offset + 1264]; t = (x[offset + 1264] ^ x[offset + 1328]) & (c >> 31); x[offset + 1264] ^= t; x[offset + 1328] ^= t;
|
||||
c = 61444 - x[offset + 1072]; t = (x[offset + 1072] ^ x[offset + 1104]) & (c >> 31); x[offset + 1072] ^= t; x[offset + 1104] ^= t;
|
||||
c = 61444 - x[offset + 1136]; t = (x[offset + 1136] ^ x[offset + 1168]) & (c >> 31); x[offset + 1136] ^= t; x[offset + 1168] ^= t;
|
||||
c = 61444 - x[offset + 1200]; t = (x[offset + 1200] ^ x[offset + 1232]) & (c >> 31); x[offset + 1200] ^= t; x[offset + 1232] ^= t;
|
||||
c = 61444 - x[offset + 1264]; t = (x[offset + 1264] ^ x[offset + 1296]) & (c >> 31); x[offset + 1264] ^= t; x[offset + 1296] ^= t;
|
||||
c = 61444 - x[offset + 1040]; t = (x[offset + 1040] ^ x[offset + 1056]) & (c >> 31); x[offset + 1040] ^= t; x[offset + 1056] ^= t;
|
||||
c = 61444 - x[offset + 1072]; t = (x[offset + 1072] ^ x[offset + 1088]) & (c >> 31); x[offset + 1072] ^= t; x[offset + 1088] ^= t;
|
||||
c = 61444 - x[offset + 1104]; t = (x[offset + 1104] ^ x[offset + 1120]) & (c >> 31); x[offset + 1104] ^= t; x[offset + 1120] ^= t;
|
||||
c = 61444 - x[offset + 1136]; t = (x[offset + 1136] ^ x[offset + 1152]) & (c >> 31); x[offset + 1136] ^= t; x[offset + 1152] ^= t;
|
||||
c = 61444 - x[offset + 1168]; t = (x[offset + 1168] ^ x[offset + 1184]) & (c >> 31); x[offset + 1168] ^= t; x[offset + 1184] ^= t;
|
||||
c = 61444 - x[offset + 1200]; t = (x[offset + 1200] ^ x[offset + 1216]) & (c >> 31); x[offset + 1200] ^= t; x[offset + 1216] ^= t;
|
||||
c = 61444 - x[offset + 1232]; t = (x[offset + 1232] ^ x[offset + 1248]) & (c >> 31); x[offset + 1232] ^= t; x[offset + 1248] ^= t;
|
||||
c = 61444 - x[offset + 1264]; t = (x[offset + 1264] ^ x[offset + 1280]) & (c >> 31); x[offset + 1264] ^= t; x[offset + 1280] ^= t;
|
||||
c = 61444 - x[offset + 1296]; t = (x[offset + 1296] ^ x[offset + 1312]) & (c >> 31); x[offset + 1296] ^= t; x[offset + 1312] ^= t;
|
||||
c = 61444 - x[offset + 0]; t = (x[offset + 0] ^ x[offset + 1024]) & (c >> 31); x[offset + 0] ^= t; x[offset + 1024] ^= t;
|
||||
c = 61444 - x[offset + 512]; t = (x[offset + 512] ^ x[offset + 1024]) & (c >> 31); x[offset + 512] ^= t; x[offset + 1024] ^= t;
|
||||
c = 61444 - x[offset + 256]; t = (x[offset + 256] ^ x[offset + 1280]) & (c >> 31); x[offset + 256] ^= t; x[offset + 1280] ^= t;
|
||||
c = 61444 - x[offset + 768]; t = (x[offset + 768] ^ x[offset + 1280]) & (c >> 31); x[offset + 768] ^= t; x[offset + 1280] ^= t;
|
||||
c = 61444 - x[offset + 256]; t = (x[offset + 256] ^ x[offset + 512]) & (c >> 31); x[offset + 256] ^= t; x[offset + 512] ^= t;
|
||||
c = 61444 - x[offset + 768]; t = (x[offset + 768] ^ x[offset + 1024]) & (c >> 31); x[offset + 768] ^= t; x[offset + 1024] ^= t;
|
||||
c = 61444 - x[offset + 128]; t = (x[offset + 128] ^ x[offset + 1152]) & (c >> 31); x[offset + 128] ^= t; x[offset + 1152] ^= t;
|
||||
c = 61444 - x[offset + 640]; t = (x[offset + 640] ^ x[offset + 1152]) & (c >> 31); x[offset + 640] ^= t; x[offset + 1152] ^= t;
|
||||
c = 61444 - x[offset + 384]; t = (x[offset + 384] ^ x[offset + 640]) & (c >> 31); x[offset + 384] ^= t; x[offset + 640] ^= t;
|
||||
c = 61444 - x[offset + 896]; t = (x[offset + 896] ^ x[offset + 1152]) & (c >> 31); x[offset + 896] ^= t; x[offset + 1152] ^= t;
|
||||
c = 61444 - x[offset + 128]; t = (x[offset + 128] ^ x[offset + 256]) & (c >> 31); x[offset + 128] ^= t; x[offset + 256] ^= t;
|
||||
c = 61444 - x[offset + 384]; t = (x[offset + 384] ^ x[offset + 512]) & (c >> 31); x[offset + 384] ^= t; x[offset + 512] ^= t;
|
||||
c = 61444 - x[offset + 640]; t = (x[offset + 640] ^ x[offset + 768]) & (c >> 31); x[offset + 640] ^= t; x[offset + 768] ^= t;
|
||||
c = 61444 - x[offset + 896]; t = (x[offset + 896] ^ x[offset + 1024]) & (c >> 31); x[offset + 896] ^= t; x[offset + 1024] ^= t;
|
||||
c = 61444 - x[offset + 1152]; t = (x[offset + 1152] ^ x[offset + 1280]) & (c >> 31); x[offset + 1152] ^= t; x[offset + 1280] ^= t;
|
||||
c = 61444 - x[offset + 64]; t = (x[offset + 64] ^ x[offset + 1088]) & (c >> 31); x[offset + 64] ^= t; x[offset + 1088] ^= t;
|
||||
c = 61444 - x[offset + 576]; t = (x[offset + 576] ^ x[offset + 1088]) & (c >> 31); x[offset + 576] ^= t; x[offset + 1088] ^= t;
|
||||
c = 61444 - x[offset + 320]; t = (x[offset + 320] ^ x[offset + 576]) & (c >> 31); x[offset + 320] ^= t; x[offset + 576] ^= t;
|
||||
c = 61444 - x[offset + 832]; t = (x[offset + 832] ^ x[offset + 1088]) & (c >> 31); x[offset + 832] ^= t; x[offset + 1088] ^= t;
|
||||
c = 61444 - x[offset + 192]; t = (x[offset + 192] ^ x[offset + 1216]) & (c >> 31); x[offset + 192] ^= t; x[offset + 1216] ^= t;
|
||||
c = 61444 - x[offset + 704]; t = (x[offset + 704] ^ x[offset + 1216]) & (c >> 31); x[offset + 704] ^= t; x[offset + 1216] ^= t;
|
||||
c = 61444 - x[offset + 448]; t = (x[offset + 448] ^ x[offset + 704]) & (c >> 31); x[offset + 448] ^= t; x[offset + 704] ^= t;
|
||||
c = 61444 - x[offset + 960]; t = (x[offset + 960] ^ x[offset + 1216]) & (c >> 31); x[offset + 960] ^= t; x[offset + 1216] ^= t;
|
||||
c = 61444 - x[offset + 192]; t = (x[offset + 192] ^ x[offset + 320]) & (c >> 31); x[offset + 192] ^= t; x[offset + 320] ^= t;
|
||||
c = 61444 - x[offset + 448]; t = (x[offset + 448] ^ x[offset + 576]) & (c >> 31); x[offset + 448] ^= t; x[offset + 576] ^= t;
|
||||
c = 61444 - x[offset + 704]; t = (x[offset + 704] ^ x[offset + 832]) & (c >> 31); x[offset + 704] ^= t; x[offset + 832] ^= t;
|
||||
c = 61444 - x[offset + 960]; t = (x[offset + 960] ^ x[offset + 1088]) & (c >> 31); x[offset + 960] ^= t; x[offset + 1088] ^= t;
|
||||
c = 61444 - x[offset + 64]; t = (x[offset + 64] ^ x[offset + 128]) & (c >> 31); x[offset + 64] ^= t; x[offset + 128] ^= t;
|
||||
c = 61444 - x[offset + 192]; t = (x[offset + 192] ^ x[offset + 256]) & (c >> 31); x[offset + 192] ^= t; x[offset + 256] ^= t;
|
||||
c = 61444 - x[offset + 320]; t = (x[offset + 320] ^ x[offset + 384]) & (c >> 31); x[offset + 320] ^= t; x[offset + 384] ^= t;
|
||||
c = 61444 - x[offset + 448]; t = (x[offset + 448] ^ x[offset + 512]) & (c >> 31); x[offset + 448] ^= t; x[offset + 512] ^= t;
|
||||
c = 61444 - x[offset + 576]; t = (x[offset + 576] ^ x[offset + 640]) & (c >> 31); x[offset + 576] ^= t; x[offset + 640] ^= t;
|
||||
c = 61444 - x[offset + 704]; t = (x[offset + 704] ^ x[offset + 768]) & (c >> 31); x[offset + 704] ^= t; x[offset + 768] ^= t;
|
||||
c = 61444 - x[offset + 832]; t = (x[offset + 832] ^ x[offset + 896]) & (c >> 31); x[offset + 832] ^= t; x[offset + 896] ^= t;
|
||||
c = 61444 - x[offset + 960]; t = (x[offset + 960] ^ x[offset + 1024]) & (c >> 31); x[offset + 960] ^= t; x[offset + 1024] ^= t;
|
||||
c = 61444 - x[offset + 1088]; t = (x[offset + 1088] ^ x[offset + 1152]) & (c >> 31); x[offset + 1088] ^= t; x[offset + 1152] ^= t;
|
||||
c = 61444 - x[offset + 1216]; t = (x[offset + 1216] ^ x[offset + 1280]) & (c >> 31); x[offset + 1216] ^= t; x[offset + 1280] ^= t;
|
||||
c = 61444 - x[offset + 32]; t = (x[offset + 32] ^ x[offset + 1056]) & (c >> 31); x[offset + 32] ^= t; x[offset + 1056] ^= t;
|
||||
c = 61444 - x[offset + 544]; t = (x[offset + 544] ^ x[offset + 1056]) & (c >> 31); x[offset + 544] ^= t; x[offset + 1056] ^= t;
|
||||
c = 61444 - x[offset + 288]; t = (x[offset + 288] ^ x[offset + 1312]) & (c >> 31); x[offset + 288] ^= t; x[offset + 1312] ^= t;
|
||||
c = 61444 - x[offset + 800]; t = (x[offset + 800] ^ x[offset + 1312]) & (c >> 31); x[offset + 800] ^= t; x[offset + 1312] ^= t;
|
||||
c = 61444 - x[offset + 288]; t = (x[offset + 288] ^ x[offset + 544]) & (c >> 31); x[offset + 288] ^= t; x[offset + 544] ^= t;
|
||||
c = 61444 - x[offset + 800]; t = (x[offset + 800] ^ x[offset + 1056]) & (c >> 31); x[offset + 800] ^= t; x[offset + 1056] ^= t;
|
||||
c = 61444 - x[offset + 160]; t = (x[offset + 160] ^ x[offset + 1184]) & (c >> 31); x[offset + 160] ^= t; x[offset + 1184] ^= t;
|
||||
c = 61444 - x[offset + 672]; t = (x[offset + 672] ^ x[offset + 1184]) & (c >> 31); x[offset + 672] ^= t; x[offset + 1184] ^= t;
|
||||
c = 61444 - x[offset + 416]; t = (x[offset + 416] ^ x[offset + 672]) & (c >> 31); x[offset + 416] ^= t; x[offset + 672] ^= t;
|
||||
c = 61444 - x[offset + 928]; t = (x[offset + 928] ^ x[offset + 1184]) & (c >> 31); x[offset + 928] ^= t; x[offset + 1184] ^= t;
|
||||
c = 61444 - x[offset + 160]; t = (x[offset + 160] ^ x[offset + 288]) & (c >> 31); x[offset + 160] ^= t; x[offset + 288] ^= t;
|
||||
c = 61444 - x[offset + 416]; t = (x[offset + 416] ^ x[offset + 544]) & (c >> 31); x[offset + 416] ^= t; x[offset + 544] ^= t;
|
||||
c = 61444 - x[offset + 672]; t = (x[offset + 672] ^ x[offset + 800]) & (c >> 31); x[offset + 672] ^= t; x[offset + 800] ^= t;
|
||||
c = 61444 - x[offset + 928]; t = (x[offset + 928] ^ x[offset + 1056]) & (c >> 31); x[offset + 928] ^= t; x[offset + 1056] ^= t;
|
||||
c = 61444 - x[offset + 1184]; t = (x[offset + 1184] ^ x[offset + 1312]) & (c >> 31); x[offset + 1184] ^= t; x[offset + 1312] ^= t;
|
||||
c = 61444 - x[offset + 96]; t = (x[offset + 96] ^ x[offset + 1120]) & (c >> 31); x[offset + 96] ^= t; x[offset + 1120] ^= t;
|
||||
c = 61444 - x[offset + 608]; t = (x[offset + 608] ^ x[offset + 1120]) & (c >> 31); x[offset + 608] ^= t; x[offset + 1120] ^= t;
|
||||
c = 61444 - x[offset + 352]; t = (x[offset + 352] ^ x[offset + 608]) & (c >> 31); x[offset + 352] ^= t; x[offset + 608] ^= t;
|
||||
c = 61444 - x[offset + 864]; t = (x[offset + 864] ^ x[offset + 1120]) & (c >> 31); x[offset + 864] ^= t; x[offset + 1120] ^= t;
|
||||
c = 61444 - x[offset + 224]; t = (x[offset + 224] ^ x[offset + 1248]) & (c >> 31); x[offset + 224] ^= t; x[offset + 1248] ^= t;
|
||||
c = 61444 - x[offset + 736]; t = (x[offset + 736] ^ x[offset + 1248]) & (c >> 31); x[offset + 736] ^= t; x[offset + 1248] ^= t;
|
||||
c = 61444 - x[offset + 480]; t = (x[offset + 480] ^ x[offset + 736]) & (c >> 31); x[offset + 480] ^= t; x[offset + 736] ^= t;
|
||||
c = 61444 - x[offset + 992]; t = (x[offset + 992] ^ x[offset + 1248]) & (c >> 31); x[offset + 992] ^= t; x[offset + 1248] ^= t;
|
||||
c = 61444 - x[offset + 224]; t = (x[offset + 224] ^ x[offset + 352]) & (c >> 31); x[offset + 224] ^= t; x[offset + 352] ^= t;
|
||||
c = 61444 - x[offset + 480]; t = (x[offset + 480] ^ x[offset + 608]) & (c >> 31); x[offset + 480] ^= t; x[offset + 608] ^= t;
|
||||
c = 61444 - x[offset + 736]; t = (x[offset + 736] ^ x[offset + 864]) & (c >> 31); x[offset + 736] ^= t; x[offset + 864] ^= t;
|
||||
c = 61444 - x[offset + 992]; t = (x[offset + 992] ^ x[offset + 1120]) & (c >> 31); x[offset + 992] ^= t; x[offset + 1120] ^= t;
|
||||
c = 61444 - x[offset + 96]; t = (x[offset + 96] ^ x[offset + 160]) & (c >> 31); x[offset + 96] ^= t; x[offset + 160] ^= t;
|
||||
c = 61444 - x[offset + 224]; t = (x[offset + 224] ^ x[offset + 288]) & (c >> 31); x[offset + 224] ^= t; x[offset + 288] ^= t;
|
||||
c = 61444 - x[offset + 352]; t = (x[offset + 352] ^ x[offset + 416]) & (c >> 31); x[offset + 352] ^= t; x[offset + 416] ^= t;
|
||||
c = 61444 - x[offset + 480]; t = (x[offset + 480] ^ x[offset + 544]) & (c >> 31); x[offset + 480] ^= t; x[offset + 544] ^= t;
|
||||
c = 61444 - x[offset + 608]; t = (x[offset + 608] ^ x[offset + 672]) & (c >> 31); x[offset + 608] ^= t; x[offset + 672] ^= t;
|
||||
c = 61444 - x[offset + 736]; t = (x[offset + 736] ^ x[offset + 800]) & (c >> 31); x[offset + 736] ^= t; x[offset + 800] ^= t;
|
||||
c = 61444 - x[offset + 864]; t = (x[offset + 864] ^ x[offset + 928]) & (c >> 31); x[offset + 864] ^= t; x[offset + 928] ^= t;
|
||||
c = 61444 - x[offset + 992]; t = (x[offset + 992] ^ x[offset + 1056]) & (c >> 31); x[offset + 992] ^= t; x[offset + 1056] ^= t;
|
||||
c = 61444 - x[offset + 1120]; t = (x[offset + 1120] ^ x[offset + 1184]) & (c >> 31); x[offset + 1120] ^= t; x[offset + 1184] ^= t;
|
||||
c = 61444 - x[offset + 1248]; t = (x[offset + 1248] ^ x[offset + 1312]) & (c >> 31); x[offset + 1248] ^= t; x[offset + 1312] ^= t;
|
||||
c = 61444 - x[offset + 32]; t = (x[offset + 32] ^ x[offset + 64]) & (c >> 31); x[offset + 32] ^= t; x[offset + 64] ^= t;
|
||||
c = 61444 - x[offset + 96]; t = (x[offset + 96] ^ x[offset + 128]) & (c >> 31); x[offset + 96] ^= t; x[offset + 128] ^= t;
|
||||
c = 61444 - x[offset + 160]; t = (x[offset + 160] ^ x[offset + 192]) & (c >> 31); x[offset + 160] ^= t; x[offset + 192] ^= t;
|
||||
c = 61444 - x[offset + 224]; t = (x[offset + 224] ^ x[offset + 256]) & (c >> 31); x[offset + 224] ^= t; x[offset + 256] ^= t;
|
||||
c = 61444 - x[offset + 288]; t = (x[offset + 288] ^ x[offset + 320]) & (c >> 31); x[offset + 288] ^= t; x[offset + 320] ^= t;
|
||||
c = 61444 - x[offset + 352]; t = (x[offset + 352] ^ x[offset + 384]) & (c >> 31); x[offset + 352] ^= t; x[offset + 384] ^= t;
|
||||
c = 61444 - x[offset + 416]; t = (x[offset + 416] ^ x[offset + 448]) & (c >> 31); x[offset + 416] ^= t; x[offset + 448] ^= t;
|
||||
c = 61444 - x[offset + 480]; t = (x[offset + 480] ^ x[offset + 512]) & (c >> 31); x[offset + 480] ^= t; x[offset + 512] ^= t;
|
||||
c = 61444 - x[offset + 544]; t = (x[offset + 544] ^ x[offset + 576]) & (c >> 31); x[offset + 544] ^= t; x[offset + 576] ^= t;
|
||||
c = 61444 - x[offset + 608]; t = (x[offset + 608] ^ x[offset + 640]) & (c >> 31); x[offset + 608] ^= t; x[offset + 640] ^= t;
|
||||
c = 61444 - x[offset + 672]; t = (x[offset + 672] ^ x[offset + 704]) & (c >> 31); x[offset + 672] ^= t; x[offset + 704] ^= t;
|
||||
c = 61444 - x[offset + 736]; t = (x[offset + 736] ^ x[offset + 768]) & (c >> 31); x[offset + 736] ^= t; x[offset + 768] ^= t;
|
||||
c = 61444 - x[offset + 800]; t = (x[offset + 800] ^ x[offset + 832]) & (c >> 31); x[offset + 800] ^= t; x[offset + 832] ^= t;
|
||||
c = 61444 - x[offset + 864]; t = (x[offset + 864] ^ x[offset + 896]) & (c >> 31); x[offset + 864] ^= t; x[offset + 896] ^= t;
|
||||
c = 61444 - x[offset + 928]; t = (x[offset + 928] ^ x[offset + 960]) & (c >> 31); x[offset + 928] ^= t; x[offset + 960] ^= t;
|
||||
c = 61444 - x[offset + 992]; t = (x[offset + 992] ^ x[offset + 1024]) & (c >> 31); x[offset + 992] ^= t; x[offset + 1024] ^= t;
|
||||
c = 61444 - x[offset + 1056]; t = (x[offset + 1056] ^ x[offset + 1088]) & (c >> 31); x[offset + 1056] ^= t; x[offset + 1088] ^= t;
|
||||
c = 61444 - x[offset + 1120]; t = (x[offset + 1120] ^ x[offset + 1152]) & (c >> 31); x[offset + 1120] ^= t; x[offset + 1152] ^= t;
|
||||
c = 61444 - x[offset + 1184]; t = (x[offset + 1184] ^ x[offset + 1216]) & (c >> 31); x[offset + 1184] ^= t; x[offset + 1216] ^= t;
|
||||
c = 61444 - x[offset + 1248]; t = (x[offset + 1248] ^ x[offset + 1280]) & (c >> 31); x[offset + 1248] ^= t; x[offset + 1280] ^= t;
|
||||
c = 61444 - x[offset + 16]; t = (x[offset + 16] ^ x[offset + 1040]) & (c >> 31); x[offset + 16] ^= t; x[offset + 1040] ^= t;
|
||||
c = 61444 - x[offset + 528]; t = (x[offset + 528] ^ x[offset + 1040]) & (c >> 31); x[offset + 528] ^= t; x[offset + 1040] ^= t;
|
||||
c = 61444 - x[offset + 272]; t = (x[offset + 272] ^ x[offset + 1296]) & (c >> 31); x[offset + 272] ^= t; x[offset + 1296] ^= t;
|
||||
c = 61444 - x[offset + 784]; t = (x[offset + 784] ^ x[offset + 1296]) & (c >> 31); x[offset + 784] ^= t; x[offset + 1296] ^= t;
|
||||
c = 61444 - x[offset + 272]; t = (x[offset + 272] ^ x[offset + 528]) & (c >> 31); x[offset + 272] ^= t; x[offset + 528] ^= t;
|
||||
c = 61444 - x[offset + 784]; t = (x[offset + 784] ^ x[offset + 1040]) & (c >> 31); x[offset + 784] ^= t; x[offset + 1040] ^= t;
|
||||
c = 61444 - x[offset + 144]; t = (x[offset + 144] ^ x[offset + 1168]) & (c >> 31); x[offset + 144] ^= t; x[offset + 1168] ^= t;
|
||||
c = 61444 - x[offset + 656]; t = (x[offset + 656] ^ x[offset + 1168]) & (c >> 31); x[offset + 656] ^= t; x[offset + 1168] ^= t;
|
||||
c = 61444 - x[offset + 400]; t = (x[offset + 400] ^ x[offset + 656]) & (c >> 31); x[offset + 400] ^= t; x[offset + 656] ^= t;
|
||||
c = 61444 - x[offset + 912]; t = (x[offset + 912] ^ x[offset + 1168]) & (c >> 31); x[offset + 912] ^= t; x[offset + 1168] ^= t;
|
||||
c = 61444 - x[offset + 144]; t = (x[offset + 144] ^ x[offset + 272]) & (c >> 31); x[offset + 144] ^= t; x[offset + 272] ^= t;
|
||||
c = 61444 - x[offset + 400]; t = (x[offset + 400] ^ x[offset + 528]) & (c >> 31); x[offset + 400] ^= t; x[offset + 528] ^= t;
|
||||
c = 61444 - x[offset + 656]; t = (x[offset + 656] ^ x[offset + 784]) & (c >> 31); x[offset + 656] ^= t; x[offset + 784] ^= t;
|
||||
c = 61444 - x[offset + 912]; t = (x[offset + 912] ^ x[offset + 1040]) & (c >> 31); x[offset + 912] ^= t; x[offset + 1040] ^= t;
|
||||
c = 61444 - x[offset + 1168]; t = (x[offset + 1168] ^ x[offset + 1296]) & (c >> 31); x[offset + 1168] ^= t; x[offset + 1296] ^= t;
|
||||
c = 61444 - x[offset + 80]; t = (x[offset + 80] ^ x[offset + 1104]) & (c >> 31); x[offset + 80] ^= t; x[offset + 1104] ^= t;
|
||||
c = 61444 - x[offset + 592]; t = (x[offset + 592] ^ x[offset + 1104]) & (c >> 31); x[offset + 592] ^= t; x[offset + 1104] ^= t;
|
||||
c = 61444 - x[offset + 336]; t = (x[offset + 336] ^ x[offset + 592]) & (c >> 31); x[offset + 336] ^= t; x[offset + 592] ^= t;
|
||||
c = 61444 - x[offset + 848]; t = (x[offset + 848] ^ x[offset + 1104]) & (c >> 31); x[offset + 848] ^= t; x[offset + 1104] ^= t;
|
||||
c = 61444 - x[offset + 208]; t = (x[offset + 208] ^ x[offset + 1232]) & (c >> 31); x[offset + 208] ^= t; x[offset + 1232] ^= t;
|
||||
c = 61444 - x[offset + 720]; t = (x[offset + 720] ^ x[offset + 1232]) & (c >> 31); x[offset + 720] ^= t; x[offset + 1232] ^= t;
|
||||
c = 61444 - x[offset + 464]; t = (x[offset + 464] ^ x[offset + 720]) & (c >> 31); x[offset + 464] ^= t; x[offset + 720] ^= t;
|
||||
c = 61444 - x[offset + 976]; t = (x[offset + 976] ^ x[offset + 1232]) & (c >> 31); x[offset + 976] ^= t; x[offset + 1232] ^= t;
|
||||
c = 61444 - x[offset + 208]; t = (x[offset + 208] ^ x[offset + 336]) & (c >> 31); x[offset + 208] ^= t; x[offset + 336] ^= t;
|
||||
c = 61444 - x[offset + 464]; t = (x[offset + 464] ^ x[offset + 592]) & (c >> 31); x[offset + 464] ^= t; x[offset + 592] ^= t;
|
||||
c = 61444 - x[offset + 720]; t = (x[offset + 720] ^ x[offset + 848]) & (c >> 31); x[offset + 720] ^= t; x[offset + 848] ^= t;
|
||||
c = 61444 - x[offset + 976]; t = (x[offset + 976] ^ x[offset + 1104]) & (c >> 31); x[offset + 976] ^= t; x[offset + 1104] ^= t;
|
||||
c = 61444 - x[offset + 80]; t = (x[offset + 80] ^ x[offset + 144]) & (c >> 31); x[offset + 80] ^= t; x[offset + 144] ^= t;
|
||||
c = 61444 - x[offset + 208]; t = (x[offset + 208] ^ x[offset + 272]) & (c >> 31); x[offset + 208] ^= t; x[offset + 272] ^= t;
|
||||
c = 61444 - x[offset + 336]; t = (x[offset + 336] ^ x[offset + 400]) & (c >> 31); x[offset + 336] ^= t; x[offset + 400] ^= t;
|
||||
c = 61444 - x[offset + 464]; t = (x[offset + 464] ^ x[offset + 528]) & (c >> 31); x[offset + 464] ^= t; x[offset + 528] ^= t;
|
||||
c = 61444 - x[offset + 592]; t = (x[offset + 592] ^ x[offset + 656]) & (c >> 31); x[offset + 592] ^= t; x[offset + 656] ^= t;
|
||||
c = 61444 - x[offset + 720]; t = (x[offset + 720] ^ x[offset + 784]) & (c >> 31); x[offset + 720] ^= t; x[offset + 784] ^= t;
|
||||
c = 61444 - x[offset + 848]; t = (x[offset + 848] ^ x[offset + 912]) & (c >> 31); x[offset + 848] ^= t; x[offset + 912] ^= t;
|
||||
c = 61444 - x[offset + 976]; t = (x[offset + 976] ^ x[offset + 1040]) & (c >> 31); x[offset + 976] ^= t; x[offset + 1040] ^= t;
|
||||
c = 61444 - x[offset + 1104]; t = (x[offset + 1104] ^ x[offset + 1168]) & (c >> 31); x[offset + 1104] ^= t; x[offset + 1168] ^= t;
|
||||
c = 61444 - x[offset + 1232]; t = (x[offset + 1232] ^ x[offset + 1296]) & (c >> 31); x[offset + 1232] ^= t; x[offset + 1296] ^= t;
|
||||
c = 61444 - x[offset + 48]; t = (x[offset + 48] ^ x[offset + 1072]) & (c >> 31); x[offset + 48] ^= t; x[offset + 1072] ^= t;
|
||||
c = 61444 - x[offset + 560]; t = (x[offset + 560] ^ x[offset + 1072]) & (c >> 31); x[offset + 560] ^= t; x[offset + 1072] ^= t;
|
||||
c = 61444 - x[offset + 304]; t = (x[offset + 304] ^ x[offset + 1328]) & (c >> 31); x[offset + 304] ^= t; x[offset + 1328] ^= t;
|
||||
c = 61444 - x[offset + 816]; t = (x[offset + 816] ^ x[offset + 1328]) & (c >> 31); x[offset + 816] ^= t; x[offset + 1328] ^= t;
|
||||
c = 61444 - x[offset + 304]; t = (x[offset + 304] ^ x[offset + 560]) & (c >> 31); x[offset + 304] ^= t; x[offset + 560] ^= t;
|
||||
c = 61444 - x[offset + 816]; t = (x[offset + 816] ^ x[offset + 1072]) & (c >> 31); x[offset + 816] ^= t; x[offset + 1072] ^= t;
|
||||
c = 61444 - x[offset + 176]; t = (x[offset + 176] ^ x[offset + 1200]) & (c >> 31); x[offset + 176] ^= t; x[offset + 1200] ^= t;
|
||||
c = 61444 - x[offset + 688]; t = (x[offset + 688] ^ x[offset + 1200]) & (c >> 31); x[offset + 688] ^= t; x[offset + 1200] ^= t;
|
||||
c = 61444 - x[offset + 432]; t = (x[offset + 432] ^ x[offset + 688]) & (c >> 31); x[offset + 432] ^= t; x[offset + 688] ^= t;
|
||||
c = 61444 - x[offset + 944]; t = (x[offset + 944] ^ x[offset + 1200]) & (c >> 31); x[offset + 944] ^= t; x[offset + 1200] ^= t;
|
||||
c = 61444 - x[offset + 176]; t = (x[offset + 176] ^ x[offset + 304]) & (c >> 31); x[offset + 176] ^= t; x[offset + 304] ^= t;
|
||||
c = 61444 - x[offset + 432]; t = (x[offset + 432] ^ x[offset + 560]) & (c >> 31); x[offset + 432] ^= t; x[offset + 560] ^= t;
|
||||
c = 61444 - x[offset + 688]; t = (x[offset + 688] ^ x[offset + 816]) & (c >> 31); x[offset + 688] ^= t; x[offset + 816] ^= t;
|
||||
c = 61444 - x[offset + 944]; t = (x[offset + 944] ^ x[offset + 1072]) & (c >> 31); x[offset + 944] ^= t; x[offset + 1072] ^= t;
|
||||
c = 61444 - x[offset + 1200]; t = (x[offset + 1200] ^ x[offset + 1328]) & (c >> 31); x[offset + 1200] ^= t; x[offset + 1328] ^= t;
|
||||
c = 61444 - x[offset + 112]; t = (x[offset + 112] ^ x[offset + 1136]) & (c >> 31); x[offset + 112] ^= t; x[offset + 1136] ^= t;
|
||||
c = 61444 - x[offset + 624]; t = (x[offset + 624] ^ x[offset + 1136]) & (c >> 31); x[offset + 624] ^= t; x[offset + 1136] ^= t;
|
||||
c = 61444 - x[offset + 368]; t = (x[offset + 368] ^ x[offset + 624]) & (c >> 31); x[offset + 368] ^= t; x[offset + 624] ^= t;
|
||||
c = 61444 - x[offset + 880]; t = (x[offset + 880] ^ x[offset + 1136]) & (c >> 31); x[offset + 880] ^= t; x[offset + 1136] ^= t;
|
||||
c = 61444 - x[offset + 240]; t = (x[offset + 240] ^ x[offset + 1264]) & (c >> 31); x[offset + 240] ^= t; x[offset + 1264] ^= t;
|
||||
c = 61444 - x[offset + 752]; t = (x[offset + 752] ^ x[offset + 1264]) & (c >> 31); x[offset + 752] ^= t; x[offset + 1264] ^= t;
|
||||
c = 61444 - x[offset + 496]; t = (x[offset + 496] ^ x[offset + 752]) & (c >> 31); x[offset + 496] ^= t; x[offset + 752] ^= t;
|
||||
c = 61444 - x[offset + 1008]; t = (x[offset + 1008] ^ x[offset + 1264]) & (c >> 31); x[offset + 1008] ^= t; x[offset + 1264] ^= t;
|
||||
c = 61444 - x[offset + 240]; t = (x[offset + 240] ^ x[offset + 368]) & (c >> 31); x[offset + 240] ^= t; x[offset + 368] ^= t;
|
||||
c = 61444 - x[offset + 496]; t = (x[offset + 496] ^ x[offset + 624]) & (c >> 31); x[offset + 496] ^= t; x[offset + 624] ^= t;
|
||||
c = 61444 - x[offset + 752]; t = (x[offset + 752] ^ x[offset + 880]) & (c >> 31); x[offset + 752] ^= t; x[offset + 880] ^= t;
|
||||
c = 61444 - x[offset + 1008]; t = (x[offset + 1008] ^ x[offset + 1136]) & (c >> 31); x[offset + 1008] ^= t; x[offset + 1136] ^= t;
|
||||
c = 61444 - x[offset + 112]; t = (x[offset + 112] ^ x[offset + 176]) & (c >> 31); x[offset + 112] ^= t; x[offset + 176] ^= t;
|
||||
c = 61444 - x[offset + 240]; t = (x[offset + 240] ^ x[offset + 304]) & (c >> 31); x[offset + 240] ^= t; x[offset + 304] ^= t;
|
||||
c = 61444 - x[offset + 368]; t = (x[offset + 368] ^ x[offset + 432]) & (c >> 31); x[offset + 368] ^= t; x[offset + 432] ^= t;
|
||||
c = 61444 - x[offset + 496]; t = (x[offset + 496] ^ x[offset + 560]) & (c >> 31); x[offset + 496] ^= t; x[offset + 560] ^= t;
|
||||
c = 61444 - x[offset + 624]; t = (x[offset + 624] ^ x[offset + 688]) & (c >> 31); x[offset + 624] ^= t; x[offset + 688] ^= t;
|
||||
c = 61444 - x[offset + 752]; t = (x[offset + 752] ^ x[offset + 816]) & (c >> 31); x[offset + 752] ^= t; x[offset + 816] ^= t;
|
||||
c = 61444 - x[offset + 880]; t = (x[offset + 880] ^ x[offset + 944]) & (c >> 31); x[offset + 880] ^= t; x[offset + 944] ^= t;
|
||||
c = 61444 - x[offset + 1008]; t = (x[offset + 1008] ^ x[offset + 1072]) & (c >> 31); x[offset + 1008] ^= t; x[offset + 1072] ^= t;
|
||||
c = 61444 - x[offset + 1136]; t = (x[offset + 1136] ^ x[offset + 1200]) & (c >> 31); x[offset + 1136] ^= t; x[offset + 1200] ^= t;
|
||||
c = 61444 - x[offset + 1264]; t = (x[offset + 1264] ^ x[offset + 1328]) & (c >> 31); x[offset + 1264] ^= t; x[offset + 1328] ^= t;
|
||||
c = 61444 - x[offset + 48]; t = (x[offset + 48] ^ x[offset + 80]) & (c >> 31); x[offset + 48] ^= t; x[offset + 80] ^= t;
|
||||
c = 61444 - x[offset + 112]; t = (x[offset + 112] ^ x[offset + 144]) & (c >> 31); x[offset + 112] ^= t; x[offset + 144] ^= t;
|
||||
c = 61444 - x[offset + 176]; t = (x[offset + 176] ^ x[offset + 208]) & (c >> 31); x[offset + 176] ^= t; x[offset + 208] ^= t;
|
||||
c = 61444 - x[offset + 240]; t = (x[offset + 240] ^ x[offset + 272]) & (c >> 31); x[offset + 240] ^= t; x[offset + 272] ^= t;
|
||||
c = 61444 - x[offset + 304]; t = (x[offset + 304] ^ x[offset + 336]) & (c >> 31); x[offset + 304] ^= t; x[offset + 336] ^= t;
|
||||
c = 61444 - x[offset + 368]; t = (x[offset + 368] ^ x[offset + 400]) & (c >> 31); x[offset + 368] ^= t; x[offset + 400] ^= t;
|
||||
c = 61444 - x[offset + 432]; t = (x[offset + 432] ^ x[offset + 464]) & (c >> 31); x[offset + 432] ^= t; x[offset + 464] ^= t;
|
||||
c = 61444 - x[offset + 496]; t = (x[offset + 496] ^ x[offset + 528]) & (c >> 31); x[offset + 496] ^= t; x[offset + 528] ^= t;
|
||||
c = 61444 - x[offset + 560]; t = (x[offset + 560] ^ x[offset + 592]) & (c >> 31); x[offset + 560] ^= t; x[offset + 592] ^= t;
|
||||
c = 61444 - x[offset + 624]; t = (x[offset + 624] ^ x[offset + 656]) & (c >> 31); x[offset + 624] ^= t; x[offset + 656] ^= t;
|
||||
c = 61444 - x[offset + 688]; t = (x[offset + 688] ^ x[offset + 720]) & (c >> 31); x[offset + 688] ^= t; x[offset + 720] ^= t;
|
||||
c = 61444 - x[offset + 752]; t = (x[offset + 752] ^ x[offset + 784]) & (c >> 31); x[offset + 752] ^= t; x[offset + 784] ^= t;
|
||||
c = 61444 - x[offset + 816]; t = (x[offset + 816] ^ x[offset + 848]) & (c >> 31); x[offset + 816] ^= t; x[offset + 848] ^= t;
|
||||
c = 61444 - x[offset + 880]; t = (x[offset + 880] ^ x[offset + 912]) & (c >> 31); x[offset + 880] ^= t; x[offset + 912] ^= t;
|
||||
c = 61444 - x[offset + 944]; t = (x[offset + 944] ^ x[offset + 976]) & (c >> 31); x[offset + 944] ^= t; x[offset + 976] ^= t;
|
||||
c = 61444 - x[offset + 1008]; t = (x[offset + 1008] ^ x[offset + 1040]) & (c >> 31); x[offset + 1008] ^= t; x[offset + 1040] ^= t;
|
||||
c = 61444 - x[offset + 1072]; t = (x[offset + 1072] ^ x[offset + 1104]) & (c >> 31); x[offset + 1072] ^= t; x[offset + 1104] ^= t;
|
||||
c = 61444 - x[offset + 1136]; t = (x[offset + 1136] ^ x[offset + 1168]) & (c >> 31); x[offset + 1136] ^= t; x[offset + 1168] ^= t;
|
||||
c = 61444 - x[offset + 1200]; t = (x[offset + 1200] ^ x[offset + 1232]) & (c >> 31); x[offset + 1200] ^= t; x[offset + 1232] ^= t;
|
||||
c = 61444 - x[offset + 1264]; t = (x[offset + 1264] ^ x[offset + 1296]) & (c >> 31); x[offset + 1264] ^= t; x[offset + 1296] ^= t;
|
||||
c = 61444 - x[offset + 16]; t = (x[offset + 16] ^ x[offset + 32]) & (c >> 31); x[offset + 16] ^= t; x[offset + 32] ^= t;
|
||||
c = 61444 - x[offset + 48]; t = (x[offset + 48] ^ x[offset + 64]) & (c >> 31); x[offset + 48] ^= t; x[offset + 64] ^= t;
|
||||
c = 61444 - x[offset + 80]; t = (x[offset + 80] ^ x[offset + 96]) & (c >> 31); x[offset + 80] ^= t; x[offset + 96] ^= t;
|
||||
c = 61444 - x[offset + 112]; t = (x[offset + 112] ^ x[offset + 128]) & (c >> 31); x[offset + 112] ^= t; x[offset + 128] ^= t;
|
||||
c = 61444 - x[offset + 144]; t = (x[offset + 144] ^ x[offset + 160]) & (c >> 31); x[offset + 144] ^= t; x[offset + 160] ^= t;
|
||||
c = 61444 - x[offset + 176]; t = (x[offset + 176] ^ x[offset + 192]) & (c >> 31); x[offset + 176] ^= t; x[offset + 192] ^= t;
|
||||
c = 61444 - x[offset + 208]; t = (x[offset + 208] ^ x[offset + 224]) & (c >> 31); x[offset + 208] ^= t; x[offset + 224] ^= t;
|
||||
c = 61444 - x[offset + 240]; t = (x[offset + 240] ^ x[offset + 256]) & (c >> 31); x[offset + 240] ^= t; x[offset + 256] ^= t;
|
||||
c = 61444 - x[offset + 272]; t = (x[offset + 272] ^ x[offset + 288]) & (c >> 31); x[offset + 272] ^= t; x[offset + 288] ^= t;
|
||||
c = 61444 - x[offset + 304]; t = (x[offset + 304] ^ x[offset + 320]) & (c >> 31); x[offset + 304] ^= t; x[offset + 320] ^= t;
|
||||
c = 61444 - x[offset + 336]; t = (x[offset + 336] ^ x[offset + 352]) & (c >> 31); x[offset + 336] ^= t; x[offset + 352] ^= t;
|
||||
c = 61444 - x[offset + 368]; t = (x[offset + 368] ^ x[offset + 384]) & (c >> 31); x[offset + 368] ^= t; x[offset + 384] ^= t;
|
||||
c = 61444 - x[offset + 400]; t = (x[offset + 400] ^ x[offset + 416]) & (c >> 31); x[offset + 400] ^= t; x[offset + 416] ^= t;
|
||||
c = 61444 - x[offset + 432]; t = (x[offset + 432] ^ x[offset + 448]) & (c >> 31); x[offset + 432] ^= t; x[offset + 448] ^= t;
|
||||
c = 61444 - x[offset + 464]; t = (x[offset + 464] ^ x[offset + 480]) & (c >> 31); x[offset + 464] ^= t; x[offset + 480] ^= t;
|
||||
c = 61444 - x[offset + 496]; t = (x[offset + 496] ^ x[offset + 512]) & (c >> 31); x[offset + 496] ^= t; x[offset + 512] ^= t;
|
||||
c = 61444 - x[offset + 528]; t = (x[offset + 528] ^ x[offset + 544]) & (c >> 31); x[offset + 528] ^= t; x[offset + 544] ^= t;
|
||||
c = 61444 - x[offset + 560]; t = (x[offset + 560] ^ x[offset + 576]) & (c >> 31); x[offset + 560] ^= t; x[offset + 576] ^= t;
|
||||
c = 61444 - x[offset + 592]; t = (x[offset + 592] ^ x[offset + 608]) & (c >> 31); x[offset + 592] ^= t; x[offset + 608] ^= t;
|
||||
c = 61444 - x[offset + 624]; t = (x[offset + 624] ^ x[offset + 640]) & (c >> 31); x[offset + 624] ^= t; x[offset + 640] ^= t;
|
||||
c = 61444 - x[offset + 656]; t = (x[offset + 656] ^ x[offset + 672]) & (c >> 31); x[offset + 656] ^= t; x[offset + 672] ^= t;
|
||||
c = 61444 - x[offset + 688]; t = (x[offset + 688] ^ x[offset + 704]) & (c >> 31); x[offset + 688] ^= t; x[offset + 704] ^= t;
|
||||
c = 61444 - x[offset + 720]; t = (x[offset + 720] ^ x[offset + 736]) & (c >> 31); x[offset + 720] ^= t; x[offset + 736] ^= t;
|
||||
c = 61444 - x[offset + 752]; t = (x[offset + 752] ^ x[offset + 768]) & (c >> 31); x[offset + 752] ^= t; x[offset + 768] ^= t;
|
||||
c = 61444 - x[offset + 784]; t = (x[offset + 784] ^ x[offset + 800]) & (c >> 31); x[offset + 784] ^= t; x[offset + 800] ^= t;
|
||||
c = 61444 - x[offset + 816]; t = (x[offset + 816] ^ x[offset + 832]) & (c >> 31); x[offset + 816] ^= t; x[offset + 832] ^= t;
|
||||
c = 61444 - x[offset + 848]; t = (x[offset + 848] ^ x[offset + 864]) & (c >> 31); x[offset + 848] ^= t; x[offset + 864] ^= t;
|
||||
c = 61444 - x[offset + 880]; t = (x[offset + 880] ^ x[offset + 896]) & (c >> 31); x[offset + 880] ^= t; x[offset + 896] ^= t;
|
||||
c = 61444 - x[offset + 912]; t = (x[offset + 912] ^ x[offset + 928]) & (c >> 31); x[offset + 912] ^= t; x[offset + 928] ^= t;
|
||||
c = 61444 - x[offset + 944]; t = (x[offset + 944] ^ x[offset + 960]) & (c >> 31); x[offset + 944] ^= t; x[offset + 960] ^= t;
|
||||
c = 61444 - x[offset + 976]; t = (x[offset + 976] ^ x[offset + 992]) & (c >> 31); x[offset + 976] ^= t; x[offset + 992] ^= t;
|
||||
c = 61444 - x[offset + 1008]; t = (x[offset + 1008] ^ x[offset + 1024]) & (c >> 31); x[offset + 1008] ^= t; x[offset + 1024] ^= t;
|
||||
c = 61444 - x[offset + 1040]; t = (x[offset + 1040] ^ x[offset + 1056]) & (c >> 31); x[offset + 1040] ^= t; x[offset + 1056] ^= t;
|
||||
c = 61444 - x[offset + 1072]; t = (x[offset + 1072] ^ x[offset + 1088]) & (c >> 31); x[offset + 1072] ^= t; x[offset + 1088] ^= t;
|
||||
c = 61444 - x[offset + 1104]; t = (x[offset + 1104] ^ x[offset + 1120]) & (c >> 31); x[offset + 1104] ^= t; x[offset + 1120] ^= t;
|
||||
c = 61444 - x[offset + 1136]; t = (x[offset + 1136] ^ x[offset + 1152]) & (c >> 31); x[offset + 1136] ^= t; x[offset + 1152] ^= t;
|
||||
c = 61444 - x[offset + 1168]; t = (x[offset + 1168] ^ x[offset + 1184]) & (c >> 31); x[offset + 1168] ^= t; x[offset + 1184] ^= t;
|
||||
c = 61444 - x[offset + 1200]; t = (x[offset + 1200] ^ x[offset + 1216]) & (c >> 31); x[offset + 1200] ^= t; x[offset + 1216] ^= t;
|
||||
c = 61444 - x[offset + 1232]; t = (x[offset + 1232] ^ x[offset + 1248]) & (c >> 31); x[offset + 1232] ^= t; x[offset + 1248] ^= t;
|
||||
c = 61444 - x[offset + 1264]; t = (x[offset + 1264] ^ x[offset + 1280]) & (c >> 31); x[offset + 1264] ^= t; x[offset + 1280] ^= t;
|
||||
c = 61444 - x[offset + 1296]; t = (x[offset + 1296] ^ x[offset + 1312]) & (c >> 31); x[offset + 1296] ^= t; x[offset + 1312] ^= t;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,327 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Southern Storm Software, Pty Ltd.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included
|
||||
* in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.futo.platformplayer.noise.crypto;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import com.futo.platformplayer.noise.protocol.Destroyable;
|
||||
|
||||
/**
|
||||
* Simple implementation of the Poly1305 message authenticator.
|
||||
*/
|
||||
public final class Poly1305 implements Destroyable {
|
||||
|
||||
// The 130-bit intermediate values are broken up into five 26-bit words.
|
||||
private byte[] nonce;
|
||||
private byte[] block;
|
||||
private int[] h;
|
||||
private int[] r;
|
||||
private int[] c;
|
||||
private long[] t;
|
||||
private int posn;
|
||||
|
||||
/**
|
||||
* Constructs a new Poly1305 message authenticator.
|
||||
*/
|
||||
public Poly1305()
|
||||
{
|
||||
nonce = new byte [16];
|
||||
block = new byte [16];
|
||||
h = new int [5];
|
||||
r = new int [5];
|
||||
c = new int [5];
|
||||
t = new long [10];
|
||||
posn = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the message authenticator with a new key.
|
||||
*
|
||||
* @param key The buffer containing the 32 byte key.
|
||||
* @param offset The offset into the buffer of the first key byte.
|
||||
*/
|
||||
public void reset(byte[] key, int offset)
|
||||
{
|
||||
System.arraycopy(key, offset + 16, nonce, 0, 16);
|
||||
Arrays.fill(h, 0);
|
||||
posn = 0;
|
||||
|
||||
// Convert the first 16 bytes of the key into a 130-bit
|
||||
// "r" value while masking off the bits that we don't need.
|
||||
r[0] = ((key[offset] & 0xFF)) |
|
||||
((key[offset + 1] & 0xFF) << 8) |
|
||||
((key[offset + 2] & 0xFF) << 16) |
|
||||
((key[offset + 3] & 0x03) << 24);
|
||||
r[1] = ((key[offset + 3] & 0x0C) >> 2) |
|
||||
((key[offset + 4] & 0xFC) << 6) |
|
||||
((key[offset + 5] & 0xFF) << 14) |
|
||||
((key[offset + 6] & 0x0F) << 22);
|
||||
r[2] = ((key[offset + 6] & 0xF0) >> 4) |
|
||||
((key[offset + 7] & 0x0F) << 4) |
|
||||
((key[offset + 8] & 0xFC) << 12) |
|
||||
((key[offset + 9] & 0x3F) << 20);
|
||||
r[3] = ((key[offset + 9] & 0xC0) >> 6) |
|
||||
((key[offset + 10] & 0xFF) << 2) |
|
||||
((key[offset + 11] & 0x0F) << 10) |
|
||||
((key[offset + 12] & 0xFC) << 18);
|
||||
r[4] = ((key[offset + 13] & 0xFF)) |
|
||||
((key[offset + 14] & 0xFF) << 8) |
|
||||
((key[offset + 15] & 0x0F) << 16);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the message authenticator with more input data.
|
||||
*
|
||||
* @param data The buffer containing the input data.
|
||||
* @param offset The offset of the first byte of input.
|
||||
* @param length The number of bytes of input.
|
||||
*/
|
||||
public void update(byte[] data, int offset, int length)
|
||||
{
|
||||
while (length > 0) {
|
||||
if (posn == 0 && length >= 16) {
|
||||
// We can process the chunk directly out of the input buffer.
|
||||
processChunk(data, offset, false);
|
||||
offset += 16;
|
||||
length -= 16;
|
||||
} else {
|
||||
// Collect up partial bytes in the block buffer.
|
||||
int temp = 16 - posn;
|
||||
if (temp > length)
|
||||
temp = length;
|
||||
System.arraycopy(data, offset, block, posn, temp);
|
||||
offset += temp;
|
||||
length -= temp;
|
||||
posn += temp;
|
||||
if (posn >= 16) {
|
||||
processChunk(block, 0, false);
|
||||
posn = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Pads the input with zeroes to a multiple of 16 bytes.
|
||||
*/
|
||||
public void pad()
|
||||
{
|
||||
if (posn != 0) {
|
||||
Arrays.fill(block, posn, 16, (byte)0);
|
||||
processChunk(block, 0, false);
|
||||
posn = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finishes the message authenticator and returns the 16-byte token.
|
||||
*
|
||||
* @param token The buffer to receive the token.
|
||||
* @param offset The offset of the token in the buffer.
|
||||
*/
|
||||
public void finish(byte[] token, int offset)
|
||||
{
|
||||
// Pad and flush the final chunk.
|
||||
if (posn != 0) {
|
||||
block[posn] = (byte)1;
|
||||
Arrays.fill(block, posn + 1, 16, (byte)0);
|
||||
processChunk(block, 0, true);
|
||||
}
|
||||
|
||||
// At this point, processChunk() has left h as a partially reduced
|
||||
// result that is less than (2^130 - 5) * 6. Perform one more
|
||||
// reduction and a trial subtraction to produce the final result.
|
||||
|
||||
// Multiply the high bits of h by 5 and add them to the 130 low bits.
|
||||
int carry = (h[4] >> 26) * 5 + h[0];
|
||||
h[0] = carry & 0x03FFFFFF;
|
||||
carry = (carry >> 26) + h[1];
|
||||
h[1] = carry & 0x03FFFFFF;
|
||||
carry = (carry >> 26) + h[2];
|
||||
h[2] = carry & 0x03FFFFFF;
|
||||
carry = (carry >> 26) + h[3];
|
||||
h[3] = carry & 0x03FFFFFF;
|
||||
h[4] = (carry >> 26) + (h[4] & 0x03FFFFFF);
|
||||
|
||||
// Subtract (2^130 - 5) from h by computing c = h + 5 - 2^130.
|
||||
// The "minus 2^130" step is implicit.
|
||||
carry = 5 + h[0];
|
||||
c[0] = carry & 0x03FFFFFF;
|
||||
carry = (carry >> 26) + h[1];
|
||||
c[1] = carry & 0x03FFFFFF;
|
||||
carry = (carry >> 26) + h[2];
|
||||
c[2] = carry & 0x03FFFFFF;
|
||||
carry = (carry >> 26) + h[3];
|
||||
c[3] = carry & 0x03FFFFFF;
|
||||
c[4] = (carry >> 26) + h[4];
|
||||
|
||||
// Borrow occurs if bit 2^130 of the previous c result is zero.
|
||||
// Carefully turn this into a selection mask so we can select either
|
||||
// h or c as the final result.
|
||||
int mask = -((c[4] >> 26) & 0x01);
|
||||
int nmask = ~mask;
|
||||
h[0] = (h[0] & nmask) | (c[0] & mask);
|
||||
h[1] = (h[1] & nmask) | (c[1] & mask);
|
||||
h[2] = (h[2] & nmask) | (c[2] & mask);
|
||||
h[3] = (h[3] & nmask) | (c[3] & mask);
|
||||
h[4] = (h[4] & nmask) | (c[4] & mask);
|
||||
|
||||
// Convert h into little-endian in the block buffer.
|
||||
block[0] = (byte)(h[0]);
|
||||
block[1] = (byte)(h[0] >> 8);
|
||||
block[2] = (byte)(h[0] >> 16);
|
||||
block[3] = (byte)((h[0] >> 24) | (h[1] << 2));
|
||||
block[4] = (byte)(h[1] >> 6);
|
||||
block[5] = (byte)(h[1] >> 14);
|
||||
block[6] = (byte)((h[1] >> 22) | (h[2] << 4));
|
||||
block[7] = (byte)(h[2] >> 4);
|
||||
block[8] = (byte)(h[2] >> 12);
|
||||
block[9] = (byte)((h[2] >> 20) | (h[3] << 6));
|
||||
block[10] = (byte)(h[3] >> 2);
|
||||
block[11] = (byte)(h[3] >> 10);
|
||||
block[12] = (byte)(h[3] >> 18);
|
||||
block[13] = (byte)(h[4]);
|
||||
block[14] = (byte)(h[4] >> 8);
|
||||
block[15] = (byte)(h[4] >> 16);
|
||||
|
||||
// Add the nonce and write the final result to the token.
|
||||
carry = (nonce[0] & 0xFF) + (block[0] & 0xFF);
|
||||
token[offset] = (byte)carry;
|
||||
for (int x = 1; x < 16; ++x) {
|
||||
carry = (carry >> 8) + (nonce[x] & 0xFF) + (block[x] & 0xFF);
|
||||
token[offset + x] = (byte)carry;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the next chunk of input data.
|
||||
*
|
||||
* @param chunk Buffer containing the input data chunk.
|
||||
* @param offset Offset of the first byte of the 16-byte chunk.
|
||||
* @param finalChunk Set to true if this is the final chunk.
|
||||
*/
|
||||
private void processChunk(byte[] chunk, int offset, boolean finalChunk)
|
||||
{
|
||||
int x;
|
||||
|
||||
// Unpack the 128-bit chunk into a 130-bit value in "c".
|
||||
c[0] = ((chunk[offset] & 0xFF)) |
|
||||
((chunk[offset + 1] & 0xFF) << 8) |
|
||||
((chunk[offset + 2] & 0xFF) << 16) |
|
||||
((chunk[offset + 3] & 0x03) << 24);
|
||||
c[1] = ((chunk[offset + 3] & 0xFC) >> 2) |
|
||||
((chunk[offset + 4] & 0xFF) << 6) |
|
||||
((chunk[offset + 5] & 0xFF) << 14) |
|
||||
((chunk[offset + 6] & 0x0F) << 22);
|
||||
c[2] = ((chunk[offset + 6] & 0xF0) >> 4) |
|
||||
((chunk[offset + 7] & 0xFF) << 4) |
|
||||
((chunk[offset + 8] & 0xFF) << 12) |
|
||||
((chunk[offset + 9] & 0x3F) << 20);
|
||||
c[3] = ((chunk[offset + 9] & 0xC0) >> 6) |
|
||||
((chunk[offset + 10] & 0xFF) << 2) |
|
||||
((chunk[offset + 11] & 0xFF) << 10) |
|
||||
((chunk[offset + 12] & 0xFF) << 18);
|
||||
c[4] = ((chunk[offset + 13] & 0xFF)) |
|
||||
((chunk[offset + 14] & 0xFF) << 8) |
|
||||
((chunk[offset + 15] & 0xFF) << 16);
|
||||
if (!finalChunk)
|
||||
c[4] |= (1 << 24);
|
||||
|
||||
// Compute h = ((h + c) * r) mod (2^130 - 5)
|
||||
|
||||
// Start with h += c. We assume that h is less than (2^130 - 5) * 6
|
||||
// and that c is less than 2^129, so the result will be less than 2^133.
|
||||
h[0] += c[0];
|
||||
h[1] += c[1];
|
||||
h[2] += c[2];
|
||||
h[3] += c[3];
|
||||
h[4] += c[4];
|
||||
|
||||
// Multiply h by r. We know that r is less than 2^124 because the
|
||||
// top 4 bits were AND-ed off by reset(). That makes h * r less
|
||||
// than 2^257. Which is less than the (2^130 - 6)^2 we want for
|
||||
// the modulo reduction step that follows. The intermediate limbs
|
||||
// are 52 bits in size, which allows us to collect up carries in the
|
||||
// extra bits of the 64 bit longs and propagate them later.
|
||||
long hv = h[0];
|
||||
t[0] = hv * r[0];
|
||||
t[1] = hv * r[1];
|
||||
t[2] = hv * r[2];
|
||||
t[3] = hv * r[3];
|
||||
t[4] = hv * r[4];
|
||||
for (x = 1; x < 5; ++x) {
|
||||
hv = h[x];
|
||||
t[x] += hv * r[0];
|
||||
t[x + 1] += hv * r[1];
|
||||
t[x + 2] += hv * r[2];
|
||||
t[x + 3] += hv * r[3];
|
||||
t[x + 4] = hv * r[4];
|
||||
}
|
||||
|
||||
// Propagate carries to convert the t limbs from 52-bit back to 26-bit.
|
||||
// The low bits are placed into h and the high bits are placed into c.
|
||||
h[0] = ((int)t[0]) & 0x03FFFFFF;
|
||||
hv = t[1] + (t[0] >> 26);
|
||||
h[1] = ((int)hv) & 0x03FFFFFF;
|
||||
hv = t[2] + (hv >> 26);
|
||||
h[2] = ((int)hv) & 0x03FFFFFF;
|
||||
hv = t[3] + (hv >> 26);
|
||||
h[3] = ((int)hv) & 0x03FFFFFF;
|
||||
hv = t[4] + (hv >> 26);
|
||||
h[4] = ((int)hv) & 0x03FFFFFF;
|
||||
hv = t[5] + (hv >> 26);
|
||||
c[0] = ((int)hv) & 0x03FFFFFF;
|
||||
hv = t[6] + (hv >> 26);
|
||||
c[1] = ((int)hv) & 0x03FFFFFF;
|
||||
hv = t[7] + (hv >> 26);
|
||||
c[2] = ((int)hv) & 0x03FFFFFF;
|
||||
hv = t[8] + (hv >> 26);
|
||||
c[3] = ((int)hv) & 0x03FFFFFF;
|
||||
hv = t[9] + (hv >> 26);
|
||||
c[4] = ((int)hv);
|
||||
|
||||
// Reduce h * r modulo (2^130 - 5) by multiplying the high 130 bits by 5
|
||||
// and adding them to the low 130 bits. This will leave the result at
|
||||
// most 5 subtractions away from the answer we want.
|
||||
int carry = h[0] + c[0] * 5;
|
||||
h[0] = carry & 0x03FFFFFF;
|
||||
carry = (carry >> 26) + h[1] + c[1] * 5;
|
||||
h[1] = carry & 0x03FFFFFF;
|
||||
carry = (carry >> 26) + h[2] + c[2] * 5;
|
||||
h[2] = carry & 0x03FFFFFF;
|
||||
carry = (carry >> 26) + h[3] + c[3] * 5;
|
||||
h[3] = carry & 0x03FFFFFF;
|
||||
carry = (carry >> 26) + h[4] + c[4] * 5;
|
||||
h[4] = carry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
Arrays.fill(nonce, (byte)0);
|
||||
Arrays.fill(block, (byte)0);
|
||||
Arrays.fill(h, (int)0);
|
||||
Arrays.fill(r, (int)0);
|
||||
Arrays.fill(c, (int)0);
|
||||
Arrays.fill(t, (long)0);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,244 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Southern Storm Software, Pty Ltd.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included
|
||||
* in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.futo.platformplayer.noise.crypto;
|
||||
|
||||
import java.security.DigestException;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Arrays;
|
||||
|
||||
import com.futo.platformplayer.noise.protocol.Destroyable;
|
||||
|
||||
/**
|
||||
* Fallback implementation of SHA256.
|
||||
*/
|
||||
public class SHA256MessageDigest extends MessageDigest implements Destroyable {
|
||||
|
||||
private int[] h;
|
||||
private byte[] block;
|
||||
private int[] w;
|
||||
private long length;
|
||||
private int posn;
|
||||
|
||||
/**
|
||||
* Constructs a new SHA256 message digest object.
|
||||
*/
|
||||
public SHA256MessageDigest() {
|
||||
super("SHA-256");
|
||||
h = new int [8];
|
||||
block = new byte [64];
|
||||
w = new int [64];
|
||||
engineReset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
Arrays.fill(h, (int)0);
|
||||
Arrays.fill(block, (byte)0);
|
||||
Arrays.fill(w, (int)0);
|
||||
}
|
||||
|
||||
private static void writeBE32(byte[] buf, int offset, int value)
|
||||
{
|
||||
buf[offset] = (byte)(value >> 24);
|
||||
buf[offset + 1] = (byte)(value >> 16);
|
||||
buf[offset + 2] = (byte)(value >> 8);
|
||||
buf[offset + 3] = (byte)value;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] engineDigest() {
|
||||
byte[] digest = new byte [32];
|
||||
try {
|
||||
engineDigest(digest, 0, 32);
|
||||
} catch (DigestException e) {
|
||||
// Shouldn't happen, but just in case.
|
||||
Arrays.fill(digest, (byte)0);
|
||||
}
|
||||
return digest;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int engineDigest(byte[] buf, int offset, int len) throws DigestException
|
||||
{
|
||||
if (len < 32)
|
||||
throw new DigestException("Invalid digest length for SHA256");
|
||||
if (posn <= (64 - 9)) {
|
||||
block[posn] = (byte)0x80;
|
||||
Arrays.fill(block, posn + 1, 64 - 8, (byte)0);
|
||||
} else {
|
||||
block[posn] = (byte)0x80;
|
||||
Arrays.fill(block, posn + 1, 64, (byte)0);
|
||||
transform(block, 0);
|
||||
Arrays.fill(block, 0, 64 - 8, (byte)0);
|
||||
}
|
||||
writeBE32(block, 64 - 8, (int)(length >> 32));
|
||||
writeBE32(block, 64 - 4, (int)length);
|
||||
transform(block, 0);
|
||||
posn = 0;
|
||||
for (int index = 0; index < 8; ++index)
|
||||
writeBE32(buf, offset + index * 4, h[index]);
|
||||
return 32;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int engineGetDigestLength() {
|
||||
return 32;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void engineReset() {
|
||||
h[0] = 0x6A09E667;
|
||||
h[1] = 0xBB67AE85;
|
||||
h[2] = 0x3C6EF372;
|
||||
h[3] = 0xA54FF53A;
|
||||
h[4] = 0x510E527F;
|
||||
h[5] = 0x9B05688C;
|
||||
h[6] = 0x1F83D9AB;
|
||||
h[7] = 0x5BE0CD19;
|
||||
length = 0;
|
||||
posn = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void engineUpdate(byte input) {
|
||||
block[posn++] = input;
|
||||
length += 8;
|
||||
if (posn >= 64) {
|
||||
transform(block, 0);
|
||||
posn = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void engineUpdate(byte[] input, int offset, int len) {
|
||||
while (len > 0) {
|
||||
if (posn == 0 && len >= 64) {
|
||||
transform(input, offset);
|
||||
offset += 64;
|
||||
len -= 64;
|
||||
length += 64 * 8;
|
||||
} else {
|
||||
int temp = 64 - posn;
|
||||
if (temp > len)
|
||||
temp = len;
|
||||
System.arraycopy(input, offset, block, posn, temp);
|
||||
posn += temp;
|
||||
length += temp * 8;
|
||||
if (posn >= 64) {
|
||||
transform(block, 0);
|
||||
posn = 0;
|
||||
}
|
||||
offset += temp;
|
||||
len -= temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final int[] k = {
|
||||
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
|
||||
0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
||||
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
|
||||
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
||||
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
|
||||
0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
||||
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
|
||||
0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
|
||||
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
|
||||
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
|
||||
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
|
||||
0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
||||
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
|
||||
0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
|
||||
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
|
||||
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
|
||||
};
|
||||
|
||||
private static int rightRotate(int value, int n)
|
||||
{
|
||||
return (value >>> n) | (value << (32 - n));
|
||||
}
|
||||
|
||||
private void transform(byte[] m, int offset)
|
||||
{
|
||||
int a, b, c, d, e, f, g, h;
|
||||
int temp1, temp2;
|
||||
int index;
|
||||
|
||||
// Initialize working variables to the current hash value.
|
||||
a = this.h[0];
|
||||
b = this.h[1];
|
||||
c = this.h[2];
|
||||
d = this.h[3];
|
||||
e = this.h[4];
|
||||
f = this.h[5];
|
||||
g = this.h[6];
|
||||
h = this.h[7];
|
||||
|
||||
// Convert the 16 input message words from big endian to host byte order.
|
||||
for (index = 0; index < 16; ++index) {
|
||||
w[index] = ((m[offset] & 0xFF) << 24) |
|
||||
((m[offset + 1] & 0xFF) << 16) |
|
||||
((m[offset + 2] & 0xFF) << 8) |
|
||||
(m[offset + 3] & 0xFF);
|
||||
offset += 4;
|
||||
}
|
||||
|
||||
// Extend the first 16 words to 64.
|
||||
for (index = 16; index < 64; ++index) {
|
||||
w[index] = w[index - 16] + w[index - 7] +
|
||||
(rightRotate(w[index - 15], 7) ^
|
||||
rightRotate(w[index - 15], 18) ^
|
||||
(w[index - 15] >>> 3)) +
|
||||
(rightRotate(w[index - 2], 17) ^
|
||||
rightRotate(w[index - 2], 19) ^
|
||||
(w[index - 2] >>> 10));
|
||||
}
|
||||
|
||||
// Compression function main loop.
|
||||
for (index = 0; index < 64; ++index) {
|
||||
temp1 = (h) + k[index] + w[index] +
|
||||
(rightRotate((e), 6) ^ rightRotate((e), 11) ^ rightRotate((e), 25)) +
|
||||
(((e) & (f)) ^ ((~(e)) & (g)));
|
||||
temp2 = (rightRotate((a), 2) ^ rightRotate((a), 13) ^ rightRotate((a), 22)) +
|
||||
(((a) & (b)) ^ ((a) & (c)) ^ ((b) & (c)));
|
||||
h = g;
|
||||
g = f;
|
||||
f = e;
|
||||
e = d + temp1;
|
||||
d = c;
|
||||
c = b;
|
||||
b = a;
|
||||
a = temp1 + temp2;
|
||||
}
|
||||
|
||||
// Add the compressed chunk to the current hash value.
|
||||
this.h[0] += a;
|
||||
this.h[1] += b;
|
||||
this.h[2] += c;
|
||||
this.h[3] += d;
|
||||
this.h[4] += e;
|
||||
this.h[5] += f;
|
||||
this.h[6] += g;
|
||||
this.h[7] += h;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Southern Storm Software, Pty Ltd.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included
|
||||
* in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.futo.platformplayer.noise.crypto;
|
||||
|
||||
import java.security.DigestException;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Arrays;
|
||||
|
||||
import com.futo.platformplayer.noise.protocol.Destroyable;
|
||||
|
||||
/**
|
||||
* Fallback implementation of SHA512.
|
||||
*
|
||||
* Note: This implementation is limited to a maximum 2^56 - 1 bytes of input.
|
||||
* That is, we don't bother trying to implement 128-bit length values.
|
||||
*/
|
||||
public class SHA512MessageDigest extends MessageDigest implements Destroyable {
|
||||
|
||||
private long[] h;
|
||||
private byte[] block;
|
||||
private long[] w;
|
||||
private long length;
|
||||
private int posn;
|
||||
|
||||
/**
|
||||
* Constructs a new SHA512 message digest object.
|
||||
*/
|
||||
public SHA512MessageDigest() {
|
||||
super("SHA-512");
|
||||
h = new long [8];
|
||||
block = new byte [128];
|
||||
w = new long [80];
|
||||
engineReset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
Arrays.fill(h, (long)0);
|
||||
Arrays.fill(block, (byte)0);
|
||||
Arrays.fill(w, (long)0);
|
||||
}
|
||||
|
||||
private static void writeBE64(byte[] buf, int offset, long value)
|
||||
{
|
||||
buf[offset] = (byte)(value >> 56);
|
||||
buf[offset + 1] = (byte)(value >> 48);
|
||||
buf[offset + 2] = (byte)(value >> 40);
|
||||
buf[offset + 3] = (byte)(value >> 32);
|
||||
buf[offset + 4] = (byte)(value >> 24);
|
||||
buf[offset + 5] = (byte)(value >> 16);
|
||||
buf[offset + 6] = (byte)(value >> 8);
|
||||
buf[offset + 7] = (byte)value;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] engineDigest() {
|
||||
byte[] digest = new byte [64];
|
||||
try {
|
||||
engineDigest(digest, 0, 64);
|
||||
} catch (DigestException e) {
|
||||
// Shouldn't happen, but just in case.
|
||||
Arrays.fill(digest, (byte)0);
|
||||
}
|
||||
return digest;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int engineDigest(byte[] buf, int offset, int len) throws DigestException
|
||||
{
|
||||
if (len < 64)
|
||||
throw new DigestException("Invalid digest length for SHA512");
|
||||
if (posn <= (128 - 17)) {
|
||||
block[posn] = (byte)0x80;
|
||||
Arrays.fill(block, posn + 1, 128 - 8, (byte)0);
|
||||
} else {
|
||||
block[posn] = (byte)0x80;
|
||||
Arrays.fill(block, posn + 1, 128, (byte)0);
|
||||
transform(block, 0);
|
||||
Arrays.fill(block, 0, 128 - 8, (byte)0);
|
||||
}
|
||||
writeBE64(block, 128 - 8, length);
|
||||
transform(block, 0);
|
||||
posn = 0;
|
||||
for (int index = 0; index < 8; ++index)
|
||||
writeBE64(buf, offset + index * 8, h[index]);
|
||||
return 64;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int engineGetDigestLength() {
|
||||
return 64;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void engineReset() {
|
||||
h[0] = 0x6a09e667f3bcc908L;
|
||||
h[1] = 0xbb67ae8584caa73bL;
|
||||
h[2] = 0x3c6ef372fe94f82bL;
|
||||
h[3] = 0xa54ff53a5f1d36f1L;
|
||||
h[4] = 0x510e527fade682d1L;
|
||||
h[5] = 0x9b05688c2b3e6c1fL;
|
||||
h[6] = 0x1f83d9abfb41bd6bL;
|
||||
h[7] = 0x5be0cd19137e2179L;
|
||||
length = 0;
|
||||
posn = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void engineUpdate(byte input) {
|
||||
block[posn++] = input;
|
||||
length += 8;
|
||||
if (posn >= 128) {
|
||||
transform(block, 0);
|
||||
posn = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void engineUpdate(byte[] input, int offset, int len) {
|
||||
while (len > 0) {
|
||||
if (posn == 0 && len >= 128) {
|
||||
transform(input, offset);
|
||||
offset += 128;
|
||||
len -= 128;
|
||||
length += 128 * 8;
|
||||
} else {
|
||||
int temp = 128 - posn;
|
||||
if (temp > len)
|
||||
temp = len;
|
||||
System.arraycopy(input, offset, block, posn, temp);
|
||||
posn += temp;
|
||||
length += temp * 8;
|
||||
if (posn >= 128) {
|
||||
transform(block, 0);
|
||||
posn = 0;
|
||||
}
|
||||
offset += temp;
|
||||
len -= temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final long[] k = {
|
||||
0x428A2F98D728AE22L, 0x7137449123EF65CDL, 0xB5C0FBCFEC4D3B2FL,
|
||||
0xE9B5DBA58189DBBCL, 0x3956C25BF348B538L, 0x59F111F1B605D019L,
|
||||
0x923F82A4AF194F9BL, 0xAB1C5ED5DA6D8118L, 0xD807AA98A3030242L,
|
||||
0x12835B0145706FBEL, 0x243185BE4EE4B28CL, 0x550C7DC3D5FFB4E2L,
|
||||
0x72BE5D74F27B896FL, 0x80DEB1FE3B1696B1L, 0x9BDC06A725C71235L,
|
||||
0xC19BF174CF692694L, 0xE49B69C19EF14AD2L, 0xEFBE4786384F25E3L,
|
||||
0x0FC19DC68B8CD5B5L, 0x240CA1CC77AC9C65L, 0x2DE92C6F592B0275L,
|
||||
0x4A7484AA6EA6E483L, 0x5CB0A9DCBD41FBD4L, 0x76F988DA831153B5L,
|
||||
0x983E5152EE66DFABL, 0xA831C66D2DB43210L, 0xB00327C898FB213FL,
|
||||
0xBF597FC7BEEF0EE4L, 0xC6E00BF33DA88FC2L, 0xD5A79147930AA725L,
|
||||
0x06CA6351E003826FL, 0x142929670A0E6E70L, 0x27B70A8546D22FFCL,
|
||||
0x2E1B21385C26C926L, 0x4D2C6DFC5AC42AEDL, 0x53380D139D95B3DFL,
|
||||
0x650A73548BAF63DEL, 0x766A0ABB3C77B2A8L, 0x81C2C92E47EDAEE6L,
|
||||
0x92722C851482353BL, 0xA2BFE8A14CF10364L, 0xA81A664BBC423001L,
|
||||
0xC24B8B70D0F89791L, 0xC76C51A30654BE30L, 0xD192E819D6EF5218L,
|
||||
0xD69906245565A910L, 0xF40E35855771202AL, 0x106AA07032BBD1B8L,
|
||||
0x19A4C116B8D2D0C8L, 0x1E376C085141AB53L, 0x2748774CDF8EEB99L,
|
||||
0x34B0BCB5E19B48A8L, 0x391C0CB3C5C95A63L, 0x4ED8AA4AE3418ACBL,
|
||||
0x5B9CCA4F7763E373L, 0x682E6FF3D6B2B8A3L, 0x748F82EE5DEFB2FCL,
|
||||
0x78A5636F43172F60L, 0x84C87814A1F0AB72L, 0x8CC702081A6439ECL,
|
||||
0x90BEFFFA23631E28L, 0xA4506CEBDE82BDE9L, 0xBEF9A3F7B2C67915L,
|
||||
0xC67178F2E372532BL, 0xCA273ECEEA26619CL, 0xD186B8C721C0C207L,
|
||||
0xEADA7DD6CDE0EB1EL, 0xF57D4F7FEE6ED178L, 0x06F067AA72176FBAL,
|
||||
0x0A637DC5A2C898A6L, 0x113F9804BEF90DAEL, 0x1B710B35131C471BL,
|
||||
0x28DB77F523047D84L, 0x32CAAB7B40C72493L, 0x3C9EBE0A15C9BEBCL,
|
||||
0x431D67C49C100D4CL, 0x4CC5D4BECB3E42B6L, 0x597F299CFC657E2AL,
|
||||
0x5FCB6FAB3AD6FAECL, 0x6C44198C4A475817L
|
||||
};
|
||||
|
||||
private static long rightRotate(long value, int n)
|
||||
{
|
||||
return (value >>> n) | (value << (64 - n));
|
||||
}
|
||||
|
||||
private void transform(byte[] m, int offset)
|
||||
{
|
||||
long a, b, c, d, e, f, g, h;
|
||||
long temp1, temp2;
|
||||
int index;
|
||||
|
||||
// Initialize working variables to the current hash value.
|
||||
a = this.h[0];
|
||||
b = this.h[1];
|
||||
c = this.h[2];
|
||||
d = this.h[3];
|
||||
e = this.h[4];
|
||||
f = this.h[5];
|
||||
g = this.h[6];
|
||||
h = this.h[7];
|
||||
|
||||
// Convert the 16 input message words from big endian to host byte order.
|
||||
for (index = 0; index < 16; ++index) {
|
||||
w[index] = ((m[offset] & 0xFFL) << 56) |
|
||||
((m[offset + 1] & 0xFFL) << 48) |
|
||||
((m[offset + 2] & 0xFFL) << 40) |
|
||||
((m[offset + 3] & 0xFFL) << 32) |
|
||||
((m[offset + 4] & 0xFFL) << 24) |
|
||||
((m[offset + 5] & 0xFFL) << 16) |
|
||||
((m[offset + 6] & 0xFFL) << 8) |
|
||||
(m[offset + 7] & 0xFFL);
|
||||
offset += 8;
|
||||
}
|
||||
|
||||
// Extend the first 16 words to 80.
|
||||
for (index = 16; index < 80; ++index) {
|
||||
w[index] = w[index - 16] + w[index - 7] +
|
||||
(rightRotate(w[index - 15], 1) ^
|
||||
rightRotate(w[index - 15], 8) ^
|
||||
(w[index - 15] >>> 7)) +
|
||||
(rightRotate(w[index - 2], 19) ^
|
||||
rightRotate(w[index - 2], 61) ^
|
||||
(w[index - 2] >>> 6));
|
||||
}
|
||||
|
||||
// Compression function main loop.
|
||||
for (index = 0; index < 80; ++index) {
|
||||
temp1 = (h) + k[index] + w[index] +
|
||||
(rightRotate((e), 14) ^ rightRotate((e), 18) ^ rightRotate((e), 41)) +
|
||||
(((e) & (f)) ^ ((~(e)) & (g)));
|
||||
temp2 = (rightRotate((a), 28) ^ rightRotate((a), 34) ^ rightRotate((a), 39)) +
|
||||
(((a) & (b)) ^ ((a) & (c)) ^ ((b) & (c)));
|
||||
h = g;
|
||||
g = f;
|
||||
f = e;
|
||||
e = d + temp1;
|
||||
d = c;
|
||||
c = b;
|
||||
b = a;
|
||||
a = temp1 + temp2;
|
||||
}
|
||||
|
||||
// Add the compressed chunk to the current hash value.
|
||||
this.h[0] += a;
|
||||
this.h[1] += b;
|
||||
this.h[2] += c;
|
||||
this.h[3] += d;
|
||||
this.h[4] += e;
|
||||
this.h[5] += f;
|
||||
this.h[6] += g;
|
||||
this.h[7] += h;
|
||||
}
|
||||
}
|
||||
+265
@@ -0,0 +1,265 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Southern Storm Software, Pty Ltd.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included
|
||||
* in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.futo.platformplayer.noise.protocol;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.ShortBufferException;
|
||||
|
||||
import com.futo.platformplayer.noise.crypto.GHASH;
|
||||
import com.futo.platformplayer.noise.crypto.RijndaelAES;
|
||||
|
||||
/**
|
||||
* Fallback implementation of "AESGCM" on platforms where
|
||||
* the JCA/JCE does not have a suitable GCM or CTR provider.
|
||||
*/
|
||||
class AESGCMFallbackCipherState implements CipherState {
|
||||
|
||||
private RijndaelAES aes;
|
||||
private long n;
|
||||
private byte[] iv;
|
||||
private byte[] enciv;
|
||||
private byte[] hashKey;
|
||||
private GHASH ghash;
|
||||
private boolean haskey;
|
||||
|
||||
/**
|
||||
* Constructs a new cipher state for the "AESGCM" algorithm.
|
||||
*/
|
||||
public AESGCMFallbackCipherState()
|
||||
{
|
||||
aes = new RijndaelAES();
|
||||
n = 0;
|
||||
iv = new byte [16];
|
||||
enciv = new byte [16];
|
||||
hashKey = new byte [16];
|
||||
ghash = new GHASH();
|
||||
haskey = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
aes.destroy();
|
||||
ghash.destroy();
|
||||
Noise.destroy(hashKey);
|
||||
Noise.destroy(iv);
|
||||
Noise.destroy(enciv);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCipherName() {
|
||||
return "AESGCM";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getKeyLength() {
|
||||
return 32;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMACLength() {
|
||||
return haskey ? 16 : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initializeKey(byte[] key, int offset) {
|
||||
// Set up the AES key.
|
||||
aes.setupEnc(key, offset, 256);
|
||||
haskey = true;
|
||||
|
||||
// Generate the hashing key by encrypting a block of zeroes.
|
||||
Arrays.fill(hashKey, (byte)0);
|
||||
aes.encrypt(hashKey, 0, hashKey, 0);
|
||||
ghash.reset(hashKey, 0);
|
||||
|
||||
// Reset the nonce.
|
||||
n = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasKey() {
|
||||
return haskey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up to encrypt or decrypt the next packet.
|
||||
*
|
||||
* @param ad The associated data for the packet.
|
||||
*/
|
||||
private void setup(byte[] ad)
|
||||
{
|
||||
// Check for nonce wrap-around.
|
||||
if (n == -1L)
|
||||
throw new IllegalStateException("Nonce has wrapped around");
|
||||
|
||||
// Format the counter/IV block.
|
||||
iv[0] = 0;
|
||||
iv[1] = 0;
|
||||
iv[2] = 0;
|
||||
iv[3] = 0;
|
||||
iv[4] = (byte)(n >> 56);
|
||||
iv[5] = (byte)(n >> 48);
|
||||
iv[6] = (byte)(n >> 40);
|
||||
iv[7] = (byte)(n >> 32);
|
||||
iv[8] = (byte)(n >> 24);
|
||||
iv[9] = (byte)(n >> 16);
|
||||
iv[10] = (byte)(n >> 8);
|
||||
iv[11] = (byte)n;
|
||||
iv[12] = 0;
|
||||
iv[13] = 0;
|
||||
iv[14] = 0;
|
||||
iv[15] = 1;
|
||||
++n;
|
||||
|
||||
// Encrypt a block of zeroes to generate the hash key to XOR
|
||||
// the GHASH tag with at the end of the encrypt/decrypt operation.
|
||||
Arrays.fill(hashKey, (byte)0);
|
||||
aes.encrypt(iv, 0, hashKey, 0);
|
||||
|
||||
// Initialize the GHASH with the associated data value.
|
||||
ghash.reset();
|
||||
if (ad != null) {
|
||||
ghash.update(ad, 0, ad.length);
|
||||
ghash.pad();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts a block in CTR mode.
|
||||
*
|
||||
* @param plaintext The plaintext to encrypt.
|
||||
* @param plaintextOffset Offset of the first plaintext byte.
|
||||
* @param ciphertext The resulting ciphertext.
|
||||
* @param ciphertextOffset Offset of the first ciphertext byte.
|
||||
* @param length The number of bytes to encrypt.
|
||||
*
|
||||
* This function can also be used to decrypt.
|
||||
*/
|
||||
private void encryptCTR(byte[] plaintext, int plaintextOffset, byte[] ciphertext, int ciphertextOffset, int length)
|
||||
{
|
||||
while (length > 0) {
|
||||
// Increment the IV and encrypt it to get the next keystream block.
|
||||
if (++(iv[15]) == 0)
|
||||
if (++(iv[14]) == 0)
|
||||
if (++(iv[13]) == 0)
|
||||
++(iv[12]);
|
||||
aes.encrypt(iv, 0, enciv, 0);
|
||||
|
||||
// XOR the keystream block with the plaintext to create the ciphertext.
|
||||
int temp = length;
|
||||
if (temp > 16)
|
||||
temp = 16;
|
||||
for (int index = 0; index < temp; ++index)
|
||||
ciphertext[ciphertextOffset + index] = (byte)(plaintext[plaintextOffset + index] ^ enciv[index]);
|
||||
|
||||
// Advance to the next block.
|
||||
plaintextOffset += temp;
|
||||
ciphertextOffset += temp;
|
||||
length -= temp;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int encryptWithAd(byte[] ad, byte[] plaintext, int plaintextOffset,
|
||||
byte[] ciphertext, int ciphertextOffset, int length)
|
||||
throws ShortBufferException {
|
||||
int space;
|
||||
if (ciphertextOffset < 0 || ciphertextOffset > ciphertext.length)
|
||||
throw new IllegalArgumentException();
|
||||
if (length < 0 || plaintextOffset < 0 || plaintextOffset > plaintext.length || length > plaintext.length || (plaintext.length - plaintextOffset) < length)
|
||||
throw new IllegalArgumentException();
|
||||
space = ciphertext.length - ciphertextOffset;
|
||||
if (!haskey) {
|
||||
// The key is not set yet - return the plaintext as-is.
|
||||
if (length > space)
|
||||
throw new ShortBufferException();
|
||||
if (plaintext != ciphertext || plaintextOffset != ciphertextOffset)
|
||||
System.arraycopy(plaintext, plaintextOffset, ciphertext, ciphertextOffset, length);
|
||||
return length;
|
||||
}
|
||||
if (space < 16 || length > (space - 16))
|
||||
throw new ShortBufferException();
|
||||
setup(ad);
|
||||
encryptCTR(plaintext, plaintextOffset, ciphertext, ciphertextOffset, length);
|
||||
ghash.update(ciphertext, ciphertextOffset, length);
|
||||
ghash.pad(ad != null ? ad.length : 0, length);
|
||||
ghash.finish(ciphertext, ciphertextOffset + length, 16);
|
||||
for (int index = 0; index < 16; ++index)
|
||||
ciphertext[ciphertextOffset + length + index] ^= hashKey[index];
|
||||
return length + 16;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int decryptWithAd(byte[] ad, byte[] ciphertext,
|
||||
int ciphertextOffset, byte[] plaintext, int plaintextOffset,
|
||||
int length) throws ShortBufferException, BadPaddingException {
|
||||
int space;
|
||||
if (ciphertextOffset < 0 || ciphertextOffset > ciphertext.length)
|
||||
throw new IllegalArgumentException();
|
||||
else
|
||||
space = ciphertext.length - ciphertextOffset;
|
||||
if (length > space)
|
||||
throw new ShortBufferException();
|
||||
if (length < 0 || plaintextOffset < 0 || plaintextOffset > plaintext.length || length > ciphertext.length || (ciphertext.length - ciphertextOffset) < length)
|
||||
throw new IllegalArgumentException();
|
||||
space = plaintext.length - plaintextOffset;
|
||||
if (!haskey) {
|
||||
// The key is not set yet - return the ciphertext as-is.
|
||||
if (length > space)
|
||||
throw new ShortBufferException();
|
||||
if (plaintext != ciphertext || plaintextOffset != ciphertextOffset)
|
||||
System.arraycopy(ciphertext, ciphertextOffset, plaintext, plaintextOffset, length);
|
||||
return length;
|
||||
}
|
||||
if (length < 16)
|
||||
Noise.throwBadTagException();
|
||||
int dataLen = length - 16;
|
||||
if (dataLen > space)
|
||||
throw new ShortBufferException();
|
||||
setup(ad);
|
||||
ghash.update(ciphertext, ciphertextOffset, dataLen);
|
||||
ghash.pad(ad != null ? ad.length : 0, dataLen);
|
||||
ghash.finish(enciv, 0, 16);
|
||||
int temp = 0;
|
||||
for (int index = 0; index < 16; ++index)
|
||||
temp |= (hashKey[index] ^ enciv[index] ^ ciphertext[ciphertextOffset + dataLen + index]);
|
||||
if ((temp & 0xFF) != 0)
|
||||
Noise.throwBadTagException();
|
||||
encryptCTR(ciphertext, ciphertextOffset, plaintext, plaintextOffset, dataLen);
|
||||
return dataLen;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CipherState fork(byte[] key, int offset) {
|
||||
CipherState cipher;
|
||||
cipher = new AESGCMFallbackCipherState();
|
||||
cipher.initializeKey(key, offset);
|
||||
return cipher;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNonce(long nonce) {
|
||||
n = nonce;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,335 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Southern Storm Software, Pty Ltd.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included
|
||||
* in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.futo.platformplayer.noise.protocol;
|
||||
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.ShortBufferException;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import com.futo.platformplayer.noise.crypto.GHASH;
|
||||
|
||||
/**
|
||||
* Emulates the "AESGCM" cipher for Noise using the "AES/CTR/NoPadding"
|
||||
* transformation from JCA/JCE.
|
||||
*
|
||||
* This class is used on platforms that don't have "AES/GCM/NoPadding",
|
||||
* but which do have the older "AES/CTR/NoPadding".
|
||||
*/
|
||||
class AESGCMOnCtrCipherState implements CipherState {
|
||||
|
||||
private Cipher cipher;
|
||||
private SecretKeySpec keySpec;
|
||||
private long n;
|
||||
private byte[] iv;
|
||||
private byte[] hashKey;
|
||||
private GHASH ghash;
|
||||
|
||||
/**
|
||||
* Constructs a new cipher state for the "AESGCM" algorithm.
|
||||
*
|
||||
* @throws NoSuchAlgorithmException The system does not have a
|
||||
* provider for this algorithm.
|
||||
*/
|
||||
public AESGCMOnCtrCipherState() throws NoSuchAlgorithmException
|
||||
{
|
||||
try {
|
||||
cipher = Cipher.getInstance("AES/CTR/NoPadding");
|
||||
} catch (NoSuchPaddingException e) {
|
||||
// AES/CTR is available, but not the unpadded version? Huh?
|
||||
throw new NoSuchAlgorithmException("AES/CTR/NoPadding not available", e);
|
||||
}
|
||||
keySpec = null;
|
||||
n = 0;
|
||||
iv = new byte [16];
|
||||
hashKey = new byte [16];
|
||||
ghash = new GHASH();
|
||||
|
||||
// Try to set a 256-bit key on the cipher. Some JCE's are
|
||||
// configured to disallow 256-bit AES if an extra policy
|
||||
// file has not been installed.
|
||||
try {
|
||||
SecretKeySpec spec = new SecretKeySpec(new byte [32], "AES");
|
||||
IvParameterSpec params = new IvParameterSpec(iv);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, spec, params);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new NoSuchAlgorithmException("AES/CTR/NoPadding does not support 256-bit keys", e);
|
||||
} catch (InvalidAlgorithmParameterException e) {
|
||||
throw new NoSuchAlgorithmException("AES/CTR/NoPadding does not support 256-bit keys", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
// There doesn't seem to be a standard API to clean out a Cipher.
|
||||
// So we instead set the key and IV to all-zeroes to hopefully
|
||||
// destroy the sensitive data in the cipher instance.
|
||||
ghash.destroy();
|
||||
Noise.destroy(hashKey);
|
||||
Noise.destroy(iv);
|
||||
keySpec = new SecretKeySpec(new byte [32], "AES");
|
||||
IvParameterSpec params = new IvParameterSpec(iv);
|
||||
try {
|
||||
cipher.init(Cipher.ENCRYPT_MODE, keySpec, params);
|
||||
} catch (InvalidKeyException e) {
|
||||
// Shouldn't happen.
|
||||
} catch (InvalidAlgorithmParameterException e) {
|
||||
// Shouldn't happen.
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCipherName() {
|
||||
return "AESGCM";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getKeyLength() {
|
||||
return 32;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMACLength() {
|
||||
return keySpec != null ? 16 : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initializeKey(byte[] key, int offset) {
|
||||
// Set the encryption key.
|
||||
keySpec = new SecretKeySpec(key, offset, 32, "AES");
|
||||
|
||||
// Generate the hashing key by encrypting a block of zeroes.
|
||||
Arrays.fill(iv, (byte)0);
|
||||
Arrays.fill(hashKey, (byte)0);
|
||||
try {
|
||||
cipher.init(Cipher.ENCRYPT_MODE, keySpec, new IvParameterSpec(iv));
|
||||
} catch (InvalidKeyException e) {
|
||||
// Shouldn't happen.
|
||||
throw new IllegalStateException(e);
|
||||
} catch (InvalidAlgorithmParameterException e) {
|
||||
// Shouldn't happen.
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
try {
|
||||
int result = cipher.update(hashKey, 0, 16, hashKey, 0);
|
||||
cipher.doFinal(hashKey, result);
|
||||
} catch (ShortBufferException e) {
|
||||
// Shouldn't happen.
|
||||
throw new IllegalStateException(e);
|
||||
} catch (IllegalBlockSizeException e) {
|
||||
// Shouldn't happen.
|
||||
throw new IllegalStateException(e);
|
||||
} catch (BadPaddingException e) {
|
||||
// Shouldn't happen.
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
ghash.reset(hashKey, 0);
|
||||
|
||||
// Reset the nonce.
|
||||
n = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasKey() {
|
||||
return keySpec != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up to encrypt or decrypt the next packet.
|
||||
*
|
||||
* @param ad The associated data for the packet.
|
||||
*/
|
||||
private void setup(byte[] ad) throws InvalidKeyException, InvalidAlgorithmParameterException
|
||||
{
|
||||
// Check for nonce wrap-around.
|
||||
if (n == -1L)
|
||||
throw new IllegalStateException("Nonce has wrapped around");
|
||||
|
||||
// Format the counter/IV block for AES/CTR/NoPadding.
|
||||
iv[0] = 0;
|
||||
iv[1] = 0;
|
||||
iv[2] = 0;
|
||||
iv[3] = 0;
|
||||
iv[4] = (byte)(n >> 56);
|
||||
iv[5] = (byte)(n >> 48);
|
||||
iv[6] = (byte)(n >> 40);
|
||||
iv[7] = (byte)(n >> 32);
|
||||
iv[8] = (byte)(n >> 24);
|
||||
iv[9] = (byte)(n >> 16);
|
||||
iv[10] = (byte)(n >> 8);
|
||||
iv[11] = (byte)n;
|
||||
iv[12] = 0;
|
||||
iv[13] = 0;
|
||||
iv[14] = 0;
|
||||
iv[15] = 1;
|
||||
++n;
|
||||
|
||||
// Initialize the CTR mode cipher with the key and IV.
|
||||
cipher.init(Cipher.ENCRYPT_MODE, keySpec, new IvParameterSpec(iv));
|
||||
|
||||
// Encrypt a block of zeroes to generate the hash key to XOR
|
||||
// the GHASH tag with at the end of the encrypt/decrypt operation.
|
||||
Arrays.fill(hashKey, (byte)0);
|
||||
try {
|
||||
cipher.update(hashKey, 0, 16, hashKey, 0);
|
||||
} catch (ShortBufferException e) {
|
||||
// Shouldn't happen.
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
|
||||
// Initialize the GHASH with the associated data value.
|
||||
ghash.reset();
|
||||
if (ad != null) {
|
||||
ghash.update(ad, 0, ad.length);
|
||||
ghash.pad();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int encryptWithAd(byte[] ad, byte[] plaintext, int plaintextOffset,
|
||||
byte[] ciphertext, int ciphertextOffset, int length)
|
||||
throws ShortBufferException {
|
||||
int space;
|
||||
if (ciphertextOffset < 0 || ciphertextOffset > ciphertext.length)
|
||||
throw new IllegalArgumentException();
|
||||
if (length < 0 || plaintextOffset < 0 || plaintextOffset > plaintext.length || length > plaintext.length || (plaintext.length - plaintextOffset) < length)
|
||||
throw new IllegalArgumentException();
|
||||
space = ciphertext.length - ciphertextOffset;
|
||||
if (keySpec == null) {
|
||||
// The key is not set yet - return the plaintext as-is.
|
||||
if (length > space)
|
||||
throw new ShortBufferException();
|
||||
if (plaintext != ciphertext || plaintextOffset != ciphertextOffset)
|
||||
System.arraycopy(plaintext, plaintextOffset, ciphertext, ciphertextOffset, length);
|
||||
return length;
|
||||
}
|
||||
if (space < 16 || length > (space - 16))
|
||||
throw new ShortBufferException();
|
||||
try {
|
||||
setup(ad);
|
||||
int result = cipher.update(plaintext, plaintextOffset, length, ciphertext, ciphertextOffset);
|
||||
cipher.doFinal(ciphertext, ciphertextOffset + result);
|
||||
} catch (InvalidKeyException e) {
|
||||
// Shouldn't happen.
|
||||
throw new IllegalStateException(e);
|
||||
} catch (InvalidAlgorithmParameterException e) {
|
||||
// Shouldn't happen.
|
||||
throw new IllegalStateException(e);
|
||||
} catch (IllegalBlockSizeException e) {
|
||||
// Shouldn't happen.
|
||||
throw new IllegalStateException(e);
|
||||
} catch (BadPaddingException e) {
|
||||
// Shouldn't happen.
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
ghash.update(ciphertext, ciphertextOffset, length);
|
||||
ghash.pad(ad != null ? ad.length : 0, length);
|
||||
ghash.finish(ciphertext, ciphertextOffset + length, 16);
|
||||
for (int index = 0; index < 16; ++index)
|
||||
ciphertext[ciphertextOffset + length + index] ^= hashKey[index];
|
||||
return length + 16;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int decryptWithAd(byte[] ad, byte[] ciphertext,
|
||||
int ciphertextOffset, byte[] plaintext, int plaintextOffset,
|
||||
int length) throws ShortBufferException, BadPaddingException {
|
||||
int space;
|
||||
if (ciphertextOffset < 0 || ciphertextOffset > ciphertext.length)
|
||||
throw new IllegalArgumentException();
|
||||
else
|
||||
space = ciphertext.length - ciphertextOffset;
|
||||
if (length > space)
|
||||
throw new ShortBufferException();
|
||||
if (length < 0 || plaintextOffset < 0 || plaintextOffset > plaintext.length || length > ciphertext.length || (ciphertext.length - ciphertextOffset) < length)
|
||||
throw new IllegalArgumentException();
|
||||
space = plaintext.length - plaintextOffset;
|
||||
if (keySpec == null) {
|
||||
// The key is not set yet - return the ciphertext as-is.
|
||||
if (length > space)
|
||||
throw new ShortBufferException();
|
||||
if (plaintext != ciphertext || plaintextOffset != ciphertextOffset)
|
||||
System.arraycopy(ciphertext, ciphertextOffset, plaintext, plaintextOffset, length);
|
||||
return length;
|
||||
}
|
||||
if (length < 16)
|
||||
Noise.throwBadTagException();
|
||||
int dataLen = length - 16;
|
||||
if (dataLen > space)
|
||||
throw new ShortBufferException();
|
||||
try {
|
||||
setup(ad);
|
||||
} catch (InvalidKeyException e) {
|
||||
// Shouldn't happen.
|
||||
throw new IllegalStateException(e);
|
||||
} catch (InvalidAlgorithmParameterException e) {
|
||||
// Shouldn't happen.
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
ghash.update(ciphertext, ciphertextOffset, dataLen);
|
||||
ghash.pad(ad != null ? ad.length : 0, dataLen);
|
||||
ghash.finish(iv, 0, 16);
|
||||
int temp = 0;
|
||||
for (int index = 0; index < 16; ++index)
|
||||
temp |= (hashKey[index] ^ iv[index] ^ ciphertext[ciphertextOffset + dataLen + index]);
|
||||
if ((temp & 0xFF) != 0)
|
||||
Noise.throwBadTagException();
|
||||
try {
|
||||
int result = cipher.update(ciphertext, ciphertextOffset, dataLen, plaintext, plaintextOffset);
|
||||
cipher.doFinal(plaintext, plaintextOffset + result);
|
||||
} catch (IllegalBlockSizeException e) {
|
||||
// Shouldn't happen.
|
||||
throw new IllegalStateException(e);
|
||||
} catch (BadPaddingException e) {
|
||||
// Shouldn't happen.
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
return dataLen;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CipherState fork(byte[] key, int offset) {
|
||||
CipherState cipher;
|
||||
try {
|
||||
cipher = new AESGCMOnCtrCipherState();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
// Shouldn't happen.
|
||||
return null;
|
||||
}
|
||||
cipher.initializeKey(key, offset);
|
||||
return cipher;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNonce(long nonce) {
|
||||
n = nonce;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Southern Storm Software, Pty Ltd.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included
|
||||
* in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.futo.platformplayer.noise.protocol;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.ShortBufferException;
|
||||
|
||||
import com.futo.platformplayer.noise.crypto.ChaChaCore;
|
||||
import com.futo.platformplayer.noise.crypto.Poly1305;
|
||||
|
||||
/**
|
||||
* Implements the ChaChaPoly cipher for Noise.
|
||||
*/
|
||||
class ChaChaPolyCipherState implements CipherState {
|
||||
|
||||
private Poly1305 poly;
|
||||
private int[] input;
|
||||
private int[] output;
|
||||
private byte[] polyKey;
|
||||
long n;
|
||||
private boolean haskey;
|
||||
|
||||
/**
|
||||
* Constructs a new cipher state for the "ChaChaPoly" algorithm.
|
||||
*/
|
||||
public ChaChaPolyCipherState()
|
||||
{
|
||||
poly = new Poly1305();
|
||||
input = new int [16];
|
||||
output = new int [16];
|
||||
polyKey = new byte [32];
|
||||
n = 0;
|
||||
haskey = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
poly.destroy();
|
||||
Arrays.fill(input, 0);
|
||||
Arrays.fill(output, 0);
|
||||
Noise.destroy(polyKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCipherName() {
|
||||
return "ChaChaPoly";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getKeyLength() {
|
||||
return 32;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMACLength() {
|
||||
return haskey ? 16 : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initializeKey(byte[] key, int offset) {
|
||||
ChaChaCore.initKey256(input, key, offset);
|
||||
n = 0;
|
||||
haskey = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasKey() {
|
||||
return haskey;
|
||||
}
|
||||
|
||||
/**
|
||||
* XOR's the output of ChaCha20 with a byte buffer.
|
||||
*
|
||||
* @param input The input byte buffer.
|
||||
* @param inputOffset The offset of the first input byte.
|
||||
* @param output The output byte buffer (can be the same as the input).
|
||||
* @param outputOffset The offset of the first output byte.
|
||||
* @param length The number of bytes to XOR between 1 and 64.
|
||||
* @param block The ChaCha20 output block.
|
||||
*/
|
||||
private static void xorBlock(byte[] input, int inputOffset, byte[] output, int outputOffset, int length, int[] block)
|
||||
{
|
||||
int posn = 0;
|
||||
int value;
|
||||
while (length >= 4) {
|
||||
value = block[posn++];
|
||||
output[outputOffset] = (byte)(input[inputOffset] ^ value);
|
||||
output[outputOffset + 1] = (byte)(input[inputOffset + 1] ^ (value >> 8));
|
||||
output[outputOffset + 2] = (byte)(input[inputOffset + 2] ^ (value >> 16));
|
||||
output[outputOffset + 3] = (byte)(input[inputOffset + 3] ^ (value >> 24));
|
||||
inputOffset += 4;
|
||||
outputOffset += 4;
|
||||
length -= 4;
|
||||
}
|
||||
if (length == 3) {
|
||||
value = block[posn];
|
||||
output[outputOffset] = (byte)(input[inputOffset] ^ value);
|
||||
output[outputOffset + 1] = (byte)(input[inputOffset + 1] ^ (value >> 8));
|
||||
output[outputOffset + 2] = (byte)(input[inputOffset + 2] ^ (value >> 16));
|
||||
} else if (length == 2) {
|
||||
value = block[posn];
|
||||
output[outputOffset] = (byte)(input[inputOffset] ^ value);
|
||||
output[outputOffset + 1] = (byte)(input[inputOffset + 1] ^ (value >> 8));
|
||||
} else if (length == 1) {
|
||||
value = block[posn];
|
||||
output[outputOffset] = (byte)(input[inputOffset] ^ value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up to encrypt or decrypt the next packet.
|
||||
*
|
||||
* @param ad The associated data for the packet.
|
||||
*/
|
||||
private void setup(byte[] ad)
|
||||
{
|
||||
if (n == -1L)
|
||||
throw new IllegalStateException("Nonce has wrapped around");
|
||||
ChaChaCore.initIV(input, n++);
|
||||
ChaChaCore.hash(output, input);
|
||||
Arrays.fill(polyKey, (byte)0);
|
||||
xorBlock(polyKey, 0, polyKey, 0, 32, output);
|
||||
poly.reset(polyKey, 0);
|
||||
if (ad != null) {
|
||||
poly.update(ad, 0, ad.length);
|
||||
poly.pad();
|
||||
}
|
||||
if (++(input[12]) == 0)
|
||||
++(input[13]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts a 64-bit integer into a buffer in little-endian order.
|
||||
*
|
||||
* @param output The output buffer.
|
||||
* @param offset The offset into the output buffer.
|
||||
* @param value The 64-bit integer value.
|
||||
*/
|
||||
private static void putLittleEndian64(byte[] output, int offset, long value)
|
||||
{
|
||||
output[offset] = (byte)value;
|
||||
output[offset + 1] = (byte)(value >> 8);
|
||||
output[offset + 2] = (byte)(value >> 16);
|
||||
output[offset + 3] = (byte)(value >> 24);
|
||||
output[offset + 4] = (byte)(value >> 32);
|
||||
output[offset + 5] = (byte)(value >> 40);
|
||||
output[offset + 6] = (byte)(value >> 48);
|
||||
output[offset + 7] = (byte)(value >> 56);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finishes up the authentication tag for a packet.
|
||||
*
|
||||
* @param ad The associated data.
|
||||
* @param length The length of the plaintext data.
|
||||
*/
|
||||
private void finish(byte[] ad, int length)
|
||||
{
|
||||
poly.pad();
|
||||
putLittleEndian64(polyKey, 0, ad != null ? ad.length : 0);
|
||||
putLittleEndian64(polyKey, 8, length);
|
||||
poly.update(polyKey, 0, 16);
|
||||
poly.finish(polyKey, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts or decrypts a buffer of bytes for the active packet.
|
||||
*
|
||||
* @param plaintext The plaintext data to be encrypted.
|
||||
* @param plaintextOffset The offset to the first plaintext byte.
|
||||
* @param ciphertext The ciphertext data that results from encryption.
|
||||
* @param ciphertextOffset The offset to the first ciphertext byte.
|
||||
* @param length The number of bytes to encrypt.
|
||||
*/
|
||||
private void encrypt(byte[] plaintext, int plaintextOffset,
|
||||
byte[] ciphertext, int ciphertextOffset, int length) {
|
||||
while (length > 0) {
|
||||
int tempLen = 64;
|
||||
if (tempLen > length)
|
||||
tempLen = length;
|
||||
ChaChaCore.hash(output, input);
|
||||
xorBlock(plaintext, plaintextOffset, ciphertext, ciphertextOffset, tempLen, output);
|
||||
if (++(input[12]) == 0)
|
||||
++(input[13]);
|
||||
plaintextOffset += tempLen;
|
||||
ciphertextOffset += tempLen;
|
||||
length -= tempLen;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int encryptWithAd(byte[] ad, byte[] plaintext, int plaintextOffset,
|
||||
byte[] ciphertext, int ciphertextOffset, int length) throws ShortBufferException {
|
||||
int space;
|
||||
if (ciphertextOffset < 0 || ciphertextOffset > ciphertext.length)
|
||||
throw new IllegalArgumentException();
|
||||
if (length < 0 || plaintextOffset < 0 || plaintextOffset > plaintext.length || length > plaintext.length || (plaintext.length - plaintextOffset) < length)
|
||||
throw new IllegalArgumentException();
|
||||
space = ciphertext.length - ciphertextOffset;
|
||||
if (!haskey) {
|
||||
// The key is not set yet - return the plaintext as-is.
|
||||
if (length > space)
|
||||
throw new ShortBufferException();
|
||||
if (plaintext != ciphertext || plaintextOffset != ciphertextOffset)
|
||||
System.arraycopy(plaintext, plaintextOffset, ciphertext, ciphertextOffset, length);
|
||||
return length;
|
||||
}
|
||||
if (space < 16 || length > (space - 16))
|
||||
throw new ShortBufferException();
|
||||
setup(ad);
|
||||
encrypt(plaintext, plaintextOffset, ciphertext, ciphertextOffset, length);
|
||||
poly.update(ciphertext, ciphertextOffset, length);
|
||||
finish(ad, length);
|
||||
System.arraycopy(polyKey, 0, ciphertext, ciphertextOffset + length, 16);
|
||||
return length + 16;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int decryptWithAd(byte[] ad, byte[] ciphertext,
|
||||
int ciphertextOffset, byte[] plaintext, int plaintextOffset,
|
||||
int length) throws ShortBufferException, BadPaddingException {
|
||||
int space;
|
||||
if (ciphertextOffset < 0 || ciphertextOffset > ciphertext.length)
|
||||
throw new IllegalArgumentException();
|
||||
else
|
||||
space = ciphertext.length - ciphertextOffset;
|
||||
if (length > space)
|
||||
throw new ShortBufferException();
|
||||
if (length < 0 || plaintextOffset < 0 || plaintextOffset > plaintext.length || length > ciphertext.length || (ciphertext.length - ciphertextOffset) < length)
|
||||
throw new IllegalArgumentException();
|
||||
space = plaintext.length - plaintextOffset;
|
||||
if (!haskey) {
|
||||
// The key is not set yet - return the ciphertext as-is.
|
||||
if (length > space)
|
||||
throw new ShortBufferException();
|
||||
if (plaintext != ciphertext || plaintextOffset != ciphertextOffset)
|
||||
System.arraycopy(ciphertext, ciphertextOffset, plaintext, plaintextOffset, length);
|
||||
return length;
|
||||
}
|
||||
if (length < 16)
|
||||
Noise.throwBadTagException();
|
||||
int dataLen = length - 16;
|
||||
if (dataLen > space)
|
||||
throw new ShortBufferException();
|
||||
setup(ad);
|
||||
poly.update(ciphertext, ciphertextOffset, dataLen);
|
||||
finish(ad, dataLen);
|
||||
int temp = 0;
|
||||
for (int index = 0; index < 16; ++index)
|
||||
temp |= (polyKey[index] ^ ciphertext[ciphertextOffset + dataLen + index]);
|
||||
if ((temp & 0xFF) != 0)
|
||||
Noise.throwBadTagException();
|
||||
encrypt(ciphertext, ciphertextOffset, plaintext, plaintextOffset, dataLen);
|
||||
return dataLen;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CipherState fork(byte[] key, int offset) {
|
||||
CipherState cipher = new ChaChaPolyCipherState();
|
||||
cipher.initializeKey(key, offset);
|
||||
return cipher;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNonce(long nonce) {
|
||||
n = nonce;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Southern Storm Software, Pty Ltd.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included
|
||||
* in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.futo.platformplayer.noise.protocol;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.ShortBufferException;
|
||||
|
||||
/**
|
||||
* Interface to an authenticated cipher for use in the Noise protocol.
|
||||
*
|
||||
* CipherState objects are used to encrypt or decrypt data during a
|
||||
* session. Once the handshake has completed, HandshakeState.split()
|
||||
* will create two CipherState objects for encrypting packets sent to
|
||||
* the other party, and decrypting packets received from the other party.
|
||||
*/
|
||||
public interface CipherState extends Destroyable {
|
||||
|
||||
/**
|
||||
* Gets the Noise protocol name for this cipher.
|
||||
*
|
||||
* @return The cipher name.
|
||||
*/
|
||||
String getCipherName();
|
||||
|
||||
/**
|
||||
* Gets the length of the key values for this cipher.
|
||||
*
|
||||
* @return The length of the key in bytes; usually 32.
|
||||
*/
|
||||
int getKeyLength();
|
||||
|
||||
/**
|
||||
* Gets the length of the MAC values for this cipher.
|
||||
*
|
||||
* @return The length of MAC values in bytes, or zero if the
|
||||
* key has not yet been initialized.
|
||||
*/
|
||||
int getMACLength();
|
||||
|
||||
/**
|
||||
* Initializes the key on this cipher object.
|
||||
*
|
||||
* @param key Points to a buffer that contains the key.
|
||||
* @param offset The offset of the key in the key buffer.
|
||||
*
|
||||
* The key buffer must contain at least getKeyLength() bytes
|
||||
* starting at offset.
|
||||
*
|
||||
* @see #hasKey()
|
||||
*/
|
||||
void initializeKey(byte[] key, int offset);
|
||||
|
||||
/**
|
||||
* Determine if this cipher object has been configured with a key.
|
||||
*
|
||||
* @return true if this cipher object has a key; false if the
|
||||
* key has not yet been set with initializeKey().
|
||||
*
|
||||
* @see #initializeKey(byte[], int)
|
||||
*/
|
||||
boolean hasKey();
|
||||
|
||||
/**
|
||||
* Encrypts a plaintext buffer using the cipher and a block of associated data.
|
||||
*
|
||||
* @param ad The associated data, or null if there is none.
|
||||
* @param plaintext The buffer containing the plaintext to encrypt.
|
||||
* @param plaintextOffset The offset within the plaintext buffer of the
|
||||
* first byte or plaintext data.
|
||||
* @param ciphertext The buffer to place the ciphertext in. This can
|
||||
* be the same as the plaintext buffer.
|
||||
* @param ciphertextOffset The first offset within the ciphertext buffer
|
||||
* to place the ciphertext and the MAC tag.
|
||||
* @param length The length of the plaintext.
|
||||
* @return The length of the ciphertext plus the MAC tag, or -1 if the
|
||||
* ciphertext buffer is not large enough to hold the result.
|
||||
*
|
||||
* @throws ShortBufferException The ciphertext buffer does not have
|
||||
* enough space to hold the ciphertext plus MAC.
|
||||
*
|
||||
* @throws IllegalStateException The nonce has wrapped around.
|
||||
*
|
||||
* @throws IllegalArgumentException One of the parameters is out of range.
|
||||
*
|
||||
* The plaintext and ciphertext buffers can be the same for in-place
|
||||
* encryption. In that case, plaintextOffset must be identical to
|
||||
* ciphertextOffset.
|
||||
*
|
||||
* There must be enough space in the ciphertext buffer to accomodate
|
||||
* length + getMACLength() bytes of data starting at ciphertextOffset.
|
||||
*/
|
||||
int encryptWithAd(byte[] ad, byte[] plaintext, int plaintextOffset, byte[] ciphertext, int ciphertextOffset, int length) throws ShortBufferException;
|
||||
|
||||
/**
|
||||
* Decrypts a ciphertext buffer using the cipher and a block of associated data.
|
||||
*
|
||||
* @param ad The associated data, or null if there is none.
|
||||
* @param ciphertext The buffer containing the ciphertext to decrypt.
|
||||
* @param ciphertextOffset The offset within the ciphertext buffer of
|
||||
* the first byte of ciphertext data.
|
||||
* @param plaintext The buffer to place the plaintext in. This can be
|
||||
* the same as the ciphertext buffer.
|
||||
* @param plaintextOffset The first offset within the plaintext buffer
|
||||
* to place the plaintext.
|
||||
* @param length The length of the incoming ciphertext plus the MAC tag.
|
||||
* @return The length of the plaintext with the MAC tag stripped off.
|
||||
*
|
||||
* @throws ShortBufferException The plaintext buffer does not have
|
||||
* enough space to store the decrypted data.
|
||||
*
|
||||
* @throws BadPaddingException The MAC value failed to verify.
|
||||
*
|
||||
* @throws IllegalStateException The nonce has wrapped around.
|
||||
*
|
||||
* @throws IllegalArgumentException One of the parameters is out of range.
|
||||
*
|
||||
* The plaintext and ciphertext buffers can be the same for in-place
|
||||
* decryption. In that case, ciphertextOffset must be identical to
|
||||
* plaintextOffset.
|
||||
*/
|
||||
int decryptWithAd(byte[] ad, byte[] ciphertext, int ciphertextOffset, byte[] plaintext, int plaintextOffset, int length) throws ShortBufferException, BadPaddingException;
|
||||
|
||||
/**
|
||||
* Creates a new instance of this cipher and initializes it with a key.
|
||||
*
|
||||
* @param key The buffer containing the key.
|
||||
* @param offset The offset into the key buffer of the first key byte.
|
||||
* @return A new CipherState of the same class as this one.
|
||||
*/
|
||||
CipherState fork(byte[] key, int offset);
|
||||
|
||||
/**
|
||||
* Sets the nonce value.
|
||||
*
|
||||
* @param nonce The new nonce value, which must be greater than or equal
|
||||
* to the current value.
|
||||
*
|
||||
* This function is intended for testing purposes only. If the nonce
|
||||
* value goes backwards then security may be compromised.
|
||||
*/
|
||||
void setNonce(long nonce);
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Southern Storm Software, Pty Ltd.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included
|
||||
* in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.futo.platformplayer.noise.protocol;
|
||||
|
||||
/**
|
||||
* Class that contains a pair of CipherState objects.
|
||||
*
|
||||
* CipherState pairs typically arise when HandshakeState.split() is called.
|
||||
*/
|
||||
public final class CipherStatePair implements Destroyable {
|
||||
|
||||
private CipherState send;
|
||||
private CipherState recv;
|
||||
|
||||
/**
|
||||
* Constructs a pair of CipherState objects.
|
||||
*
|
||||
* @param sender The CipherState to use to send packets to the remote party.
|
||||
* @param receiver The CipherState to use to receive packets from the remote party.
|
||||
*/
|
||||
public CipherStatePair(CipherState sender, CipherState receiver)
|
||||
{
|
||||
send = sender;
|
||||
recv = receiver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the CipherState to use to send packets to the remote party.
|
||||
*
|
||||
* @return The sending CipherState.
|
||||
*/
|
||||
public CipherState getSender() {
|
||||
return send;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the CipherState to use to receive packets from the remote party.
|
||||
*
|
||||
* @return The receiving CipherState.
|
||||
*/
|
||||
public CipherState getReceiver() {
|
||||
return recv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys the receiving CipherState and retains only the sending CipherState.
|
||||
*
|
||||
* This function is intended for use with one-way handshake patterns.
|
||||
*/
|
||||
public void senderOnly()
|
||||
{
|
||||
if (recv != null) {
|
||||
recv.destroy();
|
||||
recv = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys the sending CipherState and retains only the receiving CipherState.
|
||||
*
|
||||
* This function is intended for use with one-way handshake patterns.
|
||||
*/
|
||||
public void receiverOnly()
|
||||
{
|
||||
if (send != null) {
|
||||
send.destroy();
|
||||
send = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Swaps the sender and receiver.
|
||||
*/
|
||||
public void swap()
|
||||
{
|
||||
CipherState temp = send;
|
||||
send = recv;
|
||||
recv = temp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
senderOnly();
|
||||
receiverOnly();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Southern Storm Software, Pty Ltd.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included
|
||||
* in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.futo.platformplayer.noise.protocol;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import com.futo.platformplayer.noise.crypto.Curve25519;
|
||||
|
||||
/**
|
||||
* Implementation of the Curve25519 algorithm for the Noise protocol.
|
||||
*/
|
||||
class Curve25519DHState implements DHState {
|
||||
|
||||
private byte[] publicKey;
|
||||
private byte[] privateKey;
|
||||
private int mode;
|
||||
|
||||
/**
|
||||
* Constructs a new Diffie-Hellman object for Curve25519.
|
||||
*/
|
||||
public Curve25519DHState()
|
||||
{
|
||||
publicKey = new byte [32];
|
||||
privateKey = new byte [32];
|
||||
mode = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
clearKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDHName() {
|
||||
return "25519";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPublicKeyLength() {
|
||||
return 32;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPrivateKeyLength() {
|
||||
return 32;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSharedKeyLength() {
|
||||
return 32;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generateKeyPair() {
|
||||
Noise.random(privateKey);
|
||||
Curve25519.eval(publicKey, 0, privateKey, null);
|
||||
mode = 0x03;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getPublicKey(byte[] key, int offset) {
|
||||
System.arraycopy(publicKey, 0, key, offset, 32);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPublicKey(byte[] key, int offset) {
|
||||
System.arraycopy(key, offset, publicKey, 0, 32);
|
||||
Arrays.fill(privateKey, (byte)0);
|
||||
mode = 0x01;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getPrivateKey(byte[] key, int offset) {
|
||||
System.arraycopy(privateKey, 0, key, offset, 32);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPrivateKey(byte[] key, int offset) {
|
||||
System.arraycopy(key, offset, privateKey, 0, 32);
|
||||
Curve25519.eval(publicKey, 0, privateKey, null);
|
||||
mode = 0x03;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setToNullPublicKey() {
|
||||
Arrays.fill(publicKey, (byte)0);
|
||||
Arrays.fill(privateKey, (byte)0);
|
||||
mode = 0x01;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearKey() {
|
||||
Noise.destroy(publicKey);
|
||||
Noise.destroy(privateKey);
|
||||
mode = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPublicKey() {
|
||||
return (mode & 0x01) != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPrivateKey() {
|
||||
return (mode & 0x02) != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNullPublicKey() {
|
||||
if ((mode & 0x01) == 0)
|
||||
return false;
|
||||
int temp = 0;
|
||||
for (int index = 0; index < 32; ++index)
|
||||
temp |= publicKey[index];
|
||||
return temp == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void calculate(byte[] sharedKey, int offset, DHState publicDH) {
|
||||
if (!(publicDH instanceof Curve25519DHState))
|
||||
throw new IllegalArgumentException("Incompatible DH algorithms");
|
||||
Curve25519.eval(sharedKey, offset, privateKey, ((Curve25519DHState)publicDH).publicKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyFrom(DHState other) {
|
||||
if (!(other instanceof Curve25519DHState))
|
||||
throw new IllegalStateException("Mismatched DH key objects");
|
||||
if (other == this)
|
||||
return;
|
||||
Curve25519DHState dh = (Curve25519DHState)other;
|
||||
System.arraycopy(dh.privateKey, 0, privateKey, 0, 32);
|
||||
System.arraycopy(dh.publicKey, 0, publicKey, 0, 32);
|
||||
mode = dh.mode;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Southern Storm Software, Pty Ltd.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included
|
||||
* in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.futo.platformplayer.noise.protocol;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import com.futo.platformplayer.noise.crypto.Curve448;
|
||||
|
||||
/**
|
||||
* Implementation of the Curve448 algorithm for the Noise protocol.
|
||||
*/
|
||||
class Curve448DHState implements DHState {
|
||||
|
||||
private byte[] publicKey;
|
||||
private byte[] privateKey;
|
||||
private int mode;
|
||||
|
||||
/**
|
||||
* Constructs a new Diffie-Hellman object for Curve448.
|
||||
*/
|
||||
public Curve448DHState()
|
||||
{
|
||||
publicKey = new byte [56];
|
||||
privateKey = new byte [56];
|
||||
mode = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
clearKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDHName() {
|
||||
return "448";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPublicKeyLength() {
|
||||
return 56;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPrivateKeyLength() {
|
||||
return 56;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSharedKeyLength() {
|
||||
return 56;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generateKeyPair() {
|
||||
Noise.random(privateKey);
|
||||
Curve448.eval(publicKey, 0, privateKey, null);
|
||||
mode = 0x03;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getPublicKey(byte[] key, int offset) {
|
||||
System.arraycopy(publicKey, 0, key, offset, 56);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPublicKey(byte[] key, int offset) {
|
||||
System.arraycopy(key, offset, publicKey, 0, 56);
|
||||
Arrays.fill(privateKey, (byte)0);
|
||||
mode = 0x01;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getPrivateKey(byte[] key, int offset) {
|
||||
System.arraycopy(privateKey, 0, key, offset, 56);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPrivateKey(byte[] key, int offset) {
|
||||
System.arraycopy(key, offset, privateKey, 0, 56);
|
||||
Curve448.eval(publicKey, 0, privateKey, null);
|
||||
mode = 0x03;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setToNullPublicKey() {
|
||||
Arrays.fill(publicKey, (byte)0);
|
||||
Arrays.fill(privateKey, (byte)0);
|
||||
mode = 0x01;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearKey() {
|
||||
Noise.destroy(publicKey);
|
||||
Noise.destroy(privateKey);
|
||||
mode = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPublicKey() {
|
||||
return (mode & 0x01) != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPrivateKey() {
|
||||
return (mode & 0x02) != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNullPublicKey() {
|
||||
if ((mode & 0x01) == 0)
|
||||
return false;
|
||||
int temp = 0;
|
||||
for (int index = 0; index < 56; ++index)
|
||||
temp |= publicKey[index];
|
||||
return temp == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void calculate(byte[] sharedKey, int offset, DHState publicDH) {
|
||||
if (!(publicDH instanceof Curve448DHState))
|
||||
throw new IllegalArgumentException("Incompatible DH algorithms");
|
||||
Curve448.eval(sharedKey, offset, privateKey, ((Curve448DHState)publicDH).publicKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyFrom(DHState other) {
|
||||
if (!(other instanceof Curve448DHState))
|
||||
throw new IllegalStateException("Mismatched DH key objects");
|
||||
if (other == this)
|
||||
return;
|
||||
Curve448DHState dh = (Curve448DHState)other;
|
||||
System.arraycopy(dh.privateKey, 0, privateKey, 0, 56);
|
||||
System.arraycopy(dh.publicKey, 0, publicKey, 0, 56);
|
||||
mode = dh.mode;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Southern Storm Software, Pty Ltd.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included
|
||||
* in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.futo.platformplayer.noise.protocol;
|
||||
|
||||
/**
|
||||
* Interface to a Diffie-Hellman algorithm for the Noise protocol.
|
||||
*/
|
||||
public interface DHState extends Destroyable {
|
||||
|
||||
/**
|
||||
* Gets the Noise protocol name for this Diffie-Hellman algorithm.
|
||||
*
|
||||
* @return The algorithm name.
|
||||
*/
|
||||
String getDHName();
|
||||
|
||||
/**
|
||||
* Gets the length of public keys for this algorithm.
|
||||
*
|
||||
* @return The length of public keys in bytes.
|
||||
*/
|
||||
int getPublicKeyLength();
|
||||
|
||||
/**
|
||||
* Gets the length of private keys for this algorithm.
|
||||
*
|
||||
* @return The length of private keys in bytes.
|
||||
*/
|
||||
int getPrivateKeyLength();
|
||||
|
||||
/**
|
||||
* Gets the length of shared keys for this algorithm.
|
||||
*
|
||||
* @return The length of shared keys in bytes.
|
||||
*/
|
||||
int getSharedKeyLength();
|
||||
|
||||
/**
|
||||
* Generates a new random keypair.
|
||||
*/
|
||||
void generateKeyPair();
|
||||
|
||||
/**
|
||||
* Gets the public key associated with this object.
|
||||
*
|
||||
* @param key The buffer to copy the public key to.
|
||||
* @param offset The first offset in the key buffer to copy to.
|
||||
*/
|
||||
void getPublicKey(byte[] key, int offset);
|
||||
|
||||
/**
|
||||
* Sets the public key for this object.
|
||||
*
|
||||
* @param key The buffer containing the public key.
|
||||
* @param offset The first offset in the buffer that contains the key.
|
||||
*
|
||||
* If this object previously held a key pair, then this function
|
||||
* will change it into a public key only object.
|
||||
*/
|
||||
void setPublicKey(byte[] key, int offset);
|
||||
|
||||
/**
|
||||
* Gets the private key associated with this object.
|
||||
*
|
||||
* @param key The buffer to copy the private key to.
|
||||
* @param offset The first offset in the key buffer to copy to.
|
||||
*/
|
||||
void getPrivateKey(byte[] key, int offset);
|
||||
|
||||
/**
|
||||
* Sets the private key for this object.
|
||||
*
|
||||
* @param key The buffer containing the [rivate key.
|
||||
* @param offset The first offset in the buffer that contains the key.
|
||||
*
|
||||
* If this object previously held only a public key, then
|
||||
* this function will change it into a key pair.
|
||||
*/
|
||||
void setPrivateKey(byte[] key, int offset);
|
||||
|
||||
/**
|
||||
* Sets this object to the null public key and clears the private key.
|
||||
*/
|
||||
void setToNullPublicKey();
|
||||
|
||||
/**
|
||||
* Clears the key pair.
|
||||
*/
|
||||
void clearKey();
|
||||
|
||||
/**
|
||||
* Determine if this object contains a public key.
|
||||
*
|
||||
* @return Returns true if this object contains a public key,
|
||||
* or false if the public key has not yet been set.
|
||||
*/
|
||||
boolean hasPublicKey();
|
||||
|
||||
/**
|
||||
* Determine if this object contains a private key.
|
||||
*
|
||||
* @return Returns true if this object contains a private key,
|
||||
* or false if the private key has not yet been set.
|
||||
*/
|
||||
boolean hasPrivateKey();
|
||||
|
||||
/**
|
||||
* Determine if the public key in this object is the special null value.
|
||||
*
|
||||
* @return Returns true if the public key is the special null value,
|
||||
* or false otherwise.
|
||||
*/
|
||||
boolean isNullPublicKey();
|
||||
|
||||
/**
|
||||
* Performs a Diffie-Hellman calculation with this object as the private key.
|
||||
*
|
||||
* @param sharedKey Buffer to put the shared key into.
|
||||
* @param offset Offset of the first byte for the shared key.
|
||||
* @param publicDH Object that contains the public key for the calculation.
|
||||
*
|
||||
* @throws IllegalArgumentException The publicDH object is not the same
|
||||
* type as this object, or one of the objects does not contain a valid key.
|
||||
*/
|
||||
void calculate(byte[] sharedKey, int offset, DHState publicDH);
|
||||
|
||||
/**
|
||||
* Copies the key values from another DH object of the same type.
|
||||
*
|
||||
* @param other The other DH object to copy from
|
||||
*
|
||||
* @throws IllegalStateException The other DH object does not have
|
||||
* the same type as this object.
|
||||
*/
|
||||
void copyFrom(DHState other);
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Southern Storm Software, Pty Ltd.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included
|
||||
* in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.futo.platformplayer.noise.protocol;
|
||||
|
||||
/**
|
||||
* Additional API for DH objects that need special handling for
|
||||
* hybrid operations.
|
||||
*/
|
||||
public interface DHStateHybrid extends DHState {
|
||||
|
||||
/**
|
||||
* Generates a new random keypair relative to the parameters
|
||||
* in another object.
|
||||
*
|
||||
* @param remote The remote party in this communication to obtain parameters.
|
||||
*
|
||||
* @throws IllegalStateException The other or remote DH object does not have
|
||||
* the same type as this object.
|
||||
*/
|
||||
void generateKeyPair(DHState remote);
|
||||
|
||||
/**
|
||||
* Copies the key values from another DH object of the same type.
|
||||
*
|
||||
* @param other The other DH object to copy from
|
||||
* @param remote The remote party in this communication to obtain parameters.
|
||||
*
|
||||
* @throws IllegalStateException The other or remote DH object does not have
|
||||
* the same type as this object.
|
||||
*/
|
||||
void copyFrom(DHState other, DHState remote);
|
||||
|
||||
/**
|
||||
* Specifies the local peer object prior to setting a public key
|
||||
* on a remote object.
|
||||
*
|
||||
* @param local The local peer object.
|
||||
*/
|
||||
void specifyPeer(DHState local);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Southern Storm Software, Pty Ltd.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included
|
||||
* in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.futo.platformplayer.noise.protocol;
|
||||
|
||||
/**
|
||||
* Interface for objects that implement destroying.
|
||||
*
|
||||
* Applications that use the Noise protocol can inadvertently leave
|
||||
* sensitive data in the heap if steps are not taken to clean up.
|
||||
*
|
||||
* This interface can be implemented by objects that know how to
|
||||
* securely clean up after themselves.
|
||||
*
|
||||
* The Noise.destroy() function can help with destroying byte arrays
|
||||
* that hold sensitive values.
|
||||
*/
|
||||
public interface Destroyable {
|
||||
|
||||
/**
|
||||
* Destroys all sensitive state in the current object.
|
||||
*/
|
||||
void destroy();
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,340 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Southern Storm Software, Pty Ltd.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included
|
||||
* in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.futo.platformplayer.noise.protocol;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import com.futo.platformplayer.noise.crypto.NewHope;
|
||||
import com.futo.platformplayer.noise.crypto.NewHopeTor;
|
||||
|
||||
/**
|
||||
* Implementation of the New Hope post-quantum algorithm for the Noise protocol.
|
||||
*/
|
||||
final class NewHopeDHState implements DHStateHybrid {
|
||||
|
||||
enum KeyType
|
||||
{
|
||||
None,
|
||||
AlicePrivate,
|
||||
AlicePublic,
|
||||
BobPrivate,
|
||||
BobPublic,
|
||||
BobCalculated;
|
||||
}
|
||||
|
||||
private NewHopeTor nh;
|
||||
private byte[] publicKey;
|
||||
private byte[] privateKey;
|
||||
private KeyType keyType;
|
||||
|
||||
/**
|
||||
* Special version of NewHopeTor that allows explicit random data
|
||||
* to be specified for test vectors.
|
||||
*/
|
||||
private class NewHopeWithPrivateKey extends NewHopeTor {
|
||||
|
||||
byte[] randomData;
|
||||
|
||||
public NewHopeWithPrivateKey(byte[] randomData)
|
||||
{
|
||||
this.randomData = randomData;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void randombytes(byte[] buffer)
|
||||
{
|
||||
System.arraycopy(randomData, 0, buffer, 0, buffer.length);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new key exchange object for New Hope.
|
||||
*/
|
||||
public NewHopeDHState() {
|
||||
nh = null;
|
||||
publicKey = null;
|
||||
privateKey = null;
|
||||
keyType = KeyType.None;
|
||||
}
|
||||
|
||||
private boolean isAlice() {
|
||||
return keyType == KeyType.AlicePrivate || keyType == KeyType.AlicePublic;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
clearKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDHName() {
|
||||
return "NewHope";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPublicKeyLength() {
|
||||
if (isAlice())
|
||||
return NewHope.SENDABYTES;
|
||||
else
|
||||
return NewHope.SENDBBYTES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPrivateKeyLength() {
|
||||
// New Hope doesn't have private keys in the same sense as
|
||||
// Curve25519 and Curve448. Instead return the number of
|
||||
// random bytes that we need to generate each key type.
|
||||
if (isAlice())
|
||||
return 64;
|
||||
else
|
||||
return 32;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSharedKeyLength() {
|
||||
return NewHope.SHAREDBYTES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generateKeyPair() {
|
||||
clearKey();
|
||||
keyType = KeyType.AlicePrivate;
|
||||
nh = new NewHopeTor();
|
||||
publicKey = new byte [NewHope.SENDABYTES];
|
||||
nh.keygen(publicKey, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generateKeyPair(DHState remote) {
|
||||
if (remote == null) {
|
||||
// No remote public key, so always generate in Alice mode.
|
||||
generateKeyPair();
|
||||
return;
|
||||
} else if (!(remote instanceof NewHopeDHState)) {
|
||||
throw new IllegalStateException("Mismatched DH objects");
|
||||
}
|
||||
NewHopeDHState r = (NewHopeDHState)remote;
|
||||
if (r.isAlice() && r.publicKey != null) {
|
||||
// We have a remote public key for Alice, so generate in Bob mode.
|
||||
clearKey();
|
||||
keyType = KeyType.BobCalculated;
|
||||
nh = new NewHopeTor();
|
||||
publicKey = new byte [NewHope.SENDBBYTES];
|
||||
privateKey = new byte [NewHope.SHAREDBYTES];
|
||||
nh.sharedb(privateKey, 0, publicKey, 0, r.publicKey, 0);
|
||||
} else {
|
||||
generateKeyPair();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getPublicKey(byte[] key, int offset) {
|
||||
if (publicKey != null)
|
||||
System.arraycopy(publicKey, 0, key, offset, getPublicKeyLength());
|
||||
else
|
||||
Arrays.fill(key, 0, getPublicKeyLength(), (byte)0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPublicKey(byte[] key, int offset) {
|
||||
if (publicKey != null)
|
||||
Noise.destroy(publicKey);
|
||||
publicKey = new byte [getPublicKeyLength()];
|
||||
System.arraycopy(key, 0, publicKey, 0, publicKey.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getPrivateKey(byte[] key, int offset) {
|
||||
if (privateKey != null)
|
||||
System.arraycopy(privateKey, 0, key, offset, getPrivateKeyLength());
|
||||
else
|
||||
Arrays.fill(key, 0, getPrivateKeyLength(), (byte)0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPrivateKey(byte[] key, int offset) {
|
||||
clearKey();
|
||||
// Guess the key type from the length of the test data.
|
||||
if (offset == 0 && key.length == 64)
|
||||
keyType = KeyType.AlicePrivate;
|
||||
else
|
||||
keyType = KeyType.BobPrivate;
|
||||
privateKey = new byte [getPrivateKeyLength()];
|
||||
System.arraycopy(key, 0, privateKey, 0, privateKey.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setToNullPublicKey() {
|
||||
// Null public keys are not supported by New Hope.
|
||||
// Destroy the current values but otherwise ignore.
|
||||
clearKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearKey() {
|
||||
if (nh != null) {
|
||||
nh.destroy();
|
||||
nh = null;
|
||||
}
|
||||
if (publicKey != null) {
|
||||
Noise.destroy(publicKey);
|
||||
publicKey = null;
|
||||
}
|
||||
if (privateKey != null) {
|
||||
Noise.destroy(privateKey);
|
||||
privateKey = null;
|
||||
}
|
||||
keyType = KeyType.None;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPublicKey() {
|
||||
return publicKey != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPrivateKey() {
|
||||
return privateKey != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNullPublicKey() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void calculate(byte[] sharedKey, int offset, DHState publicDH) {
|
||||
if (!(publicDH instanceof NewHopeDHState))
|
||||
throw new IllegalArgumentException("Incompatible DH algorithms");
|
||||
NewHopeDHState other = (NewHopeDHState)publicDH;
|
||||
if (keyType == KeyType.AlicePrivate) {
|
||||
// Compute the shared key for Alice.
|
||||
nh.shareda(sharedKey, 0, other.publicKey, 0);
|
||||
} else if (keyType == KeyType.BobCalculated) {
|
||||
// The shared key for Bob was already computed when the key was generated.
|
||||
System.arraycopy(privateKey, 0, sharedKey, 0, NewHope.SHAREDBYTES);
|
||||
} else {
|
||||
throw new IllegalStateException("Cannot calculate with this DH object");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyFrom(DHState other) {
|
||||
if (!(other instanceof NewHopeDHState))
|
||||
throw new IllegalStateException("Mismatched DH key objects");
|
||||
if (other == this)
|
||||
return;
|
||||
NewHopeDHState dh = (NewHopeDHState)other;
|
||||
clearKey();
|
||||
switch (dh.keyType) {
|
||||
case None:
|
||||
break;
|
||||
|
||||
case AlicePrivate:
|
||||
if (dh.privateKey != null) {
|
||||
keyType = KeyType.AlicePrivate;
|
||||
privateKey = new byte [dh.privateKey.length];
|
||||
System.arraycopy(dh.privateKey, 0, privateKey, 0, privateKey.length);
|
||||
} else {
|
||||
throw new IllegalStateException("Cannot copy generated key for Alice");
|
||||
}
|
||||
break;
|
||||
|
||||
case BobPrivate:
|
||||
case BobCalculated:
|
||||
throw new IllegalStateException("Cannot copy private key for Bob without public key for Alice");
|
||||
|
||||
case AlicePublic:
|
||||
case BobPublic:
|
||||
keyType = dh.keyType;
|
||||
publicKey = new byte [dh.publicKey.length];
|
||||
System.arraycopy(dh.publicKey, 0, publicKey, 0, publicKey.length);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyFrom(DHState other, DHState remote) {
|
||||
if (remote == null) {
|
||||
copyFrom(other);
|
||||
return;
|
||||
}
|
||||
if (!(other instanceof NewHopeDHState) || !(remote instanceof NewHopeDHState))
|
||||
throw new IllegalStateException("Mismatched DH key objects");
|
||||
if (other == this)
|
||||
return;
|
||||
NewHopeDHState dh = (NewHopeDHState)other;
|
||||
NewHopeDHState remotedh = (NewHopeDHState)remote;
|
||||
clearKey();
|
||||
switch (dh.keyType) {
|
||||
case None:
|
||||
break;
|
||||
|
||||
case AlicePrivate:
|
||||
if (dh.privateKey != null) {
|
||||
// Generate Alice's public and private key now.
|
||||
keyType = KeyType.AlicePrivate;
|
||||
nh = new NewHopeWithPrivateKey(dh.privateKey);
|
||||
publicKey = new byte [NewHope.SENDABYTES];
|
||||
nh.keygen(publicKey, 0);
|
||||
} else {
|
||||
throw new IllegalStateException("Cannot copy generated key for Alice");
|
||||
}
|
||||
break;
|
||||
|
||||
case BobPrivate:
|
||||
if (dh.privateKey != null && remotedh.keyType == KeyType.AlicePublic) {
|
||||
// Now we know the public key for Alice, we can calculate Bob's public and shared keys.
|
||||
keyType = KeyType.BobCalculated;
|
||||
nh = new NewHopeWithPrivateKey(dh.privateKey);
|
||||
publicKey = new byte [NewHope.SENDBBYTES];
|
||||
privateKey = new byte [NewHope.SHAREDBYTES];
|
||||
nh.sharedb(privateKey, 0, publicKey, 0, remotedh.publicKey, 0);
|
||||
} else {
|
||||
throw new IllegalStateException("Cannot copy private key for Bob without public key for Alice");
|
||||
}
|
||||
break;
|
||||
|
||||
case BobCalculated:
|
||||
throw new IllegalStateException("Cannot copy generated key for Bob");
|
||||
|
||||
case AlicePublic:
|
||||
case BobPublic:
|
||||
keyType = dh.keyType;
|
||||
publicKey = new byte [dh.publicKey.length];
|
||||
System.arraycopy(dh.publicKey, 0, publicKey, 0, publicKey.length);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void specifyPeer(DHState local) {
|
||||
if (!(local instanceof NewHopeDHState))
|
||||
return;
|
||||
clearKey();
|
||||
if (((NewHopeDHState)local).keyType == KeyType.AlicePrivate)
|
||||
keyType = KeyType.BobPublic;
|
||||
else
|
||||
keyType = KeyType.AlicePublic;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Southern Storm Software, Pty Ltd.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included
|
||||
* in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.futo.platformplayer.noise.protocol;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
|
||||
import com.futo.platformplayer.noise.crypto.Blake2bMessageDigest;
|
||||
import com.futo.platformplayer.noise.crypto.Blake2sMessageDigest;
|
||||
import com.futo.platformplayer.noise.crypto.SHA256MessageDigest;
|
||||
import com.futo.platformplayer.noise.crypto.SHA512MessageDigest;
|
||||
|
||||
/**
|
||||
* Utility functions for the Noise protocol library.
|
||||
*/
|
||||
public final class Noise {
|
||||
|
||||
/**
|
||||
* Maximum length for Noise packets.
|
||||
*/
|
||||
public static final int MAX_PACKET_LEN = 65535;
|
||||
|
||||
private static SecureRandom random = new SecureRandom();
|
||||
|
||||
/**
|
||||
* Generates random data using the system random number generator.
|
||||
*
|
||||
* @param data The data buffer to fill with random data.
|
||||
*/
|
||||
public static void random(byte[] data)
|
||||
{
|
||||
random.nextBytes(data);
|
||||
}
|
||||
|
||||
private static boolean forceFallbacks = false;
|
||||
|
||||
/**
|
||||
* Force the use of plain Java fallback crypto implementations.
|
||||
*
|
||||
* @param force Set to true for force fallbacks, false to
|
||||
* try to use the system implementation before falling back.
|
||||
*
|
||||
* This function is intended for testing purposes to toggle between
|
||||
* the system JCA/JCE implementations and the plain Java fallback
|
||||
* reference implementations.
|
||||
*/
|
||||
public static void setForceFallbacks(boolean force)
|
||||
{
|
||||
forceFallbacks = force;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Diffie-Hellman object from its Noise protocol name.
|
||||
*
|
||||
* @param name The name of the DH algorithm; e.g. "25519", "448", etc.
|
||||
*
|
||||
* @return The Diffie-Hellman object if the name is recognized.
|
||||
*
|
||||
* @throws NoSuchAlgorithmException The name is not recognized as a
|
||||
* valid Noise protocol name, or there is no cryptography provider
|
||||
* in the system that implements the algorithm.
|
||||
*/
|
||||
public static DHState createDH(String name) throws NoSuchAlgorithmException
|
||||
{
|
||||
if (name.equals("25519"))
|
||||
return new Curve25519DHState();
|
||||
if (name.equals("448"))
|
||||
return new Curve448DHState();
|
||||
if (name.equals("NewHope"))
|
||||
return new NewHopeDHState();
|
||||
throw new NoSuchAlgorithmException("Unknown Noise DH algorithm name: " + name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a cipher object from its Noise protocol name.
|
||||
*
|
||||
* @param name The name of the cipher algorithm; e.g. "AESGCM", "ChaChaPoly", etc.
|
||||
*
|
||||
* @return The cipher object if the name is recognized.
|
||||
*
|
||||
* @throws NoSuchAlgorithmException The name is not recognized as a
|
||||
* valid Noise protocol name, or there is no cryptography provider
|
||||
* in the system that implements the algorithm.
|
||||
*/
|
||||
public static CipherState createCipher(String name) throws NoSuchAlgorithmException
|
||||
{
|
||||
if (name.equals("AESGCM")) {
|
||||
if (forceFallbacks)
|
||||
return new AESGCMFallbackCipherState();
|
||||
// "AES/GCM/NoPadding" exists in some recent JDK's but it is flaky
|
||||
// to use and not easily back-portable to older Android versions.
|
||||
// We instead emulate AESGCM on top of "AES/CTR/NoPadding".
|
||||
try {
|
||||
return new AESGCMOnCtrCipherState();
|
||||
} catch (NoSuchAlgorithmException e1) {
|
||||
// Could not find anything useful in the JCA/JCE so
|
||||
// use the pure Java fallback implementation instead.
|
||||
return new AESGCMFallbackCipherState();
|
||||
}
|
||||
} else if (name.equals("ChaChaPoly")) {
|
||||
return new ChaChaPolyCipherState();
|
||||
}
|
||||
throw new NoSuchAlgorithmException("Unknown Noise cipher algorithm name: " + name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a hash object from its Noise protocol name.
|
||||
*
|
||||
* @param name The name of the hash algorithm; e.g. "SHA256", "BLAKE2s", etc.
|
||||
*
|
||||
* @return The hash object if the name is recognized.
|
||||
*
|
||||
* @throws NoSuchAlgorithmException The name is not recognized as a
|
||||
* valid Noise protocol name, or there is no cryptography provider
|
||||
* in the system that implements the algorithm.
|
||||
*/
|
||||
public static MessageDigest createHash(String name) throws NoSuchAlgorithmException
|
||||
{
|
||||
// Look for a JCA/JCE provider first and if that doesn't work,
|
||||
// use the fallback implementations in this library instead.
|
||||
// The only algorithm that is required to be implemented by a
|
||||
// JDK is "SHA-256", although "SHA-512" is fairly common as well.
|
||||
if (name.equals("SHA256")) {
|
||||
if (forceFallbacks)
|
||||
return new SHA256MessageDigest();
|
||||
try {
|
||||
return MessageDigest.getInstance("SHA-256");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
return new SHA256MessageDigest();
|
||||
}
|
||||
} else if (name.equals("SHA512")) {
|
||||
if (forceFallbacks)
|
||||
return new SHA512MessageDigest();
|
||||
try {
|
||||
return MessageDigest.getInstance("SHA-512");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
return new SHA512MessageDigest();
|
||||
}
|
||||
} else if (name.equals("BLAKE2b")) {
|
||||
// Bouncy Castle registers the BLAKE2b variant we
|
||||
// want under the name "BLAKE2B-512".
|
||||
if (forceFallbacks)
|
||||
return new Blake2bMessageDigest();
|
||||
try {
|
||||
return MessageDigest.getInstance("BLAKE2B-512");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
return new Blake2bMessageDigest();
|
||||
}
|
||||
} else if (name.equals("BLAKE2s")) {
|
||||
// Bouncy Castle doesn't currently (June 2016) have an
|
||||
// implementation of BLAKE2s, but look for the most
|
||||
// obvious provider name in case one is added in the future.
|
||||
if (forceFallbacks)
|
||||
return new Blake2sMessageDigest();
|
||||
try {
|
||||
return MessageDigest.getInstance("BLAKE2S-256");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
return new Blake2sMessageDigest();
|
||||
}
|
||||
}
|
||||
throw new NoSuchAlgorithmException("Unknown Noise hash algorithm name: " + name);
|
||||
}
|
||||
|
||||
// The rest of this class consists of internal utility functions
|
||||
// that are not part of the public API.
|
||||
|
||||
/**
|
||||
* Destroys the contents of a byte array.
|
||||
*
|
||||
* @param array The array whose contents should be destroyed.
|
||||
*/
|
||||
static void destroy(byte[] array)
|
||||
{
|
||||
Arrays.fill(array, (byte)0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a copy of part of an array.
|
||||
*
|
||||
* @param data The buffer containing the data to copy.
|
||||
* @param offset Offset of the first byte to copy.
|
||||
* @param length The number of bytes to copy.
|
||||
*
|
||||
* @return A new array with a copy of the sub-array.
|
||||
*/
|
||||
static byte[] copySubArray(byte[] data, int offset, int length)
|
||||
{
|
||||
byte[] copy = new byte [length];
|
||||
System.arraycopy(data, offset, copy, 0, length);
|
||||
return copy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws an instance of AEADBadTagException.
|
||||
*
|
||||
* @throws BadPaddingException The AEAD exception.
|
||||
*
|
||||
* If the underlying JDK does not have the AEADBadTagException
|
||||
* class, then this function will instead throw an instance of
|
||||
* the superclass BadPaddingException.
|
||||
*/
|
||||
static void throwBadTagException() throws BadPaddingException
|
||||
{
|
||||
try {
|
||||
Class<?> c = Class.forName("javax.crypto.AEADBadTagException");
|
||||
throw (BadPaddingException)(c.newInstance());
|
||||
} catch (ClassNotFoundException e) {
|
||||
} catch (InstantiationException e) {
|
||||
} catch (IllegalAccessException e) {
|
||||
}
|
||||
throw new BadPaddingException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,835 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Southern Storm Software, Pty Ltd.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included
|
||||
* in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.futo.platformplayer.noise.protocol;
|
||||
|
||||
/**
|
||||
* Information about all supported handshake patterns.
|
||||
*/
|
||||
class Pattern {
|
||||
|
||||
private Pattern() {}
|
||||
|
||||
// Token codes.
|
||||
public static final short S = 1;
|
||||
public static final short E = 2;
|
||||
public static final short EE = 3;
|
||||
public static final short ES = 4;
|
||||
public static final short SE = 5;
|
||||
public static final short SS = 6;
|
||||
public static final short F = 7;
|
||||
public static final short FF = 8;
|
||||
public static final short FLIP_DIR = 255;
|
||||
|
||||
// Pattern flag bits.
|
||||
public static final short FLAG_LOCAL_STATIC = 0x0001;
|
||||
public static final short FLAG_LOCAL_EPHEMERAL = 0x0002;
|
||||
public static final short FLAG_LOCAL_REQUIRED = 0x0004;
|
||||
public static final short FLAG_LOCAL_EPHEM_REQ = 0x0008;
|
||||
public static final short FLAG_LOCAL_HYBRID = 0x0010;
|
||||
public static final short FLAG_LOCAL_HYBRID_REQ = 0x0020;
|
||||
public static final short FLAG_REMOTE_STATIC = 0x0100;
|
||||
public static final short FLAG_REMOTE_EPHEMERAL = 0x0200;
|
||||
public static final short FLAG_REMOTE_REQUIRED = 0x0400;
|
||||
public static final short FLAG_REMOTE_EPHEM_REQ = 0x0800;
|
||||
public static final short FLAG_REMOTE_HYBRID = 0x1000;
|
||||
public static final short FLAG_REMOTE_HYBRID_REQ = 0x2000;
|
||||
|
||||
private static final short[] noise_pattern_N = {
|
||||
FLAG_LOCAL_EPHEMERAL |
|
||||
FLAG_REMOTE_STATIC |
|
||||
FLAG_REMOTE_REQUIRED,
|
||||
|
||||
E,
|
||||
ES
|
||||
};
|
||||
|
||||
private static final short[] noise_pattern_K = {
|
||||
FLAG_LOCAL_STATIC |
|
||||
FLAG_LOCAL_EPHEMERAL |
|
||||
FLAG_LOCAL_REQUIRED |
|
||||
FLAG_REMOTE_STATIC |
|
||||
FLAG_REMOTE_REQUIRED,
|
||||
|
||||
E,
|
||||
ES,
|
||||
SS
|
||||
};
|
||||
|
||||
private static final short[] noise_pattern_X = {
|
||||
FLAG_LOCAL_STATIC |
|
||||
FLAG_LOCAL_EPHEMERAL |
|
||||
FLAG_REMOTE_STATIC |
|
||||
FLAG_REMOTE_REQUIRED,
|
||||
|
||||
E,
|
||||
ES,
|
||||
S,
|
||||
SS
|
||||
};
|
||||
|
||||
private static final short[] noise_pattern_NN = {
|
||||
FLAG_LOCAL_EPHEMERAL |
|
||||
FLAG_REMOTE_EPHEMERAL,
|
||||
|
||||
E,
|
||||
FLIP_DIR,
|
||||
E,
|
||||
EE
|
||||
};
|
||||
|
||||
private static final short[] noise_pattern_NK = {
|
||||
FLAG_LOCAL_EPHEMERAL |
|
||||
FLAG_REMOTE_STATIC |
|
||||
FLAG_REMOTE_EPHEMERAL |
|
||||
FLAG_REMOTE_REQUIRED,
|
||||
|
||||
E,
|
||||
ES,
|
||||
FLIP_DIR,
|
||||
E,
|
||||
EE
|
||||
};
|
||||
|
||||
private static final short[] noise_pattern_NX = {
|
||||
FLAG_LOCAL_EPHEMERAL |
|
||||
FLAG_REMOTE_STATIC |
|
||||
FLAG_REMOTE_EPHEMERAL,
|
||||
|
||||
E,
|
||||
FLIP_DIR,
|
||||
E,
|
||||
EE,
|
||||
S,
|
||||
ES
|
||||
};
|
||||
|
||||
private static final short[] noise_pattern_XN = {
|
||||
FLAG_LOCAL_STATIC |
|
||||
FLAG_LOCAL_EPHEMERAL |
|
||||
FLAG_REMOTE_EPHEMERAL,
|
||||
|
||||
E,
|
||||
FLIP_DIR,
|
||||
E,
|
||||
EE,
|
||||
FLIP_DIR,
|
||||
S,
|
||||
SE
|
||||
};
|
||||
|
||||
private static final short[] noise_pattern_XK = {
|
||||
FLAG_LOCAL_STATIC |
|
||||
FLAG_LOCAL_EPHEMERAL |
|
||||
FLAG_REMOTE_STATIC |
|
||||
FLAG_REMOTE_EPHEMERAL |
|
||||
FLAG_REMOTE_REQUIRED,
|
||||
|
||||
E,
|
||||
ES,
|
||||
FLIP_DIR,
|
||||
E,
|
||||
EE,
|
||||
FLIP_DIR,
|
||||
S,
|
||||
SE
|
||||
};
|
||||
|
||||
private static final short[] noise_pattern_XX = {
|
||||
FLAG_LOCAL_STATIC |
|
||||
FLAG_LOCAL_EPHEMERAL |
|
||||
FLAG_REMOTE_STATIC |
|
||||
FLAG_REMOTE_EPHEMERAL,
|
||||
|
||||
E,
|
||||
FLIP_DIR,
|
||||
E,
|
||||
EE,
|
||||
S,
|
||||
ES,
|
||||
FLIP_DIR,
|
||||
S,
|
||||
SE
|
||||
};
|
||||
|
||||
private static final short[] noise_pattern_KN = {
|
||||
FLAG_LOCAL_STATIC |
|
||||
FLAG_LOCAL_EPHEMERAL |
|
||||
FLAG_LOCAL_REQUIRED |
|
||||
FLAG_REMOTE_EPHEMERAL,
|
||||
|
||||
E,
|
||||
FLIP_DIR,
|
||||
E,
|
||||
EE,
|
||||
SE
|
||||
};
|
||||
|
||||
private static final short[] noise_pattern_KK = {
|
||||
FLAG_LOCAL_STATIC |
|
||||
FLAG_LOCAL_EPHEMERAL |
|
||||
FLAG_LOCAL_REQUIRED |
|
||||
FLAG_REMOTE_STATIC |
|
||||
FLAG_REMOTE_EPHEMERAL |
|
||||
FLAG_REMOTE_REQUIRED,
|
||||
|
||||
E,
|
||||
ES,
|
||||
SS,
|
||||
FLIP_DIR,
|
||||
E,
|
||||
EE,
|
||||
SE
|
||||
};
|
||||
|
||||
private static final short[] noise_pattern_KX = {
|
||||
FLAG_LOCAL_STATIC |
|
||||
FLAG_LOCAL_EPHEMERAL |
|
||||
FLAG_LOCAL_REQUIRED |
|
||||
FLAG_REMOTE_STATIC |
|
||||
FLAG_REMOTE_EPHEMERAL,
|
||||
|
||||
E,
|
||||
FLIP_DIR,
|
||||
E,
|
||||
EE,
|
||||
SE,
|
||||
S,
|
||||
ES
|
||||
};
|
||||
|
||||
private static final short[] noise_pattern_IN = {
|
||||
FLAG_LOCAL_STATIC |
|
||||
FLAG_LOCAL_EPHEMERAL |
|
||||
FLAG_REMOTE_EPHEMERAL,
|
||||
|
||||
E,
|
||||
S,
|
||||
FLIP_DIR,
|
||||
E,
|
||||
EE,
|
||||
SE
|
||||
};
|
||||
|
||||
private static final short[] noise_pattern_IK = {
|
||||
FLAG_LOCAL_STATIC |
|
||||
FLAG_LOCAL_EPHEMERAL |
|
||||
FLAG_REMOTE_STATIC |
|
||||
FLAG_REMOTE_EPHEMERAL |
|
||||
FLAG_REMOTE_REQUIRED,
|
||||
|
||||
E,
|
||||
ES,
|
||||
S,
|
||||
SS,
|
||||
FLIP_DIR,
|
||||
E,
|
||||
EE,
|
||||
SE
|
||||
};
|
||||
|
||||
private static final short[] noise_pattern_IX = {
|
||||
FLAG_LOCAL_STATIC |
|
||||
FLAG_LOCAL_EPHEMERAL |
|
||||
FLAG_REMOTE_STATIC |
|
||||
FLAG_REMOTE_EPHEMERAL,
|
||||
|
||||
E,
|
||||
S,
|
||||
FLIP_DIR,
|
||||
E,
|
||||
EE,
|
||||
SE,
|
||||
S,
|
||||
ES
|
||||
};
|
||||
|
||||
private static final short[] noise_pattern_XXfallback = {
|
||||
FLAG_LOCAL_STATIC |
|
||||
FLAG_LOCAL_EPHEMERAL |
|
||||
FLAG_REMOTE_STATIC |
|
||||
FLAG_REMOTE_EPHEMERAL |
|
||||
FLAG_REMOTE_EPHEM_REQ,
|
||||
|
||||
E,
|
||||
EE,
|
||||
S,
|
||||
SE,
|
||||
FLIP_DIR,
|
||||
S,
|
||||
ES
|
||||
};
|
||||
|
||||
private static final short[] noise_pattern_Xnoidh = {
|
||||
FLAG_LOCAL_STATIC |
|
||||
FLAG_LOCAL_EPHEMERAL |
|
||||
FLAG_REMOTE_STATIC |
|
||||
FLAG_REMOTE_REQUIRED,
|
||||
|
||||
E,
|
||||
S,
|
||||
ES,
|
||||
SS
|
||||
};
|
||||
|
||||
private static final short[] noise_pattern_NXnoidh = {
|
||||
FLAG_LOCAL_EPHEMERAL |
|
||||
FLAG_REMOTE_STATIC |
|
||||
FLAG_REMOTE_EPHEMERAL,
|
||||
|
||||
E,
|
||||
FLIP_DIR,
|
||||
E,
|
||||
S,
|
||||
EE,
|
||||
ES
|
||||
};
|
||||
|
||||
private static final short[] noise_pattern_XXnoidh = {
|
||||
FLAG_LOCAL_STATIC |
|
||||
FLAG_LOCAL_EPHEMERAL |
|
||||
FLAG_REMOTE_STATIC |
|
||||
FLAG_REMOTE_EPHEMERAL,
|
||||
|
||||
E,
|
||||
FLIP_DIR,
|
||||
E,
|
||||
S,
|
||||
EE,
|
||||
ES,
|
||||
FLIP_DIR,
|
||||
S,
|
||||
SE
|
||||
};
|
||||
|
||||
private static final short[] noise_pattern_KXnoidh = {
|
||||
FLAG_LOCAL_STATIC |
|
||||
FLAG_LOCAL_EPHEMERAL |
|
||||
FLAG_LOCAL_REQUIRED |
|
||||
FLAG_REMOTE_STATIC |
|
||||
FLAG_REMOTE_EPHEMERAL,
|
||||
|
||||
E,
|
||||
FLIP_DIR,
|
||||
E,
|
||||
S,
|
||||
EE,
|
||||
SE,
|
||||
ES
|
||||
};
|
||||
|
||||
private static final short[] noise_pattern_IKnoidh = {
|
||||
FLAG_LOCAL_STATIC |
|
||||
FLAG_LOCAL_EPHEMERAL |
|
||||
FLAG_REMOTE_STATIC |
|
||||
FLAG_REMOTE_EPHEMERAL |
|
||||
FLAG_REMOTE_REQUIRED,
|
||||
|
||||
E,
|
||||
S,
|
||||
ES,
|
||||
SS,
|
||||
FLIP_DIR,
|
||||
E,
|
||||
EE,
|
||||
SE
|
||||
};
|
||||
|
||||
private static final short[] noise_pattern_IXnoidh = {
|
||||
FLAG_LOCAL_STATIC |
|
||||
FLAG_LOCAL_EPHEMERAL |
|
||||
FLAG_REMOTE_STATIC |
|
||||
FLAG_REMOTE_EPHEMERAL,
|
||||
|
||||
E,
|
||||
S,
|
||||
FLIP_DIR,
|
||||
E,
|
||||
S,
|
||||
EE,
|
||||
SE,
|
||||
ES
|
||||
};
|
||||
|
||||
private static final short[] noise_pattern_NNhfs = {
|
||||
FLAG_LOCAL_EPHEMERAL |
|
||||
FLAG_LOCAL_HYBRID |
|
||||
FLAG_REMOTE_EPHEMERAL |
|
||||
FLAG_REMOTE_HYBRID,
|
||||
|
||||
E,
|
||||
F,
|
||||
FLIP_DIR,
|
||||
E,
|
||||
F,
|
||||
EE,
|
||||
FF
|
||||
};
|
||||
|
||||
private static final short[] noise_pattern_NKhfs = {
|
||||
FLAG_LOCAL_EPHEMERAL |
|
||||
FLAG_LOCAL_HYBRID |
|
||||
FLAG_REMOTE_STATIC |
|
||||
FLAG_REMOTE_EPHEMERAL |
|
||||
FLAG_REMOTE_HYBRID |
|
||||
FLAG_REMOTE_REQUIRED,
|
||||
|
||||
E,
|
||||
F,
|
||||
ES,
|
||||
FLIP_DIR,
|
||||
E,
|
||||
F,
|
||||
EE,
|
||||
FF
|
||||
};
|
||||
|
||||
private static final short[] noise_pattern_NXhfs = {
|
||||
FLAG_LOCAL_EPHEMERAL |
|
||||
FLAG_LOCAL_HYBRID |
|
||||
FLAG_REMOTE_STATIC |
|
||||
FLAG_REMOTE_EPHEMERAL |
|
||||
FLAG_REMOTE_HYBRID,
|
||||
|
||||
E,
|
||||
F,
|
||||
FLIP_DIR,
|
||||
E,
|
||||
F,
|
||||
EE,
|
||||
FF,
|
||||
S,
|
||||
ES
|
||||
};
|
||||
|
||||
private static final short[] noise_pattern_XNhfs = {
|
||||
FLAG_LOCAL_STATIC |
|
||||
FLAG_LOCAL_EPHEMERAL |
|
||||
FLAG_LOCAL_HYBRID |
|
||||
FLAG_REMOTE_EPHEMERAL |
|
||||
FLAG_REMOTE_HYBRID,
|
||||
|
||||
E,
|
||||
F,
|
||||
FLIP_DIR,
|
||||
E,
|
||||
F,
|
||||
EE,
|
||||
FF,
|
||||
FLIP_DIR,
|
||||
S,
|
||||
SE
|
||||
};
|
||||
|
||||
private static final short[] noise_pattern_XKhfs = {
|
||||
FLAG_LOCAL_STATIC |
|
||||
FLAG_LOCAL_EPHEMERAL |
|
||||
FLAG_LOCAL_HYBRID |
|
||||
FLAG_REMOTE_STATIC |
|
||||
FLAG_REMOTE_EPHEMERAL |
|
||||
FLAG_REMOTE_HYBRID |
|
||||
FLAG_REMOTE_REQUIRED,
|
||||
|
||||
E,
|
||||
F,
|
||||
ES,
|
||||
FLIP_DIR,
|
||||
E,
|
||||
F,
|
||||
EE,
|
||||
FF,
|
||||
FLIP_DIR,
|
||||
S,
|
||||
SE
|
||||
};
|
||||
|
||||
private static final short[] noise_pattern_XXhfs = {
|
||||
FLAG_LOCAL_STATIC |
|
||||
FLAG_LOCAL_EPHEMERAL |
|
||||
FLAG_LOCAL_HYBRID |
|
||||
FLAG_REMOTE_STATIC |
|
||||
FLAG_REMOTE_EPHEMERAL |
|
||||
FLAG_REMOTE_HYBRID,
|
||||
|
||||
E,
|
||||
F,
|
||||
FLIP_DIR,
|
||||
E,
|
||||
F,
|
||||
EE,
|
||||
FF,
|
||||
S,
|
||||
ES,
|
||||
FLIP_DIR,
|
||||
S,
|
||||
SE
|
||||
};
|
||||
|
||||
private static final short[] noise_pattern_KNhfs = {
|
||||
FLAG_LOCAL_STATIC |
|
||||
FLAG_LOCAL_EPHEMERAL |
|
||||
FLAG_LOCAL_REQUIRED |
|
||||
FLAG_LOCAL_HYBRID |
|
||||
FLAG_REMOTE_EPHEMERAL |
|
||||
FLAG_REMOTE_HYBRID,
|
||||
|
||||
E,
|
||||
F,
|
||||
FLIP_DIR,
|
||||
E,
|
||||
F,
|
||||
EE,
|
||||
FF,
|
||||
SE
|
||||
};
|
||||
|
||||
private static final short[] noise_pattern_KKhfs = {
|
||||
FLAG_LOCAL_STATIC |
|
||||
FLAG_LOCAL_EPHEMERAL |
|
||||
FLAG_LOCAL_REQUIRED |
|
||||
FLAG_LOCAL_HYBRID |
|
||||
FLAG_REMOTE_STATIC |
|
||||
FLAG_REMOTE_EPHEMERAL |
|
||||
FLAG_REMOTE_HYBRID |
|
||||
FLAG_REMOTE_REQUIRED,
|
||||
|
||||
E,
|
||||
F,
|
||||
ES,
|
||||
SS,
|
||||
FLIP_DIR,
|
||||
E,
|
||||
F,
|
||||
EE,
|
||||
FF,
|
||||
SE
|
||||
};
|
||||
|
||||
private static final short[] noise_pattern_KXhfs = {
|
||||
FLAG_LOCAL_STATIC |
|
||||
FLAG_LOCAL_EPHEMERAL |
|
||||
FLAG_LOCAL_REQUIRED |
|
||||
FLAG_LOCAL_HYBRID |
|
||||
FLAG_REMOTE_STATIC |
|
||||
FLAG_REMOTE_EPHEMERAL |
|
||||
FLAG_REMOTE_HYBRID,
|
||||
|
||||
E,
|
||||
F,
|
||||
FLIP_DIR,
|
||||
E,
|
||||
F,
|
||||
EE,
|
||||
FF,
|
||||
SE,
|
||||
S,
|
||||
ES
|
||||
};
|
||||
|
||||
private static final short[] noise_pattern_INhfs = {
|
||||
FLAG_LOCAL_STATIC |
|
||||
FLAG_LOCAL_EPHEMERAL |
|
||||
FLAG_LOCAL_HYBRID |
|
||||
FLAG_REMOTE_EPHEMERAL |
|
||||
FLAG_REMOTE_HYBRID,
|
||||
|
||||
E,
|
||||
F,
|
||||
S,
|
||||
FLIP_DIR,
|
||||
E,
|
||||
F,
|
||||
EE,
|
||||
FF,
|
||||
SE
|
||||
};
|
||||
|
||||
private static final short[] noise_pattern_IKhfs = {
|
||||
FLAG_LOCAL_STATIC |
|
||||
FLAG_LOCAL_EPHEMERAL |
|
||||
FLAG_LOCAL_HYBRID |
|
||||
FLAG_REMOTE_STATIC |
|
||||
FLAG_REMOTE_EPHEMERAL |
|
||||
FLAG_REMOTE_HYBRID |
|
||||
FLAG_REMOTE_REQUIRED,
|
||||
|
||||
E,
|
||||
F,
|
||||
ES,
|
||||
S,
|
||||
SS,
|
||||
FLIP_DIR,
|
||||
E,
|
||||
F,
|
||||
EE,
|
||||
FF,
|
||||
SE
|
||||
};
|
||||
|
||||
private static final short[] noise_pattern_IXhfs = {
|
||||
FLAG_LOCAL_STATIC |
|
||||
FLAG_LOCAL_EPHEMERAL |
|
||||
FLAG_LOCAL_HYBRID |
|
||||
FLAG_REMOTE_STATIC |
|
||||
FLAG_REMOTE_EPHEMERAL |
|
||||
FLAG_REMOTE_HYBRID,
|
||||
|
||||
E,
|
||||
F,
|
||||
S,
|
||||
FLIP_DIR,
|
||||
E,
|
||||
F,
|
||||
EE,
|
||||
FF,
|
||||
SE,
|
||||
S,
|
||||
ES
|
||||
};
|
||||
|
||||
private static final short[] noise_pattern_XXfallback_hfs = {
|
||||
FLAG_LOCAL_STATIC |
|
||||
FLAG_LOCAL_EPHEMERAL |
|
||||
FLAG_LOCAL_HYBRID |
|
||||
FLAG_REMOTE_STATIC |
|
||||
FLAG_REMOTE_EPHEMERAL |
|
||||
FLAG_REMOTE_EPHEM_REQ |
|
||||
FLAG_REMOTE_HYBRID |
|
||||
FLAG_REMOTE_HYBRID_REQ,
|
||||
|
||||
E,
|
||||
F,
|
||||
EE,
|
||||
FF,
|
||||
S,
|
||||
SE,
|
||||
FLIP_DIR,
|
||||
S,
|
||||
ES
|
||||
};
|
||||
|
||||
private static final short[] noise_pattern_NXnoidh_hfs = {
|
||||
FLAG_LOCAL_EPHEMERAL |
|
||||
FLAG_LOCAL_HYBRID |
|
||||
FLAG_REMOTE_STATIC |
|
||||
FLAG_REMOTE_EPHEMERAL |
|
||||
FLAG_REMOTE_HYBRID,
|
||||
|
||||
E,
|
||||
F,
|
||||
FLIP_DIR,
|
||||
E,
|
||||
F,
|
||||
S,
|
||||
EE,
|
||||
FF,
|
||||
ES
|
||||
};
|
||||
|
||||
private static final short[] noise_pattern_XXnoidh_hfs = {
|
||||
FLAG_LOCAL_STATIC |
|
||||
FLAG_LOCAL_EPHEMERAL |
|
||||
FLAG_LOCAL_HYBRID |
|
||||
FLAG_REMOTE_STATIC |
|
||||
FLAG_REMOTE_EPHEMERAL |
|
||||
FLAG_REMOTE_HYBRID,
|
||||
|
||||
E,
|
||||
F,
|
||||
FLIP_DIR,
|
||||
E,
|
||||
F,
|
||||
S,
|
||||
EE,
|
||||
FF,
|
||||
ES,
|
||||
FLIP_DIR,
|
||||
S,
|
||||
SE
|
||||
};
|
||||
|
||||
private static final short[] noise_pattern_KXnoidh_hfs = {
|
||||
FLAG_LOCAL_STATIC |
|
||||
FLAG_LOCAL_EPHEMERAL |
|
||||
FLAG_LOCAL_REQUIRED |
|
||||
FLAG_LOCAL_HYBRID |
|
||||
FLAG_REMOTE_STATIC |
|
||||
FLAG_REMOTE_EPHEMERAL |
|
||||
FLAG_REMOTE_HYBRID,
|
||||
|
||||
E,
|
||||
F,
|
||||
FLIP_DIR,
|
||||
E,
|
||||
F,
|
||||
S,
|
||||
EE,
|
||||
FF,
|
||||
SE,
|
||||
ES
|
||||
};
|
||||
|
||||
private static final short[] noise_pattern_IKnoidh_hfs = {
|
||||
FLAG_LOCAL_STATIC |
|
||||
FLAG_LOCAL_EPHEMERAL |
|
||||
FLAG_LOCAL_HYBRID |
|
||||
FLAG_REMOTE_STATIC |
|
||||
FLAG_REMOTE_EPHEMERAL |
|
||||
FLAG_REMOTE_EPHEMERAL |
|
||||
FLAG_REMOTE_HYBRID,
|
||||
|
||||
E,
|
||||
F,
|
||||
S,
|
||||
ES,
|
||||
SS,
|
||||
FLIP_DIR,
|
||||
E,
|
||||
F,
|
||||
EE,
|
||||
FF,
|
||||
SE
|
||||
};
|
||||
|
||||
private static final short[] noise_pattern_IXnoidh_hfs = {
|
||||
FLAG_LOCAL_STATIC |
|
||||
FLAG_LOCAL_EPHEMERAL |
|
||||
FLAG_LOCAL_HYBRID |
|
||||
FLAG_REMOTE_STATIC |
|
||||
FLAG_REMOTE_EPHEMERAL |
|
||||
FLAG_REMOTE_HYBRID,
|
||||
|
||||
E,
|
||||
F,
|
||||
S,
|
||||
FLIP_DIR,
|
||||
E,
|
||||
F,
|
||||
S,
|
||||
EE,
|
||||
FF,
|
||||
SE,
|
||||
ES
|
||||
};
|
||||
|
||||
/**
|
||||
* Look up the description information for a pattern.
|
||||
*
|
||||
* @param name The name of the pattern.
|
||||
* @return The pattern description or null.
|
||||
*/
|
||||
public static short[] lookup(String name)
|
||||
{
|
||||
if (name.equals("N"))
|
||||
return noise_pattern_N;
|
||||
else if (name.equals("K"))
|
||||
return noise_pattern_K;
|
||||
else if (name.equals("X"))
|
||||
return noise_pattern_X;
|
||||
else if (name.equals("NN"))
|
||||
return noise_pattern_NN;
|
||||
else if (name.equals("NK"))
|
||||
return noise_pattern_NK;
|
||||
else if (name.equals("NX"))
|
||||
return noise_pattern_NX;
|
||||
else if (name.equals("XN"))
|
||||
return noise_pattern_XN;
|
||||
else if (name.equals("XK"))
|
||||
return noise_pattern_XK;
|
||||
else if (name.equals("XX"))
|
||||
return noise_pattern_XX;
|
||||
else if (name.equals("KN"))
|
||||
return noise_pattern_KN;
|
||||
else if (name.equals("KK"))
|
||||
return noise_pattern_KK;
|
||||
else if (name.equals("KX"))
|
||||
return noise_pattern_KX;
|
||||
else if (name.equals("IN"))
|
||||
return noise_pattern_IN;
|
||||
else if (name.equals("IK"))
|
||||
return noise_pattern_IK;
|
||||
else if (name.equals("IX"))
|
||||
return noise_pattern_IX;
|
||||
else if (name.equals("XXfallback"))
|
||||
return noise_pattern_XXfallback;
|
||||
else if (name.equals("Xnoidh"))
|
||||
return noise_pattern_Xnoidh;
|
||||
else if (name.equals("NXnoidh"))
|
||||
return noise_pattern_NXnoidh;
|
||||
else if (name.equals("XXnoidh"))
|
||||
return noise_pattern_XXnoidh;
|
||||
else if (name.equals("KXnoidh"))
|
||||
return noise_pattern_KXnoidh;
|
||||
else if (name.equals("IKnoidh"))
|
||||
return noise_pattern_IKnoidh;
|
||||
else if (name.equals("IXnoidh"))
|
||||
return noise_pattern_IXnoidh;
|
||||
else if (name.equals("NNhfs"))
|
||||
return noise_pattern_NNhfs;
|
||||
else if (name.equals("NKhfs"))
|
||||
return noise_pattern_NKhfs;
|
||||
else if (name.equals("NXhfs"))
|
||||
return noise_pattern_NXhfs;
|
||||
else if (name.equals("XNhfs"))
|
||||
return noise_pattern_XNhfs;
|
||||
else if (name.equals("XKhfs"))
|
||||
return noise_pattern_XKhfs;
|
||||
else if (name.equals("XXhfs"))
|
||||
return noise_pattern_XXhfs;
|
||||
else if (name.equals("KNhfs"))
|
||||
return noise_pattern_KNhfs;
|
||||
else if (name.equals("KKhfs"))
|
||||
return noise_pattern_KKhfs;
|
||||
else if (name.equals("KXhfs"))
|
||||
return noise_pattern_KXhfs;
|
||||
else if (name.equals("INhfs"))
|
||||
return noise_pattern_INhfs;
|
||||
else if (name.equals("IKhfs"))
|
||||
return noise_pattern_IKhfs;
|
||||
else if (name.equals("IXhfs"))
|
||||
return noise_pattern_IXhfs;
|
||||
else if (name.equals("XXfallback+hfs"))
|
||||
return noise_pattern_XXfallback_hfs;
|
||||
else if (name.equals("NXnoidh+hfs"))
|
||||
return noise_pattern_NXnoidh_hfs;
|
||||
else if (name.equals("XXnoidh+hfs"))
|
||||
return noise_pattern_XXnoidh_hfs;
|
||||
else if (name.equals("KXnoidh+hfs"))
|
||||
return noise_pattern_KXnoidh_hfs;
|
||||
else if (name.equals("IKnoidh+hfs"))
|
||||
return noise_pattern_IKnoidh_hfs;
|
||||
else if (name.equals("IXnoidh+hfs"))
|
||||
return noise_pattern_IXnoidh_hfs;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverses the local and remote flags for a pattern.
|
||||
*
|
||||
* @param flags The flags, assuming that the initiator is "local".
|
||||
* @return The reversed flags, with the responder now being "local".
|
||||
*/
|
||||
public static short reverseFlags(short flags)
|
||||
{
|
||||
return (short)(((flags >> 8) & 0x00FF) | ((flags << 8) & 0xFF00));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,484 @@
|
||||
/*
|
||||
* Copyright (C) 2016 Southern Storm Software, Pty Ltd.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included
|
||||
* in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package com.futo.platformplayer.noise.protocol;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.DigestException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.ShortBufferException;
|
||||
|
||||
/**
|
||||
* Symmetric state for helping manage a Noise handshake.
|
||||
*/
|
||||
class SymmetricState implements Destroyable {
|
||||
|
||||
private String name;
|
||||
private CipherState cipher;
|
||||
private MessageDigest hash;
|
||||
private byte[] ck;
|
||||
private byte[] h;
|
||||
private byte[] prev_h;
|
||||
|
||||
/**
|
||||
* Constructs a new symmetric state object.
|
||||
*
|
||||
* @param protocolName The name of the Noise protocol, which is assumed to be valid.
|
||||
* @param cipherName The name of the cipher within protocolName.
|
||||
* @param hashName The name of the hash within protocolName.
|
||||
*
|
||||
* @throws NoSuchAlgorithmException The cipher or hash algorithm in the
|
||||
* protocol name is not supported.
|
||||
*/
|
||||
public SymmetricState(String protocolName, String cipherName, String hashName) throws NoSuchAlgorithmException
|
||||
{
|
||||
name = protocolName;
|
||||
cipher = Noise.createCipher(cipherName);
|
||||
hash = Noise.createHash(hashName);
|
||||
int hashLength = hash.getDigestLength();
|
||||
ck = new byte [hashLength];
|
||||
h = new byte [hashLength];
|
||||
prev_h = new byte [hashLength];
|
||||
|
||||
byte[] protocolNameBytes;
|
||||
try {
|
||||
protocolNameBytes = protocolName.getBytes("UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
// If UTF-8 is not supported, then we are definitely in trouble!
|
||||
throw new UnsupportedOperationException("UTF-8 encoding is not supported");
|
||||
}
|
||||
|
||||
if (protocolNameBytes.length <= hashLength) {
|
||||
System.arraycopy(protocolNameBytes, 0, h, 0, protocolNameBytes.length);
|
||||
Arrays.fill(h, protocolNameBytes.length, h.length, (byte)0);
|
||||
} else {
|
||||
hashOne(protocolNameBytes, 0, protocolNameBytes.length, h, 0, h.length);
|
||||
}
|
||||
|
||||
System.arraycopy(h, 0, ck, 0, hashLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the Noise protocol.
|
||||
*
|
||||
* @return The protocol name.
|
||||
*/
|
||||
public String getProtocolName()
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the length of MAC values in the current state.
|
||||
*
|
||||
* @return The length of the MAC value for the underlying cipher
|
||||
* or zero if the cipher has not yet been initialized with a key.
|
||||
*/
|
||||
public int getMACLength()
|
||||
{
|
||||
return cipher.getMACLength();
|
||||
}
|
||||
|
||||
/**
|
||||
* Mixes data into the chaining key.
|
||||
*
|
||||
* @param data The buffer containing the data to mix in.
|
||||
* @param offset The offset of the first data byte to mix in.
|
||||
* @param length The number of bytes to mix in.
|
||||
*/
|
||||
public void mixKey(byte[] data, int offset, int length)
|
||||
{
|
||||
int keyLength = cipher.getKeyLength();
|
||||
byte[] tempKey = new byte [keyLength];
|
||||
try {
|
||||
hkdf(ck, 0, ck.length, data, offset, length, ck, 0, ck.length, tempKey, 0, keyLength);
|
||||
cipher.initializeKey(tempKey, 0);
|
||||
} finally {
|
||||
Noise.destroy(tempKey);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mixes data into the handshake hash.
|
||||
*
|
||||
* @param data The buffer containing the data to mix in.
|
||||
* @param offset The offset of the first data byte to mix in.
|
||||
* @param length The number of bytes to mix in.
|
||||
*/
|
||||
public void mixHash(byte[] data, int offset, int length)
|
||||
{
|
||||
hashTwo(h, 0, h.length, data, offset, length, h, 0, h.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mixes a pre-shared key into the chaining key and handshake hash.
|
||||
*
|
||||
* @param key The pre-shared key value.
|
||||
*/
|
||||
public void mixPreSharedKey(byte[] key)
|
||||
{
|
||||
byte[] temp = new byte [hash.getDigestLength()];
|
||||
try {
|
||||
hkdf(ck, 0, ck.length, key, 0, key.length, ck, 0, ck.length, temp, 0, temp.length);
|
||||
mixHash(temp, 0, temp.length);
|
||||
} finally {
|
||||
Noise.destroy(temp);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mixes a pre-supplied public key into the handshake hash.
|
||||
*
|
||||
* @param dh The object containing the public key.
|
||||
*/
|
||||
public void mixPublicKey(DHState dh)
|
||||
{
|
||||
byte[] temp = new byte [dh.getPublicKeyLength()];
|
||||
try {
|
||||
dh.getPublicKey(temp, 0);
|
||||
mixHash(temp, 0, temp.length);
|
||||
} finally {
|
||||
Noise.destroy(temp);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mixes a pre-supplied public key into the chaining key.
|
||||
*
|
||||
* @param dh The object containing the public key.
|
||||
*/
|
||||
public void mixPublicKeyIntoCK(DHState dh)
|
||||
{
|
||||
byte[] temp = new byte [dh.getPublicKeyLength()];
|
||||
try {
|
||||
dh.getPublicKey(temp, 0);
|
||||
mixKey(temp, 0, temp.length);
|
||||
} finally {
|
||||
Noise.destroy(temp);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypts a block of plaintext and mixes the ciphertext into the handshake hash.
|
||||
*
|
||||
* @param plaintext The buffer containing the plaintext to encrypt.
|
||||
* @param plaintextOffset The offset within the plaintext buffer of the
|
||||
* first byte or plaintext data.
|
||||
* @param ciphertext The buffer to place the ciphertext in. This can
|
||||
* be the same as the plaintext buffer.
|
||||
* @param ciphertextOffset The first offset within the ciphertext buffer
|
||||
* to place the ciphertext and the MAC tag.
|
||||
* @param length The length of the plaintext.
|
||||
* @return The length of the ciphertext plus the MAC tag.
|
||||
*
|
||||
* @throws ShortBufferException There is not enough space in the
|
||||
* ciphertext buffer for the encrypted data plus MAC value.
|
||||
*
|
||||
* The plaintext and ciphertext buffers can be the same for in-place
|
||||
* encryption. In that case, plaintextOffset must be identical to
|
||||
* ciphertextOffset.
|
||||
*
|
||||
* There must be enough space in the ciphertext buffer to accomodate
|
||||
* length + getMACLength() bytes of data starting at ciphertextOffset.
|
||||
*/
|
||||
public int encryptAndHash(byte[] plaintext, int plaintextOffset, byte[] ciphertext, int ciphertextOffset, int length) throws ShortBufferException
|
||||
{
|
||||
int ciphertextLength = cipher.encryptWithAd(h, plaintext, plaintextOffset, ciphertext, ciphertextOffset, length);
|
||||
mixHash(ciphertext, ciphertextOffset, ciphertextLength);
|
||||
return ciphertextLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypts a block of ciphertext and mixes it into the handshake hash.
|
||||
*
|
||||
* @param ciphertext The buffer containing the ciphertext to decrypt.
|
||||
* @param ciphertextOffset The offset within the ciphertext buffer of
|
||||
* the first byte of ciphertext data.
|
||||
* @param plaintext The buffer to place the plaintext in. This can be
|
||||
* the same as the ciphertext buffer.
|
||||
* @param plaintextOffset The first offset within the plaintext buffer
|
||||
* to place the plaintext.
|
||||
* @param length The length of the incoming ciphertext plus the MAC tag.
|
||||
* @return The length of the plaintext with the MAC tag stripped off.
|
||||
*
|
||||
* @throws ShortBufferException There is not enough space in the plaintext
|
||||
* buffer for the decrypted data.
|
||||
*
|
||||
* @throws BadPaddingException The MAC value failed to verify.
|
||||
*
|
||||
* The plaintext and ciphertext buffers can be the same for in-place
|
||||
* decryption. In that case, ciphertextOffset must be identical to
|
||||
* plaintextOffset.
|
||||
*/
|
||||
public int decryptAndHash(byte[] ciphertext, int ciphertextOffset, byte[] plaintext, int plaintextOffset, int length) throws ShortBufferException, BadPaddingException
|
||||
{
|
||||
System.arraycopy(h, 0, prev_h, 0, h.length);
|
||||
mixHash(ciphertext, ciphertextOffset, length);
|
||||
return cipher.decryptWithAd(prev_h, ciphertext, ciphertextOffset, plaintext, plaintextOffset, length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits the symmetric state into two ciphers for session encryption.
|
||||
*
|
||||
* @return The pair of ciphers for sending and receiving.
|
||||
*/
|
||||
public CipherStatePair split()
|
||||
{
|
||||
return split(new byte[0], 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits the symmetric state into two ciphers for session encryption,
|
||||
* and optionally mixes in a secondary symmetric key.
|
||||
*
|
||||
* @param secondaryKey The buffer containing the secondary key.
|
||||
* @param offset The offset of the first secondary key byte.
|
||||
* @param length The length of the secondary key in bytes, which
|
||||
* must be either 0 or 32.
|
||||
* @return The pair of ciphers for sending and receiving.
|
||||
*
|
||||
* @throws IllegalArgumentException The length is not 0 or 32.
|
||||
*/
|
||||
public CipherStatePair split(byte[] secondaryKey, int offset, int length)
|
||||
{
|
||||
if (length != 0 && length != 32)
|
||||
throw new IllegalArgumentException("Secondary keys must be 0 or 32 bytes in length");
|
||||
int keyLength = cipher.getKeyLength();
|
||||
byte[] k1 = new byte [keyLength];
|
||||
byte[] k2 = new byte [keyLength];
|
||||
try {
|
||||
hkdf(ck, 0, ck.length, secondaryKey, offset, length, k1, 0, k1.length, k2, 0, k2.length);
|
||||
CipherState c1 = null;
|
||||
CipherState c2 = null;
|
||||
CipherStatePair pair = null;
|
||||
try {
|
||||
c1 = cipher.fork(k1, 0);
|
||||
c2 = cipher.fork(k2, 0);
|
||||
pair = new CipherStatePair(c1, c2);
|
||||
} finally {
|
||||
if (c1 == null || c2 == null || pair == null) {
|
||||
// Could not create some of the objects. Clean up the others
|
||||
// to avoid accidental leakage of k1 or k2.
|
||||
if (c1 != null)
|
||||
c1.destroy();
|
||||
if (c2 != null)
|
||||
c2.destroy();
|
||||
pair = null;
|
||||
}
|
||||
}
|
||||
return pair;
|
||||
} finally {
|
||||
Noise.destroy(k1);
|
||||
Noise.destroy(k2);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current value of the handshake hash.
|
||||
*
|
||||
* @return The handshake hash. This must not be modified by the caller.
|
||||
*
|
||||
* The handshake hash value is only of use to the application after
|
||||
* split() has been called.
|
||||
*/
|
||||
public byte[] getHandshakeHash()
|
||||
{
|
||||
return h;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
if (cipher != null) {
|
||||
cipher.destroy();
|
||||
cipher = null;
|
||||
}
|
||||
if (hash != null) {
|
||||
// The built-in fallback implementations are destroyable.
|
||||
// JCA/JCE implementations aren't, so try reset() instead.
|
||||
if (hash instanceof Destroyable)
|
||||
((Destroyable)hash).destroy();
|
||||
else
|
||||
hash.reset();
|
||||
hash = null;
|
||||
}
|
||||
if (ck != null) {
|
||||
Noise.destroy(ck);
|
||||
ck = null;
|
||||
}
|
||||
if (h != null) {
|
||||
Noise.destroy(h);
|
||||
h = null;
|
||||
}
|
||||
if (prev_h != null) {
|
||||
Noise.destroy(prev_h);
|
||||
prev_h = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hashes a single data buffer.
|
||||
*
|
||||
* @param data The buffer containing the data to hash.
|
||||
* @param offset Offset into the data buffer of the first byte to hash.
|
||||
* @param length Length of the data to be hashed.
|
||||
* @param output The buffer to receive the output hash value.
|
||||
* @param outputOffset Offset into the output buffer to place the hash value.
|
||||
* @param outputLength The length of the hash output.
|
||||
*
|
||||
* The output buffer can be the same as the input data buffer.
|
||||
*/
|
||||
private void hashOne(byte[] data, int offset, int length, byte[] output, int outputOffset, int outputLength)
|
||||
{
|
||||
hash.reset();
|
||||
hash.update(data, offset, length);
|
||||
try {
|
||||
hash.digest(output, outputOffset, outputLength);
|
||||
} catch (DigestException e) {
|
||||
Arrays.fill(output, outputOffset, outputLength, (byte)0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hashes two data buffers.
|
||||
*
|
||||
* @param data1 The buffer containing the first data to hash.
|
||||
* @param offset1 Offset into the first data buffer of the first byte to hash.
|
||||
* @param length1 Length of the first data to be hashed.
|
||||
* @param data2 The buffer containing the second data to hash.
|
||||
* @param offset2 Offset into the second data buffer of the first byte to hash.
|
||||
* @param length2 Length of the second data to be hashed.
|
||||
* @param output The buffer to receive the output hash value.
|
||||
* @param outputOffset Offset into the output buffer to place the hash value.
|
||||
* @param outputLength The length of the hash output.
|
||||
*
|
||||
* The output buffer can be same as either of the input buffers.
|
||||
*/
|
||||
private void hashTwo(byte[] data1, int offset1, int length1,
|
||||
byte[] data2, int offset2, int length2,
|
||||
byte[] output, int outputOffset, int outputLength)
|
||||
{
|
||||
hash.reset();
|
||||
hash.update(data1, offset1, length1);
|
||||
hash.update(data2, offset2, length2);
|
||||
try {
|
||||
hash.digest(output, outputOffset, outputLength);
|
||||
} catch (DigestException e) {
|
||||
Arrays.fill(output, outputOffset, outputLength, (byte)0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes a HMAC value using key and data values.
|
||||
*
|
||||
* @param key The buffer that contains the key.
|
||||
* @param keyOffset The offset of the key in the key buffer.
|
||||
* @param keyLength The length of the key in bytes.
|
||||
* @param data The buffer that contains the data.
|
||||
* @param dataOffset The offset of the data in the data buffer.
|
||||
* @param dataLength The length of the data in bytes.
|
||||
* @param output The output buffer to place the HMAC value in.
|
||||
* @param outputOffset Offset into the output buffer for the HMAC value.
|
||||
* @param outputLength The length of the HMAC output.
|
||||
*/
|
||||
private void hmac(byte[] key, int keyOffset, int keyLength,
|
||||
byte[] data, int dataOffset, int dataLength,
|
||||
byte[] output, int outputOffset, int outputLength)
|
||||
{
|
||||
// In all of the algorithms of interest to us, the block length
|
||||
// is twice the size of the hash length.
|
||||
int hashLength = hash.getDigestLength();
|
||||
int blockLength = hashLength * 2;
|
||||
byte[] block = new byte [blockLength];
|
||||
int index;
|
||||
try {
|
||||
if (keyLength <= blockLength) {
|
||||
System.arraycopy(key, keyOffset, block, 0, keyLength);
|
||||
Arrays.fill(block, keyLength, blockLength, (byte)0);
|
||||
} else {
|
||||
hash.reset();
|
||||
hash.update(key, keyOffset, keyLength);
|
||||
hash.digest(block, 0, hashLength);
|
||||
Arrays.fill(block, hashLength, blockLength, (byte)0);
|
||||
}
|
||||
for (index = 0; index < blockLength; ++index)
|
||||
block[index] ^= (byte)0x36;
|
||||
hash.reset();
|
||||
hash.update(block, 0, blockLength);
|
||||
hash.update(data, dataOffset, dataLength);
|
||||
hash.digest(output, outputOffset, hashLength);
|
||||
for (index = 0; index < blockLength; ++index)
|
||||
block[index] ^= (byte)(0x36 ^ 0x5C);
|
||||
hash.reset();
|
||||
hash.update(block, 0, blockLength);
|
||||
hash.update(output, outputOffset, hashLength);
|
||||
hash.digest(output, outputOffset, outputLength);
|
||||
} catch (DigestException e) {
|
||||
Arrays.fill(output, outputOffset, outputLength, (byte)0);
|
||||
} finally {
|
||||
Noise.destroy(block);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes a HKDF value.
|
||||
*
|
||||
* @param key The buffer that contains the key.
|
||||
* @param keyOffset The offset of the key in the key buffer.
|
||||
* @param keyLength The length of the key in bytes.
|
||||
* @param data The buffer that contains the data.
|
||||
* @param dataOffset The offset of the data in the data buffer.
|
||||
* @param dataLength The length of the data in bytes.
|
||||
* @param output1 The first output buffer.
|
||||
* @param output1Offset Offset into the first output buffer.
|
||||
* @param output1Length Length of the first output which can be
|
||||
* less than the hash length.
|
||||
* @param output2 The second output buffer.
|
||||
* @param output2Offset Offset into the second output buffer.
|
||||
* @param output2Length Length of the second output which can be
|
||||
* less than the hash length.
|
||||
*/
|
||||
private void hkdf(byte[] key, int keyOffset, int keyLength,
|
||||
byte[] data, int dataOffset, int dataLength,
|
||||
byte[] output1, int output1Offset, int output1Length,
|
||||
byte[] output2, int output2Offset, int output2Length)
|
||||
{
|
||||
int hashLength = hash.getDigestLength();
|
||||
byte[] tempKey = new byte [hashLength];
|
||||
byte[] tempHash = new byte [hashLength + 1];
|
||||
try {
|
||||
hmac(key, keyOffset, keyLength, data, dataOffset, dataLength, tempKey, 0, hashLength);
|
||||
tempHash[0] = (byte)0x01;
|
||||
hmac(tempKey, 0, hashLength, tempHash, 0, 1, tempHash, 0, hashLength);
|
||||
System.arraycopy(tempHash, 0, output1, output1Offset, output1Length);
|
||||
tempHash[hashLength] = (byte)0x02;
|
||||
hmac(tempKey, 0, hashLength, tempHash, 0, hashLength + 1, tempHash, 0, hashLength);
|
||||
System.arraycopy(tempHash, 0, output2, output2Offset, output2Length);
|
||||
} finally {
|
||||
Noise.destroy(tempKey);
|
||||
Noise.destroy(tempHash);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,8 @@ import com.futo.platformplayer.activities.MainActivity
|
||||
import com.futo.platformplayer.logging.Logger
|
||||
import com.futo.platformplayer.receivers.MediaControlReceiver
|
||||
import com.futo.platformplayer.timestampRegex
|
||||
import com.futo.platformplayer.views.behavior.NonScrollingTextView
|
||||
import com.futo.platformplayer.views.behavior.NonScrollingTextView.Companion
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
class PlatformLinkMovementMethod : LinkMovementMethod {
|
||||
@@ -23,6 +25,7 @@ class PlatformLinkMovementMethod : LinkMovementMethod {
|
||||
|
||||
override fun onTouchEvent(widget: TextView, buffer: Spannable, event: MotionEvent): Boolean {
|
||||
val action = event.action;
|
||||
Logger.i(TAG, "onTouchEvent (action = $action)")
|
||||
if (action == MotionEvent.ACTION_UP) {
|
||||
val x = event.x.toInt() - widget.totalPaddingLeft + widget.scrollX;
|
||||
val y = event.y.toInt() - widget.totalPaddingTop + widget.scrollY;
|
||||
|
||||
@@ -182,13 +182,14 @@ class HLS {
|
||||
|
||||
private fun parseAttributes(content: String): Map<String, String> {
|
||||
val attributes = mutableMapOf<String, String>()
|
||||
val attributePairs = content.substringAfter(":").splitToSequence(',')
|
||||
val maybeAttributePairs = content.substringAfter(":").splitToSequence(',')
|
||||
|
||||
var currentPair = StringBuilder()
|
||||
for (pair in attributePairs) {
|
||||
for (pair in maybeAttributePairs) {
|
||||
currentPair.append(pair)
|
||||
if (currentPair.count { it == '\"' } % 2 == 0) { // Check if the number of quotes is even
|
||||
val (key, value) = currentPair.toString().split('=')
|
||||
val key = currentPair.toString().substringBefore("=")
|
||||
val value = currentPair.toString().substringAfter("=")
|
||||
attributes[key.trim()] = value.trim().removeSurrounding("\"")
|
||||
currentPair = StringBuilder() // Reset for the next attribute
|
||||
} else {
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.futo.platformplayer.logging.Logger
|
||||
import com.futo.platformplayer.stores.FragmentedStorage
|
||||
import com.futo.platformplayer.stores.StringArrayStorage
|
||||
import com.futo.polycentric.core.ProcessSecret
|
||||
import com.futo.polycentric.core.PublicKey
|
||||
import com.futo.polycentric.core.base64ToByteArray
|
||||
import com.futo.polycentric.core.toBase64
|
||||
import userpackage.Protocol
|
||||
@@ -29,6 +30,18 @@ class PolycentricStorage {
|
||||
return processSecrets
|
||||
}
|
||||
|
||||
fun removeProcessSecret(publicKey: PublicKey) {
|
||||
for(p in _processSecrets.getAllValues()){
|
||||
try {
|
||||
val key = ProcessSecret.fromProto(Protocol.StorageTypeProcessSecret.parseFrom(GEncryptionProviderV1.instance.decrypt(p.base64ToByteArray())));
|
||||
if(key.system.publicKey.equals(publicKey))
|
||||
_processSecrets.remove(p);
|
||||
} catch (e: Throwable) {
|
||||
Logger.i(TAG, "Failed to decrypt process secret", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val TAG = "PolycentricStorage";
|
||||
private var _instance : PolycentricStorage? = null;
|
||||
|
||||
@@ -2,6 +2,8 @@ package com.futo.platformplayer.states
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Activity
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
@@ -20,9 +22,13 @@ import androidx.lifecycle.lifecycleScope
|
||||
import androidx.work.*
|
||||
import com.futo.platformplayer.*
|
||||
import com.futo.platformplayer.R
|
||||
import com.futo.platformplayer.UIDialogs.Action
|
||||
import com.futo.platformplayer.UIDialogs.ActionStyle
|
||||
import com.futo.platformplayer.UIDialogs.Companion.showDialog
|
||||
import com.futo.platformplayer.activities.CaptchaActivity
|
||||
import com.futo.platformplayer.activities.IWithResultLauncher
|
||||
import com.futo.platformplayer.activities.MainActivity
|
||||
import com.futo.platformplayer.activities.SettingsActivity
|
||||
import com.futo.platformplayer.api.media.platforms.js.DevJSClient
|
||||
import com.futo.platformplayer.api.media.platforms.js.JSClient
|
||||
import com.futo.platformplayer.background.BackgroundWorker
|
||||
@@ -41,6 +47,7 @@ import com.futo.platformplayer.services.DownloadService
|
||||
import com.futo.platformplayer.stores.FragmentedStorage
|
||||
import com.futo.platformplayer.stores.v2.ManagedStore
|
||||
import com.futo.platformplayer.views.ToastView
|
||||
import com.futo.polycentric.core.ApiMethods
|
||||
import kotlinx.coroutines.*
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
@@ -150,6 +157,7 @@ class StateApp {
|
||||
|
||||
//Files
|
||||
private var _tempDirectory: File? = null;
|
||||
private var _cacheDirectory: File? = null;
|
||||
private var _persistentDirectory: File? = null;
|
||||
|
||||
|
||||
@@ -318,6 +326,9 @@ class StateApp {
|
||||
_tempDirectory?.deleteRecursively();
|
||||
}
|
||||
_tempDirectory?.mkdirs();
|
||||
_cacheDirectory = File(context.filesDir, "cache");
|
||||
if(_cacheDirectory?.exists() == false)
|
||||
_cacheDirectory?.mkdirs();
|
||||
_persistentDirectory = File(context.filesDir, "persist");
|
||||
if(_persistentDirectory?.exists() == false) {
|
||||
_persistentDirectory?.mkdirs();
|
||||
@@ -377,6 +388,11 @@ class StateApp {
|
||||
Logger.i(TAG, "MainApp Starting");
|
||||
initializeFiles(true);
|
||||
|
||||
if(Settings.instance.other.polycentricLocalCache) {
|
||||
Logger.i(TAG, "Initialize Polycentric Disk Cache")
|
||||
_cacheDirectory?.let { ApiMethods.initCache(it) };
|
||||
}
|
||||
|
||||
val logFile = File(context.filesDir, "log.txt");
|
||||
if (Settings.instance.logging.logLevel > LogLevel.NONE.value) {
|
||||
val fileLogConsumer = FileLogConsumer(logFile, LogLevel.fromInt(Settings.instance.logging.logLevel), false);
|
||||
@@ -412,11 +428,24 @@ class StateApp {
|
||||
StateTelemetry.instance.upload();
|
||||
}
|
||||
|
||||
if (Settings.instance.synchronization.enabled) {
|
||||
StateSync.instance.start()
|
||||
}
|
||||
|
||||
Logger.onLogSubmitted.subscribe {
|
||||
scopeOrNull?.launch(Dispatchers.Main) {
|
||||
try {
|
||||
if (it != null) {
|
||||
UIDialogs.toast("Uploaded $it", true);
|
||||
if (!it.isNullOrEmpty()) {
|
||||
(SettingsActivity.getActivity() ?: contextOrNull)?.let { c ->
|
||||
val okButtonAction = Action(c.getString(R.string.ok), {}, ActionStyle.PRIMARY)
|
||||
val copyButtonAction = Action(c.getString(R.string.copy), {
|
||||
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
val clip = ClipData.newPlainText("Log id", it)
|
||||
clipboard.setPrimaryClip(clip)
|
||||
}, ActionStyle.NONE)
|
||||
|
||||
showDialog(c, R.drawable.ic_error, "Uploaded $it", null, null, 0, copyButtonAction, okButtonAction)
|
||||
}
|
||||
} else {
|
||||
UIDialogs.toast("Failed to upload");
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import com.futo.platformplayer.activities.IWithResultLauncher
|
||||
import com.futo.platformplayer.activities.MainActivity
|
||||
import com.futo.platformplayer.activities.SettingsActivity
|
||||
import com.futo.platformplayer.api.media.models.channels.SerializedChannel
|
||||
import com.futo.platformplayer.api.media.models.video.IPlatformVideo
|
||||
import com.futo.platformplayer.api.media.models.video.SerializedPlatformVideo
|
||||
import com.futo.platformplayer.copyTo
|
||||
import com.futo.platformplayer.encryption.GPasswordEncryptionProvider
|
||||
@@ -18,7 +19,9 @@ import com.futo.platformplayer.encryption.GPasswordEncryptionProviderV0
|
||||
import com.futo.platformplayer.fragment.mainactivity.main.ImportSubscriptionsFragment
|
||||
import com.futo.platformplayer.getNowDiffHours
|
||||
import com.futo.platformplayer.logging.Logger
|
||||
import com.futo.platformplayer.models.HistoryVideo
|
||||
import com.futo.platformplayer.models.ImportCache
|
||||
import com.futo.platformplayer.models.SubscriptionGroup
|
||||
import com.futo.platformplayer.readBytes
|
||||
import com.futo.platformplayer.stores.FragmentedStorage
|
||||
import com.futo.platformplayer.stores.v2.ManagedStore
|
||||
@@ -35,6 +38,7 @@ import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.File
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.InputStream
|
||||
import java.time.OffsetDateTime
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipInputStream
|
||||
@@ -60,9 +64,9 @@ class StateBackup {
|
||||
StatePlaylists.instance.toMigrateCheck()
|
||||
).flatten();
|
||||
|
||||
fun getCache(): ImportCache {
|
||||
fun getCache(additionalVideos: List<SerializedPlatformVideo> = listOf()): ImportCache {
|
||||
val allPlaylists = StatePlaylists.instance.getPlaylists();
|
||||
val videos = allPlaylists.flatMap { it.videos }.distinctBy { it.url };
|
||||
val videos = allPlaylists.flatMap { it.videos }.plus(additionalVideos).distinctBy { it.url };
|
||||
|
||||
val allSubscriptions = StateSubscriptions.instance.getSubscriptions();
|
||||
val channels = allSubscriptions.map { it.channel };
|
||||
@@ -239,6 +243,23 @@ class StateBackup {
|
||||
.associateBy { it.name }
|
||||
.mapValues { it.value.getAllReconstructionStrings() }
|
||||
.toMutableMap();
|
||||
|
||||
var historyVideos: List<SerializedPlatformVideo>? = null;
|
||||
try {
|
||||
storesToSave.set("subscription_groups", StateSubscriptionGroups.instance.getSubscriptionGroups().map { Json.encodeToString(it) });
|
||||
}
|
||||
catch(ex: Throwable) {
|
||||
Logger.e(TAG, "Failed to serialize subscription groups");
|
||||
}
|
||||
try {
|
||||
val history = StateHistory.instance.getRecentHistory(OffsetDateTime.MIN, 2000);
|
||||
historyVideos = history.map { it.video };
|
||||
storesToSave.set("history", history.map { it.toReconString() });
|
||||
}
|
||||
catch(ex: Throwable) {
|
||||
Logger.e(TAG, "Failed to serialize history");
|
||||
}
|
||||
|
||||
val settings = Settings.instance.encode();
|
||||
val pluginSettings = StatePlugins.instance.getPlugins()
|
||||
.associateBy { it.config.id }
|
||||
@@ -248,7 +269,7 @@ class StateBackup {
|
||||
.associateBy { it.config.id }
|
||||
.mapValues { it.value.config.sourceUrl!! };
|
||||
|
||||
val cache = getCache();
|
||||
val cache = getCache(historyVideos ?: listOf());
|
||||
|
||||
val export = ExportStructure(exportInfo, settings, storesToSave, pluginUrls, pluginSettings, cache);
|
||||
|
||||
@@ -332,19 +353,64 @@ class StateBackup {
|
||||
if(doImportStores) {
|
||||
for(store in export.stores) {
|
||||
Logger.i(TAG, "Importing store [${store.key}]");
|
||||
val relevantStore = availableStores.find { it.name == store.key };
|
||||
if(relevantStore == null) {
|
||||
Logger.w(TAG, "Unknown store [${store.key}] import");
|
||||
continue;
|
||||
if(store.key == "history") {
|
||||
withContext(Dispatchers.Main) {
|
||||
UIDialogs.showDialog(context, R.drawable.ic_move_up, "Import History", "Would you like to import history?", null, 0,
|
||||
UIDialogs.Action("No", {
|
||||
}, UIDialogs.ActionStyle.NONE),
|
||||
UIDialogs.Action("Yes", {
|
||||
for(historyStr in store.value) {
|
||||
try {
|
||||
val histObj = HistoryVideo.fromReconString(historyStr) { url ->
|
||||
return@fromReconString export.cache?.videos?.firstOrNull { it.url == url };
|
||||
}
|
||||
val hist = StateHistory.instance.getHistoryByVideo(histObj.video, true, histObj.date);
|
||||
if(hist != null)
|
||||
StateHistory.instance.updateHistoryPosition(histObj.video, hist, true, histObj.position, histObj.date, false);
|
||||
}
|
||||
catch(ex: Throwable) {
|
||||
Logger.e(TAG, "Failed to import subscription group", ex);
|
||||
}
|
||||
}
|
||||
}, UIDialogs.ActionStyle.PRIMARY))
|
||||
}
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
UIDialogs.showImportDialog(context, relevantStore, store.key, store.value, export.cache) {
|
||||
synchronized(toAwait) {
|
||||
toAwait.remove(store.key);
|
||||
if(toAwait.isEmpty())
|
||||
onConclusion();
|
||||
}
|
||||
};
|
||||
else if(store.key == "subscription_groups") {
|
||||
withContext(Dispatchers.Main) {
|
||||
UIDialogs.showDialog(context, R.drawable.ic_move_up, "Import Subscription Groups", "Would you like to import subscription groups?\nExisting groups with the same id will be overridden!", null, 0,
|
||||
UIDialogs.Action("No", {
|
||||
}, UIDialogs.ActionStyle.NONE),
|
||||
UIDialogs.Action("Yes", {
|
||||
for(groupStr in store.value) {
|
||||
try {
|
||||
val group = Json.decodeFromString<SubscriptionGroup>(groupStr);
|
||||
val existing = StateSubscriptionGroups.instance.getSubscriptionGroup(group.id);
|
||||
if(existing != null)
|
||||
StateSubscriptionGroups.instance.deleteSubscriptionGroup(existing.id, false);
|
||||
StateSubscriptionGroups.instance.updateSubscriptionGroup(group);
|
||||
}
|
||||
catch(ex: Throwable) {
|
||||
Logger.e(TAG, "Failed to import subscription group", ex);
|
||||
}
|
||||
}
|
||||
}, UIDialogs.ActionStyle.PRIMARY))
|
||||
}
|
||||
}
|
||||
else {
|
||||
val relevantStore = availableStores.find { it.name == store.key };
|
||||
if (relevantStore == null) {
|
||||
Logger.w(TAG, "Unknown store [${store.key}] import");
|
||||
continue;
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
UIDialogs.showImportDialog(context, relevantStore, store.key, store.value, export.cache) {
|
||||
synchronized(toAwait) {
|
||||
toAwait.remove(store.key);
|
||||
if(toAwait.isEmpty())
|
||||
onConclusion();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -509,6 +575,16 @@ class StateBackup {
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun fromZipBytes(str: ByteArrayInputStream): ExportStructure {
|
||||
var zip: ZipInputStream? = null;
|
||||
try {
|
||||
zip = ZipInputStream(str);
|
||||
return fromZip(zip);
|
||||
}
|
||||
finally {
|
||||
zip?.close();
|
||||
}
|
||||
}
|
||||
fun fromZip(zipStream: ZipInputStream): ExportStructure {
|
||||
var entry: ZipEntry?
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user