/* * Copyright (C) 2019 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ #import "config.h" #import "MockNfcService.h" #if ENABLE(WEB_AUTHN) #import "CtapNfcDriver.h" #import "NearFieldSPI.h" #import "NfcConnection.h" #import <WebCore/FidoConstants.h> #import <wtf/BlockPtr.h> #import <wtf/RetainPtr.h> #import <wtf/RunLoop.h> #import <wtf/Vector.h> #import "NearFieldSoftLink.h" namespace { uint8_t tagID1[] = { 0x01 }; uint8_t tagID2[] = { 0x02 }; } #if HAVE(NEAR_FIELD) @interface WKMockNFTag : NSObject <NFTag> - (instancetype)initWithType:(NFTagType)type; - (instancetype)initWithType:(NFTagType)type tagID:(NSData *)tagID; @end @implementation WKMockNFTag { NFTagType _type; RetainPtr<NSData> _tagID; } @synthesize technology=_technology; @synthesize AppData=_AppData; @synthesize UID=_UID; @synthesize ndefAvailability=_ndefAvailability; @synthesize ndefMessageSize=_ndefMessageSize; @synthesize ndefContainerSize=_ndefContainerSize; @synthesize tagA=_tagA; @synthesize tagB=_tagB; @synthesize tagF=_tagF; - (NFTagType)type { return _type; } - (NSData *)tagID { return _tagID.get(); } - (instancetype)initWithNFTag:(id<NFTag>)tag { if ((self = [super init])) { _type = tag.type; _tagID = tag.tagID; } return self; } - (void)dealloc { [_AppData release]; _AppData = nil; [_UID release]; _UID = nil; [super dealloc]; } - (NSString*)description { return nil; } - (BOOL)isEqualToNFTag:(id<NFTag>)tag { return NO; } - (instancetype)initWithType:(NFTagType)type { return [self initWithType:type tagID:adoptNS([[NSData alloc] initWithBytes:tagID1 length:sizeof(tagID1)]).get()]; } - (instancetype)initWithType:(NFTagType)type tagID:(NSData *)tagID { if ((self = [super init])) { _type = type; _tagID = tagID; } return self; } @end #endif // HAVE(NEAR_FIELD) namespace WebKit { using namespace fido; using Mock = WebCore::MockWebAuthenticationConfiguration; #if HAVE(NEAR_FIELD) namespace { static id<NFReaderSessionDelegate> globalNFReaderSessionDelegate; static MockNfcService* globalNfcService; static void NFReaderSessionSetDelegate(id, SEL, id<NFReaderSessionDelegate> delegate) { globalNFReaderSessionDelegate = delegate; } static BOOL NFReaderSessionConnectTagFail(id, SEL, NFTag *) { return NO; } static BOOL NFReaderSessionConnectTag(id, SEL, NFTag *) { return YES; } static BOOL NFReaderSessionStopPolling(id, SEL) { if (!globalNfcService) return NO; globalNfcService->receiveStopPolling(); return YES; } static BOOL NFReaderSessionStartPollingWithError(id, SEL, NSError **) { if (!globalNfcService) return NO; globalNfcService->receiveStartPolling(); return YES; } static NSData* NFReaderSessionTransceive(id, SEL, NSData *) { if (!globalNfcService) return nil; return globalNfcService->transceive(); } } // namespace #endif // HAVE(NEAR_FIELD) MockNfcService::MockNfcService(Observer& observer, const WebCore::MockWebAuthenticationConfiguration& configuration) : NfcService(observer) , m_configuration(configuration) { } NSData* MockNfcService::transceive() { if (m_configuration.nfc->payloadBase64.isEmpty()) return nil; auto result = [[NSData alloc] initWithBase64EncodedString:m_configuration.nfc->payloadBase64[0] options:NSDataBase64DecodingIgnoreUnknownCharacters]; m_configuration.nfc->payloadBase64.remove(0); return [result autorelease]; } void MockNfcService::receiveStopPolling() { // For purpose of restart polling. m_configuration.nfc->multiplePhysicalTags = false; } void MockNfcService::receiveStartPolling() { RunLoop::main().dispatch([weakThis = makeWeakPtr(*this)] { if (!weakThis) return; weakThis->detectTags(); }); } void MockNfcService::platformStartDiscovery() { #if HAVE(NEAR_FIELD) if (!!m_configuration.nfc) { globalNfcService = this; Method methodToSwizzle1 = class_getInstanceMethod(getNFReaderSessionClass(), @selector(setDelegate:)); method_setImplementation(methodToSwizzle1, (IMP)NFReaderSessionSetDelegate); Method methodToSwizzle2 = class_getInstanceMethod(getNFReaderSessionClass(), @selector(connectTag:)); if (m_configuration.nfc->error == Mock::NfcError::NoConnections) method_setImplementation(methodToSwizzle2, (IMP)NFReaderSessionConnectTagFail); else method_setImplementation(methodToSwizzle2, (IMP)NFReaderSessionConnectTag); Method methodToSwizzle3 = class_getInstanceMethod(getNFReaderSessionClass(), @selector(transceive:)); method_setImplementation(methodToSwizzle3, (IMP)NFReaderSessionTransceive); Method methodToSwizzle4 = class_getInstanceMethod(getNFReaderSessionClass(), @selector(stopPolling)); method_setImplementation(methodToSwizzle4, (IMP)NFReaderSessionStopPolling); Method methodToSwizzle5 = class_getInstanceMethod(getNFReaderSessionClass(), @selector(startPollingWithError:)); method_setImplementation(methodToSwizzle5, (IMP)NFReaderSessionStartPollingWithError); auto readerSession = adoptNS([allocNFReaderSessionInstance() init]); setConnection(NfcConnection::create(readerSession.get(), *this)); } LOG_ERROR("No nfc authenticators is available."); #endif // HAVE(NEAR_FIELD) } void MockNfcService::detectTags() const { #if HAVE(NEAR_FIELD) if (m_configuration.nfc->error == Mock::NfcError::NoTags) return; auto callback = makeBlockPtr([configuration = m_configuration] { auto tags = adoptNS([[NSMutableArray alloc] init]); if (configuration.nfc->error == Mock::NfcError::WrongTagType || configuration.nfc->multipleTags) [tags addObject:adoptNS([[WKMockNFTag alloc] initWithType:NFTagTypeUnknown]).get()]; else [tags addObject:adoptNS([[WKMockNFTag alloc] initWithType:NFTagTypeGeneric4A]).get()]; if (configuration.nfc->multipleTags) [tags addObject:adoptNS([[WKMockNFTag alloc] initWithType:NFTagTypeGeneric4A]).get()]; if (configuration.nfc->multiplePhysicalTags) [tags addObject:adoptNS([[WKMockNFTag alloc] initWithType:NFTagTypeGeneric4A tagID:adoptNS([[NSData alloc] initWithBytes:tagID2 length:sizeof(tagID2)]).get()]).get()]; [globalNFReaderSessionDelegate readerSession:nil didDetectTags:tags.get()]; }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), callback.get()); #endif // HAVE(NEAR_FIELD) } } // namespace WebKit #endif // ENABLE(WEB_AUTHN)