part of '../platform_driver_rethinkdb.dart'; class Query extends RqlQuery { final p.Query_QueryType _type; final int _token; final RqlQuery? _term; late final Map? _globalOptargs; Cursor? _cursor; final Completer _queryCompleter = Completer(); Query(this._type, this._token, [this._term, this._globalOptargs]); serialize() { List res = [_type.value]; if (_term != null) { res.add(_term.build()); } if (_globalOptargs != null) { Map optargs = {}; _globalOptargs.forEach((k, v) { optargs[k] = v is RqlQuery ? v.build() : v; }); res.add(optargs); } return json.encode(res); } } class Response { final int _token; late int _type; dynamic _data; dynamic _backtrace; dynamic _profile; late int? _errorType; List? _notes = []; Response(this._token, String jsonStr) { if (jsonStr.isNotEmpty) { Map fullResponse = json.decode(jsonStr); _type = fullResponse['t']; _data = fullResponse['r']; _backtrace = fullResponse['b']; _profile = fullResponse['p']; _notes = fullResponse['n']; _errorType = fullResponse['e']; } } } class Connection { Socket? _socket; static int _nextToken = 0; final String _host; final int _port; String _db; final String _user; final String _password; final int _protocolVersion = 0; late String _clientFirstMessage; late Digest _serverSignature; late final Map? _sslOpts; final Completer _completer = Completer(); int _responseLength = 0; final List _responseBuffer = []; final Map _replyQueries = {}; final Queue _sendQueue = Queue(); final Map _listeners = {}; Connection( this._db, this._host, this._port, this._user, this._password, this._sslOpts, ); // ignore: unnecessary_null_comparison get isClosed => _socket == null; void use(String db) { _db = db; } Future server() { // RqlQuery query = RqlQuery query = Query(p.Query_QueryType.SERVER_INFO, _getToken(), null, null); _sendQueue.add(query); return _start(query); } Future connect([bool noreplyWait = true]) { return (reconnect(noreplyWait)); } Future reconnect([bool noreplyWait = true]) { close(noreplyWait); if (_listeners["connect"] != null) { for (var func in _listeners["connect"]!) { func(); } } var sock = Socket.connect(_host, _port); if (_sslOpts != null && _sslOpts.containsKey('ca')) { SecurityContext context = SecurityContext() ..setTrustedCertificates(_sslOpts['ca']); sock = SecureSocket.connect(_host, _port, context: context); } sock.then((socket) { // ignore: unnecessary_null_comparison if (socket != null) { _socket = socket; _socket!.listen(_handleResponse, onDone: () { if (_listeners["close"] != null) { for (var func in _listeners["close"]!) { func(); } } }); _clientFirstMessage = "n=$_user,r=${_makeSalt()}"; String message = json.encode({ 'protocol_version': _protocolVersion, 'authentication_method': "SCRAM-SHA-256", // ignore: unnecessary_brace_in_string_interps 'authentication': "n,,${_clientFirstMessage}" }); List handshake = List.from(_toBytes(p.VersionDummy_Version.V1_0.value)) ..addAll(message.codeUnits) ..add(0); _socket!.add(handshake); } }).catchError((err) { _completer.completeError( RqlDriverError("Could not connect to $_host:$_port. Error $err"), ); }); return _completer.future; } _handleResponse(List bytes) { if (!_completer.isCompleted) { _handleAuthResponse(bytes); } else { _readResponse(bytes); } } _handleAuthResponse(List res) { List response = []; for (final byte in res) { if (byte == 0) { _doHandshake(response); response.clear(); } else { response.add(byte); } } } _handleAuthError(Exception error) { if (_listeners["error"] != null) { for (var func in _listeners["error"]!) { func(error); } } _completer.completeError(error); } _doHandshake(List response) { Map responseJSON = json.decode(utf8.decode(response)); if (responseJSON.containsKey('success') && responseJSON['success']) { if (responseJSON.containsKey('max_protocol_version')) { int max = responseJSON['max_protocol_version']; int min = responseJSON['min_protocol_version']; if (min > _protocolVersion || max < _protocolVersion) { //We don't actually support changing the protocol yet, so just error. _handleAuthError( RqlDriverError("""Unsupported protocol version $_protocolVersion, expected between $min and $max.""")); } } else if (responseJSON.containsKey('authentication')) { String authString = responseJSON['authentication']; Map authMap = {}; List authPieces = authString.split(','); for (var piece in authPieces) { int i = piece.indexOf('='); String key = piece.substring(0, i); String val = piece.substring(i + 1); authMap[key] = val; } if (authMap.containsKey('r')) { String salt = String.fromCharCodes(base64.decode(authMap['s'])); int i = int.parse(authMap['i']); String clientFinalMessageWithoutProof = "c=biws,r=${authMap['r']}"; //PBKDF2NS gen = PBKDF2NS(hash: sha256); //List saltedPassword = gen.generateKey(_password, salt, i, 32); var saltedPassword = hashlib.pbkdf2(_password.codeUnits, salt.codeUnits, i, 32); Digest clientKey = Hmac(sha256, saltedPassword.bytes) .convert("Client Key".codeUnits); Digest storedKey = sha256.convert(clientKey.bytes); String authMessage = "$_clientFirstMessage,$authString,$clientFinalMessageWithoutProof"; Digest clientSignature = Hmac(sha256, storedKey.bytes).convert(authMessage.codeUnits); List clientProof = _xOr(clientKey.bytes, clientSignature.bytes); Digest serverKey = Hmac(sha256, saltedPassword.bytes) .convert("Server Key".codeUnits); _serverSignature = Hmac(sha256, serverKey.bytes).convert(authMessage.codeUnits); String message = json.encode({ 'authentication': "$clientFinalMessageWithoutProof,p=${base64.encode(clientProof)}" }); List messageBytes = List.from(message.codeUnits)..add(0); _socket!.add(messageBytes); } else if (authMap.containsKey('v')) { if (base64.encode(_serverSignature.bytes) != authMap['v']) { _handleAuthError(RqlDriverError("Invalid server signature")); } else { _completer.complete(this); } } } } else { _handleAuthError(RqlDriverError( "Server dropped connection with message: ${responseJSON['error']}")); } } _handleQueryResponse(Response response) { Query query = _replyQueries.remove(response._token); Exception? hasError = _checkErrorResponse(response, query._term); // ignore: unnecessary_null_comparison if (hasError != null) { query._queryCompleter.completeError(hasError); } dynamic value; if (response._type == p.Response_ResponseType.SUCCESS_PARTIAL.value) { _replyQueries[response._token] = query; dynamic cursor; for (var note in response._notes!) { if (note == p.Response_ResponseNote.SEQUENCE_FEED.value) { cursor = cursor ?? Feed(this, query, query.optargs); } else if (note == p.Response_ResponseNote.UNIONED_FEED.value) { cursor = cursor ?? UnionedFeed(this, query, query.optargs); } else if (note == p.Response_ResponseNote.ATOM_FEED.value) { cursor = cursor ?? AtomFeed(this, query, query.optargs); } else if (note == p.Response_ResponseNote.ORDER_BY_LIMIT_FEED.value) { cursor = cursor ?? OrderByLimitFeed(this, query, query.optargs); } } cursor = cursor ?? Cursor(this, query, query.optargs); value = cursor; query._cursor = value; value._extend(response); } else if (response._type == p.Response_ResponseType.SUCCESS_SEQUENCE.value) { value = Cursor(this, query, {}); query._cursor = value; value._extend(response); } else if (response._type == p.Response_ResponseType.SUCCESS_ATOM.value) { if (response._data.length < 1) { value = null; } value = query._recursivelyConvertPseudotypes(response._data.first, null); } else if (response._type == p.Response_ResponseType.WAIT_COMPLETE.value) { //Noreply_wait response value = null; } else if (response._type == p.Response_ResponseType.SERVER_INFO.value) { query._queryCompleter.complete(response._data.first); } else { if (!query._queryCompleter.isCompleted) { query._queryCompleter .completeError(RqlDriverError("Error: ${response._data}.")); } } if (response._profile != null) { value = {"value": value, "profile": response._profile}; } if (!query._queryCompleter.isCompleted) { query._queryCompleter.complete(value); } } void close([bool noreplyWait = true]) { // ignore: unnecessary_null_comparison if (_socket != null) { if (noreplyWait) this.noreplyWait(); try { _socket!.close(); } catch (err) { // TODO: do something with err. } _socket!.destroy(); _socket = null; } } /// Alias for addListener void on(String key, Function val) { addListener(key, val); } /// Adds a listener to the connection. void addListener(String key, Function val) { List currentListeners = []; // ignore: unnecessary_null_comparison if (_listeners != null && _listeners[key] != null) { for (var element in _listeners[key]!) { currentListeners.add(element); } } currentListeners.add(val); _listeners[key] = currentListeners; } _getToken() { return ++_nextToken; } clientPort() { return _socket!.port; } clientAddress() { return _socket!.address.address; } noreplyWait() { // RqlQuery query = RqlQuery query = Query(p.Query_QueryType.NOREPLY_WAIT, _getToken(), null, null); _sendQueue.add(query); return _start(query); } _handleCursorResponse(Response response) { Cursor cursor = _replyQueries[response._token]._cursor; cursor._extend(response); cursor._outstandingRequests--; if (response._type != p.Response_ResponseType.SUCCESS_PARTIAL.value && cursor._outstandingRequests == 0) { _replyQueries[response._token]._cursor = null; } } _readResponse(res) { int responseToken; String responseBuf; int responseLen; _responseBuffer.addAll(List.from(res)); _responseLength = _responseBuffer.length; if (_responseLength >= 12) { responseToken = _fromBytes(_responseBuffer.sublist(0, 8)); responseLen = _fromBytes(_responseBuffer.sublist(8, 12)); if (_responseLength >= responseLen + 12) { responseBuf = utf8.decode(_responseBuffer.sublist(12, responseLen + 12)); _responseBuffer.removeRange(0, responseLen + 12); _responseLength = _responseBuffer.length; Response response = Response(responseToken, responseBuf); if (_replyQueries[response._token]._cursor != null) { _handleCursorResponse(response); } //if for some reason there are other queries on the line... if (_replyQueries.containsKey(response._token)) { _handleQueryResponse(response); } else { throw RqlDriverError("Unexpected response received."); } if (_responseLength > 0) { _readResponse([]); } } } } _checkErrorResponse(Response response, RqlQuery? term) { dynamic message; dynamic frames; if (response._type == p.Response_ResponseType.RUNTIME_ERROR.value) { message = response._data.first; frames = response._backtrace; int? errType = response._errorType; if (errType == p.Response_ErrorType.INTERNAL.value) { return ReqlInternalError(message, term, frames); } else if (errType == p.Response_ErrorType.RESOURCE_LIMIT.value) { return ReqlResourceLimitError(message, term, frames); } else if (errType == p.Response_ErrorType.QUERY_LOGIC.value) { return ReqlQueryLogicError(message, term, frames); } else if (errType == p.Response_ErrorType.NON_EXISTENCE.value) { return ReqlNonExistenceError(message, term, frames); } else if (errType == p.Response_ErrorType.OP_FAILED.value) { return ReqlOpFailedError(message, term, frames); } else if (errType == p.Response_ErrorType.OP_INDETERMINATE.value) { return ReqlOpIndeterminateError(message, term, frames); } else if (errType == p.Response_ErrorType.USER.value) { return ReqlUserError(message, term, frames); } else if (errType == p.Response_ErrorType.PERMISSION_ERROR.value) { return ReqlPermissionError(message, term, frames); } else { return RqlRuntimeError(message, term, frames); } } else if (response._type == p.Response_ResponseType.COMPILE_ERROR.value) { message = response._data.first; frames = response._backtrace; return RqlCompileError(message, term, frames); } else if (response._type == p.Response_ResponseType.CLIENT_ERROR.value) { message = response._data.first; frames = response._backtrace; return RqlClientError(message, term, frames); } return null; } _sendQuery() { if (_sendQueue.isNotEmpty) { Query query = _sendQueue.removeFirst(); // Error if this connection has closed if (_socket == null) { query._queryCompleter .completeError(RqlDriverError("Connection is closed.")); } else { // Send json List queryStr = utf8.encode(query.serialize()); List queryHeader = List.from(_toBytes8(query._token)) ..addAll(_toBytes(queryStr.length)) ..addAll(queryStr); _socket!.add(queryHeader); _replyQueries[query._token] = query; return query._queryCompleter.future; } } } _start(RqlQuery term, [Map? globalOptargs]) { globalOptargs ??= {}; if (globalOptargs.containsKey('db')) { globalOptargs['db'] = DB(globalOptargs['db']); } else { globalOptargs['db'] = DB(_db); } Query query = Query(p.Query_QueryType.START, _getToken(), term, globalOptargs); _sendQueue.addLast(query); return _sendQuery(); } Uint8List _toBytes(int data) { ByteBuffer buffer = Uint8List(4).buffer; ByteData bdata = ByteData.view(buffer); bdata.setInt32(0, data, Endian.little); return Uint8List.view(buffer); } Uint8List _toBytes8(int data) { ByteBuffer buffer = Uint8List(8).buffer; ByteData bdata = ByteData.view(buffer); bdata.setInt32(0, data, Endian.little); return Uint8List.view(buffer); } int _fromBytes(List data) { Uint8List buf = Uint8List.fromList(data); ByteData bdata = ByteData.view(buf.buffer); return bdata.getInt32(0, Endian.little); } String _makeSalt() { List randomBytes = []; math.Random random = math.Random.secure(); for (int i = 0; i < randomBytes.length; ++i) { randomBytes[i] = random.nextInt(255); } return base64.encode(randomBytes); } List _xOr(List result, List next) { for (int i = 0; i < result.length; i++) { result[i] ^= next[i]; } return result; } }