diff --git a/.classpath b/.classpath
index 2ed9bb6ee03d54c507ff19daa62b1c761d8cb246..c777dbbde3a8bbd90fb2b852389bae263e970b1e 100644
--- a/.classpath
+++ b/.classpath
@@ -15,5 +15,6 @@
 	<classpathentry kind="lib" path="lib/test/hamcrest-core-1.1.jar"/>
 	<classpathentry kind="lib" path="lib/test/hamcrest-library-1.1.jar"/>
 	<classpathentry kind="lib" path="lib/test/jmock-2.5.1.jar"/>
+	<classpathentry kind="lib" path="lib/protobuf.jar"/>
 	<classpathentry kind="output" path="bin"/>
 </classpath>
diff --git a/api/net/sf/briar/api/db/DatabaseComponent.java b/api/net/sf/briar/api/db/DatabaseComponent.java
index 9d3d75b08bfde09dc56daaf6836278a77d0a4c29..bea1e9e4ac6fec71a7f8c3df9c344a71019c89c9 100644
--- a/api/net/sf/briar/api/db/DatabaseComponent.java
+++ b/api/net/sf/briar/api/db/DatabaseComponent.java
@@ -59,6 +59,9 @@ public interface DatabaseComponent {
 	/** Returns the set of groups to which the user subscribes. */
 	Set<GroupId> getSubscriptions() throws DbException;
 
+	/** Returns the local transport details. */
+	Map<String, String> getTransports() throws DbException;
+
 	/** Returns the transport details for the given contact. */
 	Map<String, String> getTransports(ContactId c) throws DbException;
 
diff --git a/api/net/sf/briar/api/protocol/Transport.java b/api/net/sf/briar/api/protocol/Transport.java
new file mode 100644
index 0000000000000000000000000000000000000000..2a27836cd28b018025bebc9d399d8ff56c2ddbf0
--- /dev/null
+++ b/api/net/sf/briar/api/protocol/Transport.java
@@ -0,0 +1,1108 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: net/sf/briar/api/protocol/Transport.proto
+
+package net.sf.briar.api.protocol;
+
+public final class Transport {
+  private Transport() {}
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistry registry) {
+  }
+  public interface TransportDetailsOrBuilder
+      extends com.google.protobuf.MessageOrBuilder {
+    
+    // repeated .protocol.TransportDetails.TransportDetail details = 1;
+    java.util.List<net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetail> 
+        getDetailsList();
+    net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetail getDetails(int index);
+    int getDetailsCount();
+    java.util.List<? extends net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetailOrBuilder> 
+        getDetailsOrBuilderList();
+    net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetailOrBuilder getDetailsOrBuilder(
+        int index);
+  }
+  public static final class TransportDetails extends
+      com.google.protobuf.GeneratedMessage
+      implements TransportDetailsOrBuilder {
+    // Use TransportDetails.newBuilder() to construct.
+    private TransportDetails(Builder builder) {
+      super(builder);
+    }
+    private TransportDetails(boolean noInit) {}
+    
+    private static final TransportDetails defaultInstance;
+    public static TransportDetails getDefaultInstance() {
+      return defaultInstance;
+    }
+    
+    public TransportDetails getDefaultInstanceForType() {
+      return defaultInstance;
+    }
+    
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return net.sf.briar.api.protocol.Transport.internal_static_protocol_TransportDetails_descriptor;
+    }
+    
+    protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return net.sf.briar.api.protocol.Transport.internal_static_protocol_TransportDetails_fieldAccessorTable;
+    }
+    
+    public interface TransportDetailOrBuilder
+        extends com.google.protobuf.MessageOrBuilder {
+      
+      // required string key = 1;
+      boolean hasKey();
+      String getKey();
+      
+      // optional string value = 2;
+      boolean hasValue();
+      String getValue();
+    }
+    public static final class TransportDetail extends
+        com.google.protobuf.GeneratedMessage
+        implements TransportDetailOrBuilder {
+      // Use TransportDetail.newBuilder() to construct.
+      private TransportDetail(Builder builder) {
+        super(builder);
+      }
+      private TransportDetail(boolean noInit) {}
+      
+      private static final TransportDetail defaultInstance;
+      public static TransportDetail getDefaultInstance() {
+        return defaultInstance;
+      }
+      
+      public TransportDetail getDefaultInstanceForType() {
+        return defaultInstance;
+      }
+      
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return net.sf.briar.api.protocol.Transport.internal_static_protocol_TransportDetails_TransportDetail_descriptor;
+      }
+      
+      protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return net.sf.briar.api.protocol.Transport.internal_static_protocol_TransportDetails_TransportDetail_fieldAccessorTable;
+      }
+      
+      private int bitField0_;
+      // required string key = 1;
+      public static final int KEY_FIELD_NUMBER = 1;
+      private java.lang.Object key_;
+      public boolean hasKey() {
+        return ((bitField0_ & 0x00000001) == 0x00000001);
+      }
+      public String getKey() {
+        java.lang.Object ref = key_;
+        if (ref instanceof String) {
+          return (String) ref;
+        } else {
+          com.google.protobuf.ByteString bs = 
+              (com.google.protobuf.ByteString) ref;
+          String s = bs.toStringUtf8();
+          if (com.google.protobuf.Internal.isValidUtf8(bs)) {
+            key_ = s;
+          }
+          return s;
+        }
+      }
+      private com.google.protobuf.ByteString getKeyBytes() {
+        java.lang.Object ref = key_;
+        if (ref instanceof String) {
+          com.google.protobuf.ByteString b = 
+              com.google.protobuf.ByteString.copyFromUtf8((String) ref);
+          key_ = b;
+          return b;
+        } else {
+          return (com.google.protobuf.ByteString) ref;
+        }
+      }
+      
+      // optional string value = 2;
+      public static final int VALUE_FIELD_NUMBER = 2;
+      private java.lang.Object value_;
+      public boolean hasValue() {
+        return ((bitField0_ & 0x00000002) == 0x00000002);
+      }
+      public String getValue() {
+        java.lang.Object ref = value_;
+        if (ref instanceof String) {
+          return (String) ref;
+        } else {
+          com.google.protobuf.ByteString bs = 
+              (com.google.protobuf.ByteString) ref;
+          String s = bs.toStringUtf8();
+          if (com.google.protobuf.Internal.isValidUtf8(bs)) {
+            value_ = s;
+          }
+          return s;
+        }
+      }
+      private com.google.protobuf.ByteString getValueBytes() {
+        java.lang.Object ref = value_;
+        if (ref instanceof String) {
+          com.google.protobuf.ByteString b = 
+              com.google.protobuf.ByteString.copyFromUtf8((String) ref);
+          value_ = b;
+          return b;
+        } else {
+          return (com.google.protobuf.ByteString) ref;
+        }
+      }
+      
+      private void initFields() {
+        key_ = "";
+        value_ = "";
+      }
+      private byte memoizedIsInitialized = -1;
+      public final boolean isInitialized() {
+        byte isInitialized = memoizedIsInitialized;
+        if (isInitialized != -1) return isInitialized == 1;
+        
+        if (!hasKey()) {
+          memoizedIsInitialized = 0;
+          return false;
+        }
+        memoizedIsInitialized = 1;
+        return true;
+      }
+      
+      public void writeTo(com.google.protobuf.CodedOutputStream output)
+                          throws java.io.IOException {
+        getSerializedSize();
+        if (((bitField0_ & 0x00000001) == 0x00000001)) {
+          output.writeBytes(1, getKeyBytes());
+        }
+        if (((bitField0_ & 0x00000002) == 0x00000002)) {
+          output.writeBytes(2, getValueBytes());
+        }
+        getUnknownFields().writeTo(output);
+      }
+      
+      private int memoizedSerializedSize = -1;
+      public int getSerializedSize() {
+        int size = memoizedSerializedSize;
+        if (size != -1) return size;
+      
+        size = 0;
+        if (((bitField0_ & 0x00000001) == 0x00000001)) {
+          size += com.google.protobuf.CodedOutputStream
+            .computeBytesSize(1, getKeyBytes());
+        }
+        if (((bitField0_ & 0x00000002) == 0x00000002)) {
+          size += com.google.protobuf.CodedOutputStream
+            .computeBytesSize(2, getValueBytes());
+        }
+        size += getUnknownFields().getSerializedSize();
+        memoizedSerializedSize = size;
+        return size;
+      }
+      
+      private static final long serialVersionUID = 0L;
+      @java.lang.Override
+      protected java.lang.Object writeReplace()
+          throws java.io.ObjectStreamException {
+        return super.writeReplace();
+      }
+      
+      public static net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetail parseFrom(
+          com.google.protobuf.ByteString data)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return newBuilder().mergeFrom(data).buildParsed();
+      }
+      public static net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetail parseFrom(
+          com.google.protobuf.ByteString data,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return newBuilder().mergeFrom(data, extensionRegistry)
+                 .buildParsed();
+      }
+      public static net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetail parseFrom(byte[] data)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return newBuilder().mergeFrom(data).buildParsed();
+      }
+      public static net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetail parseFrom(
+          byte[] data,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        return newBuilder().mergeFrom(data, extensionRegistry)
+                 .buildParsed();
+      }
+      public static net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetail parseFrom(java.io.InputStream input)
+          throws java.io.IOException {
+        return newBuilder().mergeFrom(input).buildParsed();
+      }
+      public static net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetail parseFrom(
+          java.io.InputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        return newBuilder().mergeFrom(input, extensionRegistry)
+                 .buildParsed();
+      }
+      public static net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetail parseDelimitedFrom(java.io.InputStream input)
+          throws java.io.IOException {
+        Builder builder = newBuilder();
+        if (builder.mergeDelimitedFrom(input)) {
+          return builder.buildParsed();
+        } else {
+          return null;
+        }
+      }
+      public static net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetail parseDelimitedFrom(
+          java.io.InputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        Builder builder = newBuilder();
+        if (builder.mergeDelimitedFrom(input, extensionRegistry)) {
+          return builder.buildParsed();
+        } else {
+          return null;
+        }
+      }
+      public static net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetail parseFrom(
+          com.google.protobuf.CodedInputStream input)
+          throws java.io.IOException {
+        return newBuilder().mergeFrom(input).buildParsed();
+      }
+      public static net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetail parseFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        return newBuilder().mergeFrom(input, extensionRegistry)
+                 .buildParsed();
+      }
+      
+      public static Builder newBuilder() { return Builder.create(); }
+      public Builder newBuilderForType() { return newBuilder(); }
+      public static Builder newBuilder(net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetail prototype) {
+        return newBuilder().mergeFrom(prototype);
+      }
+      public Builder toBuilder() { return newBuilder(this); }
+      
+      @java.lang.Override
+      protected Builder newBuilderForType(
+          com.google.protobuf.GeneratedMessage.BuilderParent parent) {
+        Builder builder = new Builder(parent);
+        return builder;
+      }
+      public static final class Builder extends
+          com.google.protobuf.GeneratedMessage.Builder<Builder>
+         implements net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetailOrBuilder {
+        public static final com.google.protobuf.Descriptors.Descriptor
+            getDescriptor() {
+          return net.sf.briar.api.protocol.Transport.internal_static_protocol_TransportDetails_TransportDetail_descriptor;
+        }
+        
+        protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
+            internalGetFieldAccessorTable() {
+          return net.sf.briar.api.protocol.Transport.internal_static_protocol_TransportDetails_TransportDetail_fieldAccessorTable;
+        }
+        
+        // Construct using net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetail.newBuilder()
+        private Builder() {
+          maybeForceBuilderInitialization();
+        }
+        
+        private Builder(BuilderParent parent) {
+          super(parent);
+          maybeForceBuilderInitialization();
+        }
+        private void maybeForceBuilderInitialization() {
+          if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) {
+          }
+        }
+        private static Builder create() {
+          return new Builder();
+        }
+        
+        public Builder clear() {
+          super.clear();
+          key_ = "";
+          bitField0_ = (bitField0_ & ~0x00000001);
+          value_ = "";
+          bitField0_ = (bitField0_ & ~0x00000002);
+          return this;
+        }
+        
+        public Builder clone() {
+          return create().mergeFrom(buildPartial());
+        }
+        
+        public com.google.protobuf.Descriptors.Descriptor
+            getDescriptorForType() {
+          return net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetail.getDescriptor();
+        }
+        
+        public net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetail getDefaultInstanceForType() {
+          return net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetail.getDefaultInstance();
+        }
+        
+        public net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetail build() {
+          net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetail result = buildPartial();
+          if (!result.isInitialized()) {
+            throw newUninitializedMessageException(result);
+          }
+          return result;
+        }
+        
+        private net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetail buildParsed()
+            throws com.google.protobuf.InvalidProtocolBufferException {
+          net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetail result = buildPartial();
+          if (!result.isInitialized()) {
+            throw newUninitializedMessageException(
+              result).asInvalidProtocolBufferException();
+          }
+          return result;
+        }
+        
+        public net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetail buildPartial() {
+          net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetail result = new net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetail(this);
+          int from_bitField0_ = bitField0_;
+          int to_bitField0_ = 0;
+          if (((from_bitField0_ & 0x00000001) == 0x00000001)) {
+            to_bitField0_ |= 0x00000001;
+          }
+          result.key_ = key_;
+          if (((from_bitField0_ & 0x00000002) == 0x00000002)) {
+            to_bitField0_ |= 0x00000002;
+          }
+          result.value_ = value_;
+          result.bitField0_ = to_bitField0_;
+          onBuilt();
+          return result;
+        }
+        
+        public Builder mergeFrom(com.google.protobuf.Message other) {
+          if (other instanceof net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetail) {
+            return mergeFrom((net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetail)other);
+          } else {
+            super.mergeFrom(other);
+            return this;
+          }
+        }
+        
+        public Builder mergeFrom(net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetail other) {
+          if (other == net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetail.getDefaultInstance()) return this;
+          if (other.hasKey()) {
+            setKey(other.getKey());
+          }
+          if (other.hasValue()) {
+            setValue(other.getValue());
+          }
+          this.mergeUnknownFields(other.getUnknownFields());
+          return this;
+        }
+        
+        public final boolean isInitialized() {
+          if (!hasKey()) {
+            
+            return false;
+          }
+          return true;
+        }
+        
+        public Builder mergeFrom(
+            com.google.protobuf.CodedInputStream input,
+            com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+            throws java.io.IOException {
+          com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+            com.google.protobuf.UnknownFieldSet.newBuilder(
+              this.getUnknownFields());
+          while (true) {
+            int tag = input.readTag();
+            switch (tag) {
+              case 0:
+                this.setUnknownFields(unknownFields.build());
+                onChanged();
+                return this;
+              default: {
+                if (!parseUnknownField(input, unknownFields,
+                                       extensionRegistry, tag)) {
+                  this.setUnknownFields(unknownFields.build());
+                  onChanged();
+                  return this;
+                }
+                break;
+              }
+              case 10: {
+                bitField0_ |= 0x00000001;
+                key_ = input.readBytes();
+                break;
+              }
+              case 18: {
+                bitField0_ |= 0x00000002;
+                value_ = input.readBytes();
+                break;
+              }
+            }
+          }
+        }
+        
+        private int bitField0_;
+        
+        // required string key = 1;
+        private java.lang.Object key_ = "";
+        public boolean hasKey() {
+          return ((bitField0_ & 0x00000001) == 0x00000001);
+        }
+        public String getKey() {
+          java.lang.Object ref = key_;
+          if (!(ref instanceof String)) {
+            String s = ((com.google.protobuf.ByteString) ref).toStringUtf8();
+            key_ = s;
+            return s;
+          } else {
+            return (String) ref;
+          }
+        }
+        public Builder setKey(String value) {
+          if (value == null) {
+    throw new NullPointerException();
+  }
+  bitField0_ |= 0x00000001;
+          key_ = value;
+          onChanged();
+          return this;
+        }
+        public Builder clearKey() {
+          bitField0_ = (bitField0_ & ~0x00000001);
+          key_ = getDefaultInstance().getKey();
+          onChanged();
+          return this;
+        }
+        void setKey(com.google.protobuf.ByteString value) {
+          bitField0_ |= 0x00000001;
+          key_ = value;
+          onChanged();
+        }
+        
+        // optional string value = 2;
+        private java.lang.Object value_ = "";
+        public boolean hasValue() {
+          return ((bitField0_ & 0x00000002) == 0x00000002);
+        }
+        public String getValue() {
+          java.lang.Object ref = value_;
+          if (!(ref instanceof String)) {
+            String s = ((com.google.protobuf.ByteString) ref).toStringUtf8();
+            value_ = s;
+            return s;
+          } else {
+            return (String) ref;
+          }
+        }
+        public Builder setValue(String value) {
+          if (value == null) {
+    throw new NullPointerException();
+  }
+  bitField0_ |= 0x00000002;
+          value_ = value;
+          onChanged();
+          return this;
+        }
+        public Builder clearValue() {
+          bitField0_ = (bitField0_ & ~0x00000002);
+          value_ = getDefaultInstance().getValue();
+          onChanged();
+          return this;
+        }
+        void setValue(com.google.protobuf.ByteString value) {
+          bitField0_ |= 0x00000002;
+          value_ = value;
+          onChanged();
+        }
+        
+        // @@protoc_insertion_point(builder_scope:protocol.TransportDetails.TransportDetail)
+      }
+      
+      static {
+        defaultInstance = new TransportDetail(true);
+        defaultInstance.initFields();
+      }
+      
+      // @@protoc_insertion_point(class_scope:protocol.TransportDetails.TransportDetail)
+    }
+    
+    // repeated .protocol.TransportDetails.TransportDetail details = 1;
+    public static final int DETAILS_FIELD_NUMBER = 1;
+    private java.util.List<net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetail> details_;
+    public java.util.List<net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetail> getDetailsList() {
+      return details_;
+    }
+    public java.util.List<? extends net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetailOrBuilder> 
+        getDetailsOrBuilderList() {
+      return details_;
+    }
+    public int getDetailsCount() {
+      return details_.size();
+    }
+    public net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetail getDetails(int index) {
+      return details_.get(index);
+    }
+    public net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetailOrBuilder getDetailsOrBuilder(
+        int index) {
+      return details_.get(index);
+    }
+    
+    private void initFields() {
+      details_ = java.util.Collections.emptyList();
+    }
+    private byte memoizedIsInitialized = -1;
+    public final boolean isInitialized() {
+      byte isInitialized = memoizedIsInitialized;
+      if (isInitialized != -1) return isInitialized == 1;
+      
+      for (int i = 0; i < getDetailsCount(); i++) {
+        if (!getDetails(i).isInitialized()) {
+          memoizedIsInitialized = 0;
+          return false;
+        }
+      }
+      memoizedIsInitialized = 1;
+      return true;
+    }
+    
+    public void writeTo(com.google.protobuf.CodedOutputStream output)
+                        throws java.io.IOException {
+      getSerializedSize();
+      for (int i = 0; i < details_.size(); i++) {
+        output.writeMessage(1, details_.get(i));
+      }
+      getUnknownFields().writeTo(output);
+    }
+    
+    private int memoizedSerializedSize = -1;
+    public int getSerializedSize() {
+      int size = memoizedSerializedSize;
+      if (size != -1) return size;
+    
+      size = 0;
+      for (int i = 0; i < details_.size(); i++) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeMessageSize(1, details_.get(i));
+      }
+      size += getUnknownFields().getSerializedSize();
+      memoizedSerializedSize = size;
+      return size;
+    }
+    
+    private static final long serialVersionUID = 0L;
+    @java.lang.Override
+    protected java.lang.Object writeReplace()
+        throws java.io.ObjectStreamException {
+      return super.writeReplace();
+    }
+    
+    public static net.sf.briar.api.protocol.Transport.TransportDetails parseFrom(
+        com.google.protobuf.ByteString data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return newBuilder().mergeFrom(data).buildParsed();
+    }
+    public static net.sf.briar.api.protocol.Transport.TransportDetails parseFrom(
+        com.google.protobuf.ByteString data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return newBuilder().mergeFrom(data, extensionRegistry)
+               .buildParsed();
+    }
+    public static net.sf.briar.api.protocol.Transport.TransportDetails parseFrom(byte[] data)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return newBuilder().mergeFrom(data).buildParsed();
+    }
+    public static net.sf.briar.api.protocol.Transport.TransportDetails parseFrom(
+        byte[] data,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      return newBuilder().mergeFrom(data, extensionRegistry)
+               .buildParsed();
+    }
+    public static net.sf.briar.api.protocol.Transport.TransportDetails parseFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      return newBuilder().mergeFrom(input).buildParsed();
+    }
+    public static net.sf.briar.api.protocol.Transport.TransportDetails parseFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return newBuilder().mergeFrom(input, extensionRegistry)
+               .buildParsed();
+    }
+    public static net.sf.briar.api.protocol.Transport.TransportDetails parseDelimitedFrom(java.io.InputStream input)
+        throws java.io.IOException {
+      Builder builder = newBuilder();
+      if (builder.mergeDelimitedFrom(input)) {
+        return builder.buildParsed();
+      } else {
+        return null;
+      }
+    }
+    public static net.sf.briar.api.protocol.Transport.TransportDetails parseDelimitedFrom(
+        java.io.InputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      Builder builder = newBuilder();
+      if (builder.mergeDelimitedFrom(input, extensionRegistry)) {
+        return builder.buildParsed();
+      } else {
+        return null;
+      }
+    }
+    public static net.sf.briar.api.protocol.Transport.TransportDetails parseFrom(
+        com.google.protobuf.CodedInputStream input)
+        throws java.io.IOException {
+      return newBuilder().mergeFrom(input).buildParsed();
+    }
+    public static net.sf.briar.api.protocol.Transport.TransportDetails parseFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      return newBuilder().mergeFrom(input, extensionRegistry)
+               .buildParsed();
+    }
+    
+    public static Builder newBuilder() { return Builder.create(); }
+    public Builder newBuilderForType() { return newBuilder(); }
+    public static Builder newBuilder(net.sf.briar.api.protocol.Transport.TransportDetails prototype) {
+      return newBuilder().mergeFrom(prototype);
+    }
+    public Builder toBuilder() { return newBuilder(this); }
+    
+    @java.lang.Override
+    protected Builder newBuilderForType(
+        com.google.protobuf.GeneratedMessage.BuilderParent parent) {
+      Builder builder = new Builder(parent);
+      return builder;
+    }
+    public static final class Builder extends
+        com.google.protobuf.GeneratedMessage.Builder<Builder>
+       implements net.sf.briar.api.protocol.Transport.TransportDetailsOrBuilder {
+      public static final com.google.protobuf.Descriptors.Descriptor
+          getDescriptor() {
+        return net.sf.briar.api.protocol.Transport.internal_static_protocol_TransportDetails_descriptor;
+      }
+      
+      protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
+          internalGetFieldAccessorTable() {
+        return net.sf.briar.api.protocol.Transport.internal_static_protocol_TransportDetails_fieldAccessorTable;
+      }
+      
+      // Construct using net.sf.briar.api.protocol.Transport.TransportDetails.newBuilder()
+      private Builder() {
+        maybeForceBuilderInitialization();
+      }
+      
+      private Builder(BuilderParent parent) {
+        super(parent);
+        maybeForceBuilderInitialization();
+      }
+      private void maybeForceBuilderInitialization() {
+        if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) {
+          getDetailsFieldBuilder();
+        }
+      }
+      private static Builder create() {
+        return new Builder();
+      }
+      
+      public Builder clear() {
+        super.clear();
+        if (detailsBuilder_ == null) {
+          details_ = java.util.Collections.emptyList();
+          bitField0_ = (bitField0_ & ~0x00000001);
+        } else {
+          detailsBuilder_.clear();
+        }
+        return this;
+      }
+      
+      public Builder clone() {
+        return create().mergeFrom(buildPartial());
+      }
+      
+      public com.google.protobuf.Descriptors.Descriptor
+          getDescriptorForType() {
+        return net.sf.briar.api.protocol.Transport.TransportDetails.getDescriptor();
+      }
+      
+      public net.sf.briar.api.protocol.Transport.TransportDetails getDefaultInstanceForType() {
+        return net.sf.briar.api.protocol.Transport.TransportDetails.getDefaultInstance();
+      }
+      
+      public net.sf.briar.api.protocol.Transport.TransportDetails build() {
+        net.sf.briar.api.protocol.Transport.TransportDetails result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(result);
+        }
+        return result;
+      }
+      
+      private net.sf.briar.api.protocol.Transport.TransportDetails buildParsed()
+          throws com.google.protobuf.InvalidProtocolBufferException {
+        net.sf.briar.api.protocol.Transport.TransportDetails result = buildPartial();
+        if (!result.isInitialized()) {
+          throw newUninitializedMessageException(
+            result).asInvalidProtocolBufferException();
+        }
+        return result;
+      }
+      
+      public net.sf.briar.api.protocol.Transport.TransportDetails buildPartial() {
+        net.sf.briar.api.protocol.Transport.TransportDetails result = new net.sf.briar.api.protocol.Transport.TransportDetails(this);
+        int from_bitField0_ = bitField0_;
+        if (detailsBuilder_ == null) {
+          if (((bitField0_ & 0x00000001) == 0x00000001)) {
+            details_ = java.util.Collections.unmodifiableList(details_);
+            bitField0_ = (bitField0_ & ~0x00000001);
+          }
+          result.details_ = details_;
+        } else {
+          result.details_ = detailsBuilder_.build();
+        }
+        onBuilt();
+        return result;
+      }
+      
+      public Builder mergeFrom(com.google.protobuf.Message other) {
+        if (other instanceof net.sf.briar.api.protocol.Transport.TransportDetails) {
+          return mergeFrom((net.sf.briar.api.protocol.Transport.TransportDetails)other);
+        } else {
+          super.mergeFrom(other);
+          return this;
+        }
+      }
+      
+      public Builder mergeFrom(net.sf.briar.api.protocol.Transport.TransportDetails other) {
+        if (other == net.sf.briar.api.protocol.Transport.TransportDetails.getDefaultInstance()) return this;
+        if (detailsBuilder_ == null) {
+          if (!other.details_.isEmpty()) {
+            if (details_.isEmpty()) {
+              details_ = other.details_;
+              bitField0_ = (bitField0_ & ~0x00000001);
+            } else {
+              ensureDetailsIsMutable();
+              details_.addAll(other.details_);
+            }
+            onChanged();
+          }
+        } else {
+          if (!other.details_.isEmpty()) {
+            if (detailsBuilder_.isEmpty()) {
+              detailsBuilder_.dispose();
+              detailsBuilder_ = null;
+              details_ = other.details_;
+              bitField0_ = (bitField0_ & ~0x00000001);
+              detailsBuilder_ = 
+                com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ?
+                   getDetailsFieldBuilder() : null;
+            } else {
+              detailsBuilder_.addAllMessages(other.details_);
+            }
+          }
+        }
+        this.mergeUnknownFields(other.getUnknownFields());
+        return this;
+      }
+      
+      public final boolean isInitialized() {
+        for (int i = 0; i < getDetailsCount(); i++) {
+          if (!getDetails(i).isInitialized()) {
+            
+            return false;
+          }
+        }
+        return true;
+      }
+      
+      public Builder mergeFrom(
+          com.google.protobuf.CodedInputStream input,
+          com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+          throws java.io.IOException {
+        com.google.protobuf.UnknownFieldSet.Builder unknownFields =
+          com.google.protobuf.UnknownFieldSet.newBuilder(
+            this.getUnknownFields());
+        while (true) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              this.setUnknownFields(unknownFields.build());
+              onChanged();
+              return this;
+            default: {
+              if (!parseUnknownField(input, unknownFields,
+                                     extensionRegistry, tag)) {
+                this.setUnknownFields(unknownFields.build());
+                onChanged();
+                return this;
+              }
+              break;
+            }
+            case 10: {
+              net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetail.Builder subBuilder = net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetail.newBuilder();
+              input.readMessage(subBuilder, extensionRegistry);
+              addDetails(subBuilder.buildPartial());
+              break;
+            }
+          }
+        }
+      }
+      
+      private int bitField0_;
+      
+      // repeated .protocol.TransportDetails.TransportDetail details = 1;
+      private java.util.List<net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetail> details_ =
+        java.util.Collections.emptyList();
+      private void ensureDetailsIsMutable() {
+        if (!((bitField0_ & 0x00000001) == 0x00000001)) {
+          details_ = new java.util.ArrayList<net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetail>(details_);
+          bitField0_ |= 0x00000001;
+         }
+      }
+      
+      private com.google.protobuf.RepeatedFieldBuilder<
+          net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetail, net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetail.Builder, net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetailOrBuilder> detailsBuilder_;
+      
+      public java.util.List<net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetail> getDetailsList() {
+        if (detailsBuilder_ == null) {
+          return java.util.Collections.unmodifiableList(details_);
+        } else {
+          return detailsBuilder_.getMessageList();
+        }
+      }
+      public int getDetailsCount() {
+        if (detailsBuilder_ == null) {
+          return details_.size();
+        } else {
+          return detailsBuilder_.getCount();
+        }
+      }
+      public net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetail getDetails(int index) {
+        if (detailsBuilder_ == null) {
+          return details_.get(index);
+        } else {
+          return detailsBuilder_.getMessage(index);
+        }
+      }
+      public Builder setDetails(
+          int index, net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetail value) {
+        if (detailsBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          ensureDetailsIsMutable();
+          details_.set(index, value);
+          onChanged();
+        } else {
+          detailsBuilder_.setMessage(index, value);
+        }
+        return this;
+      }
+      public Builder setDetails(
+          int index, net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetail.Builder builderForValue) {
+        if (detailsBuilder_ == null) {
+          ensureDetailsIsMutable();
+          details_.set(index, builderForValue.build());
+          onChanged();
+        } else {
+          detailsBuilder_.setMessage(index, builderForValue.build());
+        }
+        return this;
+      }
+      public Builder addDetails(net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetail value) {
+        if (detailsBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          ensureDetailsIsMutable();
+          details_.add(value);
+          onChanged();
+        } else {
+          detailsBuilder_.addMessage(value);
+        }
+        return this;
+      }
+      public Builder addDetails(
+          int index, net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetail value) {
+        if (detailsBuilder_ == null) {
+          if (value == null) {
+            throw new NullPointerException();
+          }
+          ensureDetailsIsMutable();
+          details_.add(index, value);
+          onChanged();
+        } else {
+          detailsBuilder_.addMessage(index, value);
+        }
+        return this;
+      }
+      public Builder addDetails(
+          net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetail.Builder builderForValue) {
+        if (detailsBuilder_ == null) {
+          ensureDetailsIsMutable();
+          details_.add(builderForValue.build());
+          onChanged();
+        } else {
+          detailsBuilder_.addMessage(builderForValue.build());
+        }
+        return this;
+      }
+      public Builder addDetails(
+          int index, net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetail.Builder builderForValue) {
+        if (detailsBuilder_ == null) {
+          ensureDetailsIsMutable();
+          details_.add(index, builderForValue.build());
+          onChanged();
+        } else {
+          detailsBuilder_.addMessage(index, builderForValue.build());
+        }
+        return this;
+      }
+      public Builder addAllDetails(
+          java.lang.Iterable<? extends net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetail> values) {
+        if (detailsBuilder_ == null) {
+          ensureDetailsIsMutable();
+          super.addAll(values, details_);
+          onChanged();
+        } else {
+          detailsBuilder_.addAllMessages(values);
+        }
+        return this;
+      }
+      public Builder clearDetails() {
+        if (detailsBuilder_ == null) {
+          details_ = java.util.Collections.emptyList();
+          bitField0_ = (bitField0_ & ~0x00000001);
+          onChanged();
+        } else {
+          detailsBuilder_.clear();
+        }
+        return this;
+      }
+      public Builder removeDetails(int index) {
+        if (detailsBuilder_ == null) {
+          ensureDetailsIsMutable();
+          details_.remove(index);
+          onChanged();
+        } else {
+          detailsBuilder_.remove(index);
+        }
+        return this;
+      }
+      public net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetail.Builder getDetailsBuilder(
+          int index) {
+        return getDetailsFieldBuilder().getBuilder(index);
+      }
+      public net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetailOrBuilder getDetailsOrBuilder(
+          int index) {
+        if (detailsBuilder_ == null) {
+          return details_.get(index);  } else {
+          return detailsBuilder_.getMessageOrBuilder(index);
+        }
+      }
+      public java.util.List<? extends net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetailOrBuilder> 
+           getDetailsOrBuilderList() {
+        if (detailsBuilder_ != null) {
+          return detailsBuilder_.getMessageOrBuilderList();
+        } else {
+          return java.util.Collections.unmodifiableList(details_);
+        }
+      }
+      public net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetail.Builder addDetailsBuilder() {
+        return getDetailsFieldBuilder().addBuilder(
+            net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetail.getDefaultInstance());
+      }
+      public net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetail.Builder addDetailsBuilder(
+          int index) {
+        return getDetailsFieldBuilder().addBuilder(
+            index, net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetail.getDefaultInstance());
+      }
+      public java.util.List<net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetail.Builder> 
+           getDetailsBuilderList() {
+        return getDetailsFieldBuilder().getBuilderList();
+      }
+      private com.google.protobuf.RepeatedFieldBuilder<
+          net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetail, net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetail.Builder, net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetailOrBuilder> 
+          getDetailsFieldBuilder() {
+        if (detailsBuilder_ == null) {
+          detailsBuilder_ = new com.google.protobuf.RepeatedFieldBuilder<
+              net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetail, net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetail.Builder, net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetailOrBuilder>(
+                  details_,
+                  ((bitField0_ & 0x00000001) == 0x00000001),
+                  getParentForChildren(),
+                  isClean());
+          details_ = null;
+        }
+        return detailsBuilder_;
+      }
+      
+      // @@protoc_insertion_point(builder_scope:protocol.TransportDetails)
+    }
+    
+    static {
+      defaultInstance = new TransportDetails(true);
+      defaultInstance.initFields();
+    }
+    
+    // @@protoc_insertion_point(class_scope:protocol.TransportDetails)
+  }
+  
+  private static com.google.protobuf.Descriptors.Descriptor
+    internal_static_protocol_TransportDetails_descriptor;
+  private static
+    com.google.protobuf.GeneratedMessage.FieldAccessorTable
+      internal_static_protocol_TransportDetails_fieldAccessorTable;
+  private static com.google.protobuf.Descriptors.Descriptor
+    internal_static_protocol_TransportDetails_TransportDetail_descriptor;
+  private static
+    com.google.protobuf.GeneratedMessage.FieldAccessorTable
+      internal_static_protocol_TransportDetails_TransportDetail_fieldAccessorTable;
+  
+  public static com.google.protobuf.Descriptors.FileDescriptor
+      getDescriptor() {
+    return descriptor;
+  }
+  private static com.google.protobuf.Descriptors.FileDescriptor
+      descriptor;
+  static {
+    java.lang.String[] descriptorData = {
+      "\n)net/sf/briar/api/protocol/Transport.pr" +
+      "oto\022\010protocol\"~\n\020TransportDetails\022;\n\007det" +
+      "ails\030\001 \003(\0132*.protocol.TransportDetails.T" +
+      "ransportDetail\032-\n\017TransportDetail\022\013\n\003key" +
+      "\030\001 \002(\t\022\r\n\005value\030\002 \001(\tB\033\n\031net.sf.briar.ap" +
+      "i.protocol"
+    };
+    com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
+      new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() {
+        public com.google.protobuf.ExtensionRegistry assignDescriptors(
+            com.google.protobuf.Descriptors.FileDescriptor root) {
+          descriptor = root;
+          internal_static_protocol_TransportDetails_descriptor =
+            getDescriptor().getMessageTypes().get(0);
+          internal_static_protocol_TransportDetails_fieldAccessorTable = new
+            com.google.protobuf.GeneratedMessage.FieldAccessorTable(
+              internal_static_protocol_TransportDetails_descriptor,
+              new java.lang.String[] { "Details", },
+              net.sf.briar.api.protocol.Transport.TransportDetails.class,
+              net.sf.briar.api.protocol.Transport.TransportDetails.Builder.class);
+          internal_static_protocol_TransportDetails_TransportDetail_descriptor =
+            internal_static_protocol_TransportDetails_descriptor.getNestedTypes().get(0);
+          internal_static_protocol_TransportDetails_TransportDetail_fieldAccessorTable = new
+            com.google.protobuf.GeneratedMessage.FieldAccessorTable(
+              internal_static_protocol_TransportDetails_TransportDetail_descriptor,
+              new java.lang.String[] { "Key", "Value", },
+              net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetail.class,
+              net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetail.Builder.class);
+          return null;
+        }
+      };
+    com.google.protobuf.Descriptors.FileDescriptor
+      .internalBuildGeneratedFileFrom(descriptorData,
+        new com.google.protobuf.Descriptors.FileDescriptor[] {
+        }, assigner);
+  }
+  
+  // @@protoc_insertion_point(outer_class_scope)
+}
diff --git a/api/net/sf/briar/api/protocol/Transport.proto b/api/net/sf/briar/api/protocol/Transport.proto
new file mode 100644
index 0000000000000000000000000000000000000000..749c2e9df449df6143fc6b0d9f2863637ba240f9
--- /dev/null
+++ b/api/net/sf/briar/api/protocol/Transport.proto
@@ -0,0 +1,13 @@
+package protocol;
+
+option java_package = "net.sf.briar.api.protocol";
+
+message TransportDetails {
+
+	message TransportDetail {
+		required string key = 1;
+		optional string value = 2;
+	}
+
+	repeated TransportDetail details = 1;
+}
\ No newline at end of file
diff --git a/components/net/sf/briar/db/JdbcDatabase.java b/components/net/sf/briar/db/JdbcDatabase.java
index b81340fb4aabf9d9890fcb4c3b1504b71c7eaaf5..950547b50df1ec2e05fca53ef99be125fcf614f5 100644
--- a/components/net/sf/briar/db/JdbcDatabase.java
+++ b/components/net/sf/briar/db/JdbcDatabase.java
@@ -1062,6 +1062,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 				+ " WHERE contactSubscriptions.contactId = ?"
 				+ " AND statuses.contactId = ? AND status = ?"
 				+ " AND sendability > ZERO()";
+			// FIXME: Investigate the performance impact of "ORDER BY timestamp"
 			ps = txn.prepareStatement(sql);
 			ps.setInt(1, c.getInt());
 			ps.setInt(2, c.getInt());
diff --git a/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java b/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java
index d815271f3726b2a43c22deeaa6b0126a3ede28b1..c2e0a9ecc3470e9a07fd346c29adb0819604b13b 100644
--- a/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java
+++ b/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java
@@ -393,6 +393,23 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 		}
 	}
 
+	public Map<String, String> getTransports() throws DbException {
+		transportLock.readLock().lock();
+		try {
+			Txn txn = db.startTransaction();
+			try {
+				Map<String, String> transports = db.getTransports(txn);
+				db.commitTransaction(txn);
+				return transports;
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
+			}
+		} finally {
+			transportLock.readLock().unlock();
+		}
+	}
+
 	public Map<String, String> getTransports(ContactId c) throws DbException {
 		contactLock.readLock().lock();
 		try {
diff --git a/components/net/sf/briar/db/SynchronizedDatabaseComponent.java b/components/net/sf/briar/db/SynchronizedDatabaseComponent.java
index c5804db21a72161c633e51a99871639fe6a9ff86..9c26e5953a04fd70212b1ad7e688fc108eec45c7 100644
--- a/components/net/sf/briar/db/SynchronizedDatabaseComponent.java
+++ b/components/net/sf/briar/db/SynchronizedDatabaseComponent.java
@@ -296,6 +296,20 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 		}
 	}
 
+	public Map<String, String> getTransports() throws DbException {
+		synchronized(transportLock) {
+			Txn txn = db.startTransaction();
+			try {
+				Map<String, String> transports = db.getTransports(txn);
+				db.commitTransaction(txn);
+				return transports;
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
+			}
+		}
+	}
+
 	public Map<String, String> getTransports(ContactId c) throws DbException {
 		synchronized(contactLock) {
 			if(!containsContact(c)) throw new NoSuchContactException();
diff --git a/components/net/sf/briar/invitation/InvitationWorker.java b/components/net/sf/briar/invitation/InvitationWorker.java
index e9ef25eef2b7bdef2cff85005c56131e6a320182..9f62c59bbef0260b4d53b4865485a0a22a031b9f 100644
--- a/components/net/sf/briar/invitation/InvitationWorker.java
+++ b/components/net/sf/briar/invitation/InvitationWorker.java
@@ -6,21 +6,29 @@ import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
-import java.util.Random;
+import java.util.Map;
+import java.util.Map.Entry;
 
+import net.sf.briar.api.db.DatabaseComponent;
+import net.sf.briar.api.db.DbException;
 import net.sf.briar.api.invitation.InvitationCallback;
 import net.sf.briar.api.invitation.InvitationParameters;
+import net.sf.briar.api.protocol.Transport.TransportDetails;
+import net.sf.briar.api.protocol.Transport.TransportDetails.TransportDetail;
 import net.sf.briar.util.FileUtils;
 
 class InvitationWorker implements Runnable {
 
 	private final InvitationCallback callback;
 	private final InvitationParameters parameters;
+	private final DatabaseComponent databaseComponent;
 
 	InvitationWorker(final InvitationCallback callback,
-			InvitationParameters parameters) {
+			InvitationParameters parameters,
+			DatabaseComponent databaseComponent) {
 		this.callback = callback;
 		this.parameters = parameters;
+		this.databaseComponent = databaseComponent;
 	}
 
 	public void run() {
@@ -60,16 +68,25 @@ class InvitationWorker implements Runnable {
 		File invitationDat = new File(dir, "invitation.dat");
 		callback.encryptingFile(invitationDat);
 		// FIXME: Create a real invitation
+		Map<String, String> transports;
 		try {
-			Thread.sleep(2000);
-		} catch(InterruptedException ignored) {}
-		Arrays.fill(password, (char) 0);
+			transports = databaseComponent.getTransports();
+		} catch(DbException e) {
+			throw new IOException(e);
+		}
+		TransportDetails.Builder b = TransportDetails.newBuilder();
+		for(Entry<String, String> e : transports.entrySet()) {
+			TransportDetail.Builder b1 = TransportDetail.newBuilder();
+			b1.setKey(e.getKey());
+			b1.setValue(e.getValue());
+			b.addDetails(b1.build());
+		}
+		TransportDetails t = b.build();
 		FileOutputStream out = new FileOutputStream(invitationDat);
-		byte[] buf = new byte[1024];
-		new Random().nextBytes(buf);
-		out.write(buf, 0, buf.length);
+		t.writeTo(out);
 		out.flush();
 		out.close();
+		Arrays.fill(password, (char) 0);
 		return invitationDat;
 	}
 
diff --git a/components/net/sf/briar/invitation/InvitationWorkerFactoryImpl.java b/components/net/sf/briar/invitation/InvitationWorkerFactoryImpl.java
index 09eb650b5bf8fd84370b6e92011bb21e02ed2c47..62b9897262f02a2f10b336d75c161d7e8f4e4621 100644
--- a/components/net/sf/briar/invitation/InvitationWorkerFactoryImpl.java
+++ b/components/net/sf/briar/invitation/InvitationWorkerFactoryImpl.java
@@ -1,13 +1,23 @@
 package net.sf.briar.invitation;
 
+import net.sf.briar.api.db.DatabaseComponent;
 import net.sf.briar.api.invitation.InvitationCallback;
 import net.sf.briar.api.invitation.InvitationParameters;
 import net.sf.briar.api.invitation.InvitationWorkerFactory;
 
+import com.google.inject.Inject;
+
 class InvitationWorkerFactoryImpl implements InvitationWorkerFactory {
 
+	private final DatabaseComponent databaseComponent;
+
+	@Inject
+	InvitationWorkerFactoryImpl(DatabaseComponent databaseComponent) {
+		this.databaseComponent = databaseComponent;
+	}
+
 	public Runnable createWorker(InvitationCallback callback,
 			InvitationParameters parameters) {
-		return new InvitationWorker(callback, parameters);
+		return new InvitationWorker(callback, parameters, databaseComponent);
 	}
 }
diff --git a/lib/protobuf.jar b/lib/protobuf.jar
new file mode 100644
index 0000000000000000000000000000000000000000..b5f71525296cd43eefae9b3313c14be6fda696a4
Binary files /dev/null and b/lib/protobuf.jar differ
diff --git a/test/net/sf/briar/invitation/InvitationWorkerTest.java b/test/net/sf/briar/invitation/InvitationWorkerTest.java
index 482e0ac93fa4eb280b915b97bae09484c1797563..60c24597b48c9c67bcf9a28eda40bd92e1955948 100644
--- a/test/net/sf/briar/invitation/InvitationWorkerTest.java
+++ b/test/net/sf/briar/invitation/InvitationWorkerTest.java
@@ -3,10 +3,13 @@ package net.sf.briar.invitation;
 import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 import junit.framework.TestCase;
 import net.sf.briar.TestUtils;
+import net.sf.briar.api.db.DatabaseComponent;
+import net.sf.briar.api.db.DbException;
 import net.sf.briar.api.invitation.InvitationCallback;
 import net.sf.briar.api.invitation.InvitationParameters;
 
@@ -33,13 +36,15 @@ public class InvitationWorkerTest extends TestCase {
 			context.mock(InvitationCallback.class);
 		final InvitationParameters params =
 			context.mock(InvitationParameters.class);
+		final DatabaseComponent database =
+			context.mock(DatabaseComponent.class);
 		context.checking(new Expectations() {{
 			oneOf(params).getChosenLocation();
 			will(returnValue(nonExistent));
 			oneOf(callback).notFound(nonExistent);
 		}});
 
-		new InvitationWorker(callback, params).run();
+		new InvitationWorker(callback, params, database).run();
 
 		context.assertIsSatisfied();
 		File[] children = testDir.listFiles();
@@ -57,13 +62,15 @@ public class InvitationWorkerTest extends TestCase {
 			context.mock(InvitationCallback.class);
 		final InvitationParameters params =
 			context.mock(InvitationParameters.class);
+		final DatabaseComponent database =
+			context.mock(DatabaseComponent.class);
 		context.checking(new Expectations() {{
 			oneOf(params).getChosenLocation();
 			will(returnValue(exists));
 			oneOf(callback).notDirectory(exists);
 		}});
 
-		new InvitationWorker(callback, params).run();
+		new InvitationWorker(callback, params, database).run();
 
 		context.assertIsSatisfied();
 		File[] children = testDir.listFiles();
@@ -73,27 +80,27 @@ public class InvitationWorkerTest extends TestCase {
 	}
 
 	@Test
-	public void testCreatesExe() throws IOException {
+	public void testCreatesExe() throws IOException, DbException {
 		testInstallerCreation(true, false);
 	}
 
 	@Test
-	public void testCreatesJar() throws IOException {
+	public void testCreatesJar() throws IOException, DbException {
 		testInstallerCreation(false, true);
 	}
 
 	@Test
-	public void testCreatesBoth() throws IOException {
+	public void testCreatesBoth() throws IOException, DbException {
 		testInstallerCreation(true, true);
 	}
 
 	@Test
-	public void testCreatesNeither() throws IOException {
+	public void testCreatesNeither() throws IOException, DbException {
 		testInstallerCreation(false, false);
 	}
 
 	private void testInstallerCreation(final boolean createExe,
-			final boolean createJar) throws IOException {
+			final boolean createJar) throws IOException, DbException {
 		final File setup = new File(testDir, "setup.dat");
 		TestUtils.createFile(setup, "foo bar baz");
 		final File invitation = new File(testDir, "invitation.dat");
@@ -112,6 +119,8 @@ public class InvitationWorkerTest extends TestCase {
 			context.mock(InvitationCallback.class);
 		final InvitationParameters params =
 			context.mock(InvitationParameters.class);
+		final DatabaseComponent database =
+			context.mock(DatabaseComponent.class);
 		context.checking(new Expectations() {{
 			oneOf(params).getChosenLocation();
 			will(returnValue(testDir));
@@ -120,6 +129,8 @@ public class InvitationWorkerTest extends TestCase {
 			oneOf(params).getPassword();
 			will(returnValue(new char[] {'x', 'y', 'z', 'z', 'y'}));
 			oneOf(callback).encryptingFile(invitation);
+			oneOf(database).getTransports();
+			will(returnValue(Collections.singletonMap("foo", "bar")));
 			oneOf(params).shouldCreateExe();
 			will(returnValue(createExe));
 			oneOf(params).shouldCreateJar();
@@ -137,7 +148,7 @@ public class InvitationWorkerTest extends TestCase {
 			oneOf(callback).created(expectedFiles);
 		}});
 
-		new InvitationWorker(callback, params).run();
+		new InvitationWorker(callback, params, database).run();
 
 		assertTrue(invitation.exists());
 		assertEquals(createExe, exe.exists());