commit 7ad040c6c13ddb561097c95395c16fbab9118dd6 Author: JDierkse Date: Sat Dec 15 22:46:38 2018 +0100 Initial Commit 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