#include #include #include #include "utils.h" #include "main.h" CMain g_Main; CMain::CMain() : m_FingerLogic(this) { } static void STAGotIP(IpAddress ip, IpAddress mask, IpAddress gateway) { debugf("GOTIP - IP: %s, MASK: %s, GW: %s\n", ip.toString().c_str(), mask.toString().c_str(), gateway.toString().c_str()); } static void STADisconnect(const String& ssid, MacAddress bssid, WifiDisconnectReason reason) { debugf("DISCONNECT - SSID: %s, REASON: %d", ssid.c_str(), WifiEvents.getDisconnectReasonDesc(reason).c_str()); } void CMain::Init(HardwareSerial &serial) { m_Settings.Load(); //m_Settings.Save(); WifiAccessPoint.enable(false); if(m_Settings.m_aSSID[0]) { debugf("Station: %s", m_Settings.m_aSSID); WifiStation.enable(true); WifiStation.config(m_Settings.m_aSSID, m_Settings.m_aPSK); if(m_Settings.m_DHCP && m_Settings.m_aHostname[0]) WifiStation.setHostname(m_Settings.m_aHostname); if(!m_Settings.m_DHCP && !m_Settings.m_Address.isNull()) WifiStation.setIP(m_Settings.m_Address, m_Settings.m_Netmask, m_Settings.m_Gateway); WifiStation.connect(); } else { debugf("Access Point 'admin': %s", m_Settings.m_aPassword); WifiAccessPoint.config("safeweb", m_Settings.m_aPassword, AUTH_WPA2_PSK); WifiAccessPoint.enable(true); } WifiEvents.onStationGotIP(STAGotIP); WifiEvents.onStationDisconnect(STADisconnect); m_FTP.listen(21); m_FTP.addUser(m_Settings.m_aUsername, m_Settings.m_aPassword); m_HttpServer.listen(80); m_HttpServer.setBodyParser("application/json", bodyToStringParser); m_HttpServer.paths.set("/api", HttpPathDelegate(&CMain::HttpOnApi, this)); m_HttpServer.paths.set("/api/dashboard", HttpPathDelegate(&CMain::HttpOnApi, this)); m_HttpServer.paths.set("/api/fingerprint", HttpPathDelegate(&CMain::HttpOnApi, this)); m_HttpServer.paths.set("/api/fingerprint/label", HttpPathDelegate(&CMain::HttpOnApi, this)); m_HttpServer.paths.set("/api/fingerprint/delete", HttpPathDelegate(&CMain::HttpOnApi, this)); m_HttpServer.paths.set("/api/fingerprint/enroll", HttpPathDelegate(&CMain::HttpOnApi, this)); m_HttpServer.paths.set("/api/unlock", HttpPathDelegate(&CMain::HttpOnApi, this)); m_HttpServer.paths.setDefault(HttpPathDelegate(&CMain::HttpOnFile, this)); m_FingerPrint.Init(serial, 0xFFFFFFFF, 0x00000000); m_FingerLogic.Init(&m_FingerPrint); } bool CMain::HttpAuthorized(HttpRequest &request, HttpResponse &response) { String auth = request.getHeader("Authorization"); if(auth.startsWith("Basic ")) { int headerLength = auth.length() - 6; if(headerLength <= 64) { auth = base64_decode(auth.c_str() + 6, headerLength); if(auth) { int sep = auth.indexOf(':'); if(sep != -1) { String username = auth.substring(0, sep); String password = auth.substring(sep + 1); if(username == m_Settings.m_aUsername && password == m_Settings.m_aPassword) return true; } } } } response.code = HTTP_STATUS_UNAUTHORIZED; response.setHeader("WWW-Authenticate", "Basic realm=\"safeweb\""); response.setHeader("401 Wrong credentials", "Authentication required"); response.setHeader("Connection", "close"); return false; } void CMain::HttpOnApi(HttpRequest &request, HttpResponse &response) { if(!HttpAuthorized(request, response)) return; String path = request.uri.Path; if(path.length() < 6) { response.code = HTTP_STATUS_NOT_FOUND; return; } String endpoint = path.substring(5); DynamicJsonDocument jsonReq(1024); deserializeJson(jsonReq, request.getBody()); DynamicJsonDocument jsonResp(1024); response.setAllowCrossDomainOrigin("*"); response.setHeader("Access-Control-Allow-Headers", "Content-Type"); if(request.method == HTTP_OPTIONS) { response.code = HTTP_STATUS_OK; return; } HttpStatus status = HandleApi(request.method, endpoint, jsonReq, jsonResp); if(status != HTTP_STATUS_OK) { response.code = status; return; } String respString; serializeJson(jsonResp, respString); response.setContentType(MIME_JSON); response.sendString(respString); } HttpStatus CMain::HandleApi(HttpMethod method, String endpoint, JsonDocument &req, JsonDocument &resp) { String test; serializeJsonPretty(req, test); debugf("request: %s\n", test.c_str()); if(endpoint == "dashboard") { if(method != HTTP_GET) return HTTP_STATUS_METHOD_NOT_ALLOWED; JsonObject lock = resp.createNestedObject("lock"); lock["locked"] = true; JsonObject door = resp.createNestedObject("door"); door["closed"] = true; JsonObject battery = resp.createNestedObject("battery"); debugf("adc: %d\n", system_adc_read()); battery["voltage"] = (system_adc_read() / 1024.f) / 0.23; battery["color"] = "success"; if(battery["voltage"] < 3.6f) battery["color"] = "warning"; JsonObject network = resp.createNestedObject("network"); if(WifiStation.isEnabled()) { network["type"] = "Station"; network["ssid"] = WifiStation.getSSID(); network["channel"] = WifiStation.getChannel(); network["dhcp"] = WifiStation.isEnabledDHCP(); network["rssi"] = WifiStation.getRssi(); network["signal"] = Rssi2Quality(WifiStation.getRssi()); network["address"] = WifiStation.getIP().toString(); } else { network["type"] = "Access Point"; network["ssid"] = WifiAccessPoint.getSSID(); network["channel"] = WifiStation.getChannel(); network["dhcp"] = true; network["address"] = WifiAccessPoint.getIP().toString(); } } else if(endpoint == "unlock") { if(method != HTTP_POST) return HTTP_STATUS_METHOD_NOT_ALLOWED; LockUnlock(); resp["success"] = "true"; } else if(endpoint == "fingerprint") { if(method != HTTP_GET) return HTTP_STATUS_METHOD_NOT_ALLOWED; JsonArray fingerprints = resp.createNestedArray("fingerprints"); uint16_t tmp = Settings().m_FingerPrints.count(); for(uint16_t i = 0; i < tmp; i++) { JsonObject obj = fingerprints.createNestedObject(); const CSettings::CFingerPrint &finger = Settings().m_FingerPrints.valueAt(i); obj["num"] = finger.m_FingerNum; obj["label"] = String(finger.m_aLabel); char aHexDigest[SHA256_SIZE*2+1]; bytes2hex(finger.m_aDigest, sizeof(finger.m_aDigest), aHexDigest, sizeof(aHexDigest)); obj["digest"] = String(aHexDigest); } } else if(endpoint == "fingerprint/label") { if(method != HTTP_POST) return HTTP_STATUS_METHOD_NOT_ALLOWED; if(!req.containsKey("index") || !req.containsKey("label")) return HTTP_STATUS_BAD_REQUEST; int index = req["index"].as() - 1; String label = req["label"]; if(index < 0 || index >= Settings().m_FingerPrints.count()) return HTTP_STATUS_BAD_REQUEST; CSettings::CFingerPrint &finger = Settings().m_FingerPrints.valueAt(index); strncpy(finger.m_aLabel, label.c_str(), sizeof(finger.m_aLabel)); Settings().Save(); resp["success"] = true; } else if(endpoint == "fingerprint/delete") { if(method != HTTP_POST) return HTTP_STATUS_METHOD_NOT_ALLOWED; if(!req.containsKey("index")) return HTTP_STATUS_BAD_REQUEST; int index = req["index"].as() - 1; if(index < 0 || index >= Settings().m_FingerPrints.count()) return HTTP_STATUS_BAD_REQUEST; const CSettings::CFingerPrint &finger = Settings().m_FingerPrints.valueAt(index); FingerPrint().DeleteTemplate(finger.m_FingerNum, 1); Settings().m_FingerPrints.removeAt(index); Settings().Save(); resp["success"] = true; } else if(endpoint == "fingerprint/enroll") { if(method != HTTP_POST) return HTTP_STATUS_METHOD_NOT_ALLOWED; if(req.containsKey("cancel")) { if(req["cancel"].as() && m_Enrolling) { resp["success"] = FingerLogic().EnrollFinger(false); m_Enrolling = false; m_Enrolled = false; } } else { if(!req.containsKey("enrolling")) return HTTP_STATUS_BAD_REQUEST; bool enrolling = req["enrolling"]; if(enrolling && !m_Enrolling) { if(m_Enrolled) { resp["done"] = true; resp["message"] = m_EnrollMessage; m_Enrolled = false; } else { resp["error"] = m_EnrollMessage; } } else if(enrolling && m_Enrolling) { resp["done"] = false; resp["message"] = m_EnrollMessage; } else // if(!enrolling) { if(!req.containsKey("label")) return HTTP_STATUS_BAD_REQUEST; if(!m_Enrolling) { FingerLogic().EnrollFinger(); m_Enrolling = true; m_Enrolled = false; m_EnrollLabel = String(req["label"].as()); resp["done"] = false; m_EnrollMessage = "Started enroll process. Please place your finger on the sensor."; resp["message"] = m_EnrollMessage; } else { resp["done"] = true; m_Enrolled = false; resp["message"] = m_EnrollMessage; } } } } else { return HTTP_STATUS_NOT_FOUND; } return HTTP_STATUS_OK; } void CMain::HttpOnFile(HttpRequest &request, HttpResponse &response) { if(!HttpAuthorized(request, response)) return; String file = request.uri.Path; if(file == "/") file = "index.html"; if(file[0] == '/') file = file.substring(1); if(file[0] == '.') { response.code = HTTP_STATUS_FORBIDDEN; return; } response.setCache(86400, true); // It's important to use cache for better performance. response.sendFile(file); } void CMain::OnFingerInterrupt(bool finger) { m_FingerLogic.OnFingerInterrupt(finger); } void CMain::FingerEnable(bool enable) { const int pin = FINGER_ENABLE_PIN; digitalWrite(pin, !enable); } void CMain::LockSendBytes(uint8_t *pBytes, uint8_t len) { const int pin = SAFELOCK_DATA_PIN; // Init digitalWrite(pin, 0); delayMicroseconds(10 * 1000); digitalWrite(pin, 1); delayMicroseconds(10 * 1000); // Send data, calculate checksum and send checksum uint8_t chk = 0x00; for(uint8_t i = 0; i <= len; i++) { uint8_t byte; if(i == len) { byte = chk; } else { byte = pBytes[i]; chk ^= byte; } for(int8_t j = 0; j < 8; j++) { digitalWrite(pin, 0); delayMicroseconds(100); digitalWrite(pin, 1); uint8_t val = byte & (1 << j); if(val) delayMicroseconds(300); else delayMicroseconds(100); } digitalWrite(pin, 0); delayMicroseconds(100); digitalWrite(pin, 1); delayMicroseconds(8); } } void CMain::LockSendCode(uint8_t pass[8]) { uint8_t packet[9] = {0x51}; memcpy(&packet[1], pass, 8); LockSendBytes(packet, sizeof(packet)); } void CMain::LockUnlock() { LockSendCode(Settings().m_aLockCode); } void CMain::EnrollMessage(const char *msg, bool error) { m_EnrollMessage = msg; if(error) m_Enrolling = false; } void CMain::OnFingerVerified(uint16_t fingerNum, uint8_t digest[SHA256_SIZE]) { int fingerIndex = Settings().m_FingerPrints.indexOf(fingerNum); if(fingerIndex == -1) { debugf("OnFingerVerified: fingerIndex == -1"); return; } const CSettings::CFingerPrint &finger = Settings().m_FingerPrints.valueAt(fingerIndex); if(memcmp(digest, finger.m_aDigest, SHA256_SIZE) != 0) { debugf("OnFingerVerified: SHA256 mismatch"); return; } LockUnlock(); debugf("OnFingerVerified: OK!!!"); } void CMain::OnFingerEnrolled(uint16_t fingerNum, uint8_t digest[SHA256_SIZE]) { CSettings::CFingerPrint finger; finger.m_FingerNum = fingerNum; strncpy(finger.m_aLabel, m_EnrollLabel.c_str(), sizeof(finger.m_aLabel)); memcpy(finger.m_aDigest, digest, SHA256_SIZE); char aHexDigest[SHA256_SIZE*2+1]; bytes2hex(finger.m_aDigest, sizeof(finger.m_aDigest), aHexDigest, sizeof(aHexDigest)); debugf("OnFingerEnrolled: \"%s\"", aHexDigest); Serial1.printf("(sz: %d) Finger hexdigest: ", sizeof(finger.m_aDigest)); for(uint8_t i = 0; i < sizeof(finger.m_aDigest); i++) Serial1.printf("%x", finger.m_aDigest[i]); Serial1.printf("\n"); Settings().m_FingerPrints[fingerNum] = finger; Settings().Save(); m_Enrolled = true; m_Enrolling = false; m_EnrolledFinger = finger; char aBuf[512]; m_snprintf(aBuf, sizeof(aBuf), "Successfully enrolled new finger \"%s\".", finger.m_aLabel); EnrollMessage(aBuf); debugf("OnFingerEnrolled: OK!!!"); }