Created
September 18, 2017 18:07
-
-
Save andyholmes/159a6fb628dcc926518d1b129e345f47 to your computer and use it in GitHub Desktop.
KDEConnect Device Channel in GJS
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"use strict"; | |
// Imports | |
const Lang = imports.lang; | |
const Gio = imports.gi.Gio; | |
const GLib = imports.gi.GLib; | |
const GObject = imports.gi.GObject; | |
/** DeviceChannel */ | |
var DeviceChannel = new Lang.Class({ | |
Name: "JSConnectDeviceChannel", | |
Extends: GObject.Object, | |
Signals: { | |
"connected": { | |
flags: GObject.SignalFlags.RUN_FIRST | GObject.SignalFlags.DETAILED | |
}, | |
"disconnected": { | |
flags: GObject.SignalFlags.RUN_FIRST | GObject.SignalFlags.DETAILED | |
}, | |
"received": { | |
flags: GObject.SignalFlags.RUN_FIRST | GObject.SignalFlags.DETAILED, | |
param_types: [ GObject.TYPE_OBJECT ] | |
} | |
}, | |
_init: function (device) { | |
this.parent(); | |
this.device = device; | |
this._connection = null; | |
this._in = null; | |
this._out = null; | |
this._socket = null; | |
this._monitor = 0; | |
this._peer_cert = null; | |
}, | |
open: function () { | |
log("DeviceChannel.open(" + this.device.id + ")"); | |
this._socket = new Gio.Socket({ | |
family: Gio.SocketFamily.IPV4, | |
type: Gio.SocketType.STREAM, | |
protocol: Gio.SocketProtocol.TCP | |
}); | |
// Setup socket | |
this._socket.init(null); | |
this._socket.set_keepalive(true); | |
this._socket.set_option(6, 4, 10); // TCP_KEEPIDLE | |
this._socket.set_option(6, 5, 5); // TCP_KEEPINTVL | |
this._socket.set_option(6, 6, 3); // TCP_KEEPCNT | |
let tcpConnection = new Gio.TcpConnection({ socket: this._socket }); | |
tcpConnection.connect_async( | |
new Gio.InetSocketAddress({ | |
address: Gio.InetAddress.new_from_string( | |
this.device.identity.body.tcpHost | |
), | |
port: this.device.identity.body.tcpPort | |
}), | |
null, | |
Lang.bind(this, this.opened) | |
); | |
}, | |
opened: function (tcpConnection, res) { | |
log("DeviceChannel.opened(" + this.device.id + ")"); | |
try { | |
if (!tcpConnection.connect_finish(res)) { | |
throw Error("failed to connect"); | |
} | |
} catch (e) { | |
log("Error finishing connection: " + e); | |
this.emit("disconnected"); // device will call this.close() | |
return; | |
} | |
// Send identity packet, indicating we're about ready for a handshake | |
let ident = new Packet.IdentityPacket(this.device.daemon); | |
let _out = new Gio.DataOutputStream({ | |
base_stream: new Gio.UnixOutputStream({ | |
fd: this._socket.fd, | |
close_fd: false | |
}) | |
}); | |
_out.put_string(ident.toData(), null); | |
_out.close(null); | |
// Wrap the TcpConnection in TlsServerConnection and handshake | |
this._connection = Gio.TlsServerConnection.new( | |
tcpConnection, | |
this.device.daemon.certificate | |
); | |
this._connection.validation_flags = 0; | |
this._connection.authentication_mode = 1; | |
// Handle certificate | |
this._connection.connect("accept-certificate", (conn, peer_cert, flags) => { | |
if (this.device.paired) { | |
log("verifying certificate..."); | |
let paired_cert = Gio.TlsCertificate.new_from_file( | |
this.device.config_cert // Locally certificate generated with openssl, as in KDE Connect code | |
); | |
return (paired_cert.verify(null, peer_cert) === 0); | |
} else { | |
log("delaying certificate verification..."); | |
this._peer_cert = peer_cert; | |
return true; | |
} | |
}); | |
try { | |
this._connection.handshake(null); | |
} catch (e) { | |
log("Error during TLS handshake: " + e); | |
this.emit("disconnected"); // device will call this.close() | |
return false; | |
} | |
// Open In/Out Streams | |
this._in = new Gio.DataInputStream({ | |
base_stream: this._connection.input_stream, | |
newline_type: Gio.DataStreamNewlineType.LF | |
}); | |
this._out = new Gio.DataOutputStream({ | |
base_stream: this._connection.output_stream | |
}); | |
// Listen for packets | |
this._monitor = this._in.base_stream.create_source(null); | |
this._monitor.set_callback((condition) => { | |
let result = this.receive(); | |
if (!result) { this.emit("disconnected"); } // device will call this.close() | |
return result; | |
}); | |
this._monitor.attach(null); | |
this.emit("connected"); | |
return true; | |
}, | |
close: function () { | |
log("DeviceChannel.close(" + this.device.id + ")"); | |
if (this._monitor > 0) { | |
Gio.Source.remove(this._monitor); | |
this._monitor = 0; | |
} | |
try { | |
if (this._in !== null) { | |
this._in.close(null); | |
} | |
} catch (e) { | |
log("error closing data input: " + e); | |
} | |
try { | |
if (this._out !== null) { | |
this._out.close(null); | |
} | |
} catch (e) { | |
log("error closing data output: " + e); | |
} | |
try { | |
if (this._connection !== null) { | |
this._connection.close(null); | |
} | |
} catch (e) { | |
log("error closing connection: " + e); | |
} | |
try { | |
if (this._socket !== null) { | |
this._socket.close(null); | |
} | |
} catch (e) { | |
log("error closing socket: " + e); | |
} | |
this._in = null; | |
this._out = null; | |
this._connection = null; | |
this._socket = null; | |
}, | |
send: function (packet) { | |
log("DeviceChannel.send(" + packet.toString() + ")"); | |
try { | |
this._out.put_string(packet.toData(), null); | |
} catch (e) { | |
log("error sending packet: " + e); | |
// TODO: disconnect? check kdeconnect code | |
} | |
}, | |
receive: function () { | |
log("DeviceChannel.receive(" + this.device.id + ")"); | |
let data, len, packet; | |
try { | |
[data, len] = this._in.read_line(null); | |
} catch (e) { | |
log("Failed to receive packet: " + e); | |
this.emit("disconnected"); | |
} | |
if (data === null || data === undefined || !data.length) { | |
return false; | |
} | |
packet = new Packet.BasePacket(data); | |
this.emit("received", packet); // device will handle packet | |
return true; | |
} | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment