]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blobdiff - src/allmydata/introducer/interfaces.py
new introducer: signed extensible dictionary-based messages! refs #466
[tahoe-lafs/tahoe-lafs.git] / src / allmydata / introducer / interfaces.py
index 54f1701f881f85d4be85bafef92e82c9ac4a9fd8..53d875ac942ff2bd9622b7d410bba6230cea357e 100644 (file)
@@ -1,9 +1,12 @@
 
 from zope.interface import Interface
 from foolscap.api import StringConstraint, TupleOf, SetOf, DictOf, Any, \
-    RemoteInterface
+    RemoteInterface, Referenceable
+from old import RIIntroducerSubscriberClient_v1
 FURL = StringConstraint(1000)
 
+# old introducer protocol (v1):
+#
 # Announcements are (FURL, service_name, remoteinterface_name,
 #                    nickname, my_version, oldest_supported)
 #  the (FURL, service_name, remoteinterface_name) refer to the service being
@@ -14,13 +17,17 @@ FURL = StringConstraint(1000)
 #  incompatible peer. The second goal is to enable the development of
 #  backwards-compatibility code.
 
-Announcement = TupleOf(FURL, str, str,
-                       str, str, str)
+Announcement_v1 = TupleOf(FURL, str, str,
+                          str, str, str)
 
-class RIIntroducerSubscriberClient(RemoteInterface):
-    __remote_name__ = "RIIntroducerSubscriberClient.tahoe.allmydata.com"
+# v2 protocol over foolscap: Announcements are 3-tuples of (bytes, str, str)
+# or (bytes, none, none)
+Announcement_v2 = Any()
 
-    def announce(announcements=SetOf(Announcement)):
+class RIIntroducerSubscriberClient_v2(RemoteInterface):
+    __remote_name__ = "RIIntroducerSubscriberClient_v2.tahoe.allmydata.com"
+
+    def announce_v2(announcements=SetOf(Announcement_v2)):
         """I accept announcements from the publisher."""
         return None
 
@@ -41,38 +48,29 @@ class RIIntroducerSubscriberClient(RemoteInterface):
         """
         return None
 
-# When Foolscap can handle multiple interfaces (Foolscap#17), the
-# full-powered introducer will implement both RIIntroducerPublisher and
-# RIIntroducerSubscriberService. Until then, we define
-# RIIntroducerPublisherAndSubscriberService as a combination of the two, and
-# make everybody use that.
+SubscriberInfo = DictOf(str, Any())
 
-class RIIntroducerPublisher(RemoteInterface):
+class RIIntroducerPublisherAndSubscriberService_v2(RemoteInterface):
     """To publish a service to the world, connect to me and give me your
-    announcement message. I will deliver a copy to all connected subscribers."""
-    __remote_name__ = "RIIntroducerPublisher.tahoe.allmydata.com"
-
-    def publish(announcement=Announcement):
-        # canary?
-        return None
-
-class RIIntroducerSubscriberService(RemoteInterface):
-    __remote_name__ = "RIIntroducerSubscriberService.tahoe.allmydata.com"
-
-    def subscribe(subscriber=RIIntroducerSubscriberClient, service_name=str):
-        """Give me a subscriber reference, and I will call its new_peers()
-        method will any announcements that match the desired service name. I
-        will ignore duplicate subscriptions.
-        """
-        return None
-
-class RIIntroducerPublisherAndSubscriberService(RemoteInterface):
-    __remote_name__ = "RIIntroducerPublisherAndSubscriberService.tahoe.allmydata.com"
+    announcement message. I will deliver a copy to all connected subscribers.
+    To hear about services, connect to me and subscribe to a specific
+    service_name."""
+    __remote_name__ = "RIIntroducerPublisherAndSubscriberService_v2.tahoe.allmydata.com"
     def get_version():
         return DictOf(str, Any())
-    def publish(announcement=Announcement):
+    def publish(announcement=Announcement_v1):
+        return None
+    def publish_v2(announcement=Announcement_v2, canary=Referenceable):
         return None
-    def subscribe(subscriber=RIIntroducerSubscriberClient, service_name=str):
+    def subscribe(subscriber=RIIntroducerSubscriberClient_v1, service_name=str):
+        return None
+    def subscribe_v2(subscriber=RIIntroducerSubscriberClient_v2,
+                     service_name=str, subscriber_info=SubscriberInfo):
+        """Give me a subscriber reference, and I will call its announce_v2()
+        method with any announcements that match the desired service name. I
+        will ignore duplicate subscriptions. The subscriber_info dictionary
+        tells me about the subscriber, and is used for diagnostic/status
+        displays."""
         return None
 
 class IIntroducerClient(Interface):
@@ -80,41 +78,47 @@ class IIntroducerClient(Interface):
     publish their services to the rest of the world, and I help them learn
     about services available on other nodes."""
 
-    def publish(furl, service_name, remoteinterface_name):
-        """Once you call this, I will tell the world that the Referenceable
-        available at FURL is available to provide a service named
-        SERVICE_NAME. The precise definition of the service being provided is
-        identified by the Foolscap 'remote interface name' in the last
-        parameter: this is supposed to be a globally-unique string that
-        identifies the RemoteInterface that is implemented."""
+    def publish(service_name, ann, signing_key=None):
+        """Publish the given announcement dictionary (which must be
+        JSON-serializable), plus some additional keys, to the world.
+
+        Each announcement is characterized by a (service_name, serverid)
+        pair. When the server sees two announcements with the same pair, the
+        later one will replace the earlier one. The serverid is derived from
+        the signing_key, if present, otherwise it is derived from the
+        'anonymous-storage-FURL' key.
+
+        If signing_key= is set to an instance of SigningKey, it will be
+        used to sign the announcement."""
 
     def subscribe_to(service_name, callback, *args, **kwargs):
         """Call this if you will eventually want to use services with the
         given SERVICE_NAME. This will prompt me to subscribe to announcements
         of those services. Your callback will be invoked with at least two
-        arguments: a serverid (binary string), and an announcement
-        dictionary, followed by any additional callback args/kwargs you give
-        me. I will run your callback for both new announcements and for
+        arguments: a pubkey and an announcement dictionary, followed by any
+        additional callback args/kwargs you gave me. The pubkey will be None
+        unless the announcement was signed by the corresponding pubkey, in
+        which case it will be a printable string like 'v0-base32..'.
+
+        I will run your callback for both new announcements and for
         announcements that have changed, but you must be prepared to tolerate
         duplicates.
 
-        The announcement dictionary that I give you will have the following
-        keys:
+        The announcement that I give you comes from some other client. It
+        will be a JSON-serializable dictionary which (by convention) is
+        expected to have at least the following keys:
 
          version: 0
-         service-name: str('storage')
-
-         FURL: str(furl)
-         remoteinterface-name: str(ri_name)
          nickname: unicode
          app-versions: {}
          my-version: str
          oldest-supported: str
 
-        Note that app-version will be an empty dictionary until #466 is done
-        and both the introducer and the remote client have been upgraded. For
-        current (native) server types, the serverid will always be equal to
-        the binary form of the FURL's tubid.
+         service-name: str('storage')
+         anonymous-storage-FURL: str(furl)
+
+        Note that app-version will be an empty dictionary if either the
+        publishing client or the Introducer are running older code.
         """
 
     def connected_to_introducer():