IP Camera Data Privacy

I’ve had a few goes at setting up the Motion package on my Raspberry Pi, but I’ve finally abandoned it. In its stead, we recently bought an IP camera manufactured by a company called Annke which, at the time of writing, is among the best selling surveillance cameras on Amazon. It’s a nice piece of kit but given that it only cost £40 and has a lot of moving parts, it’s not one that I expect to survive down the years.

I thought it would be interesting to proxy the traffic on my phone to see what’s happening.

My iPhone isn’t jailbroken, which would have been a showstopper if the server that the app is talking to was using certificate pinning. It’s not. The first call is over plain HTTP. I’m not going to copy it here, because some of the payload is decodes to binary, and there’s a possibility that I might be broadcasting my own password. Doh!

So the first call is a GET to a server running on Amazon’s cloud service, listening on port 7080. I checked the IANA registry: while there is something assigned to that ‘officially’ [some identity management software called empowerid] I think it’s a coincidence, and it’s probably just a web server of some kind running on a non standard port. The response doesn’t report back the server software name. Included in the GET parameters, there are a series of comma and slash [url encoded] separated parameters which are base64 encoded. These decode into binary, and could be anything. Included among the readable parameters is my username.

The response back is a block of JSON, referencing different URLs on the same server, which a geolocation service reliably informs me is in a data centre operated by an outfit called OVH in Roubaix, northern France. The URLs have helpful prefixes: ‘signal’, ‘debug’, ‘ping’, and ‘ntp’ among them. Not all of the URLs are referring to web traffic: there’s one reference to telnet, which is a blast from the past, and another called ‘binnet://’ which is sufficiently non standard that Google keeps insisting on telling me about ‘bonnets’ :). That final ‘binnet’ URL refers back to the original AWS server.

The app then does a second GET to a server, this time in Tampa, Florida. I’m not going to break this one down in any sort of detail because the server refuses the connection, so it can’t be too important!

Next, the app opens a TLS connection to the French server, and does 10 separate GETs. The 3rd of these includes my password, which I registered on first run.

Here’s what I imagine is happening: the camera is going to be polling the server in France cyclically, asking the question, ‘do I need to transmit to you yet?’ When I connect to the same server via the phone app, the answer comes back as a ‘yes’. The camera starts to transmit, and the server then relays the stream back to my app. WireShark should be able to give me some pointers, but I’m running out of time to look at it today. If I find anything interesting or contradictory when I do get round to looking at it, I’ll do a separate post on it.

By the way, the app seems to be using JavaScript to instantiate the video stream in HTML5. Apple have a video from the WWDC in 2013 on exactly this topic.

So in summary, it looks like the video stream of our back garden / the cat / my wife and I occasionally waving at the camera ends up in France, with the server there ‘joining’ the connection from the camera to the app.

The end state with my Raspberry Pi was, well, while not necessary secure in and of its own right, certainly wasn’t nearly as ‘mediated’, shall we say. I set up our broadband router with a port forwarding rule and configuration for a DDNS service. That meant we could connect from our phones to the web server integrated into the Motion package. That was all over vanilla HTTP – hey, at least I set a 401 password!

While I can’t do anything about how the camera operates – a trade-off I’m willing to make based on pure utility – I’m probably going to take a look at partitioning off the network with a proper firewall behind the broadband router [which can be configured to operate as a modem only]. I’ll put all of the ‘less trusted’ devices on their own little segment. Pfsense seems to be the way to go.

Cheerio iOS, Hello Android. For now…

I’ve decided to let my Apple Developer subscription lapse in a couple of weeks. I wavered over the decision when I realised that my apps would be disappearing off the App Store. Before they go, I did a little digging on iTunes Connect to see how they’ve done. Badly: a combined total of 2.15k downloads since I released WeighMe in December 2012.

Despite the paltry totals – oh, and let’s not forget my $24 in sales! – I was impressed to see that the apps have been downloaded in a total of 77 countries. So, for my solitary users in Armenia, Ghana, Paraguay and Uzbekistan: thanks for the downloads, folks!

I want to learn Java so my next handset, when I upgrade some time around the end of the year, will be running Android. If the iPhone 7 is released without an audio jack [as is heavily rumoured], I’d jump ship regardless. I have enough chargers and whacky adapters to last me a lifetime without adding more for headphones.

All of which is a lovely story, but when I tried Android about three years ago, I hated it. And if Apple do buck the trend with the audio jacks, the rest of the manufacturers will probably follow suit. So I may quietly return to the fold and pretend that I’ve never been away :).

How does the Twitter app know my location?

[Edit: the answer to this is actually very simple. I’d been focusing so much on the various mobile frameworks that I forgot about the obvious one: it’s my source IP. There are services out there that take your IP address and, based on service provider information, turn it into a location. The same mechanism is going to be available whether you are using your browser – and where you will see commonly see location specific ads – or if you’re using an app.]

OK, maybe not the most thrilling of titles, but I’ve been interested in location services pretty much since I started developing for iOS, and something happened this morning while using the Twitter app that piqued my interest.

I followed a link to a site, which popped up an embedded browser – so a UIWebView. At the bottom of the article, amid the usual rubbish, was an link to another site telling me how a millionaire in my named home town was earning an unlikely amount working from home.

The thing is that in my Location Services settings, I have the Twitter app set to ‘never’. There are a couple of other possible candidates. I was wondering if the UIWebView was inheriting some settings from Safari, but I have the Safari Websites setting on ‘never’. Also, the call to start the Location Manager happens in the calling app – so the corresponding privacy setting should be in the context of that app.

Looking at the other System Settings under Locations Services, there’s one other candidate: iAds. I’ve not used this in my own apps, but I’ve just checked: they are views embedded in native apps, not in UIWebViews. And anyway, I have the setting disabled.

There are a few other System Settings that I have set to ‘on’, such as WiFi Networking and Location-Based alerts, none of which should have anything to do with the Twitter app.

So what’s going on? Wild conspiracy theories aside, I can’t understand how the app could be getting my location when the primary privacy setting for the app is ‘never’.

PDF to Text Conversion

This project combines a couple of well trodden paths: PDF to text conversion, and then running an app in the background with audio playback. It introduced some new concepts to me and, based on a trawl of the usual resources for problem-solving, at least a couple of issues that are worth recording.

The TL;DR version is that PDF parsing gets into pretty complicated territory – unless you happen to know C well. There are open source libraries out there, but they didn’t hit the mark for me. I’ve implemented my own parser which is crude, but works. More or less!

Any PDF manipulation in iOS is going to depend on the Quartz 2D library somewhere along the line. Whether you call it directly or rely on another API that wraps it is a matter of choice. I looked at a couple. PDFKitten has a lot of functionality and seems to be by far the most sophisticated open source library but the documentation didn’t cover the simple requirement that I had – text extraction. There’s another one called pdfiphone that I struggled to get to work, and which epitomises the main challenge that I had with this project: I have only a rudimentary knowledge of C, which is what you’re getting into with Quartz.

So the basic structure of a PDF is a series of tags associated with different types of payload. You break the document down into pages, and process each page as a stream, calling C functions associated with the tags. This is a simple adaptation of example code straight from the Quartz documentation:

for (NSInteger thisPageNum = 0; thisPageNum < numOfPages; thisPageNum++)
{
   CGPDFPageRef currentPage = CGPDFDocumentGetPage(*inputPDF, thisPageNum +1);
   CGPDFContentStreamRef myContentStream = CGPDFContentStreamCreateWithPage (currentPage);
   CGPDFScannerRef myScanner = CGPDFScannerCreate (myContentStream, myTable, NULL);
   CGPDFScannerScan (myScanner);
   CGPDFPageRelease (currentPage);
   CGPDFScannerRelease (myScanner);
   CGPDFContentStreamRelease (myContentStream);
   CGPDFOperatorTableSetCallback(myTable, "TJ", getString);
}

In the last line I call my own C function ‘getString’ when the stream encounters the tag “TJ”. Here’s the first part that was new to me: the blending of C and Objective C. My function call, which is an adaptation of code I found here, looks like this:

void getString(CGPDFScannerRef inScanner, void *userInfo)
{
   CGPDFArrayRef array;
   bool success = CGPDFScannerPopArray(inScanner, &array);
   for(size_t n = 0; n < CGPDFArrayGetCount(array); n += 1)
   {
      if(n >= CGPDFArrayGetCount(array))
      continue;
      CGPDFStringRef string;
      success = CGPDFArrayGetString(array, n, &string);
      if(success)
      {
         NSString *data = (__bridge NSString *)CGPDFStringCopyTextString(string);
         [globalSelf appendMe:data];
      }
   }
}

So there a couple of things going on here: this code is simply looking for a string – well a Quartz style CGPDFStringRef – in the payload passed in by stream process. If it finds one, it converts it into an NSString via some ‘bridge casting’ – something I’ve come across before in working with the keychain, and which you need for ARC compliance. I then take that string and append it to a property in a local method called appendMe.

It’s not possible to call ‘self’ from a C function. There are a number of possible ways around this, some of which get pretty nasty. The most elegant that I found was this:

static P2VConverter *globalSelf;

-(void)setMyselfAsGlobalVar
{
   globalSelf = self;
}

…which assigns an instance of the class that I created to do the PDF processing to a static variable called *globalSelf, and which I can then refer to as an alternate to self. To say this implementation isn’t particularly memory efficient is an understatement – but it works.

There is a rich set of tags defined by a published PDF standard – all 800 pages of it – that tell whatever is rendering the document what to do with it. The best general explanation I found was this. There is a relatively small set of text related tags and TJ seems to be the simplest. It’s also the only one that I was able to adapt from other examples. I may come back to this again.

The way I tested this was to convert an HTML page into a PDF using Safari. The more complicated the text structure in your input document – say multiple columns per page, text boxes etc – the worse this simple extraction mechanism is going to cope.

On to email based importing of files. This isn’t something that I’d ever looked at before and it turned out to be a little more complicated than I expected. The amendments to the info.plist are pretty trivial, creating the association between the file type and the app. So in the PDF reader, when you launch the app in the contextual menu, what actually happens is that the file is copied to the app’s Documents folder, and a file:// style URL which points at it is passed to the AppDelegate – specifically, the application:(UIApplication *)application handleOpenURL method. I’d assumed in the first instance, that I’d import the header for the viewcontroller into the AppDelegate – and this is a single view app, so ViewController.h – instantiate the VC, call a method I expose in the header and I’d be done:

ViewController *thisVC = [[ViewController alloc] init];
[thisVC importFromEmail:url];

This is wrong, and led to some peculiar side effects, which emerged when I started to try to set the point in the text to resume speech to reflect a change in the scrubbing control. This is what I came up with, which is a variant of this, adapted for a single view app:

UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
UINavigationController *root = [[UINavigationController alloc]initWithRootViewController:[storyboard instantiateViewControllerWithIdentifier:@"EntryVC"]];
self.window.rootViewController= root;
ViewController *thisVC = (ViewController*)[[root viewControllers] objectAtIndex:0];
if (url != nil && [url isFileURL]) 
{
   [thisVC importFromEmail:url];
   NSLog(@"url: %@", url);
}

A couple of points of note. First, the method being called here is going to run before  viewDidLoad or viewWillAppear. I normally do various inits in viewWillAppear, so I put them in a method that I call immediately in importFromEmail. Second, the string value for instantiateViewControllerWithIdentifier needs to be set in the storyboard.

Apart from the nasty callouts to C, what I spent most of my time working on scrubbing control functionality. I’ve created an IBAction method that will be called when the scrubber moves position. In the storyboard, I set the maximum value of the control to be 1, so to get the index of the new word position after dragging the control, I multiply the fraction that moving the control allows me to reference in the IBAction by the length of the original pdf string length.

Having stopped – not paused; more on that in a second – the playback, I then start a new playback in the IBAction method for the play button, having created a new string based on a range: the word index from the scrubber control as the start point, and then the length by subtracting that from the original pdf string length. There was a little bit of twiddling necessary to support this so that it would work when called multiple times.

Part of the reason I took this approach was because the continueSpeaking method on the AVSpeechSynthesizer class didn’t seem to work. This was because I was using stopSpeakingAtBoundary instead of pauseSpeakingAtBoundary – something I’ve just noticed. Doh!

This has a knock-on effect, which is that the play button has to be stateful, with a continue if the pause was pressed, or a restart with the new substring if it’s because of the setting of the scrubber control. Given that the actual quality of the string conversion is pretty basic, the fix for this exceeds the usefulness of the app.

A couple of final comments. I discovered that it’s best to keep the functionality in the IBAction method called for the scrubbing control changes pretty simple: basically just setting the property for the new word position. I got some peculiar results when trying to do some string manipulation for a property, because the method was being called before a prior execution to set it was completed.

Lastly, I encountered an odd, and as yet unresolved bug when, as an afterthought, I added support for speech based on text input directly into a UITextField. I started seeing log errors of the type _BSMachError: (os/kern) invalid name (15). This appears to be quite a common one, as the number of upvotes on this question attest to. I’m filing it in the same bucket as the stateful play button resolution: if the quality of the playback warranted it, I’d figure it out.


I was in two minds as to whether or not to write this app up given the mixed results, but I thought I might as well as it may be one of my last pieces of Objective C development.

I blogged a year ago, almost to the day, about my first Apple Watch app. I had one pretty serious watch project last summer, which I ended up canning around November. The idea was to sync accelerometer data from the watch with slow motion video, say of a golf swing. However, I ran into a problem with the precision of the data in AVAsset metadata, and which I have an as-yet unanswered question on in StackOverflow.

I also ran into the same usage issues with the watch that have been widely reported in the tech press. While I really liked the notifications [and the watch itself, which is a lovely piece of kit], the absence of any other compelling functionality barely warranted the bother of charging the thing. The borderline utility really came to the forefront for me when I was travelling, for both work and holidays, with no roaming network access. The watch stayed at home.

I also think it’s pretty telling that I bought precisely zero apps for it before I sold it a couple of months ago.

I’ll be looking to replace my current iPhone 6 Plus later this summer and I’m toying with the idea of moving to Android. I tried it before and hated it, but that was with a pretty low-end phone that I got as a stopgap after my 4S had an unfortunate encounter with the washing machine. Java would be much more useful than Objective C in my working life, and it’s a potentially interesting route into the language.

I’m sure it’s nothing personal…

Like probably the vast majority of people who are running WordPress for more than a few months, my site is frequently being hit with automated attacks. I’ve only recently noticed this in my logs so I thought it would be interesting to have a closer look.

Around the turn of the year, for reasons I can’t recall, I happened to look at the raw access logs and noticed a lot of references to ‘xmlrpc.php’, which look like this:

142.4.4.190 - - [31/Jan/2016:18:13:42 +0000] "POST /blog/xmlrpc.php HTTP/1.0" 200 58043 "-" "-"

This is a real log file entry, and is a classic example of an XMLRPC bruteforce amplification attack: someone has posted 58k at this page, to try and bruteforce the admin password. I disabled the mechanism – and just verified that it’s working this morning [two months later :)], as the 200 server response is a bit more polite than I would have expected.

At the same time I installed [yet another] plugin, which rate limits failed admin password authentication attempts. It started triggering last week with repeated admin authentication failures from a machine in Hanoi. In my latest access log file [31st January to about half an hour ago], I have 1500 POST attempts which look like this:

123.30.140.199 - - [26/Feb/2016:13:37:47 +0000] "POST /blog/wp-login.php HTTP/1.0" 200 3766 "-" "-"

I’ve not paid much attention to log formats in a long time so I had to google what those final two hyphens are: a blank referer [note to my wife on the spelling :)] and user agent field respectively. The blank user agent is indicative of some sort of automated attack and, by virtue of the fact that the person who’s running it hasn’t even bothered to make it look like a real browser, one that isn’t particularly sophisticated.

The logging pattern suggests what you’d expect: someone has harvested a set of servers that are running WordPress [how? by virtue of having the common pages that WordPress hosts. So a 200 in response to a GET for a ~/wp-login.php page, for instance], and is stepping through them.

This is another indicator of the lack of sophistication:

123.30.140.199 - - [26/Feb/2016:16:41:35 +0000] "POST /blog/wp-login.php HTTP/1.0" 200 1643 "-" "-"
123.30.140.199 - - [26/Feb/2016:16:41:37 +0000] "POST /blog/wp-login.php HTTP/1.0" 200 1643 "-" "-"
123.30.140.199 - - [26/Feb/2016:16:41:38 +0000] "POST /blog/wp-login.php HTTP/1.0" 200 1643 "-" "-"
123.30.140.199 - - [26/Feb/2016:16:41:44 +0000] "POST /blog/wp-login.php HTTP/1.0" 200 1643 "-" "-"
123.30.140.199 - - [26/Feb/2016:16:41:46 +0000] "POST /blog/wp-login.php HTTP/1.0" 200 1643 "-" "-"
123.30.140.199 - - [26/Feb/2016:16:41:58 +0000] "POST /blog/wp-login.php HTTP/1.0" 200 1643 "-" "-"
123.30.140.199 - - [26/Feb/2016:16:41:59 +0000] "POST /blog/wp-login.php HTTP/1.0" 200 1643 "-" "-"
123.30.140.199 - - [26/Feb/2016:16:42:05 +0000] "POST /blog/wp-login.php HTTP/1.0" 200 1643 "-" "-"
123.30.140.199 - - [26/Feb/2016:16:42:06 +0000] "POST /blog/wp-login.php HTTP/1.0" 200 1643 "-" "-"
123.30.140.199 - - [26/Feb/2016:16:42:13 +0000] "POST /blog/wp-login.php HTTP/1.0" 200 1643 "-" "-"
123.30.140.199 - - [26/Feb/2016:16:42:14 +0000] "POST /blog/wp-login.php HTTP/1.0" 403 9 "-" "-"
123.30.140.199 - - [26/Feb/2016:16:42:20 +0000] "POST /blog/wp-login.php HTTP/1.0" 403 9 "-" "-"
123.30.140.199 - - [26/Feb/2016:16:42:21 +0000] "POST /blog/wp-login.php HTTP/1.0" 403 9 "-" "-"
123.30.140.199 - - [26/Feb/2016:16:42:22 +0000] "POST /blog/wp-login.php HTTP/1.0" 403 9 "-" "-"
123.30.140.199 - - [26/Feb/2016:16:42:23 +0000] "POST /blog/wp-login.php HTTP/1.0" 403 9 "-" "-"

What’s happening here is that some software I’m running is blocking the user’s IP address after 10 authentication failures, shown by the 403, which is the server returning a ‘Forbidden’. What I’ve deleted from the log extract above is  that there are a total of 25 Forbidden responses by the server in a row: the attack software isn’t checking the server response codes, which is a waste of resource on their part.

I’ve had a bit of a trawl through my logs and am seeing similar, albeit less determined attacks like this, coming from all sorts of far flung places:

62.109.19.98 - - [13/Feb/2016:07:46:48 +0000] "POST /blog/xmlrpc.php HTTP/1.0" 200 58043 "-" "-"

That’s another XMLRPC bruteforce amplification attack, from Russia. A geolocation site reckons this one…

204.232.224.64 - - [12/Feb/2016:07:12:33 +0000] "POST /blog/xmlrpc.php HTTP/1.0" 200 58043 "-" "-"

…is in San Antonio, Texas. Interesting that the byte sizes being posted through are identical: 58,043. Again, that’s indicative of the same off the shelf attack software running with a pre-canned payload. Let’s do one more of these:

1.83.251.239 - - [11/Feb/2016:02:19:14 +0000] "POST /blog/xmlrpc.php HTTP/1.1" 200 45387 "-" "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)"

I can honestly say that since I first started messing around on the internet in 1992, I’ve never seen an IP address that starts with 1. The geolocation service dutifully informs me that the machine that sent this parcel of good intention is located in Xi’an in China. At least they’ve spiced things up a bit with a different sized payload.

So here’s a thing: I have a couple of blog posts on this site about a holiday we had in Vietnam. I blogged about a holiday to China that included a trip to Xi’an. I’ve also got a posting about a work trip to Russia. So… Russia and China are massive, populous countries. But Xi’an, in China? That looks like a pattern to me. I wonder if the bundle of joy – malware, whatever it is – that would be deposited on my site if it were to be compromised is tailored or localised in some way or other, based on the occurrences of those locations.

 

