Tern GSD Review

I started writing a personal review on my Tern GSD R14 purchased last August on my Mastodon. But this is probably better kept in a blog post format, so here it is.

Now that I have used it for a few months, I would like to share some experiences with the Tern GSD cargo bike.

First impression: it is heavy. Especially since I got two batteries. My bike is over 37 kg. The weight makes getting on the bike and maneuvering in tight spaces more challenging. The good thing is that it is built like a tank, sturdy and stable when parked.

After getting used to the weight, it is actually not a huge problem to ride most of the time, given the powerful Bosch cargo line motor. Getting on 12% hill with full of cargo feels like a breeze. However, I do feel like I need extra attention if I need to jump off the bike, because then I need all my strength to hold the bike steady.

It is also quite a challenge to get it into small elevators even when you can lift it upright, so I would not recommend getting it if you live in an old apartment building. Newer ones typically have deeper elevators so it wouldn’t be a problem to push it right in. Overall, I think the Tern HSD or Quick Haul might be better options if you want a more nimble and agile bike.

That said, this thing is a beast when it comes to hauling cargo. I have the Clubhouse+, Storm Box, and the Transporteur rack accessories installed. Buying groceries, picking up packages, dropping off recyclables to local recycling station has been so efficient, honestly I prefer it to cars most of the time, since I don’t need to bother finding a parking spot in the city.

It is quite capable getting passengers around too. Since we have a lot of rain and snow here I also have the Storm Shield installed, and it is so helpful at keeping the wind and water out. Comparing to a smaller bike like the HSD or Quick Haul with similar (mini) setup the GSD gives more space for the passengers, so you can fit in some cargo even when carrying a kid.

For anti-theft I use the local BikeFinder, a GPS tracker hidden in the handlebar. It comes with a 200 NOK/month insurance that allows me to use the built in frame lock only. The tracker works fine but I do wish its battery last a little longer, and easier to charge.

I got the R14 model with Rohloff hub which is quite a bit more expensive than the base S10 with Shimano Deore or the S00 with Enviolo gear hub. But I’m glad that I made the choice because the Rohloff R14 has been flawless and a joy to use. I found gear 8 or 9 is my sweet spot. And belt drive is such a good pairing with electric bikes in muddy conditions.

For winter tires, I followed Tern’s advice and got the Schwalbe Marathon Winter Plus 20×2.15. You do sacrifice quite a bit of speed compare to the stock Schwalbe Big Ben Plus due to much heavier weight and more rolling resistance, but in icy or packed snow conditions it is also much safer. The only thing I can’t safely ride with these are fresh, loose snow, especially going down or uphill.

The studs helped a little for the traction but not quite enough especially when you need to turn or going from one tire track to another. I was quite nervous when going over a motorway overpass bridge in condition like that. The bike lanes within Oslo ring 3 are fine though, as there weren’t much snow on there.

Changing into the winter tires hasn’t been much of a trouble either: putting the bike in upright position saved me from using a bike repair stand (my stand can’t handle the weight anyways). The front one is pretty straightforward. The rear one requires unplug the Rohloff cable, which is quite tightly coupled. You can find guide on YouTube or Reddit for the steps even though Tern didn’t provide an instruction officially.

For carrying our little one I first went with the most straightforward solution: Thule Yepp Maxi EasyFit. It is really simple to put on or take off, and the safety belt is easy to fasten as well. It should be quite comfortable on its own given the bike has a suspension. Having the Clubhouse+ in addition is kind of nice as it gives the child to have something to hold on to while riding. The Storm Box meanwhile can keep their feet warm and dry by blocking wind and water.

You can certainly still carry an adult plus a kid with this setup as long as the total weight including the bike is within 200 kg. But that is pushing the bike a bit far especially going uphill. It also feels less safe since you have to keep the adult closer to the center and the kid further at the back to balance the weight distribution, otherwise the bike will tilt to the back when loading.

Another option is attaching a trailer: we went for a Thule Chariot Sport 1 because for longer trips, having the child fall asleep on the Yepp feels awkward, as they certainly don’t look comfortable sleeping upright. Tern has a guide on fitting trailers to the GSD gen 2, however the M10 nut and bolt pair for attaching the Thule Chariot arm is surprisingly hard to find here. I had to ask our local bike workshop (Evo) for help.

Comparing to the GSD, the Quick Haul my wife owns has a simpler quick release skewer and the longer skewer Thule provides fits perfectly. Once attached, getting the trailer on or off is quick, steering it through the roads is also quite easy. I expected that to require some getting used to but we were able to do it as soon as we get on our bikes.

Having the kid in the trailer free up a lot of cargo space as well, we were able to ride to our local Plantasjen to get a few bags of firewood with this setup. Each bag is 40L and fits perfectly to the Storm Box slots and stacked on the rear rack quite secure with the Clubhouse+ keeping them from falling off. We can stack even more by fasten the bags with some bungee cord.

As for the front rack, the big Transporteur rack I got is handy at loading package boxes, pizza boxes, or backpacks, but it also makes sliding the bike into normal stationary bike stand a challenge, since most of those stands are in a reversed U shape the rack likely will get in your way, especially if you want to lock it with a chain or U lock.

New Beginning


It has been a while since my last post. A lot has happened. The most important one is that I have come back to China to join the fruit company everyone loves or hates.

It’s a new beginning for me, almost three years ago I joined Opera to build their new Mac browser from scratch. It’s an opportunity that few programmers could ask for. I met some incredibly talented colleagues and became really good friends with many of them. To sum up, the jump to Opera is a decision I will never regret.

But that’s a small jump compare to the one I just made. After moved to Norway for five years, I’m moving back to Beijing for a new job.

After working on desktop UI programming for a while I realized that I still prefer lower level work involving frameworks and APIs. I still love working on text and fonts in particular. Fortunately, this new job is exactly about those stuff.

Of course, the job is not supposed to be easy and being remote for a while is going to be tough. But I’m still positive about this new beginning. We will see.

Optimizing SHA-1 Performance on OS X

At work we need to do some SHA-1 verification on application startup, naturally we want it to run as fast as possible — no user likes their browser bounce too many times on the dock before showing up.

The initial implementation is extremely naïve one, yet quite portable. It’s base on Chromium’s (which our code base is built upon) portable SHA-1 implementation: we first read the entire file into a std::string, then pass this string to SHA1HashString(), job done. Simple, right?

Unfortunately, for the files we have it will take at least 280 milliseconds (61 MB/s) on a rather quick (2.6 GHz Core i7) desktop machine, among which about 30 ms was spent on file reading and the rest is on SHA-1. Needless to say, the memory footprint is quite big given the file sizes we have.

First step of using any other SHA-1 function would be decouple the ReadFileToString() function into normal stdio file reading routine:

