blob: 720ada25dc9c603d777d097aa45d2c59aa8c08ee [file] [log] [blame]
/*
*
* Copyright (c) 2013-2017 Nest Labs, Inc.
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @file
* This file contains callbacks and helper functions common across the client and
* server BDX test implementations. They can serve, along with weave-bdx-client.cpp
* and weave-bdx-server.cpp, as examples of how to use the BDX API to configure and run
* a client/server. Specifically, they handle transferring simple files using the C++
* FILE API.
*/
#include <inttypes.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
// This code uses DEVELOPMENT BDX namespace
#define WEAVE_CONFIG_BDX_NAMESPACE kWeaveManagedNamespace_Development
#include <Weave/Core/WeaveConfig.h>
#include <Weave/Support/logging/WeaveLogging.h>
#include "weave-bdx-common-development.h"
#include "ToolCommon.h"
// We can configure the server to download a requested URI using curl to represent
// grabbing an update image, but we disable this by default to not introduce the
// libcurl requirement to Weave.
#if HAVE_CURL_CURL_H
#include <curl/curl.h>
#endif
#if HAVE_CURL_EASY_H
#include <curl/easy.h>
#endif
using nl::StatusReportStr;
using namespace nl::Weave::Profiles;
using namespace nl::Weave::Profiles::Common;
using namespace nl::Weave::Profiles::BulkDataTransfer;
using namespace nl::Weave::Logging;
namespace nl {
namespace Weave {
namespace Profiles {
namespace WeaveMakeManagedNamespaceIdentifier(BDX, kWeaveManagedNamespaceDesignation_Development) {
static BdxAppState mAppStatePool[WEAVE_CONFIG_BDX_MAX_NUM_TRANSFERS];
// curled files go here
char TempFileLocation[FILENAME_MAX] = "/tmp/";
// bdx-received files go here
char ReceivedFileLocation[FILENAME_MAX] = "/tmp/";
BdxAppState *NewAppState()
{
BdxAppState *appState;
for (int i = 0; i < WEAVE_CONFIG_BDX_MAX_NUM_TRANSFERS; i++)
{
appState = &mAppStatePool[i];
if (appState->mFile != NULL || !appState->mDone || appState->mBuffer != NULL)
{
continue;
}
return appState;
}
WeaveLogError(BDX, "BDX: Ran out of app states, maximum %d", WEAVE_CONFIG_BDX_MAX_NUM_TRANSFERS);
return NULL;
}
void ResetAppStates()
{
for (int i = 0; i < WEAVE_CONFIG_BDX_MAX_NUM_TRANSFERS; i++)
{
mAppStatePool[i].mFile = NULL;
mAppStatePool[i].mDone = true;
mAppStatePool[i].mBuffer = NULL;
}
}
void SetReceivedFileLocation(const char *path)
{
strncpy(ReceivedFileLocation, path, sizeof(ReceivedFileLocation));
ReceivedFileLocation[sizeof(ReceivedFileLocation) - 1] = '\0';
// End string with / if it doesn't already
if (ReceivedFileLocation[strlen(ReceivedFileLocation) - 1] != '/')
{
ReceivedFileLocation[strlen(ReceivedFileLocation)] = '/';
}
ReceivedFileLocation[sizeof(ReceivedFileLocation) - 1] = '\0';
}
void SetTempLocation(const char *path)
{
strncpy(TempFileLocation, path, sizeof(TempFileLocation));
TempFileLocation[sizeof(TempFileLocation) - 1] = '\0';
// End string with / if it doesn't already
if (TempFileLocation[strlen(TempFileLocation) - 1] != '/')
{
TempFileLocation[strlen(TempFileLocation)] = '/';
}
TempFileLocation[sizeof(TempFileLocation) - 1] = '\0';
}
/** Helper function for use by libcurl. */
size_t WriteData(void *aPtr, size_t aSize, size_t aNmemb, FILE *aStream)
{
size_t written = fwrite(aPtr, aSize, aNmemb, aStream);
return written;
}
/** Helper function for writing data to a file. It is unclear as to why (or if) we
* need the while loop in here (this code was inherited from an older branch).
* Perhaps for handling streams that are being added to as we consume from them?
*/
size_t ReadData(char *aPtr, size_t aSize, size_t aNmemb, FILE *aStream)
{
int nread = 0, left = aNmemb;
WeaveLogDetail(BDX, "0 read_n entering\n");
while (left > 0)
{
WeaveLogDetail(BDX, "1 read_n (left: %d)\n", left);
if ((nread = fread(aPtr, aSize, aNmemb, aStream)) > 0)
{
left -= nread;
aPtr += nread;
WeaveLogDetail(BDX, "2 read_n (nread: %d, left: %d)\n", nread, left);
}
else
{
WeaveLogDetail(BDX, "3 read_n (nread: 0, left: %d)\n", left);
return aNmemb - left;
}
}
WeaveLogDetail(BDX, "4 read_n (n: %d, left: %d) exiting\n", nread, left);
return nread;
}
/** This function uses libcurl to download the file specified by the URI aFileDesignator.
* If successful, it modifies aFileDesignator to point to the location of where the
* file was downloaded to (it gets stored in a directory specified by
* TempFileLocation).
*
* @note
* If you want to get a local file, specify the file:// protocol
*/
#if HAVE_CURL_CURL_H && HAVE_CURL_EASY_H
int DownloadFile(char *aFileDesignator, size_t aFileDesignatorBufferSize)
{
CURL *curl = NULL;
FILE *fp;
CURLcode res = CURLE_FAILED_INIT;
char *pch = NULL, *file_name = NULL;
char *downloadURL = (char*)malloc(1 + strlen(aFileDesignator));
char outfilename[FILENAME_MAX];
char fileTemplate[] = "/tmp/fileXXXXXX";
int retval = 0;
strncpy(outfilename, TempFileLocation, sizeof(outfilename));
outfilename[sizeof(outfilename) - 1] = '\0';
strcpy(downloadURL, aFileDesignator);
// Extract the file name out of the download URL
pch = strtok(aFileDesignator, "/");
while (pch != NULL)
{
file_name = pch;
pch = strtok(NULL, "/");
}
strncat(outfilename, file_name, FILENAME_MAX-1);
if (mkstemp(fileTemplate) != -1)
{
curl = curl_easy_init();
}
if (curl)
{
fp = fopen(fileTemplate, "wb");
WeaveLogDetail(BDX, "BDX: Downloading Image : |%s|\n", downloadURL);
curl_easy_setopt(curl, CURLOPT_URL, downloadURL);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteData);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
res = curl_easy_perform(curl);
/* always cleanup */
curl_easy_cleanup(curl);
fclose(fp);
if (res != CURLE_OK)
{
retval = remove(fileTemplate);
if (retval != 0)
{
WeaveLogError(BDX, "BDX: Failed to remove the temporary file\n");
}
aFileDesignator = NULL;
}
else
{
retval = rename(fileTemplate, outfilename);
if (retval != 0)
{
WeaveLogError(BDX, "BDX: Failed to rename the temporary file to %s\n", outfilename);
}
strncpy(aFileDesignator, outfilename, aFileDesignatorBufferSize-1);
aFileDesignator[aFileDesignatorBufferSize-1] = '\0';
}
}
else
{
WeaveLogError(BDX, "BDX: Failed to initialize curl\n");
aFileDesignator = NULL;
}
free(downloadURL);
downloadURL = NULL;
return (int)res;
}
#endif // HAVE_CURL_CURL_H && HAVE_CURL_EASY_H
/** Example implementation of a SendInitHandler that opens the requested file if possible
* (in a directory specified by ReceivedFileLocation) and sets up the BDXTransfer
* by attaching our AppState to store the open file handle and setting the appropriate
* handlers.
*
* @err #kStatus_ServerBadState If the file to be written to couldn't be opened
*/
uint16_t BdxSendInitHandler(BDXTransfer *aXfer, SendInit *aSendInitMsg)
{
uint16_t err = kStatus_NoError;
BDXHandlers handlers =
{
NULL, NULL, NULL, NULL,
BdxPutBlockHandler, BdxXferErrorHandler, BdxXferDoneHandler, BdxErrorHandler
};
// Copy file name onto C-string
// NOTE: the original string is not NUL terminated, but we know its length.
// TODO: put this in the aXfer's fileDesignator
char fileDesignator[aSendInitMsg->mFileDesignator.theLength + strlen(ReceivedFileLocation) + 1];
memcpy(fileDesignator, ReceivedFileLocation, strlen(ReceivedFileLocation));
memcpy(fileDesignator + strlen(ReceivedFileLocation), aSendInitMsg->mFileDesignator.theString, aSendInitMsg->mFileDesignator.theLength);
fileDesignator[aSendInitMsg->mFileDesignator.theLength + strlen(ReceivedFileLocation)] = '\0';
WeaveLogDetail(BDX, "Send request for file: %s", fileDesignator);
BdxAppState * mAppState = NewAppState();
VerifyOrExit(mAppState != NULL, err = kStatus_ServerBadState);
aXfer->mAppState = mAppState;
// The client already handles Setting transfer mode, max block size, and start sending
// We just need to open the file and allocate a buffer for reading blocks
mAppState->mFile = fopen(fileDesignator, "w");
VerifyOrExit(mAppState->mFile != NULL, err = kStatus_ServerBadState);
//TODO: shouldn't be using dynamic memory allocation, but how to do that with dynamically negotiated maxBlockSize???
//perhaps just go ahead and allocate our maximum size since we know the transfer won't go above that?
mAppState->mBuffer = (uint8_t *)malloc(aSendInitMsg->mMaxBlockSize);
// All seems good, so accept the transfer and set the handlers
aXfer->mIsAccepted = true;
aXfer->mTransferMode = aSendInitMsg->mSenderDriveSupported ? kMode_SenderDrive : kMode_ReceiverDrive;
aXfer->SetHandlers(handlers);
exit:
return err;
}
/** Example implementation of a ReceiveInitHandler that downloads the requested file
* if possible and configured to do so (see DownloadFile()) and sets up the BDXTransfer
* by attaching our AppState to store the open file handle and setting the appropriate
* handlers.
*
* @err #kStatus_UnknownFile If the file couldn't be found
*/
uint16_t BdxReceiveInitHandler(BDXTransfer *aXfer, ReceiveInit *aReceiveInit)
{
uint16_t err = kStatus_NoError;
int retval = 0;
long fileSize = 0;
BdxAppState *mAppState;
char *fileDesignator = NULL;
#if !defined(HAVE_CURL_CURL_H) || !defined(HAVE_CURL_EASY_H)
char *tempFileDesignator = NULL;
#endif
FILE *targetFile = NULL;
BDXHandlers handlers =
{
NULL, NULL, NULL, BdxGetBlockHandler,
NULL, BdxXferErrorHandler, BdxXferDoneHandler, BdxErrorHandler
};
// TODO: put this in the aXfer's fileDesignator
// Copy file name onto C-string
// NOTE: the original string is not NUL terminated, but we know its length.
fileDesignator = (char *)malloc(FILENAME_MAX);
VerifyOrExit(fileDesignator != NULL, err = kStatus_ServerBadState);
memcpy(fileDesignator, aReceiveInit->mFileDesignator.theString, aReceiveInit->mFileDesignator.theLength);
fileDesignator[aReceiveInit->mFileDesignator.theLength] = '\0';
#if HAVE_CURL_CURL_H && HAVE_CURL_EASY_H
// We download the file specified by URI and then open it
WeaveLogDetail(BDX, "Download URI : %s", fileDesignator);
// NOTE: DownloadFile will mutate the fileDesignator to no longer be a URI
retval = DownloadFile(fileDesignator, FILENAME_MAX);
VerifyOrExit(retval == CURLE_OK,
err = kStatus_UnknownFile;
WeaveLogError(BDX, "Unable to download the file : %d", err));
#else
// If fileDesignator doesn't specify a local file with file:// and we
// don't have curl, exit out.
if (strncmp(fileDesignator, "file://", strlen("file://") != 0))
{
WeaveLogError(BDX, "Curl not found and we're given a non-local file path.");
err = kStatus_XferMethodNotSupported;
SuccessOrExit(err);
}
else
{
tempFileDesignator = (char *)malloc(strlen(fileDesignator));
VerifyOrExit(tempFileDesignator != NULL, err = kStatus_ServerBadState);
strcpy(tempFileDesignator, fileDesignator + strlen("file://"));
strcpy(fileDesignator, tempFileDesignator);
free(tempFileDesignator);
}
#endif // HAVE_CURL_CURL_H && HAVE_CURL_EASY_H
mAppState = NewAppState();
VerifyOrExit(mAppState != NULL, err = kStatus_ServerBadState);
aXfer->mAppState = mAppState;
// The client already handles Setting transfer mode, max block size, and start sending
// We just need to open the file and allocate a buffer for reading blocks
targetFile = fopen(fileDesignator, "r");
VerifyOrExit(targetFile != NULL,
err = kStatus_UnknownFile;
WeaveLogError(BDX, "Error opening file %s", fileDesignator));
// Use fseek/ftell to find the size of the file.
fseek(targetFile, 0, SEEK_END);
// This will only work for files below 2 GB
fileSize = ftell(targetFile);
VerifyOrExit(fileSize >= 0, err = kStatus_Unknown);
VerifyOrExit(static_cast<uint64_t>(fileSize) >= aReceiveInit->mStartOffset, err = kStatus_StartOffsetNotSupported);
retval = fseek(targetFile, aReceiveInit->mStartOffset, SEEK_SET);
VerifyOrExit(retval == 0, err = kStatus_StartOffsetNotSupported);
mAppState->mFile = targetFile;
targetFile = NULL;
if (aReceiveInit->mLength == 0)
{
aXfer->mLength = fileSize - aReceiveInit->mStartOffset;
}
else
{
aXfer->mLength = (aReceiveInit->mLength + aReceiveInit->mStartOffset > static_cast<uint64_t>(fileSize)) ? (fileSize - aReceiveInit->mStartOffset) : (aReceiveInit->mLength);
}
//TODO: shouldn't be using dynamic memory allocation, but how to do that with dynamically negotiated maxBlockSize???
//perhaps just go ahead and allocate our maximum size since we know the transfer won't go above that?
mAppState->mBuffer = (uint8_t *)malloc(aReceiveInit->mMaxBlockSize);
// All seems good, so accept the transfer and set the handlers
aXfer->mIsAccepted = true;
aXfer->mTransferMode = aReceiveInit->mReceiverDriveSupported ? kMode_ReceiverDrive : kMode_SenderDrive;
aXfer->SetHandlers(handlers);
exit:
if (fileDesignator != NULL)
{
free(fileDesignator);
}
if (targetFile != NULL)
{
fclose(targetFile);
targetFile = NULL;
}
return err;
}
/** Example implementation of a SendAccept handler that opens the file we previously
* requested to send in a SendInit message and sets up the transfer by associating
* the appropriate AppState for storing the file handle and sets the handlers for
* the BDXTransfer.
*/
WEAVE_ERROR BdxSendAcceptHandler(BDXTransfer *aXfer, SendAccept *aSendAcceptMsg)
{
WeaveLogDetail(BDX, "SendInit Accepted: %hd maxBlockSize, transfer mode is %hd", aSendAcceptMsg->mMaxBlockSize, aXfer->mTransferMode);
BdxAppState* bdxState = static_cast<BdxAppState*>(aXfer->mAppState);
WEAVE_ERROR error = WEAVE_NO_ERROR;
// The client already handles Setting transfer mode, max block size, and start sending
// We just need to open the file and allocate a buffer for reading blocks
bdxState->mFile = fopen(aXfer->mFileDesignator.theString, "r");
if (!bdxState->mFile)
{
printf("Error opening file %s\n", aXfer->mFileDesignator.theString);
exit(-1);
}
//TODO: shouldn't be using dynamic memory allocation, but how to do that with dynamically negotiated maxBlockSize???
//perhaps just go ahead and allocate our maximum size since we know the transfer won't go above that?
bdxState->mBuffer = (uint8_t*)malloc(aSendAcceptMsg->mMaxBlockSize);
return error;
}
/** Example implementation of a ReceiveAccept handler that opens the file we previously
* requested to receive in a ReceiveInit message and sets up the transfer by associating
* the appropriate AppState for storing the file handle and sets the handlers for
* the BDXTransfer.
*
* @note
* The file will be saved inside the ReceivedFileLocation directory
*/
WEAVE_ERROR BdxReceiveAcceptHandler(BDXTransfer *aXfer, ReceiveAccept *aReceiveAcceptMsg)
{
WeaveLogDetail(BDX, "ReceiveInit Accepted: %hd maxBlockSize, transfer mode is 0x%hx", aReceiveAcceptMsg->mMaxBlockSize, aXfer->mTransferMode);
BdxAppState* bdxState = static_cast<BdxAppState*>(aXfer->mAppState);
WEAVE_ERROR err = WEAVE_NO_ERROR;
// The client already handles Setting transfer mode, max block size, and start sending
// We just need to open the file and allocate a buffer for reading blocks
// NOTE: we expect mFileDesignator to be a URI, so need to chop off the protocol.
// We store the file in the ReceivedFileLocation with a different name so that running
// both client and server on the same machine won't overwrite the source file.
char * filename = strrchr(aXfer->mFileDesignator.theString, '/');
if (filename == NULL)
{
filename = aXfer->mFileDesignator.theString;
}
else
{
filename++; //skip over '/'
}
char fileDesignator[strlen(ReceivedFileLocation) + strlen(filename) + 1];
memcpy(fileDesignator, ReceivedFileLocation, strlen(ReceivedFileLocation));
memcpy(fileDesignator + strlen(ReceivedFileLocation), filename, strlen(filename));
fileDesignator[strlen(ReceivedFileLocation) + strlen(filename)] = '\0';
WeaveLogDetail(BDX, "File being saved to: %s", fileDesignator);
bdxState->mFile = fopen(fileDesignator, "w");
if (!bdxState->mFile)
{
WeaveLogDetail(BDX, "Error opening file %s\n", fileDesignator);
exit(-1);
}
return err;
}
/** Example implementation of a RejectHandler, which simply logs the StatusReport
* and sets the transfer as complete so the example client knows to exit.
*/
void BdxRejectHandler(BDXTransfer *aXfer, StatusReport *aReport)
{
WeaveLogProgress(BDX, "BDX Init message rejected: %d", aReport->mStatusCode);
// mark as done to close client
((BdxAppState*)aXfer->mAppState)->mDone = true;
}
/** Example implementation of a GetBlockHandler that reads a block from the associated
* open file handle, stores it in the AppState's buffer, and sets the parameters as
* appropriate so the protocol can handle the block.
*/
void BdxGetBlockHandler(BDXTransfer *aXfer,
uint64_t *aLength,
uint8_t **aDataBlock,
bool *aIsLastBlock)
{
BdxAppState* bdxState = static_cast<BdxAppState*>(aXfer->mAppState);
uint64_t blockSize = 0;
if (aXfer->mLength != 0 && ((aXfer->mLength - aXfer->mBytesSent) < aXfer->mMaxBlockSize))
{
blockSize = aXfer->mLength - aXfer->mBytesSent;
}
else
{
blockSize = aXfer->mMaxBlockSize;
}
*aLength = fread(bdxState->mBuffer, 1, blockSize, bdxState->mFile);
*aDataBlock = bdxState->mBuffer;
aXfer->mBytesSent += blockSize;
*aIsLastBlock = (*aLength < aXfer->mMaxBlockSize) ? true : false;
}
/** Example implementation of a PutBlockHandler that dumps the block to stdout for
* debugging and then writes it to the file handle associated with the transfer.
*/
void BdxPutBlockHandler(BDXTransfer *aXfer, uint64_t aLength, uint8_t *aDataBlock, bool aIsLastBlock)
{
BdxAppState* bdxState = static_cast<BdxAppState*>(aXfer->mAppState);
DumpMemory(aDataBlock, aLength, "--> ", 16);
if (bdxState->mFile)
{
// Write bulk data to disk.
int wtd = fwrite(aDataBlock, 1, aLength, bdxState->mFile);
WeaveLogDetail(BDX, "PutBlockHandler wrote %d bytes to disk", wtd);
}
}
/** Example XferErrorHandler that simply logs the error and closes the entire program.
* A real implemenation on a platform should obviously try to handle the error in a more
* intelligent manner rather than hard exiting.
*/
//TODO: decide when we should be freeing any resources (esp. mallocs) associated with
//a transfer, including freeing up the BdxAppState object.
void BdxXferErrorHandler(BDXTransfer *aXfer, StatusReport *aXferError)
{
BdxAppState *appState = (BdxAppState *)(aXfer->mAppState);
WeaveLogProgress(BDX, "Transfer error: %d", aXferError->mStatusCode);
if (appState->mFile)
{
if (fclose(appState->mFile))
{
printf("Error closing file! Permissions?\n");
}
appState->mFile = NULL;
}
appState->mDone = true;
// app-defined state to tell main() to terminate client program
if (appState->mBuffer != NULL)
{
free(appState->mBuffer);
}
appState->mBuffer = NULL;
aXfer->Shutdown();
}
/** Example XferDoneHandler that closes the associated file handler, notifies our
* AppState that the transfer is complete (so the client can shut down), and frees
* any dynamically allocated resources for this transfer.
*/
void BdxXferDoneHandler(BDXTransfer *aXfer)
{
WeaveLogDetail(BDX, "Transfer complete!");
BdxAppState *appState = (BdxAppState *)(aXfer->mAppState);
if (appState->mFile)
{
if (fclose(appState->mFile))
{
printf("Error closing file! Permissions?\n");
}
appState->mFile = NULL;
}
appState->mDone = true;
// app-defined state to tell main() to terminate client program
if (appState->mBuffer != NULL)
{
free(appState->mBuffer);
}
appState->mBuffer = NULL;
aXfer->Shutdown();
}
/** Example ErrorHandler that logs the error and shuts down the transfer as well as the
* client.
*/
void BdxErrorHandler(BDXTransfer *aXfer, WEAVE_ERROR aErrorCode)
{
BdxAppState *appState = static_cast<BdxAppState *>(aXfer->mAppState);
WeaveLogProgress(BDX, "BDX error: %s", ErrorStr(aErrorCode));
// app-defined state to tell main() to terminate client program
appState->mDone = true;
if (appState->mFile)
{
fclose(appState->mFile);
appState->mFile = NULL;
}
if (appState->mBuffer != NULL)
{
free(appState->mBuffer);
appState->mBuffer = NULL;
}
aXfer->Shutdown();
}
// namespaces
}
}
}
}