As per the title, and the obvious lack of finesse, I know that my server is just one on what’s probably a very long list of candidates that these automated attacks are hitting. WordPress has had something of a chequered history from a security point of view: it’s a natural target. While I’ve done the easy stuff to shore it up – like blocking a blank user agent – the options are relatively limited. That’s fine, given the fairly low-rent nature of the stuff being thrown at it, but I’d really prefer not to be distributing malware to people. Migrating off WordPress looks like it would be a pain so if the ancillary approaches start to look like they’re too much trouble I’ll just delete the site.

Grand Central Station

Just a quick post on a picture that I took last Saturday at Grand Central Station in New York:

Grand Central

Grand Central

This is an HDR image based on 3 exposures, bracketed around F7.1. So the shortest exposure was 0.6 seconds and the longest 10 seconds. I applied for a photo pass, but didn’t get one [not sure why: possibly because I only did it a week in advance]. As you’re not allowed to use a tripod without one, I propped my camera up on my hat and gloves on the ledge of the balcony – surprisingly effective. This was the day of the Snowstorm Jonas, so the outside light coming through the windows was pretty muted. There’s still a little bit of chromatic aberration though, possibly because I took this at 16 mm [on my 16-35mm F2.8].

I did take some pictures during the snow, but it wasn’t the sort of weather to be messing around with tripods. This is what a Prius looks like under a couple of feet of snow [taken on my phone]:

Snowy Prius

Snowy Prius

 

Cambodia Trip: From Crickets to Kettlegate

So: our most recent long haul trip was split across 4 venues: Siem Reap [4 nights]; Phnom Penh [2 nights]; Koh Kong [3 nights]; Kep [2 nights], and then finally back to Phnom Penh for the duration.

This is the first of what I promise to keep to two geeky departures, starting with camera gear. I radically over-packed, and took every lens I have – you know, just in case you miss something. This brainwave was partly inspired by the fact that my old Crumpler bag, a veteran of 8 years, finally gave up the ghost and my replacement Lowe Pro [Flipside 400 AW] had the extra capacity. I’d assumed that when we were in a riverside resort in Koh Kong, there would be wildlife to see. Wrong: we were told by one local person that the reason there is a dearth of wildlife – and occasionally eerily so – is that people were so desperate for food after the Khmer Rouge regime that they trapped wild animals to the point of picking the land clean of them. I used my walkabout lens [24-105] a lot, my wide lens [16-35] a little, and my macro once on a point of principle. Everything else was ballast. My wife also had some new kit to try out: her Olympus OMD EM-II. A great camera, but I find it puzzling that manufacturers think it’s a sane piece of economics to ship a complicated piece of kit with a soft copy of the 400 page manual.

We flew into Siem Reap from Singapore. The preceding long haul schlep with Singapore Airlines was pretty comfortable as 13 hour flights go, although memorable principally for our ordering a glass of wine 10 minutes before breakfast was served. To be fair, we didn’t know, and given the way events were to unfurl it’s a shame we didn’t fill our boots with booze on the first three days anyway [more on that soon]. Rather bizarrely, we also saw Richard and Judy, former doyennes of daytime TV, at Changi when we were transiting.

Siem Reap itself is unremarkable. Its rapid recent expansion to accommodate the Angkor complex tourism is reflected in the slightly wild west feel to the place. There is a pretty decent covered market, which we spent quite a while pottering around until the mix of jet lag and intense humidity forced us out of the place, and backpackers are well catered for in an area called Pub Street. We stayed in a hotel called the Hanuman Alaya Boutique. The room was great and the staff were really friendly, although it has to be said that the sound proofing between the rooms was non existent. They also plonked us in a room right across from reception which we considered changing for about 5 seconds: we were exhausted for the first few days and could have slept propped up in the corner of a train station.

We started our temple-fest on the second day with visits to the Angkors brothers, Thom and Wat. Even with a reasonably early start, they were both very busy, so much so that it was pretty impactful on the experience. No-one has a god-given right to visit these places, but if you’re expecting a tranquil sunrise experience, think again.

Angkor Wat

Whose leg?

Whose leg?

Big heads

Big heads

Early the next morning, we hit one of the highlights of our trip: ironically, this was a tranquil sunrise experience: the almost-deserted Ta Prohm,  of Tomb Raider fame. I’ve limited myself to two geek outs, and here’s the second. I really couldn’t get over this place. Anyone who has ‘invested’ as much time in computer games as I have is going to have the same, completely inverted reaction: it looks just like a scene from a whole genre of platformers. Take your pick: Tomb Raider, Prince of Persia, Drake’s Fortune. Someone from those design teams went to Ta Prohm, and you see it, in essence, again and again. Despite the lack of levers to open doors and raise water levels, it is an utterly fabulous place, and worth the journey to Cambodia in its own right.

Ta Prohm

...again

…again

That afternoon, we went on a boat trip through a canal system and out to the edge of Tonle Sap lake. This was a bit of a misfire, which continued for the rest of the day. While the scenery was interesting, once we got out onto to the lake, we did an about face and went straight back to the car. Stopping off at the floating village we chugged past would have been good.

Dignity just about maintained

Dignity just about maintained

