You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
115 lines
5.7 KiB
115 lines
5.7 KiB
#include "protocol.h" |
|
#include "../../lib/debug_config.h" |
|
#include <string.h> |
|
#include <ctype.h> |
|
|
|
uint64_t mf_get64(const uint8_t* p) { |
|
uint64_t v = 0; |
|
for (unsigned i = 0; i < 8; i++) v |= (uint64_t)p[i] << (8 * i); |
|
return v; |
|
} |
|
|
|
void mf_put64(uint8_t* p, uint64_t value) { |
|
for (unsigned i = 0; i < 8; i++) p[i] = (uint8_t)(value >> (8 * i)); |
|
} |
|
|
|
int mf_zero(const uint8_t* p, size_t n) { |
|
for (size_t i = 0; i < n; i++) if (p[i]) return 0; |
|
return 1; |
|
} |
|
|
|
/* Строгий UTF-8: без overlong, суррогатов и кодовых точек за Unicode. */ |
|
static int mf_utf8(const unsigned char* p) { |
|
while (*p) { |
|
unsigned c = *p++; |
|
if (c < 0x80) continue; |
|
unsigned count, value, minimum; |
|
if (c >= 0xc2 && c <= 0xdf) { count = 1; value = c & 31; minimum = 0x80; } |
|
else if (c >= 0xe0 && c <= 0xef) { count = 2; value = c & 15; minimum = 0x800; } |
|
else if (c >= 0xf0 && c <= 0xf4) { count = 3; value = c & 7; minimum = 0x10000; } |
|
else return 0; |
|
for (unsigned i = 0; i < count; i++) { |
|
if ((*p & 0xc0) != 0x80) return 0; |
|
value = (value << 6) | (*p++ & 63); |
|
} |
|
if (value < minimum || value > 0x10ffff || (value >= 0xd800 && value <= 0xdfff)) return 0; |
|
} |
|
return 1; |
|
} |
|
|
|
int mf_path_valid(const char* path, int root_allowed) { |
|
if (!path || strnlen(path, MF_PATH_MAX + 1) > MF_PATH_MAX) return 0; |
|
if (!*path) return root_allowed; |
|
if (!mf_utf8((const unsigned char*)path)) return 0; |
|
const char* start = path; |
|
unsigned components = 0; |
|
for (const char* p = path;; p++) { |
|
unsigned char c = (unsigned char)*p; |
|
if (c && c != '/' && (c < 32 || c == 127 || strchr("\\:*?\"<>|", c))) return 0; |
|
if (c && c != '/') continue; |
|
size_t n = (size_t)(p - start); |
|
if (++components > 60) return 0; |
|
if (!n || n > MF_NAME_MAX || start[0] == '.' || start[n - 1] == '.' || start[n - 1] == ' ') return 0; |
|
char base[9]; size_t k = 0; |
|
while (k < n && k < sizeof(base) - 1 && start[k] != '.') { base[k] = (char)toupper((unsigned char)start[k]); k++; } |
|
base[k] = 0; |
|
if (!strcmp(base, "CON") || !strcmp(base, "PRN") || !strcmp(base, "AUX") || !strcmp(base, "NUL") || |
|
(k == 4 && (!memcmp(base, "COM", 3) || !memcmp(base, "LPT", 3)) && base[3] >= '1' && base[3] <= '9')) return 0; |
|
if (!c) return 1; |
|
start = p + 1; |
|
} |
|
} |
|
|
|
int mf_encode(const struct mf_message* m, uint8_t* p, size_t cap) { |
|
size_t a = strnlen(m->path, sizeof(m->path)), b = strnlen(m->target, sizeof(m->target)); |
|
size_t len = MF_WIRE_HEADER + a + b + m->data_len; |
|
if (a > MF_PATH_MAX || b > MF_PATH_MAX || m->data_len > MF_BLOCK_SIZE || cap < len) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_MEDIA, "member_files: encode overflow paths=%zu/%zu data=%u cap=%zu", a, b, m->data_len, cap); |
|
return -1; |
|
} |
|
memset(p, 0, MF_WIRE_HEADER); |
|
p[0] = 2; p[1] = m->kind; p[2] = m->command; p[3] = (uint8_t)m->status; p[4] = (uint8_t)(m->status >> 8); |
|
mf_put64(p + 5, m->request_id); mf_put64(p + 13, m->owner); mf_put64(p + 21, m->offset); |
|
mf_put64(p + 29, m->size); mf_put64(p + 37, m->cursor); |
|
memcpy(p + 45, m->version, 16); memcpy(p + 61, m->upload, 16); memcpy(p + 77, m->hash, 32); |
|
mf_put64(p + 109, m->mtime); p[117] = m->type; p[118] = m->more; |
|
p[119] = (uint8_t)a; p[120] = (uint8_t)(a >> 8); p[121] = (uint8_t)b; p[122] = (uint8_t)(b >> 8); |
|
p[123] = (uint8_t)m->data_len; p[124] = (uint8_t)(m->data_len >> 8); |
|
memcpy(p + MF_WIRE_HEADER, m->path, a); memcpy(p + MF_WIRE_HEADER + a, m->target, b); |
|
memcpy(p + MF_WIRE_HEADER + a + b, m->data, m->data_len); |
|
return (int)len; |
|
} |
|
|
|
int mf_decode(const uint8_t* p, size_t n, struct mf_message* m) { |
|
if (!p || !m || n < MF_WIRE_HEADER) goto bad; |
|
size_t a = p[119] | ((size_t)p[120] << 8), b = p[121] | ((size_t)p[122] << 8); |
|
size_t d = p[123] | ((size_t)p[124] << 8); |
|
if (p[0] != 2 || (p[1] != MF_REQUEST && p[1] != MF_RESPONSE) || p[2] < MF_LIST || p[2] > MF_STREAM_CANCEL || |
|
a > MF_PATH_MAX || b > MF_PATH_MAX || d > MF_BLOCK_SIZE || n != MF_WIRE_HEADER + a + b + d || |
|
memchr(p + MF_WIRE_HEADER, 0, a + b) || p[118] > 1) goto bad; |
|
memset(m, 0, sizeof(*m)); |
|
m->kind = p[1]; m->command = p[2]; m->status = p[3] | ((uint16_t)p[4] << 8); |
|
m->request_id = mf_get64(p + 5); m->owner = mf_get64(p + 13); m->offset = mf_get64(p + 21); |
|
m->size = mf_get64(p + 29); m->cursor = mf_get64(p + 37); m->mtime = mf_get64(p + 109); |
|
memcpy(m->version, p + 45, 16); memcpy(m->upload, p + 61, 16); memcpy(m->hash, p + 77, 32); |
|
m->type = p[117]; m->more = p[118]; m->data_len = (uint32_t)d; |
|
memcpy(m->path, p + MF_WIRE_HEADER, a); memcpy(m->target, p + MF_WIRE_HEADER + a, b); |
|
memcpy(m->data, p + MF_WIRE_HEADER + a + b, d); |
|
if (!m->request_id || m->status > MF_TIMEOUT || (m->kind == MF_REQUEST && m->status)) goto bad; |
|
return 0; |
|
bad: |
|
DEBUG_WARN(DEBUG_CATEGORY_MEDIA, "member_files: malformed wire packet bytes=%zu", n); |
|
return -1; |
|
} |
|
|
|
const char* mf_status_text(int status) { |
|
static const char* texts[] = { "OK", "Access denied", "Not found", "Invalid path", "Invalid range", "Already exists", |
|
"Busy", "File changed", "Storage full", "I/O error", "Invalid packet", "Hash mismatch", "Cancelled", "Timeout" }; |
|
return status >= 0 && status <= MF_TIMEOUT ? texts[status] : "Unknown error"; |
|
} |
|
|
|
const char* mf_command_text(int command) { |
|
static const char* commands[] = { "SWEEP", "LIST", "STAT", "READ", "BEGIN_WRITE", "WRITE", "COMMIT", "ABORT", |
|
"MKDIR", "RENAME", "REMOVE", "STREAM_WRITE", "STREAM_READ", "DATA", "END", "CANCEL" }; |
|
return command >= 0 && command <= MF_STREAM_CANCEL ? commands[command] : "UNKNOWN"; |
|
}
|
|
|