/** * @file tftp.c * @brief tftp server * @author Sander S. */ #define LOGGER_LEVEL_ALL #include "tftp.h" #ifdef TESTING #include "mocs.h" #endif #define VIRT_INDEX_TXT 0 #define VIRT_IMAGE_BMP 1 #define VIRT_IMAGE_GIF 2 #define VIRT_TEXT_TXT 3 #define MAX_VIRT_FILES 4 #define IMAGE_BUFFER_SIZE 81920 static const char* TAG = "tftp_server"; static tftp_custom_file_t virt_file[] = {{.name = "index.txt", .data = NULL, .len = 0, .offset = 0}, {.name = "virtImage.bmp", .data = NULL, .len = 0, .offset = 0}, {.name = "virtImage.gif", .data = NULL, .len = 0, .offset = 0}, {.name = "virtText.txt", .data = NULL, .len = 0, .offset = 0}}; static int str_cat_str(char* dest, size_t dest_size, const char* src) { size_t dest_len = strlen(dest); size_t src_len = strlen(src); if (dest_len + src_len > dest_size) { return -1; } memcpy(dest + dest_len, src, src_len); return 0; } static int str_cat(char* dest, size_t dest_size, char c) { size_t dest_len = strlen(dest); if (dest_len + 1 > dest_size) { return -1; } dest[dest_len] = c; dest[dest_len + 1] = '\0'; return 0; } /** * @brief tftp custom file functions to set the offset and read the data * @param[in,out] handle Custom file handles * @param[in] offset The offset to set * @param[in] whence The origin of the offset */ void tftp_custom_fseek(tftp_custom_file_t* handle, size_t offset, int whence) { switch (whence) { case SEEK_SET: handle->offset = offset; break; case SEEK_CUR: handle->offset += offset; break; case SEEK_END: break; } if (handle->offset > handle->len) { handle->offset = handle->len; } } /** * @brief tftp custom file functions to read the data * auto rolling over the offset * if the bytes to read is bigger than the remaining bytes * it will read the remaining bytes and set the bytes to 0 * @param[out] buf The buffer to write the data to * @param[in] bytes The number of bytes to read * @param[in,out] handle Custom file handles */ size_t tftp_custom_fread(void* buf, size_t bytes, tftp_custom_file_t* handle) { if (handle->offset + bytes > handle->len) { bytes = handle->len - handle->offset; } memcpy(buf, handle->data + handle->offset, bytes); handle->offset += bytes; ((char*)buf)[bytes] = '\0'; if (handle->offset > handle->len) { bytes = 0; } return bytes; } /** * @brief tftp custom file functions to write the data * auto rolling over the offset * * @param buf The buffer to write the data to * @param bytes The number of bytes to write * @param handle The handle to the file to write to * @return The number of bytes written */ size_t tftp_custom_fwrite(const void* buf, size_t bytes, tftp_custom_file_t* handle) { if (handle->offset + bytes > handle->len) { bytes = handle->len - handle->offset; } memcpy(handle->data + handle->offset, buf, bytes); handle->offset += bytes; if (handle->offset > handle->len) { bytes = 0; } return bytes; } /** * @brief tftp helper functions */ /** * @brief This function is called when a file is opened * It should return a handle to the file or NULL if the file does not exist * The handle contains a ptr to or the actual file data or a virtual file * * @param fname The name of the file to open * @param mode Mode string from TFTP RFC * @param write Flag indicating read (0) or write (!= 0) access * @return void* File handle supplied to other functions */ void* tftp_open(const char* fname, const char* mode, uint8_t write) { LOG_INFO(TAG, "Opening %s", fname); UNUSED(mode); if (strcmp(fname, virt_file[VIRT_INDEX_TXT].name) == 0 && write == TFTP_READ) { tftp_custom_fseek(&virt_file[VIRT_INDEX_TXT], 0, SEEK_SET); return &virt_file[0]; } else if (strcmp(fname, virt_file[VIRT_IMAGE_BMP].name) == 0 && write != TFTP_READ) { return &virt_file[VIRT_IMAGE_BMP]; } else if (strcmp(fname, virt_file[VIRT_IMAGE_GIF].name) == 0 && write != TFTP_READ) { return &virt_file[VIRT_IMAGE_GIF]; } else if (strcmp(fname, virt_file[VIRT_TEXT_TXT].name) == 0 && write != TFTP_READ) { return &virt_file[VIRT_TEXT_TXT]; } return fopen(fname, write ? "wb" : "rb"); } /** * @brief This function is called when a file is closed * * @param handle The handle to the file to close */ void tftp_close(void* handle) { LOG_INFO(TAG, "closing file"); if (handle == NULL) { LOG_CRIT(TAG, "handle is null"); return; } if (handle == &virt_file[VIRT_IMAGE_BMP]) { lcd_clear_images(); lcd_clear_text(); lcd_draw_bmp_img((uint8_t*)virt_file[VIRT_IMAGE_BMP].data, 0, 0); } if (handle == &virt_file[VIRT_IMAGE_GIF]) { lcd_clear_images(); lcd_clear_text(); lcd_draw_gif((uint8_t*)virt_file[VIRT_IMAGE_GIF].data, virt_file[VIRT_IMAGE_GIF].offset, 0, 0); } if (handle == &virt_file[VIRT_TEXT_TXT]) { lcd_clear_images(); lcd_clear_text(); lcd_display_text((const char*)virt_file[VIRT_TEXT_TXT].data, 0, 0, LCD_COLOR_WHITE, LCD_TRANSPARENT, LCD_FONT16); } if (handle == &virt_file[VIRT_INDEX_TXT] || handle == &virt_file[VIRT_IMAGE_BMP] || handle == &virt_file[VIRT_IMAGE_GIF] || handle == &virt_file[VIRT_TEXT_TXT]) { ((tftp_custom_file_t*)handle)->offset = 0; return; } fclose((FILE*)handle); } /** * @brief This function is called when a file is read * The virtual files are filtered out first * then the file is trying to get read from the llfs * * @param handle File handle returned by open() * @param buf Target buffer to copy read data to * @param bytes Number of bytes to copy to buf * @return int >= 0: Success; < 0: Error */ int tftp_read(void* handle, void* buf, int bytes) { int ret = 0; LOG_INFO(TAG, "reading file"); if (handle == NULL) { LOG_CRIT(TAG, "handle is null"); return -1; } FILE* file = (FILE*)handle; if ((tftp_custom_file_t*)file == &virt_file[VIRT_INDEX_TXT]) { ret = (int)tftp_custom_fread(buf, (size_t)bytes, (tftp_custom_file_t*)file); return ret; } else if ((tftp_custom_file_t*)file == &virt_file[VIRT_IMAGE_BMP]) { LOG_CRIT(TAG, "Exception: Trying to read a write only file"); return -1; } else if ((tftp_custom_file_t*)file == &virt_file[VIRT_IMAGE_GIF]) { LOG_CRIT(TAG, "Exception: Trying to read a write only file"); return -1; } else if ((tftp_custom_file_t*)file == &virt_file[VIRT_TEXT_TXT]) { LOG_CRIT(TAG, "Exception: Trying to read a write only file"); return -1; } ret = (int)fread(buf, sizeof(uint8_t), (size_t)bytes, file); if (ret <= 0) { return -1; } return ret; } /** * @brief This function is called when a file is written * * @param handle File handle returned by open() * @param p PBUF adjusted such that payload pointer points to the beginning of write data. * In other words, TFTP headers are stripped off. * @return int >= 0: Success; < 0: Error */ int tftp_write(void* handle, struct pbuf* p) { LOG_INFO(TAG, "Writing file"); tftp_custom_file_t* file = (tftp_custom_file_t*)handle; if (file == &virt_file[VIRT_IMAGE_BMP] || file == &virt_file[VIRT_IMAGE_GIF] || file == &virt_file[VIRT_TEXT_TXT]) { return (int)tftp_custom_fwrite(p->payload, (size_t)(p->len), file); } return -1; } /** * @brief This function creates the file list for index.txt */ void init_index(void) { size_t len = 0; // Add len of the virt files to the size for (int i = 0; i < MAX_VIRT_FILES; i++) { len += strlen(virt_file[i].name) + 1; } void* mem = NULL; // Pointer for internal use by the llfs library llfs_file_t* file; while ((file = llfs_next_file(&mem, NULL)) != NULL) { len += strlen(file->name) + 1; } len++; // +1 for the \0 virt_file[VIRT_INDEX_TXT].data = calloc(len, sizeof(char)); if (virt_file[VIRT_INDEX_TXT].data == NULL) { LOG_FATAL(TAG, "Could not allocate memory for index.txt"); return; } virt_file[VIRT_INDEX_TXT].len = len; for (int i = 0; i < MAX_VIRT_FILES; i++) { str_cat_str(virt_file[VIRT_INDEX_TXT].data, len, virt_file[i].name); str_cat(virt_file[VIRT_INDEX_TXT].data, len, '\n'); } mem = NULL; file = NULL; while ((file = llfs_next_file(&mem, NULL)) != NULL) { str_cat_str(virt_file[VIRT_INDEX_TXT].data, len, file->name); str_cat(virt_file[VIRT_INDEX_TXT].data, len, '\n'); } } struct tftp_context tftpContext_s = {.open = tftp_open, .close = tftp_close, .read = tftp_read, .write = tftp_write}; /** * @brief Initialize tftp server */ void tftp_server_init(void) { LOG_INFO(TAG, "Initializing tftp server"); init_index(); LOG_DEBUG(TAG, "index.txt: %s", virt_file[VIRT_INDEX_TXT].data); // init the virtImage virt_file with 80kb of ram virt_file[VIRT_IMAGE_BMP].data = calloc(IMAGE_BUFFER_SIZE, sizeof(char)); if (virt_file[VIRT_IMAGE_BMP].data == NULL) { LOG_FATAL(TAG, "Could not allocate memory for virtImage.bmp/virtImage.gif"); return; } virt_file[VIRT_IMAGE_BMP].len = IMAGE_BUFFER_SIZE; virt_file[VIRT_IMAGE_GIF].data = virt_file[VIRT_IMAGE_BMP].data; virt_file[VIRT_IMAGE_GIF].len = virt_file[VIRT_IMAGE_BMP].len; virt_file[VIRT_TEXT_TXT].data = virt_file[VIRT_IMAGE_BMP].data; virt_file[VIRT_TEXT_TXT].len = virt_file[VIRT_IMAGE_BMP].len; // Init the tftp server if (tftp_init(&tftpContext_s) != ERR_OK) { LOG_FATAL(TAG, "Could not initialize tftp server"); tftp_server_deinit(); return; } LOG_INFO(TAG, "tftp server initialized successfully"); } void tftp_server_deinit(void) { LOG_INFO(TAG, "Deinitializing tftp server"); tftp_cleanup(); LOG_INFO(TAG, "tftp server deinitialized successfully"); free(virt_file[VIRT_INDEX_TXT].data); virt_file[VIRT_INDEX_TXT].data = NULL; virt_file[VIRT_INDEX_TXT].len = 0; virt_file[VIRT_INDEX_TXT].offset = 0; free(virt_file[VIRT_IMAGE_BMP].data); virt_file[VIRT_IMAGE_BMP].data = NULL; virt_file[VIRT_IMAGE_BMP].len = 0; virt_file[VIRT_IMAGE_GIF].data = NULL; virt_file[VIRT_IMAGE_GIF].len = 0; virt_file[VIRT_TEXT_TXT].data = NULL; virt_file[VIRT_TEXT_TXT].len = 0; }