• Stars
    star
    758
  • Rank 59,918 (Top 2 %)
  • Language
    JavaScript
  • License
    MIT License
  • Created over 10 years ago
  • Updated over 2 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

An implementation of the Chromecast CASTV2 protocol

castv2

NPM version Dependency Status npm

An implementation of the Chromecast CASTV2 protocol

This module is an implementation of the Chromecast CASTV2 protocol over TLS. The internet is very sparse on information about the new Chromecast protocol so big props go to github.com/vincentbernat and his nodecastor module that helped me start off on the right foot and save a good deal of time in my research.

The module provides both a Client and a Server implementation of the low-level protocol. The server is (sadly) pretty useless because device authentication gets in the way for now (and maybe for good). The client still allows you to connect and exchange messages with a Chromecast dongle without any restriction.

Installation

$ npm install castv2

On windows, to avoid native modules dependencies, use

$ npm install castv2 --no-optional

Usage

var Client = require('castv2').Client;
var mdns = require('mdns');

var browser = mdns.createBrowser(mdns.tcp('googlecast'));

browser.on('serviceUp', function(service) {
  console.log('found device %s at %s:%d', service.name, service.addresses[0], service.port);
  ondeviceup(service.addresses[0]);
  browser.stop();
});

browser.start();

function ondeviceup(host) {

  var client = new Client();
  client.connect(host, function() {
    // create various namespace handlers
    var connection = client.createChannel('sender-0', 'receiver-0', 'urn:x-cast:com.google.cast.tp.connection', 'JSON');
    var heartbeat  = client.createChannel('sender-0', 'receiver-0', 'urn:x-cast:com.google.cast.tp.heartbeat', 'JSON');
    var receiver   = client.createChannel('sender-0', 'receiver-0', 'urn:x-cast:com.google.cast.receiver', 'JSON');

    // establish virtual connection to the receiver
    connection.send({ type: 'CONNECT' });

    // start heartbeating
    setInterval(function() {
      heartbeat.send({ type: 'PING' });
    }, 5000);

    // launch YouTube app
    receiver.send({ type: 'LAUNCH', appId: 'YouTube', requestId: 1 });

    // display receiver status updates
    receiver.on('message', function(data, broadcast) {
      if(data.type = 'RECEIVER_STATUS') {
        console.log(data.status);
      }
    });
  });

}

Run it with the following command to get a full trace of the messages exchanged with the dongle.

$ DEBUG=* node example.js

Protocol description

This is an attempt at documenting the low-level protocol. I hope it will give sender-app makers a clearer picture of what is happening behind the curtain, and give the others ideas about how this kind of protocol can be implemented. The information presented here has been collated from various internet sources (mainly example code and other attempts to implement the protocol) and my own trial and error. Correct me as needed as I may have gotten concepts or namings wrong.

The TLS / Protocol Buffers layer

