Configuration interface

This commit is contained in:
2025-12-03 20:11:12 +01:00
parent 3222cc349d
commit 74991a9107
8 changed files with 2864 additions and 64 deletions

2
README
View File

@@ -128,6 +128,8 @@ Bauteilliste
------------
1x ESP32-S3 Nano (Waveshare)
berrybase.de
eckstein-shop.de
5x Taster schwarz
2x Taster gelb
1x M12 Einbaubuchse

View File

@@ -1,6 +1,6 @@
[platformio]
default_envs=
esp32-s3-nano
esp32-s3-nano
[env]
platform = espressif32
@@ -9,18 +9,19 @@ lib_deps =
Preferences
Wifi
Wire
ArduinoJson @ 6.18.5
ESP32Async/AsyncTCP@3.4.9
ESP32Async/ESPAsyncWebServer@3.9.1
ttlappalainen/NMEA2000-library@4.24
robtillaart/SHT31@^0.5.2
# adafruit/Adafruit NeoPixel
# only these files will be emedded into firmware
board_build.embed_files =
lib/generated/index.html.gz
lib/generated/index.js.gz
lib/generated/index.css.gz
lib/generated/config.json.gz
lib/generated/index.html.gz
lib/generated/index.js.gz
lib/generated/sha256.js.gz
lib/generated/index.css.gz
lib/generated/config.json.gz
extra_scripts =
pre:extra_pre.py

View File

