1/* 2 * Copyright (C) 2009-2012 by Matthias Ringwald 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 3. Neither the name of the copyright holders nor the names of 14 * contributors may be used to endorse or promote products derived 15 * from this software without specific prior written permission. 16 * 4. Any redistribution, use, or modification is done solely for 17 * personal benefit and not for any commercial purpose or for 18 * monetary gain. 19 * 20 * THIS SOFTWARE IS PROVIDED BY MATTHIAS RINGWALD AND CONTRIBUTORS 21 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 23 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MATTHIAS 24 * RINGWALD OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 25 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 26 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS 27 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 28 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 29 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 30 * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 31 * SUCH DAMAGE. 32 * 33 * Please inquire about commercial licensing options at [email protected] 34 * 35 */ 36#include "rfcomm_service_db.h" 37#include "btstack_debug.h" 38 39#import <Foundation/Foundation.h> 40 41#define BTdaemonID "ch.ringwald.btdaemon" 42#define BTDaemonPrefsPath "Library/Preferences/ch.ringwald.btdaemon.plist" 43 44#define MAX_RFCOMM_CHANNEL_NR 30 45 46#define RFCOMM_SERVICES_KEY "rfcommServices" 47#define PREFS_CHANNEL @"channel" 48#define PREFS_LAST_USED @"lastUsed" 49 50static NSMutableDictionary *rfcomm_services = nil; 51 52// Device info 53static void db_open(void){ 54 55 if (rfcomm_services) return; 56 57 NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; 58 59 NSDictionary * dict = (NSDictionary*) CFPreferencesCopyAppValue(CFSTR(RFCOMM_SERVICES_KEY), CFSTR(BTdaemonID)); 60 rfcomm_services = [[NSMutableDictionary alloc] initWithCapacity:([dict count]+5)]; 61 62 // copy entries 63 for (id key in dict) { 64 NSDictionary *value = [dict objectForKey:key]; 65 NSMutableDictionary *serviceEntry = [NSMutableDictionary dictionaryWithCapacity:[value count]]; 66 [serviceEntry addEntriesFromDictionary:value]; 67 [rfcomm_services setObject:serviceEntry forKey:key]; 68 } 69 [pool release]; 70} 71 72static void db_synchronize(void){ 73 // 3 different ways 74 75 // Core Foundation 76 CFPreferencesSetValue(CFSTR(RFCOMM_SERVICES_KEY), (CFPropertyListRef) rfcomm_services, CFSTR(BTdaemonID), kCFPreferencesCurrentUser, kCFPreferencesCurrentHost); 77 CFPreferencesSynchronize(CFSTR(BTdaemonID), kCFPreferencesCurrentUser, kCFPreferencesCurrentHost); 78 79 // NSUserDefaults didn't work 80 // 81 // NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; 82 // [defaults setPersistentDomain:remote_devices forName:BTdaemonID]; 83 // [defaults synchronize]; 84} 85 86// MARK: PERSISTENT RFCOMM CHANNEL ALLOCATION 87 88static int firstFreeChannelNr(void){ 89 BOOL channelUsed[MAX_RFCOMM_CHANNEL_NR+1]; 90 int i; 91 for (i=0; i<=MAX_RFCOMM_CHANNEL_NR ; i++) channelUsed[i] = NO; 92 channelUsed[0] = YES; 93 channelUsed[1] = YES; // preserve channel #1 for testing 94 for (NSDictionary * serviceEntry in [rfcomm_services allValues]){ 95 int channel = [(NSNumber *) [serviceEntry objectForKey:PREFS_CHANNEL] intValue]; 96 channelUsed[channel] = YES; 97 } 98 for (i=0;i<=MAX_RFCOMM_CHANNEL_NR;i++) { 99 if (channelUsed[i] == NO) return i; 100 } 101 return -1; 102} 103 104static void deleteLeastUsed(void){ 105 NSString * leastUsedName = nil; 106 NSDate * leastUsedDate = nil; 107 for (NSString * serviceName in [rfcomm_services allKeys]){ 108 NSDictionary *service = [rfcomm_services objectForKey:serviceName]; 109 NSDate *serviceDate = [service objectForKey:PREFS_LAST_USED]; 110 if (leastUsedName == nil || [leastUsedDate compare:serviceDate] == NSOrderedDescending) { 111 leastUsedName = serviceName; 112 leastUsedDate = serviceDate; 113 continue; 114 } 115 } 116 if (leastUsedName){ 117 // NSLog(@"removing %@", leastUsedName); 118 [rfcomm_services removeObjectForKey:leastUsedName]; 119 } 120} 121 122static void addService(NSString * serviceName, int channel){ 123 NSMutableDictionary * serviceEntry = [NSMutableDictionary dictionaryWithCapacity:2]; 124 [serviceEntry setObject:[NSNumber numberWithInt:channel] forKey:PREFS_CHANNEL]; 125 [serviceEntry setObject:[NSDate date] forKey:PREFS_LAST_USED]; 126 [rfcomm_services setObject:serviceEntry forKey:serviceName]; 127} 128 129uint8_t rfcomm_service_db_channel_for_service(const char *serviceName){ 130 131 db_open(); 132 133 NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; 134 135 NSLog(@"persistent_rfcomm_channel for %s", serviceName); 136 137 // find existing entry 138 NSString *serviceString = [NSString stringWithUTF8String:serviceName]; 139 NSMutableDictionary *serviceEntry = [rfcomm_services objectForKey:serviceString]; 140 if (serviceEntry){ 141 // update timestamp 142 [serviceEntry setObject:[NSDate date] forKey:PREFS_LAST_USED]; 143 144 db_synchronize(); 145 146 return [(NSNumber *) [serviceEntry objectForKey:PREFS_CHANNEL] intValue]; 147 } 148 // free channel exist? 149 int channel = firstFreeChannelNr(); 150 if (channel < 0){ 151 // free channel 152 deleteLeastUsed(); 153 channel = firstFreeChannelNr(); 154 } 155 addService(serviceString, channel); 156 157 db_synchronize(); 158 159 [pool release]; 160 161 return channel; 162} 163