It turns out we were supposed to do precisely that, but our guide who, with the benefit of hindsight, was  occasionally a bit pants, didn’t want to waste our time with all of that nonsense. Equally, we were supposed to go an a foodie tour of a street market, which seemed to surprise our guide. In the end, he sort of cobbled it together and, while I think my wife was a little disappointed, I quite enjoyed it. Recounting this does sound as if we were a little bit spineless, but we were still badly jet lagged and struggling with the heat. Also, you tend to find with these gigs you’re halfway through them before you realise it’s not what you expected.

We managed to squeeze in a couple of culinary treats at Street 60 market. I tried a variety of deep fried bugs. Here’s the role call:

  • Grubs: disgusting.
  • Grasshoppers: plausible but they were pretty big [about 3 inches], which is quite off-putting. I distinctly recall not wanting to bite the thing in two, in case there was any trailing gubbins. The main downside of trying to munch it down in one go was that I unwittingly started the process with a hind leg hanging out of my mouth.
  • What were described as water beetles, which were a little over an inch long. These looked like diving beetles to me. The procedure was to break the carapace off before eating. Disgusting.
  • Crickets. These were actually quite tasty. Slightly sweet, very crunchy and with a sort of nutty / meaty favour. They go well with beer, which is drunk over ice, apparently.
mmm

mmm

We finished the day with a Cambodian barbecue. It’s cooked on a very distinctive dome shaped metal cooking implement [dome facing up the ways, so convex], encircled in what could best be described as a soup moat. This sits directly on a burner. You place a broth in the moat to boil seafood [prawns and squid] and mushrooms. At the top of the dome, you put a lump of pork fat on to melt, which – well, in theory, stops the strips of very lean beef and fatty bacon from sticking to the surface. Very tasty indeed.

Shock, horror: I woke up the next morning feeling like death warmed up, and had to skip the main activity for the day which was a cooking class. My wife went, while I remained bed bound – or more precisely, within striking distance of the loo – pretty much for the rest of the day.

That set a precedent for about the next 5 days, with either one of both of us not feeling well enough to hit all the points on the itinerary. I’m going to skip forward a few days to the Friday of the first week, by which time we’d finished up site-seeing in Siem Reap and had flown back to Phnom Penh. That flight, on the Thursday, was awful: I talked fairly seriously with my wife about seeing if we could drive it, but the prospect of 5 hours in the car as opposed to 35 minutes in the air was a hard sell. We were getting ready to head out site-seeing on the Friday morning, and my wife felt really unwell, and nearly fainted on the way back up to the room for something [probably an emergency loo break]. Our guide turned up a few minutes later, took one look at us, and took us to a medical facility where we ended up spending the day doing various tests.

The conclusion was that we were both diagnosed with amoebic dysentery. When the doctor told us, the first thing that popped into my mind was Spike Milligan’s epitaph: ‘I told you I was ill!’ While we were plumbed in to various drips and waiting test results, we thought about pulling the plug and heading home on the spot. When things go wrong you suddenly realise you are a very, very long way from home. Then the doctor came in, told us that we weren’t that sick and [implicitly] that we should put up and shut up :).

Because I succumbed a day earlier than my wife, I missed about 2 1/2 days of sight seeing. That included everything we’d originally planned on doing in Phnom Penh, including the S-21 prison, and the Killing Fields.

We were just about to drive to our next destination at this point in Koh Kong, which is a floating ‘glamping’ hotel on the side of a river called the Preat. Before I get on to that I’ll just gloss over some of the sites we saw around Siem Reap on our last day [the Wednesday of the first week]. We had a look at Banteay Srei and Banteay Samre. Well, I think we did. We actually went to 3 temple ruins that day, but I was feeling awful and had to sit out one in the car, so I’m pretty hazy on the details.

Banteay

Banteay

Banteay

A couple of final points to tick off. The first is what caused us to get ill. While on the Monday night I did eat half a bag of bugs sold by some geezer at the side of a road, who was shooing still-flying varieties off his wares before flogging them, the timing is probably wrong. The incubation period is 2-4 days, apparently, which moves the unwashed amoebic finger of suspicion back a day or two [I ate the bugs about 8 hours before I started getting sick]. Our most likely candidate was a cheap and cheerful restaurant that we ate in on the Saturday in downtown Siem Reap, where we shared a starter of fresh spring rolls filled with salad and prawns. They were pretty good at the time.

And while we were still in rude health, we had a fantastic meal on the Saturday night in Siem Reap at a spot called The Chanrey Tree.

We transferred over to the 4 Rivers Floating Lodge in Koh Kong, after about a 4 hour drive from Phnom Penh and 20 minute boat transfer. The setting is stunning, and they have done a really good job with the setup. Although you have your own platform to lower yourself into the water for a swim, my wife was at the peak of her symptoms, and the thoughts of what the repercussions might be from glugging down a few mouthfuls of Eau de Mekong Tributary left us both erring on the side of caution. I did as much kayaking as my wife’s frayed nerves could cope with. Just to explain, I have a profoundly bad sense of direction – so what could possibly go wrong on a river that opens into the Gulf of Thailand?

4 Rivers Lodge