@@ -1,5 +1,6 @@
#include <Arduino.h>
#include <Preferences.h>
#include <ArduinoJson.h>
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
@@ -12,6 +13,40 @@
#include "Nmea2kTwai.h"
#include <map>
#include "mbedtls/md.h" // for SHA256
#include <esp32/clk.h> // for cpu frequency
String get_sha256(String payload) {
byte shaResult[32];
mbedtls_md_context_t ctx;
mbedtls_md_type_t md_type = MBEDTLS_MD_SHA256;
mbedtls_md_init(&ctx);
mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(md_type), 0);
mbedtls_md_starts(&ctx);
mbedtls_md_update(&ctx, (const unsigned char *) payload.c_str(), payload.length());
mbedtls_md_finish(&ctx, shaResult);
mbedtls_md_free(&ctx);
// convert to hex string
char buffer[sizeof(shaResult)*2 + 1];
const char hexmap[] = "0123456789abcdef";
for (int i = 0; i < sizeof(shaResult); i++) {
buffer[i*2] = hexmap[(shaResult[i] >> 4) & 0x0F];
buffer[i*2+1] = hexmap[shaResult[i] & 0x0F];
}
buffer[sizeof(buffer) - 1] = '\0';
String hash = String(buffer);
Serial.print("SHA256 payload: ");
Serial.print(payload);
Serial.println();
Serial.print("SHA256-Hash: ");
Serial.print(hash);
Serial.println();
return hash;
}
// Logging
static const char* TAG = "main.cpp";
@@ -51,10 +86,13 @@ void send_embedded_file(String name, AsyncWebServerRequest *request)
}
}
uint64_t chipid = ESP.getEfuseMac();
const char* wifi_ssid = "OBPKP61";
const char* wifi_pass = "keypad61";
AsyncWebServer server(80);
unsigned long lastSensor = 0;
unsigned long lastPrint = 0;
unsigned long counter = 0;
@@ -70,6 +108,8 @@ uint buzzerpower = 50; // TBD make use of this
SHT31 sht(SHT31_ADDRESS);
bool sht_available = false;
float temp = 0.0;
float hum = 0.0;
int nodeid; // NMEA2000 id on bus
Nmea2kTwai &NMEA2000=*(new Nmea2kTwai(CAN_TX, CAN_RX, CAN_RECOVERY_PERIOD));
@@ -80,45 +120,26 @@ String processor(const String& var) {
return "";
}
/* Low level wifi setup (alternative)
static void wifi_event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
// printf("Event nr: %ld!\n", event_id);
String uptime_with_unit() {
int64_t uptime = esp_timer_get_time() / 1000000;
String uptime_unit;
if (uptime < 120) {
uptime_unit = " seconds";
} else {
if (uptime < 2 * 3600) {
uptime /= 60;
uptime_unit = " minutes";
} else if (uptime < 2 * 3600 * 24) {
uptime /= 3600;
uptime_unit = " hours";
} else {
uptime /= 86400;
uptime_unit = " days";
}
}
return String(uptime) + " " + uptime_unit;
}
void wifi_init_softap()
{
esp_netif_init();
esp_event_loop_create_default();
esp_netif_create_default_wifi_ap();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL, NULL);
esp_wifi_init(&cfg);
wifi_config_t wifi_config = {
.ap = {
.ssid = wifi_ssid,
.ssid_len = strlen(wifi_ssid),
.channel = WIFI_CHANNEL,
.password = wifi_pass,
.max_connection = WIFI_MAX_STA,
.authmode = WIFI_AUTH_WPA2_PSK,
.pmf_cfg = {
.required = true,
},
},
};
esp_wifi_set_mode(WIFI_MODE_AP);
esp_wifi_set_config(WIFI_IF_AP, &wifi_config);
esp_wifi_start();
ESP_LOGI(TAG, "wifi_init_softap finished. SSID:%s password:%s channel:%d",
ESP_WIFI_SSID, ESP_WIFI_PASS, ESP_WIFI_CHANNEL);
}
*/
void setup() {
Serial.begin(115200);
@@ -167,16 +188,65 @@ void setup() {
send_embedded_file(it->first, request);
});
}
// WIP: API
/*
server.on("/api/status", HTTP_GET, [](AsyncWebServerRequest *request){
// API fast hack
server.on("/api/capabilities", HTTP_GET, [](AsyncWebServerRequest *request){
StaticJsonDocument<100> doc;
doc["temp"] = 22.3;
doc["ip"] = WiFi.localIP().toString();
doc["apPwChange"] = "true";
String out;
serializeJson(doc, out);
request->send(200, "application/json", out);
}); */
});
server.on("/api/checkpass", HTTP_GET, [](AsyncWebServerRequest *request){
StaticJsonDocument<100> doc;
doc["status"] = "FAILED";
String out;
serializeJson(doc, out);
request->send(200, "application/json", out);
});
server.on("/api/config", HTTP_GET, [](AsyncWebServerRequest *request){
StaticJsonDocument<100> doc;
doc["systemName"] = "Keypad1";
doc["version"] = "0.0";
doc["fwtype"] = "unknown";
doc["salt"] = "secret";
String out;
serializeJson(doc, out);
request->send(200, "application/json", out);
});
server.on("/api/resetconfig", HTTP_GET, [](AsyncWebServerRequest *request){
StaticJsonDocument<100> doc;
doc["status"] = "FAILED";
String out;
serializeJson(doc, out);
request->send(200, "application/json", out);
});
server.on("/api/status", HTTP_GET, [](AsyncWebServerRequest *request){
StaticJsonDocument<200> doc;
int cpu_freq = esp_clk_cpu_freq() / 1000000;
doc["cpuspeed"] = String(cpu_freq) + "MHz";
char ssid[13];
snprintf(ssid, 13, "%04X%08X", (uint16_t)(chipid >> 32), (uint32_t)chipid);
doc["chipid"] = String(ssid);
doc["uptime"] = uptime_with_unit();
doc["heap"]=(long)xPortGetFreeHeapSize();
doc["temp"] = String(temp, 1);
doc["hum"] = String(hum, 1);
doc["status"] = "OK";
String out;
serializeJson(doc, out);
request->send(200, "application/json", out);
});
server.on("/api/setconfig", HTTP_POST, [](AsyncWebServerRequest *request){
StaticJsonDocument<100> doc;
doc["status"] = "FAILED";
String out;
serializeJson(doc, out);
request->send(200, "application/json", out);
});
// TODO POST vom Client entgegennehmen
server.begin();
@@ -299,6 +369,9 @@ void setup() {
Serial.print(stat, HEX);
Serial.println();
// Additional tests
String passhash = get_sha256("secretTEST");
}
void loop() {
@@ -390,10 +463,9 @@ void loop() {
}
if (button > 0) {
sht.read();
Serial.print(sht.getTemperature(), 1);
Serial.print(temp, 1);
Serial.print("\t");
Serial.println(sht.getHumidity(), 1);
Serial.println(hum, 1);
// Debounce delay to avoid multiple triggers
delay(200);
}
@@ -402,6 +474,13 @@ void loop() {
// NMEA2000.loop();
// NMEA2000.ParseMessages();
if (millis() - lastSensor >= 5000) {
lastSensor = millis();
sht.read();
temp = sht.getTemperature();
hum = sht.getHumidity();
}
// development heartbeat
if (millis() - lastPrint >= 1000) {
lastPrint = millis();

View File

@@ -1,20 +1,129 @@
[
{
"name": "systemName",
"label": "system name",
"label": "System name",
"type": "string",
"default": "OBPkeypad61",
"check": "checkSystemName",
"description": "system name, used for the access point and for services",
"category": "system"
"description": "System name, used also for the access point SSID.",
"category": "System"
},
{
"name": "logLevel",
"label": "Log level",
"type": "list",
"default": "0",
"list": [
{"l":"Off (0)","v":0},
{"l":"Error (1)","v":1},
{"l":"Warning (2)","v":2},
{"l":"Info (3)","v":3},
{"l":"Debug (4)","v":4},
{"l":"Verbose (5)","v":5}
],
"description": "Log level at the USB port.\nHigher level means more output.",
"category": "System"
},
{
"name": "adminPassword",
"label": "Admin Password",
"type": "password",
"default": "esp32admin",
"check": "checkAdminPass",
"description": "Set the password for config modifications",
"category": "System"
},
{
"name": "useAdminPass",
"label": "Use Admin-Pass",
"type": "boolean",
"default": "true",
"description": "A password for config modifications is required.",
"category": "System"
},
{
"name": "apPassword",
"label": "Wifi password",
"type": "password",
"default": "keypad61",
"check": "checkApPass",
"description": "set the password for the Wifi access point",
"category": "system",
"description": "Set the password for the Wifi access point.",
"category": "Wifi",
"capabilities":{"apPwChange":["true"]}
},
{
"name": "apIp",
"label": "AP IP-Address",
"type": "string",
"default":"192.168.15.1",
"check": "checkApIp",
"description": "The IP address for the wifi access point.\nClients will get addresses within the same subnet.",
"category":"Wifi"
},
{
"name": "apMask",
"label": "AP Net-Mask",
"type": "string",
"default": "255.255.255.0",
"check": "checkNetMask",
"description": "The network mask for the access point.",
"category": "Wifi"
},
{
"name": "stopApTime",
"label": "AP time off",
"type": "number",
"default": "0",
"min": 0,
"max": 60,
"check": "checkMinMax",
"description": "Stop the access point after that many minutes if not used.\n1 to 60 minutes.\n\n'0' means that the access point is permanently enabled.",
"category": "Wifi"
},
{
"name": "cpuSpeed",
"label": "CPU Speed [MHz]",
"type": "list",
"default": "160",
"list": [
"80",
"160",
"240"
],
"description": "CPU speed in MHz [80|160|240].\nSlower speed means less power consumption.",
"category": "Hardware"
},
{
"name": "ledBrightness",
"label": "LED brightness",
"type": "number",
"default": 64,
"min": 0,
"max": 255,
"description": "The brightness of the destination leds (0..255).",
"category": "Hardware"
},
{
"name": "ledRgbBrightness",
"label": "RGB-LED brightness",
"type": "number",
"default": 64,
"min": 0,
"max": 255,
"description": "The brightness of the rgb status led (0..255).",
"category": "Hardware"
},
{
"name": "tempFormat",
"label": "Temperature Format",
"type": "list",
"default": "C",
"list": [
"K",
"C",
"F"
],
"description": "Temperature format: Kelvin, Celsius or Fahrenheit [K|C|F].",
"category": "Units"
}
]

View File

@@ -0,0 +1,291 @@
* {
box-sizing: border-box;
}
body {
font-family: Arial, Helvetica, sans-serif;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
.main {
display: flex;
flex-direction: column;
margin: 0.2em;
overflow: hidden;
}
#tabs {
display: flex;
flex-wrap: wrap;
border-bottom: 1px solid grey;
margin-bottom: 0.5em;
}
#tabs .tab {
background-color: lightgray;
padding: 0.5em;
/*border: 1px;
border-color: grey;
border-style: solid; */
border: 1px solid grey;
opacity: 0.6;
}
#tabs .tab.active {
opacity: 1;
}
#tabPages {
overflow: auto;
padding-bottom: 1ex;
border-bottom: 1px solid grey;
}
.configForm {
padding-bottom: 0.5em;
}
.configForm .buttons {
margin-bottom: 0.5em;
}
.configForm .content>div:nth-child(even) {
background-color: rgb(211 211 211 / 43%);
}
#statusPage .even {
background-color: rgb(211 211 211 / 43%);
}
#statusPageContent {
margin-bottom: 0.5em;
}
.counter-row .value{
text-align: right;
width: 6em;
}
.icon-row .label{
width: 8.7em;
}
.category .title .label {
opacity: 1;
margin-left: 1em;
}
.changed input{
color: green;
}
.changed select{
color: green;
}
.category.changed{
color: green;
}
span.label {
width: 10em;
display: inline-block;
opacity: 0.6;
}
.configForm .value {
width: 21em;
display: flex;
flex-direction: row;
margin-bottom: 0.2em;
}
span#connected {
display: inline-block;
background-color: red;
width: 1em;
height: 1em;
border-radius: 50%;
}
span#connected.ok{
background-color: #13ac13;
}
.row {
padding: 0.5em;
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-items: center;
}
input,select {
border: 1px solid #808080a1;
font-size: 0.9em;
padding: 0.2em;
}
.filter {
display: inline-block;
}
button.infoButton {
margin-left: 1em;
vertical-align: bottom;
}
.category .title {
padding-left: 0.5em;
background-color: lightgray;
padding-top: 0.3em;
padding-bottom: 0.5em;
border-bottom: 1px solid darkgray;
display: flex;
align-items: center;
}
.value button {
margin-left: 0.5em;
}
.hidden{
display: none !important;
}
.dash.invalid{
display: none;
}
button.addunassigned {
margin-left: 1em;
}
.msgDetails .value {
width: 5em;
text-align: right;
}
.msgDetails .label {
width: 5em;
}
.overlayContainer {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: #80808070;
display: flex;
overflow-y: auto;
}
.overlay {
margin: auto;
background-color: white;
padding: 0.5em;
max-width: 100%;
box-sizing: border-box;
}
.overlayContent {
padding: 0.5em;
}
div#overlayContent.text{
white-space: pre-line;
}
.overlayButtons {
border-top: 1px solid grey;
padding-top: 0.5em;
display: flex;
flex-direction: row;
justify-content: end;
}
.buttons button {
margin: 0.2ex 0;
padding: 0.5em;
}
.overlayButtons button {
padding: 0.5em;
margin-left: 0.3em;
}
button#reset {
padding: 0.5em;
}
h1 {
margin-bottom: 0;
}
.icon-eye {
background-image: url("data:image/svg+xml;utf-8,<svg width='24' height='24' xmlns='http://www.w3.org/2000/svg' xmlns:svg='http://www.w3.org/2000/svg'> <g class='layer'> <title>Layer 1</title> <path d='m0,0l24,0l0,24l-24,0l0,-24z' fill='none' id='svg_1'/> <path d='m12,6c3.79,0 7.17,2.13 8.82,5.5c-1.65,3.37 -5.03,5.5 -8.82,5.5s-7.17,-2.13 -8.82,-5.5c1.65,-3.37 5.03,-5.5 8.82,-5.5m0,-2c-5,0 -9.27,3.11 -11,7.5c1.73,4.39 6,7.5 11,7.5s9.27,-3.11 11,-7.5c-1.73,-4.39 -6,-7.5 -11,-7.5zm0,5c1.38,0 2.5,1.12 2.5,2.5s-1.12,2.5 -2.5,2.5s-2.5,-1.12 -2.5,-2.5s1.12,-2.5 2.5,-2.5m0,-2c-2.48,0 -4.5,2.02 -4.5,4.5s2.02,4.5 4.5,4.5s4.5,-2.02 4.5,-4.5s-2.02,-4.5 -4.5,-4.5z' id='svg_2'/> </g> </svg>");
margin-left: 0.5em;
opacity: 0.3;
}
.icon{
height: 1.5em;
width: 1.5em;
display: inline-block;
}
.icon-more{
background-image: url("data:image/svg+xml;utf-8,<svg width='24' height='24' xmlns='http://www.w3.org/2000/svg' xmlns:svg='http://www.w3.org/2000/svg'> <g class='layer'> <title>Layer 1</title> <path d='m24,24l-24,0l0,-24l24,0l0,24z' fill='none' id='svg_1' opacity='0.87'/> <path d='m16.59,8.59l-4.59,4.58l-4.59,-4.58l-1.41,1.41l6,6l6,-6l-1.41,-1.41z' id='svg_2'/> </g> </svg>");
}
.icon-less{
background-image: url("data:image/svg+xml;utf-8,<svg width='24' height='24' xmlns='http://www.w3.org/2000/svg' xmlns:svg='http://www.w3.org/2000/svg'> <g class='layer'> <title>Layer 1</title> <path d='m0,0l24,0l0,24l-24,0l0,-24z' fill='none' id='svg_1'/> <path d='m12,8l-6,6l1.41,1.41l4.59,-4.58l4.59,4.58l1.41,-1.41l-6,-6z' id='svg_2'/> </g> </svg>");
}
.icon-eye.active{
opacity: 1;
}
.dash {
width: 6.5em;
height: 4em;
display: flex;
flex-direction: column;
border: 1px solid grey;
overflow: hidden;
margin: 0;
box-sizing: border-box;
font-size: 1.2em;
justify-content: space-between;
}
div#dashboardPage {
display: flex;
flex-wrap: wrap;
justify-content: center;
}
.dashTitle {
font-size: 0.8em;
background-color: lightgray;
}
.dashValue{
font-size: 1.6em;
text-align: center;
}
.dashValue.formatLatitude {
font-size: 1.1em;
}
.dashValue.formatLongitude {
font-size: 1.1em;
}
.dashValue.formatDate {
font-size: 1.2em;
}
.footer {
display: flex;
flex-direction: row;
padding: 0.1em;
background-color: lightgray;
font-size: 0.7em;
}
.footer .unit{
}
.footer .source{
flex: 1;
}
#adminPassInput {
margin-bottom: 1em;
}
input#uploadFile {
margin-top: 0.5em;
margin-bottom: 0.5em;
}
div#uploadProgress {
width: 100%;
max-width: 20em;
height: 1em;
margin-left: 1em;
/* margin-right: auto; */
border: 1px solid grey;
margin-top: 0.5em;
margin-bottom: 0.5em;
}
#uploadDone{
background-color: blue;
width: 0px;
height: 100%;
}
.error{
color: red;
}
input.error{
background-color: rgba(255, 0, 0, 0.329);
}

View File

@@ -1,17 +1,166 @@
<!DOCTYPE HTML>
<html>
<head>
<title>OBPkeypad 6/1</title>
<!DOCTYPE html>
<html><head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>OBPkeyboard 6/1</title>
<link rel="icon" href="data:,">
<script>
if (!window.isSecureContext) {
const s = document.createElement('script');
s.src = 'sha256.js';
document.head.appendChild(s);
}
</script>
<script type="text/javascript" src="index.js"></script>
<link rel="stylesheet" href="index.css">
</head>
<body>
<div class="main">
<h1>OBPkeypad 6/1</h1>
<p>Work in progress</p>
<p><a href="https://computerclub.hoogi.de/obpkeypad/">Furter Information</a></p>
<h1 id="headline">OBPkb</h1>
<div class="row">
<span class="label" id="conn_label">disconnected</span>
<span class="value" id="connected"></span>
</div>
<div id="tabs">
<div class="tab active" data-page="statusPage">Status</div>
<div class="tab" data-page="configPage">Config</div>
<div class="tab" data-page="updatePage">Update</div>
<div class="tab" data-url="https://computerclub.hoogi.de/obpkeypad" data-window="help" id="helpButton">Help</div>
</div>
<div id="tabPages">
<div id="statusPage" class="tabPage">
<div id="statusPageContent">
<div class="row">
<span class="label">Firmware</span>
<span class="value" id="version">---</span>
<button class="infoButton" id="converterInfo">?</button>
</div>
<div class="row even">
<span class="label">MCU-ID</span>
<span class="value" id="chipid">---</span>
</div>
<div class="row">
<span class="label">CPU speed</span>
<span class="value" id="cpuspeed">---</span>
</div>
<div class="row even">
<span class="label">Free heap</span>
<span class="value" id="heap">---</span>
</div>
<div class="row">
<span class="label">Uptime</span>
<span class="value" id="uptime">---</span>
</div>
<div class="row even">
<span class="label">NMEA2000 State</span>
[<span class="value" id="n2knode">---</span>]&nbsp;
<span class="value" id="n2kstate">UNKNOWN</span>
</div>
<div class="row">
<span class="label">Dest A</span>
<span class="value" id="destA">---</span>
</div>
<div class="row even">
<span class="label">Dest B</span>
<span class="value" id="destB">---</span>
</div>
<div class="row">
<span class="label">Dest C</span>
<span class="value" id="destC">---</span>
</div>
<div class="row even">
<span class="label">Sensor: Temperature</span>
<span class="value" id="temp">---</span>°C
</div>
<div class="row">
<span class="label">Sensor: Humidity</span>
<span class="value" id="hum">---</span>%
</div>
</div>
<button id="reset">Reset</button>
</div>
<div class="configForm tabPage hidden" id="configPage">
<div class="buttons">
<button id="resetForm">ReloadConfig</button>
<button id="forgetPass">ForgetPass</button>
<button id="changeConfig">Save&amp;Restart</button>
<button id="exportConfig">Export</button>
<button id="importConfig">Import</button>
<button id="factoryReset">FactoryReset</button>
</div>
<div class="configFormRows">
</div>
</div>
<div class="tabPage hidden" id="updatePage">
<div class="row">
<span class="label">Firmware type</span>
<span class="value status-fwtype">---</span>
</div>
<div class="row">
<span class="label">Chip type</span>
<span class="value status-chipid">---</span>
</div>
<div class="row">
<span class="label">Current version</span>
<span class="value status-version">---</span>
</div>
<div class="row">
<span class="label">New Firmware</span>
<input type="file" name="file1" id="uploadFile">
</div>
<div class="row">
<span class="label"></span>
<span id="imageProperties" class="value"></span>
</div>
<div id="uploadProgress">
<div id="uploadDone"></div>
</div>
<div class="buttons">
<button id="uploadBin">Upload</button>
</div>
</div>
</div>
</div>
<div class="overlayContainer hidden" id="overlayContainer">
<div id="overlay" class="overlay">
<div id="overlayContent" class="overlayContent">
AHA
</div>
<div class="overlayButtons">
</div>
</div>
</div>
<div class="overlayContainer hidden" id="adminPassOverlay">
<div id="adminPassOverlay" class="overlay">
<div id="adminPassOverlayContent" class="overlayContent">
<h2>Admin Password</h2>
<div id="adminPassHint"></div>
<div id="adminPassError" ></div>
<input id="adminPassInput" type="password">
<div class="row">
<span class="label">remember me</span>
<select id="adminPassKeep">
<option value="true">on</option>
<option value="false" selected>off</option>
</select>
</div>
<div class="overlayButtons">
<button id="adminPassCancel">Cancel</button>
<button id="adminPassOk">OK</button>
</div>
</div>
</div>
</div>
</body>
</html>

