Beispiel #1
0
/*
 * Returns the SHA1 hash for a specified file path
 *
 * req: TLV_TYPE_FILE_PATH - The file path that is to be stat'd
 */
DWORD request_fs_sha1(Remote *remote, Packet *packet)
{
	Packet *response = packet_create_response(packet);
	char *filePath;
	DWORD result = ERROR_SUCCESS;
	SHA_CTX context;

	FILE *fd;
	size_t ret;
	unsigned char buff[16384];
	unsigned char hash[SHA_DIGEST_LENGTH + 1] = {0};

	filePath = packet_get_tlv_value_string(packet, TLV_TYPE_FILE_PATH);

	result = fs_fopen(filePath, "rb", &fd);
	if (result == ERROR_SUCCESS) {
		SHA1_Init(&context);

		while ((ret = fread(buff, 1, sizeof(buff), fd)) > 0 ) {
			SHA1_Update(&context, buff, ret);
		}

		fclose(fd);
		SHA1_Final(hash, &context);

		packet_add_tlv_raw(response, TLV_TYPE_FILE_NAME, hash, sizeof(hash));
	}

	packet_add_tlv_uint(response, TLV_TYPE_RESULT, result);
	return PACKET_TRANSMIT(remote, response, NULL);
}
Beispiel #2
0
/*
 * Gets information about the file path that is supplied and returns it to the
 * requestor
 *
 * req: TLV_TYPE_FILE_PATH - The file path that is to be stat'd
 */
DWORD request_fs_stat(Remote *remote, Packet *packet)
{
	Packet *response = packet_create_response(packet);
	struct meterp_stat buf;
	char *filePath;
	char *expanded = NULL;
	DWORD result = ERROR_SUCCESS;

	filePath = packet_get_tlv_value_string(packet, TLV_TYPE_FILE_PATH);

	if (!filePath) {
		result = ERROR_INVALID_PARAMETER;
		goto out;
	}

	expanded = fs_expand_path(filePath);
	if (expanded == NULL) {
		result = ERROR_NOT_ENOUGH_MEMORY;
		goto out;
	}

	result = fs_stat(expanded, &buf);
	if (0 == result) {
		packet_add_tlv_raw(response, TLV_TYPE_STAT_BUF, &buf, sizeof(buf));
	}

	free(expanded);

out:
	packet_add_tlv_uint(response, TLV_TYPE_RESULT, result);
	return PACKET_TRANSMIT(remote, response, NULL);
}
Beispiel #3
0
/*
 * Expands a file path and returns the expanded path to the requestor
 *
 * req: TLV_TYPE_FILE_PATH - The file path to expand
 */
DWORD request_fs_file_expand_path(Remote *remote, Packet *packet)
{
	Packet *response = packet_create_response(packet);
	DWORD result = ERROR_SUCCESS;
	char *expanded = NULL;
	char *regular;

	regular = packet_get_tlv_value_string(packet, TLV_TYPE_FILE_PATH);
	if (regular == NULL) {
		result = ERROR_INVALID_PARAMETER;
		goto out;
	}

	// Allocate storage for the expanded path
	expanded = fs_expand_path(regular);
	if (expanded == NULL) {
		result = ERROR_NOT_ENOUGH_MEMORY;
		goto out;
	}

	packet_add_tlv_string(response, TLV_TYPE_FILE_PATH, expanded);
	free(expanded);
out:
	packet_add_tlv_uint(response, TLV_TYPE_RESULT, result);
	return PACKET_TRANSMIT(remote, response, NULL);
}
Beispiel #4
0
/*!
 * @brief Transmit a `TLV_TYPE_RESULT` response if `response` is present.
 * @param result The result to be sent.
 * @param remote Reference to the remote connection to send the response to.
 * @param response the Response to add the `result` to.
 */
DWORD packet_transmit_response(DWORD result, Remote* remote, Packet* response)
{
	if (response)
	{
		packet_add_tlv_uint(response, TLV_TYPE_RESULT, result);
		return PACKET_TRANSMIT(remote, response, NULL);
	}
	return ERROR_NOT_ENOUGH_MEMORY;
}
/*
 * core_channel_open
 * -----------------
 *
 * Opens a channel with the remote endpoint.  The response handler for this
 * request will establish the relationship on the other side.
 *
 * opt: TLV_TYPE_CHANNEL_TYPE 
 *      The channel type to allocate.  If set, the function returns, allowing
 *      a further up extension handler to allocate the channel.
 */
DWORD remote_request_core_channel_open(Remote *remote, Packet *packet)
{
	Packet *response;
	DWORD res = ERROR_SUCCESS;
	Channel *newChannel;
	PCHAR channelType;
	DWORD flags = 0;

	do
	{
		dprintf( "[CHANNEL] Opening new channel for packet %p", packet );

		// If the channel open request had a specific channel type
		if ((channelType = packet_get_tlv_value_string(packet, TLV_TYPE_CHANNEL_TYPE)))
		{
			res = ERROR_NOT_FOUND;
			break;
		}

		// Get any flags that were supplied
		flags = packet_get_tlv_value_uint(packet, TLV_TYPE_FLAGS);

		dprintf( "[CHANNEL] Opening %s %u", channelType, flags );

		// Allocate a response
		response = packet_create_response(packet);
		
		// Did the response allocation fail?
		if ((!response) || (!(newChannel = channel_create(0, flags))))
		{
			res = ERROR_NOT_ENOUGH_MEMORY;
			break;
		}

		dprintf( "[CHANNEL] Opened %s %u", channelType, flags );

		// Get the channel class and set it
		newChannel->cls = packet_get_tlv_value_uint(packet, TLV_TYPE_CHANNEL_CLASS);

		dprintf( "[CHANNEL] Channel class for %s: %u", channelType, newChannel->cls );

		// Add the new channel identifier to the response
		if ((res = packet_add_tlv_uint(response, TLV_TYPE_CHANNEL_ID,
				channel_get_id(newChannel))) != ERROR_SUCCESS)
			break;

		// Transmit the response
		dprintf( "[CHANNEL] Sending response for %s", channelType  );
		res = PACKET_TRANSMIT(remote, response, NULL);

		dprintf( "[CHANNEL] Done" );

	} while (0);

	return res;
}
Beispiel #6
0
/*
 * Gets the directory separator for this system
 */
DWORD request_fs_separator(Remote *remote, Packet *packet)
{
	Packet *response = packet_create_response(packet);

	packet_add_tlv_string(response, TLV_TYPE_STRING, FS_SEPARATOR);

	packet_add_tlv_uint(response, TLV_TYPE_RESULT, ERROR_SUCCESS);

	return PACKET_TRANSMIT(remote, response, NULL);
}
Beispiel #7
0
/*
 * Gets the current working directory
 *
 * req: TLV_TYPE_DIRECTORY_PATH - The directory path to change the working
 *                                directory to.
 */
DWORD request_fs_getwd(Remote * remote, Packet * packet)
{
	Packet *response = packet_create_response(packet);
	char *directory = NULL;
	DWORD result;

	result = fs_getwd(&directory);
	if (directory != NULL) {
		packet_add_tlv_string(response, TLV_TYPE_DIRECTORY_PATH, directory);
		free(directory);
	}

	packet_add_tlv_uint(response, TLV_TYPE_RESULT, result);
	return PACKET_TRANSMIT(remote, response, NULL);
}
Beispiel #8
0
/*
 * Gets the contents of a given directory path and returns the list of file
 * names to the requestor.
 *
 * req: TLV_TYPE_DIRECTORY_PATH - The directory that should be listed
 */
DWORD request_fs_ls(Remote * remote, Packet * packet)
{
	Packet *response = packet_create_response(packet);
	LPCSTR directory = packet_get_tlv_value_string(packet, TLV_TYPE_DIRECTORY_PATH);
	DWORD result;

	if (!directory) {
		result = ERROR_INVALID_PARAMETER;
	} else {
		result = fs_ls(directory, request_fs_ls_cb, response);
	}

	packet_add_tlv_uint(response, TLV_TYPE_RESULT, result);
	return PACKET_TRANSMIT(remote, response, NULL);
}
Beispiel #9
0
/*
 * Removes the supplied directory from disk if it's empty
 *
 * req: TLV_TYPE_DIRECTORY_PATH - The directory that is to be removed.
 */
DWORD request_fs_delete_dir(Remote * remote, Packet * packet)
{
	Packet *response = packet_create_response(packet);
	char *directory;
	DWORD result;
	directory = packet_get_tlv_value_string(packet, TLV_TYPE_DIRECTORY_PATH);

	if (directory == NULL) {
		result = ERROR_INVALID_PARAMETER;
	} else {
		result = fs_delete_dir(directory);
	}

	packet_add_tlv_uint(response, TLV_TYPE_RESULT, result);
	return PACKET_TRANSMIT(remote, response, NULL);
}
Beispiel #10
0
/*
 * Removes the supplied file from disk
 *
 * req: TLV_TYPE_FILE_PATH - The file that is to be removed.
 */
DWORD request_fs_delete_file(Remote *remote, Packet *packet)
{
	Packet *response = packet_create_response(packet);
	char *path;
	DWORD result = ERROR_SUCCESS;

	path = packet_get_tlv_value_string(packet, TLV_TYPE_FILE_PATH);

	if (!path) {
		result = ERROR_INVALID_PARAMETER;
	} else {
		result = fs_delete_file(path);
	}

	packet_add_tlv_uint(response, TLV_TYPE_RESULT, result);
	return PACKET_TRANSMIT(remote, response, NULL);
}
/*
 * core_crypto_negotiate
 * ---------------------
 *
 * Negotiates a cryptographic session with the remote host
 *
 * req: TLV_TYPE_CIPHER_NAME       -- The cipher being selected.
 * opt: TLV_TYPE_CIPHER_PARAMETERS -- The paramters passed to the cipher for
 *                                    initialization
 */
DWORD remote_request_core_crypto_negotiate(Remote *remote, Packet *packet)
{
	LPCSTR cipherName = packet_get_tlv_value_string(packet,
			TLV_TYPE_CIPHER_NAME);
	DWORD res = ERROR_INVALID_PARAMETER;
	Packet *response = packet_create_response(packet);

	// If a cipher name was supplied, set it
	if (cipherName)
		res = remote_set_cipher(remote, cipherName, packet);

	// Transmit a response
	if (response)
	{
		packet_add_tlv_uint(response, TLV_TYPE_RESULT, res);

		PACKET_TRANSMIT(remote, response, NULL);
	}

	return ERROR_SUCCESS;
}
/*
 * core_channel_close
 * ------------------
 *
 * Closes a previously opened channel.
 *
 * req: TLV_TYPE_CHANNEL_ID -- The channel identifier to close
 */
DWORD remote_request_core_channel_close(Remote *remote, Packet *packet)
{
	Packet *response = packet_create_response(packet);
	DWORD res = ERROR_SUCCESS, channelId;
	Channel *channel = NULL;

	dprintf("[CHANNEL] remote_request_core_channel_close.");

	do
	{
		// Get the channel identifier
		channelId = packet_get_tlv_value_uint(packet, TLV_TYPE_CHANNEL_ID);

		// Try to locate the specified channel
		if (!(channel = channel_find_by_id(channelId)))
		{
			res = ERROR_NOT_FOUND;
			break;
		}

		// Destroy the channel
		channel_destroy(channel, packet);

		if (response)
		{
			packet_add_tlv_uint(response, TLV_TYPE_CHANNEL_ID, channelId);
		}

	} while (0);

	// Transmit the acknowledgement
	if (response)
	{
		packet_add_tlv_uint(response, TLV_TYPE_RESULT, res);

		res = PACKET_TRANSMIT(remote, response, NULL);
	}

	return res;
}
Beispiel #13
0
DWORD request_core_enumextcmd(Remote* remote, Packet* packet)
{
	BOOL bResult = FALSE;
	Packet* pResponse = packet_create_response(packet);

	if (pResponse != NULL)
	{
		EnumExtensions enumExt;
		enumExt.pResponse = pResponse;
		enumExt.lpExtensionName = packet_get_tlv_value_string(packet, TLV_TYPE_STRING);

		dprintf("[LISTEXTCMD] Listing extension commands for %s ...", enumExt.lpExtensionName);
		// Start by enumerating the names of the extensions
		bResult = list_enumerate(gExtensionList, ext_cmd_callback, &enumExt);

		packet_add_tlv_uint(pResponse, TLV_TYPE_RESULT, ERROR_SUCCESS);

		PACKET_TRANSMIT(remote, pResponse, NULL);
	}

	return ERROR_SUCCESS;
}
/*
 * core_channel_write
 * ------------------
 *
 * Write data from a channel into the local output buffer for it
 */
DWORD remote_request_core_channel_write(Remote *remote, Packet *packet)
{
	Packet *response = packet_create_response(packet);
	DWORD res = ERROR_SUCCESS, channelId, written = 0;
	Tlv channelData;
	Channel * channel = NULL;

	do
	{
		channelId = packet_get_tlv_value_uint(packet, TLV_TYPE_CHANNEL_ID);

		// Try to locate the specified channel
		if (!(channel = channel_find_by_id(channelId)))
		{
			res = ERROR_NOT_FOUND;
			break;
		}

		lock_acquire( channel->lock );

		// Get the channel data buffer
		if ((res = packet_get_tlv(packet, TLV_TYPE_CHANNEL_DATA, &channelData)) != ERROR_SUCCESS)
			break;

		// Handle the write operation differently based on the class of channel
		switch (channel_get_class(channel))
		{
			// If it's buffered, write it to the local buffer cache
			case CHANNEL_CLASS_BUFFERED:
				res = channel_write_to_buffered(channel, channelData.buffer, channelData.header.length, (PULONG)&written);
				break;
			// If it's non-buffered, call the native write operation handler if
			// one is implemented
			default:
				{
					NativeChannelOps *ops = (NativeChannelOps *)&channel->ops;
					if (ops->write)
						res = ops->write(channel, packet, ops->context, 
								channelData.buffer, channelData.header.length, 
								&written);
					else
						res = ERROR_NOT_SUPPORTED;
				}
				break;
		}

	} while (0);

	if( channel )
		lock_release( channel->lock );

	// Transmit the acknowledgement
	if (response)
	{
		packet_add_tlv_uint(response, TLV_TYPE_RESULT, res);
		packet_add_tlv_uint(response, TLV_TYPE_LENGTH, written);
		packet_add_tlv_uint(response, TLV_TYPE_CHANNEL_ID, channelId);

		res = PACKET_TRANSMIT(remote, response, NULL);
	}

	return res;
}
/*!
 * @brief Notify routine for a tcp server channel to pick up its new client connections..
 * @param remote Pointer to the remote instance.
 * @param serverCtx Pointer to the TCP server context.
 * @returns Indication of success or failure.
 * @retval ERROR_SUCCESS Notification completed successfully.
 */
DWORD tcp_channel_server_notify(Remote * remote, TcpServerContext * serverCtx)
{
    DWORD dwResult = ERROR_SUCCESS;
    TcpClientContext* clientctx = NULL;
    Packet* request = NULL;
    SOCKADDR_IN6 clientaddr = { 0 };
    SOCKADDR_IN6 serveraddr = { 0 };
    SOCKET sock = 0;
    DWORD size = 0;
    char* localhost = NULL;
    char* peerhost = NULL;
    int localport = 0;
    int peerport = 0;

    do
    {
        if (!serverCtx)
        {
            BREAK_WITH_ERROR("[TCP-SERVER] tcp_channel_server_notify. serverCtx == NULL", ERROR_INVALID_HANDLE);
        }

        ResetEvent(serverCtx->notify);

        size = sizeof(SOCKADDR_IN6);

        sock = accept(serverCtx->fd, (SOCKADDR*)&clientaddr, &size);
        if (sock == INVALID_SOCKET)
        {
            if (WSAGetLastError() == WSAEWOULDBLOCK)
            {
                Sleep(100);
                break;
            }

            BREAK_ON_WSAERROR("[TCP-SERVER] tcp_channel_server_notify. accept failed");
        }

        dprintf("[TCP-SERVER] tcp_channel_server_notify. Got new client connection on channel %d. sock=%d", channel_get_id(serverCtx->channel), sock);

        clientctx = tcp_channel_server_create_client(serverCtx, sock);
        if (!clientctx)
        {
            BREAK_WITH_ERROR("[TCP-SERVER] tcp_channel_server_notify. clientctx == NULL", ERROR_INVALID_HANDLE);
        }

        size = sizeof(SOCKADDR_IN6);

        if (getsockname(serverCtx->fd, (SOCKADDR *)&serveraddr, &size) == SOCKET_ERROR)
        {
            BREAK_ON_WSAERROR("[TCP-SERVER] request_net_tcp_server_channel_open. getsockname failed");
        }

        if (!serverCtx->ipv6)
        {
            localhost = inet_ntoa(((SOCKADDR_IN*)&serveraddr)->sin_addr);
        }

        if (!localhost)
        {
            localhost = "";
        }

        localport = ntohs(serverCtx->ipv6 ? serveraddr.sin6_port : ((SOCKADDR_IN*)&serveraddr)->sin_port);

        if (!serverCtx->ipv6)
        {
            peerhost = inet_ntoa(((SOCKADDR_IN*)&clientaddr)->sin_addr);
        }

        if (!peerhost)
        {
            peerhost = "";
        }

        peerport = ntohs(serverCtx->ipv6 ? clientaddr.sin6_port : ((SOCKADDR_IN*)&clientaddr)->sin_port);

        dprintf("[TCP-SERVER] tcp_channel_server_notify. New connection %s:%d <- %s:%d", localhost, localport, peerhost, peerport);

        request = packet_create(PACKET_TLV_TYPE_REQUEST, "tcp_channel_open");
        if (!request)
        {
            BREAK_WITH_ERROR("[TCP-SERVER] request_net_tcp_server_channel_open. packet_create failed", ERROR_INVALID_HANDLE);
        }

        packet_add_tlv_uint(request, TLV_TYPE_CHANNEL_ID, channel_get_id(clientctx->channel));
        packet_add_tlv_uint(request, TLV_TYPE_CHANNEL_PARENTID, channel_get_id(serverCtx->channel));
        packet_add_tlv_string(request, TLV_TYPE_LOCAL_HOST, localhost);
        packet_add_tlv_uint(request, TLV_TYPE_LOCAL_PORT, localport);
        packet_add_tlv_string(request, TLV_TYPE_PEER_HOST, peerhost);
        packet_add_tlv_uint(request, TLV_TYPE_PEER_PORT, peerport);

        dwResult = PACKET_TRANSMIT(serverCtx->remote, request, NULL);

    } while (0);

    return dwResult;
}
DWORD request_core_loadlib(Remote *remote, Packet *packet) {
	Packet *response = packet_create_response(packet);
	DWORD res = ERROR_SUCCESS;
	HMODULE library;
	PCHAR libraryPath;
	DWORD flags = 0;
	PCHAR targetPath;
	int local_error = 0;
	Command *command;
	Command *first = extensionCommands;

	do {
		Tlv dataTlv;

		libraryPath = packet_get_tlv_value_string(packet, TLV_TYPE_LIBRARY_PATH);
		flags = packet_get_tlv_value_uint(packet, TLV_TYPE_FLAGS);

		// Invalid library path?
		if (!libraryPath) {
			res = ERROR_INVALID_PARAMETER;
			break;
		}

		if (flags & LOAD_LIBRARY_FLAG_LOCAL) {
			// i'd be surprised if we could load
			// libraries off the remote system without breaking severely.
			res = ERROR_NOT_SUPPORTED;
			break;
		}

		// Get the library's file contents
		if ((packet_get_tlv(packet, TLV_TYPE_DATA,
			&dataTlv) != ERROR_SUCCESS) ||
			(!(targetPath = packet_get_tlv_value_string(packet,
			TLV_TYPE_TARGET_PATH)))) {
			res = ERROR_INVALID_PARAMETER;
			break;
		}

		dprintf("targetPath: %s", targetPath);

		library = dlopenbuf(targetPath, dataTlv.buffer, dataTlv.header.length);
		dprintf("dlopenbuf(%s): %08x / %s", targetPath, library, dlerror());
		if (!library) {
			res = ERROR_NOT_FOUND;
			break;
		}

		// If this library is supposed to be an extension library, try to
		// call its Init routine
		if (flags & LOAD_LIBRARY_FLAG_EXTENSION) {
			PEXTENSION pExtension = (PEXTENSION)malloc(sizeof(EXTENSION));
			if (!pExtension) {
				res = ERROR_NOT_ENOUGH_MEMORY;
				break;
			}
			//DWORD(*init)(Remote *remote);

			pExtension->init = dlsym(library, "InitServerExtension");

			// Call the init routine in the library
			if (pExtension->init) {
				dprintf("calling InitServerExtension");
				pExtension->end = first;
				res = pExtension->init(remote);
				pExtension->start = extensionCommands;
				pExtension->getname = dlsym(library, "GetExtensionName");
				pExtension->deinit = dlsym(library, "DeinitServerExtension");

				if (pExtension->getname) {
					pExtension->getname(pExtension->name, sizeof(pExtension->name));
				}
				list_push(gExtensionList, pExtension);
			}
			else {
				free(pExtension);
			}

			if (response) {
				for (command = pExtension->start; command != pExtension->end; command = command->next) {
					packet_add_tlv_string(response, TLV_TYPE_METHOD, command->method);
				}
			}
		}

	} while (0);

	if (response) {
		packet_add_tlv_uint(response, TLV_TYPE_RESULT, res);
		PACKET_TRANSMIT(remote, response, NULL);
	}

	return (res);
}
/*
 * core_channel_read
 * -----------------
 *
 * From from the local buffer and write back to the requester
 *
 * Takes TLVs:
 *
 * req: TLV_TYPE_CHANNEL_ID -- The channel identifier to read from
 * req: TLV_TYPE_LENGTH     -- The number of bytes to read
 */
