Files
spicetools/api/resources/dart/spiceapi-websocket/src/connection.dart

194 lines
4.1 KiB
Dart

part of spiceapi;
class Connection {
// settings
static const _TIMEOUT = Duration(seconds: 2);
static const _BUFFER_SIZE = 1024 * 8;
// state
final String host, pass;
final int port;
var resource;
List<int> _dataBuffer;
StreamController<Response> _responses;
StreamController<Connection> _connections;
WebSocket _socket;
RC4 _cipher;
bool _disposed = false;
Connection(this.host, this.port, this.pass,
{this.resource, bool refreshSession=true}) {
// initialize
_dataBuffer = List<int>();
_responses = StreamController<Response>.broadcast();
_connections = StreamController<Connection>.broadcast();
if (pass.length > 0)
_cipher = RC4(utf8.encode(pass));
// initialize socket
this._socket = WebSocket("ws://$host:${port + 1}");
this._socket.binaryType = "arraybuffer";
// listen to events
this._socket.onOpen.listen((e) async {
// refresh session
bool error = false;
if (refreshSession) {
try {
await controlRefreshSession(this);
} on Error {
error = true;
} on TimeoutException {
error = true;
}
}
// mark as connected
if (!this._connections.isClosed)
this._connections.add(this);
if (error)
this.dispose();
});
this._socket.onMessage.listen((e) {
// get data
var data = e.data;
if (data is ByteBuffer)
data = data.asUint8List();
// check type
if (data is List<int>) {
// cipher
if (_cipher != null)
_cipher.crypt(data);
// add data to buffer
_dataBuffer.addAll(data);
// check buffer size
if (_dataBuffer.length > _BUFFER_SIZE) {
this.dispose();
return;
}
// check for completed message
for (int i = 0; i < _dataBuffer.length; i++) {
if (_dataBuffer[i] == 0) {
// get message data and remove from buffer
var msgData = List<int>.from(_dataBuffer.getRange(0, i));
_dataBuffer.removeRange(0, i + 1);
// check data length
if (msgData.length > 0) {
// convert to JSON
var msgStr = utf8.decode(msgData, allowMalformed: false);
// build response
var res = Response.fromJson(msgStr);
this._responses.add(res);
}
}
}
}
});
this._socket.onClose.listen((e) {
this.dispose();
});
this._socket.onError.listen((e) {
this.dispose();
});
}
void changePass(String pass) {
if (pass.length > 0)
_cipher = RC4(utf8.encode(pass));
else
_cipher = null;
}
void dispose() {
if (_socket != null)
_socket.close();
_socket = null;
if (_responses != null)
_responses.close();
if (_connections != null)
_connections.close();
this._disposed = true;
this.free();
}
bool isDisposed() {
return this._disposed;
}
void free() {
// release optional resource
if (this.resource != null) {
this.resource.release();
this.resource = null;
}
}
bool isFree() {
return this.resource == null;
}
Future<Connection> onConnect() {
return _connections.stream.first;
}
bool isValid() {
return this._socket != null && !this._disposed;
}
Future<Response> request(Request req) {
// add response listener
var res = _awaitResponse(req._id);
// write request
_writeRequest(req);
// return future response
return res.then((res) {
// validate first
res.validate();
// return it
return res;
});
}
void _writeRequest(Request req) async {
// convert to JSON
var json = req.toJson() + "\x00";
var jsonEncoded = utf8.encode(json);
// cipher
if (_cipher != null)
_cipher.crypt(jsonEncoded);
// write to socket
this._socket.sendByteBuffer(Int8List.fromList(jsonEncoded).buffer);
}
Future<Response> _awaitResponse(int id) {
return _responses.stream.timeout(_TIMEOUT).firstWhere(
(res) => res._id == id, orElse: null);
}
}