Bug Fix and API adjustment

- Check token validity
- Prevent unlogged user to fetch contact
- Fix missing 'tolist' conversion in contact fetch method
- (IMPORTANT) Fix async bug in TCP controller (reconstruct the  handler)
This commit is contained in:
Linloir 2022-10-14 13:35:55 +08:00
parent 834e810663
commit 52c32c2e74
No known key found for this signature in database
GPG Key ID: 58EEB209A0F2C366
6 changed files with 106 additions and 48 deletions

View File

@ -1,7 +1,7 @@
/* /*
* @Author : Linloir * @Author : Linloir
* @Date : 2022-10-06 15:44:16 * @Date : 2022-10-06 15:44:16
* @LastEditTime : 2022-10-12 18:23:31 * @LastEditTime : 2022-10-14 10:26:00
* @Description : * @Description :
*/ */
@ -30,9 +30,9 @@ void main(List<String> arguments) async {
listenSocket.listen( listenSocket.listen(
(socket) { (socket) {
var controller = TCPController(socket: socket); var controller = TCPController(socket: socket);
controller.inStream.listen((request) async { controller.requestStreamBroadcast.listen((request) async {
print('[L] ${request.toJSON}'); print('[L] ${request.toJSON}');
if(request.tokenID == null) { if(!(await DataBaseHelper().isTokenValid(tokenid: request.tokenID))) {
if(controllerMap[controller] == null) { if(controllerMap[controller] == null) {
controllerMap[controller] = (() async => (await DataBaseHelper().createToken()))(); controllerMap[controller] = (() async => (await DataBaseHelper().createToken()))();
} }
@ -46,7 +46,6 @@ void main(List<String> arguments) async {
); );
controller.outStream.add(tokenResponse); controller.outStream.add(tokenResponse);
} }
//TODO: check if token id is not in tokenid list
tokenMap[request.tokenID!] = tokenMap[request.tokenID!] ?? controller; tokenMap[request.tokenID!] = tokenMap[request.tokenID!] ?? controller;
switch(request.requestType) { switch(request.requestType) {
case RequestType.checkState: { case RequestType.checkState: {
@ -151,6 +150,11 @@ void main(List<String> arguments) async {
controller.outStream.add(response); controller.outStream.add(response);
break; break;
} }
case RequestType.addContact: {
var response = await onAddContact(request, socket);
controller.outStream.add(response);
break;
}
case RequestType.fetchContact: { case RequestType.fetchContact: {
var response = await onFetchContact(request, socket); var response = await onFetchContact(request, socket);
controller.outStream.add(response); controller.outStream.add(response);

View File

@ -1,7 +1,7 @@
/* /*
* @Author : Linloir * @Author : Linloir
* @Date : 2022-10-06 16:15:01 * @Date : 2022-10-06 16:15:01
* @LastEditTime : 2022-10-13 20:36:34 * @LastEditTime : 2022-10-14 12:13:23
* @Description : * @Description :
*/ */
@ -83,6 +83,24 @@ class DataBaseHelper {
); );
} }
Future<bool> isTokenValid({
required int? tokenid,
}) async {
if(tokenid == null) {
return false;
}
var tokenQueryResult = await _database.query(
'tokens',
where: 'tokenid = ?',
whereArgs: [
tokenid
]
);
return tokenQueryResult.isNotEmpty;
}
//Creates new token //Creates new token
Future<int> createToken() async { Future<int> createToken() async {
//Insert new row //Insert new row
@ -628,13 +646,19 @@ class DataBaseHelper {
} }
//Find current binded userID //Find current binded userID
var currentUserID = (await _database.query( var currentUserIDQueryResult = (await _database.query(
'bindings', 'bindings',
where: 'tokenid = ?', where: 'tokenid = ?',
whereArgs: [ whereArgs: [
tokenID tokenID
] ]
))[0]['userid'] as int; ));
if(currentUserIDQueryResult.isEmpty) {
throw Exception('User not logged in');
}
var currentUserID = currentUserIDQueryResult[0]['userid'] as int;
//Fetch all contacts //Fetch all contacts
var contactsQueryResult = await _database.query( var contactsQueryResult = await _database.query(
@ -665,14 +689,19 @@ class DataBaseHelper {
throw Exception('Invalid device token'); throw Exception('Invalid device token');
} }
//Find current binded userID var currentUserIDQueryResult = (await _database.query(
var currentUserID = (await _database.query(
'bindings', 'bindings',
where: 'tokenid = ?', where: 'tokenid = ?',
whereArgs: [ whereArgs: [
tokenID tokenID
] ]
))[0]['userid'] as int; ));
if(currentUserIDQueryResult.isEmpty) {
throw Exception('User not logged in');
}
var currentUserID = currentUserIDQueryResult[0]['userid'] as int;
//Fetch pending contacts //Fetch pending contacts
var contactsQueryResult = await _database.query( var contactsQueryResult = await _database.query(
@ -707,13 +736,19 @@ class DataBaseHelper {
} }
//Find current binded userID //Find current binded userID
var currentUserID = (await _database.query( var currentUserIDQueryResult = (await _database.query(
'bindings', 'bindings',
where: 'tokenid = ?', where: 'tokenid = ?',
whereArgs: [ whereArgs: [
tokenID tokenID
] ]
))[0]['userid'] as int; ));
if(currentUserIDQueryResult.isEmpty) {
throw Exception('User not logged in');
}
var currentUserID = currentUserIDQueryResult[0]['userid'] as int;
//Fetch pending contacts //Fetch pending contacts
var contactsQueryResult = await _database.query( var contactsQueryResult = await _database.query(

View File

@ -1,7 +1,7 @@
/* /*
* @Author : Linloir * @Author : Linloir
* @Date : 2022-10-08 20:52:48 * @Date : 2022-10-08 20:52:48
* @LastEditTime : 2022-10-13 20:30:53 * @LastEditTime : 2022-10-14 11:29:03
* @Description : * @Description :
*/ */
@ -259,9 +259,9 @@ Future<TCPResponse> onFetchContact(TCPRequest request, Socket socket) async {
type: ResponseType.fromRequestType(request.requestType), type: ResponseType.fromRequestType(request.requestType),
status: ResponseStatus.ok, status: ResponseStatus.ok,
body: { body: {
"contacts": contacts.map((e) => e.jsonObject), "contacts": contacts.map((e) => e.jsonObject).toList(),
"pending": pendingContacts.map((e) => e.jsonObject), "pending": pendingContacts.map((e) => e.jsonObject).toList(),
"requesting": requestingContacts.map((e) => e.jsonObject) "requesting": requestingContacts.map((e) => e.jsonObject).toList()
} }
); );
} on Exception catch (exception) { } on Exception catch (exception) {

View File

@ -1,7 +1,7 @@
/* /*
* @Author : Linloir * @Author : Linloir
* @Date : 2022-10-08 15:10:04 * @Date : 2022-10-08 15:10:04
* @LastEditTime : 2022-10-12 13:45:01 * @LastEditTime : 2022-10-14 10:23:16
* @Description : * @Description :
*/ */
@ -9,6 +9,7 @@ import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:async/async.dart';
import 'package:tcp_server/tcpcontroller/request.dart'; import 'package:tcp_server/tcpcontroller/request.dart';
import 'package:tcp_server/tcpcontroller/response.dart'; import 'package:tcp_server/tcpcontroller/response.dart';
@ -24,26 +25,31 @@ class TCPController {
int payloadLength = 0; int payloadLength = 0;
//Construct a stream which emits events on intact requests //Construct a stream which emits events on intact requests
StreamController<List<int>> _requestStreamController = StreamController()..close(); StreamController<List<int>> _requestRawStreamController = StreamController();
StreamController<File> _payloadRawStreamController = StreamController();
//Construct a payload stream which forward the incoming byte into temp file //Construct a payload stream which forward the incoming byte into temp file
StreamController<List<int>> _payloadStreamController = StreamController()..close(); StreamController<List<int>> _payloadPullStreamController = StreamController()..close();
//Provide a request stream for caller functions to listen on //Provide a request stream for caller functions to listen on
final StreamController<TCPRequest> _inStreamController = StreamController(); final StreamController<TCPRequest> _requestStreamController = StreamController();
Stream<TCPRequest> get inStream => _inStreamController.stream; Stream<TCPRequest>? _requestStreamBroadcast;
Stream<TCPRequest> get requestStreamBroadcast {
_requestStreamBroadcast ??= _requestStreamController.stream.asBroadcastStream();
return _requestStreamBroadcast!;
}
//Provide a post stream for caller functions to push to //Provide a post stream for caller functions to push to
final StreamController<TCPResponse> _outStreamController = StreamController(); final StreamController<TCPResponse> _responseStreamController = StreamController();
StreamSink<TCPResponse> get outStream => _outStreamController.sink; StreamSink<TCPResponse> get outStream => _responseStreamController.sink;
TCPController({ TCPController({
required this.socket required this.socket
}) { }) {
socket.listen(socketHandler); socket.listen(_pullRequest);
//This future never ends, would that be bothersome? //This future never ends, would that be bothersome?
Future(() async { Future(() async {
await for(var response in _outStreamController.stream) { await for(var response in _responseStreamController.stream) {
await socket.addStream(response.stream); await socket.addStream(response.stream);
} }
}); });
@ -52,15 +58,26 @@ class TCPController {
// _outStreamController.stream.listen((response) async { // _outStreamController.stream.listen((response) async {
// await socket.addStream(response.stream); // await socket.addStream(response.stream);
// }); // });
Future(() async {
var requestQueue = StreamQueue(_requestRawStreamController.stream);
var payloadQueue = StreamQueue(_payloadRawStreamController.stream);
while(await Future<bool>(() => !_requestRawStreamController.isClosed && !_payloadRawStreamController.isClosed)) {
var response = await requestQueue.next;
var payload = await payloadQueue.next;
await _pushRequest(requestBytes: response, tempFile: payload);
}
requestQueue.cancel();
payloadQueue.cancel();
});
} }
//Listen to the incoming stream and emits event whenever there is a intact request //Listen to the incoming stream and emits event whenever there is a intact request
void socketHandler(Uint8List fetchedData) { void _pullRequest(Uint8List fetchedData) {
//Put incoming data into buffer //Put incoming data into buffer
buffer.addAll(fetchedData); buffer.addAll(fetchedData);
//Consume buffer until it's not enough for first 8 byte of a message //Consume buffer until it's not enough for first 8 byte of a message
while(true) { while(true) {
if(requestLength == 0 && payloadLength == 0 && _payloadStreamController.isClosed) { if(requestLength == 0 && payloadLength == 0 && _payloadPullStreamController.isClosed) {
//New request //New request
if(buffer.length >= 8) { if(buffer.length >= 8) {
//Buffered data has more than 8 bytes, enough to read request length and body length //Buffered data has more than 8 bytes, enough to read request length and body length
@ -68,24 +85,19 @@ class TCPController {
payloadLength = Uint8List.fromList(buffer.sublist(4, 8)).buffer.asInt32List()[0]; payloadLength = Uint8List.fromList(buffer.sublist(4, 8)).buffer.asInt32List()[0];
//Clear the length indicator bytes //Clear the length indicator bytes
buffer.removeRange(0, 8); buffer.removeRange(0, 8);
//Create temp file to read payload (might be huge)
var tempFile = File('${Directory.current.path}/.tmp/${DateTime.now().microsecondsSinceEpoch}')..createSync();
//Initialize payload transmission controller //Initialize payload transmission controller
_payloadStreamController = StreamController(); _payloadPullStreamController = StreamController();
//Create a future that listens to the status of the payload transmission //Create a future that listens to the status of the payload transmission
var payloadTransmission = Future(() async { () {
await for(var data in _payloadStreamController.stream) { var payloadPullStream = _payloadPullStreamController.stream;
await tempFile.writeAsBytes(data, mode: FileMode.append, flush: true); var tempFile = File('${Directory.current.path}/.tmp/${DateTime.now().microsecondsSinceEpoch}')..createSync();
} Future(() async {
}); await for(var data in payloadPullStream) {
//Bind request construction on stream await tempFile.writeAsBytes(data, mode: FileMode.append, flush: true);
_requestStreamController = StreamController(); }
_requestStreamController.stream.listen((requestBytes) { _payloadRawStreamController.add(tempFile);
//When request stream is closed by controller
payloadTransmission.then((_) {
_inStreamController.add(TCPRequest(requestBytes, tempFile));
}); });
}); }();
} }
else { else {
//Buffered data is not long enough //Buffered data is not long enough
@ -100,8 +112,7 @@ class TCPController {
if(buffer.length >= requestLength) { if(buffer.length >= requestLength) {
//Got intact request json //Got intact request json
//Emit request buffer through stream //Emit request buffer through stream
_requestStreamController.add(buffer.sublist(0, requestLength)); _requestRawStreamController.add(buffer.sublist(0, requestLength));
_requestStreamController.close();
//Remove proccessed buffer //Remove proccessed buffer
buffer.removeRange(0, requestLength); buffer.removeRange(0, requestLength);
//Clear awaiting request length //Clear awaiting request length
@ -118,18 +129,18 @@ class TCPController {
if(buffer.length >= payloadLength) { if(buffer.length >= payloadLength) {
//Last few bytes to emit //Last few bytes to emit
//Send the last few bytes to stream //Send the last few bytes to stream
_payloadStreamController.add(Uint8List.fromList(buffer.sublist(0, payloadLength))); _payloadPullStreamController.add(Uint8List.fromList(buffer.sublist(0, payloadLength)));
//Clear buffer //Clear buffer
buffer.removeRange(0, payloadLength); buffer.removeRange(0, payloadLength);
//Set payload length to zero //Set payload length to zero
payloadLength = 0; payloadLength = 0;
//Close the payload transmission stream //Close the payload transmission stream
_payloadStreamController.close(); _payloadPullStreamController.close();
} }
else { else {
//Part of payload //Part of payload
//Transmit all to stream //Transmit all to stream
_payloadStreamController.add(Uint8List.fromList(buffer)); _payloadPullStreamController.add(Uint8List.fromList(buffer));
//Reduce payload bytes left //Reduce payload bytes left
payloadLength -= buffer.length; payloadLength -= buffer.length;
//Clear buffer //Clear buffer
@ -141,4 +152,11 @@ class TCPController {
} }
} }
} }
Future<void> _pushRequest({
required List<int> requestBytes,
required File tempFile
}) async {
_requestStreamController.add(TCPRequest(requestBytes, tempFile));
}
} }

View File

@ -23,7 +23,7 @@ packages:
source: hosted source: hosted
version: "2.3.1" version: "2.3.1"
async: async:
dependency: transitive dependency: "direct main"
description: description:
name: async name: async
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"

View File

@ -12,6 +12,7 @@ dependencies:
sqflite_common: ^2.3.0 sqflite_common: ^2.3.0
crypto: ^3.0.2 crypto: ^3.0.2
convert: ^3.0.2 convert: ^3.0.2
async: ^2.9.0
# dependencies: # dependencies:
# path: ^1.8.0 # path: ^1.8.0