|
|
Subscribe / Log in / New account

JMAP — reinventing IMAP

March 16, 2016

This article was contributed by Neil Brown

The Internet Message Access Protocol version 4 (IMAP4) is a mainstay of remote email access. The vast majority of email clients support the use of IMAP4 to read and manage email. Even web-based email clients that may not really need remote access often use IMAP4, whether to simplify implementation, to improve security through clear separation, or to enhance scalability. IMAP4 recently passed its 21st birthday and, while it is still the king of email access, there is a contender in the wings that appears to be preparing to make a takeover bid: JMAP, the JSON Mail Access Protocol.

JMAP is being developed and promoted by FastMail, a cloud email provider with a history of open-source support, at least of the "open core" variety: it has contributed over half the Git commits to the Cyrus IMAP server in the last five years and is a significant sponsor of the "Roundcube Next" project to build a new webmail client. JMAP, as Bron Gondwana explained in his lightning talk at linux.conf.au 2016, started life as an internal API used by FastMail between its JavaScript in-browser email client and its back-end servers. Positive experiences with open source convinced the company that an open specification would bring more value than keeping the protocol proprietary, so it created a more formal specification and started migrating the internal code base toward it.

Why replace IMAP4?

The first IETF RFC describing IMAP4 was published in December 1994, but it has not been static since then. A quick check of the RFC index shows over 60 RFCs that propose revisions, discuss extensions, or provide clarifications for the specification; the most recent being RFC7377, which was published in October 2014.

This ongoing development and revision could mean that IMAP4 has or will gain all the features that it needs to remain relevant and functional. Or it could mean that it has become a bloated monster with little internal coherence, and multiple implementations each supporting some, but not all, of the extensions. Gondwana was clearly inclined to the latter interpretation, but the specifics he gave were primarily about scope.

A modern email client needs more than just access to messages. It requires the ability to send email (using a protocol like SMTP or LMTP), look up a contacts database (possibly using LDAP or CardDAV), and often to query and update a calendar (perhaps with CalDAV). Managing all of these independently not only increases the configuration burden but is much more likely to run afoul of firewalls or related networking problems. Having a single protocol accessing all this data using a single port is likely to either work smoothly or fail completely, both outcomes seen as preferable to a mix of bits that work and bits that don't.

JMAP basics

JMAP addresses those concerns by defining a single protocol that can access all of the mentioned services and data types, and possibly more in the future, in a fairly uniform way. Configuration is entirely automatic: a DNS lookup for an "SRV" record for _jmaps._tcp.example.com will report a server and port to connect to for someone with an @example.com email address. An https POST request to a well-known URI (/.well-known/jmap) allows JavaScript Object Notation (JSON) messages to be sent and replies received. These messages can create and authenticate a session, which results in an authentication token. This token can then authenticate future connections to any of a small number of URIs that allow different actions to be performed and data to be extracted.

There are upload and download URIs for sending and receiving attachments, raw RFC2822 mail files, and any binary large objects (BLOBs). There are also an event source URI for receiving asynchronous change notifications and an API URI for general request/response interaction. Each API message can contain multiple requests that are processed in sequence, each of which can generate one or more responses, all of which are combined into a single reply. Allowing multiple requests and replies in a single message reduces the number of round-trips to the server to gather complete information, which is important for getting good responsiveness on a high-latency network link.

The JMAP data model identifies a small number of object types, some that exist in their own right and others that are synthesized. The former group includes Mailboxes and Messages, Contact Groups and Contacts, and Calendars and Calendar Events. Objects of these types are each a collection of named attributes that can be requested separately by the client. One particular attribute that is common to all types is an ID, which is a string value, assigned by the server, that is unique within the type and stable for the object's lifetime. Using this ID, objects of a given type (e.g. Foo) can be retrieved (GetFoos), modified if not immutable, and deleted (SetFoos). Each type has a "state" value that is effectively incremented whenever any object of that type is changed, and it is possible to ask for any updates since a particular state (GetFooUpdates). This simplifies the task of keeping a client-side cache up to date with any changes on the server. JMAP also includes the option of a side-band protocol to proactively notify the client of changes so that it doesn't need to poll periodically. This can use a platform-specific "push" mechanism, if available, or the client can make a persistent connection to the event source URI.

For each of the base types (Messages, Contacts, Calendar Events) there is a synthetic List type that is used to represent the results of a search for that type within a particular account. These search results are treated like a separate data type, as they are expected to be cached by the server. The client can request a "window" into a list by indicating a start position and a limit on the number of entries to report. In the case of MessageLists, there is also an Updates function, so getMessageListUpdates will report any changes to the result of a previous search if those results are still cached by the server.

JMAP extras

While this pattern of containers, lists, and objects covers much of the data model, it is not quite complete. There are three exceptions: Accounts, Threads, and SearchSnippets.

The "Accounts" abstraction allows a single user to have access to multiple accounts; there will always be a primary account but there may also be group accounts or accounts that the user been delegated responsibility for. Accounts are a bit like the three container types in that it is possible to get a list of all the accounts in a single request, though no search filtering is possible. Unlike the container, it is not possible to get a list of Updates, though a client can discover if the list of accounts has changed at all.

"Threads" are lists of related messages. Each message is assigned a threadId by a mechanism determined by the server rather than by the protocol and a given thread is all of the messages with that threadId. This could nearly be implemented with the getMessageList function since it can filter based on threadId, but threads are such a central concept in email handling that the protocol can be more efficient if they are a first-class citizen. For example, the getMessageList function can be asked to fetchMessages in which case it returns not just the list of message IDs, but also (as a separate response) the actual messages themselves (or more precisely: a given set of attributes for each message). In a similar way, it can be asked to fetchThreads and will report a list of all threads found by the search, each represented as a flat list of message IDs without any parent/child relationships exposed. These cannot just be a property of the messages since there will likely be a different number of threads than messages. Having a separate object type makes this easy.

"SearchSnippets" are a bit like Threads in that they exist to optimize a particularly valuable part of the user experience. One of the attributes that can be fetched for any message is a "preview", which is a line or two of the most relevant text, possibly skipping quoted text or salutations. For a specific search filter and a specific message, a SearchSnippet contains an alternate preview text that includes sections of the message that match search strings in the filter and has those strings marked for easy highlighting. It also includes an alternate version of the Subject with similar markup. A getMesssageList request can include a fetchSnippets directive so that these snippets are provided to the client.

Messages, mailboxes, and tags

Messages are undoubtedly the richest and most interesting objects managed by JMAP. Messages are mostly immutable, with the only permitted changes being to change the list of mailboxes the message is a member of and to set or clear one of four flags: isUnread, isFlagged, isAnswered, and isDraft. These roughly correspond to the \Seen, \Flagged, \Answered, and \Draft flags supported by IMAP4. The other two IMAP4 flags: \Deleted and \Recent are not supported by JMAP, presumably because they aren't particularly useful. \Deleted is only needed for a two-stage delete, which is adequately handled by moving a message to a "Trash" folder. \Recent has semantics that are not particularly user-friendly: it is quite possible for the server to be required to clear \Recent before the user has had a chance to see any hint of the message at all.

While IMAP4 allows arbitrary user-defined flags to be assigned to a message, JMAP does not. Instead, it allows a message to be attached to an arbitrary number of user-defined mailboxes. This allows the email client to treat mailboxes like folders, tags, or even both. This last suggests a slight weakness in the protocol. It is quite possible that a user would want some mailboxes to behave like tags and others to behave like folders: when dragged to a folder, a message would be removed from the current folder, while dragging to a tag would just add that tag. A client cannot support this distinction without imposing its own interpretation of mailbox names, such as assuming that any child of the "tags" mailbox should be treated as a tag.

A JMAP Mailbox can have a mustBeOnlyMailbox flag set, in which case messages can only be in that mailbox if they aren't in any other. This can force a mailbox to behave like a folder, but then none of the messages in it can belong to any tag-like mailboxes, so it seems rather restrictive.

Mailbox roles and sending email

Only five years ago, IMAP gained the concept of special-use mailboxes thanks to RFC6154. This defined mailbox attributes like \Drafts, \Sent, \Junk, and \Trash so that an IMAP client could use those flags instead of depending on hard-coded (English language) names. JMAP has a matching concept, referred to as mailbox "roles", with some minor variations such as \Junk being replaced by spam. One role that deserves special attention is the outbox role.

When a message is placed in an outbox mailbox, it must have the isDraft flag set; the implication is that it has been queued for delivery. At some future time, which may be immediately or may be when the time in the "Date:" header is reached, the JMAP server should attempt to deliver the message. Once this attempt completes, the message is deleted from the outbox and re-created in the folder with sent role. The specification is not explicit on what happens if the delivery attempt fails. The likely implication is that a delivery status email would appear in the inbox. Given how easy it can be to miss or be confused by such messages, protocol support that would allow the client to highlight and help resolve failures would be quite valuable.

Spam filtering is important in any modern email application and JMAP provides some help in this direction. Aside from the spam role already mentioned, which makes it easy to find spam, there is a dedicated "reportMessages" request that can be used to report that a message is, or is not, spam in the eyes of the user. This is typically used to train the per-user spam filter. Unfortunately, the specification does not spend time justifying various decisions, so we do not know why this request is needed rather then allowing spam status to be set by simply moving messages into or out of the spam mailbox. All we know is that the specification is quite explicit that moving messages like this would be a separate operation from reporting their spam status.

Managing MIME

JMAP is able to handle messages as uninterpreted blobs of RFC2822 text or as completely parsed messages with various headers and details of attachments available as attributes in the JSON encoding. When collecting a message from the server, a client can ask for specific fields and will get the relevant data in pure UTF-8 with no escaping or transport encoding. When uploading a message to the server, it can list the content of various fields and describe attachments that have previously been uploaded as ordinary files, and the server will combine all the parts together using the appropriate encoding. There is even a provision to extract a textBody from a message that only contains an HTML version, or to get an HTML version of plain text.

While this MIME (Multipurpose Internet Mail Extensions) transcoding is useful, it appears to be focused on matching common usage rather than precisely mirroring the MIME specification. MIME supports generic recursive multipart structuring of messages, where any MIME part can itself be multipart, typically multipart/mixed for attachments, multipart/alternative for different renderings of the same message (text or HTML), or multipart/related for HTML combined with some image files. While the standard allows multiple levels of multiparts and a variety of subtypes of multipart, JMAP knows little of this. There is only one pair of alternatives, text or HTML. Together with this, there are attachments, which might be tagged inline in the multipart/related case. One specific type of attachment — an email message — can result in a recursive structure and JMAP handles this correctly, but no other recursion is apparent. This contrasts with IMAP4, which does support reporting of nested structure in the BODYSTRUCTURE response.

While this is theoretically limiting, it does cover the vast majority of email. Since a client can deal directly with the undecoded RFC2822 message if it chooses, it may be an acceptable tradeoff. Unfortunately, there is no easy way for such a client to determine if there might be anything extra to decode, so it cannot know if it needs to fetch the raw message.

One place where this might be a real problem is with signed or encrypted email. The JMAP specification makes no mention of S/MIME, the Secure MIME standard. While there may be difficult issues around deciding whether a server should be trusted to sign or decrypt messages, it would be nice to have the option, and it would be nice if the server told the client when the message might need decrypting or if a signature needs checking.

What else is missing from JMAP?

Email involves such a rich and varied experience that it is probably impossible for any protocol to really cover everybody's favorite feature. There are, however, two missing features worthy of note that both relate to the processing of mail as it arrives — an area currently unaddressed by JMAP. One is the ability to register a "vacation" message to be automatically sent in reply to incoming messages. This was raised on the discussion list and a possible protocol enhancement to cover it was discussed. This suggests that there is an openness to enhance the protocol to meet requirements.

The other feature, one close to my heart, is saved searches. Whether you use procmail, Sieve-based filtering, or a similar mechanism to direct new messages to different mailboxes based on content, or use Notmuch or similar to perform searches at the moment you open a virtual mailbox, there is a clear need to be able to save rules or searches somewhere; having this exposed in JMAP would be quite helpful. This is even mentioned in the JMAP specification, though clearly as an aside. In the section on negotiating extension support, there are some hypothetical example extensions including "com.fastmail.savedSearch:4". Clearly this need has been thought about, but no clear resolution has been found. It may well be a challenge to provide support in the protocol that is useful to clients without being an undue burden on servers. But it would be a poor email access protocol in this day and age that didn't provide for saved searches in some form.

Status

JMAP is clearly something that FastMail wants people to play with. It has released a proxy server, written in Perl, that provides a gateway between JMAP on the client side, and IMAP and SMTP on the server side. The various parts can be downloaded from GitHub and built without too much difficulty, or a hosted version can be used — though, obviously, don't give that the password of an important email account. The downloads include a simple JavaScript email client that talks JMAP and can be used to complete the experience.

While JMAP is certainly interesting, and ticks a lot of the right boxes, but it is far from certain that it can gather sufficient momentum that anyone other than FastMail and a few niche players will invest in it. To achieve that, it would need to be a lot better than "good enough"; it would need to be "compelling", and probably also "exciting". The proxy server should help to reduce the typical chicken-and-egg problem that new protocols face by making it possible to use a JMAP client even if your service provider doesn't support JMAP natively. Whether this will be enough to encourage people to join the adventure is hard to know. We'll just have to wait and see.


Index entries for this article
GuestArticlesBrown, Neil


to post comments

JMAP — reinventing IMAP

Posted Mar 17, 2016 9:12 UTC (Thu) by pebolle (guest, #35204) [Link] (2 responses)

0) JMAP was new to me, so it's nice to get introduced to it.

1) This puzzled me:
> When a message is placed in an outbox mailbox, it must have the isDraft flag set;
> the implication is that it has been queued for delivery.

Is that really correct? If so, JMAP seems to use a definition of draft that is at odds with the common usage of that term.

JMAP — reinventing IMAP

