Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added WebSocket support in AtLookup #709

Closed
wants to merge 30 commits into from
Closed
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
b586311
changed version of websocket
purnimavenkatasubbu Sep 26, 2024
caa7591
websocket changes
purnimavenkatasubbu Oct 7, 2024
ffc02f5
Merge remote-tracking branch 'origin/trunk' into ws_version2
gkc Oct 7, 2024
63fc299
fixed closing ws connection
purnimavenkatasubbu Oct 15, 2024
df4658d
changes
purnimavenkatasubbu Oct 16, 2024
ab88f5e
Merge branch 'trunk' of https://github.com/atsign-foundation/at_libra…
purnimavenkatasubbu Oct 16, 2024
d88a139
Merge branch 'trunk' of https://github.com/atsign-foundation/at_libra…
purnimavenkatasubbu Oct 22, 2024
417234c
refactored code
purnimavenkatasubbu Oct 22, 2024
0eca375
Merge branch 'trunk' of https://github.com/atsign-foundation/at_libra…
purnimavenkatasubbu Oct 23, 2024
b78716a
refactored code based on factories
purnimavenkatasubbu Nov 4, 2024
3f453af
removed commented code
purnimavenkatasubbu Nov 4, 2024
0452d85
Merge branch 'trunk' of https://github.com/atsign-foundation/at_libra…
purnimavenkatasubbu Nov 5, 2024
a72cf1a
addressed review comments
purnimavenkatasubbu Nov 5, 2024
a7c8e64
updated unit tests
purnimavenkatasubbu Nov 12, 2024
aee94bb
fixed failing test
purnimavenkatasubbu Nov 12, 2024
a67171b
fix: Updated mock references to prevent null pointer errors
sitaram-kalluri Nov 13, 2024
49b840b
fix: Add unit test - A test to verify the connections are invalidated…
sitaram-kalluri Nov 13, 2024
4f23462
fixed failing unit test
purnimavenkatasubbu Nov 13, 2024
95fc90c
pubspec
purnimavenkatasubbu Nov 13, 2024
a635041
correct atsign and removed commented code
purnimavenkatasubbu Nov 13, 2024
65f6141
removed useWebSocket flag and added unit tests
purnimavenkatasubbu Nov 18, 2024
61e2913
renamed variables and minor changes
purnimavenkatasubbu Nov 18, 2024
b4eef12
addressed review comments
purnimavenkatasubbu Nov 19, 2024
ab72ec6
Merge branch 'trunk' of https://github.com/atsign-foundation/at_libra…
purnimavenkatasubbu Nov 21, 2024
be8058b
addressed review commits
purnimavenkatasubbu Nov 21, 2024
b32009b
removed commented code
purnimavenkatasubbu Nov 22, 2024
9ea9415
review comments
purnimavenkatasubbu Nov 25, 2024
5dc5a47
removed commented code
purnimavenkatasubbu Nov 25, 2024
3e05187
addressed more review comments
purnimavenkatasubbu Dec 4, 2024
8e5aaee
changing heirarchy and removeing outboundconnection
purnimavenkatasubbu Dec 6, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/at_lookup/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
## 4.0.0
- feat: Introduce websocket support in at_lookup_impl
## 3.0.49
- build[deps]: Upgraded the following packages:
- at_commons to v5.0.0
Expand Down
3 changes: 3 additions & 0 deletions packages/at_lookup/lib/at_lookup.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@ library at_lookup;
export 'src/at_lookup.dart';
export 'src/at_lookup_impl.dart';
export 'src/connection/outbound_connection.dart';
export 'src/connection/outbound_websocket_connection.dart';
export 'src/connection/outbound_connection_impl.dart';
export 'src/exception/at_lookup_exception.dart';
export 'src/monitor_client.dart';
export 'src/cache/secondary_address_finder.dart';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since we have modified SecondaryUrlFinder constructor and exported this class, the change should be treated as breaking and have a major release

Copy link
Member Author

@purnimavenkatasubbu purnimavenkatasubbu Nov 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated be8058b

export 'src/cache/cacheable_secondary_address_finder.dart';
export 'src/util/secure_socket_util.dart';
export 'src/connection/outbound_websocket_connection_impl.dart';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please avoid exporting the implementation classes.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

export 'src/connection/at_lookup_outbound_connection_factory.dart';
107 changes: 55 additions & 52 deletions packages/at_lookup/lib/src/at_lookup_impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,38 @@ import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';