The client connects to the Chromecast through TLS on port 8009. Once the connection is established server and client exchange length-prefixed binary messages (that we'll call packets).

Packets have the following structure :

+----------------+------------------------------------------------+
| Packet length  |               Payload (message)                |
+----------------+------------------------------------------------+

Packet length is a 32 bits Big Endian Unsigned Integer (UInt32BE in nodejs parlance) that determines the payload size.

Messages are serialized with Protocol Buffers and structured as follows (excerpt of cast_channel.proto with comments stripped) :

message CastMessage {
  enum ProtocolVersion {
    CASTV2_1_0 = 0;
  }
  required ProtocolVersion protocol_version = 1;

  required string source_id = 2;
  required string destination_id = 3;

  required string namespace = 4;

  enum PayloadType {
    STRING = 0;
    BINARY = 1;
  }
  required PayloadType payload_type = 5;

  optional string payload_utf8 = 6;
  optional bytes payload_binary = 7;
}

The original .proto file can also be found in the Chromium source tree.

Using this structure the sender and receiver platforms (eg. The Chrome browser and the Chromecast device) as well as sender and receiver applications (eg. a Chromecast receiver app and a Chrome browser sender app for YouTube) communicate on channels.

Senders and receivers identify themselves through IDs : source_id and destination_id. The sending platform (eg. the Chrome browser) usually uses sender-0. The receiving platform (the Chromecast dongle) uses receiver-0. Other senders and receivers use identifiers such as sender-sdqo7ozi6s4a, client-4637 or web-4. We'll dig into that later.

Namespaces

Senders and receivers communicate through channels defined by the namespace field. Each namespace corresponds to a protocol that can have its own semantics. Protocol-specific data is carried in the payload_utf8 or payload_binary fields. Either one or the other is present in the message depending on the payload_type field value. Thanks to that, applications can define their own protocols and transparently exchange arbitrary data, including binary data, alleviating the need to establish additional connections (ie. websockets).

Though, many protocols use JSON encoded messages / commands, which makes them easy to understand and implement.

Each sender or receiver can implement one or multiple protocols. For instance the Chromecast platform (receiver-0) implements the protocols for the following namespaces : urn:x-cast:com.google.cast.tp.connection, urn:x-cast:com.google.cast.tp.heartbeat, urn:x-cast:com.google.cast.receiver and urn:x-cast:com.google.cast.tp.deviceauth.

Communicating with receivers

Before being able to exchange messages with a receiver (be it an application or the platform), a sender must establish a virtual connection with it. This is accomplished through the urn:x-cast:com.google.cast.tp.connection namespace / protocol. This has the effect of both allowing the sender to send messages to the receiver, and of subscribing the sender to the receiver's broadcasts (eg. status updates).

The protocol is JSON encoded and the semantics are pretty simple :

Message payload Description
{ "type": "CONNECT" } establishes a virtual connection between the sender and the receiver
{ "type": "CLOSE" } closes a virtual connection

The sender may receive a CLOSE message from the receiver that terminates the virtual connection. This sometimes happens in error cases.

Once the virtual connection is established messages can be exchanged. Broadcasts from the receiver will have a * value for the destination_id field.

Keeping the connection alive

Connections are kept alive through the urn:x-cast:com.google.cast.tp.heartbeat namespace / protocol. At regular intervals, the sender must send a PING message that will get answered by a PONG. The protocol is JSON encoded.

Message payload Description
{ "type": "PING" } notifies the other end that we are sill alive
{ "type": "PONG" } the other end acknowledges that we are

Failing to do so will lead to connection termination. The default interval seems to be 5 seconds. This protocol allows the Chromecast to detect unresponsive / offline senders much quicker than the TCP keepalive mechanism.

Device authentication

Device authentication enables a sender to authenticate a Chromecast device. Authenticating the device is purely optional from a sender's perspective, though the official SDK libraries do it to prevent rogue Chromecast devices to communicate with the official sender platforms. Device authentication is taken care of by the urn:x-cast:com.google.cast.tp.deviceauth namespace / protocol.

First, the sender sends a challenge message to the platform receiver receiver-0 which responds by either a response message containing a signature, certificate and a variable number of certificate authority certificates that the sent certificate is verified against or an error message. These 3 payloads are protocol buffers encoded and described in cast_channel.proto as follows :

message AuthChallenge {
}

message AuthResponse {
  required bytes signature = 1;
  required bytes client_auth_certificate = 2;
  repeated bytes client_ca = 3;
}

message AuthError {
  enum ErrorType {
    INTERNAL_ERROR = 0;
    NO_TLS = 1;  // The underlying connection is not TLS
  }
  required ErrorType error_type = 1;
}

message DeviceAuthMessage {
  optional AuthChallenge challenge = 1;
  optional AuthResponse response = 2;
  optional AuthError error = 3;
}

The challenge message is empty in the current version of the protocol (CAST v2.1.0), yet official sender platforms are checking the returned certificate and signature. Details of the verification process can be found in this issue.

Controlling applications

The platform receiver receiver-0 implements the urn:x-cast:com.google.cast.receiver namespace / protocol which provides an interface to launch, stop, and query the status of running applications. receiver-0 also broadcast status messages on this namespace when other senders launch, stop, or affect the status of running apps. It also allows checking the app for availability.

The protocol is JSON encoded and is request / response based. Requests include a type field containing the type of the request, namely LAUNCH, STOP, GET_STATUS and GET_APP_AVAILABILITY, and a requestId field that will be reflected in the receiver's response and allows the sender to pair request and responses. requestId is not shown in the table below but must be present in every request. In the wild, it is an initially random integer that gets incremented for each subsequent request.

Message payload Description
{ "type": "LAUNCH", appId: <string> } launches an application
{ "type": "STOP", sessionId: <string> } stops a running instance of an application
{ "type": "GET_STATUS" } returns the status of the platform receiver, including details about running apps.
{ "type": "GET_APP_AVAILABILITY", appId: <array> } returns availability of requested apps. appId is an array of application IDs.

appId may be eg. YouTube or CC1AD845 for the Default Media Receiver app. A sessionId identifies a running instance of an application and is provided in status messages.

As these requests affect the receiver's status they all return a RECEIVER_STATUS message of the following form :

{
  "requestId": 8476438,
  "status": { 
    "applications": [
      { "appId": "CC1AD845",
        "displayName": "Default Media Receiver",
        "namespaces": [ 
          "urn:x-cast:com.google.cast.player.message",
          "urn:x-cast:com.google.cast.media"
        ],
        "sessionId": "7E2FF513-CDF6-9A91-2B28-3E3DE7BAC174",
        "statusText": "Ready To Cast",
        "transportId":  "web-5" }
    ],
    "isActiveInput": true,
    "volume": { 
      "level": 1,
      "muted": false 
    }
  },
  "type": "RECEIVER_STATUS"
}

This response indicates an instance of the Default Media Receiver is running with sessionId 7E2FF513-CDF6-9A91-2B28-3E3DE7BAC174. namespaces indicates which protocols are supported by the running app. This could allow any sender application implementing the media protocol to control playback on this session.

Another important field here is transportId as it is the destinationId to be used to communicate with the app. Note that the app being a receiver like any other you must issue it a CONNECT message through the urn:x-cast:com.google.cast.tp.connection protocol before being able to send messages. In this case, this will have the side effect of subscribing you to media updates (on the media channel) of this Default Media Player session.

You can join an existing session (launched by another sender) by issuing the same CONNECT message.

Controlling device volume

receiver-0 allows setting volume and muting at the device-level through the SET_VOLUME request on urn:x-cast:com.google.cast.receiver.

Message payload Description
{ "type": "SET_VOLUME", "volume": { level: <float> } } sets volume. level is a float between 0 and 1
{ "type": "SET_VOLUME", "volume": { muted: <boolean> } } mutes / unmutes. muted is true or false

Contributors

More Repositories

1

node-castv2-client

A Chromecast client based on the new (CASTV2) protocol
JavaScript
638
star
2

b-spline

B-spline interpolation
JavaScript
287
star
3

node-upnp-mediarenderer-client

An UPnP/DLNA MediaRenderer client
JavaScript
121
star
4

node-google-search-scraper

Google search scraper with captcha solving support
JavaScript
82
star
5

duckduckgo

Simple duckduckgo results scraping
Python
65
star
6

pop-buffer

Progressive encoding for 3D meshes
JavaScript
59
star
7

styx

Simple, high-performance event streaming broker
Go
52
star
8

cubic-hermite-spline

Cubic Hermite spline interpolation
JavaScript
44
star
9

parse-cube-lut

Cube LUT (IRIDAS/Adobe) parser
JavaScript
34
star
10

rbf

Radial Basis Function (RBF) interpolation
JavaScript
32
star
11

apply-cube-lut

Apply a Cube (IRIDAS/Adobe) LUT to an image
JavaScript
31
star
12

node-upnp-device-client

A simple and versatile UPnP device client
JavaScript
31
star
13

node-rtmpdump

A streamable wrapper around the rtmpdump CLI
JavaScript
19
star
14

parse-wavefront-obj

Wavefront OBJ parser
JavaScript
18
star
15

t411

T411 API client
JavaScript
17
star
16

quantize-vertices

Quantizes vertices to any bit precision
JavaScript
14
star
17

bezier-curve

Bezier curve interpolation
JavaScript
13
star
18

merge-vertices

Merges mesh vertices having identical coordinates
JavaScript
10
star
19

parse-stl

STL (ASCII and binary) file parser
JavaScript
9
star
20

parse-3ds

Parses 3D Studio .3DS files
JavaScript
7
star
21

serialize-stl

STL (ASCII and binary) file serialization
JavaScript
7
star
22

node-sse-emitter

Server-Sent Events as simple as they can get
JavaScript
6
star
23

remove-orphan-vertices

Removes orphan vertices in a simplicial complex
JavaScript
6
star
24

serialize-wavefront-obj

Wavefront OBJ serializer
JavaScript
5
star
25

merge-meshes

Merges multiple meshes into one
JavaScript
5
star
26

vertices-bounding-box

Computes the bounding box of a set of vertices
JavaScript
4
star
27

rescale-vertices

Rescales vertices to the dimensions of a target bounding box
JavaScript
4
star
28

node-host-filtering-proxy

A straightforward host filtering HTTP proxy
JavaScript
4
star
29

ndarray-from-image

Extracts an image RGBA pixels as a ndarray
JavaScript
3
star
30

remove-degenerate-cells

Removes degenerate cells in a simplicial complex
JavaScript
3
star
31

serialize-stl-binary

STL binary serialization
JavaScript
3
star
32

node-generate-source-map

Generates an identity source-map from a javascript file
JavaScript
3
star
33

canvas-from-ndarray

Updates a canvas RGBA pixels from an ndarray
JavaScript
2
star
34

talk-react-bdxio

React.js et l'avenir de l'interface utilisateur HTML5
CSS
2
star
35

parse-stl-binary

STL binary parser
JavaScript
2
star
36

talk-react-bordeauxjs

Keep calm and React !
2
star
37

require

Experiments with module loading in the browser
JavaScript
1
star
38

react-starter

JavaScript
1
star
39

serialize-stl-ascii

STL ASCII serialization
JavaScript
1
star
40

talk-ansible-best

Ansible
JavaScript
1
star
41

eventemitter

Lightweight isomorphic event emitter
JavaScript
1
star
42

set-canvas-pixels

Sets a canvas pixels from an array of RGBA pixel values
JavaScript
1
star
43

get-canvas-pixels

Get an array of RGBA pixel values from a canvas
JavaScript
1
star
44

node-url-stream

Transforms a stream of URLs into a stream of their body content
JavaScript
1
star
45

router

Lightweight isomorphic router
JavaScript
1
star
46

ndarray-from-canvas

Extracts a canvas RGBA pixels as a ndarray
JavaScript
1
star
47

parse-stl-ascii

STL ASCII parser
JavaScript
1
star