diff --git a/.fvm/fvm_config.json b/.fvm/fvm_config.json index af49d10..26eda9b 100644 --- a/.fvm/fvm_config.json +++ b/.fvm/fvm_config.json @@ -1,4 +1,4 @@ { - "flutterSdkVersion": "3.0.4", + "flutterSdkVersion": "3.3.5", "flavors": {} } \ No newline at end of file diff --git a/assets/keys/certificate.cer b/assets/keys/certificate.cer new file mode 100644 index 0000000..782248a --- /dev/null +++ b/assets/keys/certificate.cer @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIIEFTCCAv2gAwIBAgIUczUTebcj+DOcv4SFWOJR+kJt8k8wDQYJKoZIhvcNAQEL +BQAwgagxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQH +Ew1TYW4gRnJhbmNpc2NvMRkwFwYDVQQKExBDbG91ZGZsYXJlLCBJbmMuMRswGQYD +VQQLExJ3d3cuY2xvdWRmbGFyZS5jb20xNDAyBgNVBAMTK01hbmFnZWQgQ0EgOWVi +NzNlOWI3NTQzMWJkNTZiZmQ2MjQ4YmJhMzMxMDQwHhcNMjIwOTE0MTkxOTAwWhcN +MzcwOTEwMTkxOTAwWjAiMQswCQYDVQQGEwJVUzETMBEGA1UEAxMKQ2xvdWRmbGFy +ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAN5BLR5zXpq8X4uRd6yD +nYCk0c735kFtuvkzJv/dXroWDQCz5pfHHjmXW0mBtGUV4CyowKymVakjQd6iSeDP +SVkUqMjXbgQ2wYMnlJF3hJKREe9RcEa5vJOu/tJFuUDf/fMWlQJSbH1MyoC1uBRX +wSE96oxZU6EDvluroq7roZX3oaK36EwK2AKCRxlzjjS3FnMxpH1nNh+EdWgsNqi2 ++Qmr3+DKhZMgR6FsZ8XlwnJVMS8pJxDLwN11HMXpeorg9BblYBRR5UHQoqIgM2qD +jZiprnpEfn2Kp1Qh6cjwfy49BrC4rBQJSnEZJGCkpVg0+OkU/rI/GdQSPdIfbZ+A +VzUCAwEAAaOBuzCBuDATBgNVHSUEDDAKBggrBgEFBQcDAjAMBgNVHRMBAf8EAjAA +MB0GA1UdDgQWBBRvDaKkJ7HxmZSSxXyguiOMw37JYjAfBgNVHSMEGDAWgBQm6tVr +jXZOViYsrBOCio26eL+hoDBTBgNVHR8ETDBKMEigRqBEhkJodHRwOi8vY3JsLmNs +b3VkZmxhcmUuY29tL2Y2ZjRhMzhlLTI5MjgtNGQxNy05M2I4LWZhNWU4ZTIyNjIw +Mi5jcmwwDQYJKoZIhvcNAQELBQADggEBACWzu7Q7CyW/ObuUisTGKhVTwbd0pNs2 +vGTDj5Ue1Sn2+9Vc5FuQsENS05S74039hiQKV0KYRZMxsAqbcPayFbqEsqsw7kmd +Iz5E0n4iQGhKUukWBCd9VcSwaKhZans8ZHpUzIUuUMVD3mLkZClAnj/7PZk+hje2 +JhTaf4mpKM1qjnXa2ImFpjBxINrO9jDwD9/dIODfyFFT4qR5QcJOZeCEoiR4tQmq +ybG26DzIjk55HaoaP46gN/vsfmXz2t0SqVmjFiT7+18PwQW4QMkzpq0keEJlqJIN +lAYEiYblRMliNqo1ysjL8M336n9zw8LisI7SSRv4Ci/LENTuM0xXnm0= +-----END CERTIFICATE----- \ No newline at end of file diff --git a/assets/keys/private.key b/assets/keys/private.key new file mode 100644 index 0000000..66327ec --- /dev/null +++ b/assets/keys/private.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDeQS0ec16avF+L +kXesg52ApNHO9+ZBbbr5Myb/3V66Fg0As+aXxx45l1tJgbRlFeAsqMCsplWpI0He +okngz0lZFKjI124ENsGDJ5SRd4SSkRHvUXBGubyTrv7SRblA3/3zFpUCUmx9TMqA +tbgUV8EhPeqMWVOhA75bq6Ku66GV96Git+hMCtgCgkcZc440txZzMaR9ZzYfhHVo +LDaotvkJq9/gyoWTIEehbGfF5cJyVTEvKScQy8DddRzF6XqK4PQW5WAUUeVB0KKi +IDNqg42Yqa56RH59iqdUIenI8H8uPQawuKwUCUpxGSRgpKVYNPjpFP6yPxnUEj3S +H22fgFc1AgMBAAECggEAZCUGCYR+ikZ63s6LGAat/3tEtndpHu4so4Dy/7NlrX+m +GDz3mEg2TEjw7ywsdqfmvPjE6IaCbpXeZkzF8TA1opf2fZjkj62EgG5jOTCbYddQ +N3+Akb27ROhDFcgo8xx6tv/j4In3LHZfgKNg8HmiIjJd7yOI1TJ0oaWyxhKSWpnF +GeGtBSwKJEICTKalAzR8TclEYIhFHffdHrvUwoj4Yge03tF3HjLOi+cWUx6e6NEt +KcuulsV6lQM028KKGdumVX0xxTdX4hmR2ygjKM6SzIL3XmAcX6NkFC+3ub+sVjCz +cX5ouPHsCT2/M1bu1w0g9ybChVaH4ZISJyoDHFaeuQKBgQDwNiZzRAWKSin7yAJS +m3R1dOWS8oYwWJKXck/tPfpt+YXqyUfQ/u5HNTtfn5DfJLlI5/AfBTT2+X/w4w8v +ytIVefLS7+hU0CZVIT3/Y82ky/N5qwGN12TXylCtJJC8w64dNG4ULfNRdNGnwaoq +AHB8YxKNl9HMBFKFYJMQuhWoRwKBgQDs3OEDyZxpryd/2PNF4a/Lige8o3n3fQF6 +umYzNBI4nFzBhe9vNhVr8tgMuaoNql5ApKJbt0okQOAftM6xodtfh+KxdmDQ8oLK +BVS4TytuibOFF3ZyFOOzxBxHHnm/sgAZgstl0GPZBVlb15qrNspUS9MYADg8+aN/ +AiX14uM+owKBgQDEakK+dZxiG4qAyma7zUlI0bD2m0CGP6Z+F4arYsZnLmUGOldy +2UFVEH3gDsWS8KBgsOZzNvq8B/9JpSBB4AIwdWrMeEbtMtZlPa1IKv94BRytG9tF +dB6NJG0bZo7DCu5QCxMHhRs0O+VC2uSdO4a+7vO4u69ctxwS24jlYINc+QKBgQCb +t5ZilA+1VwZDwZAld/rHeAYgGOUdNFxdn9+CeBAmkX1VaMUBOvAYWL16mDDY7REr +tFBctYITlWcC0S41j3AWPNJm0qlRlK0xPRH2XW3zLKoNrGAdHeiYjSv+AWYPBWmV +W+x2EesiiKa3f2Xae5nGk1bC55oRVBkNbY8hOLkmuwKBgQDHXFdCQFpG+Rg3XI69 +RH2aEYqhqZNx6vOrN1BPKRJo85gLb27w2FP5a3v440YdJAoN+BGpk+CzrGtqj5SC +kaRWqlDC6zLBQkTmp1DhgR8/OJcg3eYM8ALLSq3DdwUTHQU+cH5mCixmrJiCc1Eh +K4WUZQ9rWiFnjy0W3gNwhDY67A== +-----END PRIVATE KEY----- \ No newline at end of file diff --git a/lib/lime.dart b/lib/lime.dart index 711350c..6fa72e8 100644 --- a/lib/lime.dart +++ b/lib/lime.dart @@ -2,7 +2,8 @@ library lime; export 'src/protocol/guid.dart' show guid; export 'src/protocol/extensions/string.extension.dart' show StringExtension; -export 'src/protocol/extensions/notification_event.extension.dart' show NotificationEventExtension; +export 'src/protocol/extensions/notification_event.extension.dart' + show NotificationEventExtension; export 'src/protocol/command.dart' show Command; export 'src/protocol/document.dart' show Document; export 'src/protocol/envelope.dart' show Envelope; @@ -16,7 +17,8 @@ export 'src/protocol/reason.dart' show Reason; export 'src/protocol/enums/command_method.enum.dart' show CommandMethod; export 'src/protocol/enums/command_status.enum.dart' show CommandStatus; export 'src/protocol/enums/notification_event.enum.dart' show NotificationEvent; -export 'src/protocol/enums/session_compression.enum.dart' show SessionCompression; +export 'src/protocol/enums/session_compression.enum.dart' + show SessionCompression; export 'src/protocol/enums/session_encryption.enum.dart' show SessionEncryption; export 'src/protocol/enums/session_state.enum.dart' show SessionState; export 'src/protocol/enums/presence_status.enum.dart' show PresenceStatus; @@ -30,13 +32,22 @@ export 'src/protocol/identity.dart' show Identity; export 'src/protocol/session.dart' show Session; export 'src/protocol/presence.dart' show Presence; export 'src/protocol/security/authentication.dart' show Authentication; -export 'src/protocol/security/guest_authentication.dart' show GuestAuthentication; +export 'src/protocol/security/guest_authentication.dart' + show GuestAuthentication; export 'src/protocol/security/key_authentication.dart' show KeyAuthentication; -export 'src/protocol/security/external_authentication.dart' show ExternalAuthentication; -export 'src/protocol/security/plain_authentication.dart' show PlainAuthentication; -export 'src/protocol/security/transport_authentication.dart' show TransportAuthentication; -export 'src/protocol/security/enums/authentication_scheme.enum.dart' show AuthenticationScheme; +export 'src/protocol/security/external_authentication.dart' + show ExternalAuthentication; +export 'src/protocol/security/plain_authentication.dart' + show PlainAuthentication; +export 'src/protocol/security/transport_authentication.dart' + show TransportAuthentication; +export 'src/protocol/security/enums/authentication_scheme.enum.dart' + show AuthenticationScheme; +export 'package:lime/src/protocol/extensions/authentication_schema.extension.dart' + show AuthenticationSchemaExtension; export 'src/protocol/client/client_channel.dart' show ClientChannel; export 'src/protocol/network/transport.dart' show Transport; export 'src/protocol/network/web_socket_transport.dart' show WebSocketTransport; -export 'src/protocol/network/lime.exception.dart' show LimeException; +export 'src/protocol/exceptions/lime.exception.dart' show LimeException; +export 'src/protocol/exceptions/insecure_socket.exception.dart' + show InsecureSocketException; diff --git a/lib/src/protocol/client/channel.dart b/lib/src/protocol/client/channel.dart index 561113a..81e8b5f 100644 --- a/lib/src/protocol/client/channel.dart +++ b/lib/src/protocol/client/channel.dart @@ -53,7 +53,7 @@ abstract class Channel { state = SessionState.isNew; // Start listening to the stream - transport.onEvelope?.stream.listen( + transport.onEnvelope?.stream.listen( (event) { if (event.containsKey('state')) { logger.info('Received envelope is a Session'); @@ -63,7 +63,10 @@ abstract class Channel { logger.info('Received envelope is a Command'); final command = Command.fromJson(event); - if (autoReplyPings && command.uri == '/ping' && command.method == CommandMethod.get && isForMe(command)) { + if (autoReplyPings && + command.uri == '/ping' && + command.method == CommandMethod.get && + isForMe(command)) { logger.info('Auto reply ping..'); final commandSend = Command( @@ -93,6 +96,11 @@ abstract class Channel { onError: (e) => logger.shout('stream error: $e'), onDone: () => logger.info('stream done'), ); + + //Start listening to onConnectionDone Stream + transport.onConnectionDone?.stream.listen((bool closed) { + onCloseConnection(closed); + }); } /// Open the connection with the transport @@ -152,7 +160,10 @@ abstract class Channel { bool isForMe(Envelope envelope) { return envelope.to == null || envelope.to.toString() == localNode?.toString() || - localNode?.toString().substring(0, envelope.to.toString().length).toLowerCase() == + localNode + ?.toString() + .substring(0, envelope.to.toString().length) + .toLowerCase() == envelope.to.toString().toLowerCase(); } @@ -167,4 +178,7 @@ abstract class Channel { /// A function that will be executed when an [Envelope] of type [Command] is received by the [Transport] layer void onCommand(Command command) {} + + /// A function that will be executed when the server close the connection + void onCloseConnection(bool closed) {} } diff --git a/lib/src/protocol/client/client_channel.dart b/lib/src/protocol/client/client_channel.dart index 23724ab..e023b1d 100644 --- a/lib/src/protocol/client/client_channel.dart +++ b/lib/src/protocol/client/client_channel.dart @@ -38,6 +38,9 @@ class ClientChannel extends Channel { /// Exposes a [StreamController] to allow listening when a new [Session] with [SessionState.failed] type is received by the channel final onSessionFailed = StreamController(); + /// Exposes a [StreamController] to allow listening when the server closes the connection + final onConnectionDone = StreamController(); + /// A function that will be completed when a [Session] of type [SessionState.authenticating] is received late Function(Session) _onSessionAuthenticating; @@ -51,7 +54,8 @@ class ClientChannel extends Channel { late Function(Session) _onSessionFinished; /// Establishes the session - Future establishSession(String identity, String instance, Authentication authentication) async { + Future establishSession( + String identity, String instance, Authentication authentication) async { Session session = await startNewSession(); session = await authenticateSession(identity, instance, authentication); @@ -144,7 +148,8 @@ class ClientChannel extends Channel { } /// Send a [Session] type [Envelope] with state [SessionState.authenticating] to start the authenticate - Future authenticateSession(String identity, String instance, Authentication authentication) async { + Future authenticateSession( + String identity, String instance, Authentication authentication) async { if (state != SessionState.authenticating) { throw Exception('Cannot authenticate a session in the $state state.'); } @@ -237,4 +242,9 @@ class ClientChannel extends Channel { void onMessage(Message message) { onReceiveMessage.sink.add(message); } + + @override + void onCloseConnection(bool closed) { + onConnectionDone.sink.add(closed); + } } diff --git a/lib/src/protocol/command.dart b/lib/src/protocol/command.dart index 6b7341e..0202903 100644 --- a/lib/src/protocol/command.dart +++ b/lib/src/protocol/command.dart @@ -26,7 +26,7 @@ class Command extends Envelope { dynamic resource; /// Action to be taken to the resource - CommandMethod? method; + CommandMethod method; /// Indicates the status of the action taken to the resource CommandStatus? status = CommandStatus.pending; @@ -42,7 +42,7 @@ class Command extends Envelope { final Node? pp, final Map? metadata, this.uri, - this.method, + required this.method, this.reason, this.resource, this.status, @@ -54,6 +54,7 @@ class Command extends Envelope { Map command = {}; command[Envelope.idKey] = id; + command[methodKey] = describeEnum(method); if (from != null) { command[Envelope.fromKey] = from.toString(); @@ -71,10 +72,6 @@ class Command extends Envelope { command[Envelope.metadataKey] = metadata; } - if (method != null) { - command[methodKey] = describeEnum(method!); - } - if (status != null) { command[statusKey] = describeEnum(status!); } @@ -108,6 +105,9 @@ class Command extends Envelope { to: envelope.to, pp: envelope.pp, metadata: envelope.metadata, + method: json[methodKey] != null + ? CommandMethod.values.firstWhere((e) => describeEnum(e) == json[methodKey]) + : CommandMethod.unknown, ); if (json.containsKey(reasonKey)) { @@ -115,13 +115,7 @@ class Command extends Envelope { } if (json.containsKey(statusKey)) { - command.status = CommandStatus.values - .firstWhere((e) => describeEnum(e) == json[statusKey]); - } - - if (json.containsKey(methodKey)) { - command.method = CommandMethod.values - .firstWhere((e) => describeEnum(e) == json[methodKey]); + command.status = CommandStatus.values.firstWhere((e) => describeEnum(e) == json[statusKey]); } if (json.containsKey(uriKey)) { diff --git a/lib/src/protocol/enums/command_method.enum.dart b/lib/src/protocol/enums/command_method.enum.dart index fff1ea4..0567970 100644 --- a/lib/src/protocol/enums/command_method.enum.dart +++ b/lib/src/protocol/enums/command_method.enum.dart @@ -23,4 +23,6 @@ enum CommandMethod { /// Merges the resource document with an existing one. If the resource doesn't exists, it is created. merge, + + unknown, } diff --git a/lib/src/protocol/enums/notification_event.enum.dart b/lib/src/protocol/enums/notification_event.enum.dart index 281e608..aaa9273 100644 --- a/lib/src/protocol/enums/notification_event.enum.dart +++ b/lib/src/protocol/enums/notification_event.enum.dart @@ -1,19 +1,10 @@ -enum NotificationEvent { - /// A problem occurred during the processing of the message. - failed, +export 'package:lime/src/protocol/extensions/notification_event.extension.dart'; +enum NotificationEvent { /// The message was received and accepted by the server. /// This event is similar to the but is emitted by an intermediate node (hop) and not by the message's final destination. accepted, - /// The message format was validated by the server. - @Deprecated("This specific event should not be sent anymore") - validated, - - /// The dispatch of the message was authorized by the server. - @Deprecated("This specific event should not be sent anymore") - authorized, - /// The message was dispatched to the destination by the server. /// This event is similar to the but is emitted by an intermediate node (hop) and not by the message's final destination. dispatched, @@ -24,5 +15,16 @@ enum NotificationEvent { /// The node has consumed the content of the message. consumed, + /// A problem occurred during the processing of the message. + failed, + + /// The message format was validated by the server. + @Deprecated("This specific event should not be sent anymore") + validated, + + /// The dispatch of the message was authorized by the server. + @Deprecated("This specific event should not be sent anymore") + authorized, + unknown, } diff --git a/lib/src/protocol/envelope.dart b/lib/src/protocol/envelope.dart index fce52d6..964798d 100644 --- a/lib/src/protocol/envelope.dart +++ b/lib/src/protocol/envelope.dart @@ -1,3 +1,4 @@ +import 'guid.dart'; import 'media_type.dart'; import 'node.dart'; @@ -34,7 +35,7 @@ class Envelope { /// Allows converting a collection of key/value pairs, [Map] to a [Envelope] object factory Envelope.fromJson(Map json) { return Envelope( - id: json.containsKey(idKey) ? json[idKey] : null, + id: json.containsKey(idKey) ? json[idKey] : guid(), from: json.containsKey(fromKey) ? Node.parse(json[fromKey]) : null, to: json.containsKey(toKey) ? Node.parse(json[toKey]) : null, pp: json.containsKey(ppKey) ? Node.parse(json[ppKey]) : null, diff --git a/lib/src/protocol/exceptions/insecure_socket.exception.dart b/lib/src/protocol/exceptions/insecure_socket.exception.dart new file mode 100644 index 0000000..ee10f75 --- /dev/null +++ b/lib/src/protocol/exceptions/insecure_socket.exception.dart @@ -0,0 +1,9 @@ +class InsecureSocketException implements Exception { + String message; + + InsecureSocketException(this.message); + + Map toJson() => { + 'message': message, + }; +} diff --git a/lib/src/protocol/network/lime.exception.dart b/lib/src/protocol/exceptions/lime.exception.dart similarity index 100% rename from lib/src/protocol/network/lime.exception.dart rename to lib/src/protocol/exceptions/lime.exception.dart diff --git a/lib/src/protocol/extensions/authentication_schema.extension.dart b/lib/src/protocol/extensions/authentication_schema.extension.dart new file mode 100644 index 0000000..1759751 --- /dev/null +++ b/lib/src/protocol/extensions/authentication_schema.extension.dart @@ -0,0 +1,8 @@ +import 'package:flutter/foundation.dart'; +import 'package:lime/lime.dart'; + +extension AuthenticationSchemaExtension on AuthenticationScheme { + AuthenticationScheme getValue(String? value) => + AuthenticationScheme.values.firstWhere((e) => describeEnum(e) == value, + orElse: () => AuthenticationScheme.unknown); +} diff --git a/lib/src/protocol/extensions/notification_event.extension.dart b/lib/src/protocol/extensions/notification_event.extension.dart index 8f9f5c8..5cd641c 100644 --- a/lib/src/protocol/extensions/notification_event.extension.dart +++ b/lib/src/protocol/extensions/notification_event.extension.dart @@ -3,6 +3,19 @@ import 'package:flutter/foundation.dart'; import '../enums/notification_event.enum.dart'; extension NotificationEventExtension on NotificationEvent { - NotificationEvent getValue(String value) => + NotificationEvent getValue(String? value) => NotificationEvent.values.firstWhere((e) => describeEnum(e) == value, orElse: () => NotificationEvent.unknown); + + bool isLowerThan(NotificationEvent? other) { + const events = { + NotificationEvent.accepted: 0, + NotificationEvent.dispatched: 1, + NotificationEvent.received: 2, + NotificationEvent.consumed: 3, + NotificationEvent.failed: 4, + NotificationEvent.unknown: 99, + }; + + return (events[this] ?? 99) < (events[other ?? NotificationEvent.unknown] ?? 99); + } } diff --git a/lib/src/protocol/identity.dart b/lib/src/protocol/identity.dart index f6ff4db..530d7bb 100644 --- a/lib/src/protocol/identity.dart +++ b/lib/src/protocol/identity.dart @@ -38,14 +38,13 @@ class Identity implements IIdentity { } /// Tries to parse the string to a valid Identity. - static bool tryParse( - String s, + static Identity? tryParse( + String? s, ) { try { - parse(s); - return true; + return parse(s); } catch (e) { - return false; + return null; } } diff --git a/lib/src/protocol/network/envelope_listener.dart b/lib/src/protocol/network/envelope_listener.dart index 7575990..18ac0ee 100644 --- a/lib/src/protocol/network/envelope_listener.dart +++ b/lib/src/protocol/network/envelope_listener.dart @@ -2,5 +2,8 @@ import 'dart:async'; class EnvelopeListener { /// A [StreamController] to allow listening when a new envelope is received by the websocket - StreamController>? onEvelope; + StreamController>? onEnvelope; + + /// A [StreamController] to allow listening when server close the connection + StreamController? onConnectionDone; } diff --git a/lib/src/protocol/network/transport.dart b/lib/src/protocol/network/transport.dart index 22cdd46..c60a0b2 100644 --- a/lib/src/protocol/network/transport.dart +++ b/lib/src/protocol/network/transport.dart @@ -9,7 +9,10 @@ import 'envelope_listener.dart'; /// Base class for the supported transport types abstract class Transport extends EnvelopeListener { /// Opens the transport connection with the specified Uri. - Future open(final String uri); + Future open( + final String uri, { + final bool useMtls = false, + }); /// Closes the connection Future close(); diff --git a/lib/src/protocol/network/web_socket_transport.dart b/lib/src/protocol/network/web_socket_transport.dart index a7b8d5c..f522be8 100644 --- a/lib/src/protocol/network/web_socket_transport.dart +++ b/lib/src/protocol/network/web_socket_transport.dart @@ -1,9 +1,13 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; +import 'package:flutter/services.dart'; + +import '../../utils/lime.utils.dart'; import '../enums/session_encryption.enum.dart'; import '../enums/session_compression.enum.dart'; import '../envelope.dart'; +import '../exceptions/insecure_socket.exception.dart'; import 'transport.dart'; import 'package:simple_logger/simple_logger.dart'; @@ -11,9 +15,11 @@ import 'package:pretty_json/pretty_json.dart'; /// Allows websocket communication based a Transport base class class WebSocketTransport implements Transport { - StreamController>? stream = + StreamController>? onEnvelopeStream = StreamController>(); + StreamController? onConnectionDoneStream = StreamController(); + /// A websocket for communication WebSocket? socket; @@ -34,7 +40,23 @@ class WebSocketTransport implements Transport { } @override - Future open(final String uri) async { + Future open( + final String uri, { + final bool useMtls = false, + }) async { + HttpClient? customClient; + + if (useMtls) { + List keyBytes = await _getKeyBytes(); + List certificateChainBytes = await _getCertificateChainBytes(); + + final context = SecurityContext(withTrustedRoots: true); + context.usePrivateKeyBytes(keyBytes); + context.useCertificateChainBytes(certificateChainBytes); + + customClient = HttpClient(context: context); + } + if (uri.contains('wss://')) { encryption = SessionEncryption.tls; } else { @@ -43,33 +65,45 @@ class WebSocketTransport implements Transport { compression = SessionCompression.none; - // connect to the socket server - socket = await WebSocket.connect(uri, protocols: ['lime']); - logger.info('Connected to: $uri'); - - // listen for responses from the server - socket?.listen( - // handle data from the server - (data) { - final response = jsonDecode(data); - - logger.info('Envelope received: \n' + prettyJson(response, indent: 2)); - - onEvelope?.add(response); - }, - - // handle errors - onError: (error) { - logger.shout('Error: $error'); - socket?.close(); - }, - - // handle server ending connection - onDone: () { - logger.warning('Server closed the connection.'); - socket?.close(); - }, - ); + try { + // connect to the socket server + socket = await WebSocket.connect(uri, customClient: customClient); + logger.info('Connected to: $uri'); + + // listen for responses from the server + socket?.listen( + // handle data from the server + (data) { + final response = jsonDecode(data); + + logger.info( + 'Envelope received: $uri \n' + prettyJson(response, indent: 2)); + + onEnvelope?.add(response); + }, + + // handle errors + onError: (error) { + logger.shout('Error: $error'); + socket?.close(); + }, + + // handle server ending connection + onDone: () { + logger.warning('Server closed the connection.'); + onConnectionDone?.add(true); + socket?.close(); + }, + ); + } on WebSocketException catch (e) { + if (e.message.endsWith('was not upgraded to websocket')) { + throw InsecureSocketException( + 'This connection is not secure. Please contact the system administrator.'); + } + } on HandshakeException { + throw InsecureSocketException( + 'This connection is not secure. Please contact the system administrator.'); + } } @override @@ -97,11 +131,19 @@ class WebSocketTransport implements Transport { } @override - get onEvelope => stream; + get onEnvelope => onEnvelopeStream; @override - set onEvelope(StreamController>? _onEvelope) { - stream = _onEvelope; + set onEnvelope(StreamController>? _onEnvelope) { + onEnvelopeStream = _onEnvelope; + } + + @override + get onConnectionDone => onConnectionDoneStream; + + @override + set onConnectionDone(StreamController? _onConnectionDone) { + onConnectionDoneStream = _onConnectionDone; } @override @@ -109,4 +151,18 @@ class WebSocketTransport implements Transport { @override SessionEncryption? encryption; + + Future> _getKeyBytes() async { + return (await rootBundle + .load('packages/${LimeUtils.packageName}/assets/keys/private.key')) + .buffer + .asInt8List(); + } + + Future> _getCertificateChainBytes() async { + return (await rootBundle.load( + 'packages/${LimeUtils.packageName}/assets/keys/certificate.cer')) + .buffer + .asInt8List(); + } } diff --git a/lib/src/protocol/security/authentication.dart b/lib/src/protocol/security/authentication.dart index c519c75..1922910 100644 --- a/lib/src/protocol/security/authentication.dart +++ b/lib/src/protocol/security/authentication.dart @@ -5,5 +5,5 @@ abstract class Authentication { final AuthenticationScheme _scheme; AuthenticationScheme get scheme => _scheme; - Authentication(final this._scheme); + Authentication(this._scheme); } diff --git a/lib/src/protocol/security/enums/authentication_scheme.enum.dart b/lib/src/protocol/security/enums/authentication_scheme.enum.dart index 61643ec..068be38 100644 --- a/lib/src/protocol/security/enums/authentication_scheme.enum.dart +++ b/lib/src/protocol/security/enums/authentication_scheme.enum.dart @@ -1,3 +1,5 @@ +export 'package:lime/src/protocol/extensions/authentication_schema.extension.dart'; + /// Defines the valid authentication schemes values. enum AuthenticationScheme { /// The server doesn't requires a client credential, and provides a temporary identity to the node. @@ -15,5 +17,7 @@ enum AuthenticationScheme { key, /// Third-party authentication. - external + external, + + unknown, } diff --git a/lib/src/protocol/session.dart b/lib/src/protocol/session.dart index 106837f..af38021 100644 --- a/lib/src/protocol/session.dart +++ b/lib/src/protocol/session.dart @@ -7,6 +7,7 @@ import 'security/enums/authentication_scheme.enum.dart'; import 'security/external_authentication.dart'; import 'security/key_authentication.dart'; import 'security/authentication.dart'; +import 'security/plain_authentication.dart'; import 'enums/session_compression.enum.dart'; import 'enums/session_encryption.enum.dart'; import 'enums/session_state.enum.dart'; @@ -117,17 +118,18 @@ class Session extends Envelope { if (authentication != null) { if (authentication is KeyAuthentication) { - KeyAuthentication keyAuth = authentication as KeyAuthentication; + final keyAuth = authentication as KeyAuthentication; session[authenticationKey] = {"key": keyAuth.key}; - } - if (authentication is ExternalAuthentication) { - ExternalAuthentication externalAuth = - authentication as ExternalAuthentication; + } else if (authentication is ExternalAuthentication) { + final externalAuth = authentication as ExternalAuthentication; session[authenticationKey] = { "token": externalAuth.token, "issuer": externalAuth.issuer, "scheme": describeEnum(externalAuth.scheme) }; + } else if (authentication is PlainAuthentication) { + final plainAuth = authentication as PlainAuthentication; + session[authenticationKey] = {'password': plainAuth.password}; } } @@ -146,8 +148,7 @@ class Session extends Envelope { ); if (json.containsKey(stateKey)) { - session.state = SessionState.values - .firstWhere((e) => describeEnum(e) == json[stateKey]); + session.state = SessionState.values.firstWhere((e) => describeEnum(e) == json[stateKey]); } if (json.containsKey(schemeOptionsKey)) { diff --git a/lib/src/utils/lime.utils.dart b/lib/src/utils/lime.utils.dart new file mode 100644 index 0000000..2585e67 --- /dev/null +++ b/lib/src/utils/lime.utils.dart @@ -0,0 +1,3 @@ +abstract class LimeUtils { + static const String packageName = 'lime'; +} \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index b926d05..221751a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -28,7 +28,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.8.2" + version: "2.9.0" boolean_selector: dependency: transitive description: @@ -98,7 +98,7 @@ packages: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.2.1" charcode: dependency: transitive description: @@ -119,7 +119,7 @@ packages: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" code_builder: dependency: transitive description: @@ -133,7 +133,7 @@ packages: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.15.0" + version: "1.16.0" convert: dependency: transitive description: @@ -161,7 +161,7 @@ packages: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.3.1" file: dependency: transitive description: @@ -269,21 +269,21 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.11" + version: "0.12.12" material_color_utilities: dependency: transitive description: name: material_color_utilities url: "https://pub.dartlang.org" source: hosted - version: "0.1.3" + version: "0.1.5" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "1.8.0" mime: dependency: transitive description: @@ -311,7 +311,7 @@ packages: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.0" + version: "1.8.2" pool: dependency: transitive description: @@ -379,7 +379,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.1" + version: "1.9.0" stack_trace: dependency: transitive description: @@ -407,21 +407,21 @@ packages: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.2.1" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.4.8" + version: "0.4.12" timing: dependency: transitive description: @@ -449,7 +449,7 @@ packages: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.1" + version: "2.1.2" watcher: dependency: transitive description: @@ -472,5 +472,5 @@ packages: source: hosted version: "3.1.0" sdks: - dart: ">=2.16.0 <3.0.0" + dart: ">=2.17.0-0 <3.0.0" flutter: ">=1.17.0" diff --git a/pubspec.yaml b/pubspec.yaml index ee2d9ab..a9becce 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: lime description: LIME Protocol for Dart/Flutter - A lightweight messaging library -version: 0.0.15 +version: 0.0.16 homepage: https://limeprotocol.org/ repository: https://github.com/takenet/lime-dart @@ -23,3 +23,15 @@ dev_dependencies: build_runner: ^2.1.10 flutter_lints: ^1.0.0 +# The following section is specific to Flutter packages. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + # uses-material-design: true + + # To add assets to your application, add an assets section, like this: + assets: + - assets/keys/ + diff --git a/test/protocol/client/client_channel_test.mocks.dart b/test/protocol/client/client_channel_test.mocks.dart index d85afe3..fb1d1d7 100644 --- a/test/protocol/client/client_channel_test.mocks.dart +++ b/test/protocol/client/client_channel_test.mocks.dart @@ -32,7 +32,6 @@ class _FakeSimpleLogger_1 extends _i1.Fake implements _i3.SimpleLogger {} /// See the documentation for Mockito's code generation for more information. class MockWebSocketTransport extends _i1.Mock implements _i4.WebSocketTransport { - @override set stream(_i2.StreamController>? _stream) => super.noSuchMethod(Invocation.setter(#stream, _stream), returnValueForMissingStub: null); @@ -65,11 +64,11 @@ class MockWebSocketTransport extends _i1.Mock super.noSuchMethod(Invocation.setter(#encryption, _encryption), returnValueForMissingStub: null); @override - set onEvelope(_i2.StreamController>? _onEvelope) => - super.noSuchMethod(Invocation.setter(#onEvelope, _onEvelope), + set onEnvelope(_i2.StreamController>? _onEnvelope) => + super.noSuchMethod(Invocation.setter(#onEnvelope, _onEnvelope), returnValueForMissingStub: null); @override - _i2.Future open(String? uri) => + _i2.Future open(String? uri, {bool useMtls = false}) => (super.noSuchMethod(Invocation.method(#open, [uri]), returnValue: Future.value(), returnValueForMissingStub: Future.value()) as _i2.Future); diff --git a/test/protocol/command_test.dart b/test/protocol/command_test.dart index d5ca4c4..eb1ff66 100644 --- a/test/protocol/command_test.dart +++ b/test/protocol/command_test.dart @@ -24,7 +24,7 @@ void main() { final command = Command.fromJson(json); expect(command.metadata, equals(json['metadata'])); - expect(describeEnum(command.method!), equals(json['method'])); + expect(describeEnum(command.method), equals(json['method'])); expect(command.resource, equals(json['resource'])); expect(describeEnum(command.status!), equals(json['status'])); expect(command.type, equals(json['type'])); @@ -47,7 +47,7 @@ void main() { final jsonDoc = command.toJson(); expect(jsonDoc['metadata'], equals(command.metadata)); - expect(jsonDoc['method'], equals(describeEnum(command.method!))); + expect(jsonDoc['method'], equals(describeEnum(command.method))); expect(jsonDoc['resource'], equals(command.resource)); expect(jsonDoc['status'], equals(describeEnum(command.status!))); expect(jsonDoc['type'], equals(command.type)); diff --git a/test/protocol/identity_test.dart b/test/protocol/identity_test.dart index bb55dc9..281f7e4 100644 --- a/test/protocol/identity_test.dart +++ b/test/protocol/identity_test.dart @@ -25,11 +25,12 @@ void main() { }); group('Identity.tryParse()', () { - test('should returns false', () { - expect(Identity.tryParse(''), equals(false)); + test('should returns null', () { + expect(Identity.tryParse(''), equals(null)); }); - test('should returns true', () { - expect(Identity.tryParse('name@domain'), equals(true)); + test('should returns Identity', () { + final result = Identity.tryParse('name@domain'); + expect(result is Identity, equals(true)); }); });