diff --git a/doorlockd/config.h.in b/doorlockd/config.h.in index 4ffc0d3..c4a36e9 100644 --- a/doorlockd/config.h.in +++ b/doorlockd/config.h.in @@ -15,7 +15,6 @@ #define DEFAULT_LDAP_SERVER "ldaps://ldap.binary.kitchen" #define DEFAULT_BINDDN "cn=%s,ou=Users,dc=binary-kitchen,dc=de" #define DEFAULT_LOG_FILE "/var/log/doorlockd.log" -#define DEFAULT_ALLOWED_IP_PREFIX "172.23.3." #define DEFAULT_PID_FILE "/var/run/doorlockd.pid" #endif diff --git a/doorlockd/daemon.h b/doorlockd/daemon.h index ba7e3b5..5c94fd6 100644 --- a/doorlockd/daemon.h +++ b/doorlockd/daemon.h @@ -3,6 +3,11 @@ #include +// Daemonizes the process if daemonize is true. +// If daemonize is true, it will write the new PID to the file "pidFile" +// +// This function will also redirect stdin, out and err to the +// specified files void daemonize(const bool daemonize, const std::string &dir, const std::string &stdinfile, diff --git a/doorlockd/door.cpp b/doorlockd/door.cpp index ede12cb..553fc10 100644 --- a/doorlockd/door.cpp +++ b/doorlockd/door.cpp @@ -13,8 +13,8 @@ Door::Door() : { _l(LogLevel::info, "Initializing Raspberry Pi GPIOs"); wiringPiSetup(); - pinMode(_LOCKPIN, OUTPUT); - pinMode(_SCHNAPPER, OUTPUT); + pinMode(_HEARTBEATPIN, OUTPUT); + pinMode(_SCHNAPPERPIN, OUTPUT); lock(); } @@ -31,49 +31,73 @@ Door &Door::get() void Door::lock() { - digitalWrite(_SCHNAPPER, HIGH); + digitalWrite(_SCHNAPPERPIN, HIGH); _l(LogLevel::info, "Door closed"); - if (_open == true) + + if (_state == State::Unlocked) { - _open = false; + // Stop the Heartbeat Thread + _state = State::Locked; _heartbeat.join(); } + // Turn off all lights system("wget -O /dev/null --timeout 3 \"http://homer.binary.kitchen:8080/set?color=000000\" > /dev/null 2>&1"); } void Door::unlock() { + // In any case, klacker the schnapper _schnapper = true; - if (_open == true) + // If heartbeat is already running, return + if (_state == State::Unlocked) { return; } - _open = true; + // If not, first set state to unlocked + _state = State::Unlocked; + + // Start the Heartbeat Thread _heartbeat = std::thread([this] () { + + // One "beat" is one complete cycle of the heartbeat clock auto beat = [] () { - digitalWrite(_LOCKPIN, HIGH); + digitalWrite(_HEARTBEATPIN, HIGH); usleep(10000); - digitalWrite(_LOCKPIN, LOW); + digitalWrite(_HEARTBEATPIN, LOW); usleep(10000); }; - digitalWrite(_SCHNAPPER, HIGH); - while (_open) { + // The default of the Schnapperpin: always high + digitalWrite(_SCHNAPPERPIN, HIGH); + + // Heartbeat while the state is unlocked + while (_state == State::Unlocked) { + + // In case of schnapper, send 0x55 resp. 0xaa to the schnapperpin if (_schnapper == true) { for (int i = 0; i < 32 ; i++) { - digitalWrite(_SCHNAPPER, LOW); + // Set '0' + digitalWrite(_SCHNAPPERPIN, LOW); + // cycle and send beat(); - digitalWrite(_SCHNAPPER, HIGH); + // Set '1' + digitalWrite(_SCHNAPPERPIN, HIGH); + // cycle and send beat(); } - digitalWrite(_SCHNAPPER, HIGH); + + // Reset schnapperpin + digitalWrite(_SCHNAPPERPIN, HIGH); + // and deactivate schnapper for the next round _schnapper = false; } + + // Heartbeat beat(); } }); diff --git a/doorlockd/door.h b/doorlockd/door.h index d59c2a1..5ed3d64 100644 --- a/doorlockd/door.h +++ b/doorlockd/door.h @@ -6,29 +6,52 @@ #include "logger.h" +/* + * The Door class. + * + * This class exists as a singleton as only one door and hence one object may exist. + * Available via the get() method. + * + * This class is responsible for opening and closing the door by sending + * the heartbeat to the AVR board via Raspberry Pi GPIOs + * + * Whenever the unlock() is called, this class also sends a 0x55 resp. 0xaa + * to the AVR in order to klacker the schnapper. + */ class Door { public: - static Door &get(); + // Returns the singleton + static Door &get(); ~Door(); + enum class State {Locked, Unlocked}; + + // Lock the door void lock(); + // Unlock the door void unlock(); private: Door(); + // used for logging const Logger &_l; - bool _open = { false }; + // Indicates the internal state: Door is open or locked + State _state = { Door::State::Locked }; + + // A Heartbeat thread is started when the door is unlocked std::thread _heartbeat = { }; + // Read by the Heartbeat thread if it should klacker the schnapper or not bool _schnapper = { false }; - static constexpr int _LOCKPIN = 10; - static constexpr int _SCHNAPPER = 7; + // WiringPi GPIO Pins + static constexpr int _HEARTBEATPIN = 10; + static constexpr int _SCHNAPPERPIN = 7; }; #endif diff --git a/doorlockd/epaper.cpp b/doorlockd/epaper.cpp index 5e510ee..9dc329a 100644 --- a/doorlockd/epaper.cpp +++ b/doorlockd/epaper.cpp @@ -16,7 +16,8 @@ using namespace std; Epaper::Epaper() : _logger(Logger::get()) { - memset(_prevImg, 0xFF, ARRAY_SIZE); + memset(_prevImg, 0xFF, _ARRAY_SIZE); + // Initialize Epaper library bsp_init(); } @@ -32,12 +33,12 @@ Epaper &Epaper::get() void Epaper::draw(const string &uri) { - unsigned char buffer[ARRAY_SIZE]; - snprintf((char*)buffer, ARRAY_SIZE, "qrencode -l M -d 100 -s 5 \"%s\" -o /tmp/qr.png", uri.c_str()); + unsigned char buffer[_ARRAY_SIZE]; + snprintf((char*)buffer, _ARRAY_SIZE, "qrencode -l M -d 100 -s 5 \"%s\" -o /tmp/qr.png", uri.c_str()); system((char*)buffer); FILE* f = popen("composite -geometry +90+0 /tmp/qr.png /usr/local/share/doorlockd/template.png -colorspace gray -depth 1 gray:-", "r"); - int i = fread(buffer, ARRAY_SIZE, 1, f); + int i = fread(buffer, _ARRAY_SIZE, 1, f); if (i != 1) { _logger(LogLevel::error, "Image format error"); @@ -47,5 +48,5 @@ void Epaper::draw(const string &uri) pclose(f); epd_DisplayImg(EPDType_270, buffer, _prevImg); - memcpy(_prevImg, buffer, ARRAY_SIZE); + memcpy(_prevImg, buffer, _ARRAY_SIZE); } diff --git a/doorlockd/epaper.h b/doorlockd/epaper.h index c0826f8..a7c82be 100644 --- a/doorlockd/epaper.h +++ b/doorlockd/epaper.h @@ -6,24 +6,35 @@ #include "logger.h" +/* + * The "Epaper" class. + * + * Wrapper for the epaper third Party library. + * + * Exists as singleton, as only one display may exist. + */ class Epaper { public: - constexpr static int HEIGHT = 176; // In Pixel - constexpr static int WIDTH = 33; // In Byte - - constexpr static int ARRAY_SIZE = HEIGHT * WIDTH; - static Epaper &get(); ~Epaper(); + // This function will draw template.png to the display and + // convert the uri to a QR-Code and paste it on top of template.png + // using imagemagick void draw(const std::string &uri); private: + constexpr static int _HEIGHT = 176; // In Pixel + constexpr static int _WIDTH = 33; // In Byte + constexpr static int _ARRAY_SIZE = _HEIGHT * _WIDTH; + Epaper(); - uint8_t _prevImg[ARRAY_SIZE]; + // The old image is needed when updating the Epaper display + // It informs the display which pixels have to be flipped + uint8_t _prevImg[_ARRAY_SIZE]; const Logger &_logger; }; diff --git a/doorlockd/logic.cpp b/doorlockd/logic.cpp index 82a2de5..05a1971 100644 --- a/doorlockd/logic.cpp +++ b/doorlockd/logic.cpp @@ -16,16 +16,14 @@ using namespace std; Logic::Logic(const chrono::seconds tokenTimeout, const string &ldapServer, const string &bindDN, - const string &webPrefix, - const string &allowedIpPrefix) : + const string &webPrefix) : _logger(Logger::get()), _door(Door::get()), _epaper(Epaper::get()), _tokenTimeout(tokenTimeout), _ldapServer(ldapServer), _bindDN(bindDN), - _webPrefix(webPrefix), - _allowedIpPrefix(allowedIpPrefix) + _webPrefix(webPrefix) { srand(time(NULL)); _createNewToken(false); @@ -34,7 +32,7 @@ Logic::Logic(const chrono::seconds tokenTimeout, while (_run) { unique_lock l(_mutex); - _c.wait_for(l, _tokenTimeout); + _tokenCondition.wait_for(l, _tokenTimeout); if (_run == false) { break; @@ -48,7 +46,7 @@ Logic::Logic(const chrono::seconds tokenTimeout, Logic::~Logic() { _run = false; - _c.notify_one(); + _tokenCondition.notify_one(); _tokenUpdater.join(); } @@ -119,14 +117,14 @@ out: Logic::Response Logic::_lock() { - if (_state == LOCKED) + if (_state == Door::State::Locked) { _logger(LogLevel::warning, "Unable to lock: already closed"); return AlreadyLocked; } _door.lock(); - _state = LOCKED; + _state = Door::State::Locked; _createNewToken(false); return Success; @@ -137,12 +135,12 @@ Logic::Response Logic::_unlock() _door.unlock(); _createNewToken(false); - if (_state == UNLOCKED) + if (_state == Door::State::Unlocked) { _logger(LogLevel::warning, "Unable to unlock: already unlocked"); return AlreadyUnlocked; } else { - _state = UNLOCKED; + _state = Door::State::Unlocked; } return Success; diff --git a/doorlockd/logic.h b/doorlockd/logic.h index 9a84f82..2097631 100644 --- a/doorlockd/logic.h +++ b/doorlockd/logic.h @@ -12,6 +12,12 @@ #include "door.h" #include "logger.h" +/* The "Logic" class + * + * This class is initilized by all settings. + * + * It parses incoming JSON-Requests and returns the Response Code. + */ class Logic { public: @@ -19,8 +25,7 @@ public: Logic(const std::chrono::seconds tokenTimeout, const std::string &ldapServer, const std::string &bindDN, - const std::string &webPrefix, - const std::string &allowedIpPrefix); + const std::string &webPrefix); ~Logic(); enum Response { @@ -37,40 +42,63 @@ public: LDAPInit, // Ldap initialization failed }; + // Parse incoming JSON Requests Response parseRequest(const std::string &str); private: + // Internal lock wrapper Response _lock(); + // Internal unlock wrapper Response _unlock(); + // Checks if the incoming token is valid bool _checkToken(const std::string &token); + + // Checks if incoming credentials against LDAP Response _checkLDAP(const std::string &user, const std::string &password); + + // Creates a new random token and draws it on the epaper. + // stillValid indicates whether the old (previous) token is still valid void _createNewToken(const bool stillValid); const Logger &_logger; + + // Door reference Door &_door; + // Epaper reference Epaper &_epaper; + // Tokens are 64-bit hexadecimal values using Token = uint64_t; + // The current token Token _curToken = { 0x0000000000000000 }; - bool _prevValid = { false }; + // The previous token Token _prevToken = { 0x0000000000000000 }; + // Indicates whether the previous token is valid + bool _prevValid = { false }; + // Tokens are refreshed all tokenTimout seconds const std::chrono::seconds _tokenTimeout; - const std::string _ldapServer; - const std::string _bindDN; - const std::string _webPrefix; - const std::string _allowedIpPrefix; - + // Thread for asynchronosly updating tokens std::thread _tokenUpdater = {}; - std::condition_variable _c = {}; - std::mutex _mutex = {}; + // Thread can be force-triggered for updates using the condition variable + std::condition_variable _tokenCondition = {}; + // stop indicator for the thread bool _run = true; + // Token mutex + std::mutex _mutex = {}; - enum {LOCKED, UNLOCKED} _state = { LOCKED }; + // The URI of the ldap server + const std::string _ldapServer; + // LDAP bindDN + const std::string _bindDN; + // Prefix of the website + const std::string _webPrefix; + + Door::State _state = { Door::State::Locked }; }; #endif diff --git a/doorlockd/main.cpp b/doorlockd/main.cpp index 58570a5..e974921 100644 --- a/doorlockd/main.cpp +++ b/doorlockd/main.cpp @@ -30,6 +30,9 @@ void signal_handler(int signum) logic.reset(); } +/* + * Session class handles asynchronosly handles one incoming TCP session + */ class session : public std::enable_shared_from_this { @@ -63,6 +66,9 @@ private: char _data[_maxLen]; }; +/* + * The TCP server + */ class server { @@ -105,13 +111,13 @@ int main(int argc, char** argv) string ldapServer; string bindDN; string lockPagePrefix; - string allowedIpPrefix; string logfile; string pidFile; bool foreground = false; l(LogLevel::notice, "Starting doorlockd"); + // Load SPI and I2C modules system("/usr/bin/gpio load spi"); system("/usr/bin/gpio load i2c"); @@ -125,7 +131,6 @@ int main(int argc, char** argv) ("ldap,s", po::value(&ldapServer)->default_value(DEFAULT_LDAP_SERVER), "Ldap Server") ("bidndn,b", po::value(&bindDN)->default_value(DEFAULT_BINDDN), "Bind DN, %s means username") ("web,w", po::value(&lockPagePrefix)->default_value(DEFAULT_WEB_PREFIX), "Prefix of the webpage") - ("ip,i", po::value(&allowedIpPrefix)->default_value(DEFAULT_ALLOWED_IP_PREFIX), "Default allowed IP Prefix") ("foreground,f", po::bool_switch(&foreground)->default_value(false), "Run in foreground") ("logfile,l", po::value(&logfile)->default_value(DEFAULT_LOG_FILE), "Log file") ("pid,z", po::value(&pidFile)->default_value(DEFAULT_PID_FILE), "PID file"); @@ -166,8 +171,7 @@ int main(int argc, char** argv) logic = unique_ptr(new Logic(tokenTimeout, ldapServer, bindDN, - lockPagePrefix, - allowedIpPrefix)); + lockPagePrefix)); try { server s(io_service, port);