The first article of the series explained the principles of the google-cast protocol, and the technologies that it uses. In this second part, we will see how the receiver device is discovered, and how the sender device connects to it.
Discovery - The Early Days
When the first ChromeCast was launched, no SDK was available. The receiver discovery was done via SSDP. This was the discovery part of the communication protocol used by the ChromeCast at the time: DIAL. Although DIAL is an open protocol, specified by Netflix, its usage in the ChromeCast was private.
Since DIAL is fully documented, the protocol was quickly reverse engineered and alternative receiver implementations such as leapcast appeared. However, these projects soon became useless once Google published the ChromeCast firmware using a completely new protocol, and an associated SDK.
Now, the discovery is done with the SSDP alternative (and much more popular) protocol: mDNS and DNS-SD. mDNS/DNS-SD are server-less hostname and service discovery protocols (as is SSDP), but based on DNS entries. google-cast exposes its service on the LAN as googlecast. One can find all google-cast devices on the LAN easily from a linux machine:
$ avahi-browse -r _googlecast._tcp
The service definition is:
- service name: googlecast
- service type: tcp
- service port: 8009
The service also exposes a TXT entry containing other information:
|id||The unique id of the device||A string|
|md||The device model type||Chromecast Nexus Player Chromecast Audio|
|ve||This seems to identify a version of the protocol||02: On ChromeCast 1 devices. 04: On Nexus player devices. 05: On ChromeCast audio devices.|
|ic||The path of the icon associated to the device||/setup/icon.png|
|ca||The certificate authority used by the device||4100: on Chromecast 1 devices. 5: on other devices.|
|fn||The friendly name that the user can configure. This is the name that is printed when the list of receiver devices is shown on the sender application to select the output.|
Once the service has been discovered on the network, the sender connects to the receiver device via a persistent SSL connection, on the port advertised in the DNS-SD announcement (i.e. 8009). The cipher chain is similar on all devices, but not the same one:
- ChromeCast 1: ECDHE-RSA-AES128-GCM-SHA256
- Nexus Player: ECDHE-RSA-AES256-GCM-SHA384
- ChromeCast Audio: ECDHE-RSA-AES256-SHA
As explained in the first article, this SSL connection is used for all communications between the sender device and the receiver device. Different communication channels are multiplexed in this SSL connection.
The communication is message based. Messages are serialized with protobuf. Protobuf is the format that made binary serialization popular. Since google open-sourced it, many other alternative appeared. These alternatives often claim to be more efficient than protobuf, and they often make this metric as the only justification to use them instead of protobuf. However, AFAIK, none of them provide the most interesting features of protobuf:
- Some tools to generate serialization/deserialization code in many languages.
- Backward compatibility of messages and optional messages fields.
The first point allows to use protobuf very easily. The second one is very important as soon as you design something that will evolve : When you use protobuf and you add new fields to existing messages, legacy clients will support the new fields but just ignore the new fields.
In order to send the messages on the connection, a header is added before the messages. This header is simply the size of the message that follows. This is necessary because the link between the sender and receiver device is stream based (TCP), and the protobuf format requires to know the size of the message to parse it.
Adding this header allows the recipient of the message to wait until all data of the message is received before parsing it. If an error is detected during the parsing of the message, then the SSL connection is closed.
The googlecast protobuf declaration file is available in the chromium sources: https://chromium.googlesource.com/chromium/src.git/+/master/extensions/common/api/cast_channel/cast_channel.proto
A CastMessage has the following simple structure:
- an union of utf8 or binary payload
All fields are quite self-explanatory, and the .proto file contains many comments. The protocol version is always 1. The source and destination fields allows to identify sources and destinations. These fields are part of the multiplexing of the SSL connection. These identifiers can identify the device or an application. So several ids can be used in the same connection so that different applications or system features can communicate at the same time between a sender and a receiver device.
The namespace is another part of the multiplexing of the SSL connection. A namespace identifies for what feature the message is intended. Finally, there is a payload ending the message. The payload can be either a binary blob or a UTF8 string. Almost all messages use a string payload with a JSON object encoded in it.
To Be Continued
The cast_channel.proto file also contains definitions of other messages used to authenticate the receiver device. These messages will be studied in detail in the following article.