Sample 11.  channel name space management and security with filter and translator

Based on applications name space management requirements, we may need to "relocate"/"mount" the names imported (from a connection to a remote name space) to a specific sub-region in name space. For example, if we have a name space in desktop computer and connect to a PDA and a laptop, we can set translators at connections so that names imported from PDA will appear under "/pda/" and names from laptop will appear under "/laptop/". Or if our application use integer as ids/names, we may want to relocate ids from the 1st connection to [1000-1999] and ids from next connection to [2000-2999] and so on. That is similar to the way how we mount remote file-systems to local file-system.
Based on security requirements, we may need to use filters to restrict the valid range of names allowed to pass in/out specific channel connections. For example, a server's name space connect to 2 clients and we want that these clients' name spaces and messaging are totally separate, so that one client is unware of anything happening inside the other client's name space such as new name-publications and message passing. That is also similar to the way we protect network by firewalls and NATs.

This sample uses a linear name space with strings as ids and implements a server which can connect to multiple clients. For each connection, the server will relocate the imported names to sub-region. For demo purpose, we simply put the names from clients under "/client0/...", "/client1/..."... and so on. So a name "basketball" in the first client will appear in server's name space as "/client0/basketball".

First we define sample filter and translator in chat_defs.hpp.

When filter is created and bound for a connection,  a prefix string ( such as "/client0/", "/client1/" ) is passed in . Then this filter will check name space change messages (init_subscription, publication/unpublication, subscription/unsubscription) to block names/ids which are not allowed; in our sample, valid names should begin with correct prefix. both input and output directions will be checked:
class filter_t : public filter_type<string> {
public:
  string prefix;
  filter_t(string pre) : prefix(pre) {}
  bool block_inward (string &id) {
    if (id.find(prefix) != 0) {
      cout << "id [" << id << "] blocked at inward<" << prefix << ">" << endl;
      return true;
    }
    return false;
  }
  bool block_outward (string &id) {
    if (id.find(prefix) != 0) {
      cout << "id [" << id << "] blocked at outward<" << prefix << ">" << endl;
      return true;
    }
    return false;
  }
};

When translator is created and bound for a connection,  a prefix string ( such as "/client0/", "/client1/" ) is passed in which designates where imported names will appear. Then both incoming and outgoing messages will be translated by the translator. In our simple demo, the translation is adding and removing of the prefix:
class translator_t : public translator_type<string> {
public:
  string prefix;
  translator_t(string pre) : prefix(pre) {}
  void translate_inward (string & id) {
    cout << "translate_inward : id[" << id << "] into [" ;
    id = prefix + id;
    cout << id <<  "]\n";
  }
  void translate_outward (string & id) {
    cout << "translate_outward : id[" << id << "] into [" ;
    id.erase(0,prefix.size());
    cout << id << "]\n";
  }
};

A binder is just the combination of filter and translator.
class binder : public binder_type<string> {
public:
  binder(string prefix) {
    filter = new filter_t(prefix);
    translator = new translator_t(prefix);
  }
};

Next lets see how filter and translator is set up in srv.cpp.

In srv.cpp, we define the following callback function, so that a proper binder object will be created based on information about the connection (here we use information about socket). In real applications, we may need security provisions such as which range of ids/names are allowed for which remote connections. Here we simply relocate names for remote clients to "/client0/", "/client1/", etc.
binder * binder_generator(boost::shared_ptr<tcp::socket> sock)
{
  static int num = 0;
  string prefix = "/client";
  std::ostringstream os;
  os << num;
  num++;
  return new binder(prefix+os.str()+"/");
}

Next in srv.cpp when we accept connection from clients, binder_generator is passed in so that proper binder will be created and used for each new connection:
    connector.async_accept(atoi(srv_port), // channel address
    boost::bind(asio_bind_sock_chan<chat_chan, chat_chan::text_marshaler_registry>(&binder_generator),
            boost::ref(chan), boost::ref(mar_reg), _1, _2,_3));

A sample session could be as following:
1. start server
    srv 8888
2. start first client (test1)
    cli test1 localhost 8888
3. start 2nd client (test2)
    cli test2 localhost 8888
4. in test1, add a new id/name such as "fishing"; from server's log, we'll see it appears in server's namespace as "/client0/fishing";
    in test1, we can use "fishing" to send message to server, while at server we need to use "/client0/fishing" to send message to             test1.
5. in test2, add a new id/name such as "reading"; from server's log, we'll see it appears in server's namespace as "/client1/reading";
    in test2, we can use "reading" to send message to server, while at server we need to use "/client1/reading" to send message to         test2.
6. notice that test1 is not aware of what is happening with test2 and vice versa.

Complete source code listing:
chat_defs.hpp
cli.cpp
srv.cpp