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

[darwin-framework-tool] Use NSURLSession instead of the NetworkFramew… #37964

Merged
merged 1 commit into from
Mar 12, 2025
Merged
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
231 changes: 54 additions & 177 deletions examples/darwin-framework-tool/commands/dcl/HTTPSRequest.mm
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
#include <system/SystemError.h>

#include <CommonCrypto/CommonDigest.h>
#include <Network/Network.h>
#include <Foundation/Foundation.h>

#include <unistd.h>

Expand All @@ -41,186 +41,69 @@
constexpr const char * kErrorSizeMismatch = "The response size does not match the expected size: ";
} // namespace

@interface NSURLSessionDelegateAllowAll : NSObject <NSURLSessionDelegate>
@end

@implementation NSURLSessionDelegateAllowAll
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * credential))completionHandler
{
completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
}
@end

namespace chip {
namespace tool {
namespace https {
namespace {
constexpr uint16_t kResponseBufferSize = 4096;
constexpr uint64_t kConnectionTimeoutSeconds = 10;
constexpr uint64_t kSendTimeoutSeconds = 10;
constexpr uint64_t kReceiveTimeoutSeconds = 10;
constexpr const char * kDispatchQueueName = "com.chip.httpsrequest";
constexpr const char * kContentContextName = "httpsrequest";
constexpr const char * kErrorSendHTTPRequest = "Failed to send HTTP request";
constexpr const char * kErrorSendHTTPRequestTimeout = "Failed to send HTTP request: timeout";
constexpr const char * kErrorReceiveHTTPResponse = "Failed to read HTTP response";
constexpr const char * kErrorReceiveHTTPResponseTimeout = "Failed to read HTTP response: timeout";
constexpr const char * kErrorConnection = "Failed to connect to: ";
constexpr const char * kErrorConnectionTimeout = "Timeout connecting to: ";
constexpr const char * kErrorConnectionUnknowState = "Unknown connection state";
constexpr const char * kErrorReceiveHTTPResponse = "Failed to read HTTP response: ";
constexpr const char * kErrorReceiveHTTPResponseTimeout = "Failed to read HTTP response (timeout): ";
constexpr const char * kErrorDigestMismatch = "The response digest does not match the expected digest";

constexpr sec_protocol_verify_t NULL_VERIFIER = ^(sec_protocol_metadata_t metadata,
sec_trust_t trust_ref,
sec_protocol_verify_complete_t complete) {
complete(true);
};

class HTTPSSessionHolder {
public:
HTTPSSessionHolder() {};

~HTTPSSessionHolder()
{
VerifyOrReturn(nullptr != mConnection);
nw_connection_cancel(mConnection);
mConnection = nullptr;
}

CHIP_ERROR Init(std::string & hostname, uint16_t port, HttpsSecurityMode securityMode)
{
__auto_type semaphore = dispatch_semaphore_create(0);
__block CHIP_ERROR result = CHIP_NO_ERROR;
__auto_type queue = dispatch_queue_create(kDispatchQueueName, DISPATCH_QUEUE_SERIAL);

__auto_type endpoint = nw_endpoint_create_host(hostname.c_str(), std::to_string(port).c_str());

nw_parameters_configure_protocol_block_t tls_options;
switch (securityMode) {
case HttpsSecurityMode::kDefault: {
tls_options = NW_PARAMETERS_DEFAULT_CONFIGURATION;
break;
}
case HttpsSecurityMode::kDisableValidation: {
tls_options = ^(nw_protocol_options_t options) {
sec_protocol_options_t sec_options = nw_tls_copy_sec_protocol_options(options);
sec_protocol_options_set_verify_block(sec_options, NULL_VERIFIER, queue);
};
break;
}
case HttpsSecurityMode::kDisableHttps: {
tls_options = NW_PARAMETERS_DISABLE_PROTOCOL;
break;
}
}

// NW_PARAMETERS_DISABLE_PROTOCOL
nw_parameters_t parameters = nw_parameters_create_secure_tcp(tls_options, NW_PARAMETERS_DEFAULT_CONFIGURATION);

mConnection = nw_connection_create(endpoint, parameters);
VerifyOrReturnError(nullptr != mConnection, CHIP_ERROR_INTERNAL);

nw_connection_set_state_changed_handler(mConnection, ^(nw_connection_state_t state, nw_error_t error) {
switch (state) {
case nw_connection_state_waiting:
case nw_connection_state_preparing:
break;
case nw_connection_state_ready:
result = CHIP_NO_ERROR;
dispatch_semaphore_signal(semaphore);
break;
case nw_connection_state_failed:
case nw_connection_state_cancelled:
ChipLogError(chipTool, "%s%s", kErrorConnection, hostname.c_str());
result = CHIP_ERROR_NOT_CONNECTED;
dispatch_semaphore_signal(semaphore);
default:
ChipLogError(chipTool, "%s", kErrorConnectionUnknowState);
break;
}
});

nw_connection_set_queue(mConnection, queue);
nw_connection_start(mConnection);

if (dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, kConnectionTimeoutSeconds * NSEC_PER_SEC)) != 0) {
ChipLogError(chipTool, "%s%s", kErrorConnectionTimeout, hostname.c_str());
return CHIP_ERROR_TIMEOUT;
}

nw_connection_set_state_changed_handler(mConnection, nullptr);
return result;
}

CHIP_ERROR SendRequest(std::string & request)
{
__auto_type semaphore = dispatch_semaphore_create(0);
__block CHIP_ERROR result = CHIP_NO_ERROR;

__auto_type context = nw_content_context_create(kContentContextName);
__auto_type data = dispatch_data_create(request.c_str(), request.size(), dispatch_get_main_queue(), DISPATCH_DATA_DESTRUCTOR_DEFAULT);
nw_connection_send(mConnection, data, context, true, ^(nw_error_t error) {
if (nullptr != error) {
ChipLogError(chipTool, "%s", kErrorSendHTTPRequest);
result = CHIP_ERROR_BAD_REQUEST;
}
dispatch_semaphore_signal(semaphore);
});

if (dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, kSendTimeoutSeconds * NSEC_PER_SEC)) != 0) {
ChipLogError(chipTool, "%s", kErrorSendHTTPRequestTimeout);
return CHIP_ERROR_TIMEOUT;
}

return result;
CHIP_ERROR SendRequest(const std::string & hostname, uint16_t port, const std::string & path, HttpsSecurityMode securityMode, std::string & response)
{
std::string urlString = (securityMode == HttpsSecurityMode::kDisableHttps ? "http://" : "https://") + hostname + ":" + std::to_string(port) + path;
__auto_type * requestURL = [NSURL URLWithString:[NSString stringWithUTF8String:urlString.c_str()]];
__auto_type * urlRequest = [NSMutableURLRequest requestWithURL:requestURL];
[urlRequest setHTTPMethod:@"GET"];
[urlRequest setValue:@"application/json" forHTTPHeaderField:@"Accept"];
[urlRequest setValue:@"close" forHTTPHeaderField:@"Connection"];

__auto_type * config = [NSURLSessionConfiguration defaultSessionConfiguration];

NSURLSession * session;
if (securityMode == HttpsSecurityMode::kDisableValidation) {
session = [NSURLSession sessionWithConfiguration:config
delegate:(id<NSURLSessionDelegate>) [[NSURLSessionDelegateAllowAll alloc] init]
delegateQueue:nil];
} else {
session = [NSURLSession sessionWithConfiguration:config];
}

CHIP_ERROR ReceiveResponse(std::string & response)
{
__auto_type semaphore = dispatch_semaphore_create(0);
__block CHIP_ERROR result = CHIP_NO_ERROR;
__block std::string receivedData;

nw_connection_receive(mConnection, 1, kResponseBufferSize, ^(dispatch_data_t content, nw_content_context_t context, bool isComplete, nw_error_t error) {
if (nullptr != error) {
ChipLogError(chipTool, "%s", kErrorReceiveHTTPResponse);
result = CHIP_ERROR_INTERNAL;
} else if (nullptr != content) {
size_t total_size = dispatch_data_get_size(content);
receivedData.reserve(total_size);

dispatch_data_apply(content, ^(dispatch_data_t region, size_t offset, const void * buffer, size_t size) {
receivedData.append(static_cast<const char *>(buffer), size);
return true;
});

result = CHIP_NO_ERROR;
}

dispatch_semaphore_signal(semaphore);
});

if (dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, kReceiveTimeoutSeconds * NSEC_PER_SEC)) != 0) {
ChipLogError(chipTool, "%s", kErrorReceiveHTTPResponseTimeout);
return CHIP_ERROR_TIMEOUT;
}

response = receivedData;
return result;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block CHIP_ERROR rv = CHIP_NO_ERROR;
__block std::string receivedData;
NSURLSessionDataTask * task = [session dataTaskWithRequest:urlRequest
completionHandler:^(NSData * data, NSURLResponse * resp, NSError * error) {
if (error) {
ChipLogError(chipTool, "%s%s", kErrorReceiveHTTPResponse, [[error localizedDescription] UTF8String]);
rv = CHIP_ERROR_BAD_REQUEST;
} else {
receivedData.assign((const char *) [data bytes], [data length]);
rv = CHIP_NO_ERROR;
}
dispatch_semaphore_signal(semaphore);
}];

[task resume];

if (dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, kSendTimeoutSeconds * NSEC_PER_SEC)) != 0) {
ChipLogError(chipTool, "%s%s", kErrorReceiveHTTPResponseTimeout, hostname.c_str());
return CHIP_ERROR_TIMEOUT;
}

