mconnect - KDE Connect protocol implementation in Vala/C
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

198 lines
5.1 KiB

/* ex:ts=4:sw=4:sts=4:et */
/* -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* AUTHORS
* Maciek Borzecki <maciek.borzecki (at] gmail.com>
*/
using MConn;
/**
* Device communication channel
*
* Automatically handle channel encoding.
*/
class DeviceChannel : Object {
public signal void connected();
public signal void disconnected();
public signal void packet_received(Packet pkt);
private InetSocketAddress _isa = null;
private SocketConnection _conn = null;
private DataOutputStream _dout = null;
private DataInputStream _din = null;
private uint _srcid = 0;
// channel encryption method
private Crypt _crypt = null;
public DeviceChannel(InetAddress host, uint port, Crypt crypt) {
_isa = new InetSocketAddress(host, (uint16) port);
_crypt = crypt;
}
public async void open() {
assert(this._isa != null);
var client = new SocketClient();
try {
_conn = yield client.connect_async(_isa);
} catch (Error e) {
//
critical("failed to connect to %s:%u: %s",
_isa.address.to_string(), _isa.port,
e.message);
return;
// TODO emit disconnected signal?
}
debug("connected to %s:%u", _isa.address.to_string(), _isa.port);
// use data streams
_dout = new DataOutputStream(_conn.output_stream);
_din = new DataInputStream(_conn.input_stream);
// messages end with \n\n
_din.set_newline_type(DataStreamNewlineType.LF);
// setup socket monitoring
Socket sock = _conn.get_socket();
// enable keepalive
sock.set_keepalive(true);
// prep source for monitoring events
SocketSource source = sock.create_source(IOCondition.IN | IOCondition.ERR |
IOCondition.HUP);
source.set_callback((src, cond) => {
this._io_ready();
return true;
});
// attach source
_srcid = source.attach(null);
connected();
}
public async void close() {
debug("closing connection");
if (_srcid > 0) {
Source.remove(_srcid);
_srcid = 0;
}
_din.close();
_dout.close();
_conn.close();
_din = null;
_dout = null;
_conn = null;
}
/**
* send:
* Possibly blocking
*
* @param: instance of Packet
**/
public async void send(Packet pkt) {
string to_send = pkt.to_string() + "\n";
debug("send data: %s", to_send);
// _dout.put_string(data);
try {
_dout.put_string(to_send);
} catch (IOError e) {
critical("failed to send message: %s", e.message);
// TODO disconnect?
}
}
public async void receive() throws Error {
size_t line_len;
// read line up to newline
string data = yield _din.read_upto_async("\n", -1,
Priority.DEFAULT,
null,
out line_len);
debug("received line: %s", data);
// expecting \n\n
_din.read_byte();
_din.read_byte();
Packet pkt = Packet.new_from_data(data);
if (pkt == null) {
critical("failed to build packet from data");
return;
}
handle_packet(pkt);
}
private async void _io_ready() {
debug("check for IO");
try {
debug("try read");
this.receive();
} catch (Error e) {
critical("error occurred: %d: %s", e.code, e.message);
}
}
private void handle_packet(Packet pkt) {
debug("handle packet of type: %s", pkt.pkt_type);
if (pkt.pkt_type == Packet.ENCRYPTED) {
handle_encrypted_packet(pkt);
} else {
// signal that we got a packet
packet_received(pkt);
}
}
private void handle_encrypted_packet(Packet pkt) {
// Ecypted packets have 'data' member in body. The 'data'
// member is an array of strings, each string is base64
// encoded data, of length appropriate for channel ecryption
// method.
Json.Array arr = pkt.body.get_array_member("data");
if (arr == null) {
critical("missing data member in encrypted packet");
return;
}
var msgbytes = new ByteArray();
arr.foreach_element((a, i, node) => {
debug("node data: %s", node.get_string());
// encrypted data is base64 encoded
uchar[] data = Base64.decode(node.get_string());
var dbytes = new Bytes.take(data);
ByteArray decrypted = this._crypt.decrypt(dbytes);
debug("data length: %zu", decrypted.data.length);
msgbytes.append(decrypted.data);
});
// data should be complete now
debug("total length of packet data: %zu", msgbytes.len);
// make sure there is \0 at the end
msgbytes.append({'\0'});
string decrypted_data = ((string)msgbytes.data).dup();
debug("decrypted data: %s", decrypted_data);
Packet dec_pkt = Packet.new_from_data(decrypted_data);
if (dec_pkt == null) {
critical("failed to parse decrypted packet");
} else {
packet_received(dec_pkt);
}
}
}