refactor: Move packages into packages/
This commit is contained in:
44
packages/moxxmpp_socket_tcp/.gitignore
vendored
Normal file
44
packages/moxxmpp_socket_tcp/.gitignore
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
migrate_working_dir/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
#.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
**/doc/api/
|
||||
**/ios/Flutter/.last_build_id
|
||||
.dart_tool/
|
||||
.flutter-plugins
|
||||
.flutter-plugins-dependencies
|
||||
.packages
|
||||
.pub-cache/
|
||||
.pub/
|
||||
/build/
|
||||
|
||||
# Symbolication related
|
||||
app.*.symbols
|
||||
|
||||
# Obfuscation related
|
||||
app.*.map.json
|
||||
|
||||
# Android Studio will place build artifacts here
|
||||
/android/app/debug
|
||||
/android/app/profile
|
||||
/android/app/release
|
||||
30
packages/moxxmpp_socket_tcp/.metadata
Normal file
30
packages/moxxmpp_socket_tcp/.metadata
Normal file
@@ -0,0 +1,30 @@
|
||||
# This file tracks properties of this Flutter project.
|
||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||
#
|
||||
# This file should be version controlled.
|
||||
|
||||
version:
|
||||
revision: 18a827f3933c19f51862dde3fa472197683249d6
|
||||
channel: stable
|
||||
|
||||
project_type: app
|
||||
|
||||
# Tracks metadata for the flutter migrate command
|
||||
migration:
|
||||
platforms:
|
||||
- platform: root
|
||||
create_revision: 18a827f3933c19f51862dde3fa472197683249d6
|
||||
base_revision: 18a827f3933c19f51862dde3fa472197683249d6
|
||||
- platform: linux
|
||||
create_revision: 18a827f3933c19f51862dde3fa472197683249d6
|
||||
base_revision: 18a827f3933c19f51862dde3fa472197683249d6
|
||||
|
||||
# User provided section
|
||||
|
||||
# List of Local paths (relative to this file) that should be
|
||||
# ignored by the migrate tool.
|
||||
#
|
||||
# Files that are not part of the templates will be ignored by default.
|
||||
unmanaged_files:
|
||||
- 'lib/main.dart'
|
||||
- 'ios/Runner.xcodeproj/project.pbxproj'
|
||||
3
packages/moxxmpp_socket_tcp/CHANGELOG.md
Normal file
3
packages/moxxmpp_socket_tcp/CHANGELOG.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## 1.0.0
|
||||
|
||||
- Initial version.
|
||||
21
packages/moxxmpp_socket_tcp/LICENSE
Normal file
21
packages/moxxmpp_socket_tcp/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Alexander "PapaTutuWawa"
|
||||
|
||||
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.
|
||||
16
packages/moxxmpp_socket_tcp/README.md
Normal file
16
packages/moxxmpp_socket_tcp/README.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# moxxmpp_socket
|
||||
|
||||
A socket for moxxmpp that implements the connection algorithm as specified by
|
||||
[RFC6210](https://xmpp.org/rfcs/rfc6120.html) and [XEP-0368](https://xmpp.org/extensions/xep-0368.html),
|
||||
while also supporting StartTLS and direct TLS.
|
||||
|
||||
In order to make this package independent of Flutter, I removed DNS SRV resolution from
|
||||
the package. The `TCPSocketWrapper` contains a method called `srvQuery` that can be
|
||||
overridden by the user. It takes the domain to query and a DNSSEC flag and is expected
|
||||
to return the list of SRV records, encoded by `MoxSrvRecord` objects. To perform the
|
||||
resolution, one can use any DNS library. A Flutter plugin implementing SRV resolution
|
||||
is, for example, [moxdns](https://codeberg.org/moxxy/moxdns).
|
||||
|
||||
## License
|
||||
|
||||
See `./LICENSE`.
|
||||
5
packages/moxxmpp_socket_tcp/analysis_options.yaml
Normal file
5
packages/moxxmpp_socket_tcp/analysis_options.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
include: ../analysis_options.yaml
|
||||
|
||||
analyzer:
|
||||
exclude:
|
||||
- "lib/src/generated/*.dart"
|
||||
4
packages/moxxmpp_socket_tcp/lib/moxxmpp_socket_tcp.dart
Normal file
4
packages/moxxmpp_socket_tcp/lib/moxxmpp_socket_tcp.dart
Normal file
@@ -0,0 +1,4 @@
|
||||
library moxxmpp_socket_tcp;
|
||||
|
||||
export 'src/record.dart';
|
||||
export 'src/socket.dart';
|
||||
8
packages/moxxmpp_socket_tcp/lib/src/record.dart
Normal file
8
packages/moxxmpp_socket_tcp/lib/src/record.dart
Normal file
@@ -0,0 +1,8 @@
|
||||
/// A data class to represent a DNS SRV record.
|
||||
class MoxSrvRecord {
|
||||
MoxSrvRecord(this.priority, this.weight, this.target, this.port);
|
||||
final int priority;
|
||||
final int weight;
|
||||
final int port;
|
||||
final String target;
|
||||
}
|
||||
21
packages/moxxmpp_socket_tcp/lib/src/rfc_2782.dart
Normal file
21
packages/moxxmpp_socket_tcp/lib/src/rfc_2782.dart
Normal file
@@ -0,0 +1,21 @@
|
||||
import 'package:moxxmpp_socket_tcp/src/record.dart';
|
||||
|
||||
/// Sorts the SRV records according to priority and weight.
|
||||
int srvRecordSortComparator(MoxSrvRecord a, MoxSrvRecord b) {
|
||||
if (a.priority < b.priority) {
|
||||
return -1;
|
||||
} else {
|
||||
if (a.priority > b.priority) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// a.priority == b.priority
|
||||
if (a.weight < b.weight) {
|
||||
return -1;
|
||||
} else if (a.weight > b.weight) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
305
packages/moxxmpp_socket_tcp/lib/src/socket.dart
Normal file
305
packages/moxxmpp_socket_tcp/lib/src/socket.dart
Normal file
@@ -0,0 +1,305 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:moxxmpp/moxxmpp.dart';
|
||||
import 'package:moxxmpp_socket_tcp/src/record.dart';
|
||||
import 'package:moxxmpp_socket_tcp/src/rfc_2782.dart';
|
||||
|
||||
/// TCP socket implementation for XmppConnection
|
||||
class TCPSocketWrapper extends BaseSocketWrapper {
|
||||
TCPSocketWrapper(this._logData)
|
||||
: _log = Logger('TCPSocketWrapper'),
|
||||
_dataStream = StreamController.broadcast(),
|
||||
_eventStream = StreamController.broadcast(),
|
||||
_secure = false,
|
||||
_ignoreSocketClosure = false;
|
||||
Socket? _socket;
|
||||
bool _ignoreSocketClosure;
|
||||
final StreamController<String> _dataStream;
|
||||
final StreamController<XmppSocketEvent> _eventStream;
|
||||
StreamSubscription<dynamic>? _socketSubscription;
|
||||
|
||||
final Logger _log;
|
||||
final bool _logData;
|
||||
|
||||
bool _secure;
|
||||
|
||||
@override
|
||||
bool isSecure() => _secure;
|
||||
|
||||
@override
|
||||
bool whitespacePingAllowed() => true;
|
||||
|
||||
@override
|
||||
bool managesKeepalives() => false;
|
||||
|
||||
/// Allow the socket to be destroyed by cancelling internal subscriptions.
|
||||
void destroy() {
|
||||
_socketSubscription?.cancel();
|
||||
}
|
||||
|
||||
/// Called on connect to perform a SRV query against [domain]. If [dnssec] is true,
|
||||
/// then DNSSEC validation should be performed.
|
||||
///
|
||||
/// Returns a list of SRV records. If none are available or an error occured, an empty
|
||||
/// list is returned.
|
||||
@visibleForOverriding
|
||||
Future<List<MoxSrvRecord>> srvQuery(String domain, bool dnssec) async {
|
||||
return <MoxSrvRecord>[];
|
||||
}
|
||||
|
||||
bool _onBadCertificate(dynamic certificate, String domain) {
|
||||
_log.fine('Bad certificate: ${certificate.toString()}');
|
||||
//final isExpired = certificate.endValidity.isAfter(DateTime.now());
|
||||
// TODO(Unknown): Either validate the certificate ourselves or use a platform native
|
||||
// hostname verifier (or Dart adds it themselves)
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<bool> _xep368Connect(String domain) async {
|
||||
// TODO(Unknown): Maybe do DNSSEC one day
|
||||
final results = await srvQuery('_xmpps-client._tcp.$domain', false);
|
||||
if (results.isEmpty) {
|
||||
return false;
|
||||
}
|
||||
|
||||
results.sort(srvRecordSortComparator);
|
||||
for (final srv in results) {
|
||||
try {
|
||||
_log.finest('Attempting secure connection to ${srv.target}:${srv.port}...');
|
||||
_ignoreSocketClosure = true;
|
||||
|
||||
// Workaround: We cannot set the SNI directly when using SecureSocket.connect.
|
||||
// instead, we connect using a regular socket and then secure it. This allows
|
||||
// us to set the SNI to whatever we want.
|
||||
final sock = await Socket.connect(
|
||||
srv.target,
|
||||
srv.port,
|
||||
timeout: const Duration(seconds: 5),
|
||||
);
|
||||
_socket = await SecureSocket.secure(
|
||||
sock,
|
||||
host: domain,
|
||||
supportedProtocols: const [ xmppClientALPNId ],
|
||||
onBadCertificate: (cert) => _onBadCertificate(cert, domain),
|
||||
);
|
||||
|
||||
_ignoreSocketClosure = false;
|
||||
_secure = true;
|
||||
_log.finest('Success!');
|
||||
return true;
|
||||
} on SocketException catch(e) {
|
||||
_log.finest('Failure! $e');
|
||||
_ignoreSocketClosure = false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<bool> _rfc6120Connect(String domain) async {
|
||||
// TODO(Unknown): Maybe do DNSSEC one day
|
||||
final results = await srvQuery('_xmpp-client._tcp.$domain', false);
|
||||
results.sort(srvRecordSortComparator);
|
||||
|
||||
for (final srv in results) {
|
||||
try {
|
||||
_log.finest('Attempting connection to ${srv.target}:${srv.port}...');
|
||||
_ignoreSocketClosure = true;
|
||||
_socket = await Socket.connect(
|
||||
srv.target,
|
||||
srv.port,
|
||||
timeout: const Duration(seconds: 5),
|
||||
);
|
||||
|
||||
_ignoreSocketClosure = false;
|
||||
_log.finest('Success!');
|
||||
return true;
|
||||
} on SocketException catch(e) {
|
||||
_log.finest('Failure! $e');
|
||||
_ignoreSocketClosure = false;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return _rfc6120FallbackConnect(domain);
|
||||
}
|
||||
|
||||
/// Connect to [host] with port [port] and returns true if the connection
|
||||
/// was successfully established. Does not setup the streams as this has
|
||||
/// to be done by the caller.
|
||||
Future<bool> _hostPortConnect(String host, int port) async {
|
||||
try {
|
||||
_log.finest('Attempting fallback connection to $host:$port...');
|
||||
_ignoreSocketClosure = true;
|
||||
_socket = await Socket.connect(
|
||||
host,
|
||||
port,
|
||||
timeout: const Duration(seconds: 5),
|
||||
);
|
||||
_log.finest('Success!');
|
||||
return true;
|
||||
} on SocketException catch(e) {
|
||||
_log.finest('Failure! $e');
|
||||
_ignoreSocketClosure = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Connect to [domain] using the default C2S port of XMPP. Returns
|
||||
/// true if the connection was successful. Does not setup the streams
|
||||
/// as [_rfc6120FallbackConnect] should only be called from
|
||||
/// [_rfc6120Connect], which already sets the streams up on a successful
|
||||
/// connection.
|
||||
Future<bool> _rfc6120FallbackConnect(String domain) async {
|
||||
return _hostPortConnect(domain, 5222);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> secure(String domain) async {
|
||||
if (_secure) {
|
||||
_log.warning('Connection is already marked as secure. Doing nothing');
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_socket == null) {
|
||||
_log.severe('Failed to secure socket since _socket is null');
|
||||
return false;
|
||||
}
|
||||
|
||||
_ignoreSocketClosure = true;
|
||||
|
||||
try {
|
||||
_socket = await SecureSocket.secure(
|
||||
_socket!,
|
||||
supportedProtocols: const [ xmppClientALPNId ],
|
||||
onBadCertificate: (cert) => _onBadCertificate(cert, domain),
|
||||
);
|
||||
|
||||
_secure = true;
|
||||
_ignoreSocketClosure = false;
|
||||
_setupStreams();
|
||||
return true;
|
||||
} on SocketException {
|
||||
_ignoreSocketClosure = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void _setupStreams() {
|
||||
if (_socket == null) {
|
||||
_log.severe('Failed to setup streams as _socket is null');
|
||||
return;
|
||||
}
|
||||
|
||||
_socketSubscription = _socket!.listen(
|
||||
(List<int> event) {
|
||||
final data = utf8.decode(event);
|
||||
if (_logData) {
|
||||
_log.finest('<== $data');
|
||||
}
|
||||
_dataStream.add(data);
|
||||
},
|
||||
onError: (Object error) {
|
||||
_log.severe(error.toString());
|
||||
_eventStream.add(XmppSocketErrorEvent(error));
|
||||
},
|
||||
);
|
||||
// ignore: implicit_dynamic_parameter
|
||||
_socket!.done.then((_) {
|
||||
if (!_ignoreSocketClosure) {
|
||||
_eventStream.add(XmppSocketClosureEvent());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> connect(String domain, { String? host, int? port }) async {
|
||||
_ignoreSocketClosure = false;
|
||||
_secure = false;
|
||||
|
||||
// Connection order:
|
||||
// 1. host:port, if given
|
||||
// 2. XEP-0368
|
||||
// 3. RFC 6120
|
||||
// 4. RFC 6120 fallback
|
||||
|
||||
if (host != null && port != null) {
|
||||
_log.finest('Specific host and port given');
|
||||
if (await _hostPortConnect(host, port)) {
|
||||
_setupStreams();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (await _xep368Connect(domain)) {
|
||||
_setupStreams();
|
||||
return true;
|
||||
}
|
||||
|
||||
// NOTE: _rfc6120Connect already attempts the fallback
|
||||
if (await _rfc6120Connect(domain)) {
|
||||
_setupStreams();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@override
|
||||
void close() {
|
||||
if (_socketSubscription != null) {
|
||||
_log.finest('Closing socket subscription');
|
||||
_socketSubscription!.cancel();
|
||||
}
|
||||
|
||||
if (_socket == null) {
|
||||
_log.warning('Failed to close socket since _socket is null');
|
||||
return;
|
||||
}
|
||||
|
||||
_ignoreSocketClosure = true;
|
||||
try {
|
||||
_socket!.close();
|
||||
} catch(e) {
|
||||
_log.warning('Closing socket threw exception: $e');
|
||||
}
|
||||
_ignoreSocketClosure = false;
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<String> getDataStream() => _dataStream.stream.asBroadcastStream();
|
||||
|
||||
@override
|
||||
Stream<XmppSocketEvent> getEventStream() => _eventStream.stream.asBroadcastStream();
|
||||
|
||||
@override
|
||||
void write(Object? data, { String? redact }) {
|
||||
if (_socket == null) {
|
||||
_log.severe('Failed to write to socket as _socket is null');
|
||||
return;
|
||||
}
|
||||
|
||||
if (data != null && data is String && _logData) {
|
||||
if (redact != null) {
|
||||
_log.finest('**> $redact');
|
||||
} else {
|
||||
_log.finest('==> $data');
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
_socket!.write(data);
|
||||
} on SocketException catch (e) {
|
||||
_log.severe(e);
|
||||
_eventStream.add(XmppSocketErrorEvent(e));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void prepareDisconnect() {
|
||||
_ignoreSocketClosure = true;
|
||||
}
|
||||
}
|
||||
20
packages/moxxmpp_socket_tcp/pubspec.yaml
Normal file
20
packages/moxxmpp_socket_tcp/pubspec.yaml
Normal file
@@ -0,0 +1,20 @@
|
||||
name: moxxmpp_socket_tcp
|
||||
description: A socket for moxxmpp using TCP that implements the RFC6120 connection algorithm and XEP-0368
|
||||
version: 0.1.0
|
||||
homepage: https://codeberg.org/moxxy/moxxmpp
|
||||
publish_to: https://git.polynom.me/api/packages/Moxxy/pub
|
||||
|
||||
environment:
|
||||
sdk: '>=2.18.0 <3.0.0'
|
||||
|
||||
dependencies:
|
||||
logging: ^1.0.2
|
||||
meta: ^1.6.0
|
||||
moxxmpp:
|
||||
hosted: https://git.polynom.me/api/packages/Moxxy/pub
|
||||
version: 0.1.0
|
||||
|
||||
dev_dependencies:
|
||||
lints: ^2.0.0
|
||||
test: ^1.16.0
|
||||
very_good_analysis: ^3.0.1
|
||||
4
packages/moxxmpp_socket_tcp/pubspec_overrides.yaml
Normal file
4
packages/moxxmpp_socket_tcp/pubspec_overrides.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
# melos_managed_dependency_overrides: moxxmpp
|
||||
dependency_overrides:
|
||||
moxxmpp:
|
||||
path: ../moxxmpp
|
||||
30
packages/moxxmpp_socket_tcp/test/widget_test.dart
Normal file
30
packages/moxxmpp_socket_tcp/test/widget_test.dart
Normal file
@@ -0,0 +1,30 @@
|
||||
// This is a basic Flutter widget test.
|
||||
//
|
||||
// To perform an interaction with a widget in your test, use the WidgetTester
|
||||
// utility in the flutter_test package. For example, you can send tap and scroll
|
||||
// gestures. You can also use WidgetTester to find child widgets in the widget
|
||||
// tree, read text, and verify that the values of widget properties are correct.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:moxxmpp_socket/main.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
||||
// Build our app and trigger a frame.
|
||||
await tester.pumpWidget(const MyApp());
|
||||
|
||||
// Verify that our counter starts at 0.
|
||||
expect(find.text('0'), findsOneWidget);
|
||||
expect(find.text('1'), findsNothing);
|
||||
|
||||
// Tap the '+' icon and trigger a frame.
|
||||
await tester.tap(find.byIcon(Icons.add));
|
||||
await tester.pump();
|
||||
|
||||
// Verify that our counter has incremented.
|
||||
expect(find.text('0'), findsNothing);
|
||||
expect(find.text('1'), findsOneWidget);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user