Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BDX][Darwin] Add basic BDX protocol supports for Matter.framework #21161

Merged
merged 3 commits into from
Jul 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,14 @@

#include "OTAProviderDelegate.h"
#import <Matter/Matter.h>
#include <fstream>

constexpr uint8_t kUpdateTokenLen = 32;

@interface OTAProviderDelegate ()
@property NSString * mOTAFilePath;
@property NSFileHandle * mFileHandle;
@property NSNumber * mFileOffset;
@property DeviceSoftwareVersionModel * candidate;
@end

Expand All @@ -37,15 +40,15 @@ - (instancetype)init
return self;
}

// TODO: When BDX is added to Matter.framework, update to initialize
// it when there is an update available.
- (void)handleQueryImage:(MTROtaSoftwareUpdateProviderClusterQueryImageParams * _Nonnull)params
completionHandler:(void (^_Nonnull)(MTROtaSoftwareUpdateProviderClusterQueryImageResponseParams * _Nullable data,
NSError * _Nullable error))completionHandler
{
NSError * error;
_selectedCandidate.status = @(MTROtaSoftwareUpdateProviderOTAQueryStatusNotAvailable);
if (![params.protocolsSupported containsObject:@(MTROtaSoftwareUpdateProviderOTADownloadProtocolBDXSynchronous)]) {

auto isBDXProtocolSupported =
[params.protocolsSupported containsObject:@(MTROtaSoftwareUpdateProviderOTADownloadProtocolBDXSynchronous)];
if (!isBDXProtocolSupported) {
_selectedCandidate.status = @(MTROtaSoftwareUpdateProviderOTAQueryStatusDownloadProtocolNotSupported);
error =
[[NSError alloc] initWithDomain:@"OTAProviderDomain"
Expand All @@ -55,42 +58,47 @@ - (void)handleQueryImage:(MTROtaSoftwareUpdateProviderClusterQueryImageParams *
return;
}

if ([self SelectOTACandidate:params.vendorId rPID:params.productId rSV:params.softwareVersion]) {
_selectedCandidate.status = @(MTROtaSoftwareUpdateProviderOTAQueryStatusUpdateAvailable);
_selectedCandidate.updateToken = [self generateUpdateToken];
if (params.requestorCanConsent.integerValue == 1) {
_selectedCandidate.userConsentNeeded
= (_userConsentState == OTAProviderUserUnknown || _userConsentState == OTAProviderUserDenied) ? @(1) : @(0);
NSLog(@"User Consent Needed: %@", _selectedCandidate.userConsentNeeded);
} else {
NSLog(@"Requestor cannot obtain user consent. Our State: %hhu", _userConsentState);
switch (_userConsentState) {
case OTAProviderUserGranted:
NSLog(@"User Consent Granted");
_queryImageStatus = MTROtaSoftwareUpdateProviderOTAQueryStatusUpdateAvailable;
break;

case OTAProviderUserObtaining:
NSLog(@"User Consent Obtaining");
_queryImageStatus = MTROtaSoftwareUpdateProviderOTAQueryStatusBusy;
break;

case OTAProviderUserDenied:
case OTAProviderUserUnknown:
NSLog(@"User Consent Denied or Uknown");
_queryImageStatus = MTROtaSoftwareUpdateProviderOTAQueryStatusNotAvailable;
break;
}
_selectedCandidate.status = @(_queryImageStatus);
}
} else {
auto hasCandidate = [self SelectOTACandidate:params.vendorId rPID:params.productId rSV:params.softwareVersion];
if (!hasCandidate) {
NSLog(@"Unable to select OTA Image.");
_selectedCandidate.status = @(MTROtaSoftwareUpdateProviderOTAQueryStatusNotAvailable);
error = [[NSError alloc]
initWithDomain:@"OTAProviderDomain"
code:MTRErrorCodeInvalidState
userInfo:@{ NSLocalizedDescriptionKey : NSLocalizedString(@"Unable to select Candidate.", nil) }];
return;
}

_selectedCandidate.updateToken = [self generateUpdateToken];

if (params.requestorCanConsent.integerValue == 1) {
_selectedCandidate.status = @(MTROtaSoftwareUpdateProviderOTAQueryStatusUpdateAvailable);
_selectedCandidate.userConsentNeeded
= (_userConsentState == OTAProviderUserUnknown || _userConsentState == OTAProviderUserDenied) ? @(1) : @(0);
NSLog(@"User Consent Needed: %@", _selectedCandidate.userConsentNeeded);
completionHandler(_selectedCandidate, error);
return;
}

NSLog(@"Requestor cannot obtain user consent. Our State: %hhu", _userConsentState);
switch (_userConsentState) {
case OTAProviderUserGranted:
NSLog(@"User Consent Granted");
_queryImageStatus = MTROtaSoftwareUpdateProviderOTAQueryStatusUpdateAvailable;
break;

case OTAProviderUserObtaining:
NSLog(@"User Consent Obtaining");
_queryImageStatus = MTROtaSoftwareUpdateProviderOTAQueryStatusBusy;
break;

case OTAProviderUserDenied:
case OTAProviderUserUnknown:
NSLog(@"User Consent Denied or Uknown");
_queryImageStatus = MTROtaSoftwareUpdateProviderOTAQueryStatusNotAvailable;
break;
}
_selectedCandidate.status = @(_queryImageStatus);
completionHandler(_selectedCandidate, error);
}

Expand All @@ -110,6 +118,66 @@ - (void)handleNotifyUpdateApplied:(MTROtaSoftwareUpdateProviderClusterNotifyUpda
completionHandler(nil);
}

- (void)handleBDXTransferSessionBegin:(NSString * _Nonnull)fileDesignator
offset:(NSNumber * _Nonnull)offset
completionHandler:(void (^)(NSError * error))completionHandler
{
NSLog(@"BDX TransferSession begin with %@ (offset: %@ )", fileDesignator, offset);

auto * handle = [NSFileHandle fileHandleForReadingAtPath:fileDesignator];
if (handle == nil) {
auto errorString = [NSString stringWithFormat:@"Error accessing file at at %@", fileDesignator];
auto error = [[NSError alloc] initWithDomain:@"OTAProviderDomain"
code:MTRErrorCodeGeneralError
userInfo:@{ NSLocalizedDescriptionKey : NSLocalizedString(errorString, nil) }];
completionHandler(error);
return;
}

NSError * seekError = nil;
[handle seekToOffset:[offset unsignedLongValue] error:&seekError];
if (seekError != nil) {
auto errorString = [NSString stringWithFormat:@"Error seeking file (%@) to offset %@", fileDesignator, offset];
auto error = [[NSError alloc] initWithDomain:@"OTAProviderDomain"
code:MTRErrorCodeGeneralError
userInfo:@{ NSLocalizedDescriptionKey : NSLocalizedString(errorString, nil) }];
completionHandler(error);
return;
}

_mFileHandle = handle;
_mFileOffset = offset;
completionHandler(nil);
}

- (void)handleBDXTransferSessionEnd:(NSError * _Nullable)error
{
NSLog(@"BDX TransferSession end with error: %@", error);
_mFileHandle = nil;
_mFileOffset = nil;
}

- (void)handleBDXQuery:(NSNumber * _Nonnull)blockSize
blockIndex:(NSNumber * _Nonnull)blockIndex
bytesToSkip:(NSNumber * _Nonnull)bytesToSkip
completionHandler:(void (^)(NSData * _Nullable data, BOOL isEOF))completionHandler
{
NSLog(@"BDX Query received blockSize: %@, blockIndex: %@", blockSize, blockIndex);

NSError * error = nil;
auto offset = [_mFileOffset unsignedLongValue] + [bytesToSkip unsignedLongLongValue]
+ ([blockSize unsignedLongValue] * [blockIndex unsignedLongValue]);
[_mFileHandle seekToOffset:offset error:&error];
if (error != nil) {
NSLog(@"Error seeking to offset %@", @(offset));
completionHandler(nil, NO);
return;
}

NSData * data = [_mFileHandle readDataOfLength:[blockSize unsignedLongValue]];
completionHandler(data, [[_mFileHandle availableData] length] == 0);
}

- (void)SetOTAFilePath:(const char *)path
{
_mOTAFilePath = [NSString stringWithUTF8String:path];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class OTASoftwareUpdateBase : public CHIPCommandBridge {
: CHIPCommandBridge(commandName)
{
}
void SetCandidatesFromFilePath(char * _Nonnull filePath);
CHIP_ERROR SetCandidatesFromFilePath(char * _Nonnull filePath);
CHIP_ERROR SetUserConsentStatus(char * _Nonnull status);
static constexpr size_t kFilepathBufLen = 256;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ bool ParseOTAHeader(chip::OTAImageHeaderParser & parser, const char * otaFilePat
static bool ParseJsonFileAndPopulateCandidates(
const char * filepath, NSMutableArray<DeviceSoftwareVersionModel *> ** _Nonnull candidates)
{
bool ret = false;
Json::Value root;
Json::CharReaderBuilder builder;
JSONCPP_STRING errs;
Expand All @@ -70,85 +69,112 @@ static bool ParseJsonFileAndPopulateCandidates(

if (!ifs.good()) {
ChipLogError(SoftwareUpdate, "Error opening ifstream with file: \"%s\"", filepath);
return ret;
return false;
}

if (!parseFromStream(builder, ifs, &root, &errs)) {
ChipLogError(SoftwareUpdate, "Error parsing JSON from file: \"%s\"", filepath);
return ret;
return false;
}

const Json::Value devSofVerModValue = root["deviceSoftwareVersionModel"];
if (!devSofVerModValue || !devSofVerModValue.isArray()) {
ChipLogError(SoftwareUpdate, "Error: Key deviceSoftwareVersionModel not found or its value is not of type Array");
} else {
*candidates = [[NSMutableArray alloc] init];
for (auto iter : devSofVerModValue) {
DeviceSoftwareVersionModel * candidate = [[DeviceSoftwareVersionModel alloc] init];
candidate.deviceModelData.vendorId = [NSNumber numberWithUnsignedInt:iter.get("vendorId", 1).asUInt()];
candidate.deviceModelData.productId = [NSNumber numberWithUnsignedInt:iter.get("productId", 1).asUInt()];
candidate.softwareVersion = [NSNumber numberWithUnsignedLong:iter.get("softwareVersion", 10).asUInt64()];
candidate.softwareVersionString =
[NSString stringWithUTF8String:iter.get("softwareVersionString", "1.0.0").asCString()];
candidate.deviceModelData.cDVersionNumber = [NSNumber numberWithUnsignedInt:iter.get("cDVersionNumber", 0).asUInt()];
candidate.deviceModelData.softwareVersionValid = iter.get("softwareVersionValid", true).asBool() ? YES : NO;
candidate.deviceModelData.minApplicableSoftwareVersion =
[NSNumber numberWithUnsignedLong:iter.get("minApplicableSoftwareVersion", 0).asUInt64()];
candidate.deviceModelData.maxApplicableSoftwareVersion =
[NSNumber numberWithUnsignedLong:iter.get("maxApplicableSoftwareVersion", 1000).asUInt64()];
candidate.deviceModelData.otaURL = [NSString stringWithUTF8String:iter.get("otaURL", "https://test.com").asCString()];
[*candidates addObject:candidate];
ret = true;
}
return false;
}

*candidates = [[NSMutableArray alloc] init];

bool ret = false;
for (auto iter : devSofVerModValue) {
DeviceSoftwareVersionModel * candidate = [[DeviceSoftwareVersionModel alloc] init];

auto vendorId = [NSNumber numberWithUnsignedInt:iter.get("vendorId", 1).asUInt()];
auto productId = [NSNumber numberWithUnsignedInt:iter.get("productId", 1).asUInt()];
auto softwareVersion = [NSNumber numberWithUnsignedLong:iter.get("softwareVersion", 10).asUInt64()];
auto softwareVersionString = [NSString stringWithUTF8String:iter.get("softwareVersionString", "1.0.0").asCString()];
auto cDVersionNumber = [NSNumber numberWithUnsignedInt:iter.get("cDVersionNumber", 0).asUInt()];
auto softwareVersionValid = iter.get("softwareVersionValid", true).asBool() ? YES : NO;
auto minApplicableSoftwareVersion =
[NSNumber numberWithUnsignedLong:iter.get("minApplicableSoftwareVersion", 0).asUInt64()];
auto maxApplicableSoftwareVersion =
[NSNumber numberWithUnsignedLong:iter.get("maxApplicableSoftwareVersion", 1000).asUInt64()];
auto otaURL = [NSString stringWithUTF8String:iter.get("otaURL", "https://test.com").asCString()];

candidate.deviceModelData.vendorId = vendorId;
candidate.deviceModelData.productId = productId;
candidate.softwareVersion = softwareVersion;
candidate.softwareVersionString = softwareVersionString;
candidate.deviceModelData.cDVersionNumber = cDVersionNumber;
candidate.deviceModelData.softwareVersionValid = softwareVersionValid;
candidate.deviceModelData.minApplicableSoftwareVersion = minApplicableSoftwareVersion;
candidate.deviceModelData.maxApplicableSoftwareVersion = maxApplicableSoftwareVersion;
candidate.deviceModelData.otaURL = otaURL;
[*candidates addObject:candidate];
ret = true;
}

return ret;
}

CHIP_ERROR OTASoftwareUpdateSetFilePath::RunCommand()
{
SetCandidatesFromFilePath(mOTACandidatesFilePath);
auto error = SetCandidatesFromFilePath(mOTACandidatesFilePath);
SetCommandExitStatus(nil);

return CHIP_NO_ERROR;
return error;
}

CHIP_ERROR OTASoftwareUpdateSetStatus::RunCommand()
{
CHIP_ERROR error = CHIP_NO_ERROR;
error = SetUserConsentStatus(mUserConsentStatus);
auto error = SetUserConsentStatus(mUserConsentStatus);
SetCommandExitStatus(nil);

return error;
}

void OTASoftwareUpdateBase::SetCandidatesFromFilePath(char * _Nonnull filePath)
CHIP_ERROR OTASoftwareUpdateBase::SetCandidatesFromFilePath(char * _Nonnull filePath)
{
NSMutableArray<DeviceSoftwareVersionModel *> * candidates;
ChipLogDetail(chipTool, "Setting candidates from file path: %s", filePath);
ParseJsonFileAndPopulateCandidates(filePath, &candidates);
VerifyOrReturnError(ParseJsonFileAndPopulateCandidates(filePath, &candidates), CHIP_ERROR_INTERNAL);

for (DeviceSoftwareVersionModel * candidate : candidates) {
chip::OTAImageHeaderParser parser;
chip::OTAImageHeader header;
ParseOTAHeader(parser, [candidate.deviceModelData.otaURL UTF8String], header);

auto otaURL = [candidate.deviceModelData.otaURL UTF8String];
VerifyOrReturnError(ParseOTAHeader(parser, otaURL, header), CHIP_ERROR_INVALID_ARGUMENT);

ChipLogDetail(chipTool, "Validating image list candidate %s: ", [candidate.deviceModelData.otaURL UTF8String]);
VerifyOrDie([candidate.deviceModelData.vendorId unsignedIntValue] == header.mVendorId);
VerifyOrDie([candidate.deviceModelData.productId unsignedIntValue] == header.mProductId);
VerifyOrDie([candidate.softwareVersion unsignedLongValue] == header.mSoftwareVersion);
VerifyOrDie([candidate.softwareVersionString length] == header.mSoftwareVersionString.size());
VerifyOrDie(memcmp([candidate.softwareVersionString UTF8String], header.mSoftwareVersionString.data(),
header.mSoftwareVersionString.size())
== 0);

auto vendorId = [candidate.deviceModelData.vendorId unsignedIntValue];
auto productId = [candidate.deviceModelData.productId unsignedIntValue];
auto softwareVersion = [candidate.softwareVersion unsignedLongValue];
auto softwareVersionString = [candidate.softwareVersionString UTF8String];
auto softwareVersionStringLength = [candidate.softwareVersionString length];
auto minApplicableSoftwareVersion = [candidate.deviceModelData.minApplicableSoftwareVersion unsignedLongValue];
auto maxApplicableSoftwareVersion = [candidate.deviceModelData.maxApplicableSoftwareVersion unsignedLongValue];

VerifyOrReturnError(vendorId == header.mVendorId, CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(productId == header.mProductId, CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(softwareVersion == header.mSoftwareVersion, CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(softwareVersionStringLength == header.mSoftwareVersionString.size(), CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(
memcmp(softwareVersionString, header.mSoftwareVersionString.data(), header.mSoftwareVersionString.size()) == 0,
CHIP_ERROR_INVALID_ARGUMENT);

if (header.mMinApplicableVersion.HasValue()) {
VerifyOrDie(
[candidate.deviceModelData.minApplicableSoftwareVersion unsignedLongValue] == header.mMinApplicableVersion.Value());
VerifyOrReturnError(minApplicableSoftwareVersion == header.mMinApplicableVersion.Value(), CHIP_ERROR_INVALID_ARGUMENT);
}

if (header.mMaxApplicableVersion.HasValue()) {
VerifyOrDie(
[candidate.deviceModelData.maxApplicableSoftwareVersion unsignedLongValue] == header.mMaxApplicableVersion.Value());
VerifyOrReturnError(maxApplicableSoftwareVersion == header.mMaxApplicableVersion.Value(), CHIP_ERROR_INVALID_ARGUMENT);
}

parser.Clear();
}

mOTADelegate.candidates = candidates;
return CHIP_NO_ERROR;
}

CHIP_ERROR OTASoftwareUpdateBase::SetUserConsentStatus(char * _Nonnull otaSTatus)
Expand Down
22 changes: 22 additions & 0 deletions src/darwin/Framework/CHIP/MTROTAProviderDelegate.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,28 @@ NS_ASSUME_NONNULL_BEGIN
- (void)handleNotifyUpdateApplied:(MTROtaSoftwareUpdateProviderClusterNotifyUpdateAppliedParams *)params
completionHandler:(StatusCompletion)completionHandler;

/**
* Notify the delegate when a BDX Session starts
*
*/
- (void)handleBDXTransferSessionBegin:(NSString * _Nonnull)fileDesignator
offset:(NSNumber * _Nonnull)offset
completionHandler:(void (^)(NSError * error))completionHandler;

/**
* Notify the delegate when a BDX Session ends
*
*/
- (void)handleBDXTransferSessionEnd:(NSError * _Nullable)error;

/**
* Notify the delegate when a BDX Query message has been received
*
*/
- (void)handleBDXQuery:(NSNumber * _Nonnull)blockSize
blockIndex:(NSNumber * _Nonnull)blockIndex
bytesToSkip:(NSNumber * _Nonnull)bytesToSkip
completionHandler:(void (^)(NSData * _Nullable data, BOOL isEOF))completionHandler;
@end

NS_ASSUME_NONNULL_END
Loading