Posted Mar 17, 2016 9:22 UTC (Thu) by neilbrown (subscriber, #359) [Link] (1 responses)

I think the idea is that messages with "isDraft" set are messages that have been authored locally and not yet sent. When you create a message in the client, it is uploaded to the server, probably placed in the "drafts" folder, almost certainly has the isDraft flag set. Then you move it to "outbox" and off it goes. When it re-appears in "sent" it probably doesn't have isDraft set.

JMAP — reinventing IMAP

Posted Mar 20, 2016 21:58 UTC (Sun) by brong (guest, #87268) [Link]

Indeed, that's how it works.

You can see a pretty horrible implementation of an instantaneous outbox here:

https://github.com/jmapio/jmap-perl/blob/master/JMAP/Imap...

This is the JMAP proxy, which talks IMAP, CalDAV and CardDAV at one side, and JMAP at the other. We're using it for testing client development.

It's pretty much feature complete apart from the authentication flow, and of course the changes to calendaring which will be made alongside the TC-API group at CalConnect, so we can have a shared JSON representation for calendar events. We're hoping to nail down that specification in Hong Kong next month:

https://www.calconnect.org/events/calconnect-xxxvi-april-...

...

The plan for 'outbox' at FastMail once it's finished is that it doesn't actually send immediately. Messages will sit there until they reach their "internaldate" timestamp, and then be sent. A regular append and move without setting internaldate will send within 1 second, but clients will easily be able to set a send time of 30 seconds in the future, and then they can delete the message or move it back to Drafts within that time to abort send.

I blogged about some of the underlying architecture design we'll need for that here:

https://blog.fastmail.com/2015/12/25/a-ghost-of-fastmail-...

Bron

JMAP — reinventing IMAP

Posted Mar 17, 2016 13:26 UTC (Thu) by hkario (subscriber, #94864) [Link] (2 responses)

oh look, another protocol for Gmail to botch or completely ignore

JMAP — reinventing IMAP

Posted Mar 17, 2016 18:52 UTC (Thu) by bronson (subscriber, #4806) [Link]

gmail, and every other mail client in the world.

JMAP — reinventing IMAP

Posted Mar 20, 2016 22:07 UTC (Sun) by brong (guest, #87268) [Link]

It actually fits the gmail model pretty well. We took both GMail's extensions to IMAP and the standards into account when designing JMAP.

Indeed, the current FastMail API (a more rough-around-the-edges precursor to JMAP) was designed by Neil and I sitting opposite each other at our desks in Oslo with very few other distractions. He was designing the data model in Javascript for Overture:

https://github.com/fastmail/overture

And I had just finished a year and a bit of replacing all Cyrus IMAP's core datastructures with safe locking to guarantee CONDSTORE and QRESYNC accuracy as well as enabling efficient cross-datacentre replication, so I knew those standards inside out.

We've build on CONDSTORE in particular, but also on the concepts underlying GMail's X-GM-THRID and X-GM-MSGID fields. The JMAP proxy, when talking to GMail, will use those values (and the "\Allmail" folder with X-GM-LABELS) to get more efficient entire-account syncing without having to re-calculate threads locally.

FastMail exposes a very similar data model via the totally undocumented "CID" field (equivalent to X-GM-THRID - there's a patch to call it THRID if that ever gets standardised) as well as a bunch of commands XCONVFETCH and XCONVMULTISORT which allow cross folder sort, and thread statistic fetches. Our API is written on top of those.

(we don't yet have labels - there's some more Cyrus internal changes requried, but we have a plan that will give complete standards compliant IMAP _and_ labels, and both of them fast, on the same datastore. I should be writing that rather than talking on the internet - but y'know, it's Monday morning and I haven't had coffee yet)

Bron.

JMAP — reinventing IMAP

Posted Mar 18, 2016 3:34 UTC (Fri) by smurf (subscriber, #17840) [Link]

The bad MIME support kills this idea for me, at least in the current incarnation. For instance, certain email servers send a calendar entry alongside text/html and text/plain. Plus there's PGP/MIME alongside S/MIME.

That's too narrowly geared to a "dumb webmailer" frontend. A server which allows for a JSONish approach to accessing MIME emails, with the "dumb" access layered on top of that instead of "instead of that", would be great.

Hopefully, somebody will take the code in that direction.

JMAP — reinventing IMAP

Posted Mar 18, 2016 10:08 UTC (Fri) by miquels (guest, #59247) [Link] (2 responses)

There will be an independent implementation of JMAP soon. Dovecot is one of the most widely used imap/pop/lmtp servers out there, and recently it was announced that the next version, v2.3, will have JMAP support.

Mike.

JMAP — reinventing IMAP

Posted Apr 13, 2016 10:07 UTC (Wed) by inputmice (guest, #108219) [Link] (1 responses)

Do you have a source to back up the claim that dovecot 2.3 will support JMAP? I haven't been able to find any announcement in that regard.

Dovecot: JMAP support in v2.3

Posted May 12, 2016 12:31 UTC (Thu) by johnp (guest, #108732) [Link]

[...] the [HTTP] API was designed to look mostly like JMAP, which we're planning to implement also for v2.3.
Source

JMAP — reinventing IMAP

Posted Mar 18, 2016 22:11 UTC (Fri) by SLi (subscriber, #53131) [Link]

A most excellent article! I hadn't previously heard of JMAP, but this article not only presents it but gives valuable and thoughtful criticism of possible shortcomings of the protocol.

JMAP — reinventing IMAP

Posted Mar 26, 2016 20:01 UTC (Sat) by jengelh (guest, #33263) [Link]

>A modern email client [...] requires the ability to send email, look up a contacts database, and often to query and update a calendar. [...] Having a single protocol [...]

Why does that sound so much like MAPI/ActiveSync, and xkcd.com/927 .

JMAP — reinventing IMAP

Posted Mar 26, 2016 21:44 UTC (Sat) by eduard.munteanu (guest, #66641) [Link] (24 responses)

The least they could have done by 2016: reaching for a suitable binary encoding. With all that metadata, blobs etc. flying around, I suspect JSON won't be nice. JSON is never nice. How much actual thought went into this?

JMAP — reinventing IMAP

Posted Mar 26, 2016 22:28 UTC (Sat) by neilbrown (subscriber, #359) [Link] (23 responses)

The section "Why is it not a binary protocol?" of http://jmap.io/ might give you some answers.

JMAP — reinventing IMAP

Posted Mar 27, 2016 0:28 UTC (Sun) by magila (guest, #49627) [Link] (22 responses)

> However, history has shown text-based protocols are much easier to debug

This is highly debatable, especially when we have tools like wireshark dissectors. Frankly, if you can't handle debugging binary protocols you have no business implementing network protocols for widespread use. If anything, the history of test-based protocols has been defined by interop clusterfucks like HTTP, SMTP, IRC, etc.

> No need to write new custom, error-prone parsers.

The difficulty of implementing binary protocols has been wildly overstated by text-based advocates. Even aside from that, this argument only applies to protocols with a custom bitstream format. If they're too squeamish for some simple pointer arithmetic then they can use an interchange format like Cap'n Proto or Protocol Buffers and get the same experience as JSON but with a richer set of data types, less complexity, and much better efficiency.

> The difference in speed is likely to be minimal, especially if you GZIP the exchange

It's not really about speed, it's about having a simple and unambiguous protocol. The complexity and pervasive ambiguity of text-based protocols is what leads to the aforementioned interop problems. Also, beware information leakage when sending compressed data over an encrypted channel.

JMAP — reinventing IMAP

Posted Mar 27, 2016 3:06 UTC (Sun) by Cyberax (✭ supporter ✭, #52523) [Link] (21 responses)

> If anything, the history of test-based protocols has been defined by interop clusterfucks like HTTP, SMTP, IRC, etc.
And the sterling success of ASN.1-based protocols, like H.323 and the OSI stack. Sure.

Binary encodings are the wrong solution pretty much always. Their only advantage is usually a slightly more efficient encoding, but that also is usually questionable.

First, simple tagged key-value binary encodings (like BSON) bring absolutely nothing, they just help the parser to be a bit more efficient. And if we look at SMTP or HTTP then _parsing_ has never really been a problem, it's interpretation of the parsed data that is tricky.

Second, if we go down the rabbit hole of data schemas and tight specifications then we get ASN.1 as a result or something closely approximating it. With all the problems of versioning, global type identification, type mapping and general awfulness.

Third, anybody who thinks that "dissectors in Wireshark" are enough for debugging is a freaking idiot and should be forced to debug interoperability problems in proprietary SCTP implementations by using partially dumped packets (up to the first \0 symbol) in log files. Until they see the light or kill themselves, whichever comes first.

I'm _really_ glad that the industry settled on mostly-text specifications like JSON (even though I'd like something just a _little_ bit more carefully specified). Unfortunately, HTTP/2 happened but it can be easily ignored for now.

JMAP — reinventing IMAP

Posted Mar 27, 2016 9:41 UTC (Sun) by kentonv (subscriber, #92073) [Link] (9 responses)

> Second, if we go down the rabbit hole of data schemas and tight specifications then we get ASN.1 as a result or something closely approximating it. With all the problems of versioning, global type identification, type mapping and general awfulness.

I guess you haven't worked with Protobuf or Cap'n Proto, which are binary formats that handle versioning arguably more cleanly than JSON, yet have type-safe schemas unlike BSON?

JMAP — reinventing IMAP

Posted Mar 27, 2016 20:09 UTC (Sun) by Cyberax (✭ supporter ✭, #52523) [Link] (8 responses)

> I guess you haven't worked with Protobuf or Cap'n Proto, which are binary formats that handle versioning arguably more cleanly than JSON, yet have type-safe schemas unlike BSON?
I worked with protobuf quite a bit (haven't had a chance to play with Cap'n Proto yet). For example, I reverse engineered the Android Auto protocol recently ( https://github.com/Cyberax/aauto ) which is totally protobuf-based.

Protobuf doesn't really have a schema. It's more like JSON actually, the structures simply use tag numbers instead of field names and there are slightly more data types available. Versioning support is mostly non-existant - unknown fields are just ignored and conflicting definitions cause havoc.

And there's now a canonical JSON mapping for protobuf as well.

I've read several years ago a post about protobuf from a guy in Google - he wrote that protobuf actually slightly predates JSON and these days they would have just used pure JSON instead.

JMAP — reinventing IMAP

Posted Mar 28, 2016 17:34 UTC (Mon) by kentonv (subscriber, #92073) [Link] (7 responses)

So... I am actually the primary author of Google's open source Protobuf release (though not the original inventor of the protocol, and I haven't worked on it in ~5 years).

> Protobuf doesn't really have a schema.

That's not true at all. https://developers.google.com/protocol-buffers/docs/proto

The schema is not transmitted on the wire. The schema is used to encode/decode on each end.

The raw wire format is numeric tags and values, but no one actually uses protobuf without schemas.

"Reverse engineering" protobufs usually means:
1. Feed a few messages to protoc --decode_raw.
2. Guess the meaning of each numeric tag.
3. Write a .proto file assigning names and types to the tags.

Or, better yet:
1. Yank the protobuf descriptors out of the app binary, which provide the complete schema.

I don't see any .proto files in your repo nor any reference to libprotobuf. Did you reverse engineer from the byte level and write your own decoder?

> Versioning support is mostly non-existant - unknown fields are just ignored

Yes, that's the best way to do versioning, and is exactly the same strategy people use with JSON. Using actual version numbers is a pain as you end up with lots of branchy code to handle every version. That's what Google had before protobuf, and protobuf was explicitly designed to fix it.

Protobuf is better than JSON, though, because with Protobuf you have a schema where you can declare default values to replace things missing on the wire, whereas with JSON you have to check for the existence and type of every single field before accessing it or otherwise risk unexpected exceptions and security bugs.

> and conflicting definitions cause havoc.

In my experience this is not a problem people run into often. You have one owner of the protocol who decides which changes become official. If you want a protocol to be extensible, you use "extensions" which allow third parties to extend the protocol without conflicting (or in proto3 you use the "Any" type).

(Moreover, conflicting definitions would cause an equal amount of havoc for JSON.)

> he wrote that protobuf actually slightly predates JSON and these days they would have just used pure JSON instead.

Sorry, that post was wrong. That's definitely not the opinion held by senior engineers at Google.

1. The importance of type-safety and the implicit documentation provided by schemas is widely recognized inside Google.

2. The vast majority of code at Google is written in statically-typed languages (C++ and Java) where JSON is highly annoying to use due to being dynamic. Protobuf uses schemas to generate classes with pleasant interfaces.

3. Protobuf parsing as-is is responsible for several percent of CPU cycles fleet-wide -- some servers report 30% or more. That equates to many millions of dollars annually. JSON would be an order of magnitude slower, which would cost a massive amount of money.

4. Google stores petabytes of data in Protobuf format. This data would be much larger as JSON. No, compression doesn't magically fix it (compressed protobufs are still much smaller than compressed JSON, particularly for highly-structured (i.e. not textual) data).

5. Similarly, the network bandwidth overhead would be unacceptable.

6. The latency cost of encoding and compressing JSON would be unacceptable for many systems in Google even if there were CPU cycles to spare.

JMAP — reinventing IMAP

Posted Mar 28, 2016 19:26 UTC (Mon) by Cyberax (✭ supporter ✭, #52523) [Link] (6 responses)

> The schema is not transmitted on the wire. The schema is used to encode/decode on each end.
> The raw wire format is numeric tags and values, but no one actually uses protobuf without schemas.
Well, I do.

> Yank the protobuf descriptors out of the app binary, which provide the complete schema.
That might not be legal, though.

> I don't see any .proto files in your repo nor any reference to libprotobuf. Did you reverse engineer from the byte level and write your own decoder?
Yep. Protobuf is self-describing so it wasn't complicated. And adding .proto files into the mix was just not worth it.

> Yes, that's the best way to do versioning, and is exactly the same strategy people use with JSON.
But that's exactly my point. JSON is semantically very similar to protobuf wire format - the differences are really minor (tags instead of names and more integer types in protobuf).

All the protobuf "smarts" are in the mapping layer which enforces the schema, provides default values and so on. This mapping layer can be built atop JSON just as easily (as many people have done, many times in many companies) to provide nice statically-typed interfaces.

You can _almost_ treat protobufs as an encoding for JSON. Contrast it with ASN.1 PER where you actually have to use the schema to decode raw messages as field types and offsets are not transmitted on the wire.

JMAP — reinventing IMAP

Posted Mar 28, 2016 20:04 UTC (Mon) by kentonv (subscriber, #92073) [Link] (5 responses)

Yes, it should generally be easy to map any records-and-lists format to any other records-and-lists format. Indeed, you could pair the Protobuf generated code with JSON encoding, and some people do this.

But once you have those generated classes, then it makes no difference to the developer whether the bytes were JSON or Protobuf. Protobuf can produce a textual representation for debugging as needed. Why waste cycles and bytes encoding human-readable text all the time?

It sounds like you're the kind of person who reads raw network dumps a lot, but that you're also the kind of person who doesn't like to use the tools provided to you for this purpose, so I guess that would explain a preference for human-readable messages on the wire. But, I think between using the tools and spending millions of dollars on additional computer hardware, using the tools seems more reasonable.

> And adding .proto files into the mix was just not worth it.

Well, using libprotobuf and a .proto file would have saved you from writing quite a bit of code.

JMAP — reinventing IMAP

Posted Mar 28, 2016 21:19 UTC (Mon) by Cyberax (✭ supporter ✭, #52523) [Link] (4 responses)

> But once you have those generated classes, then it makes no difference to the developer whether the bytes were JSON or Protobuf. Protobuf can produce a textual representation for debugging as needed.
But it doesn't do it normally.

> Why waste cycles and bytes encoding human-readable text all the time?
The amount of wasted cycles isn't noticeable even at 10G wire speeds.

> It sounds like you're the kind of person who reads raw network dumps a lot
I don't read network dumps normally, but I do have to do it now and when. Usually during a high-stress breakage situation.

> but that you're also the kind of person who doesn't like to use the tools provided to you for this purpose, so I guess that would explain a preference for human-readable messages on the wire.
I actually like tools that map a domain object model into messages/database/whatever. But they do have their price in being opaque when you have to diagnose a problem.

JSON helps in this regard by making the whole stack less opaque.

> Well, using libprotobuf and a .proto file would have saved you from writing quite a bit of code.
Probably not in this case.

JMAP — reinventing IMAP

Posted Mar 28, 2016 21:47 UTC (Mon) by kentonv (subscriber, #92073) [Link] (3 responses)

> The amount of wasted cycles isn't noticeable even at 10G wire speeds.

Uh, what? The absolute fastest JSON parsers top out around a gigabit per second, consuming 100% of CPU time on parsing, in idealized benchmark scenarios. You're asserting that you can do 10gbps and the CPU usage isn't even noticeable?

JMAP — reinventing IMAP

Posted Mar 28, 2016 22:05 UTC (Mon) by Cyberax (✭ supporter ✭, #52523) [Link] (2 responses)

> Uh, what? The absolute fastest JSON parsers top out around a gigabit per second, consuming 100% of CPU time on parsing, in idealized benchmark scenarios. You're asserting that you can do 10gbps and the CPU usage isn't even noticeable?
Yes, when compared to parsing protobufs. The fastest possible JSON parser is based on bitslicing and SSE: http://parabix.costar.sfu.ca/ and with it you can get 10G performance.

JMAP — reinventing IMAP

Posted Mar 28, 2016 22:32 UTC (Mon) by kentonv (subscriber, #92073) [Link] (1 responses)

... no, sorry. That's not a JSON parser, it's a regex matcher. They claim 1GB/s (close to 10gbps) performance matching "\p{Greek}". There's a whole lot more work involved in parsing JSON.

Generally the fastest JSON parser is RapidJSON. Protobuf beats it handily (3x or more) in most benchmarks, e.g.

https://github.com/erickt/rust-serialization-benchmarks

(Cap'n Proto in turn handily beats Protobuf and can in fact saturate a 10gbps link.)

JMAP — reinventing IMAP

Posted Mar 29, 2016 0:18 UTC (Tue) by Cyberax (✭ supporter ✭, #52523) [Link]

> ... no, sorry. That's not a JSON parser, it's a regex matcher.
They actually have a full XML parser with same performance characteristics. There's a JSON parser there as an example.

> Generally the fastest JSON parser is RapidJSON. Protobuf beats it handily (3x or more) in most benchmarks, e.g.
I have my own Parabix-based JSON parser that is used in production to do switching for a JSON-based UDP protocol. It saturates multiple 10G links (though it's also multithreaded).

And yeah, even 3x performance difference in _parsing_ is pretty much negligible these days.

JMAP — reinventing IMAP

Posted Mar 27, 2016 12:43 UTC (Sun) by smurf (subscriber, #17840) [Link] (8 responses)

The point is that JSON is the "native protocol" of JavaScript, and the main point of the whole exercise is to facilitate a simple interface for a web-based email frontend with as little effort as possible. Thus JSON wrapped in HTTP, instead of _anything_ more sensible. Thus no explicit support for arbitrary MIME structures, or multipart/anything-except-alternate messages, let alone signing and encryption. Heck, the protocol is not even able to handle Exchange's text/plain|html|calendar multipart messages in a sensible way. I could go on.

I really doubt that JSON is in any way more debuggable than a binary protocol. If you ever saw a nested multi-page JSON dump without any formatting whatsoever, and needed to start count braces to make heads or tails of it, you'll know what I mean.

JMAP — reinventing IMAP

Posted Mar 27, 2016 19:42 UTC (Sun) by bronson (subscriber, #4806) [Link] (6 responses)

> If you ever saw a nested multi-page JSON dump without any formatting whatsoever, and needed to start count braces to make heads or tails of it, you'll know what I mean.

Why wouldn't you install JSON Formatter and then click on arrows to navigate?

JMAP — reinventing IMAP

Posted Mar 27, 2016 20:05 UTC (Sun) by zlynx (guest, #2285) [Link] (1 responses)

If you need to do that then why aren't you using a binary protocol and a parser / dissector?

JMAP — reinventing IMAP

Posted Mar 28, 2016 4:26 UTC (Mon) by bronson (subscriber, #4806) [Link]

Because that's a much bigger pain in the ass of course, even for trivially small data.

JMAP — reinventing IMAP

Posted Mar 28, 2016 7:19 UTC (Mon) by smurf (subscriber, #17840) [Link] (3 responses)

Because writing a JSON browser plugin for Wireshark is something nobody has done yet, and copying/pasting huge hunks of JSON between it and some program or browser window is about as annoying as squinting at squiggles.

IMHO, encoding some data into a protocol should be a function in the mathematical sense, i.e. any possible payload shall encode to exactly one datagram. JSON lets you play way too many games with whitespace, escaping and whatnot for that to be realistic.

And yes, computers are fast enough to eat JSON at mind-boggling speed these days, but if messagepack or smile or … can do the exact same thing with even less work (and no ambiguity), why not use that?

JMAP — reinventing IMAP

Posted Mar 28, 2016 7:32 UTC (Mon) by Cyberax (✭ supporter ✭, #52523) [Link] (2 responses)

> Because writing a JSON browser plugin for Wireshark is something nobody has done yet
Uhm. Wireshark supports JSON out of box. Try it.

> And yes, computers are fast enough to eat JSON at mind-boggling speed these days, but if messagepack or smile or … can do the exact same thing with even less work (and no ambiguity), why not use that?
Because once it breaks (and it WILL break, no doubt about it) debugging the breakage will be a nightmare.

Everything, and I mean it, EVERYTHING, in software should be designed with the assumption: "What is going to happen _when_ it breaks?"

JMAP — reinventing IMAP

Posted Mar 28, 2016 7:59 UTC (Mon) by neilbrown (subscriber, #359) [Link] (1 responses)

> Uhm. Wireshark supports JSON out of box. Try it.

I can't see how this is at all relevant for JMAP. JMAP is currently only defined over HTTPS, and until wireshark can see inside TLS, it doesn't really matter if it can decode the JSON or not.

JMAP — reinventing IMAP

Posted Mar 28, 2016 8:01 UTC (Mon) by Cyberax (✭ supporter ✭, #52523) [Link]

Wireshark can see inside TLS: https://wiki.wireshark.org/SSL

JMAP — reinventing IMAP

Posted Mar 27, 2016 19:51 UTC (Sun) by Cyberax (✭ supporter ✭, #52523) [Link]

> The point is that JSON is the "native protocol" of JavaScript
That hasn't been true for quite a long time. Initially JSON was indeed something that could just be eval()'ed by JS, but nobody does this anymore even in JS these days.

> hus JSON wrapped in HTTP, instead of _anything_ more sensible. Thus no explicit support for arbitrary MIME structures, or multipart/anything-except-alternate messages
Why should there _be_ such support? JSON can be arbitrarily nested so you don't _need_ anything special to support complex MIME structures and multiparts.

> let alone signing and encryption.
JSON signing and encryption is easy: https://tools.ietf.org/html/rfc7515 Now try that with plain email...

> I really doubt that JSON is in any way more debuggable than a binary protocol. If you ever saw a nested multi-page JSON dump without any formatting whatsoever, and needed to start count braces to make heads or tails of it, you'll know what I mean.
And now imagine that you don't even _have_ braces, instead you have binary offsets. Or worse, no offsets at all and structure information kept separately.

Besides, pretty-printing JSON or extracting well-formed fragments from it is trivial these days.

JMAP — reinventing IMAP

Posted Apr 6, 2016 15:24 UTC (Wed) by eduard.munteanu (guest, #66641) [Link] (1 responses)

Parsing things like HTTP is plainly awful for the writer of said parser. The spec is a mess. Binary formats are usually much easier to parse and quite a bit more efficient at dealing with arbitrary data.

Stuff like whitespace trimming, folding multiple lines and so on really have no place in a protocol that's intended to be parsed by machines. Or do you intend to run human servers?

I also doubt text-based protocols are easier to debug. Perhaps that's easier in a "two sticks light a fire easier than a broken lighter" sense. You should be using a proper parser to debug binary formats and I think that's a point of contention. Seeing the many kludgy scripts out there using regexps to parse stuff, it's no wonder people have difficulties and dismiss binary formats as difficult.

JMAP — reinventing IMAP

Posted Apr 11, 2016 14:25 UTC (Mon) by jchaxby (subscriber, #63942) [Link]

There's an important point to be made in favour of text-based protocols: debug logging tends to log protocol units. Do you find it easier to read "02 63 6e 04 03 6a 63 68" or cn=jch (I think I carefully chose the right part of the LDAP filter)?

Parsing text is not really any different to parsing text. In the example above you've got length counted strings and 0x04 in the middle or you've got strings separated by "=".

Arguing for binary-based mail related protocols is a bit fruitless. At some stage you're going to be parsing RFC5321 and RFC5322. If you can't handle text parsing then you're already in serious trouble :) You'd better be able to parse LDIF and LDAP filter strings as well.

My reaction to JMAP is basically to run around cheering. I wrote an IMAP server and the problems with the protocol are legion. Parsing is the easy bit, getting the protocol to actually work sensibly is not at all easy. I also wrote an LDAP server. Oddly enough, parsing the protocol is the easy bit. Logging was a bit harder: logging binary is not exactly friendly (and yes, you do want to log what you got and what you thought it was). The actual protocol was rather easier to deal with, apart from access control anyway.

Parsing is well understood, it's been well-understood for decades. You can optimize a protocol for speed, but that doesn't mean that binary protocols are faster or more desirable, it means that for particular problem domains an optimized protocol is a good thing. The bottle neck with my mail server(s) and LDAP server(s) was never the protocol.


Copyright © 2016, Eklektix, Inc.
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds