Index: docs/remote_protocol.html
===================================================================
--- docs/remote_protocol.html	(revision 15167)
+++ docs/remote_protocol.html	(working copy)
@@ -6,6 +6,8 @@
 <body bgcolor="white" text="black">
 <h1>Remote Backend Protocol</h1>
 
+<!-- FIXME: document 35.1 -->
+
 <p>
 This document describes <em>version 35.0</em> of the protocol used by Xapian's
 remote backend.  The major protocol version increased to 35 in Xapian 1.1.5.
Index: tests/api_metadata.cc
===================================================================
--- tests/api_metadata.cc	(revision 15191)
+++ tests/api_metadata.cc	(working copy)
@@ -137,8 +137,7 @@
 }
 
 // Test metadata iterators.
-// !remote because the remote backend doesn't support metadata iteration.
-DEFINE_TESTCASE(metadata5, writable && !remote) {
+DEFINE_TESTCASE(metadata5, writable) {
     Xapian::WritableDatabase db = get_writable_database();
 
     // Check that iterator on empty database returns nothing.
@@ -241,12 +240,7 @@
     TEST(iter != db.metadata_keys_end());
     TEST_EQUAL(*iter, "foo1");
 
-    // Check that skip_to can move backwards.
-    iter.skip_to("");
-    TEST(iter != db.metadata_keys_end());
-    TEST_EQUAL(*iter, "a");
-
-    // Skip back to the foo1 key.
+    // Check that skipping to the current key works.
     iter.skip_to("foo1");
     TEST(iter != db.metadata_keys_end());
     TEST_EQUAL(*iter, "foo1");
Index: net/remoteserver.cc
===================================================================
--- net/remoteserver.cc	(revision 15167)
+++ net/remoteserver.cc	(working copy)
@@ -183,13 +183,14 @@
 		&RemoteServer::msg_setmetadata,
 		&RemoteServer::msg_addspelling,
 		&RemoteServer::msg_removespelling,
-		// MSG_GETMSET - used during a conversation.
-		// MSG_SHUTDOWN - handled by get_message().
+		0, // MSG_GETMSET - used during a conversation.
+		0, // MSG_SHUTDOWN - handled by get_message().
+		&RemoteServer::msg_openmetadatakeylist,
 	    };
 
 	    string message;
 	    size_t type = get_message(idle_timeout, message);
-	    if (type >= sizeof(dispatch)/sizeof(dispatch[0])) {
+	    if (type >= sizeof(dispatch)/sizeof(dispatch[0]) || !dispatch[type]) {
 		string errmsg("Unexpected message type ");
 		errmsg += str(type);
 		throw Xapian::InvalidArgumentError(errmsg);
@@ -664,6 +665,18 @@
 }
 
 void
+RemoteServer::msg_openmetadatakeylist(const string & message)
+{
+    const Xapian::TermIterator end = db->metadata_keys_end(message);
+    Xapian::TermIterator t = db->metadata_keys_begin(message);
+    for (; t != end; ++t) {
+	send_message(REPLY_METADATAKEYLIST, *t);
+    }
+
+    send_message(REPLY_DONE, string());
+}
+
+void
 RemoteServer::msg_setmetadata(const string & message)
 {
     if (!wdb)
Index: common/remote-database.h
===================================================================
--- common/remote-database.h	(revision 15167)
+++ common/remote-database.h	(working copy)
@@ -173,6 +173,9 @@
     void get_mset(Xapian::MSet &mset,
 		  const vector<Xapian::MatchSpy *> & matchspies);
 
+    /// Get remote metadata key list.
+    TermList * open_metadata_keylist(const std::string & prefix) const;
+
     /// Get remote termlist.
     TermList * open_term_list(Xapian::docid did) const;
 
Index: common/remoteprotocol.h
===================================================================
--- common/remoteprotocol.h	(revision 15167)
+++ common/remoteprotocol.h	(working copy)
@@ -44,8 +44,9 @@
 // 33: 1.1.3 Support for passing matchspies over the remote connection.
 // 34: 1.1.4 Support for metadata over with remote databases.
 // 35: 1.1.5 Support for add_spelling() and remove_spelling().
+// 35.1: 1.2.4 Support for metadata_keys_begin().
 #define XAPIAN_REMOTE_PROTOCOL_MAJOR_VERSION 35
-#define XAPIAN_REMOTE_PROTOCOL_MINOR_VERSION 0
+#define XAPIAN_REMOTE_PROTOCOL_MINOR_VERSION 1
 
 /** Message types (client -> server).
  *
@@ -81,6 +82,7 @@
     MSG_REMOVESPELLING,		// Remove a spelling
     MSG_GETMSET,		// Get MSet
     MSG_SHUTDOWN,		// Shutdown
+    MSG_METADATAKEYLIST,	// Iterator for metadata keys
     MSG_MAX
 };
 
@@ -107,6 +109,7 @@
     REPLY_ADDDOCUMENT,		// Add Document
     REPLY_RESULTS,		// Results (MSet)
     REPLY_METADATA,		// Metadata
+    REPLY_METADATAKEYLIST,	// Iterator for metadata keys
     REPLY_MAX
 };
 
Index: common/remoteserver.h
===================================================================
--- common/remoteserver.h	(revision 15167)
+++ common/remoteserver.h	(working copy)
@@ -152,6 +152,9 @@
     // get metadata
     void msg_getmetadata(const std::string & message);
 
+    // read metadata key list
+	void msg_openmetadatakeylist(const std::string & message);    
+
     // set metadata
     void msg_setmetadata(const std::string & message);
 
Index: backends/remote/remote-database.cc
===================================================================
--- backends/remote/remote-database.cc	(revision 15167)
+++ backends/remote/remote-database.cc	(working copy)
@@ -133,6 +133,35 @@
 }
 
 TermList *
+RemoteDatabase::open_metadata_keylist(const std::string &prefix) const
+{
+    // Ensure that total_length and doccount are up-to-date.
+    if (!cached_stats_valid) update_stats();
+
+    send_message(MSG_METADATAKEYLIST, prefix);
+
+    string message;
+    AutoPtr<NetworkTermList> tlist(
+	new NetworkTermList(0, doccount,
+			    Xapian::Internal::RefCntPtr<const RemoteDatabase>(this),
+			    0));
+    vector<NetworkTermListItem> & items = tlist->items;
+
+    char type;
+    while ((type = get_message(message)) == REPLY_METADATAKEYLIST) {
+	NetworkTermListItem item;
+	item.tname = message;
+	items.push_back(item);
+    }
+    if (type != REPLY_DONE) {
+	throw Xapian::NetworkError("Bad message received", context);
+    }
+
+    tlist->current_position = tlist->items.begin();
+    return tlist.release();
+}
+
+TermList *
 RemoteDatabase::open_term_list(Xapian::docid did) const
 {
     Assert(did);