import 'package:at_chops/at_chops.dart';
import 'package:at_commons/at_builders.dart';
import 'package:at_commons/at_commons.dart';
import 'package:at_lookup/at_lookup.dart';
import 'package:at_lookup/src/connection/at_connection.dart';
import 'package:at_lookup/src/connection/outbound_message_listener.dart';
import 'package:at_utils/at_logger.dart';
import 'package:crypto/crypto.dart';
import 'package:crypton/crypton.dart';
import 'package:mutex/mutex.dart';
import 'package:at_chops/at_chops.dart';

class AtLookupImpl implements AtLookUp {
final logger = AtSignLogger('AtLookup');

/// Listener for reading verb responses from the remote server
late OutboundMessageListener messageListener;

OutboundConnection? _connection;
AtConnection?
_connection; // Single variable for both socket and WebSocket connections

OutboundConnection? get connection => _connection;
AtConnection? get connection => _connection;

/// Setters or methods to initialize the connection
void setSocketConnection(OutboundConnection socketConnection) {
_connection = socketConnection;
}

void setWebSocketConnection(OutboundWebSocketConnection webSocketConnection) {
_connection = webSocketConnection;
}

late AtLookupOutboundConnectionFactory atOutboundConnectionFactory;

@override
late SecondaryAddressFinder secondaryAddressFinder;
Expand All @@ -44,12 +57,6 @@ class AtLookupImpl implements AtLookUp {

late SecureSocketConfig _secureSocketConfig;

late final AtLookupSecureSocketFactory socketFactory;

late final AtLookupSecureSocketListenerFactory socketListenerFactory;

late AtLookupOutboundConnectionFactory outboundConnectionFactory;

/// Represents the client configurations.
late Map<String, dynamic> _clientConfig;

Expand All @@ -61,9 +68,10 @@ class AtLookupImpl implements AtLookUp {
SecondaryAddressFinder? secondaryAddressFinder,
SecureSocketConfig? secureSocketConfig,
Map<String, dynamic>? clientConfig,
AtLookupSecureSocketFactory? secureSocketFactory,
AtLookupSecureSocketListenerFactory? socketListenerFactory,
AtLookupOutboundConnectionFactory? outboundConnectionFactory}) {
AtLookupOutboundConnectionFactory? atOutboundConnectionFactory}) {
// Default to secure socket factory
this.atOutboundConnectionFactory =
atOutboundConnectionFactory ?? AtLookupSecureSocketFactory();
_currentAtSign = atSign;
_rootDomain = rootDomain;
_rootPort = rootPort;
Expand All @@ -73,11 +81,6 @@ class AtLookupImpl implements AtLookUp {
// Stores the client configurations.
// If client configurations are not available, defaults to empty map
_clientConfig = clientConfig ?? {};
socketFactory = secureSocketFactory ?? AtLookupSecureSocketFactory();
this.socketListenerFactory =
socketListenerFactory ?? AtLookupSecureSocketListenerFactory();
this.outboundConnectionFactory =
outboundConnectionFactory ?? AtLookupOutboundConnectionFactory();
}

@Deprecated('use CacheableSecondaryAddressFinder')
Expand Down Expand Up @@ -246,16 +249,18 @@ class AtLookupImpl implements AtLookUp {
await _connection!.close();
}
logger.info('Creating new connection');
//1. find secondary url for atsign from lookup library

// 1. Find secondary URL for the atsign from the lookup library
SecondaryAddress secondaryAddress =
await secondaryAddressFinder.findSecondary(_currentAtSign);
var host = secondaryAddress.host;
var port = secondaryAddress.port;
//2. create a connection to secondary server
await createOutBoundConnection(
host, port.toString(), _currentAtSign, _secureSocketConfig);
//3. listen to server response
messageListener = socketListenerFactory.createListener(_connection!);

// 2. Create a connection to the secondary server
await createOutboundConnection(
host, port.toString(), _secureSocketConfig);

// 3. Listen to server response
messageListener.listen();
logger.info('New connection created OK');
}
Expand Down Expand Up @@ -524,31 +529,40 @@ class AtLookupImpl implements AtLookUp {
await createConnection();
try {
await _cramAuthenticationMutex.acquire();

if (!_connection!.getMetaData()!.isAuthenticated) {
// Use the connection and message listener dynamically
await _sendCommand((FromVerbBuilder()
..atSign = _currentAtSign
..clientConfig = _clientConfig)
.buildCommand());

var fromResponse = await messageListener.read(
transientWaitTimeMillis: 4000, maxWaitMilliSeconds: 10000);
logger.info('from result:$fromResponse');

if (fromResponse.isEmpty) {
return false;
}

fromResponse = fromResponse.trim().replaceAll('data:', '');

var digestInput = '$secret$fromResponse';
var bytes = utf8.encode(digestInput);
var digest = sha512.convert(bytes);

await _sendCommand('cram:$digest\n');
var cramResponse = await messageListener.read(
transientWaitTimeMillis: 4000, maxWaitMilliSeconds: 10000);

if (cramResponse == 'data:success') {
logger.info('auth success');
_connection!.getMetaData()!.isAuthenticated = true;
} else {
throw UnAuthenticatedException('Auth failed');
}
}

return _connection!.getMetaData()!.isAuthenticated;
} finally {
_cramAuthenticationMutex.release();
Expand Down Expand Up @@ -628,19 +642,28 @@ class AtLookupImpl implements AtLookUp {
!(_connection!.getMetaData()!.isAuthenticated);
}

Future<bool> createOutBoundConnection(String host, String port,
String toAtSign, SecureSocketConfig secureSocketConfig) async {
Future<bool> createOutboundConnection(
String host, String port, SecureSocketConfig secureSocketConfig) async {
try {
SecureSocket secureSocket =
await socketFactory.createSocket(host, port, secureSocketConfig);
_connection =
outboundConnectionFactory.createOutboundConnection(secureSocket);
// Create the socket connection using the factory
final underlying = await atOutboundConnectionFactory.createUnderlying(
host, port, secureSocketConfig);

// Create the outbound connection and listener using the factory's methods
final outboundConnection =
atOutboundConnectionFactory.outBoundConnectionFactory(underlying);
messageListener = atOutboundConnectionFactory
.atLookupSocketListenerFactory(outboundConnection);

_connection = outboundConnection;

// Set idle time if applicable
if (outboundConnectionTimeout != null) {
_connection!.setIdleTime(outboundConnectionTimeout);
outboundConnection.setIdleTime(outboundConnectionTimeout);
}
} on SocketException {
throw SecondaryConnectException(
'unable to connect to secondary $toAtSign on $host:$port');
'Unable to connect to secondary $_currentAtSign on $host:$port');
}
return true;
}
Expand All @@ -661,7 +684,7 @@ class AtLookupImpl implements AtLookUp {
Future<void> _sendCommand(String command) async {
await createConnection();
logger.finer('SENDING: $command');
await _connection!.write(command);
_connection!.write(command);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

await is removed here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think await is needed

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I verified the code and "write" operation on the socket does not return a future. Hence suggested to remove await.

}

@override
Expand All @@ -682,23 +705,3 @@ class AtLookupImpl implements AtLookUp {
@override
String? enrollmentId;
}

class AtLookupSecureSocketFactory {
Future<SecureSocket> createSocket(
String host, String port, SecureSocketConfig socketConfig) async {
return await SecureSocketUtil.createSecureSocket(host, port, socketConfig);
}
}

class AtLookupSecureSocketListenerFactory {
OutboundMessageListener createListener(
OutboundConnection outboundConnection) {
return OutboundMessageListener(outboundConnection);
}
}

class AtLookupOutboundConnectionFactory {
OutboundConnection createOutboundConnection(SecureSocket secureSocket) {
return OutboundConnectionImpl(secureSocket);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,10 @@ class SecondaryAddressCacheEntry {
class SecondaryUrlFinder {
final String _rootDomain;
final int _rootPort;
late final AtLookupSecureSocketFactory _socketFactory;
late final AtLookupOutboundConnectionFactory _socketFactory;

SecondaryUrlFinder(this._rootDomain, this._rootPort,
{AtLookupSecureSocketFactory? socketFactory}) {
{AtLookupOutboundConnectionFactory? socketFactory}) {
_socketFactory = socketFactory ?? AtLookupSecureSocketFactory();
}

Expand Down Expand Up @@ -188,11 +188,11 @@ class SecondaryUrlFinder {
var prompt = false;
var once = true;

socket = await _socketFactory.createSocket(
socket = await _socketFactory.createUnderlying(
_rootDomain, '$_rootPort', SecureSocketConfig());
_logger.finer('findSecondaryUrl: connection to root server established');
// listen to the received data event stream
socket.listen((List<int> event) async {
socket!.listen((List<int> event) async {
_logger.finest('root socket listener received: $event');
answer = utf8.decode(event);

Expand Down
8 changes: 3 additions & 5 deletions packages/at_lookup/lib/src/connection/at_connection.dart
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import 'dart:io';
abstract class AtConnection<T> {
/// The underlying connection
T get underlying;

abstract class AtConnection {
/// Write a data to the underlying socket of the connection
/// @param - data - Data to write to the socket
/// @throws [AtIOException] for any exception during the operation
void write(String data);

/// Retrieves the socket of underlying connection
Socket getSocket();

/// closes the underlying connection
Future<void> close();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import 'dart:io';

import 'package:at_commons/at_commons.dart';
import 'package:at_lookup/at_lookup.dart';

import 'outbound_message_listener.dart';

/// This factory is responsible for creating the underlying connection,
/// an outbound connection wrapper, and the message listener for a
/// specific type of connection (e.g., `SecureSocket` or `WebSocket`).
abstract class AtLookupOutboundConnectionFactory<T, U> {
/// Creates the underlying connection of type [T].
///
/// Takes [host], [port], and [secureSocketConfig] as parameters to establish
/// a secure connection based on the provided configuration.
Future<T> createUnderlying(
String host, String port, SecureSocketConfig secureSocketConfig);

/// Wraps the underlying connection of type [T] into an outbound connection [U].
U outBoundConnectionFactory(T underlying);

/// Creates an [OutboundMessageListener] to manage messages for the given [U] connection.
OutboundMessageListener atLookupSocketListenerFactory(U connection);
}

/// Factory class to create a secure outbound connection over [SecureSocket].
///
/// This class handles the creation of a secure socket-based connection,
/// its outbound connection wrapper, and the corresponding message listener.
class AtLookupSecureSocketFactory extends AtLookupOutboundConnectionFactory<
SecureSocket, OutboundConnection> {
/// Creates a secure socket connection to the specified [host] and [port]
/// using the given [secureSocketConfig].
///
/// Returns a [SecureSocket] that establishes a secure connection
@override
Future<SecureSocket> createUnderlying(
String host, String port, SecureSocketConfig secureSocketConfig) async {
return await SecureSocketUtil.createSecureSocket(
host, port, secureSocketConfig);
}

/// Wraps the [SecureSocket] connection into an [OutboundConnection] instance.
@override
OutboundConnection outBoundConnectionFactory(SecureSocket underlying) {
return OutboundConnectionImpl(underlying);
}

/// Creates an [OutboundMessageListener] to manage messages for the secure
/// socket-based [OutboundConnection].
@override
OutboundMessageListener atLookupSocketListenerFactory(
OutboundConnection connection) {
return OutboundMessageListener(connection);
}
}

/// Factory class to create a WebSocket-based outbound connection.
///
/// This class handles the creation of a WebSocket connection,
/// its outbound connection wrapper, and the associated message listener.
class AtLookupWebSocketFactory extends AtLookupOutboundConnectionFactory<
WebSocket, OutboundWebSocketConnection> {
/// Creates a WebSocket connection to the specified [host] and [port]
/// using the given [secureSocketConfig].
@override
Future<WebSocket> createUnderlying(
String host, String port, SecureSocketConfig secureSocketConfig) async {
final socket = await SecureSocketUtil.createSecureSocket(
host, port, secureSocketConfig,
isWebSocket: true);
return socket as WebSocket;
}

/// Wraps the [WebSocket] connection into an [OutboundWebSocketConnection] instance.
///
/// This outbound connection manages WebSocket-specific message handling and
/// provides additional methods for WebSocket communication.
@override
OutboundWebSocketConnection outBoundConnectionFactory(WebSocket underlying) {
return OutboundWebsocketConnectionImpl(underlying);
}

/// Creates an [OutboundMessageListener] to manage messages for the
/// WebSocket-based [OutboundWebSocketConnection].
@override
OutboundMessageListener atLookupSocketListenerFactory(
OutboundWebSocketConnection connection) {
return OutboundMessageListener(connection);
}
}
Loading
Loading