private:
nw_connection_t mConnection = nullptr;
};

std::string BuildRequest(std::string & hostname, std::string & path)
{
return "GET " + path + " HTTP/1.1\r\n" + //
"Host: " + hostname + "\r\n" + //
"Accept: application/json\r\n" + //
"Connection: close\r\n\r\n"; //
}

CHIP_ERROR RemoveHeader(std::string & response)
{
// TODO: Parse the response status. Why are we doing HTTP by hand?
size_t headerEnd = response.find("\r\n\r\n");
VerifyOrReturnError(std::string::npos != headerEnd, CHIP_ERROR_INVALID_ARGUMENT);

auto body = response.substr(headerEnd + 4);
response = body;

return CHIP_NO_ERROR;
response = receivedData;
return rv;
}

CHIP_ERROR MaybeCheckResponseSize(const std::string & response, const chip::Optional<uint32_t> & optionalExpectedSize)
Expand Down Expand Up @@ -348,14 +231,8 @@ CHIP_ERROR Request(std::string hostname, uint16_t port, std::string path, Json::
}
ChipLogDetail(chipTool, "%s request to %s:%u%s", protocol, hostname.c_str(), port, path.c_str());

std::string request = BuildRequest(hostname, path);
std::string response;

HTTPSSessionHolder session;
ReturnErrorOnFailure(session.Init(hostname, port, securityMode));
ReturnErrorOnFailure(session.SendRequest(request));
ReturnErrorOnFailure(session.ReceiveResponse(response));
ReturnErrorOnFailure(RemoveHeader(response));
ReturnErrorOnFailure(SendRequest(hostname, port, path, securityMode, response));
ReturnErrorOnFailure(MaybeCheckResponseSize(response, optionalExpectedSize));
ReturnErrorOnFailure(MaybeCheckResponseDigest(response, optionalExpectedDigest));
ReturnErrorOnFailure(ConvertResponseToJSON(response, jsonResponse));
Expand Down
Loading