aboutsummaryrefslogtreecommitdiff
path: root/xmpp-vala/src/module/xep/0234_jingle_file_transfer.vala
diff options
context:
space:
mode:
Diffstat (limited to 'xmpp-vala/src/module/xep/0234_jingle_file_transfer.vala')
-rw-r--r--xmpp-vala/src/module/xep/0234_jingle_file_transfer.vala168
1 files changed, 128 insertions, 40 deletions
diff --git a/xmpp-vala/src/module/xep/0234_jingle_file_transfer.vala b/xmpp-vala/src/module/xep/0234_jingle_file_transfer.vala
index 1c0323be..4581019f 100644
--- a/xmpp-vala/src/module/xep/0234_jingle_file_transfer.vala
+++ b/xmpp-vala/src/module/xep/0234_jingle_file_transfer.vala
@@ -7,50 +7,42 @@ namespace Xmpp.Xep.JingleFileTransfer {
private const string NS_URI = "urn:xmpp:jingle:apps:file-transfer:5";
public class Module : Jingle.ContentType, XmppStreamModule {
+
+ public signal void file_incoming(XmppStream stream, FileTransfer file_transfer);
+
public static Xmpp.ModuleIdentity<Module> IDENTITY = new Xmpp.ModuleIdentity<Module>(NS_URI, "0234_jingle_file_transfer");
+ public SessionInfoType session_info_type = new SessionInfoType();
public override void attach(XmppStream stream) {
stream.get_module(ServiceDiscovery.Module.IDENTITY).add_feature(stream, NS_URI);
stream.get_module(Jingle.Module.IDENTITY).register_content_type(this);
+ stream.get_module(Jingle.Module.IDENTITY).register_session_info_type(session_info_type);
}
public override void detach(XmppStream stream) {
stream.get_module(ServiceDiscovery.Module.IDENTITY).remove_feature(stream, NS_URI);
}
- public string content_type_ns_uri() {
- return NS_URI;
- }
- public Jingle.TransportType content_type_transport_type() {
- return Jingle.TransportType.STREAMING;
- }
+ public string ns_uri { get { return NS_URI; } }
+ public Jingle.TransportType required_transport_type { get { return Jingle.TransportType.STREAMING; } }
+ public uint8 required_components { get { return 1; } }
+
public Jingle.ContentParameters parse_content_parameters(StanzaNode description) throws Jingle.IqError {
return Parameters.parse(this, description);
}
- public void handle_content_session_info(XmppStream stream, Jingle.Session session, StanzaNode info, Iq.Stanza iq) throws Jingle.IqError {
- switch (info.name) {
- case "received":
- stream.get_module(Iq.Module.IDENTITY).send_iq(stream, new Iq.Stanza.result(iq));
- break;
- case "checksum":
- // TODO(hrxi): handle hash
- stream.get_module(Iq.Module.IDENTITY).send_iq(stream, new Iq.Stanza.result(iq));
- break;
- default:
- throw new Jingle.IqError.UNSUPPORTED_INFO(@"unsupported file transfer info $(info.name)");
- }
- }
- public signal void file_incoming(XmppStream stream, FileTransfer file_transfer);
+ public Jingle.ContentParameters create_content_parameters(Object object) throws Jingle.IqError {
+ assert_not_reached();
+ }
public async bool is_available(XmppStream stream, Jid full_jid) {
bool? has_feature = yield stream.get_module(ServiceDiscovery.Module.IDENTITY).has_entity_feature(stream, full_jid, NS_URI);
if (has_feature == null || !(!)has_feature) {
return false;
}
- return yield stream.get_module(Jingle.Module.IDENTITY).is_available(stream, Jingle.TransportType.STREAMING, full_jid);
+ return yield stream.get_module(Jingle.Module.IDENTITY).is_available(stream, required_transport_type, required_components, full_jid);
}
- public async void offer_file_stream(XmppStream stream, Jid receiver_full_jid, InputStream input_stream, string basename, int64 size, string? precondition_name = null, Object? precondition_options = null) throws IOError {
+ public async void offer_file_stream(XmppStream stream, Jid receiver_full_jid, InputStream input_stream, string basename, int64 size, string? precondition_name = null, Object? precondition_options = null) throws Jingle.Error {
StanzaNode file_node;
StanzaNode description = new StanzaNode.build("description", NS_URI)
.add_self_xmlns()
@@ -64,25 +56,83 @@ public class Module : Jingle.ContentType, XmppStreamModule {
warning("Sending file %s without size, likely going to cause problems down the road...", basename);
}
- Jingle.Session session;
- try {
- session = yield stream.get_module(Jingle.Module.IDENTITY)
- .create_session(stream, Jingle.TransportType.STREAMING, receiver_full_jid, Jingle.Senders.INITIATOR, "a-file-offer", description, precondition_name, precondition_options); // TODO(hrxi): Why "a-file-offer"?
- } catch (Jingle.Error e) {
- throw new IOError.FAILED(@"couldn't create Jingle session: $(e.message)");
+ Parameters parameters = Parameters.parse(this, description);
+
+ Jingle.Module jingle_module = stream.get_module(Jingle.Module.IDENTITY);
+
+ Jingle.Transport? transport = yield jingle_module.select_transport(stream, required_transport_type, required_components, receiver_full_jid, Set.empty());
+ if (transport == null) {
+ throw new Jingle.Error.NO_SHARED_PROTOCOLS("No suitable transports");
+ }
+ Jingle.SecurityPrecondition? precondition = jingle_module.get_security_precondition(precondition_name);
+ if (precondition_name != null && precondition == null) {
+ throw new Jingle.Error.UNSUPPORTED_SECURITY("No suitable security precondiiton found");
+ }
+ Jid? my_jid = stream.get_flag(Bind.Flag.IDENTITY).my_jid;
+ if (my_jid == null) {
+ throw new Jingle.Error.GENERAL("Couldn't determine own JID");
}
- session.terminate_on_connection_close = false;
+ Jingle.TransportParameters transport_params = transport.create_transport_parameters(stream, required_components, my_jid, receiver_full_jid);
+ Jingle.SecurityParameters? security_params = precondition != null ? precondition.create_security_parameters(stream, my_jid, receiver_full_jid, precondition_options) : null;
- yield session.conn.input_stream.close_async();
+ Jingle.Content content = new Jingle.Content.initiate_sent("a-file-offer", Jingle.Senders.INITIATOR,
+ this, parameters,
+ transport, transport_params,
+ precondition, security_params,
+ my_jid, receiver_full_jid);
- // TODO(hrxi): catch errors
- yield session.conn.output_stream.splice_async(input_stream, OutputStreamSpliceFlags.CLOSE_SOURCE|OutputStreamSpliceFlags.CLOSE_TARGET);
+ ArrayList<Jingle.Content> contents = new ArrayList<Jingle.Content>();
+ contents.add(content);
+
+
+ Jingle.Session? session = null;
+ try {
+ session = yield jingle_module.create_session(stream, contents, receiver_full_jid);
+
+ // Wait for the counterpart to accept our offer
+ ulong content_notify_id = 0;
+ content_notify_id = content.notify["state"].connect(() => {
+ if (content.state == Jingle.Content.State.ACCEPTED) {
+ Idle.add(offer_file_stream.callback);
+ content.disconnect(content_notify_id);
+ }
+ });
+ yield;
+
+ // Send the file data
+ Jingle.StreamingConnection connection = content.component_connections.values.to_array()[0] as Jingle.StreamingConnection;
+ IOStream io_stream = yield connection.stream.wait_async();
+ yield io_stream.input_stream.close_async();
+ yield io_stream.output_stream.splice_async(input_stream, OutputStreamSpliceFlags.CLOSE_SOURCE|OutputStreamSpliceFlags.CLOSE_TARGET);
+ yield connection.terminate(true);
+ } catch (Jingle.Error e) {
+ session.terminate(Jingle.ReasonElement.FAILED_TRANSPORT, e.message, e.message);
+ throw new Jingle.Error.GENERAL(@"couldn't create Jingle session: $(e.message)");
+ }
}
public override string get_ns() { return NS_URI; }
public override string get_id() { return IDENTITY.id; }
}
+public class SessionInfoType : Jingle.SessionInfoNs, Object {
+
+ public string ns_uri { get { return NS_URI; } }
+
+ public void handle_content_session_info(XmppStream stream, Jingle.Session session, StanzaNode info, Iq.Stanza iq) throws Jingle.IqError {
+ switch (info.name) {
+ case "received":
+ break;
+ case "checksum":
+ // TODO(hrxi): handle hash
+ break;
+ default:
+ throw new Jingle.IqError.UNSUPPORTED_INFO(@"unsupported file transfer info $(info.name)");
+ }
+ }
+
+}
+
public class Parameters : Jingle.ContentParameters, Object {
Module parent;
@@ -127,24 +177,42 @@ public class Parameters : Jingle.ContentParameters, Object {
return new Parameters(parent, description, media_type, name, size);
}
- public void on_session_initiate(XmppStream stream, Jingle.Session session) {
- parent.file_incoming(stream, new FileTransfer(session, this));
+ public StanzaNode get_description_node() {
+ return original_description;
+ }
+
+ public async void handle_proposed_content(XmppStream stream, Jingle.Session session, Jingle.Content content) {
+ parent.file_incoming(stream, new FileTransfer(session, content, this));
}
+
+ public void modify(XmppStream stream, Jingle.Session session, Jingle.Content content, Jingle.Senders senders) { }
+
+ public void accept(XmppStream stream, Jingle.Session session, Jingle.Content content) { }
+
+ public void handle_accept(XmppStream stream, Jingle.Session session, Jingle.Content content, StanzaNode description_node) { }
+
+ public void terminate(bool we_terminated, string? reason_name, string? reason_text) { }
}
// Does nothing except wrapping an input stream to signal EOF after reading
// `max_size` bytes.
private class FileTransferInputStream : InputStream {
+
+ public signal void closed();
+
InputStream inner;
int64 remaining_size;
+
public FileTransferInputStream(InputStream inner, int64 max_size) {
this.inner = inner;
this.remaining_size = max_size;
}
+
private ssize_t update_remaining(ssize_t read) {
this.remaining_size -= read;
return read;
}
+
public override ssize_t read(uint8[] buffer_, Cancellable? cancellable = null) throws IOError {
unowned uint8[] buffer = buffer_;
if (remaining_size <= 0) {
@@ -155,6 +223,7 @@ private class FileTransferInputStream : InputStream {
}
return update_remaining(inner.read(buffer, cancellable));
}
+
public override async ssize_t read_async(uint8[]? buffer_, int io_priority = GLib.Priority.DEFAULT, Cancellable? cancellable = null) throws IOError {
unowned uint8[] buffer = buffer_;
if (remaining_size <= 0) {
@@ -165,16 +234,21 @@ private class FileTransferInputStream : InputStream {
}
return update_remaining(yield inner.read_async(buffer, io_priority, cancellable));
}
+
public override bool close(Cancellable? cancellable = null) throws IOError {
+ closed();
return inner.close(cancellable);
}
+
public override async bool close_async(int io_priority = GLib.Priority.DEFAULT, Cancellable? cancellable = null) throws IOError {
+ closed();
return yield inner.close_async(io_priority, cancellable);
}
}
public class FileTransfer : Object {
Jingle.Session session;
+ Jingle.Content content;
Parameters parameters;
public Jid peer { get { return session.peer_full_jid; } }
@@ -184,19 +258,33 @@ public class FileTransfer : Object {
public InputStream? stream { get; private set; }
- public FileTransfer(Jingle.Session session, Parameters parameters) {
+ public FileTransfer(Jingle.Session session, Jingle.Content content, Parameters parameters) {
this.session = session;
+ this.content = content;
this.parameters = parameters;
- this.stream = new FileTransferInputStream(session.conn.input_stream, size);
}
- public void accept(XmppStream stream) throws IOError {
- session.accept(stream, parameters.original_description);
- session.conn.output_stream.close();
+ public async void accept(XmppStream stream) throws IOError {
+ content.accept();
+
+ Jingle.StreamingConnection connection = content.component_connections.values.to_array()[0] as Jingle.StreamingConnection;
+ try {
+ IOStream io_stream = yield connection.stream.wait_async();
+ FileTransferInputStream ft_stream = new FileTransferInputStream(io_stream.input_stream, size);
+ io_stream.output_stream.close();
+ ft_stream.closed.connect(() => {
+ session.terminate(Jingle.ReasonElement.SUCCESS, null, null);
+ });
+ this.stream = ft_stream;
+ } catch (FutureError.EXCEPTION e) {
+ warning("Error accepting Jingle file-transfer: %s", connection.stream.exception.message);
+ } catch (FutureError e) {
+ warning("FutureError accepting Jingle file-transfer: %s", e.message);
+ }
}
public void reject(XmppStream stream) {
- session.reject(stream);
+ content.reject();
}
}