Add password to MQTT fileupload

This commit is contained in:
Theo Arends 2021-05-13 12:42:44 +02:00
parent 9742a4e249
commit 0d37a677ae
3 changed files with 105 additions and 51 deletions

View File

@ -33,6 +33,7 @@ struct FMQTT {
uint32_t file_size = 0; // MQTT total file size uint32_t file_size = 0; // MQTT total file size
uint32_t file_type = 0; // MQTT File type (See UploadTypes) uint32_t file_type = 0; // MQTT File type (See UploadTypes)
uint8_t* file_buffer = nullptr; // MQTT file buffer uint8_t* file_buffer = nullptr; // MQTT file buffer
const char* file_password = nullptr; // MQTT password
MD5Builder md5; // MQTT md5 MD5Builder md5; // MQTT md5
String file_md5; // MQTT received file md5 (32 chars) String file_md5; // MQTT received file md5 (32 chars)
uint16_t topic_size; // MQTT topic length with terminating <null> uint16_t topic_size; // MQTT topic length with terminating <null>
@ -62,11 +63,63 @@ uint32_t FileUploadChunckSize(void) {
- Payload ({"Id":116,"Data":"<base64 encoded FileUploadChunckSize>"}<null>) - Payload ({"Id":116,"Data":"<base64 encoded FileUploadChunckSize>"}<null>)
*/ */
const uint32_t PubSubClientHeaderSize = 5; // MQTT_MAX_HEADER_SIZE const uint32_t PubSubClientHeaderSize = 5; // MQTT_MAX_HEADER_SIZE
// return (((MQTT_MAX_PACKET_SIZE - PubSubClientHeaderSize - FMqtt.topic_size - FileTransferHeaderSize) / 4) * 3) -2;
return MQTT_MAX_PACKET_SIZE - PubSubClientHeaderSize - FMqtt.topic_size - FileTransferHeaderSize; return MQTT_MAX_PACKET_SIZE - PubSubClientHeaderSize - FMqtt.topic_size - FileTransferHeaderSize;
} }
uint32_t MqttFileUploadValidate(uint32_t rcv_id) {
if ((0 == FMqtt.file_id) && (rcv_id > 0) && (FMqtt.file_size > 0) && (FMqtt.file_type > 0)) {
FMqtt.file_buffer = nullptr; // Init upload buffer
if (!FMqtt.file_password || (strcmp(FMqtt.file_password, SettingsText(SET_MQTT_PWD)) != 0)) {
return 1; // Invalid password
}
if (UPL_SETTINGS != FMqtt.file_type) { // Check enough flash space for intermediate upload
uint32_t head_room = (FlashWriteMaxSector() - FlashWriteStartSector()) * SPI_FLASH_SEC_SIZE;
uint32_t rounded_size = (FMqtt.file_size + SPI_FLASH_SEC_SIZE -1) & (~(SPI_FLASH_SEC_SIZE - 1));
if (rounded_size > head_room) {
return 2; // Not enough space
}
}
if (UPL_TASMOTA == FMqtt.file_type) {
if (Update.begin(FMqtt.file_size)) {
FMqtt.file_buffer = &FMqtt.file_id; // Dummy buffer
SettingsSave(1); // Free flash for OTA update
}
}
else if (UPL_SETTINGS == FMqtt.file_type) {
if (SettingsConfigBackup()) {
FMqtt.file_buffer = settings_buffer;
}
}
else {
return 3; // Invalid file type
}
FMqtt.file_id = rcv_id;
FMqtt.file_pos = 0;
FMqtt.md5 = MD5Builder();
FMqtt.md5.begin();
ResponseCmndChar(PSTR(D_JSON_STARTED));
MqttPublishPrefixTopic_P(STAT, XdrvMailbox.command); // Enforce stat/wemos10/FILEUPLOAD
}
else if ((FMqtt.file_id > 0) && (FMqtt.file_id != rcv_id)) {
// Error receiving data
if (UPL_TASMOTA == FMqtt.file_type) {
Update.end(true);
}
else if (UPL_SETTINGS == FMqtt.file_type) {
SettingsBufferFree();
}
return 4; // Upload aborted
}
return 0; // No error
}
void CmndFileUpload(void) { void CmndFileUpload(void) {
/* /*
Upload (binary) max 700 bytes chunks of data base64 encoded with MD5 hash over base64 decoded data Upload (binary) max 700 bytes chunks of data base64 encoded with MD5 hash over base64 decoded data
@ -76,6 +129,8 @@ void CmndFileUpload(void) {
FileUpload {"Id":116,"Data":" ... "} FileUpload {"Id":116,"Data":" ... "}
FileUpload {"Id":116,"Md5":"496fcbb433bbca89833063174d2c5747"} FileUpload {"Id":116,"Md5":"496fcbb433bbca89833063174d2c5747"}
*/ */
if (XdrvMailbox.grpflg) { return; }
const char* base64_data = nullptr; const char* base64_data = nullptr;
uint32_t rcv_id = 0; uint32_t rcv_id = 0;
char* dataBuf = (char*)XdrvMailbox.data; char* dataBuf = (char*)XdrvMailbox.data;
@ -101,52 +156,23 @@ void CmndFileUpload(void) {
if (val) { FMqtt.file_md5 = val.getStr(); } if (val) { FMqtt.file_md5 = val.getStr(); }
val = root[PSTR("DATA")]; val = root[PSTR("DATA")];
if (val) { base64_data = val.getStr(); } if (val) { base64_data = val.getStr(); }
val = root[PSTR("PASS")];
if (val) { FMqtt.file_password = val.getStr(); }
} }
} }
} else { } else {
rcv_id = FMqtt.file_id; rcv_id = FMqtt.file_id;
} }
if ((0 == FMqtt.file_id) && (rcv_id > 0) && (FMqtt.file_size > 0) && (FMqtt.file_type > 0)) { uint32_t error = MqttFileUploadValidate(rcv_id);
// Init upload buffer if (error) {
FMqtt.file_buffer = nullptr; FMqtt.file_buffer = nullptr;
if (UPL_TASMOTA == FMqtt.file_type) { TasmotaGlobal.masterlog_level = LOG_LEVEL_NONE; // Enable logging
if (Update.begin(FMqtt.file_size)) {
FMqtt.file_buffer = &FMqtt.file_id; // Dummy buffer
}
}
else if (UPL_SETTINGS == FMqtt.file_type) {
if (SettingsConfigBackup()) {
FMqtt.file_buffer = settings_buffer;
}
}
if (!FMqtt.file_buffer) { char error_txt[TOPSZ];
ResponseCmndChar(PSTR(D_JSON_INVALID_FILE_TYPE)); snprintf_P(error_txt, sizeof(error_txt), PSTR(D_JSON_ERROR " %d"), error);
} else { ResponseCmndChar(error_txt);
FMqtt.file_id = rcv_id;
FMqtt.file_pos = 0;
FMqtt.md5 = MD5Builder();
FMqtt.md5.begin();
ResponseCmndChar(PSTR(D_JSON_STARTED));
MqttPublishPrefixTopic_P(STAT, XdrvMailbox.command); // Enforce stat/wemos10/FILEUPLOAD
}
}
else if ((FMqtt.file_id > 0) && (FMqtt.file_id != rcv_id)) {
// Error receiving data
if (UPL_TASMOTA == FMqtt.file_type) {
Update.end(true);
}
else if (UPL_SETTINGS == FMqtt.file_type) {
SettingsBufferFree();
}
FMqtt.file_buffer = nullptr;
ResponseCmndChar(PSTR(D_JSON_ABORTED));
} }
if (FMqtt.file_buffer) { if (FMqtt.file_buffer) {
@ -182,7 +208,7 @@ void CmndFileUpload(void) {
} }
if ((FMqtt.file_pos > rcvd_bytes) && ((FMqtt.file_pos % 102400) <= rcvd_bytes)) { if ((FMqtt.file_pos > rcvd_bytes) && ((FMqtt.file_pos % 102400) <= rcvd_bytes)) {
TasmotaGlobal.masterlog_level = LOG_LEVEL_NONE; // Enable logging TasmotaGlobal.masterlog_level = LOG_LEVEL_NONE; // Enable logging
AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_UPLOAD "Progress %d kB"), FMqtt.file_pos / 1024); AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_UPLOAD "Progress %d kB"), FMqtt.file_pos / 1024);
TasmotaGlobal.masterlog_level = LOG_LEVEL_DEBUG_MORE; // Hide upload data logging TasmotaGlobal.masterlog_level = LOG_LEVEL_DEBUG_MORE; // Hide upload data logging
} }
@ -230,7 +256,8 @@ void CmndFileUpload(void) {
FMqtt.file_id = 0; FMqtt.file_id = 0;
FMqtt.file_size = 0; FMqtt.file_size = 0;
FMqtt.file_type = 0; FMqtt.file_type = 0;
FMqtt.file_md5 = (const char*) nullptr; // Force deallocation of the String internal memory FMqtt.file_md5 = (const char*) nullptr; // Force deallocation of the String internal memory
FMqtt.file_password = nullptr;
} }
MqttPublishPrefixTopic_P(STAT, XdrvMailbox.command); // Enforce stat/wemos10/FILEUPLOAD MqttPublishPrefixTopic_P(STAT, XdrvMailbox.command); // Enforce stat/wemos10/FILEUPLOAD
ResponseClear(); ResponseClear();
@ -244,6 +271,7 @@ void CmndFileDownload(void) {
FileDownload 2 - Start download of settings file FileDownload 2 - Start download of settings file
FileDownload - Continue downloading data until reception of MD5 hash FileDownload - Continue downloading data until reception of MD5 hash
*/ */
if (XdrvMailbox.grpflg) { return; }
if (FMqtt.file_id && FMqtt.file_buffer) { if (FMqtt.file_id && FMqtt.file_buffer) {
bool finished = false; bool finished = false;

View File

@ -42,6 +42,7 @@ import json
broker = "domus1" # MQTT broker ip address or name broker = "domus1" # MQTT broker ip address or name
broker_port = 1883 # MQTT broker port broker_port = 1883 # MQTT broker port
mypassword = "" # Tasmota MQTT password
mytopic = "demo" # Tasmota MQTT topic mytopic = "demo" # Tasmota MQTT topic
#myfile = "../../build_output/firmware/tasmota32.bin" # Tasmota esp32 firmware file name #myfile = "../../build_output/firmware/tasmota32.bin" # Tasmota esp32 firmware file name
myfile = "../../build_output/firmware/tasmota.bin.gz" # Tasmota esp8266 firmware file name myfile = "../../build_output/firmware/tasmota.bin.gz" # Tasmota esp8266 firmware file name
@ -55,6 +56,7 @@ mysubscribe = "stat/"+mytopic+"/FILEUPLOAD" # Case sensitive
Ack_flag = False Ack_flag = False
use_base64 = False
file_id = 114 # Even id between 2 and 254 file_id = 114 # Even id between 2 and 254
file_chunk_size = 700 # Default Tasmota MQTT max message size file_chunk_size = 700 # Default Tasmota MQTT max message size
@ -69,6 +71,11 @@ def on_message(client, userdata, msg):
# print("Received message =",str(msg.payload.decode("utf-8"))) # print("Received message =",str(msg.payload.decode("utf-8")))
root = json.loads(msg.payload.decode("utf-8")) root = json.loads(msg.payload.decode("utf-8"))
if "FileUpload" in root: rcv_code = root["FileUpload"]
if "Error" in rcv_code:
print("Error: "+rcv_code)
return
if "Command" in root: rcv_code = root["Command"] if "Command" in root: rcv_code = root["Command"]
if rcv_code == "Error": if rcv_code == "Error":
print("Error: Command error") print("Error: Command error")
@ -106,7 +113,7 @@ fo.seek(0, 2) # os.SEEK_END
file_size = fo.tell() file_size = fo.tell()
fo.seek(0, 0) # os.SEEK_SET fo.seek(0, 0) # os.SEEK_SET
client.publish(mypublish, "{\"File\":\""+myfile+"\",\"Id\":"+str("%3d"%file_id)+",\"Type\":"+str(myfiletype)+",\"Size\":"+str(file_size)+"}") client.publish(mypublish, "{\"Pass\":\""+mypassword+"\",\"File\":\""+myfile+"\",\"Id\":"+str("%3d"%file_id)+",\"Type\":"+str(myfiletype)+",\"Size\":"+str(file_size)+"}")
Ack_flag = True Ack_flag = True
out_hash_md5 = hashlib.md5() out_hash_md5 = hashlib.md5()
@ -120,10 +127,13 @@ while Run_flag:
chunk = fo.read(file_chunk_size) chunk = fo.read(file_chunk_size)
if chunk: if chunk:
out_hash_md5.update(chunk) # Update hash out_hash_md5.update(chunk) # Update hash
base64_encoded_data = base64.b64encode(chunk) if use_base64:
base64_data = base64_encoded_data.decode('utf-8') base64_encoded_data = base64.b64encode(chunk)
# Message length used by Tasmota (FileTransferHeaderSize) base64_data = base64_encoded_data.decode('utf-8')
client.publish(mypublish, "{\"Id\":"+str("%3d"%file_id)+",\"Data\":\""+base64_data+"\"}") # Message length used by Tasmota (FileTransferHeaderSize)
client.publish(mypublish, "{\"Id\":"+str("%3d"%file_id)+",\"Data\":\""+base64_data+"\"}")
else:
client.publish(mypublish+"201", chunk)
Ack_flag = True Ack_flag = True
else: else:

View File

@ -42,6 +42,7 @@ import json
broker = "domus1" # MQTT broker ip address or name broker = "domus1" # MQTT broker ip address or name
broker_port = 1883 # MQTT broker port broker_port = 1883 # MQTT broker port
mypassword = "" # Tasmota MQTT password
mytopic = "demo" # Tasmota MQTT topic mytopic = "demo" # Tasmota MQTT topic
myfile = "Config_demo_9.4.0.3.dmp" # Tasmota Settings file name myfile = "Config_demo_9.4.0.3.dmp" # Tasmota Settings file name
myfiletype = 2 # Tasmota Settings file type myfiletype = 2 # Tasmota Settings file type
@ -54,6 +55,7 @@ mysubscribe = "stat/"+mytopic+"/FILEUPLOAD" # Case sensitive
Ack_flag = False Ack_flag = False
use_base64 = True
file_id = 116 # Even id between 2 and 254 file_id = 116 # Even id between 2 and 254
file_chunk_size = 700 # Default Tasmota MQTT max message size file_chunk_size = 700 # Default Tasmota MQTT max message size
@ -62,11 +64,22 @@ def on_message(client, userdata, msg):
global Ack_flag global Ack_flag
global file_chunk_size global file_chunk_size
rcv_code = ""
rcv_id = 0 rcv_id = 0
# print("Received message =",str(msg.payload.decode("utf-8"))) # print("Received message =",str(msg.payload.decode("utf-8")))
root = json.loads(msg.payload.decode("utf-8")) root = json.loads(msg.payload.decode("utf-8"))
if "FileUpload" in root: rcv_code = root["FileUpload"]
if "Error" in rcv_code:
print("Error: "+rcv_code)
return
if "Command" in root: rcv_code = root["Command"]
if rcv_code == "Error":
print("Error: Command error")
return
if "Id" in root: rcv_id = root["Id"] if "Id" in root: rcv_id = root["Id"]
if rcv_id == file_id: if rcv_id == file_id:
if "MaxSize" in root: file_chunk_size = root["MaxSize"] if "MaxSize" in root: file_chunk_size = root["MaxSize"]
@ -99,7 +112,7 @@ fo.seek(0, 2) # os.SEEK_END
file_size = fo.tell() file_size = fo.tell()
fo.seek(0, 0) # os.SEEK_SET fo.seek(0, 0) # os.SEEK_SET
client.publish(mypublish, "{\"File\":\""+myfile+"\",\"Id\":"+str("%3d"%file_id)+",\"Type\":"+str(myfiletype)+",\"Size\":"+str(file_size)+"}") client.publish(mypublish, "{\"Pass\":\""+mypassword+"\",\"File\":\""+myfile+"\",\"Id\":"+str("%3d"%file_id)+",\"Type\":"+str(myfiletype)+",\"Size\":"+str(file_size)+"}")
Ack_flag = True Ack_flag = True
out_hash_md5 = hashlib.md5() out_hash_md5 = hashlib.md5()
@ -113,10 +126,13 @@ while Run_flag:
chunk = fo.read(file_chunk_size) chunk = fo.read(file_chunk_size)
if chunk: if chunk:
out_hash_md5.update(chunk) # Update hash out_hash_md5.update(chunk) # Update hash
base64_encoded_data = base64.b64encode(chunk) if use_base64:
base64_data = base64_encoded_data.decode('utf-8') base64_encoded_data = base64.b64encode(chunk)
# Message length used by Tasmota (FileTransferHeaderSize) base64_data = base64_encoded_data.decode('utf-8')
client.publish(mypublish, "{\"Id\":"+str("%3d"%file_id)+",\"Data\":\""+base64_data+"\"}") # Message length used by Tasmota (FileTransferHeaderSize)
client.publish(mypublish, "{\"Id\":"+str("%3d"%file_id)+",\"Data\":\""+base64_data+"\"}")
else:
client.publish(mypublish+"201", chunk)
Ack_flag = True Ack_flag = True
else: else: