diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..29e9e3f --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +*~ +#* +*# +/src/obj +/src/MW.cpp +/include/MW.h +/src/MWSession.cpp +/include/MWSession.h +/src/BuiltinSequences.cpp +/include/BuiltinSequences.h \ No newline at end of file diff --git a/README.md b/README.md index 0001253..efa6e7a 100644 --- a/README.md +++ b/README.md @@ -31,3 +31,8 @@ c++ -std=c++11 -pthread -I. -DICE_CPP11_MAPPING -c Printer.cpp Server.cpp ``` c++ -pthread -o server Printer.o Server.o -lIce++11 ``` + +# Develpment +## TODO + - User Object + - ... \ No newline at end of file diff --git a/certs/README.md b/certs/README.md new file mode 100644 index 0000000..8d303b6 --- /dev/null +++ b/certs/README.md @@ -0,0 +1,49 @@ +# Demo Certificates + +This directory contains certificates used by the clients and servers in our +sample programs. These certificates are for testing purposes only and should +**never** be used in a production environment. + +As provided, the server certificates use `127.0.0.1` for the Common Name, the +IP address and DNS name. This works fine when you run the client and server on +the same host. However, if you want to run them on separate hosts, you may need +to regenerate the certificates. (This is especially true for the JavaScript +examples.) + +We've included the Python script `makedemocerts.py` to simplify this task. + +## Prerequisites + +You'll need Python to run the script. The script also depends on a utility +package from a separate [ZeroC repository][1]. You can install this package as +follows: + +``` +pip install zeroc-icecertutils +``` + +## Usage + +Running the script with `-h` displays the following usage information: + +``` +Usage: certs/makedemocerts.py [options] + +Options: +-h Show this message. +-d | --debug Debugging output. +--ip The IP address for the server certificate. +--dns The DNS name for the server certificate. +--use-dns Use the DNS name for the server certificate common + name (default is to use the IP address). +``` + +The `--ip`, `--dns`, and `--use-dns` options affect the generation of the server +certificate. Without any arguments, the script prompts for the value of the IP +address and DNS name. + +You can specify an alternate IP address using `--ip` and an alternate DNS name +using `--dns`. The `--use-dns` flag forces the script to use the DNS name as +the server's Common Name instead of the IP address. + +[1]: https://github.com/zeroc-ice/icecertutils diff --git a/certs/cacert.der b/certs/cacert.der new file mode 100644 index 0000000..e4e6af2 Binary files /dev/null and b/certs/cacert.der differ diff --git a/certs/cacert.jks b/certs/cacert.jks new file mode 100644 index 0000000..5141f34 Binary files /dev/null and b/certs/cacert.jks differ diff --git a/certs/cacert.pem b/certs/cacert.pem new file mode 100644 index 0000000..f3d8528 --- /dev/null +++ b/certs/cacert.pem @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIIDyTCCArGgAwIBAgIIYY3YvoWm5Q0wDQYJKoZIhvcNAQELBQAwgYsxFTATBgNV +BAMMDEljZSBEZW1vcyBDQTEMMAoGA1UECwwDSWNlMRQwEgYDVQQKDAtaZXJvQywg +SW5jLjEQMA4GA1UEBwwHSnVwaXRlcjEQMA4GA1UECAwHRmxvcmlkYTELMAkGA1UE +BhMCVVMxHTAbBgkqhkiG9w0BCQEWDmluZm9AemVyb2MuY29tMB4XDTE4MDgwNzA2 +MTk1MFoXDTIzMDgwNjA2MTk1MFowgYsxFTATBgNVBAMMDEljZSBEZW1vcyBDQTEM +MAoGA1UECwwDSWNlMRQwEgYDVQQKDAtaZXJvQywgSW5jLjEQMA4GA1UEBwwHSnVw +aXRlcjEQMA4GA1UECAwHRmxvcmlkYTELMAkGA1UEBhMCVVMxHTAbBgkqhkiG9w0B +CQEWDmluZm9AemVyb2MuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEApN5uTu4/GV2EeJqnh6VQPfJOVHZ8FBJ1GXGYtA6tA07EU7CCyDPZ3kS83J+h +n7cOit56f3exPbTK1zZiUYxjFt7ttkvHJau1z8o7eNm212kQ1jAa78bnSEWHK+3m +BVARsk0BOqG5xdTQleWKE0C4TcifceAIeoOaOY4gqHryFWmmfzQjiWmseN/LZG+z +EFjI65duSz51W/3ttb/Qs8iHomx88EyViatXAFVvVdC7t5wlat88sRpAL+zaNMhZ +fZQwmIzvCcZe7buWwEelUB7q5wFMoXhoIibZLDsOjY7nQ7lPCP3cv9V87v+r1ziM +AVX6F6+XuS8BZVFEE8BbvGx7XwIDAQABoy8wLTAMBgNVHRMEBTADAQH/MB0GA1Ud +DgQWBBQKSxy2SFmKdd72PjuZOqub4ZCFajANBgkqhkiG9w0BAQsFAAOCAQEAhGDc +qkGm7hhbXCBCK1eGLuh4t3Ktl5kRtDe/LReQu2f+HS9aGaKHOfJdaABOXaQUK8ip +jiCnqoWqton96lGZUN1AI4OrpPE9vMF/s6oJ84eEn+CyTjDFnnGX0yDuCQSRmSMs +zcwIynlnnyKls2WTJqzKG46QpdedHiMWYVNRNyQzQVgYjXEamMPXELkB6hseW34s +lnJnEE5xkxRyHhuunkWt76YyJcfUHNGzzIFgDbA/13tycGvqsPnOn27WJ654ergG +Ln7PPBm58AIb+7sDUgWL0quY/vUr7fd2gkXnrL4LOOL6DBFYH5NdHBg0MsdDB6bT +fBpVrDdZVkgcXPUWKQ== +-----END CERTIFICATE----- diff --git a/certs/client.jks b/certs/client.jks new file mode 100644 index 0000000..77896d6 Binary files /dev/null and b/certs/client.jks differ diff --git a/certs/client.p12 b/certs/client.p12 new file mode 100644 index 0000000..04c1c78 Binary files /dev/null and b/certs/client.p12 differ diff --git a/certs/makedemocerts.py b/certs/makedemocerts.py new file mode 100755 index 0000000..4a42b35 --- /dev/null +++ b/certs/makedemocerts.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python +# ********************************************************************** +# +# Copyright (c) 2003-2018 ZeroC, Inc. All rights reserved. +# +# ********************************************************************** + +import os, sys, socket, getopt + +try: + import IceCertUtils +except Exception as ex: + print("couldn't load IceCertUtils, did you install the `zeroc-icecertutils'\n" + "package from the Python package repository?\nerror: " + str(ex)) + sys.exit(1) + +def usage(): + print("Usage: " + sys.argv[0] + " [options]") + print("") + print("Options:") + print("-h Show this message.") + print("-d | --debug Debugging output.") + print("--ip The IP address for the server certificate.") + print("--dns The DNS name for the server certificate.") + print("--use-dns Use the DNS name for the server certificate common") + print(" name (default is to use the IP address)." ) + sys.exit(1) + +# +# Check arguments +# +debug = False +ip = None +dns = None +usedns = False +impl = "" +try: + opts, args = getopt.getopt(sys.argv[1:], "hd", ["help", "debug", "ip=", "dns=","use-dns","impl="]) +except getopt.GetoptError as e: + print("Error %s " % e) + usage() + sys.exit(1) + +for (o, a) in opts: + if o == "-h" or o == "--help": + usage() + sys.exit(0) + elif o == "-d" or o == "--debug": + debug = True + elif o == "--ip": + ip = a + elif o == "--dns": + dns = a + elif o == "--use-dns": + usedns = True + elif o == "--impl": + impl = a + +def request(question, newvalue, value): + while True: + sys.stdout.write(question) + sys.stdout.flush() + input = sys.stdin.readline().strip() + if input == 'n': + sys.stdout.write(newvalue) + sys.stdout.flush() + return sys.stdin.readline().strip() + else: + return value + +# +# Change to the directory where the certs files are stored +# +os.chdir(os.path.dirname(os.path.abspath(__file__))) + +if not ip: + try: + ip = socket.gethostbyname(socket.gethostname()) + except: + ip = "127.0.0.1" + ip = request("The IP address used for the server certificate will be: " + ip + "\n" + "Do you want to keep this IP address? (y/n) [y]", "IP : ", ip) + +if not dns: + dns = "localhost" + dns = request("The DNS name used for the server certificate will be: " + dns + "\n" + "Do you want to keep this DNS name? (y/n) [y]", "DNS : ", dns) + +CertificateFactory = vars(IceCertUtils)[impl + "CertificateFactory"] +factory = CertificateFactory(debug=debug, cn="Ice Demos CA") + +# +# CA certificate +# +factory.getCA().save("cacert.pem").save("cacert.der") + +# Client certificate +client = factory.create("client") +client.save("client.p12") + +# Server certificate +server = factory.create("server", cn = (dns if usedns else ip), ip=ip, dns=dns) +server.save("server.p12") + +try: + factory.getCA().save("cacert.pem").save("cacert.jks") # Used by the Database/library demo + server.save("server.jks", caalias="cacert") + client.save("client.jks", caalias="cacert") + + # Don't try to generate the BKS if the JKS generation fails + try: + server.save("server.bks", caalias="cacert") + client.save("client.bks", caalias="cacert") + except Exception as ex: + for f in ["server.bks", "client.bks"]: + if os.path.exists(f): os.remove(f) + print("warning: couldn't generate BKS certificates for Android applications:\n" + str(ex)) + print("Please fix this issue if you want to run the Android demos.") + +except Exception as ex: + for f in ["server.jks", "client.jks"]: + if os.path.exists(f): os.remove(f) + print("warning: couldn't generate JKS certificates for Java applications:\n" + str(ex)) + print("Please fix this issue if you want to run the Java demos.") + +factory.destroy() diff --git a/certs/server.jks b/certs/server.jks new file mode 100644 index 0000000..5b79f6c Binary files /dev/null and b/certs/server.jks differ diff --git a/certs/server.p12 b/certs/server.p12 new file mode 100644 index 0000000..9ac38c2 Binary files /dev/null and b/certs/server.p12 differ diff --git a/include/MWMap.h b/include/MWMap.h new file mode 100644 index 0000000..d630cbb --- /dev/null +++ b/include/MWMap.h @@ -0,0 +1,38 @@ +#ifndef MW_MAP_I_H +#define MW_MAP_I_H + +#include +#include +#include + +class MWMapCallbackAdapter +{ + public: + virtual void init(Ice::StringSeq) = 0; + virtual void join(const std::shared_ptr&) = 0; + virtual void leave(const std::shared_ptr&) = 0; + virtual void send(const std::shared_ptr&) = 0; +}; + +class MWMap +{ + public: + MWMap(bool trace, const std::shared_ptr& logger); + void reserve(const std::string&); + void unreserve(const std::string&); + void join(const std::string&, const std::shared_ptr&); + void leave(const std::string&); + long long send(const std::string&, std::string); + + private: + + using MWMapCallbackMap = std::map>; + + MWMapCallbackMap _members; + std::set _reserved; + std::mutex _mutex; + const bool _trace; + const std::shared_ptr _logger; +}; + +#endif diff --git a/include/MWSessionI.h b/include/MWSessionI.h new file mode 100644 index 0000000..15972c5 --- /dev/null +++ b/include/MWSessionI.h @@ -0,0 +1,28 @@ +#ifndef MW_SESSION_I_H +#define MW_SESSION_I_H + +#include +#include + +class MWSessionI : public MW::MWSession +{ +public: + + MWSessionI(const std::shared_ptr&, const std::string&, bool trace, const std::shared_ptr& logger); + + virtual void setCallback(std::shared_ptr, const Ice::Current&) override; + virtual long long send(std::string, const Ice::Current&) override; + virtual void destroy(const Ice::Current&) override; + +private: + + const std::shared_ptr _MWMap; + const std::string _name; + std::shared_ptr _callback; + bool _destroy = false; + std::mutex _mutex; + const bool _trace; + const std::shared_ptr _logger; +}; + +#endif diff --git a/include/MWSessionManagerI.h b/include/MWSessionManagerI.h new file mode 100644 index 0000000..5e93323 --- /dev/null +++ b/include/MWSessionManagerI.h @@ -0,0 +1,25 @@ + +#ifndef MW_SESSION_MANAGER_I_H +#define MW_SESSION_MANAGER_I_H + +#include +#include +#include + +class MWSessionManagerI : public Glacier2::SessionManager +{ +public: + + MWSessionManagerI(const std::shared_ptr&, bool trace, const std::shared_ptr& logger); + + virtual std::shared_ptr create(std::string, + std::shared_ptr, + const Ice::Current&) override; +private: + + const std::shared_ptr _MWMap; + const bool _trace; + const std::shared_ptr _logger; +}; + +#endif diff --git a/src/MW.ice b/src/MW.ice new file mode 100644 index 0000000..ac76f41 --- /dev/null +++ b/src/MW.ice @@ -0,0 +1,77 @@ +#pragma once +#include "Ice/BuiltinSequences.ice" +#include "Glacier2/Session.ice" + +module MW +{ +/** + * + * The InvalidMessageException is raised when a user sends an invalid + * message to the server. A message is considered invalid if the + * message size exceeds the maximum message size. + * + **/ + +exception InvalidMessageException +{ + /** + * + * The reason why the message was rejected by the server. + * + **/ + string reason; +} + +class MWMapEvent +{ + /** The timestamp. */ + long timestamp; + /** The name of the user. */ + string name; +} + +/** + * + * A sequence of state changes in the MW map. + * + * @see MWMapEvent + * + **/ +sequence MWMapEventSeq; + +/** + * + * This event is generated when a user joins the MW map. + * + * @see MWMapEvent + * + **/ +class UserJoinedEvent extends MWMapEvent +{ +} + +/** + * + * This event is generated when a user leaves the MW map. + * + * @see MWMapEvent + * + **/ +class UserLeftEvent extends MWMapEvent +{ +} + +/** + * + * This event is generated when a user sends a posotion in the + * map. + * + * @see MWMapEvent + * + **/ +class PositionEvent extends MWMapEvent +{ + /** The contents of the message. */ + string message; +} +} \ No newline at end of file diff --git a/src/MWMap.cpp b/src/MWMap.cpp new file mode 100644 index 0000000..7f81ece --- /dev/null +++ b/src/MWMap.cpp @@ -0,0 +1,99 @@ + +#include + +using namespace std; + +MWMap::MWMap(bool trace, const shared_ptr& logger) : + _trace(trace), + _logger(logger) +{ +} + +void +MWMap::reserve(const string& name) +{ + lock_guard sync(_mutex); + if(_reserved.find(name) != _reserved.end() || _members.find(name) != _members.end()) + { + throw runtime_error("The name " + name + " is already in use."); + } + _reserved.insert(name); +} + +void +MWMap::unreserve(const string& name) +{ + lock_guard sync(_mutex); + _reserved.erase(name); +} + +void +MWMap::join(const string& name, const shared_ptr& callback) +{ + lock_guard sync(_mutex); + long long timestamp = chrono::duration_cast(chrono::system_clock::now().time_since_epoch()).count(); + + _reserved.erase(name); + + Ice::StringSeq names; + for(const auto& q : _members) + { + names.push_back(q.first); + } + + callback->init(move(names)); + + _members[name] = callback; + + auto e = make_shared(timestamp, name); + for(const auto& q: _members) + { + q.second->join(e); + } + + if(_trace) + { + Ice::Trace out(_logger, "info"); + out << "User '" << name << "' joined the MW Map."; + } +} + +void +MWMap::leave(const string& name) +{ + lock_guard sync(_mutex); + long long timestamp = chrono::duration_cast(chrono::system_clock::now().time_since_epoch()).count(); + + _members.erase(name); + + auto e = make_shared(timestamp, name); + for(const auto& q: _members) + { + q.second->leave(e); + } + + if(_trace) + { + Ice::Trace out(_logger, "info"); + out << "User '" << name << "' left the MWMap."; + } +} + +Ice::Long +MWMap::send(const string& name, string message) +{ + lock_guard sync(_mutex); + long long timestamp = chrono::duration_cast(chrono::system_clock::now().time_since_epoch()).count(); + + auto e = make_shared(timestamp, name, message); + for(const auto& q: _members) + { + q.second->send(e); + } + if(_trace) + { + Ice::Trace out(_logger, "info"); + out << name << ": " << message; + } + return timestamp; +} diff --git a/src/MWServer.cpp b/src/MWServer.cpp new file mode 100644 index 0000000..9ae5db3 --- /dev/null +++ b/src/MWServer.cpp @@ -0,0 +1,84 @@ +#include +#include +// #include + +using namespace std; + +class MWServer : public Ice::Service +{ +public: + + virtual bool start(int argc, char* argv[], int&) override; + virtual bool stop() override; + +private: + + shared_ptr _adapter; +}; + +bool +MWServer::start(int, char*[], int& status) +{ + // int timeout = communicator()->getProperties()->getPropertyAsIntWithDefault("PollingChatSessionTimeout", 10); + bool traceEnabled = communicator()->getProperties()->getPropertyAsIntWithDefault("Server.Trace", 0) != 0; + auto logger = communicator()->getLogger(); + + try + { + _adapter = communicator()->createObjectAdapter("MWServer"); + + auto mwmap = make_shared(traceEnabled, logger); + if(traceEnabled) + { + Ice::Trace out(logger, "info"); + out << "MW room created ok."; + } + _adapter->add(make_shared(mwmap, traceEnabled, logger), + Ice::stringToIdentity("MWSessionManager")); + + if(traceEnabled) + { + Ice::Trace out(logger, "info"); + out << "MW session manager created ok."; + } + // _adapter->add(make_shared(chatRoom, timeout, traceEnabled, logger), + // Ice::stringToIdentity("PollingChatSessionFactory")); + + // if(traceEnabled) + // { + // Ice::Trace out(logger, "info"); + // out << "Polling chat session factory created ok."; + // } + + _adapter->activate(); + if(traceEnabled) + { + Ice::Trace out(logger, "info"); + out << "MW server started ok."; + } + } + catch(const Ice::LocalException&) + { + status = 1; + throw; + } + status = 0; + return true; +} + +bool +MWServer::stop() +{ + return true; +} + +int +main(int argc, char* argv[]) +{ +#ifdef ICE_STATIC_LIBS + Ice::registerIceSSL(); + Ice::registerIceWS(); +#endif + MWServer app; + return app.main(argc, argv); +} diff --git a/src/MWSession.ice b/src/MWSession.ice new file mode 100644 index 0000000..6c96a44 --- /dev/null +++ b/src/MWSession.ice @@ -0,0 +1,113 @@ +#pragma once + +#include +#include +#include + +module MW +{ + +/** + * + * The MWRoomCallback interface is the interface that clients implement + * as their callback object. + * + * The server calls operations of this interface to communicate + * with connected clients. + * + **/ +interface MWMapCallback +{ + /** + * + * The server invokes this operation when the client sets the callback + * for a session. This provides the client with the initial list of users + * currently in the MW room. + * + * @param users The names of users currently in the MW room. + * + **/ + void init(Ice::StringSeq users); + + /** + * + * The server invokes this operation to deliver a message + * that was sent to the MW room. + * + * @param name The name of the user that send the message. + * + * @param message The contents of the message. + * + * @param timestamp The time at which the message was sent. + * + **/ + void send(long timestamp, string name, string message); + + /** + * + * The server invokes this operation when a user joins + * the MW room. + * + * @param name The name of the user that joined the MW room. + * + * @param timestamp The time at which the user joined the MW room. + * + **/ + void join(long timestamp, string name); + + /** + * + * The servers invokes this operation when a user leaves + * the MW room. + * + * @param name The name of the user that left the MW room. + * + * @param timestamp The time at which the user left the MW room. + * + **/ + void leave(long timestamp, string name); +} + +/** + * + * A MWSession is a custom Glacier2::Session for clients that use + * Glacier2 and support callbacks (such as C++, C# and clients). + * + * @see Glacier2::Session + * + **/ +interface MWSession extends Glacier2::Session +{ + /** + * + * The setCallback operation is called by clients to set the + * callback used to receive notification of activity in the + * room. Clients receive notifications as soon as they call this + * operation (before setCallback returns). + * + * The first callback made by the server is a call to + * MWRoomCallback::init, which delivers the current list of + * users to the client. + * + * @param cb The callback the server uses to deliver notifications. + * + * @see MWRoomCallback + * + **/ + void setCallback(MWMapCallback* cb); + + /** + * + * Send a message to the MW room. + * + * @param message The message to be sent. + * + * @return The time at which the message is sent. + * + * @throws InvalidMessageException should the message be invalid. + * + **/ + long send(string message) throws InvalidMessageException; +} + +} \ No newline at end of file diff --git a/src/MWSessionI.cpp b/src/MWSessionI.cpp new file mode 100644 index 0000000..79b7cbb --- /dev/null +++ b/src/MWSessionI.cpp @@ -0,0 +1,210 @@ + +#include +#include + +using namespace std; + +class SessionCallbackAdapter : public MWMapCallbackAdapter, + public enable_shared_from_this +{ +public: + + SessionCallbackAdapter(const shared_ptr& callback, + const shared_ptr& session, + bool trace, const shared_ptr& logger, const std::string& name) : + _callback(callback), + _session(session), + _trace(trace), + _logger(logger), + _name(name) + { + } + + void init(Ice::StringSeq users) override + { + auto self = shared_from_this(); + try + { + _callback->initAsync(users, nullptr, [self](std::exception_ptr eptr) { self->failed(eptr); }); + } + catch(const Ice::CommunicatorDestroyedException&) + { + // Ignored server is being shutdown + } + } + + void join(const shared_ptr& e) override + { + auto self = shared_from_this(); + try + { + _callback->joinAsync(e->timestamp, e->name, nullptr, [self](exception_ptr eptr) { self->failed(eptr); }); + } + catch(const Ice::CommunicatorDestroyedException&) + { + // Ignored server is being shutdown + } + } + + void leave(const shared_ptr& e) override + { + auto self = shared_from_this(); + try + { + _callback->leaveAsync(e->timestamp, e->name, nullptr, [self](exception_ptr eptr) { self->failed(eptr); }); + } + catch(const Ice::CommunicatorDestroyedException&) + { + // Ignored server is being shutdown + } + } + + void send(const shared_ptr& e) override + { + auto self = shared_from_this(); + try + { + _callback->sendAsync(e->timestamp, e->name, e->message, nullptr, [self](exception_ptr eptr) { self->failed(eptr); }); + } + catch(const Ice::CommunicatorDestroyedException&) + { + // Ignored server is being shutdown + } + } + + void failed(exception_ptr) + { + if(_trace) + { + Ice::Trace out(_logger, "info"); + out << "Error sending request to user '" << _name << "'. The user's session will be destroyed."; + } + try + { + _session->ice_endpoints(Ice::EndpointSeq())->destroy(); // Collocated call. + } + catch(const Ice::LocalException&) + { + } + } + +private: + + const shared_ptr _callback; + const shared_ptr _session; + const bool _trace; + const shared_ptr _logger; + const string _name; +}; + +MWSessionI::MWSessionI(const shared_ptr& MWMap, const string& name, bool trace, const shared_ptr& logger) : + _MWMap(MWMap), + _name(name), + _trace(trace), + _logger(logger) +{ +} + +void +MWSessionI::setCallback(shared_ptr callback, const Ice::Current& current) +{ + lock_guard sync(_mutex); + if(_destroy) + { + if(_trace) + { + Ice::Trace out(_logger, "info"); + out << "User '" << _name << "' tried to set the session callback but the session is already destroyed."; + } + throw Ice::ObjectNotExistException(__FILE__, __LINE__); + } + + if(_callback || !callback) + { + return; + } + + Ice::Context ctx; + ctx["_fwd"] = "o"; + _callback = make_shared( + callback->ice_context(ctx), + Ice::uncheckedCast(current.adapter->createProxy(current.id)), + _trace, _logger, _name); + _MWRoom->join(_name, _callback); +} + +long long +MWSessionI::send(string message, const Ice::Current&) +{ + lock_guard sync(_mutex); + if(_destroy) + { + if(_trace) + { + Ice::Trace out(_logger, "info"); + out << "User '" << _name << "' tried to send a message but the session is already destroyed."; + } + throw Ice::ObjectNotExistException(__FILE__, __LINE__); + } + if(!_callback) + { + if(_trace) + { + Ice::Trace out(_logger, "info"); + out << "User '" << _name << "' tried to send a message without setting the callback."; + } + throw MW::InvalidMessageException("You cannot send messages until you join the MW Map."); + } + string msg; + try + { + msg = validateMessage(message); + } + catch(const exception& ex) + { + if(_trace) + { + Ice::Trace out(_logger, "info"); + out << "User '" << _name << "' sent an invalid message:\n" << ex; + } + throw MW::InvalidMessageException(ex.what()); + } + return _MWMap->send(_name, move(msg)); +} + +void +MWSessionI::destroy(const Ice::Current& current) +{ + lock_guard sync(_mutex); + if(_destroy) + { + if(_trace) + { + Ice::Trace out(_logger, "info"); + out << "Trying to destroy the session for user '" << _name << "' but the session is already destroyed."; + } + throw Ice::ObjectNotExistException(__FILE__, __LINE__); + } + try + { + current.adapter->remove(current.id); + if(_callback) + { + _MWMap->leave(_name); + } + else + { + _MWMap->unreserve(_name); + } + } + catch(const Ice::ObjectAdapterDeactivatedException&) + { + // No need to clean up, the server is shutting down. + } + if(_trace) + { + Ice::Trace out(_logger, "info"); + out << "Push session for user '" << _name << "' destroyed."; + } + _destroy = true; +} diff --git a/src/MWSessionManagerI.cpp b/src/MWSessionManagerI.cpp new file mode 100644 index 0000000..3eddb46 --- /dev/null +++ b/src/MWSessionManagerI.cpp @@ -0,0 +1,66 @@ +#include +#include +#include + +using namespace std; + +ChatSessionManagerI::ChatSessionManagerI(const shared_ptr& chatRoom, bool trace, + const shared_ptr& logger) : + _chatRoom(chatRoom), + _trace(trace), + _logger(logger) +{ +} + +shared_ptr +ChatSessionManagerI::create(string name, + shared_ptr sessionControl, + const Ice::Current& current) +{ + string vname; + try + { + vname = validateName(name); + _chatRoom->reserve(vname); + } + catch(const exception& ex) + { + if(_trace) + { + Ice::Trace out(_logger, "info"); + out << "Cannot create push session:\n" << ex; + } + throw Glacier2::CannotCreateSessionException(ex.what()); + } + + shared_ptr proxy; + try + { + auto session = make_shared(_chatRoom, vname, _trace, _logger); + proxy = Ice::uncheckedCast(current.adapter->addWithUUID(session)); + + Ice::IdentitySeq ids; + ids.push_back(proxy->ice_getIdentity()); + sessionControl->identities()->add(ids); + } + catch(const Ice::LocalException& ex) + { + if(_trace) + { + Ice::Trace out(_logger, "info"); + out << "Cannot create push session for user '" << vname << "':\n" << ex; + } + if(proxy) + { + proxy->destroy(); + } + throw Glacier2::CannotCreateSessionException("internal server error"); + } + + if(_trace) + { + Ice::Trace out(_logger, "info"); + out << "Push session created for user '" << vname << "'."; + } + return proxy; +} diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..1b87c1e --- /dev/null +++ b/src/Makefile @@ -0,0 +1,29 @@ +CC=c++ +IDIR =../include +CFLAGS=-I. -DICE_CPP11_MAPPING -I $(IDIR) -std=c++11 + +ODIR=obj +LDIR =../lib + +LIBS=-lm -lIce++11 + +_DEPS = MW.h MWMap.h MWSession.h MWSessionI.h MWSessionManagerI.h +DEPS = $(patsubst %,$(IDIR)/%,$(_DEPS)) + +_OBJ = MW.o MWServer.o MWMap.o MWSession.o MWSesionManagerI.o MWUtils.o +OBJ = $(patsubst %,$(ODIR)/%,$(_OBJ)) + +$(ODIR)/%.o: %.cpp $(DEPS) + $(CC) -c -o $@ $< $(CFLAGS) + +slice: + slice2cpp -I. -I../../ice/slice *.ice + mv *.h ../include + +mwserver: $(OBJ) + $(CC) -o $@ $^ $(CFLAGS) $(LIBS) + +.PHONY: clean + +clean: + rm -f $(ODIR)/*.o *~ core $(INCDIR)/*~ diff --git a/src/Printer.ice b/src/Printer.ice deleted file mode 100644 index 610a9e8..0000000 --- a/src/Printer.ice +++ /dev/null @@ -1,7 +0,0 @@ -module Demo -{ - interface Printer - { - void printString(string s); - } -} \ No newline at end of file diff --git a/src/Server.cpp b/src/Server.cpp deleted file mode 100644 index b5a4803..0000000 --- a/src/Server.cpp +++ /dev/null @@ -1,38 +0,0 @@ -#include -#include - -using namespace std; -using namespace Demo; - -class PrinterI : public Printer -{ -public: - virtual void printString(string s, const Ice::Current&) override; -}; - -void -PrinterI::printString(string s, const Ice::Current&) -{ - cout << s << endl; -} - -int -main(int argc, char* argv[]) -{ - try - { - Ice::CommunicatorHolder ich(argc, argv); - // Server implementation here ... - auto adapter = ich->createObjectAdapterWithEndpoints("SimplePrinterAdapter", "default -p 10000"); - auto servant = make_shared(); - adapter->add(servant, ich->stringToIdentity("SimplePrinter")); - adapter->activate(); - ich->waitForShutdown(); - } - catch(const std::exception& e) - { - cerr << e.what() << endl; - return 1; - } - return 0; -} diff --git a/src/config.glacier2router b/src/config.glacier2router new file mode 100644 index 0000000..69912f7 --- /dev/null +++ b/src/config.glacier2router @@ -0,0 +1,84 @@ +# +# Set the Glacier2 instance name. +# +Glacier2.InstanceName=MWServer + +# +# The client-visible endpoint of Glacier2. This should be an endpoint +# visible from the public Internet. +# +Glacier2.Client.Endpoints=ssl -p 4064 -t 10000 -h 127.0.0.1:tcp -p 4502 -t 10000 -h 127.0.0.1 + +# +# The server-visible endpoint of Glacier2. This endpoint is only +# required if callbacks are needed (leave empty otherwise). This +# should be an endpoint on an internal network (like 192.168.x.x), or +# on the loopback, so that the server is not directly accessible from +# the Internet. +# +Glacier2.Server.Endpoints=tcp -h 127.0.0.1 + +# +# The proxy of the session manager. +# +Glacier2.SessionManager=ChatSessionManager:tcp -h 127.0.0.1 -p 10001 + +# +# Accept only requests to the machine where the session manager is +# running. +# +Glacier2.Filter.Address.Accept=127.0.0.1:10001 + +# +# For this demo, we use the null permissions verifier. This permissions +# verifier allows any user-id / password combination. +# +Glacier2.PermissionsVerifier=MWServer/NullPermissionsVerifier + +# +# The timeout for inactive sessions. If any client session is inactive +# for longer than this value, the session expires and is removed. The +# unit is seconds. +# +Glacier2.SessionTimeout=30 + +# +# Turn off buffering, it's not useful for the chat demo. +# +Glacier2.Server.Buffered=0 +Glacier2.Client.Buffered=0 + +# +# Security Tracing +# +# 0 = no security tracing +# 1 = trace messages +# +#IceSSL.Trace.Security=1 + +# +# SSL Configuration +# +Ice.Plugin.IceSSL=IceSSL:createIceSSL +IceSSL.DefaultDir=../certs +IceSSL.CAs=cacert.pem +IceSSL.CertFile=server.p12 +IceSSL.Password=password +IceSSL.Keychain=../../../certs/glacier2.keychain +IceSSL.KeychainPassword=password +IceSSL.VerifyPeer=0 + +# +# Ice Tracing +# +#Ice.Trace.Network=1 +#Ice.Warn.Connections=1 +#Ice.Trace.Protocol=1 + +# +# We configure the server thread pool as we want the glacier2router +# to be multi threaded. +# + +Ice.ThreadPool.Server.Size=4 +Ice.ThreadPool.Server.SizeMax=10 diff --git a/src/config.mwserver b/src/config.mwserver new file mode 100644 index 0000000..0a56a71 --- /dev/null +++ b/src/config.mwserver @@ -0,0 +1,44 @@ +# +# The endpoint of the session server's object adapter. This should be +# an endpoint on an internal network (like 192.168.x.x), or on the +# loopback, so that the session server is not directly accessible from +# the Internet. +# +ChatServer.Endpoints=tcp -h 127.0.0.1 -p 10001 + +# +# Warn about connection exceptions +# +#Ice.Warn.Connections=1 + +# +# Network Tracing +# +# 0 = no network tracing +# 1 = trace connection establishment and closure +# 2 = like 1, but more detailed +# 3 = like 2, but also trace data transfer +# +#Ice.Trace.Network=3 + +# +# Protocol Tracing +# +# 0 = no protocol tracing +# 1 = trace protocol messages +# +#Ice.Trace.Protocol=1 + +# +# Chat Server Tracing +# +# 0 = disable chat server tracing +# 1 = enable chat server tracing +Server.Trace=1 + +# +# We configure the server thread pool as we want the chatserver +# to be multi threaded. +# +Ice.ThreadPool.Server.Size=4 +Ice.ThreadPool.Server.SizeMax=10 \ No newline at end of file