identifyWait method
IdentifyWait triggers an identify (if the connection has not already been identified) and returns a future that completes when the identify protocol completes.
Implementation
@override
Future<void> identifyWait(Conn conn) async {
final peerId = conn.remotePeer;
final identifyWaitStart = DateTime.now();
Completer<void>? completerToAwait;
await _connsMutex.synchronized(() async {
final mutexAcquiredTime = DateTime.now().difference(identifyWaitStart);
var entry = _conns[conn];
if (entry != null) {
// OPTIMIZATION: If identify already succeeded for this connection, return immediately.
// This prevents re-running identify on every newStream call, which would cause
// 30-second timeouts when the connection is stale.
if (entry.identifySucceeded) {
_log.fine(
' [IDENTIFY-WAIT-ALREADY-SUCCEEDED] Peer $peerId already identified on this connection, skipping');
// completerToAwait remains null, so function will return immediately
return;
}
if (entry.identifyWaitCompleter != null &&
!entry.identifyWaitCompleter!.isCompleted) {
completerToAwait = entry.identifyWaitCompleter;
// No need to spawn _identifyConn again if one is already running for this entry.
} else {
entry.identifyWaitCompleter = Completer<void>();
completerToAwait = entry.identifyWaitCompleter;
// Spawn _identifyConn as this is a new request for this entry or previous one completed/failed.
_spawnIdentifyConn(conn, entry);
}
} else {
if (conn.isClosed) {
_log.warning(
' [IDENTIFY-WAIT-PHASE-1-CONN-CLOSED] Connection to peer=$peerId is already closed. Not creating entry or starting identify.');
// Completer to await will remain null, function will return.
return;
}
entry = _addConnWithLock(
conn); // _addConnWithLock returns the new/existing entry
entry.identifyWaitCompleter = Completer<void>();
completerToAwait = entry.identifyWaitCompleter;
_spawnIdentifyConn(conn, entry);
}
});
final mutexReleasedTime = DateTime.now().difference(identifyWaitStart);
if (completerToAwait == null) {
_log.warning(
' [IDENTIFY-WAIT-NO-COMPLETER] No completer to await for peer=$peerId (e.g., connection was closed). Identify will not complete.');
return; // Or throw, depending on desired behavior for closed conns.
}
try {
await completerToAwait!.future;
final totalDuration = DateTime.now().difference(identifyWaitStart);
} catch (e, st) {
final totalDuration = DateTime.now().difference(identifyWaitStart);
_log.warning(
' [IDENTIFY-WAIT-ERROR] Identify completer for peer=$peerId completed with error, total_duration=${totalDuration.inMilliseconds}ms, error=$e\n$st');
// Handle timeout exceptions gracefully.
// A timeout indicates the peer is unreachable, not a security concern.
// The failure event has already been emitted by _spawnIdentifyConn's catchError,
// so applications can listen for EvtPeerIdentificationFailed if needed.
// Rethrowing typed IdentifyTimeoutException allows callers to specifically handle timeouts.
if (e is IdentifyTimeoutException) {
_log.warning(
' [IDENTIFY-WAIT-TIMEOUT] Identify timeout for peer=$peerId handled gracefully. Rethrowing typed exception for caller to handle.');
rethrow;
}
// Also handle timeout detection from generic exceptions
if (isTimeoutException(e)) {
_log.warning(
' [IDENTIFY-WAIT-TIMEOUT-DETECTED] Identify timeout detected for peer=$peerId. Converting to typed exception.');
throw IdentifyTimeoutException(
peerId: peerId,
message: 'Identify timed out for peer $peerId',
cause: e,
);
}
// CRITICAL SECURITY FIX: For non-timeout errors, we MUST rethrow.
// If identify fails (other than timeout), the connection is unverified
// and potentially insecure (missing keys/metadata).
// Callers (like BasicHost.connect or newStream) expect identifyWait to complete successfully
// before proceeding. If we swallow the error, they proceed with a degraded connection.
// By rethrowing, we ensure BasicHost aborts the connection.
rethrow;
}
}