|
|
- /* 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);
- }
- }
- }
|