DWORD remote_request_core_channel_read(Remote *remote, Packet *packet)
{
	DWORD res = ERROR_SUCCESS, bytesToRead, bytesRead, channelId;
	Packet *response = packet_create_response(packet);
	PUCHAR temporaryBuffer = NULL;
	Channel *channel = NULL;

	do
	{
		if (!response)
		{
			res = ERROR_NOT_ENOUGH_MEMORY;
			break;
		}

		// Get the number of bytes to read
		bytesToRead = packet_get_tlv_value_uint(packet, TLV_TYPE_LENGTH);
		channelId   = packet_get_tlv_value_uint(packet, TLV_TYPE_CHANNEL_ID);

		// Try to locate the specified channel
		if (!(channel = channel_find_by_id(channelId)))
		{
			res = ERROR_NOT_FOUND;
			break;
		}

		lock_acquire( channel->lock );

		// Allocate temporary storage
		if (!(temporaryBuffer = (PUCHAR)malloc(bytesToRead)))
		{
			res = ERROR_NOT_ENOUGH_MEMORY;
			break;
		}

		switch (channel_get_class(channel))
		{
			// If it's buffered, read from the local buffer and either transmit 
			// the buffer in the response or write it back asynchronously
			// depending on the mode of the channel.
			case CHANNEL_CLASS_BUFFERED:
				// Read in from local
				res = channel_read_from_buffered(channel, temporaryBuffer, 
				    bytesToRead, (PULONG)&bytesRead);
				break;
			// Handle read I/O for the pool class
			case CHANNEL_CLASS_POOL:
				// If the channel has a read handler
				if (channel->ops.pool.read)
					res = channel->ops.pool.read(channel, packet, 
							channel->ops.pool.native.context, temporaryBuffer, 
							bytesToRead, &bytesRead);
				else
					res = ERROR_NOT_SUPPORTED;
				break;
			default:
				res = ERROR_NOT_SUPPORTED;
		}

		// If we've so far been successful and we have a temporary buffer...
		if ((res == ERROR_SUCCESS) &&(temporaryBuffer) && (bytesRead))
		{
			// If the channel should operate synchronously, add the data to theresponse
			if (channel_is_flag(channel, CHANNEL_FLAG_SYNCHRONOUS))
			{
				// if the channel data is ment to be compressed, compress it!
				if( channel_is_flag( channel, CHANNEL_FLAG_COMPRESS ) )
					packet_add_tlv_raw(response, TLV_TYPE_CHANNEL_DATA|TLV_META_TYPE_COMPRESSED, temporaryBuffer, bytesRead);
				else
					packet_add_tlv_raw(response, TLV_TYPE_CHANNEL_DATA, temporaryBuffer, bytesRead);

				res = ERROR_SUCCESS;
			}
			// Otherwise, asynchronously write the buffer to the remote endpoint
			else
			{
				if ((res = channel_write(channel, remote, NULL, 0, temporaryBuffer, bytesRead, NULL)) != ERROR_SUCCESS)
					break;
			}
		}

	} while (0);
	
	if( channel )
		lock_release( channel->lock );

	if (temporaryBuffer)
		free(temporaryBuffer);

	// Transmit the acknowledgement
	if (response)
	{
		packet_add_tlv_uint(response, TLV_TYPE_RESULT, res);
		packet_add_tlv_uint(response, TLV_TYPE_LENGTH, bytesRead);
		packet_add_tlv_uint(response, TLV_TYPE_CHANNEL_ID, channelId);

		res = PACKET_TRANSMIT(remote, response, NULL);
	}

	return res;
}