File diff suppressed because it is too large Load Diff

526
web/sha256.js Normal file
View File

@@ -0,0 +1,526 @@
/**
* [js-sha256]{@link https://github.com/emn178/js-sha256}
*
* @version 0.11.1
* @author Chen, Yi-Cyuan [emn178@gmail.com]
* @copyright Chen, Yi-Cyuan 2014-2025
* @license MIT
*/
/*jslint bitwise: true */
(function () {
'use strict';
var ERROR = 'input is invalid type';
var WINDOW = typeof window === 'object';
var root = WINDOW ? window : {};
if (root.JS_SHA256_NO_WINDOW) {
WINDOW = false;
}
var WEB_WORKER = !WINDOW && typeof self === 'object';
var NODE_JS = !root.JS_SHA256_NO_NODE_JS && typeof process === 'object' && process.versions && process.versions.node && process.type != 'renderer';
if (NODE_JS) {
root = global;
} else if (WEB_WORKER) {
root = self;
}
var COMMON_JS = !root.JS_SHA256_NO_COMMON_JS && typeof module === 'object' && module.exports;
var AMD = typeof define === 'function' && define.amd;
var ARRAY_BUFFER = !root.JS_SHA256_NO_ARRAY_BUFFER && typeof ArrayBuffer !== 'undefined';
var HEX_CHARS = '0123456789abcdef'.split('');
var EXTRA = [-2147483648, 8388608, 32768, 128];
var SHIFT = [24, 16, 8, 0];
var K = [
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
];
var OUTPUT_TYPES = ['hex', 'array', 'digest', 'arrayBuffer'];
var blocks = [];
if (root.JS_SHA256_NO_NODE_JS || !Array.isArray) {
Array.isArray = function (obj) {
return Object.prototype.toString.call(obj) === '[object Array]';
};
}
if (ARRAY_BUFFER && (root.JS_SHA256_NO_ARRAY_BUFFER_IS_VIEW || !ArrayBuffer.isView)) {
ArrayBuffer.isView = function (obj) {
return typeof obj === 'object' && obj.buffer && obj.buffer.constructor === ArrayBuffer;
};
}
var createOutputMethod = function (outputType, is224) {
return function (message) {
return new Sha256(is224, true).update(message)[outputType]();
};
};
var createMethod = function (is224) {
var method = createOutputMethod('hex', is224);
if (NODE_JS) {
method = nodeWrap(method, is224);
}
method.create = function () {
return new Sha256(is224);
};
method.update = function (message) {
return method.create().update(message);
};
for (var i = 0; i < OUTPUT_TYPES.length; ++i) {
var type = OUTPUT_TYPES[i];
method[type] = createOutputMethod(type, is224);
}
return method;
};
var nodeWrap = function (method, is224) {
var crypto = require('crypto')
var Buffer = require('buffer').Buffer;
var algorithm = is224 ? 'sha224' : 'sha256';
var bufferFrom;
if (Buffer.from && !root.JS_SHA256_NO_BUFFER_FROM) {
bufferFrom = Buffer.from;
} else {
bufferFrom = function (message) {
return new Buffer(message);
};
}
var nodeMethod = function (message) {
if (typeof message === 'string') {
return crypto.createHash(algorithm).update(message, 'utf8').digest('hex');
} else {
if (message === null || message === undefined) {
throw new Error(ERROR);
} else if (message.constructor === ArrayBuffer) {
message = new Uint8Array(message);
}
}
if (Array.isArray(message) || ArrayBuffer.isView(message) ||
message.constructor === Buffer) {
return crypto.createHash(algorithm).update(bufferFrom(message)).digest('hex');
} else {
return method(message);
}
};
return nodeMethod;
};
var createHmacOutputMethod = function (outputType, is224) {
return function (key, message) {
return new HmacSha256(key, is224, true).update(message)[outputType]();
};
};
var createHmacMethod = function (is224) {
var method = createHmacOutputMethod('hex', is224);
method.create = function (key) {
return new HmacSha256(key, is224);
};
method.update = function (key, message) {
return method.create(key).update(message);
};
for (var i = 0; i < OUTPUT_TYPES.length; ++i) {
var type = OUTPUT_TYPES[i];
method[type] = createHmacOutputMethod(type, is224);
}
return method;
};
function Sha256(is224, sharedMemory) {
if (sharedMemory) {
blocks[0] = blocks[16] = blocks[1] = blocks[2] = blocks[3] =
blocks[4] = blocks[5] = blocks[6] = blocks[7] =
blocks[8] = blocks[9] = blocks[10] = blocks[11] =
blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0;
this.blocks = blocks;
} else {
this.blocks = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
}
if (is224) {
this.h0 = 0xc1059ed8;
this.h1 = 0x367cd507;
this.h2 = 0x3070dd17;
this.h3 = 0xf70e5939;
this.h4 = 0xffc00b31;
this.h5 = 0x68581511;
this.h6 = 0x64f98fa7;
this.h7 = 0xbefa4fa4;
} else { // 256
this.h0 = 0x6a09e667;
this.h1 = 0xbb67ae85;
this.h2 = 0x3c6ef372;
this.h3 = 0xa54ff53a;
this.h4 = 0x510e527f;
this.h5 = 0x9b05688c;
this.h6 = 0x1f83d9ab;
this.h7 = 0x5be0cd19;
}
this.block = this.start = this.bytes = this.hBytes = 0;
this.finalized = this.hashed = false;
this.first = true;
this.is224 = is224;
}
Sha256.prototype.update = function (message) {
if (this.finalized) {
return;
}
var notString, type = typeof message;
if (type !== 'string') {
if (type === 'object') {
if (message === null) {
throw new Error(ERROR);
} else if (ARRAY_BUFFER && message.constructor === ArrayBuffer) {
message = new Uint8Array(message);
} else if (!Array.isArray(message)) {
if (!ARRAY_BUFFER || !ArrayBuffer.isView(message)) {
throw new Error(ERROR);
}
}
} else {
throw new Error(ERROR);
}
notString = true;
}
var code, index = 0, i, length = message.length, blocks = this.blocks;
while (index < length) {
if (this.hashed) {
this.hashed = false;
blocks[0] = this.block;
this.block = blocks[16] = blocks[1] = blocks[2] = blocks[3] =
blocks[4] = blocks[5] = blocks[6] = blocks[7] =
blocks[8] = blocks[9] = blocks[10] = blocks[11] =
blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0;
}
if (notString) {
for (i = this.start; index < length && i < 64; ++index) {
blocks[i >>> 2] |= message[index] << SHIFT[i++ & 3];
}
} else {
for (i = this.start; index < length && i < 64; ++index) {
code = message.charCodeAt(index);
if (code < 0x80) {
blocks[i >>> 2] |= code << SHIFT[i++ & 3];
} else if (code < 0x800) {
blocks[i >>> 2] |= (0xc0 | (code >>> 6)) << SHIFT[i++ & 3];
blocks[i >>> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3];
} else if (code < 0xd800 || code >= 0xe000) {
blocks[i >>> 2] |= (0xe0 | (code >>> 12)) << SHIFT[i++ & 3];
blocks[i >>> 2] |= (0x80 | ((code >>> 6) & 0x3f)) << SHIFT[i++ & 3];
blocks[i >>> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3];
} else {
code = 0x10000 + (((code & 0x3ff) << 10) | (message.charCodeAt(++index) & 0x3ff));
blocks[i >>> 2] |= (0xf0 | (code >>> 18)) << SHIFT[i++ & 3];
blocks[i >>> 2] |= (0x80 | ((code >>> 12) & 0x3f)) << SHIFT[i++ & 3];
blocks[i >>> 2] |= (0x80 | ((code >>> 6) & 0x3f)) << SHIFT[i++ & 3];
blocks[i >>> 2] |= (0x80 | (code & 0x3f)) << SHIFT[i++ & 3];
}
}
}
this.lastByteIndex = i;
this.bytes += i - this.start;
if (i >= 64) {
this.block = blocks[16];
this.start = i - 64;
this.hash();
this.hashed = true;
} else {
this.start = i;
}
}
if (this.bytes > 4294967295) {
this.hBytes += this.bytes / 4294967296 << 0;
this.bytes = this.bytes % 4294967296;
}
return this;
};
Sha256.prototype.finalize = function () {
if (this.finalized) {
return;
}
this.finalized = true;
var blocks = this.blocks, i = this.lastByteIndex;
blocks[16] = this.block;
blocks[i >>> 2] |= EXTRA[i & 3];
this.block = blocks[16];
if (i >= 56) {
if (!this.hashed) {
this.hash();
}
blocks[0] = this.block;
blocks[16] = blocks[1] = blocks[2] = blocks[3] =
blocks[4] = blocks[5] = blocks[6] = blocks[7] =
blocks[8] = blocks[9] = blocks[10] = blocks[11] =
blocks[12] = blocks[13] = blocks[14] = blocks[15] = 0;
}
blocks[14] = this.hBytes << 3 | this.bytes >>> 29;
blocks[15] = this.bytes << 3;
this.hash();
};
Sha256.prototype.hash = function () {
var a = this.h0, b = this.h1, c = this.h2, d = this.h3, e = this.h4, f = this.h5, g = this.h6,
h = this.h7, blocks = this.blocks, j, s0, s1, maj, t1, t2, ch, ab, da, cd, bc;
for (j = 16; j < 64; ++j) {
// rightrotate
t1 = blocks[j - 15];
s0 = ((t1 >>> 7) | (t1 << 25)) ^ ((t1 >>> 18) | (t1 << 14)) ^ (t1 >>> 3);
t1 = blocks[j - 2];
s1 = ((t1 >>> 17) | (t1 << 15)) ^ ((t1 >>> 19) | (t1 << 13)) ^ (t1 >>> 10);
blocks[j] = blocks[j - 16] + s0 + blocks[j - 7] + s1 << 0;
}
bc = b & c;
for (j = 0; j < 64; j += 4) {
if (this.first) {
if (this.is224) {
ab = 300032;
t1 = blocks[0] - 1413257819;
h = t1 - 150054599 << 0;
d = t1 + 24177077 << 0;
} else {
ab = 704751109;
t1 = blocks[0] - 210244248;
h = t1 - 1521486534 << 0;
d = t1 + 143694565 << 0;
}
this.first = false;
} else {
s0 = ((a >>> 2) | (a << 30)) ^ ((a >>> 13) | (a << 19)) ^ ((a >>> 22) | (a << 10));
s1 = ((e >>> 6) | (e << 26)) ^ ((e >>> 11) | (e << 21)) ^ ((e >>> 25) | (e << 7));
ab = a & b;
maj = ab ^ (a & c) ^ bc;
ch = (e & f) ^ (~e & g);
t1 = h + s1 + ch + K[j] + blocks[j];
t2 = s0 + maj;
h = d + t1 << 0;
d = t1 + t2 << 0;
}
s0 = ((d >>> 2) | (d << 30)) ^ ((d >>> 13) | (d << 19)) ^ ((d >>> 22) | (d << 10));
s1 = ((h >>> 6) | (h << 26)) ^ ((h >>> 11) | (h << 21)) ^ ((h >>> 25) | (h << 7));
da = d & a;
maj = da ^ (d & b) ^ ab;
ch = (h & e) ^ (~h & f);
t1 = g + s1 + ch + K[j + 1] + blocks[j + 1];
t2 = s0 + maj;
g = c + t1 << 0;
c = t1 + t2 << 0;
s0 = ((c >>> 2) | (c << 30)) ^ ((c >>> 13) | (c << 19)) ^ ((c >>> 22) | (c << 10));
s1 = ((g >>> 6) | (g << 26)) ^ ((g >>> 11) | (g << 21)) ^ ((g >>> 25) | (g << 7));
cd = c & d;
maj = cd ^ (c & a) ^ da;
ch = (g & h) ^ (~g & e);
t1 = f + s1 + ch + K[j + 2] + blocks[j + 2];
t2 = s0 + maj;
f = b + t1 << 0;
b = t1 + t2 << 0;
s0 = ((b >>> 2) | (b << 30)) ^ ((b >>> 13) | (b << 19)) ^ ((b >>> 22) | (b << 10));
s1 = ((f >>> 6) | (f << 26)) ^ ((f >>> 11) | (f << 21)) ^ ((f >>> 25) | (f << 7));
bc = b & c;
maj = bc ^ (b & d) ^ cd;
ch = (f & g) ^ (~f & h);
t1 = e + s1 + ch + K[j + 3] + blocks[j + 3];
t2 = s0 + maj;
e = a + t1 << 0;
a = t1 + t2 << 0;
this.chromeBugWorkAround = true;
}
this.h0 = this.h0 + a << 0;
this.h1 = this.h1 + b << 0;
this.h2 = this.h2 + c << 0;
this.h3 = this.h3 + d << 0;
this.h4 = this.h4 + e << 0;
this.h5 = this.h5 + f << 0;
this.h6 = this.h6 + g << 0;
this.h7 = this.h7 + h << 0;
};
Sha256.prototype.hex = function () {
this.finalize();
var h0 = this.h0, h1 = this.h1, h2 = this.h2, h3 = this.h3, h4 = this.h4, h5 = this.h5,
h6 = this.h6, h7 = this.h7;
var hex = HEX_CHARS[(h0 >>> 28) & 0x0F] + HEX_CHARS[(h0 >>> 24) & 0x0F] +
HEX_CHARS[(h0 >>> 20) & 0x0F] + HEX_CHARS[(h0 >>> 16) & 0x0F] +
HEX_CHARS[(h0 >>> 12) & 0x0F] + HEX_CHARS[(h0 >>> 8) & 0x0F] +
HEX_CHARS[(h0 >>> 4) & 0x0F] + HEX_CHARS[h0 & 0x0F] +
HEX_CHARS[(h1 >>> 28) & 0x0F] + HEX_CHARS[(h1 >>> 24) & 0x0F] +
HEX_CHARS[(h1 >>> 20) & 0x0F] + HEX_CHARS[(h1 >>> 16) & 0x0F] +
HEX_CHARS[(h1 >>> 12) & 0x0F] + HEX_CHARS[(h1 >>> 8) & 0x0F] +
HEX_CHARS[(h1 >>> 4) & 0x0F] + HEX_CHARS[h1 & 0x0F] +
HEX_CHARS[(h2 >>> 28) & 0x0F] + HEX_CHARS[(h2 >>> 24) & 0x0F] +
HEX_CHARS[(h2 >>> 20) & 0x0F] + HEX_CHARS[(h2 >>> 16) & 0x0F] +
HEX_CHARS[(h2 >>> 12) & 0x0F] + HEX_CHARS[(h2 >>> 8) & 0x0F] +
HEX_CHARS[(h2 >>> 4) & 0x0F] + HEX_CHARS[h2 & 0x0F] +
HEX_CHARS[(h3 >>> 28) & 0x0F] + HEX_CHARS[(h3 >>> 24) & 0x0F] +
HEX_CHARS[(h3 >>> 20) & 0x0F] + HEX_CHARS[(h3 >>> 16) & 0x0F] +
HEX_CHARS[(h3 >>> 12) & 0x0F] + HEX_CHARS[(h3 >>> 8) & 0x0F] +
HEX_CHARS[(h3 >>> 4) & 0x0F] + HEX_CHARS[h3 & 0x0F] +
HEX_CHARS[(h4 >>> 28) & 0x0F] + HEX_CHARS[(h4 >>> 24) & 0x0F] +
HEX_CHARS[(h4 >>> 20) & 0x0F] + HEX_CHARS[(h4 >>> 16) & 0x0F] +
HEX_CHARS[(h4 >>> 12) & 0x0F] + HEX_CHARS[(h4 >>> 8) & 0x0F] +
HEX_CHARS[(h4 >>> 4) & 0x0F] + HEX_CHARS[h4 & 0x0F] +
HEX_CHARS[(h5 >>> 28) & 0x0F] + HEX_CHARS[(h5 >>> 24) & 0x0F] +
HEX_CHARS[(h5 >>> 20) & 0x0F] + HEX_CHARS[(h5 >>> 16) & 0x0F] +
HEX_CHARS[(h5 >>> 12) & 0x0F] + HEX_CHARS[(h5 >>> 8) & 0x0F] +
HEX_CHARS[(h5 >>> 4) & 0x0F] + HEX_CHARS[h5 & 0x0F] +
HEX_CHARS[(h6 >>> 28) & 0x0F] + HEX_CHARS[(h6 >>> 24) & 0x0F] +
HEX_CHARS[(h6 >>> 20) & 0x0F] + HEX_CHARS[(h6 >>> 16) & 0x0F] +
HEX_CHARS[(h6 >>> 12) & 0x0F] + HEX_CHARS[(h6 >>> 8) & 0x0F] +
HEX_CHARS[(h6 >>> 4) & 0x0F] + HEX_CHARS[h6 & 0x0F];
if (!this.is224) {
hex += HEX_CHARS[(h7 >>> 28) & 0x0F] + HEX_CHARS[(h7 >>> 24) & 0x0F] +
HEX_CHARS[(h7 >>> 20) & 0x0F] + HEX_CHARS[(h7 >>> 16) & 0x0F] +
HEX_CHARS[(h7 >>> 12) & 0x0F] + HEX_CHARS[(h7 >>> 8) & 0x0F] +
HEX_CHARS[(h7 >>> 4) & 0x0F] + HEX_CHARS[h7 & 0x0F];
}
return hex;
};
Sha256.prototype.toString = Sha256.prototype.hex;
Sha256.prototype.digest = function () {
this.finalize();
var h0 = this.h0, h1 = this.h1, h2 = this.h2, h3 = this.h3, h4 = this.h4, h5 = this.h5,
h6 = this.h6, h7 = this.h7;
var arr = [
(h0 >>> 24) & 0xFF, (h0 >>> 16) & 0xFF, (h0 >>> 8) & 0xFF, h0 & 0xFF,
(h1 >>> 24) & 0xFF, (h1 >>> 16) & 0xFF, (h1 >>> 8) & 0xFF, h1 & 0xFF,
(h2 >>> 24) & 0xFF, (h2 >>> 16) & 0xFF, (h2 >>> 8) & 0xFF, h2 & 0xFF,
(h3 >>> 24) & 0xFF, (h3 >>> 16) & 0xFF, (h3 >>> 8) & 0xFF, h3 & 0xFF,
(h4 >>> 24) & 0xFF, (h4 >>> 16) & 0xFF, (h4 >>> 8) & 0xFF, h4 & 0xFF,
(h5 >>> 24) & 0xFF, (h5 >>> 16) & 0xFF, (h5 >>> 8) & 0xFF, h5 & 0xFF,
(h6 >>> 24) & 0xFF, (h6 >>> 16) & 0xFF, (h6 >>> 8) & 0xFF, h6 & 0xFF
];
if (!this.is224) {
arr.push((h7 >>> 24) & 0xFF, (h7 >>> 16) & 0xFF, (h7 >>> 8) & 0xFF, h7 & 0xFF);
}
return arr;
};
Sha256.prototype.array = Sha256.prototype.digest;
Sha256.prototype.arrayBuffer = function () {
this.finalize();
var buffer = new ArrayBuffer(this.is224 ? 28 : 32);
var dataView = new DataView(buffer);
dataView.setUint32(0, this.h0);
dataView.setUint32(4, this.h1);
dataView.setUint32(8, this.h2);
dataView.setUint32(12, this.h3);
dataView.setUint32(16, this.h4);
dataView.setUint32(20, this.h5);
dataView.setUint32(24, this.h6);
if (!this.is224) {
dataView.setUint32(28, this.h7);
}
return buffer;
};
function HmacSha256(key, is224, sharedMemory) {
var i, type = typeof key;
if (type === 'string') {
var bytes = [], length = key.length, index = 0, code;
for (i = 0; i < length; ++i) {
code = key.charCodeAt(i);
if (code < 0x80) {
bytes[index++] = code;
} else if (code < 0x800) {
bytes[index++] = (0xc0 | (code >>> 6));
bytes[index++] = (0x80 | (code & 0x3f));
} else if (code < 0xd800 || code >= 0xe000) {
bytes[index++] = (0xe0 | (code >>> 12));
bytes[index++] = (0x80 | ((code >>> 6) & 0x3f));
bytes[index++] = (0x80 | (code & 0x3f));
} else {
code = 0x10000 + (((code & 0x3ff) << 10) | (key.charCodeAt(++i) & 0x3ff));
bytes[index++] = (0xf0 | (code >>> 18));
bytes[index++] = (0x80 | ((code >>> 12) & 0x3f));
bytes[index++] = (0x80 | ((code >>> 6) & 0x3f));
bytes[index++] = (0x80 | (code & 0x3f));
}
}
key = bytes;
} else {
if (type === 'object') {
if (key === null) {
throw new Error(ERROR);
} else if (ARRAY_BUFFER && key.constructor === ArrayBuffer) {
key = new Uint8Array(key);
} else if (!Array.isArray(key)) {
if (!ARRAY_BUFFER || !ArrayBuffer.isView(key)) {
throw new Error(ERROR);
}
}
} else {
throw new Error(ERROR);
}
}
if (key.length > 64) {
key = (new Sha256(is224, true)).update(key).array();
}
var oKeyPad = [], iKeyPad = [];
for (i = 0; i < 64; ++i) {
var b = key[i] || 0;
oKeyPad[i] = 0x5c ^ b;
iKeyPad[i] = 0x36 ^ b;
}
Sha256.call(this, is224, sharedMemory);
this.update(iKeyPad);
this.oKeyPad = oKeyPad;
this.inner = true;
this.sharedMemory = sharedMemory;
}
HmacSha256.prototype = new Sha256();
HmacSha256.prototype.finalize = function () {
Sha256.prototype.finalize.call(this);
if (this.inner) {
this.inner = false;
var innerHash = this.array();
Sha256.call(this, this.is224, this.sharedMemory);
this.update(this.oKeyPad);
this.update(innerHash);
Sha256.prototype.finalize.call(this);
}
};
var exports = createMethod();
exports.sha256 = exports;
exports.sha224 = createMethod(true);
exports.sha256.hmac = createHmacMethod();
exports.sha224.hmac = createHmacMethod(true);
if (COMMON_JS) {
module.exports = exports;
} else {
root.sha256 = exports.sha256;
root.sha224 = exports.sha224;
if (AMD) {
define(function () {
return exports;
});
}
}
})();