Compare commits

..

No commits in common. "main" and "v1.1.1" have entirely different histories.
main ... v1.1.1

7 changed files with 85 additions and 280 deletions

View File

@ -1,14 +0,0 @@
# Files and directories created by pub.
Dockerfile
build/
.dart_tool/
.git/
.github/
.gitignore
.packages
.data/
.tmp/
# Conventional directory for build output.
build/

View File

@ -1,8 +1,8 @@
# Specify the Dart SDK base image version using dart:<version> (ex: dart:2.12) # Specify the Dart SDK base image version using dart:<version> (ex: dart:2.12)
FROM dart:stable AS compile FROM dart:stable AS build
# Resolve app dependencies. # Resolve app dependencies.
WORKDIR /lchatserver WORKDIR /app
COPY pubspec.* ./ COPY pubspec.* ./
RUN dart pub get RUN dart pub get
@ -10,18 +10,14 @@ RUN dart pub get
COPY . . COPY . .
# Ensure packages are still up-to-date if anything has changed # Ensure packages are still up-to-date if anything has changed
RUN dart pub get --offline RUN dart pub get --offline
RUN dart compile exe bin/tcp_server.dart -o bin/tcp_server RUN dart compile exe bin/server.dart -o bin/server
FROM ubuntu:latest # Build minimal serving image from AOT-compiled `/server` and required system
# libraries and configuration files stored in `/runtime/` from the build stage.
RUN apt-get update && apt-get -y install libsqlite3-0 libsqlite3-dev FROM scratch
COPY --from=build /runtime/ /
# Copy the previously built executable into the scratch layer COPY --from=build /app/bin/server /app/bin/
RUN mkdir /lchatserver
COPY --from=compile /runtime/ /lchatserver/
COPY --from=compile /lchatserver/bin/tcp_server /lchatserver/bin/
# Start server. # Start server.
EXPOSE 20706 EXPOSE 20706
WORKDIR /lchatserver/bin CMD ["/app/bin/server"]
CMD ["/lchatserver/bin/tcp_server"]

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-23 10:33:58 * @LastEditTime : 2022-10-17 22:56:11
* @Description : * @Description :
*/ */
@ -15,21 +15,20 @@ import 'package:tcp_server/tcpcontroller/request.dart';
import 'package:tcp_server/tcpcontroller/response.dart'; import 'package:tcp_server/tcpcontroller/response.dart';
void main(List<String> arguments) async { void main(List<String> arguments) async {
//Set port
var address = arguments.isEmpty ? '127.0.0.1' : arguments[0];
//Set address //Set address
var port = arguments.isEmpty ? 20706 : int.tryParse(arguments[0]) ?? 20706; var port = arguments.length < 2 ? 20706 : int.tryParse(arguments[1]) ?? 20706;
print('[L] [STARTUP ]-----------------------');
print('[L] Running at directory ${Directory.current.path}');
//Create nessesary working directories //Create nessesary working directories
await Directory('${Directory.current.path}/.tmp').create();
await Directory('${Directory.current.path}/.data').create(); await Directory('${Directory.current.path}/.data').create();
await Directory('${Directory.current.path}/.data/.tmp').create();
await Directory('${Directory.current.path}/.data/files').create(); await Directory('${Directory.current.path}/.data/files').create();
await DataBaseHelper().initialize(); await DataBaseHelper().initialize();
Map<int, List<TCPController>> tokenMap = {}; Map<int, List<TCPController>> tokenMap = {};
Map<TCPController, Future<int>> controllerMap = {}; Map<TCPController, Future<int>> controllerMap = {};
var listenSocket = await ServerSocket.bind(InternetAddress.anyIPv4, port); var listenSocket = await ServerSocket.bind(address, port);
listenSocket.listen( listenSocket.listen(
(socket) { (socket) {
var controller = TCPController(socket: socket); var controller = TCPController(socket: socket);
@ -38,31 +37,17 @@ void main(List<String> arguments) async {
onError: (_) { onError: (_) {
print('[L] [EXCEPTION]-----------------------'); print('[L] [EXCEPTION]-----------------------');
print('[L] TCP Controller ran into exception'); print('[L] TCP Controller ran into exception');
print('[L] socket: ${controller.socket.address}:${controller.socket.port}'); print('[L] Remote: ${controller.socket.remoteAddress}:${controller.socket.remotePort}');
var token = controllerMap[controller]; var token = controllerMap[controller];
controllerMap.remove(controller); controllerMap.remove(controller);
tokenMap[token]?.remove(controller); tokenMap[token]?.remove(controller);
}, }
onDone: () {
var token = controllerMap[controller];
controllerMap.remove(controller);
tokenMap[token]?.remove(controller);
},
cancelOnError: true
); );
controller.requestStreamBroadcast.listen( controller.requestStreamBroadcast.listen(
(request) async { (request) async {
print('[L] [INCOMING ]-----------------------'); print('[L] [INCOMING ]-----------------------');
print('[L] Incoming from ${controller.socket.remoteAddress}:${controller.socket.remotePort}'); print('[L] Incoming from ${controller.socket.remoteAddress}:${controller.socket.remotePort}');
if(request.requestType == RequestType.sendMessage) { print('[L] Message: ${request.toJSON}');
print('[L] Message: (Message body)');
}
else if(request.requestType == RequestType.modifyProfile) {
print('[L] Profile: (Profile body)');
}
else {
print('[L] Message: ${request.toJSON}');
}
if(!(await DataBaseHelper().isTokenValid(tokenid: request.tokenID))) { 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()))();
@ -84,38 +69,17 @@ void main(List<String> arguments) async {
switch(request.requestType) { switch(request.requestType) {
case RequestType.checkState: { case RequestType.checkState: {
var response = await onCheckState(request, socket); var response = await onCheckState(request, socket);
try { controller.outStream.add(response);
controller.outStream.add(response);
} catch (e) {
print('[E] [EXCEPTION]-----------------------');
var token = controllerMap[controller];
controllerMap.remove(controller);
tokenMap[token]?.remove(controller);
}
break; break;
} }
case RequestType.register: { case RequestType.register: {
var response = await onRegister(request, socket); var response = await onRegister(request, socket);
try { controller.outStream.add(response);
controller.outStream.add(response);
} catch (e) {
print('[E] [EXCEPTION]-----------------------');
var token = controllerMap[controller];
controllerMap.remove(controller);
tokenMap[token]?.remove(controller);
}
break; break;
} }
case RequestType.login: { case RequestType.login: {
var response = await onLogin(request, socket); var response = await onLogin(request, socket);
try { controller.outStream.add(response);
controller.outStream.add(response);
} catch (e) {
print('[E] [EXCEPTION]-----------------------');
var token = controllerMap[controller];
controllerMap.remove(controller);
tokenMap[token]?.remove(controller);
}
break; break;
} }
case RequestType.logout: { case RequestType.logout: {
@ -125,38 +89,17 @@ void main(List<String> arguments) async {
} }
case RequestType.profile: { case RequestType.profile: {
var response = await onFetchProfile(request, socket); var response = await onFetchProfile(request, socket);
try { controller.outStream.add(response);
controller.outStream.add(response);
} catch (e) {
print('[E] [EXCEPTION]-----------------------');
var token = controllerMap[controller];
controllerMap.remove(controller);
tokenMap[token]?.remove(controller);
}
break; break;
} }
case RequestType.modifyProfile: { case RequestType.modifyProfile: {
var response = await onModifyProfile(request, socket); var response = await onModifyProfile(request, socket);
try { controller.outStream.add(response);
controller.outStream.add(response);
} catch (e) {
print('[E] [EXCEPTION]-----------------------');
var token = controllerMap[controller];
controllerMap.remove(controller);
tokenMap[token]?.remove(controller);
}
break; break;
} }
case RequestType.modifyPassword: { case RequestType.modifyPassword: {
var response = await onModifyPassword(request, socket); var response = await onModifyPassword(request, socket);
try { controller.outStream.add(response);
controller.outStream.add(response);
} catch (e) {
print('[E] [EXCEPTION]-----------------------');
var token = controllerMap[controller];
controllerMap.remove(controller);
tokenMap[token]?.remove(controller);
}
break; break;
} }
case RequestType.sendMessage: { case RequestType.sendMessage: {
@ -180,22 +123,16 @@ void main(List<String> arguments) async {
); );
for(var controller in targetControllers) { for(var controller in targetControllers) {
try { try {
print('[L] [MSGFOWARD]-----------------------');
print('[L] Forwarding message to ${controller.socket.remoteAddress}:${controller.socket.remotePort}');
controller.outStream.add(forwardResponse); controller.outStream.add(forwardResponse);
} catch(e) { } catch(e) {
print('[E] [EXCEPTION]-----------------------'); print(e);
var token = controllerMap[controller];
controllerMap.remove(controller);
tokenMap[token]?.remove(controller);
continue;
} }
} }
// //Update Fetch Histories //Update Fetch Histories
// await DataBaseHelper().setFetchHistoryFor( await DataBaseHelper().setFetchHistoryFor(
// tokenID: device, tokenID: device,
// newTimeStamp: message.timestamp newTimeStamp: message.timestamp
// ); );
} }
var targetUserID = message.receiverID; var targetUserID = message.receiverID;
var targetDevices = await DataBaseHelper().fetchTokenIDsViaUserID(userID: targetUserID); var targetDevices = await DataBaseHelper().fetchTokenIDsViaUserID(userID: targetUserID);
@ -218,121 +155,51 @@ void main(List<String> arguments) async {
var token = controllerMap[controller]; var token = controllerMap[controller];
controllerMap.remove(controller); controllerMap.remove(controller);
tokenMap[token]?.remove(controller); tokenMap[token]?.remove(controller);
continue;
} }
} }
// //Update Fetch Histories //Update Fetch Histories
// await DataBaseHelper().setFetchHistoryFor( await DataBaseHelper().setFetchHistoryFor(
// tokenID: device, tokenID: device,
// newTimeStamp: message.timestamp newTimeStamp: message.timestamp
// ); );
} }
var response = await onSendMessage(request, socket); var response = await onSendMessage(request, socket);
try { controller.outStream.add(response);
controller.outStream.add(response);
} catch (e) {
print('[E] [EXCEPTION]-----------------------');
var token = controllerMap[controller];
controllerMap.remove(controller);
tokenMap[token]?.remove(controller);
}
break; break;
} }
case RequestType.fetchMessage: { case RequestType.fetchMessage: {
var response = await onFetchMessage(request, socket); var response = await onFetchMessage(request, socket);
try { controller.outStream.add(response);
controller.outStream.add(response);
} catch (e) {
print('[E] [EXCEPTION]-----------------------');
var token = controllerMap[controller];
controllerMap.remove(controller);
tokenMap[token]?.remove(controller);
}
break; break;
} }
case RequestType.findFile: { case RequestType.findFile: {
var response = await onFindFile(request, socket); var response = await onFindFile(request, socket);
try { controller.outStream.add(response);
controller.outStream.add(response);
} catch (e) {
print('[E] [EXCEPTION]-----------------------');
var token = controllerMap[controller];
controllerMap.remove(controller);
tokenMap[token]?.remove(controller);
}
break; break;
} }
case RequestType.fetchFile: { case RequestType.fetchFile: {
var response = await onFetchFile(request, socket); var response = await onFetchFile(request, socket);
try { controller.outStream.add(response);
controller.outStream.add(response);
} catch (e) {
print('[E] [EXCEPTION]-----------------------');
var token = controllerMap[controller];
controllerMap.remove(controller);
tokenMap[token]?.remove(controller);
}
break; break;
} }
case RequestType.searchUser: { case RequestType.searchUser: {
var response = await onSearchUser(request, socket); var response = await onSearchUser(request, socket);
try { controller.outStream.add(response);
controller.outStream.add(response);
} catch (e) {
print('[E] [EXCEPTION]-----------------------');
var token = controllerMap[controller];
controllerMap.remove(controller);
tokenMap[token]?.remove(controller);
}
break; break;
} }
case RequestType.addContact: { case RequestType.addContact: {
var response = await onAddContact(request, socket); var response = await onAddContact(request, socket);
try { controller.outStream.add(response);
controller.outStream.add(response);
} catch (e) {
print('[E] [EXCEPTION]-----------------------');
var token = controllerMap[controller];
controllerMap.remove(controller);
tokenMap[token]?.remove(controller);
}
var contactResponse = await onFetchContact(
TCPRequest.fromData(
type: RequestType.fetchContact,
body: {},
tokenID: request.tokenID
),
socket
);
controller.outStream.add(contactResponse);
break; break;
} }
case RequestType.fetchContact: { case RequestType.fetchContact: {
var response = await onFetchContact(request, socket); var response = await onFetchContact(request, socket);
try { controller.outStream.add(response);
controller.outStream.add(response);
} catch (e) {
print('[E] [EXCEPTION]-----------------------');
var token = controllerMap[controller];
controllerMap.remove(controller);
tokenMap[token]?.remove(controller);
}
break;
}
case RequestType.ackFetch: {
onAckFetch(request, socket);
break; break;
} }
case RequestType.unknown: { case RequestType.unknown: {
var response = await onUnknownRequest(request, socket); var response = await onUnknownRequest(request, socket);
try { controller.outStream.add(response);
controller.outStream.add(response);
} catch (e) {
print('[E] [EXCEPTION]-----------------------');
var token = controllerMap[controller];
controllerMap.remove(controller);
tokenMap[token]?.remove(controller);
}
break; break;
} }
default: { default: {

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-22 21:08:27 * @LastEditTime : 2022-10-18 14:12:22
* @Description : * @Description :
*/ */
@ -25,7 +25,7 @@ class DataBaseHelper {
Future<void> initialize() async { Future<void> initialize() async {
_database = await databaseFactoryFfi.openDatabase( _database = await databaseFactoryFfi.openDatabase(
'${Directory.current.path}/.data/.tmp/database.db', '${Directory.current.path}/.tmp/database.db',
options: OpenDatabaseOptions( options: OpenDatabaseOptions(
version: 1, version: 1,
onCreate: (db, version) async { onCreate: (db, version) async {
@ -34,7 +34,7 @@ class DataBaseHelper {
''' '''
CREATE TABLE users ( CREATE TABLE users (
userid integer primary key autoincrement, userid integer primary key autoincrement,
username text unique not null, username text not null,
passwd text not null, passwd text not null,
avatar text avatar text
); );
@ -241,29 +241,17 @@ class DataBaseHelper {
//Insert into users //Insert into users
try { try {
await _database.transaction((txn) async { await _database.insert(
var result = await txn.query( 'users',
'users', {
where: 'username = ?', 'username': identity.userName,
whereArgs: [ 'passwd': identity.userPasswd,
identity.userName 'avatar': null
] },
); conflictAlgorithm: ConflictAlgorithm.rollback
if(result.isNotEmpty) { );
throw Exception('Username already exists'); } catch (conflict) {
} throw Exception(['Database failure', conflict.toString()]);
await txn.insert(
'users',
{
'username': identity.userName,
'passwd': identity.userPasswd,
'avatar': null
},
conflictAlgorithm: ConflictAlgorithm.rollback
);
});
} catch (e) {
rethrow;
} }
//Get new userid //Get new userid
@ -370,7 +358,7 @@ class DataBaseHelper {
//Fetch unfetched messages //Fetch unfetched messages
var unfetchMsgQueryResult = await _database.query( var unfetchMsgQueryResult = await _database.query(
'msgs left outer join msgfiles on msgs.md5encoded = msgfiles.msgmd5', 'msgs join msgfiles on msgs.md5encoded = msgfiles.msgmd5',
columns: [ columns: [
'msgs.userid as userid', 'msgs.userid as userid',
'msgs.targetid as targetid', 'msgs.targetid as targetid',
@ -403,19 +391,19 @@ class DataBaseHelper {
}).toList(); }).toList();
//Set new fetch history //Set new fetch history
// if(unfetchMsgQueryResult.isNotEmpty) { if(unfetchMsgQueryResult.isNotEmpty) {
// await _database.update( await _database.update(
// 'histories', 'histories',
// { {
// 'lastfetch': unfetchMsgQueryResult[0]['timestamp'] 'lastfetch': unfetchMsgQueryResult[0]['timestamp']
// }, },
// where: 'tokenid = ? and userid = ?', where: 'tokenid = ? and userid = ?',
// whereArgs: [ whereArgs: [
// tokenID, tokenID,
// userID userID
// ] ]
// ); );
// } }
//return result //return result
return unfetchMessages; return unfetchMessages;
@ -629,10 +617,6 @@ class DataBaseHelper {
'username': userInfo.userName, 'username': userInfo.userName,
'avatar': userInfo.userAvatar 'avatar': userInfo.userAvatar
}, },
where: 'userid = ?',
whereArgs: [
currentUserID
],
conflictAlgorithm: ConflictAlgorithm.rollback conflictAlgorithm: ConflictAlgorithm.rollback
); );
} catch (conflict) { } catch (conflict) {

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-22 20:56:15 * @LastEditTime : 2022-10-15 00:40:24
* @Description : * @Description :
*/ */
@ -276,14 +276,6 @@ Future<TCPResponse> onFetchContact(TCPRequest request, Socket socket) async {
} }
} }
void onAckFetch(TCPRequest request, Socket socket) async {
//Update Fetch Histories
await DataBaseHelper().setFetchHistoryFor(
tokenID: request.tokenID,
newTimeStamp: request.body['timestamp'] as int,
);
}
Future<TCPResponse> onUnknownRequest(TCPRequest request, Socket socket) async { Future<TCPResponse> onUnknownRequest(TCPRequest request, Socket socket) async {
return TCPResponse( return TCPResponse(
type: ResponseType.fromRequestType(request.requestType), type: ResponseType.fromRequestType(request.requestType),

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-22 21:20:57 * @LastEditTime : 2022-10-19 10:41:12
* @Description : * @Description :
*/ */
@ -58,21 +58,18 @@ class TCPController {
print('[L] Remote: ${socket.remoteAddress}:${socket.remotePort}'); print('[L] Remote: ${socket.remoteAddress}:${socket.remotePort}');
print('[L] Local: ${socket.address}:${socket.port}'); print('[L] Local: ${socket.address}:${socket.port}');
Future(() async { Future(() async {
try { await for(var request in socket) {
await for(var request in socket) { _pullRequest(request);
_pullRequest(request); await Future.delayed(const Duration(microseconds: 0));
await Future.delayed(const Duration(microseconds: 0));
}
} catch (e) {
_requestStreamController.addError(e);
_responseStreamController.addError(e);
} }
}).then((_) { }).then((_) {
print('[L] [CLOSED ]-----------------------'); print('[L] [CLOSED ]-----------------------');
print('[L] Connection closed: ${socket.address}:${socket.port}'); print('[L] Connection closed: ${socket.address}:${socket.port}<-${socket.remoteAddress}:${socket.remotePort}');
_requestStreamController.close(); _requestStreamController.close();
_responseStreamController.close(); }).onError((error, stackTrace) {
}); print(error);
_requestStreamController.addError(error ?? Error());
},);
// socket.listen( // socket.listen(
// _pullRequest, // _pullRequest,
// onError: (e) { // onError: (e) {
@ -93,8 +90,7 @@ class TCPController {
await socket.addStream(response.stream); await socket.addStream(response.stream);
} }
} catch (e) { } catch (e) {
print('[E] [EXCEPTION]-----------------------'); print(e);
print('[E] Adding bytes to socket stream failed');
await socket.flush(); await socket.flush();
socket.close(); socket.close();
} }
@ -136,7 +132,7 @@ class TCPController {
//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 payloadPullStream = _payloadPullStreamController.stream; var payloadPullStream = _payloadPullStreamController.stream;
var tempFile = File('${Directory.current.path}/.data/.tmp/${DateTime.now().microsecondsSinceEpoch}$_fileCounter')..createSync(); var tempFile = File('${Directory.current.path}/.tmp/${DateTime.now().microsecondsSinceEpoch}$_fileCounter')..createSync();
_fileCounter += 1; _fileCounter += 1;
_fileCounter %= 1000; _fileCounter %= 1000;
Future(() async { Future(() async {
@ -205,11 +201,6 @@ class TCPController {
required List<int> requestBytes, required List<int> requestBytes,
required File tempFile required File tempFile
}) async { }) async {
try{ _requestStreamController.add(TCPRequest(requestBytes, tempFile));
_requestStreamController.add(TCPRequest(requestBytes, tempFile));
} catch (e) {
print('[E] [EXCEPTION]-----------------------');
print('[E] Adding bytes to request stream failed');
}
} }
} }

View File

@ -1,7 +1,7 @@
/* /*
* @Author : Linloir * @Author : Linloir
* @Date : 2022-10-08 15:14:26 * @Date : 2022-10-08 15:14:26
* @LastEditTime : 2022-10-22 20:54:40 * @LastEditTime : 2022-10-09 22:56:26
* @Description : * @Description :
*/ */
import 'dart:convert'; import 'dart:convert';
@ -16,7 +16,6 @@ enum RequestType {
modifyPassword('MODIFYPASSWD'), //Modify user password modifyPassword('MODIFYPASSWD'), //Modify user password
modifyProfile ('MODIFYPROFILE'), //Modify user profile modifyProfile ('MODIFYPROFILE'), //Modify user profile
sendMessage ('SENDMSG'), //Send message sendMessage ('SENDMSG'), //Send message
ackFetch ('ACKFETCH'), //Ack fetched messages, update fetch history
fetchMessage ('FETCHMSG'), //Fetch message fetchMessage ('FETCHMSG'), //Fetch message
findFile ('FINDFILE'), //Find file by md5 before transmitting the file findFile ('FINDFILE'), //Find file by md5 before transmitting the file
fetchFile ('FETCHFILE'), //Fetch file and file md5 by message md5 fetchFile ('FETCHFILE'), //Fetch file and file md5 by message md5
@ -41,16 +40,6 @@ class TCPRequest {
File? payload; File? payload;
TCPRequest(List<int> data, this.payload): _data = jsonDecode(String.fromCharCodes(data)); TCPRequest(List<int> data, this.payload): _data = jsonDecode(String.fromCharCodes(data));
TCPRequest.fromData({
required RequestType type,
required Map<String, Object?> body,
required int? tokenID,
this.payload
}): _data = {
'request': type.value,
'tokenid': tokenID,
'body': body
};
TCPRequest.none(): _data = {}; TCPRequest.none(): _data = {};
String get toJSON => jsonEncode(_data); String get toJSON => jsonEncode(_data);