From 7ad040c6c13ddb561097c95395c16fbab9118dd6 Mon Sep 17 00:00:00 2001 From: JDierkse Date: Sat, 15 Dec 2018 22:46:38 +0100 Subject: [PATCH] Initial Commit --- .gitignore | 12 + Application/Makefile | 1 + Application/Makefile.conf | 1 + Application/PresenceDetection.cc | 177 +++++++++ Application/Test.cc | 236 ++++++++++++ Bluetooth/Device.cpp | 103 +++++ Bluetooth/Device.h | 40 ++ Bluetooth/Functions.cpp | 110 ++++++ Bluetooth/Functions.h | 19 + Bluetooth/Makefile | 1 + Bluetooth/Makefile.conf | 1 + Bluetooth/Socket.cpp | 30 ++ Bluetooth/Socket.h | 24 ++ Makefile | 67 ++++ Makefile.conf | 1 + Makefile.conf.local | 23 ++ Makefile.conf.rpi | 25 ++ Makefile.target | 22 ++ PresenceDetection.ini | 14 + README.md | 0 UniFi/Device.cpp | 215 +++++++++++ UniFi/Device.h | 50 +++ UniFi/Makefile | 1 + UniFi/Makefile.conf | 1 + Util/HttpClient.cpp | 629 +++++++++++++++++++++++++++++++ Util/HttpClient.h | 42 +++ Util/HttpClientHelpers.h | 45 +++ Util/Makefile | 1 + Util/Makefile.conf | 1 + Util/Timer.cpp | 51 +++ Util/Timer.h | 31 ++ 31 files changed, 1974 insertions(+) create mode 100644 .gitignore create mode 120000 Application/Makefile create mode 120000 Application/Makefile.conf create mode 100644 Application/PresenceDetection.cc create mode 100644 Application/Test.cc create mode 100644 Bluetooth/Device.cpp create mode 100644 Bluetooth/Device.h create mode 100644 Bluetooth/Functions.cpp create mode 100644 Bluetooth/Functions.h create mode 120000 Bluetooth/Makefile create mode 120000 Bluetooth/Makefile.conf create mode 100644 Bluetooth/Socket.cpp create mode 100644 Bluetooth/Socket.h create mode 100644 Makefile create mode 120000 Makefile.conf create mode 100644 Makefile.conf.local create mode 100644 Makefile.conf.rpi create mode 100644 Makefile.target create mode 100644 PresenceDetection.ini create mode 100644 README.md create mode 100644 UniFi/Device.cpp create mode 100644 UniFi/Device.h create mode 120000 UniFi/Makefile create mode 120000 UniFi/Makefile.conf create mode 100644 Util/HttpClient.cpp create mode 100644 Util/HttpClient.h create mode 100644 Util/HttpClientHelpers.h create mode 120000 Util/Makefile create mode 120000 Util/Makefile.conf create mode 100644 Util/Timer.cpp create mode 100644 Util/Timer.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dc11cce --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +*.o +*.d +*.a +.*.swp +.AppleDouble +.debug +.gdbinit +gdb +core-* +PresenceDetection +test +fixPermissions.sh diff --git a/Application/Makefile b/Application/Makefile new file mode 120000 index 0000000..d0b0e8e --- /dev/null +++ b/Application/Makefile @@ -0,0 +1 @@ +../Makefile \ No newline at end of file diff --git a/Application/Makefile.conf b/Application/Makefile.conf new file mode 120000 index 0000000..6d666f5 --- /dev/null +++ b/Application/Makefile.conf @@ -0,0 +1 @@ +../Makefile.conf \ No newline at end of file diff --git a/Application/PresenceDetection.cc b/Application/PresenceDetection.cc new file mode 100644 index 0000000..b706c8e --- /dev/null +++ b/Application/PresenceDetection.cc @@ -0,0 +1,177 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "UniFi/Device.h" +#include "Bluetooth/Device.h" + + +int main(int argc, char** argv) +{ + try + { + setlogmask(LOG_UPTO(LOG_INFO)); + openlog("PresenceDetection", LOG_CONS | LOG_NDELAY | LOG_PERROR | LOG_PID, LOG_USER); + + boost::program_options::options_description description("Options"); + description.add_options() + ("help,h", "Provide Help Message") + ("daemon,d", "Run the process as Daemon"); + + boost::program_options::variables_map vm; + boost::program_options::store(boost::program_options::parse_command_line(argc, argv, description), vm); + boost::program_options::notify(vm); + + if (vm.count("help")) + { + std::cerr << description << std::endl; + return 1; + } + + bool daemon = false; + if (vm.count("daemon")) + daemon = true; + + boost::property_tree::ptree pt; + boost::property_tree::ini_parser::read_ini("PresenceDetection.ini", pt); + + if (daemon) + { + syslog(LOG_INFO, "Starting Daemon"); + pid_t pid, sid; + pid = fork(); + + if (pid < 0) + exit(EXIT_FAILURE); + if (pid > 0) + exit(EXIT_SUCCESS); + + umask(0); + sid = setsid(); + if (sid < 0) + exit(EXIT_FAILURE); + + if ((chdir("/")) < 0) + exit(EXIT_FAILURE); + + close(STDIN_FILENO); + close(STDOUT_FILENO); + close(STDERR_FILENO); + } + + int port; + boost::optional presenceDetectionPort = pt.get_child_optional("PresenceDetection.Port"); + if (presenceDetectionPort) + port = pt.get("PresenceDetection.Port"); + else + throw std::runtime_error("Port directive missing in ini file."); + + std::string target; + boost::optional presenceDetectionTarget = pt.get_child_optional("PresenceDetection.Target"); + if (presenceDetectionTarget) + target = pt.get("PresenceDetection.Target"); + else + throw std::runtime_error("Target directive missing in ini file."); + + bool unifi = false; + boost::optional presenceDetectionUniFi = pt.get_child_optional("PresenceDetection.UniFi"); + if (presenceDetectionUniFi) + unifi = (pt.get("PresenceDetection.UniFi") == "On"); + + bool bluetooth = false; + boost::optional presenceDetectionBluetooth = pt.get_child_optional("PresenceDetection.Bluetooth"); + if (presenceDetectionBluetooth) + bluetooth = (pt.get("PresenceDetection.Bluetooth") == "On"); + + std::shared_ptr pUniFiDevice; + if (unifi) + { + std::string hostname; + boost::optional unifiHostname = pt.get_child_optional("UniFi.Hostname"); + if (unifiHostname) + hostname = pt.get("UniFi.Hostname"); + else + throw std::runtime_error("UniFi Hostname directive missing in ini file."); + + std::string username; + boost::optional unifiUsername = pt.get_child_optional("UniFi.Username"); + if (unifiUsername) + username = pt.get("UniFi.Username"); + else + throw std::runtime_error("UniFi Username directive missing in ini file."); + + std::string password; + boost::optional unifiPassword = pt.get_child_optional("UniFi.Password"); + if (unifiPassword) + password = pt.get("UniFi.Password"); + else + throw std::runtime_error("UniFi Password directive missing in ini file."); + + std::string cookiefile; + boost::optional unifiCookieFile = pt.get_child_optional("UniFi.CookieFile"); + if (unifiCookieFile) + cookiefile = pt.get("UniFi.CookieFile"); + else + throw std::runtime_error("UniFi CookieFile directive missing in ini file."); + + pUniFiDevice = std::make_shared(hostname, username, password, cookiefile, target); + } + + std::shared_ptr pBluetoothDevice; + if (bluetooth) + { + std::string devices; + boost::optional bluetoothDevices = pt.get_child_optional("Bluetooth.Devices"); + if (bluetoothDevices) + devices = pt.get("Bluetooth.Devices"); + else + throw std::runtime_error("Bluetooth Devices directive missing in ini file."); + + pBluetoothDevice = std::make_shared(devices, target); + } + + sigset_t wset; + sigemptyset(&wset); + sigaddset(&wset,SIGHUP); + sigaddset(&wset,SIGINT); + sigaddset(&wset,SIGTERM); + int sig; + sigwait(&wset,&sig); + + syslog(LOG_INFO, "Stopping..."); + + if (pUniFiDevice) + pUniFiDevice->Stop(); + + if (pBluetoothDevice) + pBluetoothDevice->Stop(); + + if (pUniFiDevice) + pUniFiDevice->Wait(); + + if (pBluetoothDevice) + pBluetoothDevice->Wait(); + + closelog(); + } + catch (const std::exception& e) + { + std::stringstream ss; + ss << "ERROR: " << e.what() << std::endl; + + std::cerr << ss.str() << std::endl; + syslog(LOG_ERR, "%s", ss.str().c_str()); + + closelog(); + + return -1; + } + + return 0; +} diff --git a/Application/Test.cc b/Application/Test.cc new file mode 100644 index 0000000..154807c --- /dev/null +++ b/Application/Test.cc @@ -0,0 +1,236 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/* Defaults */ +static bdaddr_t bdaddr; +static int size = 44; +static int ident = 200; +static int delay = 1; +static int count = -1; +static int timeout = 10; +static int reverse = 0; +static int verify = 0; + +/* Stats */ +static int sent_pkt = 0; +static int recv_pkt = 0; + +static float tv2fl(struct timeval tv) +{ + return (float)(tv.tv_sec*1000.0) + (float)(tv.tv_usec/1000.0); +} + +static void stat(int sig) +{ + int loss = sent_pkt ? (float)((sent_pkt-recv_pkt)/(sent_pkt/100.0)) : 0; + printf("%d sent, %d received, %d%% loss\n", sent_pkt, recv_pkt, loss); + exit(0); +} + +static void ping(const char* svr) +{ + struct sigaction sa; + struct sockaddr_l2 addr; + socklen_t optlen; + unsigned char *send_buf; + unsigned char *recv_buf; + char str[18]; + int i, sk, lost; + uint8_t id; + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = stat; + sigaction(SIGINT, &sa, NULL); + + send_buf = static_cast(malloc(L2CAP_CMD_HDR_SIZE + size)); + recv_buf = static_cast(malloc(L2CAP_CMD_HDR_SIZE + size)); + if (!send_buf || !recv_buf) { + perror("Can't allocate buffer"); + exit(1); + } + + /* Create socket */ + sk = socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_L2CAP); + if (sk < 0) { + perror("Can't create socket"); + goto error; + } + + /* Bind to local address */ + memset(&addr, 0, sizeof(addr)); + addr.l2_family = AF_BLUETOOTH; + bacpy(&addr.l2_bdaddr, &bdaddr); + + if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + perror("Can't bind socket"); + goto error; + } + + /* Connect to remote device */ + memset(&addr, 0, sizeof(addr)); + addr.l2_family = AF_BLUETOOTH; + str2ba(svr, &addr.l2_bdaddr); + + if (connect(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + perror("Can't connect"); + goto error; + } + + /* Get local address */ + memset(&addr, 0, sizeof(addr)); + optlen = sizeof(addr); + + if (getsockname(sk, (struct sockaddr *) &addr, &optlen) < 0) { + perror("Can't get local address"); + goto error; + } + + ba2str(&addr.l2_bdaddr, str); + printf("Ping: %s from %s (data size %d) ...\n", svr, str, size); + + /* Initialize send buffer */ + for (i = 0; i < size; i++) + send_buf[L2CAP_CMD_HDR_SIZE + i] = (i % 40) + 'A'; + + id = ident; + + while (count == -1 || count-- > 0) { + struct timeval tv_send, tv_recv, tv_diff; + l2cap_cmd_hdr *send_cmd = (l2cap_cmd_hdr *) send_buf; + l2cap_cmd_hdr *recv_cmd = (l2cap_cmd_hdr *) recv_buf; + + /* Build command header */ + send_cmd->ident = id; + send_cmd->len = htobs(size); + + if (reverse) + send_cmd->code = L2CAP_ECHO_RSP; + else + send_cmd->code = L2CAP_ECHO_REQ; + + gettimeofday(&tv_send, NULL); + + /* Send Echo Command */ + if (send(sk, send_buf, L2CAP_CMD_HDR_SIZE + size, 0) <= 0) { + perror("Send failed"); + goto error; + } + + /* Wait for Echo Response */ + lost = 0; + while (1) { + struct pollfd pf[1]; + int err; + + pf[0].fd = sk; + pf[0].events = POLLIN; + + if ((err = poll(pf, 1, timeout * 1000)) < 0) { + perror("Poll failed"); + goto error; + } + + if (!err) { + lost = 1; + break; + } + + if ((err = recv(sk, recv_buf, L2CAP_CMD_HDR_SIZE + size, 0)) < 0) { + perror("Recv failed"); + goto error; + } + + if (!err){ + printf("Disconnected\n"); + goto error; + } + + recv_cmd->len = btohs(recv_cmd->len); + + /* Check for our id */ + if (recv_cmd->ident != id) + continue; + + /* Check type */ + if (!reverse && recv_cmd->code == L2CAP_ECHO_RSP) + break; + + if (recv_cmd->code == L2CAP_COMMAND_REJ) { + printf("Peer doesn't support Echo packets\n"); + goto error; + } + + } + sent_pkt++; + + if (!lost) { + recv_pkt++; + + gettimeofday(&tv_recv, NULL); + timersub(&tv_recv, &tv_send, &tv_diff); + + if (verify) { + /* Check payload length */ + if (recv_cmd->len != size) { + fprintf(stderr, "Received %d bytes, expected %d\n", + recv_cmd->len, size); + goto error; + } + + /* Check payload */ + if (memcmp(&send_buf[L2CAP_CMD_HDR_SIZE], + &recv_buf[L2CAP_CMD_HDR_SIZE], size)) { + fprintf(stderr, "Response payload different.\n"); + goto error; + } + } + + printf("%d bytes from %s id %d time %.2fms\n", recv_cmd->len, svr, + id - ident, tv2fl(tv_diff)); + + if (delay) + sleep(delay); + } else { + printf("no response from %s: id %d\n", svr, id - ident); + } + + if (++id > 254) + id = ident; + } + stat(0); + free(send_buf); + free(recv_buf); + return; + +error: + close(sk); + free(send_buf); + free(recv_buf); + exit(1); +} + +int main(int /*argc*/, char** /*argv[]*/) +{ + std::string address = "48:4B:AA:85:30:C5"; + ping(address.c_str()); + + //address = "d0:c5:f3:84:99:de"; + //ping(address.c_str()); + + return 0; +} diff --git a/Bluetooth/Device.cpp b/Bluetooth/Device.cpp new file mode 100644 index 0000000..f1a45c5 --- /dev/null +++ b/Bluetooth/Device.cpp @@ -0,0 +1,103 @@ +#include +#include +#include +#include "Functions.h" +#include "Device.h" + + +namespace PresenceDetection { +namespace Bluetooth { + +Device::Device(const std::string& devices, const std::string& target) : + m_target(target) +{ + boost::split(m_devices, devices, boost::is_any_of(",")); + Start(); +} + +Device::~Device() +{ +} + +void Device::Start() +{ + m_timer.Start(10000, std::bind(&Device::UpdatePresentDevices, this)); +} + +void Device::Stop() +{ + m_timer.Stop(); +} + +void Device::Wait() +{ + m_timer.Wait(); +} + +void Device::UpdatePresentDevices() +{ + std::vector presentDevices; + std::vector addedDevices; + std::vector removedDevices; + + for (std::vector::iterator it = m_devices.begin(); it != m_devices.end(); ++it) + { + if (Functions::Ping(*it)) + { + if (std::find(m_presentDevices.begin(), m_presentDevices.end(), *it) == m_presentDevices.end()) + addedDevices.push_back(*it); + presentDevices.push_back(*it); + } + } + + for (std::vector::iterator it = m_presentDevices.begin(); it != m_presentDevices.end(); ++it) + if (std::find(presentDevices.begin(), presentDevices.end(), *it) == presentDevices.end()) + removedDevices.push_back(*it); + + for (std::vector::iterator it = addedDevices.begin(); it != addedDevices.end(); ++it) + { + std::stringstream ss; + ss << "Bluetooth: + " << *it; + syslog(LOG_INFO, "%s", ss.str().c_str()); + + std::stringstream url; + url << m_target << "/Bluetooth/+/" << *it; + + try + { + m_httpClient.GetUrlSilent(url.str()); + } + catch (const std::exception& e) + { + std::stringstream ss; + ss << "BluetoothDevice::UpdatePresentDevices() - Error: " << e.what() << std::endl; + syslog(LOG_ERR, "%s", ss.str().c_str()); + } + } + + for (std::vector::iterator it = removedDevices.begin(); it != removedDevices.end(); ++it) + { + std::stringstream ss; + ss << "Bluetooth: - " << *it; + syslog(LOG_INFO, "%s", ss.str().c_str()); + + std::stringstream url; + url << m_target << "/Bluetooth/-/" << *it; + + try + { + m_httpClient.GetUrlSilent(url.str()); + } + catch (const std::exception& e) + { + std::stringstream ss; + ss << "BluetoothDevice::UpdatePresentDevices() - Error: " << e.what() << std::endl; + syslog(LOG_ERR, "%s", ss.str().c_str()); + } + } + + m_presentDevices.assign(presentDevices.begin(), presentDevices.end()); +} + +} // namespace Bluetooth +} // namespace PresenceDetection diff --git a/Bluetooth/Device.h b/Bluetooth/Device.h new file mode 100644 index 0000000..2ca75ea --- /dev/null +++ b/Bluetooth/Device.h @@ -0,0 +1,40 @@ +#ifndef BLUETOOTH_DEVICE_H +#define BLUETOOTH_DEVICE_H + +#include +#include +#include "Util/Timer.h" +#include "Util/HttpClient.h" + + +namespace PresenceDetection { +namespace Bluetooth { + +class Device +{ +public: + Device(const std::string& devices, const std::string& target); + ~Device(); + + void Start(); + void Stop(); + void Wait(); + +private: + void UpdatePresentDevices(); + +private: + Util::Timer m_timer; + Util::HttpClient m_httpClient; + + std::vector m_devices; + + std::string m_target; + + std::vector m_presentDevices; +}; + +} // namespace Bluetooth +} // namespace PresenceDetection + +#endif // BLUETOOTH_DEVICE_H diff --git a/Bluetooth/Functions.cpp b/Bluetooth/Functions.cpp new file mode 100644 index 0000000..b234d1d --- /dev/null +++ b/Bluetooth/Functions.cpp @@ -0,0 +1,110 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "Socket.h" +#include "Functions.h" + + +namespace PresenceDetection { +namespace Bluetooth { + +bool Functions::Ping(const std::string& macAddress) +{ + struct sockaddr_l2 socketAddress; + + int size = 44; + int messageSize = L2CAP_CMD_HDR_SIZE + size; + std::vector sendBuffer(messageSize); + std::vector receiveBuffer(messageSize); + + Socket socket; + int socketDescriptor = socket.Descriptor(); + if (socketDescriptor < 0) + throw std::runtime_error("Can't create Bluetooth socket"); + + memset(&socketAddress, 0, sizeof(socketAddress)); + socketAddress.l2_family = AF_BLUETOOTH; + + bdaddr_t bdaddr; + bacpy(&socketAddress.l2_bdaddr, &bdaddr); + + if (bind(socketDescriptor, reinterpret_cast(&socketAddress), sizeof(socketAddress)) < 0) + throw std::runtime_error("Can't bind socket"); + + memset(&socketAddress, 0, sizeof(socketAddress)); + socketAddress.l2_family = AF_BLUETOOTH; + str2ba(macAddress.c_str(), &socketAddress.l2_bdaddr); + + if (connect(socketDescriptor, reinterpret_cast(&socketAddress), sizeof(socketAddress)) < 0) + return false; + + memset(&socketAddress, 0, sizeof(socketAddress)); + socklen_t optlen = sizeof(socketAddress); + + if (getsockname(socketDescriptor, reinterpret_cast(&socketAddress), &optlen) < 0) + throw std::runtime_error("Can't get local address"); + + char str[18]; + ba2str(&socketAddress.l2_bdaddr, str); + + for (int i = 0; i < size; ++i) + sendBuffer[L2CAP_CMD_HDR_SIZE + i] = (i % 40) + 'A'; + + int ident = 200; + uint8_t id = ident; + + l2cap_cmd_hdr* send_cmd = reinterpret_cast(sendBuffer.data()); + l2cap_cmd_hdr* recv_cmd = reinterpret_cast(receiveBuffer.data()); + + send_cmd->ident = id; + send_cmd->len = htobs(size); + send_cmd->code = L2CAP_ECHO_REQ; + + if (send(socketDescriptor, sendBuffer.data(), messageSize, 0) <= 0) + throw std::runtime_error("Send failed"); + + while (1) + { + struct pollfd pf[1]; + + pf[0].fd = socketDescriptor; + pf[0].events = POLLIN; + + int err; + int timeout = 500; + if ((err = poll(pf, 1, timeout)) < 0) + throw std::runtime_error("Poll failed"); + + if (!err) + return false; + + if ((err = recv(socketDescriptor, receiveBuffer.data(), messageSize, 0)) < 0) + return false; + + if (!err) + throw std::runtime_error("Disconnected"); + + recv_cmd->len = btohs(recv_cmd->len); + + if (recv_cmd->ident != id) + continue; + + if (recv_cmd->code == L2CAP_ECHO_RSP) + break; + + if (recv_cmd->code == L2CAP_COMMAND_REJ) + throw std::runtime_error("Peer doesn't support Echo packets"); + } + + return true; +} + +} // namespace Bluetooth +} // namespace PresenceDetection diff --git a/Bluetooth/Functions.h b/Bluetooth/Functions.h new file mode 100644 index 0000000..3f68c9e --- /dev/null +++ b/Bluetooth/Functions.h @@ -0,0 +1,19 @@ +#ifndef BLUETOOTH_FUNCTIONS_H +#define BLUETOOTH_FUNCTIONS_H + +#include + + +namespace PresenceDetection { +namespace Bluetooth { + +class Functions +{ +public: + static bool Ping(const std::string& macAddress); +}; + +} // namespace Bluetooth +} // namespace PresenceDetection + +#endif // BLUETOOTH_FUNCTIONS_H diff --git a/Bluetooth/Makefile b/Bluetooth/Makefile new file mode 120000 index 0000000..d0b0e8e --- /dev/null +++ b/Bluetooth/Makefile @@ -0,0 +1 @@ +../Makefile \ No newline at end of file diff --git a/Bluetooth/Makefile.conf b/Bluetooth/Makefile.conf new file mode 120000 index 0000000..6d666f5 --- /dev/null +++ b/Bluetooth/Makefile.conf @@ -0,0 +1 @@ +../Makefile.conf \ No newline at end of file diff --git a/Bluetooth/Socket.cpp b/Bluetooth/Socket.cpp new file mode 100644 index 0000000..fd641e0 --- /dev/null +++ b/Bluetooth/Socket.cpp @@ -0,0 +1,30 @@ +#include +#include +#include +#include +#include "Socket.h" + + +namespace PresenceDetection { +namespace Bluetooth { + +Socket::Socket() +{ + m_descriptor = socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_L2CAP); + if (m_descriptor < 0) + throw std::runtime_error("Can't create Bluetooth socket"); +} + +Socket::~Socket() +{ + close(m_descriptor); +} + +int Socket::Descriptor() const +{ + return m_descriptor; +} + +} // namespace Bluetooth +} // namespace PresenceDetection + diff --git a/Bluetooth/Socket.h b/Bluetooth/Socket.h new file mode 100644 index 0000000..fb67221 --- /dev/null +++ b/Bluetooth/Socket.h @@ -0,0 +1,24 @@ +#ifndef BLUETOOTH_SOCKET_H +#define BLUETOOTH_SOCKET_H + + +namespace PresenceDetection { +namespace Bluetooth { + +class Socket +{ +public: + Socket(); + ~Socket(); + + int Descriptor() const; + +private: + int m_descriptor; +}; + +} // namespace Bluetooth +} // namespace PresenceDetection + +#endif // BLUETOOTH_SOCKET_H + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..61216ac --- /dev/null +++ b/Makefile @@ -0,0 +1,67 @@ +# +# Makefile +# + +include Makefile.conf + +ARNAME = $(notdir $(CURDIR)).a + +SUBDIRS := $(wildcard */.) +SUBDIRS := $(foreach dir,$(SUBDIRS),$(subst /.,,$(dir))) +FILTER = doc% +SUBDIRS := $(filter-out $(FILTER),$(SUBDIRS)) +SUBDIRARS = $(foreach dir,$(SUBDIRS),$(dir)/$(dir).a) + +LOCALSOURCES = $(wildcard *.cpp *.cc) +SOURCES = $(wildcard *.cpp */*.cpp */*/*.cpp */*/*/*.cpp) +LOCALOBJECT_ = $(LOCALSOURCES:.cpp=.o) +LOCALOBJECTS = $(LOCALOBJECT_:.cc=.o) +OBJECTS = $(SOURCES:.cpp=.o) +LOCALDEPENDS = $(LOCALOBJECTS:.o=.d) +DEPENDS = $(OBJECTS:.o=.d) +TARGETS = + +-include Makefile.target + +artifacts: $(SUBDIRS) $(LOCALOBJECTS) + +ifneq ($(MAKECMDGOALS),clean) +-include $(LOCALDEPENDS) +endif + +%.d: %.cpp + @echo [MM] $@ + @$(CC) $(CFLAGS) $< -MM -MT $(@:.d=.o) >$@ + +%.o: %.cpp + @echo [CC] $@ + @nice -n 19 $(CC) -c $(CFLAGS) $< -o $@ + +%.d: %.cc + @echo [MM] $@ + @$(CC) $(CFLAGS) $< -MM -MT $(@:.d=.o) >$@ + +%.o: %.cc + @echo [CC] $@ + @nice -n 19 $(CC) -c $(CFLAGS) $< -o $@ + +$(ARNAME): $(SUBDIRS) $(LOCALOBJECTS) + @echo [AR] $@ + @nice -n 19 $(AR) rcuT $@ $(LOCALOBJECTS) $(SUBDIRARS) + +$(OBJECTS): | $(SUBDIRS) + +.PHONY: $(SUBDIRS) +$(SUBDIRS): + @$(MAKE) -S -C $@ artifacts + +.PHONY: all +all: $(TARGETS) + +.PHONY: clean +clean: + @for dir in $(SUBDIRS); do \ + $(MAKE) -S -C $$dir $@; \ + done + @rm -f $(LOCALOBJECTS) $(LOCALDEPENDS) $(ARNAME) $(TARGETS) || true + diff --git a/Makefile.conf b/Makefile.conf new file mode 120000 index 0000000..c58b57f --- /dev/null +++ b/Makefile.conf @@ -0,0 +1 @@ +Makefile.conf.local \ No newline at end of file diff --git a/Makefile.conf.local b/Makefile.conf.local new file mode 100644 index 0000000..5ce1541 --- /dev/null +++ b/Makefile.conf.local @@ -0,0 +1,23 @@ +# +# Makefile.conf.local +# + +CFLAGS := -g3 -O3 -Wall -fPIC +CFLAGS += -Wall -Wextra -Wstrict-aliasing -pedantic -fmax-errors=5 -Werror -Wunreachable-code -Wcast-qual -Wdisabled-optimization -Wformat=2 -Winit-self -Wlogical-op -Wmissing-include-dirs -Wnoexcept -Woverloaded-virtual -Wsign-promo -Wstrict-null-sentinel -Wswitch-default -Wno-unused -Wno-variadic-macros -Wno-parentheses -fdiagnostics-show-option +# CFLAGS += -Wold-style-cast -Wundef -Wshadow -Wctor-dtor-privacy -Wredundant-decls -Wstrict-overflow=5 -Wcast-align +LFLAGS := -lpthread -lm -lboost_system -lboost_thread -lboost_program_options -lboost_date_time -lssl -lcrypto -lcurl -lz -ldl -lbluetooth + +AR := ar +CC := g++ +STRIP := strip +OBJCOPY := objcopy + +CFLAGS += -fpermissive +CFLAGS += -I/opt/build/PresenceDetection + +CFLAGS += -ffunction-sections -fdata-sections +LFLAGS += -Wl,--gc-sections +SFLAGS := -s -R .comment -R .gnu.version --strip-unneeded + +DEBUGDIR := .debug +DATETIME := `date +'%y%m%d-%H%M'` diff --git a/Makefile.conf.rpi b/Makefile.conf.rpi new file mode 100644 index 0000000..1f98d8a --- /dev/null +++ b/Makefile.conf.rpi @@ -0,0 +1,25 @@ +# +# Makefile.conf +# + +CFLAGS := -g3 -O3 -fPIC -marm -march=armv6 -std=c++11 +CFLAGS += -Wall -Wextra -Wstrict-aliasing -pedantic -fmax-errors=5 -Werror -Wunreachable-code -Wcast-qual -Wdisabled-optimization -Wformat=2 -Winit-self -Wlogical-op -Wmissing-include-dirs -Wnoexcept -Woverloaded-virtual -Wsign-promo -Wstrict-null-sentinel -Wswitch-default -Wno-unused -Wno-variadic-macros -Wno-parentheses -fdiagnostics-show-option +# CFLAGS += -Wold-style-cast -Wundef -Wshadow -Wctor-dtor-privacy -Wredundant-decls -Wstrict-overflow=5 -Wcast-align +LFLAGS := -lpthread -lm -lboost_system -lboost_thread -lboost_program_options -lboost_date_time -lssl -lcrypto -lcurl -lmysqlcppconn -lmysqlclient -lz -ldl + +AR := /opt/build/rpi-armv6/tools/arm-bcm2708/arm-linux-gnueabihf/bin/arm-linux-gnueabihf-ar +CC := /opt/build/rpi-armv6/tools/arm-bcm2708/arm-linux-gnueabihf/bin/arm-linux-gnueabihf-g++ +STRIP := /opt/build/rpi-armv6/tools/arm-bcm2708/arm-linux-gnueabihf/bin/arm-linux-gnueabihf-strip +OBJCOPY := /opt/build/rpi-armv6/tools/arm-bcm2708/arm-linux-gnueabihf/bin/arm-linux-gnueabihf-objcopy + +CFLAGS += -I/opt/build/rpi-armv6/include -I/opt/build/rpi-armv6/include/mysql +LFLAGS += -L/opt/build/rpi-armv6/lib --sysroot /opt/build/rpi-armv6/tools/arm-bcm2708/arm-linux-gnueabihf/arm-linux-gnueabihf/sysroot -Wl,-rpath,/opt/build/rpi-armv6/lib + +CFLAGS += -I/opt/build/DomoticaServer + +CFLAGS += -ffunction-sections -fdata-sections +LFLAGS += -Wl,--gc-sections +SFLAGS := -s -R .comment -R .gnu.version --strip-unneeded + +DEBUGDIR := .debug +DATETIME := `date +'%y%m%d-%H%M'` diff --git a/Makefile.target b/Makefile.target new file mode 100644 index 0000000..456fe78 --- /dev/null +++ b/Makefile.target @@ -0,0 +1,22 @@ +# +# Makefile.target +# + +PresenceDetection: Application/PresenceDetection.o $(OBJECTS) + $(eval $@_DATETIME := $(DATETIME)) + @echo [LD] $@ + @$(CC) -o $@ $^ $(LFLAGS) $(CFLAGS) + @$(OBJCOPY) --only-keep-debug $@ $(DEBUGDIR)/$@-$($@_DATETIME).debug + @$(STRIP) $(SFLAGS) $@ + @$(OBJCOPY) --add-gnu-debuglink="$(DEBUGDIR)/$@-$($@_DATETIME).debug" $@ + @chmod -x $(DEBUGDIR)/$@-$($@_DATETIME).debug + +test: Application/Test.o $(OBJECTS) + @echo [LD] $@ + @$(CC) -o $@ $^ $(LFLAGS) $(CFLAGS) + @$(STRIP) $(SFLAGS) $@ + +.DEFAULT_GOAL := PresenceDetection + +TARGETS += PresenceDetection test + diff --git a/PresenceDetection.ini b/PresenceDetection.ini new file mode 100644 index 0000000..31ccd34 --- /dev/null +++ b/PresenceDetection.ini @@ -0,0 +1,14 @@ +[PresenceDetection] +Port = 8000 +Target = +UniFi = On +Bluetooth = On + +[UniFi] +Hostname = UniFi +Username = +Password = +CookieFile = /tmp/UniFi_Cookies + +[Bluetooth] +Devices = diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/UniFi/Device.cpp b/UniFi/Device.cpp new file mode 100644 index 0000000..7e89bf5 --- /dev/null +++ b/UniFi/Device.cpp @@ -0,0 +1,215 @@ +#include +#include +#include +#include +#include "Device.h" + + +namespace PresenceDetection { +namespace UniFi { + +Device::Device(const std::string& hostname, const std::string& username, const std::string& password, const std::string& cookieFile, const std::string& target) : + m_loggedIn(false), + m_hostname(hostname), + m_username(username), + m_password(password), + m_cookieFile(cookieFile), + m_target(target) +{ + m_offlineTimeout = 120; + Start(); +} + +Device::~Device() +{ + Logout(); +} + +void Device::Start() +{ + m_timer.Start(5000, std::bind(&Device::UpdatePresentDevices, this)); +} + +void Device::Stop() +{ + m_timer.Stop(); +} + +void Device::Wait() +{ + m_timer.Wait(); +} + +bool Device::Login() +{ + std::stringstream url; + url << "https://" << m_hostname << "/api/login"; + + std::ostringstream json; + boost::property_tree::ptree root; + root.put("password", m_password); + root.put("username", m_username); + boost::property_tree::write_json(json, root); + + try + { + std::stringstream output; + output << m_httpClient.GetUrlPostContents(url.str(), m_cookieFile, json.str(), "application/json"); + + boost::property_tree::ptree pt; + boost::property_tree::read_json(output, pt); + + std::string result = pt.get_child("meta").get("rc"); + + if (result != "ok") + { + std::stringstream error; + error << "Login Failed - " << output.str(); + throw std::runtime_error(error.str()); + } + } + catch (const std::exception& e) + { + std::stringstream ss; + ss << "UniFiDevice::Login() - Error: " << e.what() << std::endl; + syslog(LOG_ERR, "%s", ss.str().c_str()); + std::lock_guard lock(m_mutex); + m_loggedIn = false; + return m_loggedIn; + } + + std::lock_guard lock(m_mutex); + m_loggedIn = true; + return m_loggedIn; +} + +void Device::Logout() +{ + std::stringstream url; + url << "https://" << m_hostname << "/logout"; + + std::lock_guard lock(m_mutex); + m_loggedIn = false; + + try + { + m_httpClient.GetUrlSilent(url.str(), m_cookieFile); + } + catch (const std::exception& e) + { + std::stringstream ss; + ss << "UniFiDevice::Logout() - Error: " << e.what() << std::endl; + syslog(LOG_ERR, "%s", ss.str().c_str()); + } +} + +void Device::UpdatePresentDevices() +{ + bool loggedIn; + { + std::lock_guard lock(m_mutex); + loggedIn = m_loggedIn; + } + + if (!loggedIn) + if (!Login()) + return; + + std::stringstream url; + url << "https://" << m_hostname << "/api/s/default/stat/sta"; + + std::time_t timeStamp = std::time(nullptr); + std::vector presentDevices; + std::vector addedDevices; + std::vector removedDevices; + + try + { + std::stringstream output; + output << m_httpClient.GetUrlContents(url.str(), m_cookieFile); + + boost::property_tree::ptree pt; + boost::property_tree::read_json(output, pt); + + std::string result = pt.get_child("meta").get("rc"); + + if (result != "ok") + { + std::stringstream error; + error << "Query Failed"; + throw std::runtime_error(error.str()); + } + + BOOST_FOREACH(boost::property_tree::ptree::value_type &v, pt.get_child("data")) + { + std::string macAddress = v.second.get("mac"); + int lastSeen = v.second.get("last_seen"); + + if ((timeStamp - lastSeen) < m_offlineTimeout) + { + if (std::find(m_presentDevices.begin(), m_presentDevices.end(), macAddress) == m_presentDevices.end()) + addedDevices.push_back(macAddress); + presentDevices.push_back(macAddress); + } + } + } + catch (const std::exception& e) + { + std::stringstream ss; + ss << "UniFiDevice::IsDevicePresent() - Error: " << e.what() << std::endl; + syslog(LOG_ERR, "%s", ss.str().c_str()); + Logout(); + return; + } + + for (std::vector::iterator it = m_presentDevices.begin(); it != m_presentDevices.end(); ++it) + if (std::find(presentDevices.begin(), presentDevices.end(), *it) == presentDevices.end()) + removedDevices.push_back(*it); + + for (std::vector::iterator it = addedDevices.begin(); it != addedDevices.end(); ++it) + { + std::stringstream ss; + ss << "UniFi: + " << *it; + syslog(LOG_INFO, "%s", ss.str().c_str()); + + std::stringstream url; + url << m_target << "/UniFi/+/" << *it; + + try + { + m_httpClient.GetUrlSilent(url.str()); + } + catch (const std::exception& e) + { + std::stringstream ss; + ss << "UniFiDevice::UpdatePresentDevices() - Error: " << e.what() << std::endl; + syslog(LOG_ERR, "%s", ss.str().c_str()); + } + } + + for (std::vector::iterator it = removedDevices.begin(); it != removedDevices.end(); ++it) + { + std::stringstream ss; + ss << "UniFi: - " << *it; + syslog(LOG_INFO, "%s", ss.str().c_str()); + + std::stringstream url; + url << m_target << "/UniFi/-/" << *it; + + try + { + m_httpClient.GetUrlSilent(url.str()); + } + catch (const std::exception& e) + { + std::stringstream ss; + ss << "UniFiDevice::UpdatePresentDevices() - Error: " << e.what() << std::endl; + syslog(LOG_ERR, "%s", ss.str().c_str()); + } + } + + m_presentDevices.assign(presentDevices.begin(), presentDevices.end()); +} + +} // namespace UniFi +} // namespace PresenceDetection diff --git a/UniFi/Device.h b/UniFi/Device.h new file mode 100644 index 0000000..cadddfc --- /dev/null +++ b/UniFi/Device.h @@ -0,0 +1,50 @@ +#ifndef UNIFI_DEVICE_H +#define UNIFI_DEVICE_H + +#include +#include +#include +#include "Util/Timer.h" +#include "Util/HttpClient.h" + + +namespace PresenceDetection { +namespace UniFi { + +class Device +{ +public: + Device(const std::string& hostname, const std::string& username, const std::string& password, const std::string& cookieFile, const std::string& target); + ~Device(); + + void Start(); + void Stop(); + void Wait(); + +private: + bool Login(); + void Logout(); + + void UpdatePresentDevices(); + +private: + Util::Timer m_timer; + Util::HttpClient m_httpClient; + + std::mutex m_mutex; + bool m_loggedIn; + std::string m_hostname; + std::string m_username; + std::string m_password; + std::string m_cookieFile; + + std::string m_target; + int m_offlineTimeout; + + std::vector m_presentDevices; +}; + +} // namespace UniFi +} // namespace PresenceDetection + +#endif // UNIFI_DEVICE_H diff --git a/UniFi/Makefile b/UniFi/Makefile new file mode 120000 index 0000000..d0b0e8e --- /dev/null +++ b/UniFi/Makefile @@ -0,0 +1 @@ +../Makefile \ No newline at end of file diff --git a/UniFi/Makefile.conf b/UniFi/Makefile.conf new file mode 120000 index 0000000..6d666f5 --- /dev/null +++ b/UniFi/Makefile.conf @@ -0,0 +1 @@ +../Makefile.conf \ No newline at end of file diff --git a/Util/HttpClient.cpp b/Util/HttpClient.cpp new file mode 100644 index 0000000..eae67e4 --- /dev/null +++ b/Util/HttpClient.cpp @@ -0,0 +1,629 @@ +#include +#include +#include +#include +#include "HttpClientHelpers.h" +#include "HttpClient.h" + + +namespace PresenceDetection { +namespace Util { + +HttpClient::HttpClient() +{ + curl_global_init(CURL_GLOBAL_ALL); + + m_userAgents.push_back("Opera/9.80 (X11; Linux i686; U; en) Presto/2.7.62 Version/11.00"); + + InitializeLocks(); +} + +HttpClient::~HttpClient() +{ + FreeLocks(); + curl_global_cleanup(); +} + +std::string HttpClient::GetUrlContents(const std::string& url) const +{ + std::string buffer; + + CURL* curl = curl_easy_init(); + + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); + curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0); + curl_easy_setopt(curl, CURLOPT_USERAGENT, m_userAgents[rand() % m_userAgents.size()].c_str()); + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer); + + int code = 0; + curl_easy_perform(curl); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code); + curl_easy_cleanup(curl); + + if (code != 200) + { + std::stringstream error; + error << "Error (" << code << ") encountered while retrieving " << url << "\n"; + error << "Data: " << buffer; + throw std::runtime_error(error.str()); + } + + return buffer; +} + +void HttpClient::GetUrlSilent(const std::string& url) const +{ + CURL* curl = curl_easy_init(); + + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); + curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0); + curl_easy_setopt(curl, CURLOPT_USERAGENT, m_userAgents[rand() % m_userAgents.size()].c_str()); + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, nullptr); + + int code = 0; + curl_easy_perform(curl); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code); + curl_easy_cleanup(curl); + + if (code != 200) + { + std::stringstream error; + error << "Error (" << code << ") encountered while retrieving " << url << "\n"; + throw std::runtime_error(error.str()); + } +} + +std::string HttpClient::GetUrlContents(const std::string& url, const std::string& cookieFile) const +{ + std::string buffer; + + CURL* curl = curl_easy_init(); + + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); + curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0); + curl_easy_setopt(curl, CURLOPT_USERAGENT, m_userAgents[rand() % m_userAgents.size()].c_str()); + curl_easy_setopt(curl, CURLOPT_COOKIEFILE, cookieFile.c_str()); + curl_easy_setopt(curl, CURLOPT_COOKIEJAR, cookieFile.c_str()); + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer); + + int code = 0; + curl_easy_perform(curl); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code); + curl_easy_cleanup(curl); + + if (code != 200) + { + std::stringstream error; + error << "Error (" << code << ") encountered while retrieving " << url << "\n"; + error << "Data: " << buffer; + throw std::runtime_error(error.str()); + } + + return buffer; +} + +void HttpClient::GetUrlSilent(const std::string& url, const std::string& cookieFile) const +{ + CURL* curl = curl_easy_init(); + + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); + curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0); + curl_easy_setopt(curl, CURLOPT_USERAGENT, m_userAgents[rand() % m_userAgents.size()].c_str()); + curl_easy_setopt(curl, CURLOPT_COOKIEFILE, cookieFile.c_str()); + curl_easy_setopt(curl, CURLOPT_COOKIEJAR, cookieFile.c_str()); + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, nullptr); + + int code = 0; + curl_easy_perform(curl); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code); + curl_easy_cleanup(curl); + + if (code != 200) + { + std::stringstream error; + error << "Error (" << code << ") encountered while retrieving " << url << "\n"; + throw std::runtime_error(error.str()); + } +} + +std::string HttpClient::GetUrlPostContents(const std::string& url, const std::string& postData, const std::string& contentType) const +{ + std::string buffer; + + CURL* curl = curl_easy_init(); + + std::stringstream contentTypeString; + contentTypeString << "Content-Type: " << contentType; + + struct curl_slist *list = NULL; + list = curl_slist_append(list, contentTypeString.str().c_str()); + + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); + curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0); + curl_easy_setopt(curl, CURLOPT_USERAGENT, m_userAgents[rand() % m_userAgents.size()].c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list); + curl_easy_setopt(curl, CURLOPT_POST, 1); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str()); + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, postData.size()); + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer); + + int code = 0; + curl_easy_perform(curl); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code); + curl_easy_cleanup(curl); + + if (code != 200) + { + std::stringstream error; + error << "Error (" << code << ") encountered while retrieving " << url << "\n"; + error << "Data: " << buffer; + throw std::runtime_error(error.str()); + } + + return buffer; +} + +void HttpClient::GetUrlPostSilent(const std::string& url, const std::string& postData, const std::string& contentType) const +{ + CURL* curl = curl_easy_init(); + + std::stringstream contentTypeString; + contentTypeString << "Content-Type: " << contentType; + + struct curl_slist *list = NULL; + list = curl_slist_append(list, contentTypeString.str().c_str()); + + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); + curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0); + curl_easy_setopt(curl, CURLOPT_USERAGENT, m_userAgents[rand() % m_userAgents.size()].c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list); + curl_easy_setopt(curl, CURLOPT_POST, 1); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str()); + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, postData.size()); + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, nullptr); + + int code = 0; + curl_easy_perform(curl); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code); + curl_easy_cleanup(curl); + + if (code != 200) + { + std::stringstream error; + error << "Error (" << code << ") encountered while retrieving " << url << "\n"; + throw std::runtime_error(error.str()); + } +} + +std::string HttpClient::GetUrlPostContents(const std::string& url, const std::string& cookieFile, const std::string& postData, const std::string& contentType) const +{ + std::string buffer; + + CURL* curl = curl_easy_init(); + + std::stringstream contentTypeString; + contentTypeString << "Content-Type: " << contentType; + + struct curl_slist *list = NULL; + list = curl_slist_append(list, contentTypeString.str().c_str()); + + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); + curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0); + curl_easy_setopt(curl, CURLOPT_USERAGENT, m_userAgents[rand() % m_userAgents.size()].c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list); + curl_easy_setopt(curl, CURLOPT_POST, 1); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str()); + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, postData.size()); + curl_easy_setopt(curl, CURLOPT_COOKIEFILE, cookieFile.c_str()); + curl_easy_setopt(curl, CURLOPT_COOKIEJAR, cookieFile.c_str()); + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer); + + int code = 0; + curl_easy_perform(curl); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code); + curl_easy_cleanup(curl); + + if (code != 200) + { + std::stringstream error; + error << "Error (" << code << ") encountered while retrieving " << url << "\n"; + error << "Data: " << buffer; + throw std::runtime_error(error.str()); + } + + return buffer; +} + +void HttpClient::GetUrlPostSilent(const std::string& url, const std::string& cookieFile, const std::string& postData, const std::string& contentType) const +{ + CURL* curl = curl_easy_init(); + + std::stringstream contentTypeString; + contentTypeString << "Content-Type: " << contentType; + + struct curl_slist *list = NULL; + list = curl_slist_append(list, contentTypeString.str().c_str()); + + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); + curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0); + curl_easy_setopt(curl, CURLOPT_USERAGENT, m_userAgents[rand() % m_userAgents.size()].c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list); + curl_easy_setopt(curl, CURLOPT_POST, 1); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str()); + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, postData.size()); + curl_easy_setopt(curl, CURLOPT_COOKIEFILE, cookieFile.c_str()); + curl_easy_setopt(curl, CURLOPT_COOKIEJAR, cookieFile.c_str()); + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, nullptr); + + int code = 0; + curl_easy_perform(curl); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code); + curl_easy_cleanup(curl); + + if (code != 200) + { + std::stringstream error; + error << "Error (" << code << ") encountered while retrieving " << url << "\n"; + throw std::runtime_error(error.str()); + } +} + +std::string HttpClient::GetUrlPostAttachmentContents(const std::string& url, const std::string& postData, const std::string& filename, const std::string& fileFieldname) const +{ + std::string buffer; + std::string contents; + std::ifstream fileStream(filename, std::ios::in | std::ios::binary); + + if (fileStream) + { + fileStream.seekg(0, std::ios::end); + contents.resize(fileStream.tellg()); + fileStream.seekg(0, std::ios::beg); + fileStream.read(&contents[0], contents.size()); + fileStream.close(); + } + + CURL* curl = curl_easy_init(); + CURLcode result; + + struct curl_httppost *formpost = nullptr; + struct curl_httppost *lastptr = nullptr; + struct curl_slist *headerlist = nullptr; + static const char headerBuffer[] = "Expect:"; + + curl_global_init(CURL_GLOBAL_ALL); + + curl_formadd(&formpost, &lastptr, + CURLFORM_COPYNAME, "cache-control:", + CURLFORM_COPYCONTENTS, "no-cache", + CURLFORM_END); + + curl_formadd(&formpost, &lastptr, + CURLFORM_COPYNAME, "content-type:", + CURLFORM_COPYCONTENTS, "multipart/form-data", + CURLFORM_END); + + std::vector postTokens; + boost::split(postTokens, postData, boost::is_any_of("&")); + + for (std::vector::iterator it = postTokens.begin(); it != postTokens.end(); ++it) + { + std::vector tokens; + boost::split(tokens, *it, boost::is_any_of("=")); + + curl_formadd(&formpost, &lastptr, + CURLFORM_COPYNAME, tokens[0].c_str(), + CURLFORM_COPYCONTENTS, tokens[1].c_str(), + CURLFORM_END); + } + + curl_formadd(&formpost, &lastptr, + CURLFORM_COPYNAME, fileFieldname.c_str(), + CURLFORM_BUFFER, "data", + CURLFORM_BUFFERPTR, contents.data(), + CURLFORM_BUFFERLENGTH, contents.size(), + CURLFORM_END); + + headerlist = curl_slist_append(headerlist, headerBuffer); + + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer); + + int code = 0; + curl_easy_perform(curl); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code); + curl_easy_cleanup(curl); + curl_formfree(formpost); + curl_slist_free_all(headerlist); + + if (code != 200) + { + std::stringstream error; + error << "Error (" << code << ") encountered while retrieving " << url << "\n"; + error << "Data: " << buffer; + throw std::runtime_error(error.str()); + } + + return buffer; +} + +void HttpClient::GetUrlPostAttachmentSilent(const std::string& url, const std::string& postData, const std::string& filename, const std::string& fileFieldname) const +{ + std::string contents; + std::ifstream fileStream(filename, std::ios::in | std::ios::binary); + + if (fileStream) + { + fileStream.seekg(0, std::ios::end); + contents.resize(fileStream.tellg()); + fileStream.seekg(0, std::ios::beg); + fileStream.read(&contents[0], contents.size()); + fileStream.close(); + } + + CURL* curl = curl_easy_init(); + CURLcode result; + + struct curl_httppost *formpost = nullptr; + struct curl_httppost *lastptr = nullptr; + struct curl_slist *headerlist = nullptr; + static const char headerBuffer[] = "Expect:"; + + curl_global_init(CURL_GLOBAL_ALL); + + curl_formadd(&formpost, &lastptr, + CURLFORM_COPYNAME, "cache-control:", + CURLFORM_COPYCONTENTS, "no-cache", + CURLFORM_END); + + curl_formadd(&formpost, &lastptr, + CURLFORM_COPYNAME, "content-type:", + CURLFORM_COPYCONTENTS, "multipart/form-data", + CURLFORM_END); + + std::vector postTokens; + boost::split(postTokens, postData, boost::is_any_of("&")); + + for (std::vector::iterator it = postTokens.begin(); it != postTokens.end(); ++it) + { + std::vector tokens; + boost::split(tokens, *it, boost::is_any_of("=")); + + curl_formadd(&formpost, &lastptr, + CURLFORM_COPYNAME, tokens[0].c_str(), + CURLFORM_COPYCONTENTS, tokens[1].c_str(), + CURLFORM_END); + } + + curl_formadd(&formpost, &lastptr, + CURLFORM_COPYNAME, fileFieldname.c_str(), + CURLFORM_BUFFER, "data", + CURLFORM_BUFFERPTR, contents.data(), + CURLFORM_BUFFERLENGTH, contents.size(), + CURLFORM_END); + + headerlist = curl_slist_append(headerlist, headerBuffer); + + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost); + + int code = 0; + curl_easy_perform(curl); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code); + curl_easy_cleanup(curl); + curl_formfree(formpost); + curl_slist_free_all(headerlist); + + if (code != 200) + { + std::stringstream error; + error << "Error (" << code << ") encountered while retrieving " << url << "\n"; + throw std::runtime_error(error.str()); + } +} + +std::string HttpClient::GetUrlPostAttachmentContents(const std::string& url, const std::string& cookieFile, const std::string& postData, const std::string& filename, const std::string& fileFieldname) const +{ + std::string buffer; + std::string contents; + std::ifstream fileStream(filename, std::ios::in | std::ios::binary); + + if (fileStream) + { + fileStream.seekg(0, std::ios::end); + contents.resize(fileStream.tellg()); + fileStream.seekg(0, std::ios::beg); + fileStream.read(&contents[0], contents.size()); + fileStream.close(); + } + + CURL* curl = curl_easy_init(); + CURLcode result; + + struct curl_httppost *formpost = nullptr; + struct curl_httppost *lastptr = nullptr; + struct curl_slist *headerlist = nullptr; + static const char headerBuffer[] = "Expect:"; + + curl_global_init(CURL_GLOBAL_ALL); + + curl_formadd(&formpost, &lastptr, + CURLFORM_COPYNAME, "cache-control:", + CURLFORM_COPYCONTENTS, "no-cache", + CURLFORM_END); + + curl_formadd(&formpost, &lastptr, + CURLFORM_COPYNAME, "content-type:", + CURLFORM_COPYCONTENTS, "multipart/form-data", + CURLFORM_END); + + std::vector postTokens; + boost::split(postTokens, postData, boost::is_any_of("&")); + + for (std::vector::iterator it = postTokens.begin(); it != postTokens.end(); ++it) + { + std::vector tokens; + boost::split(tokens, *it, boost::is_any_of("=")); + + curl_formadd(&formpost, &lastptr, + CURLFORM_COPYNAME, tokens[0].c_str(), + CURLFORM_COPYCONTENTS, tokens[1].c_str(), + CURLFORM_END); + } + + curl_formadd(&formpost, &lastptr, + CURLFORM_COPYNAME, fileFieldname.c_str(), + CURLFORM_BUFFER, "data", + CURLFORM_BUFFERPTR, contents.data(), + CURLFORM_BUFFERLENGTH, contents.size(), + CURLFORM_END); + + headerlist = curl_slist_append(headerlist, headerBuffer); + + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost); + curl_easy_setopt(curl, CURLOPT_COOKIEFILE, cookieFile.c_str()); + curl_easy_setopt(curl, CURLOPT_COOKIEJAR, cookieFile.c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &WriteCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer); + + int code = 0; + curl_easy_perform(curl); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code); + curl_easy_cleanup(curl); + curl_formfree(formpost); + curl_slist_free_all(headerlist); + + if (code != 200) + { + std::stringstream error; + error << "Error (" << code << ") encountered while retrieving " << url << "\n"; + error << "Data: " << buffer; + throw std::runtime_error(error.str()); + } + + return buffer; +} + +void HttpClient::GetUrlPostAttachmentSilent(const std::string& url, const std::string& cookieFile, const std::string& postData, const std::string& filename, const std::string& fileFieldname) const +{ + std::string contents; + std::ifstream fileStream(filename, std::ios::in | std::ios::binary); + + if (fileStream) + { + fileStream.seekg(0, std::ios::end); + contents.resize(fileStream.tellg()); + fileStream.seekg(0, std::ios::beg); + fileStream.read(&contents[0], contents.size()); + fileStream.close(); + } + + CURL* curl = curl_easy_init(); + CURLcode result; + + struct curl_httppost *formpost = nullptr; + struct curl_httppost *lastptr = nullptr; + struct curl_slist *headerlist = nullptr; + static const char headerBuffer[] = "Expect:"; + + curl_global_init(CURL_GLOBAL_ALL); + + curl_formadd(&formpost, &lastptr, + CURLFORM_COPYNAME, "cache-control:", + CURLFORM_COPYCONTENTS, "no-cache", + CURLFORM_END); + + curl_formadd(&formpost, &lastptr, + CURLFORM_COPYNAME, "content-type:", + CURLFORM_COPYCONTENTS, "multipart/form-data", + CURLFORM_END); + + std::vector postTokens; + boost::split(postTokens, postData, boost::is_any_of("&")); + + for (std::vector::iterator it = postTokens.begin(); it != postTokens.end(); ++it) + { + std::vector tokens; + boost::split(tokens, *it, boost::is_any_of("=")); + + curl_formadd(&formpost, &lastptr, + CURLFORM_COPYNAME, tokens[0].c_str(), + CURLFORM_COPYCONTENTS, tokens[1].c_str(), + CURLFORM_END); + } + + curl_formadd(&formpost, &lastptr, + CURLFORM_COPYNAME, fileFieldname.c_str(), + CURLFORM_BUFFER, "data", + CURLFORM_BUFFERPTR, contents.data(), + CURLFORM_BUFFERLENGTH, contents.size(), + CURLFORM_END); + + headerlist = curl_slist_append(headerlist, headerBuffer); + + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost); + curl_easy_setopt(curl, CURLOPT_COOKIEFILE, cookieFile.c_str()); + curl_easy_setopt(curl, CURLOPT_COOKIEJAR, cookieFile.c_str()); + + int code = 0; + curl_easy_perform(curl); + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code); + curl_easy_cleanup(curl); + curl_formfree(formpost); + curl_slist_free_all(headerlist); + + if (code != 200) + { + std::stringstream error; + error << "Error (" << code << ") encountered while retrieving " << url << "\n"; + throw std::runtime_error(error.str()); + } +} + +size_t HttpClient::WriteCallback(char* data, size_t size, size_t nmemb, std::string* writerData) +{ + if (writerData == nullptr) + return size * nmemb; + + writerData->append(data, size * nmemb); + return size * nmemb; +} + +} // namespace Util +} // namespace PresenceDetection diff --git a/Util/HttpClient.h b/Util/HttpClient.h new file mode 100644 index 0000000..710943f --- /dev/null +++ b/Util/HttpClient.h @@ -0,0 +1,42 @@ +#ifndef UTIL_HTTPCLIENT_H +#define UTIL_HTTPCLIENT_H + +#include +#include + + +namespace PresenceDetection { +namespace Util { + +class HttpClient +{ +public: + HttpClient(); + ~HttpClient(); + + HttpClient(const HttpClient&) = delete; + + std::string GetUrlContents(const std::string& url) const; + void GetUrlSilent(const std::string& url) const; + std::string GetUrlContents(const std::string& url, const std::string& cookieFile) const; + void GetUrlSilent(const std::string& url, const std::string& cookieFile) const; + std::string GetUrlPostContents(const std::string& url, const std::string& postData, const std::string& contentType) const; + void GetUrlPostSilent(const std::string& url, const std::string& postData, const std::string& contentType) const; + std::string GetUrlPostContents(const std::string& url, const std::string& cookieFile, const std::string& postData, const std::string& contentType) const; + void GetUrlPostSilent(const std::string& url, const std::string& cookieFile, const std::string& postData, const std::string& contentType) const; + std::string GetUrlPostAttachmentContents(const std::string& url, const std::string& postData, const std::string& filename, const std::string& fileFieldname) const; + void GetUrlPostAttachmentSilent(const std::string& url, const std::string& postData, const std::string& filename, const std::string& fileFieldname) const; + std::string GetUrlPostAttachmentContents(const std::string& url, const std::string& cookieFile, const std::string& postData, const std::string& filename, const std::string& fileFieldname) const; + void GetUrlPostAttachmentSilent(const std::string& url, const std::string& cookieFile, const std::string& postData, const std::string& filename, const std::string& fileFieldname) const; + +private: + static size_t WriteCallback(char* data, size_t size, size_t nmemb, std::string* writerData); + +private: + std::vector m_userAgents; +}; + +} // namespace Util +} // namespace PresenceDetection + +#endif // UTIL_HTTPCLIENT_H diff --git a/Util/HttpClientHelpers.h b/Util/HttpClientHelpers.h new file mode 100644 index 0000000..d7c809b --- /dev/null +++ b/Util/HttpClientHelpers.h @@ -0,0 +1,45 @@ +#ifndef UTIL_HTTPCLIENTHELPERS_H +#define UTIL_HTTPCLIENTHELPERS_H + +#include +#include +#include + + +namespace PresenceDetection { +namespace Util { + +static std::vector > g_mutexes; + +static void LockCallback(int mode, int type, const char* /*file*/, int /*line*/) +{ + if (mode & CRYPTO_LOCK) + g_mutexes[type]->lock(); + else + g_mutexes[type]->unlock(); +} + +static unsigned long ThreadId(void) +{ + return (unsigned long)pthread_self(); +} + +static void InitializeLocks(void) +{ + g_mutexes.resize(CRYPTO_num_locks()); + for (int i = 0; i < CRYPTO_num_locks(); ++i) + g_mutexes[i] = boost::shared_ptr(new boost::mutex()); + + CRYPTO_set_id_callback(ThreadId); + CRYPTO_set_locking_callback(LockCallback); +} + +static void FreeLocks(void) +{ + CRYPTO_set_locking_callback(NULL); +} + +} // namespace Util +} // namespace PresenceDetection + +#endif // UTIL_HTTPCLIENTHELPERS_H diff --git a/Util/Makefile b/Util/Makefile new file mode 120000 index 0000000..d0b0e8e --- /dev/null +++ b/Util/Makefile @@ -0,0 +1 @@ +../Makefile \ No newline at end of file diff --git a/Util/Makefile.conf b/Util/Makefile.conf new file mode 120000 index 0000000..6d666f5 --- /dev/null +++ b/Util/Makefile.conf @@ -0,0 +1 @@ +../Makefile.conf \ No newline at end of file diff --git a/Util/Timer.cpp b/Util/Timer.cpp new file mode 100644 index 0000000..e3e39b8 --- /dev/null +++ b/Util/Timer.cpp @@ -0,0 +1,51 @@ +#include +#include "Timer.h" + + +namespace PresenceDetection { +namespace Util { + +Timer::Timer() : + m_run(false) +{ +} + +Timer::~Timer() +{ + Stop(); +} + +void Timer::Start(int interval, std::function function) +{ + if (m_run.load(std::memory_order_acquire)) + Stop(); + + m_run.store(true, std::memory_order_release); + m_thread = std::thread([this, interval, function]() + { + while (m_run.load(std::memory_order_acquire)) + { + function(); + std::this_thread::sleep_for(std::chrono::milliseconds(interval)); + } + }); +} + +void Timer::Stop() +{ + m_run.store(false, std::memory_order_release); +} + +bool Timer::IsRunning() const noexcept +{ + return (m_run.load(std::memory_order_acquire) && m_thread.joinable()); +} + +void Timer::Wait() +{ + if (m_thread.joinable()) + m_thread.join(); +} + +} // namespace Util +} // namespace PresenceDetection diff --git a/Util/Timer.h b/Util/Timer.h new file mode 100644 index 0000000..8f010d7 --- /dev/null +++ b/Util/Timer.h @@ -0,0 +1,31 @@ +#ifndef UTIL_TIMER_H +#define UTIL_TIMER_H + +#include +#include +#include + + +namespace PresenceDetection { +namespace Util { + +class Timer +{ +public: + Timer(); + ~Timer(); + + void Start(int interval, std::function func); + void Stop(); + bool IsRunning() const noexcept; + void Wait(); + +private: + std::atomic m_run; + std::thread m_thread; +}; + +} // namespace Util +} // namespace PresenceDetection + +#endif // UTIL_TIMER_H