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