FILE* file = fopen(path, "rb");
char buf[1 << 16];
size_t len;
// SHA-1 context initialization.
while ((len = fread(buf, 1, sizeof(buf), file)) > 0) {
// SHA-1 context update.
// SHA-1 context done and get the value.

By doing this we can already save quite a lot of time in std::string concatenation, now the time spent on file reading is down to 5 ms. There is not much room for further improvement, let’s see how we can improve the SHA-1 performance.

A well known fast SHA-1 implementation is the one written by Linus Torvalds for GIT, it’s called block-sha1. The code is derived from the Mozilla NSS library but Linus claimed that he has rewritten it entirely. This performed indeed quite well, the actual calculation time is now down to 60 ~ 65 ms (246 MB/s).

However block-sha1 license is not entirely clear for our closed source use, Linus said that he wouldn’t mind to license it as MPL but so far we still consider it’s licensed as rest of GIT since it resides in its repository. We obviously can’t link to GPLv2 licensed code in closed source software.

Another alternative is Steve Reid’s SHA-1 implementation in C, which is completely public domain code. It performances quite fast as well, around 88 ms for us, which equals to 181 MB/s.

Now that we have a good backup plan, I tried the SHA-1 implementation from Mozilla as well, it takes almost as long as Steve Reid’s implementation, no real improvement here but there is not much burden in license for us either.

I would like to try OpenSSL’s implementation as well, since according to Improving the Performance of the Secure Hash Algorithm (SHA-1), it has more optimized implementation for Intel SIMD instructions (SSE3). However I didn’t managed to get the project build with OpenSSL due to some other complications. Also because I managed to find a better alternative than fiddling with OpenSSL’s fragile API: the CommonDigest library from Apple. It performed much better than block-sha1: only takes 40ms (400 MB/s). From the source released, Apple seemed to be using the cross-platform OpenSSL implementation here as well, but it still would be nice to see how does it compare with OpenSSL library shipped with the system. I will try to post some results in upcoming days.

Preserving Extended Attributes on OS X

When codesigning a Mach-O file (OS X executables or libraries), the signature information will be stored in the file itself through some Mach-O extension. When codesiging a bundle (.app or .framework), _CodeSignature directory will be created. But what happens when you codesigning a plain text file? Signature information will be stored in extended attributes. Because of that, when packaging or copying files like those, you would expect the tools to preserve extended attributes. Not all of them do that by default.

tar on OS X preserves extended attributes by default, both archive and unarchive. But zip doesn’t, a better replacement is ditto -k, ditto can be used as a replacement for cp as well, though cp in OS X preserves extended attributes by default.

When using rsync, -E or --extended-attributes will make sure it copies extended attributes.

When creating a dmg with hdiutil, keep in mind the makehybrid command will lost extended attributes, so you will have to use alternative ways.


Codesigning is one of the worst issues we had been having since we started working on the new Opera for Mac. How Apple managed to screw this up never ceased to amaze us.

Since yesterday morning our build servers started to get CSSMERR_TP_NOT_TRUSTED error while code signing the Mac builds. Well, we didn’t notice until trying to release the new Opera Next build in the afternoon, which is obviously a bad timing. When it happened, immediate reaction was search for it in Google, unfortunately, when it happened words haven’t been spread yet so all results we got were from early 2009 ~ 2011, about some intermediate certificates missing, which completely mislead us. We spent a couple of hours inspecting certificates on all 3 of our Mac buildbot servers, none of them seemed wrong. One of my colleagues tried to resign a package locally with certificates/keys installed, got the same error as well.

Fortunately our build server didn’t get the same error every time so we managed to get a build for release.

When I later did a search for the same keyword but limit the results in last 24 hours, we finally found the real answer to the problem this time. According to this discussion:

Apple timestamp server(s) after all that is the problem here. If I add the --timestamp=none option, codesign always succeeds.

I have exactly the same problem. Probably Apple got two timeservers, with one broken, and a 50% chance for us to reach the working one.

And it worked for us perfectly as well. The only thing I didn’t know was whether it’s safe to release a build without requesting a timestamp (or where can we find other trusted timestamp servers).

This morning I woke up and saw this summary about yesterday’s incident.

According to Allan Odgaard (the author of TextMate):

As long as the key hasn’t expired, there should be no issue with shipping an app without a date stamp, and quite sure I have shipped a few builds without the signed date stamp.

That at least give us some confidence that if such incident happen again, it shouldn’t be a big issue to turn timestamp off.

Update: More explanations from Apple:

The point of cryptographic timestamps is to assist with situations where your key is compromised. You recover from key compromise by asking Apple to revoke your certificate, which will invalidate (as far as code signing and Gatekeeper are concerned) every signature ever made with it unless it has a cryptographic timestamp that proves it was made before you lost control of your key. Every signature that does not have such a timestamp will become invalid upon revocation.