Is there a clock in iOS that can be used that cannot be changed by the user
Asked Answered
P

6

25

I am trying to design a system where real-time events happen and I want to synchronise them to a clock. [NSDate date] would normally be ok but the user could change this and cheat the system. All I need is a clock that I can take relative times from (a 64-bit counter for example) - I don't need absolute time (or time of day etc).

Is there such an API I can use?

EDIT: I would also like to add that this clock needs to be persistent over sessions of the application.

Pulsate answered 10/7, 2012 at 12:45 Comment(4)
The rule of thumb is: "If it's on the user's device, he can mess with it." This may not be 100% true, but it's pretty close.Spiegeleisen
Yet, there must be a low-level, persistent clock (even if unaccessible through public APIs). If not, How does the device know the time when it's turned on? If you could tap into that, you could at least get relative seconds elapsed between sessions.Vaisya
The device could get elapsed time between last shutdown and this boot, but once it's booted, it can be messed with, and by the time your app started, all bets are off.Spiegeleisen
Essentially the same problem: How can I locally detect iPhone clock advancement by a user between app runs?Cindicindie
I
8

If all you need is a clock to measure events as monotonically increasing, you could just get the current system uptime ala clock_gettime(CLOCK_MONOTONIC). If restarting the device is a problem, just save the last value used and at next launch use the last saved value as an offset. The Problem here may be that if they actually shut the device off, it won't count that time. But if you're worried about the user speeding up time, they can't do that, only slow it down.

Imaret answered 10/7, 2012 at 13:8 Comment(3)
Are there any docs for clock_gettime(CLOCK_MONOTONIC)? I found this. I tried including/importing <time.h>, but Xcode still doesn't recognize CLOCK_MONOTONIC and accuses me of an implicit definition of clock_gettime().Gumshoe
@EdMarty is there a way to find out if the device was restarted?Washday
There's no way to be absolutely certain without keeping track of the time using an external source (and even then, man-in-the-middle, blah, blah, blah). That said, you can keep track of how long the device has been running since the device booted (using an esoteric sysctl call). If it's less, than you know for certain that it has been reset since the last time you checked. Note: You don't want to use mach_absolute_time(), since that actually pauses while the device is asleep. Getting the boot time: #10331520Imaret
Z
10

The best monotonically increasing number on an iPhone is mach_absolute_time() which is a count of CPU ticks since the last reboot. If you would like it in seconds for any reason, it is most easily fetched with CACurrentMediaTime().

Using this to create an "always-increment" is pretty easy. On first launch, store the current value wherever you like. When you exit, and periodically, save the offset from that value. When you restart, check the current value; if it is less than your previous base value, replace your base value (there's been a reboot).

All of this of course can be cleared by removing your app unless you store something on the server.

Note that this cannot be used as a strong security measure. There is no way to prevent an authorized user from forging requests. So depending on what you mean by "cheat the system," it may not be a solvable problem. The best you can do is have constant diligence looking for hacks and dealing with them.

Zachery answered 10/7, 2012 at 13:5 Comment(2)
just to note, checking whether the value is less doesn't say for certain there's been a reboot or not. User could've rebooted then waited more than the last recorded tick.Yost
The difficulty with CACurrentMediaTime() and mach_absolute_time() is that they stop running when the device is not active.When you lock the device and put it in your pocket, they keep running for a couple minutes and then pause when the device goes to standby mode.Impressment
I
8

If all you need is a clock to measure events as monotonically increasing, you could just get the current system uptime ala clock_gettime(CLOCK_MONOTONIC). If restarting the device is a problem, just save the last value used and at next launch use the last saved value as an offset. The Problem here may be that if they actually shut the device off, it won't count that time. But if you're worried about the user speeding up time, they can't do that, only slow it down.

Imaret answered 10/7, 2012 at 13:8 Comment(3)
Are there any docs for clock_gettime(CLOCK_MONOTONIC)? I found this. I tried including/importing <time.h>, but Xcode still doesn't recognize CLOCK_MONOTONIC and accuses me of an implicit definition of clock_gettime().Gumshoe
@EdMarty is there a way to find out if the device was restarted?Washday
There's no way to be absolutely certain without keeping track of the time using an external source (and even then, man-in-the-middle, blah, blah, blah). That said, you can keep track of how long the device has been running since the device booted (using an esoteric sysctl call). If it's less, than you know for certain that it has been reset since the last time you checked. Note: You don't want to use mach_absolute_time(), since that actually pauses while the device is asleep. Getting the boot time: #10331520Imaret
P
6

I am using this in our app

+ (NSDate *) getAWSDate
{
    NSDate *today = nil; 
   NSString *dateString = nil;

    NSString *awsURL = @"http://s3.amazonaws.com";
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:awsURL]];  
    [request setHTTPMethod:@"HEAD"];  

    NSHTTPURLResponse *response;  
    [NSURLConnection sendSynchronousRequest:request returningResponse:&response error: NULL];  

    if ([response respondsToSelector:@selector(allHeaderFields)]) {  
        dateString = [[response allHeaderFields] objectForKey:@"Date"];
        dateString = [dateString stringByReplacingOccurrencesOfString:@"GMT" withString:@"+0000"];
        NSDateFormatter *df = [[NSDateFormatter alloc] init];  
    df.dateFormat = @"EEE, dd MMM yyyy HH:mm:ss z";
    df.locale = [[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"] autorelease];  
    df.timeZone = [NSTimeZone timeZoneWithAbbreviation:@"GMT"];  
    today = [df dateFromString:dateString];
    [df release];    
    }           
    return today;
}
Polygynist answered 10/7, 2012 at 13:46 Comment(3)
Thanks for the code. I will use it if I decide to use a time server. At the moment I am trying to avoid network connectivity.Pulsate
Weird thing. I'm getting "(null)" on the iPad Air with this code. With an older iPad (3rd gen) and simulator, it works! Both running iOS 8.0.2. Any ideas?Forceps
How did that cope when S3 went down this week?Kerril
A
5

I think mach_absolute_time() may be what you'll want. There is quite a bit of information here: http://developer.apple.com/library/mac/#qa/qa1398/_index.html

It is basically a counter of the number of ticks since the device started. This will be reset on reboot but other than that it is a monotonically increasing counter.

You can keep the last value of the counter around and deal with it if the counter has decreased since the last time (the device was restarted). In this case you can add the last known value to the current value to get a lower bound of the time that has elapsed, all you'd lose is the time between the last session and the device shut down, you would keep the time between the reboot and the next session though.

Allister answered 10/7, 2012 at 13:3 Comment(3)
He needs a clock persistent across sessions, so that's not going to help.Spiegeleisen
mach_absolute_time() is only reset on reboot of the device.Allister
The counter may not have decreased if you imagine the scenario where the user resets the iphone, plays the game and shuts it down 5 minutes later, then resets the iphone again and plays the game after 5 minutes. But it seems to be the most reliable method - I would just lose time but not speed up.Pulsate
I
3

What I know, that there is no any other method to get correct time. I would use NTP.

Network Time Protocol (NTP) is a networking protocol for synchronizing the clocks of computer systems over packet-switched, variable-latency data networks. More: http://en.wikipedia.org/wiki/Network_Time_Protocol

In one project I'm going to use NTP (tomorrow i quess), but I already did some research:

iOS library: https://github.com/jbenet/ios-ntp - Author notes: The implementation is not a rigorous as described in those RFCs since the goal was to improve time accuracy to with in a second, not to fractions of milliseconds.

For non-commercial project look on: www.packages.ubuntu.com/source/lucid/ntp

C library: www.hillstone-software.com/hs_ntp_details.htm

Documentation: www.nist.gov/pml/div688/grp40/its.cfm

Irmgard answered 10/7, 2012 at 12:58 Comment(0)
E
1

The only idea that I can think of is to use a NTP server to obtain the clock, but that would require network connectivity.

Eolithic answered 10/7, 2012 at 12:54 Comment(4)
Yes, I was hoping to avoid that.Pulsate
And a user determined enough to fake clock time in order to cheat could also fake your NTP server.Frottage
Sure they could, but that is a significant jump in sophistication over messing with the date/time settings in the user control panels. I have friends who are not the least bit technical who have figured out they can cheat games that way (or maybe they read about it online, whatever) but it's really easy to do. These same people would have no idea what NTP was much less have the motivation or ability to spoof a server, even if it is easy for a seasoned hacker.Gramme
@Suboptimus, good point. Still I wonder what the app's to do when there's no network connection. Should it stop working, or trust the user-defined time?Gumshoe

© 2022 - 2024 — McMap. All rights reserved.