The food at the place was occasionally awful. They had one day where there seemed to be a complete meltdown in the kitchen: very long delays for food which really wasn’t great when it did arrive . I know this sounds like a very first-world moan, but the Lodge comes with a 5 star price tag, which sets expectations accordingly. That said, it was typical of an experience that we repeated in 2 or 3 of the places we stayed: there were aspects of the service which weren’t great, but the people working there were so friendly and trying so hard to be helpful, actually breaking the fourth wall to complain about something felt like we’d be walking up to Bambi and saying ‘Well fella, shame about your Ma!’, and kicking him in the head.

We had plenty of opportunity for excursions at the Lodge, but as my wife was feeling awful, we skipped on everything except a firefly spotting river trip after sunset on the first evening. Oh. My. God. I never fail to be gobsmacked at how lacking in basic common sense people can be, and yet still survive into adulthood. Case in point: a guy acting as spotter pointed at a tree with lots of fireflies. So 4 people of the be-beaded / new age / let’s-wear-something-wispy type [or, more simply, ‘idiots’] all immediately transferred to the other side of the boat, nearly capsizing it, to take pictures with flashes that weren’t going to come out anyway. They might as well have been using the flash to take pictures of frigging Jupiter.

Next pitstop was Kep, described as a sleepy seaside town, with little beyond a market and the deserted remains of houses used by senior officers of the Khmer Rouge as holiday boltholes. Sleepy it may well be for 51 and a half weeks of the year, but our visit coincided with the annual Water Festival, a national holiday when a sizeable chunk of the population cuts and runs for the coast for 3 days. It was mobbed. We stayed at a place called the Veranda Natural Resort. The room was bizarre: it was a hangar fitted out in a weird 1970s decor. It must have been the guts of 25 feet square, too much space to make use of. The bed had these shin-high shelves either side [beyond bruising, God knows what they were for], the safe was on the floor, there was no veranda [despite the name], and no kettle. We were so non-plussed by the tour of the room that we actually thought that they were trying to pull a fast one and queried it – and asked about the kettle [note the pattern]. The staff insisted it was the right room [correct] and admitted that there were more rooms than kettles and that they’d try to do something for us, although they were fully booked.

The next day, when we came back to the room to find it made up, a door which we’d thought was locked and assumed was adjoining, was lying open to reveal a small, well equipped kitchen. We were briefly sheepish, shuffled our feet and, uhm, didn’t talk about kettles again.

After a couple of days of downtime, we were starting to feel better and getting antsy to do something. We emailed our guide [the splendid Thea] and arranged to leave Kep a day early and head back to Phnom Penh. The adapted [post-illness] itinerary had us doing the transfer from Kep and site seeing all during the morning before our flight. Taking the extra day meant we could do it at our leisure.

A few of final thoughts on Kep: the market was actually pretty interesting, processing lots of crab that people were buying straight out of the water. They formed the main ingredient in a fantastic meal we had on our last night in a spot called the Democrat [anachronistically decorated with pictures of various US Democratic politicians]. I also spent an afternoon walking around the ‘Kep National Park’, right next to the hotel. I only got a couple of fleeting glances at some birds but I think there would definitely be stuff to see there, given a bit more time than I could throw at it.

[we were wearing shorts] IMG_7756

If this shot, which I took in the park, had a name it would be ‘I’ve dragged my macro lens 6000 bloody miles so I’m going to find something to take a picture of with it’:

Have Macro, will travel...

And so our final day in Phnom Penh. While it’s not super developed, we both really enjoyed it: we had a nice walk through the wide boulevards around the palace. We had a drink [soft, due to meds unfortunately!] in the Foreign Correspondents’ Club, had a walk around one fairly boutique-ey little Street 240, and went for a pretty fancy meal on our last evening at a spot called Malis.

Water Festival Traffic

We booked back into the same hotel that we’d stayed in earlier in the holiday: La Rose Suites. It was stunning: art deco styling, beautifully appointed rooms and the staff were fantastic. As my wife remarked, could you imagine leaving a hotel in London and being handed a business card on the way out with the comment, ‘don’t worry, if you get lost, give us a call and we’ll come and find you’.

The last morning, before we went to the airport, we went to the prison in Phnom Penh [S-21], and then finally on to the Killing Fields. I was talking about this at work today and surprised myself by  getting quite upset about it. The prison is full of pretty grizzly images. The Killing Fields [actually one of an estimated 250 or so where people were killed. I hadn’t realised] is just strange, poignant, and very moving. There are a myriad of large bones poking through the ground on the path you follow: what I did at the start, and what I think is a natural reaction, is to disassociate what you’re seeing from the remains of someone who was murdered, and simply try to identify what bones they are. What pulled me up short was finding – ‘finding’ is the wrong word. You don’t have to seek them out – teeth. I’m far from an expert but they looked like a child’s pre-molars to me, based on the size, shape and absence of root. Whoever it was they were young.

The only thing I felt like taking a picture of...

That was it. Reading this back, the holiday sounds like a series of disasters interspersed by car and plane journeys, but we actually had a really fun time. I’m the last person to make sweeping generalisations, but what the hell, it’s nearly Christmas, so I’m going to treat myself to one. I enjoyed Vietnam, but there were aspects of it that pushed my buttons. It’s impossible not to make comparisons with its neighbour, and I’d sooner go back to Cambodia.

Building an Electronic Programme Guide [part 3]

This is my third and final write-up on the development of an electronic programme guide app. As of part 2, the main scrolling view with the programme details, scaled to length, are displayed.

Next up I wanted to have the ability to display programme details. This seemed like it was going to be pretty straightforward: when building the per-programme view, I included a button, which is transparent, and has the same dimensions as the view itself. The first problem is associating the programme details with the button itself. There is plenty of discussion on StackOverflow about how to do this in the least offensive way. I went with a category and then object association. This allowed me to set the GUID as a property for the button.

I rebuilt the app, hit the button and… entered into a fortnight of debugging an EXC_BAD_ACCESS error. I knew what the problem was: the ARC memory management was dereferencing the button object once it was set. I tried lots of different options, such as adding the buttons to an array, set with various properties, and passing the array back to the main view controller. Nothing worked until I did more reading around the property attributes, and ended up redefining the Interface Builder defaults for the scrolling contentView to:

@property (nonatomic, strong) IBOutlet UIView *contentView;

That ‘strong’ means that everything in the view is held in memory. It has to be said that the app is very heavy on memory – as a direct consequence of that view object retention. It routinely occupies 63Mb in my testing.

Next up is the popup that is rendered. So finding the programme itself is pretty easy, using an NSPredicate based on the GUID. What proved a bit harder to deal with is if the main view [the ‘contentView’ for the scrollView] is zoomed. As you have to add the popup view to the zoomed parent, the former is going to inherit the zoom setting. I couldn’t think of an elegant way around this so I worked around it in stages. First off, the popup sits on a blurred view of the current background:

// This is quite neat: make a CGRect of the currently visible part of the scrollview:
CGRect visibleRect = [scrollView convertRect:scrollView.bounds toView:contentView];
visualEffectView = [[UIVisualEffectView alloc] initWithFrame:visibleRect];
visualEffectView.effect = blurEffect;
visualEffectView.frame = contentView.bounds;
[contentView addSubview:visualEffectView];

Next, I register the scrollView offset in a property:

scrollOffSet = scrollView.contentOffset;

…set the zoomScale to 1, and disable the ability to zoom:

[scrollView setZoomScale:1.0];
scrollView.scrollEnabled = NO;
scrollView.maximumZoomScale = 1.0;
scrollView.minimumZoomScale = 1.0;

Placing the programme details subview is then relative to the currently visible rectangle:

float xForLittleView = visibleRect.origin.x + 30 ;
float yForLittleView = visibleRect.origin.y + 100;

CGRect progViewRect = CGRectMake(xForLittleView, yForLittleView, 350, 500);

I then have to undo the various view settings when the button to dismiss the view is touched:

[visualEffectView removeFromSuperview];
[littleView removeFromSuperview];
scrollView.scrollEnabled = YES;
scrollView.maximumZoomScale = 2.0;
scrollView.minimumZoomScale = 0.8;
[scrollView setZoomScale:zoomScale];
[scrollView setContentOffset:scrollOffSet];

It’s all a bit clunky, but it works. I imagine that this sort of interface plumbing actually happens quite a lot behind the scenes. That said, I may have missed a trick to do it in an easier way.

I’ll call out two more details that I wrestled with. The first is a search facility on the programme title. I wanted the NSPredicate to support as many search terms as the user entered. My initial idea was to split the UITextField input on spaces, and then loop through the resulting array, appending to a stringWithFormat, where all but the first element would be in the form:

AND (title CONTAINS[c][/c] %@)

Having experimented with this, it appears that predicateWithFormat has to have the actual string passed to it, as opposed to a variable containing the string. Which I have to say strikes me as a little odd. The functional upshot of this is that I couldn’t support a variable number of search terms. I support up to three, and construct a separate predicateWithFormat for each possibility.

One final problem that I couldn’t find a fix for was implementing a UITableView’s delegates in a class that I pass the view into as a parameter. I couldn’t find a way of getting the cellForRowAtIndexPath delegate method to be called. The conclusion I came to with this was that it was setting the delegate to self, when ‘self’ was the custom object, rather than the view. It was largely a cosmetic thing [I’ve noticed that for complicated apps, I have a tendency to pile way to much code into the main viewController] so it was easily solved.

Here’s what may be the final version of the app looks like, showing a search result in the popup view, and the to/from dates for the EPG coverage:

Search Results

Search Results

The other buttons that I haven’t talked about explicitly are an ability to switch between days, and initiate a download of EPG data – but which are pretty straightforward. What’s still either ugly or hasn’t been fully implemented is the download progress indicator, and also the what’s-on-now quick look on the Apple Watch, as I want to have a mess around with something completely unrelated to this app: the motion detection capability.

I did add a quick fix to ‘justify’ the right hand side of the ‘table’ of programmes. Formerly they were falling off the contentView. I simply check if the rightmost width of the cell is going to be greater than the width of the contentView. If so, I set it to be the same as the width.

So that’s it. I have a pretty serviceable EPG app, which I use myself over the ad-funded variant I had before, which I guess is a fair indicator of utility. Main lesson learned: not knowing what those property attributes meant tripped me up really badly!