blob: 00bd61d30bb99584bb29ea71837eb46d8740ec2a [file] [log] [blame]
/**
*
* Copyright (c) 2020 Project CHIP Authors
*
* 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.
*/
#import "MTRError.h"
#import "MTRError_Internal.h"
#import <app/MessageDef/StatusIB.h>
#import <app/util/af-enums.h>
#import <app/util/error-mapping.h>
#import <inet/InetError.h>
#import <lib/support/TypeTraits.h>
NSString * const MTRErrorDomain = @"MTRErrorDomain";
NSString * const MTRInteractionErrorDomain = @"MTRInteractionErrorDomain";
// Class for holding on to a CHIP_ERROR that we can use as the value
// in a dictionary.
@interface MTRErrorHolder : NSObject
@property (nonatomic, readonly) CHIP_ERROR error;
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
- (instancetype)initWithError:(CHIP_ERROR)error;
@end
@implementation MTRError
+ (NSError *)errorForCHIPErrorCode:(CHIP_ERROR)errorCode
{
if (errorCode == CHIP_NO_ERROR) {
return nil;
}
if (errorCode.IsIMStatus()) {
chip::app::StatusIB status(errorCode);
return [MTRError errorForIMStatus:status];
}
NSMutableDictionary * userInfo = [[NSMutableDictionary alloc] init];
MTRErrorCode code = MTRErrorCodeGeneralError;
if (errorCode == CHIP_ERROR_INVALID_STRING_LENGTH) {
code = MTRErrorCodeInvalidStringLength;
[userInfo addEntriesFromDictionary:@{ NSLocalizedDescriptionKey : NSLocalizedString(@"A list length is invalid.", nil) }];
} else if (errorCode == CHIP_ERROR_INVALID_INTEGER_VALUE) {
code = MTRErrorCodeInvalidIntegerValue;
[userInfo addEntriesFromDictionary:@{ NSLocalizedDescriptionKey : NSLocalizedString(@"Unexpected integer value.", nil) }];
} else if (errorCode == CHIP_ERROR_INVALID_ARGUMENT) {
code = MTRErrorCodeInvalidArgument;
[userInfo addEntriesFromDictionary:@{ NSLocalizedDescriptionKey : NSLocalizedString(@"An argument is invalid.", nil) }];
} else if (errorCode == CHIP_ERROR_INVALID_MESSAGE_LENGTH) {
code = MTRErrorCodeInvalidMessageLength;
[userInfo
addEntriesFromDictionary:@{ NSLocalizedDescriptionKey : NSLocalizedString(@"A message length is invalid.", nil) }];
} else if (errorCode == CHIP_ERROR_INCORRECT_STATE) {
code = MTRErrorCodeInvalidState;
[userInfo addEntriesFromDictionary:@{ NSLocalizedDescriptionKey : NSLocalizedString(@"Invalid object state.", nil) }];
} else if (errorCode == CHIP_ERROR_INTEGRITY_CHECK_FAILED) {
code = MTRErrorCodeIntegrityCheckFailed;
[userInfo addEntriesFromDictionary:@{ NSLocalizedDescriptionKey : NSLocalizedString(@"Integrity check failed.", nil) }];
} else if (errorCode == CHIP_ERROR_TIMEOUT) {
code = MTRErrorCodeTimeout;
[userInfo addEntriesFromDictionary:@{ NSLocalizedDescriptionKey : NSLocalizedString(@"Transaction timed out.", nil) }];
} else if (errorCode == CHIP_ERROR_BUFFER_TOO_SMALL) {
code = MTRErrorCodeBufferTooSmall;
[userInfo addEntriesFromDictionary:@{ NSLocalizedDescriptionKey : NSLocalizedString(@"A buffer is too small.", nil) }];
} else if (errorCode == CHIP_ERROR_FABRIC_EXISTS) {
code = MTRErrorCodeFabricExists;
[userInfo addEntriesFromDictionary:@{
NSLocalizedDescriptionKey : NSLocalizedString(@"The device is already a member of this fabric.", nil)
}];
} else {
code = MTRErrorCodeGeneralError;
[userInfo addEntriesFromDictionary:@{
NSLocalizedDescriptionKey :
[NSString stringWithFormat:NSLocalizedString(@"Undefined error:%u.", nil), errorCode.AsInteger()],
@"errorCode" : @(errorCode.AsInteger()),
}];
}
userInfo[@"underlyingError"] = [[MTRErrorHolder alloc] initWithError:errorCode];
return [NSError errorWithDomain:MTRErrorDomain code:code userInfo:userInfo];
;
}
+ (NSError *)errorForIMStatus:(const chip::app::StatusIB &)status
{
if (status.IsSuccess()) {
return nil;
}
NSString * description;
using chip::Protocols::InteractionModel::Status;
switch (status.mStatus) {
case Status::Failure:
default: {
description = NSLocalizedString(@"Operation was not successful.", nil);
break;
}
case Status::InvalidSubscription: {
description = NSLocalizedString(@"Subscription ID is not active.", nil);
break;
}
case Status::UnsupportedAccess: {
description = NSLocalizedString(@"The sender of the action or command does not have authorization or access.", nil);
break;
}
case Status::UnsupportedEndpoint: {
description = NSLocalizedString(@"The endpoint indicated is unsupported on the node.", nil);
break;
}
case Status::InvalidAction: {
description = NSLocalizedString(
@"The action is malformed, has missing fields, or fields with invalid values. Action not carried out.", nil);
break;
}
case Status::UnsupportedCommand: {
description = NSLocalizedString(
@"The specified action or command indicated is not supported on the device. Command or action not carried out.", nil);
break;
}
case Status::InvalidCommand: {
description = NSLocalizedString(
@"The cluster command is malformed, has missing fields, or fields with invalid values. Command not carried out.", nil);
break;
}
case Status::UnsupportedAttribute: {
description
= NSLocalizedString(@"The specified attribute or attribute data field or entry does not exist on the device.", nil);
break;
}
case Status::ConstraintError: {
description = NSLocalizedString(@"Out of range error or set to a reserved value.", nil);
break;
}
case Status::UnsupportedWrite: {
description = NSLocalizedString(@"Attempt to write a read-only attribute.", nil);
break;
}
case Status::ResourceExhausted: {
description = NSLocalizedString(@"An action or operation failed due to insufficient available resources. ", nil);
break;
}
case Status::NotFound: {
description = NSLocalizedString(@"The indicated data field or entry could not be found.", nil);
break;
}
case Status::UnreportableAttribute: {
description = NSLocalizedString(@"Reports cannot be issued for this attribute.", nil);
break;
}
case Status::InvalidDataType: {
description = NSLocalizedString(
@"The data type indicated is undefined or invalid for the indicated data field. Command or action not carried out.",
nil);
break;
}
case Status::UnsupportedRead: {
description = NSLocalizedString(@"Attempt to read a write-only attribute.", nil);
break;
}
case Status::DataVersionMismatch: {
description = NSLocalizedString(@"Cluster instance data version did not match request path.", nil);
break;
}
case Status::Timeout: {
description = NSLocalizedString(@"The transaction was aborted due to time being exceeded.", nil);
break;
}
case Status::Busy: {
description = NSLocalizedString(
@"The receiver is busy processing another action that prevents the execution of the incoming action.", nil);
break;
}
case Status::UnsupportedCluster: {
description = NSLocalizedString(@"The cluster indicated is not supported", nil);
break;
}
// Gap in values is intentional.
case Status::NoUpstreamSubscription: {
description = NSLocalizedString(@"Proxy does not have a subscription to the source.", nil);
break;
}
case Status::NeedsTimedInteraction: {
description = NSLocalizedString(@"An Untimed Write or Untimed Invoke interaction was used for an attribute or command that "
@"requires a Timed Write or Timed Invoke.",
nil);
break;
}
case Status::UnsupportedEvent: {
description = NSLocalizedString(@"The event indicated is unsupported on the cluster.", nil);
break;
}
}
NSMutableDictionary * userInfo = [[NSMutableDictionary alloc] init];
userInfo[NSLocalizedDescriptionKey] = description;
if (status.mClusterStatus.HasValue()) {
userInfo[@"clusterStatus"] = @(status.mClusterStatus.Value());
}
return [NSError errorWithDomain:MTRInteractionErrorDomain code:chip::to_underlying(status.mStatus) userInfo:userInfo];
}
+ (CHIP_ERROR)errorToCHIPErrorCode:(NSError * _Nullable)error
{
if (error == nil) {
return CHIP_NO_ERROR;
}
if (error.domain == MTRInteractionErrorDomain) {
chip::app::StatusIB status(static_cast<chip::Protocols::InteractionModel::Status>(error.code));
if (error.userInfo != nil && error.userInfo[@"clusterStatus"] != nil) {
status.mClusterStatus.Emplace([error.userInfo[@"clusterStatus"] unsignedCharValue]);
}
return status.ToChipError();
}
if (error.domain != MTRErrorDomain) {
return CHIP_ERROR_INTERNAL;
}
if (error.userInfo != nil) {
id underlyingError = error.userInfo[@"underlyingError"];
if (underlyingError != nil && [underlyingError isKindOfClass:[MTRErrorHolder class]]) {
return ((MTRErrorHolder *) underlyingError).error;
}
}
chip::ChipError::StorageType code;
switch (error.code) {
case MTRErrorCodeInvalidStringLength:
code = CHIP_ERROR_INVALID_STRING_LENGTH.AsInteger();
break;
case MTRErrorCodeInvalidIntegerValue:
code = CHIP_ERROR_INVALID_INTEGER_VALUE.AsInteger();
break;
case MTRErrorCodeInvalidArgument:
code = CHIP_ERROR_INVALID_ARGUMENT.AsInteger();
break;
case MTRErrorCodeInvalidMessageLength:
code = CHIP_ERROR_INVALID_MESSAGE_LENGTH.AsInteger();
break;
case MTRErrorCodeInvalidState:
code = CHIP_ERROR_INCORRECT_STATE.AsInteger();
break;
case MTRErrorCodeIntegrityCheckFailed:
code = CHIP_ERROR_INTEGRITY_CHECK_FAILED.AsInteger();
break;
case MTRErrorCodeTimeout:
code = CHIP_ERROR_TIMEOUT.AsInteger();
break;
case MTRErrorCodeBufferTooSmall:
code = CHIP_ERROR_BUFFER_TOO_SMALL.AsInteger();
break;
case MTRErrorCodeGeneralError: {
if (error.userInfo != nil && error.userInfo[@"errorCode"] != nil) {
code = static_cast<decltype(code)>([error.userInfo[@"errorCode"] unsignedLongValue]);
break;
}
// Weird error we did not create. Fall through.
default:
code = CHIP_ERROR_INTERNAL.AsInteger();
break;
}
}
return chip::ChipError(code);
}
@end
@implementation MTRErrorHolder
- (instancetype)initWithError:(CHIP_ERROR)error
{
if (!(self = [super init])) {
return nil;
}
_error = error;
return self;
}
@end