Autobindings, Remove Server code from Android, Debug mode Fixes
continuous-integration/drone/pr Build is passing Details

This commit is contained in:
Sarah Jamie Lewis 2023-03-02 13:17:44 -08:00
parent 68c50b3c4b
commit 444c70a255
22 changed files with 438 additions and 152 deletions

5
.gitignore vendored
View File

@ -8,6 +8,11 @@
.buildlog/
.history
.svn/
package.json
package-lock.json
libCwtch*
cwtch.aar
node_modules
# IntelliJ related
*.iml

View File

@ -1 +1 @@
2023-02-08-16-57-v1.10.5
v0.0.2

View File

@ -1 +1 @@
2023-02-08-21-57-v1.10.5
v0.0.2

View File

@ -323,7 +323,7 @@ class MainActivity: FlutterActivity() {
val profile: String = call.argument("ProfileOnion") ?: ""
val conversation: Int = call.argument("conversation") ?: 0
val target: Int = call.argument("target") ?: 0
result.success(Cwtch.sendInvitation(profile, conversation.toLong(), target.toLong()))
result.success(Cwtch.sendInviteMessage(profile, conversation.toLong(), target.toLong()))
return
}
@ -345,14 +345,14 @@ class MainActivity: FlutterActivity() {
"RestartSharing" -> {
val profile: String = call.argument("ProfileOnion") ?: ""
val filepath: String = call.argument("filekey") ?: ""
result.success(Cwtch.restartSharing(profile, filepath))
result.success(Cwtch.restartFileShare(profile, filepath))
return
}
"StopSharing" -> {
val profile: String = call.argument("ProfileOnion") ?: ""
val filepath: String = call.argument("filekey") ?: ""
result.success(Cwtch.stopSharing(profile, filepath))
result.success(Cwtch.stopFileShare(profile, filepath))
return
}
@ -381,25 +381,18 @@ class MainActivity: FlutterActivity() {
val passNew2: String = call.argument("NewPassAgain") ?: ""
Cwtch.changePassword(profile, pass, passNew, passNew2)
}
"GetMessage" -> {
val profile: String = call.argument("ProfileOnion") ?: ""
val conversation: Int = call.argument("conversation") ?: 0
val indexI: Int = call.argument("index") ?: 0
result.success(Cwtch.getMessage(profile, conversation.toLong(), indexI.toLong()))
return
}
"GetMessageByID" -> {
val profile: String = call.argument("ProfileOnion") ?: ""
val conversation: Int = call.argument("conversation") ?: 0
val id: Int = call.argument("id") ?: 0
result.success(Cwtch.getMessageByID(profile, conversation.toLong(), id.toLong()))
result.success(Cwtch.getMessageById(profile, conversation.toLong(), id.toLong()))
return
}
"GetMessageByContentHash" -> {
val profile: String = call.argument("ProfileOnion") ?: ""
val conversation: Int = call.argument("conversation") ?: 0
val contentHash: String = call.argument("contentHash") ?: ""
result.success(Cwtch.getMessagesByContentHash(profile, conversation.toLong(), contentHash))
result.success(Cwtch.getMessageByContentHash(profile, conversation.toLong(), contentHash))
return
}
"SetMessageAttribute" -> {
@ -409,7 +402,7 @@ class MainActivity: FlutterActivity() {
val midx: Int = call.argument("Message") ?: 0
val key: String = call.argument("key") ?: ""
val value: String = call.argument("value") ?: ""
Cwtch.setMessageAttribute(profile, conversation.toLong(), channel.toLong(), midx.toLong(), key, value)
Cwtch.updateMessageAttribute(profile, conversation.toLong(), channel.toLong(), midx.toLong(), key, value)
}
"AcceptConversation" -> {
val profile: String = call.argument("ProfileOnion") ?: ""
@ -419,12 +412,12 @@ class MainActivity: FlutterActivity() {
"BlockContact" -> {
val profile: String = call.argument("ProfileOnion") ?: ""
val conversation: Int = call.argument("conversation") ?: 0
Cwtch.blockContact(profile, conversation.toLong())
Cwtch.blockConversation(profile, conversation.toLong())
}
"UnblockContact" -> {
val profile: String = call.argument("ProfileOnion") ?: ""
val conversation: Int = call.argument("conversation") ?: 0
Cwtch.unblockContact(profile, conversation.toLong())
Cwtch.unblockConversation(profile, conversation.toLong())
}
"DownloadFile" -> {
@ -436,7 +429,7 @@ class MainActivity: FlutterActivity() {
val filekey: String = call.argument("filekey") ?: ""
// FIXME: Prevent spurious calls by Intent
if (profile != "") {
Cwtch.downloadFile(profile, conversation.toLong(), filepath, manifestpath, filekey)
Cwtch.downloadFileDefaultLimit(profile, conversation.toLong(), filepath, manifestpath, filekey)
}
}
"CheckDownloadStatus" -> {
@ -450,14 +443,9 @@ class MainActivity: FlutterActivity() {
val fileKey: String = call.argument("fileKey") ?: ""
Cwtch.verifyOrResumeDownload(profile, conversation.toLong(), fileKey)
}
"SendProfileEvent" -> {
val onion: String= call.argument("onion") ?: ""
val jsonEvent: String = call.argument("jsonEvent") ?: ""
Cwtch.sendProfileEvent(onion, jsonEvent)
}
"SendAppEvent" -> {
val jsonEvent: String = call.argument("jsonEvent") ?: ""
Cwtch.sendAppEvent(jsonEvent)
"UpdateSettings" -> {
val json: String = call.argument("json") ?: ""
Cwtch.updateSettings(json)
}
"ResetTor" -> {
Cwtch.resetTor()
@ -471,7 +459,7 @@ class MainActivity: FlutterActivity() {
val profile: String = call.argument("ProfileOnion") ?: ""
val server: String = call.argument("server") ?: ""
val groupName: String = call.argument("groupName") ?: ""
Cwtch.createGroup(profile, server, groupName)
Cwtch.startGroup(profile, server, groupName)
}
"DeleteProfile" -> {
val profile: String = call.argument("ProfileOnion") ?: ""
@ -486,7 +474,7 @@ class MainActivity: FlutterActivity() {
"DeleteConversation" -> {
val profile: String = call.argument("ProfileOnion") ?: ""
val conversation: Int = call.argument("conversation") ?: 0
Cwtch.deleteContact(profile, conversation.toLong())
Cwtch.deleteConversation(profile, conversation.toLong())
}
"SetProfileAttribute" -> {
val profile: String = call.argument("ProfileOnion") ?: ""
@ -501,44 +489,6 @@ class MainActivity: FlutterActivity() {
val v: String = call.argument("Val") ?: ""
Cwtch.setConversationAttribute(profile, conversation.toLong(), key, v)
}
"LoadServers" -> {
val password: String = call.argument("Password") ?: ""
Cwtch.loadServers(password)
}
"CreateServer" -> {
val password: String = call.argument("Password") ?: ""
val desc: String = call.argument("Description") ?: ""
val autostart: Boolean = call.argument("Autostart") ?: false
Cwtch.createServer(password, desc, autostart)
}
"DeleteServer" -> {
val serverOnion: String = call.argument("ServerOnion") ?: ""
val password: String = call.argument("Password") ?: ""
Cwtch.deleteServer(serverOnion, password)
}
"LaunchServers" -> {
Cwtch.launchServers()
}
"LaunchServer" -> {
val serverOnion: String = call.argument("ServerOnion") ?: ""
Cwtch.launchServer(serverOnion)
}
"StopServer" -> {
val serverOnion: String = call.argument("ServerOnion") ?: ""
Cwtch.stopServer(serverOnion)
}
"StopServers" -> {
Cwtch.stopServers()
}
"DestroyServers" -> {
Cwtch.destroyServers()
}
"SetServerAttribute" -> {
val serverOnion: String = call.argument("ServerOnion") ?: ""
val key: String = call.argument("Key") ?: ""
val v: String = call.argument("Val") ?: ""
Cwtch.setServerAttribute(serverOnion, key, v)
}
"ImportProfile" -> {
val file: String = call.argument("file") ?: ""
val pass: String = call.argument("pass") ?: ""

View File

@ -3,6 +3,6 @@
VERSION=`cat LIBCWTCH-GO-MACOS.version`
echo $VERSION
curl --fail https://build.openprivacy.ca/files/libCwtch-go-macos-$VERSION/libCwtch.x64.dylib --output libCwtch.x64.dylib
curl --fail https://build.openprivacy.ca/files/libCwtch-go-macos-$VERSION/libCwtch.arm64.dylib --output libCwtch.arm64.dylib
curl --fail https://build.openprivacy.ca/files/libCwtch-autobindings-$VERSION/libCwtch.x64.dylib --output libCwtch.x64.dylib
curl --fail https://build.openprivacy.ca/files/libCwtch-autobindings-$VERSION/libCwtch.arm64.dylib --output libCwtch.arm64.dylib

View File

@ -2,7 +2,7 @@ $Env:VERSION = type LIBCWTCH-GO.version
echo $Env:VERSION
# This should automatically fail on error...
Invoke-WebRequest -Uri https://build.openprivacy.ca/files/libCwtch-go-$Env:VERSION/libCwtch.dll -OutFile windows/libCwtch.dll
Invoke-WebRequest -Uri https://build.openprivacy.ca/files/libCwtch-autobindings-$Env:VERSION/libCwtch.dll -OutFile windows/libCwtch.dll
#Invoke-WebRequest -Uri https://build.openprivacy.ca/files/libCwtch-go-$Env:VERSION/cwtch.aar -OutFile android/cwtch/cwtch.aar
#Invoke-WebRequest -Uri https://build.openprivacy.ca/files/libCwtch-go-$Env:VERSION/libCwtch.so -Outfile linux/libCwtch.so

View File

@ -3,5 +3,5 @@
VERSION=`cat LIBCWTCH-GO.version`
echo $VERSION
curl --fail https://build.openprivacy.ca/files/libCwtch-go-$VERSION/cwtch.aar --output android/cwtch/cwtch.aar
curl --fail https://build.openprivacy.ca/files/libCwtch-go-$VERSION/libCwtch.so --output linux/libCwtch.so
curl --fail https://build.openprivacy.ca/files/libCwtch-autobindings-$VERSION/cwtch.aar --output android/cwtch/cwtch.aar
curl --fail https://build.openprivacy.ca/files/libCwtch-autobindings-$VERSION/libCwtch.so --output linux/libCwtch.so

View File

@ -32,12 +32,7 @@ abstract class Cwtch {
// ignore: non_constant_identifier_names
void ResetTor();
// todo: remove these
// ignore: non_constant_identifier_names
void SendProfileEvent(String onion, String jsonEvent);
// ignore: non_constant_identifier_names
void SendAppEvent(String jsonEvent);
void UpdateSettings(String json);
// ignore: non_constant_identifier_names
void AcceptContact(String profileOnion, int contactHandle);
@ -136,4 +131,6 @@ abstract class Cwtch {
void dispose();
Future<dynamic> GetDebugInfo();
bool IsServersCompiled();
}

View File

@ -29,6 +29,7 @@ typedef FreeFn = void Function(Pointer<Utf8>);
typedef void_from_string_string_function = Void Function(Pointer<Utf8>, Int32, Pointer<Utf8>, Int32);
typedef VoidFromStringStringFn = void Function(Pointer<Utf8>, int, Pointer<Utf8>, int);
typedef VoidFromStringFn = void Function(Pointer<Utf8>, int);
typedef void_from_string_string_string_function = Void Function(Pointer<Utf8>, Int32, Pointer<Utf8>, Int32, Pointer<Utf8>, Int32);
typedef VoidFromStringStringStringFn = void Function(Pointer<Utf8>, int, Pointer<Utf8>, int, Pointer<Utf8>, int);
@ -361,30 +362,6 @@ class CwtchFfi implements Cwtch {
return jsonMessage;
}
@override
// ignore: non_constant_identifier_names
void SendProfileEvent(String onion, String json) {
var sendAppBusEvent = library.lookup<NativeFunction<string_string_to_void_function>>("c_SendProfileEvent");
// ignore: non_constant_identifier_names
final SendAppBusEvent = sendAppBusEvent.asFunction<StringStringFn>();
final utf8onion = onion.toNativeUtf8();
final utf8json = json.toNativeUtf8();
SendAppBusEvent(utf8onion, utf8onion.length, utf8json, utf8json.length);
malloc.free(utf8onion);
malloc.free(utf8json);
}
@override
// ignore: non_constant_identifier_names
void SendAppEvent(String json) {
var sendAppBusEvent = library.lookup<NativeFunction<string_to_void_function>>("c_SendAppEvent");
// ignore: non_constant_identifier_names
final SendAppBusEvent = sendAppBusEvent.asFunction<StringFn>();
final utf8json = json.toNativeUtf8();
SendAppBusEvent(utf8json, utf8json.length);
malloc.free(utf8json);
}
@override
// ignore: non_constant_identifier_names
void AcceptContact(String profileOnion, int contactHandle) {
@ -437,7 +414,7 @@ class CwtchFfi implements Cwtch {
@override
// ignore: non_constant_identifier_names
Future<dynamic> SendInvitation(String profileOnion, int contactHandle, int target) async {
var sendInvitation = library.lookup<NativeFunction<get_json_blob_from_str_int_int_function>>("c_SendInvitation");
var sendInvitation = library.lookup<NativeFunction<get_json_blob_from_str_int_int_function>>("c_SendInviteMessage");
// ignore: non_constant_identifier_names
final SendInvitation = sendInvitation.asFunction<GetJsonBlobFromStrIntIntFn>();
final u1 = profileOnion.toNativeUtf8();
@ -467,7 +444,7 @@ class CwtchFfi implements Cwtch {
@override
// ignore: non_constant_identifier_names
void DownloadFile(String profileOnion, int contactHandle, String filepath, String manifestpath, String filekey) {
var dlFile = library.lookup<NativeFunction<void_from_string_int_string_string_string_function>>("c_DownloadFile");
var dlFile = library.lookup<NativeFunction<void_from_string_int_string_string_string_function>>("c_DownloadFileDefaultLimit");
// ignore: non_constant_identifier_names
final DownloadFile = dlFile.asFunction<VoidFromStringIntStringStringStringFn>();
final u1 = profileOnion.toNativeUtf8();
@ -546,12 +523,12 @@ class CwtchFfi implements Cwtch {
@override
// ignore: non_constant_identifier_names
void CreateGroup(String profileOnion, String server, String groupName) {
var createGroup = library.lookup<NativeFunction<void_from_string_string_string_function>>("c_CreateGroup");
var createGroup = library.lookup<NativeFunction<void_from_string_string_string_function>>("c_StartGroup");
// ignore: non_constant_identifier_names
final CreateGroup = createGroup.asFunction<VoidFromStringStringStringFn>();
final u1 = profileOnion.toNativeUtf8();
final u2 = server.toNativeUtf8();
final u3 = groupName.toNativeUtf8();
final u3 = server.toNativeUtf8();
final u2 = groupName.toNativeUtf8();
CreateGroup(u1, u1.length, u2, u2.length, u3, u3.length);
malloc.free(u1);
@ -627,7 +604,7 @@ class CwtchFfi implements Cwtch {
@override
// ignore: non_constant_identifier_names
void SetMessageAttribute(String profile, int conversation, int channel, int message, String key, String val) {
var setMessageAttribute = library.lookup<NativeFunction<void_from_string_int_int_int_string_string_function>>("c_SetMessageAttribute");
var setMessageAttribute = library.lookup<NativeFunction<void_from_string_int_int_int_string_string_function>>("c_UpdateMessageAttribute");
// ignore: non_constant_identifier_names
final SetMessageAttribute = setMessageAttribute.asFunction<VoidFromStringIntIntIntStringStringFn>();
final u1 = profile.toNativeUtf8();
@ -762,7 +739,7 @@ class CwtchFfi implements Cwtch {
@override
// ignore: non_constant_identifier_names
Future GetMessageByContentHash(String profile, int handle, String contentHash) async {
var getMessagesByContentHashC = library.lookup<NativeFunction<get_json_blob_from_str_int_string_function>>("c_GetMessagesByContentHash");
var getMessagesByContentHashC = library.lookup<NativeFunction<get_json_blob_from_str_int_string_function>>("c_GetMessageByContentHash");
// ignore: non_constant_identifier_names
final GetMessagesByContentHash = getMessagesByContentHashC.asFunction<GetJsonBlobFromStrIntStringFn>();
final utf8profile = profile.toNativeUtf8();
@ -911,4 +888,19 @@ class CwtchFfi implements Cwtch {
malloc.free(utf8profile);
malloc.free(ut8filekey);
}
@override
void UpdateSettings(String json) {
var updateSettings = library.lookup<NativeFunction<string_to_void_function>>("c_UpdateSettings");
// ignore: non_constant_identifier_names
final UpdateSettingsFn = updateSettings.asFunction<VoidFromStringFn>();
final u1 = json.toNativeUtf8();
UpdateSettingsFn(u1, u1.length);
malloc.free(u1);
}
@override
bool IsServersCompiled() {
return library.providesSymbol("c_LoadServers");
}
}

View File

@ -358,4 +358,15 @@ class CwtchGomobile implements Cwtch {
void StopSharing(String profile, String filekey) {
cwtchPlatform.invokeMethod("StopSharing", {"ProfileOnion": profile, "filekey": filekey});
}
@override
void UpdateSettings(String json) {
cwtchPlatform.invokeMethod("UpdateSettings", {"json": json});
}
@override
bool IsServersCompiled() {
// never for android builds...
return false;
}
}

View File

@ -1,5 +1,6 @@
import 'dart:ui';
import 'package:cwtch/config.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/widgets.dart';
@ -9,6 +10,13 @@ class ProfileListState extends ChangeNotifier {
List<ProfileInfoState> _profiles = [];
int get num => _profiles.length;
@override
void dispose() {
EnvironmentConfig.debugLog("disposal of profile infostate called...");
EnvironmentConfig.debugLog(StackTrace.current.toString());
super.dispose();
}
void add(String onion, String name, String picture, String defaultPicture, String contactsJson, String serverJson, bool online, bool autostart, bool encrypted) {
var idx = _profiles.indexWhere((element) => element.onion == onion);
if (idx == -1) {

9
lib/third_party/base32/LICENSE vendored Normal file
View File

@ -0,0 +1,9 @@
The original version of the base32 code in this library is from https://github.com/Daegalus/dart-base32
Copyright (c) 2012 Yulian Kuncheff
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.

196
lib/third_party/base32/base32.dart vendored Normal file
View File

@ -0,0 +1,196 @@
import 'dart:typed_data';
import 'package:cwtch/third_party/base32/encoding.dart';
// ignore: camel_case_types
class base32 {
/// Takes in a [byteList] converts it to a Uint8List so that I can run
/// bit operations on it, then outputs a [String] representation of the
/// base32.
static String encode(Uint8List bytesList, {Encoding encoding = Encoding.standardRFC4648}) {
var base32Chars = EncodingUtils.getChars(encoding);
var i = 0;
var count = (bytesList.length ~/ 5) * 5;
var base32str = '';
while (i < count) {
var v1 = bytesList[i++];
var v2 = bytesList[i++];
var v3 = bytesList[i++];
var v4 = bytesList[i++];
var v5 = bytesList[i++];
base32str += base32Chars[v1 >> 3] +
base32Chars[(v1 << 2 | v2 >> 6) & 31] +
base32Chars[(v2 >> 1) & 31] +
base32Chars[(v2 << 4 | v3 >> 4) & 31] +
base32Chars[(v3 << 1 | v4 >> 7) & 31] +
base32Chars[(v4 >> 2) & 31] +
base32Chars[(v4 << 3 | v5 >> 5) & 31] +
base32Chars[v5 & 31];
}
var remain = bytesList.length - count;
if (remain == 1) {
var v1 = bytesList[i];
base32str += base32Chars[v1 >> 3] + base32Chars[(v1 << 2) & 31];
if (EncodingUtils.getPadded(encoding)) {
base32str += '======';
}
} else if (remain == 2) {
var v1 = bytesList[i++];
var v2 = bytesList[i];
base32str += base32Chars[v1 >> 3] + base32Chars[(v1 << 2 | v2 >> 6) & 31] + base32Chars[(v2 >> 1) & 31] + base32Chars[(v2 << 4) & 31];
if (EncodingUtils.getPadded(encoding)) {
base32str += '====';
}
} else if (remain == 3) {
var v1 = bytesList[i++];
var v2 = bytesList[i++];
var v3 = bytesList[i];
base32str += base32Chars[v1 >> 3] + base32Chars[(v1 << 2 | v2 >> 6) & 31] + base32Chars[(v2 >> 1) & 31] + base32Chars[(v2 << 4 | v3 >> 4) & 31] + base32Chars[(v3 << 1) & 31];
if (EncodingUtils.getPadded(encoding)) {
base32str += '===';
}
} else if (remain == 4) {
var v1 = bytesList[i++];
var v2 = bytesList[i++];
var v3 = bytesList[i++];
var v4 = bytesList[i];
base32str += base32Chars[v1 >> 3] +
base32Chars[(v1 << 2 | v2 >> 6) & 31] +
base32Chars[(v2 >> 1) & 31] +
base32Chars[(v2 << 4 | v3 >> 4) & 31] +
base32Chars[(v3 << 1 | v4 >> 7) & 31] +
base32Chars[(v4 >> 2) & 31] +
base32Chars[(v4 << 3) & 31];
if (EncodingUtils.getPadded(encoding)) {
base32str += '=';
}
}
return base32str;
}
static Uint8List _hexDecode(final String input) => Uint8List.fromList([
for (int i = 0; i < input.length; i += 2) int.parse(input.substring(i, i + 2), radix: 16),
]);
static String _hexEncode(final Uint8List input) => [for (int i = 0; i < input.length; i++) input[i].toRadixString(16).padLeft(2, '0')].join();
/// Takes in a [hex] string, converts the string to a byte list
/// and runs a normal encode() on it. Returning a [String] representation
/// of the base32.
static String encodeHexString(String b32hex, {Encoding encoding = Encoding.standardRFC4648}) {
return encode(_hexDecode(b32hex), encoding: encoding);
}
/// Takes in a [utf8string], converts the string to a byte list
/// and runs a normal encode() on it. Returning a [String] representation
/// of the base32.
static String encodeString(String utf8string, {Encoding encoding = Encoding.standardRFC4648}) {
return encode(Uint8List.fromList(utf8string.codeUnits), encoding: encoding);
}
/// Takes in a [base32] string and decodes it back to a [String] in hex format.
static String decodeAsHexString(String base32, {Encoding encoding = Encoding.standardRFC4648}) {
return _hexEncode(decode(base32, encoding: encoding));
}
/// Takes in a [base32] string and decodes it back to a [String].
static String decodeAsString(String base32, {Encoding encoding = Encoding.standardRFC4648}) {
return decode(base32, encoding: encoding).toList().map((charCode) => String.fromCharCode(charCode)).join();
}
/// Takes in a [base32] string and decodes it back to a [Uint8List] that can be
/// converted to a hex string using hexEncode
static Uint8List decode(String base32, {Encoding encoding = Encoding.standardRFC4648}) {
if (base32.isEmpty) {
return Uint8List(0);
}
base32 = _pad(base32, encoding: encoding);
if (!_isValid(base32, encoding: encoding)) {
throw FormatException('Invalid Base32 characters');
}
if (encoding == Encoding.crockford) {
base32 = base32.replaceAll('-', '');
} // Handle crockford dashes.
var base32Decode = EncodingUtils.getDecodeMap(encoding);
var length = base32.indexOf('=');
if (length == -1) {
length = base32.length;
}
var i = 0;
var count = length >> 3 << 3;
var bytes = <int>[];
while (i < count) {
var v1 = base32Decode[base32[i++]] ?? 0;
var v2 = base32Decode[base32[i++]] ?? 0;
var v3 = base32Decode[base32[i++]] ?? 0;
var v4 = base32Decode[base32[i++]] ?? 0;
var v5 = base32Decode[base32[i++]] ?? 0;
var v6 = base32Decode[base32[i++]] ?? 0;
var v7 = base32Decode[base32[i++]] ?? 0;
var v8 = base32Decode[base32[i++]] ?? 0;
bytes.add((v1 << 3 | v2 >> 2) & 255);
bytes.add((v2 << 6 | v3 << 1 | v4 >> 4) & 255);
bytes.add((v4 << 4 | v5 >> 1) & 255);
bytes.add((v5 << 7 | v6 << 2 | v7 >> 3) & 255);
bytes.add((v7 << 5 | v8) & 255);
}
var remain = length - count;
if (remain == 2) {
var v1 = base32Decode[base32[i++]] ?? 0;
var v2 = base32Decode[base32[i++]] ?? 0;
bytes.add((v1 << 3 | v2 >> 2) & 255);
} else if (remain == 4) {
var v1 = base32Decode[base32[i++]] ?? 0;
var v2 = base32Decode[base32[i++]] ?? 0;
var v3 = base32Decode[base32[i++]] ?? 0;
var v4 = base32Decode[base32[i++]] ?? 0;
bytes.add((v1 << 3 | v2 >> 2) & 255);
bytes.add((v2 << 6 | v3 << 1 | v4 >> 4) & 255);
} else if (remain == 5) {
var v1 = base32Decode[base32[i++]] ?? 0;
var v2 = base32Decode[base32[i++]] ?? 0;
var v3 = base32Decode[base32[i++]] ?? 0;
var v4 = base32Decode[base32[i++]] ?? 0;
var v5 = base32Decode[base32[i++]] ?? 0;
bytes.add((v1 << 3 | v2 >> 2) & 255);
bytes.add((v2 << 6 | v3 << 1 | v4 >> 4) & 255);
bytes.add((v4 << 4 | v5 >> 1) & 255);
} else if (remain == 7) {
var v1 = base32Decode[base32[i++]] ?? 0;
var v2 = base32Decode[base32[i++]] ?? 0;
var v3 = base32Decode[base32[i++]] ?? 0;
var v4 = base32Decode[base32[i++]] ?? 0;
var v5 = base32Decode[base32[i++]] ?? 0;
var v6 = base32Decode[base32[i++]] ?? 0;
var v7 = base32Decode[base32[i++]] ?? 0;
bytes.add((v1 << 3 | v2 >> 2) & 255);
bytes.add((v2 << 6 | v3 << 1 | v4 >> 4) & 255);
bytes.add((v4 << 4 | v5 >> 1) & 255);
bytes.add((v5 << 7 | v6 << 2 | v7 >> 3) & 255);
}
return Uint8List.fromList(bytes);
}
static bool _isValid(String b32str, {Encoding encoding = Encoding.standardRFC4648}) {
var regex = EncodingUtils.getRegex(encoding);
if (b32str.length % 2 != 0 || !regex.hasMatch(b32str)) {
return false;
}
return true;
}
static String _pad(String base32, {Encoding encoding = Encoding.standardRFC4648}) {
if (EncodingUtils.getPadded(encoding)) {
int neededPadding = (8 - base32.length % 8) % 8;
return base32.padRight(base32.length + neededPadding, '=');
}
return base32;
}
}

52
lib/third_party/base32/encoding.dart vendored Normal file
View File

@ -0,0 +1,52 @@
class EncodingUtils {
static String getChars(Encoding encoding) {
return _encodeMap[encoding]!;
}
static Map<String, int> getDecodeMap(Encoding encoding) {
var map = _decodeMap[encoding];
if (map != null) {
return map;
} else {
var chars = _encodeMap[encoding]!;
// ignore: omit_local_variable_types
Map<String, int> map = {};
for (var i = 0; i < 32; i++) {
map[chars[i]] = i;
}
_decodeMap[encoding] = map;
return map;
}
}
static RegExp getRegex(Encoding encoding) {
return _regexMap[encoding]!;
}
static bool getPadded(Encoding encoding) {
return _padded[encoding]!;
}
static final _regexMap = {
Encoding.standardRFC4648: RegExp(r'^[A-Z2-7=]+$'),
Encoding.nonStandardRFC4648Lower: RegExp(r'^[a-z2-7=]+$'),
Encoding.base32Hex: RegExp(r'^[0-9A-V=]+$'),
Encoding.crockford: RegExp(r'^[0123456789ABCDEFGHJKMNPQRSTVWXYZ-]+$'),
Encoding.zbase32: RegExp(r'^[ybndrfg8ejkmcpqxot1uwisza345h769]+$'),
Encoding.geohash: RegExp(r'^[0123456789bcdefghjkmnpqrstuvwxyz=]+$')
};
static final _encodeMap = {
Encoding.standardRFC4648: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567',
Encoding.nonStandardRFC4648Lower: 'abcdefghijklmnopqrstuvwxyz234567',
Encoding.base32Hex: '0123456789ABCDEFGHIJKLMNOPQRSTUV',
Encoding.crockford: '0123456789ABCDEFGHJKMNPQRSTVWXYZ',
Encoding.zbase32: 'ybndrfg8ejkmcpqxot1uwisza345h769',
Encoding.geohash: '0123456789bcdefghjkmnpqrstuvwxyz'
};
static final Map<Encoding, Map<String, int>> _decodeMap = {};
static final _padded = {Encoding.standardRFC4648: true, Encoding.nonStandardRFC4648Lower: true, Encoding.base32Hex: true, Encoding.crockford: false, Encoding.zbase32: false, Encoding.geohash: true};
}
enum Encoding { standardRFC4648, base32Hex, crockford, zbase32, geohash, nonStandardRFC4648Lower }

View File

@ -307,12 +307,13 @@ class _ContactsViewState extends State<ContactsView> {
}
void _pushServers() {
var profile = Provider.of<ProfileInfoState>(context);
var profileInfoState = Provider.of<ProfileInfoState>(context, listen: false);
Navigator.of(context).push(
PageRouteBuilder(
pageBuilder: (bcontext, a1, a2) {
return MultiProvider(
providers: [ChangeNotifierProvider(create: (context) => profile), Provider.value(value: Provider.of<FlwtchState>(context))],
providers: [ChangeNotifierProvider.value(value: profileInfoState), Provider.value(value: Provider.of<FlwtchState>(context))],
child: ProfileServersView(),
);
},

View File

@ -377,22 +377,27 @@ class _GlobalSettingsViewState extends State<GlobalSettingsView> {
Visibility(
visible: !Platform.isAndroid && !Platform.isIOS,
child: SwitchListTile(
title: Text(AppLocalizations.of(context)!.settingServers, style: TextStyle(color: settings.current().mainTextColor)),
subtitle: Text(AppLocalizations.of(context)!.settingServersDescription),
value: settings.isExperimentEnabled(ServerManagementExperiment),
onChanged: (bool value) {
Provider.of<ServerListState>(context, listen: false).clear();
if (value) {
settings.enableExperiment(ServerManagementExperiment);
} else {
settings.disableExperiment(ServerManagementExperiment);
}
// Save Settings...
saveSettings(context);
},
title: Text(AppLocalizations.of(context)!.settingServers),
subtitle: Provider.of<FlwtchState>(context, listen: false).cwtch.IsServersCompiled()
? Text(AppLocalizations.of(context)!.settingServersDescription)
: Text("This version of Cwtch has been compiled without support for the server hosting experiment."),
value: Provider.of<FlwtchState>(context, listen: false).cwtch.IsServersCompiled() && settings.isExperimentEnabled(ServerManagementExperiment),
onChanged: Provider.of<FlwtchState>(context, listen: false).cwtch.IsServersCompiled()
? (bool value) {
Provider.of<ServerListState>(context, listen: false).clear();
if (value) {
settings.enableExperiment(ServerManagementExperiment);
} else {
settings.disableExperiment(ServerManagementExperiment);
}
// Save Settings...
saveSettings(context);
}
: null,
activeTrackColor: settings.theme.defaultButtonColor,
inactiveTrackColor: settings.theme.defaultButtonDisabledColor,
secondary: Icon(CwtchIcons.dns_24px, color: settings.current().mainTextColor),
inactiveThumbColor: settings.theme.defaultButtonDisabledColor,
secondary: Icon(CwtchIcons.dns_24px),
)),
SwitchListTile(
title: Text(AppLocalizations.of(context)!.settingFileSharing, style: TextStyle(color: settings.current().mainTextColor)),
@ -694,10 +699,5 @@ String getThemeName(context, String theme) {
/// Send an UpdateGlobalSettings to the Event Bus
saveSettings(context) {
var settings = Provider.of<Settings>(context, listen: false);
final updateSettingsEvent = {
"EventType": "UpdateGlobalSettings",
"Data": {"Data": jsonEncode(settings.asJson())},
};
final updateSettingsEventJson = jsonEncode(updateSettingsEvent);
Provider.of<FlwtchState>(context, listen: false).cwtch.SendAppEvent(updateSettingsEventJson);
Provider.of<FlwtchState>(context, listen: false).cwtch.UpdateSettings(jsonEncode(settings.asJson()));
}

View File

@ -299,10 +299,6 @@ class _MessageViewState extends State<MessageView> {
static const GroupMessageLengthMax = 1600;
void _sendMessage([String? ignoredParam]) {
// Trim message
final messageWithoutNewLine = ctrlrCompose.value.text.trimRight();
ctrlrCompose.value = TextEditingValue(text: messageWithoutNewLine, selection: TextSelection.fromPosition(TextPosition(offset: messageWithoutNewLine.length)));
// Do this after we trim to preserve enter-behaviour...
bool isOffline = Provider.of<ContactInfoState>(context, listen: false).isOnline() == false;
bool performingAntiSpam = Provider.of<ContactInfoState>(context, listen: false).antispamTickets == 0;
@ -311,6 +307,10 @@ class _MessageViewState extends State<MessageView> {
return;
}
// Trim message
final messageWithoutNewLine = ctrlrCompose.value.text.trimRight();
ctrlrCompose.value = TextEditingValue(text: messageWithoutNewLine, selection: TextSelection.fromPosition(TextPosition(offset: messageWithoutNewLine.length)));
// peers and groups currently have different length constraints (servers can store less)...
var actualMessageLength = ctrlrCompose.value.text.length;
var lengthOk = (isGroup && actualMessageLength < GroupMessageLengthMax) || actualMessageLength <= P2PMessageLengthMax;
@ -368,6 +368,11 @@ class _MessageViewState extends State<MessageView> {
return;
}
// At this point we have decided to send the text to the backend, failure is still possible
// but it will show as an error-ed message, as such the draft can be purged.
Provider.of<ContactInfoState>(context, listen: false).messageDraft = null;
ctrlrCompose.clear();
var profileOnion = Provider.of<ContactInfoState>(context, listen: false).profileOnion;
var identifier = Provider.of<ContactInfoState>(context, listen: false).identifier;
var profile = Provider.of<ProfileInfoState>(context, listen: false);
@ -388,11 +393,8 @@ class _MessageViewState extends State<MessageView> {
);
}
Provider.of<ContactInfoState>(context, listen: false).messageDraft = null;
ctrlrCompose.clear();
focusNode.requestFocus();
Provider.of<FlwtchState>(context, listen: false).cwtch.SetConversationAttribute(profileOnion, identifier, LastMessageSeenTimeKey, DateTime.now().toIso8601String());
focusNode.requestFocus();
}
Widget _buildPreviewBox() {

View File

@ -106,7 +106,10 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
));
// Servers
if (Provider.of<Settings>(context).isExperimentEnabled(ServerManagementExperiment) && !Platform.isAndroid && !Platform.isIOS) {
if (Provider.of<FlwtchState>(context, listen: false).cwtch.IsServersCompiled() &&
Provider.of<Settings>(context).isExperimentEnabled(ServerManagementExperiment) &&
!Platform.isAndroid &&
!Platform.isIOS) {
actions.add(
IconButton(icon: Icon(CwtchIcons.dns_black_24dp), splashRadius: Material.defaultSplashRadius / 2, tooltip: AppLocalizations.of(context)!.serversManagerTitleShort, onPressed: _pushServers));
}
@ -150,7 +153,7 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
settings: RouteSettings(name: "servers"),
pageBuilder: (bcontext, a1, a2) {
return MultiProvider(
providers: [Provider.value(value: Provider.of<FlwtchState>(context))],
providers: [ChangeNotifierProvider.value(value: globalServersList), Provider.value(value: Provider.of<FlwtchState>(context))],
child: ServersView(),
);
},
@ -226,7 +229,7 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
shape: RoundedRectangleBorder(borderRadius: BorderRadius.horizontal(left: Radius.circular(180), right: Radius.circular(180))),
),
child: Text(
key:Key("addNewProfileActual"),
key: Key("addNewProfileActual"),
AppLocalizations.of(context)!.addProfileTitle,
semanticsLabel: AppLocalizations.of(context)!.addProfileTitle,
style: TextStyle(fontWeight: FontWeight.bold),

View File

@ -37,10 +37,6 @@ class _RemoteServerViewState extends State<RemoteServerView> {
@override
void initState() {
super.initState();
var serverInfoState = Provider.of<RemoteServerInfoState>(context, listen: false);
if (serverInfoState.description.isNotEmpty) {
ctrlrDesc.text = serverInfoState.description;
}
}
@override
@ -50,6 +46,11 @@ class _RemoteServerViewState extends State<RemoteServerView> {
@override
Widget build(BuildContext context) {
var serverInfoState = Provider.of<RemoteServerInfoState>(context, listen: false);
if (serverInfoState.description.isNotEmpty) {
ctrlrDesc.text = serverInfoState.description;
}
return Consumer3<ProfileInfoState, RemoteServerInfoState, Settings>(builder: (context, profile, serverInfoState, settings, child) {
return Scaffold(
appBar: AppBar(title: Text(ctrlrDesc.text.isNotEmpty ? ctrlrDesc.text : serverInfoState.onion)),

View File

@ -6,6 +6,7 @@ import 'package:cwtch/models/appstate.dart';
import 'package:cwtch/models/contact.dart';
import 'package:cwtch/models/message.dart';
import 'package:cwtch/models/profile.dart';
import 'package:cwtch/third_party/base32/base32.dart';
import 'package:cwtch/views/contactsview.dart';
import 'package:cwtch/widgets/staticmessagebubble.dart';
import 'package:flutter/material.dart';
@ -180,6 +181,8 @@ class MessageRowState extends State<MessageRow> with SingleTickerProviderStateMi
String imagePath = Provider.of<MessageMetadata>(context).senderImage!;
if (sender != null) {
imagePath = Provider.of<Settings>(context).isExperimentEnabled(ImagePreviewsExperiment) ? sender.imagePath : sender.defaultImagePath;
} else {
imagePath = RandomProfileImage(Provider.of<MessageMetadata>(context).senderHandle);
}
Widget wdgPortrait = GestureDetector(
onTap: !isGroup
@ -388,6 +391,8 @@ void modalShowReplies(
var sender = profile.contactList.findContact(e.getMetadata().senderHandle);
if (sender != null) {
imagePath = showImage ? sender.imagePath : sender.defaultImagePath;
} else {
imagePath = RandomProfileImage(e.getMetadata().senderHandle);
}
if (fromMe) {
@ -453,3 +458,61 @@ void modalShowReplies(
});
});
}
// temporary until we do real picture selection
String RandomProfileImage(String onion) {
var choices = [
"001-centaur",
"002-kraken",
"003-dinosaur",
"004-tree-1",
"005-hand",
"006-echidna",
"007-robot",
"008-mushroom",
"009-harpy",
"010-phoenix",
"011-dragon-1",
"012-devil",
"013-troll",
"014-alien",
"015-minotaur",
"016-madre-monte",
"017-satyr",
"018-karakasakozou",
"019-pirate",
"020-werewolf",
"021-scarecrow",
"022-valkyrie",
"023-curupira",
"024-loch-ness-monster",
"025-tree",
"026-cerberus",
"027-gryphon",
"028-mermaid",
"029-vampire",
"030-goblin",
"031-yeti",
"032-leprechaun",
"033-medusa",
"034-chimera",
"035-elf",
"036-hydra",
"037-cyclops",
"038-pegasus",
"039-narwhal",
"040-woodcutter",
"041-zombie",
"042-dragon",
"043-frankenstein",
"044-witch",
"045-fairy",
"046-genie",
"047-pinocchio",
"048-ghost",
"049-wizard",
"050-unicorn"
];
var encoding = base32.decode(onion.toUpperCase());
return "assets/profiles/" + choices[encoding[33] % choices.length] + ".png";
}

View File

@ -81,7 +81,7 @@ class _RemoteServerRowState extends State<RemoteServerRow> {
settings: RouteSettings(name: "remoteserverview"),
pageBuilder: (bcontext, a1, a2) {
return MultiProvider(
providers: [Provider.value(value: profile), ChangeNotifierProvider(create: (context) => server), Provider.value(value: Provider.of<FlwtchState>(context))],
providers: [ChangeNotifierProvider.value(value: profile), ChangeNotifierProvider.value(value: server), Provider.value(value: Provider.of<FlwtchState>(context))],
child: RemoteServerView(),
);
},

View File

@ -91,16 +91,12 @@ class _ServerRowState extends State<ServerRow> {
}
void _pushEditServer(ServerInfoState server) {
Provider.of<ErrorHandler>(context).reset();
Provider.of<ErrorHandler>(context, listen: false).reset();
Navigator.of(context).push(MaterialPageRoute<void>(
settings: RouteSettings(name: "serveraddedit"),
builder: (BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider<ServerInfoState>(
create: (_) => server,
)
],
providers: [ChangeNotifierProvider.value(value: server)],
child: AddEditServerView(),
);
},