(15 intermediate revisions by the same user not shown)
Line 3: Line 3:
 
Application is required to establish a WebSocket server where ChronoFly can send real time timing data.
 
Application is required to establish a WebSocket server where ChronoFly can send real time timing data.
  
==WebSocket server example==
+
==Protocol==
'''NodeJs'''
+
  
var ws = require("nodejs-websocket");
 
 
var server = ws.createServer(function(conn) {
 
    var station;
 
    conn.on("text", function(msg) {
 
        var dataObject = JSON.parse(msg);
 
        station = dataObject.station;
 
        conn.sendText("bumb-" + dataObject.data.id);
 
        console.log(Date() + " got time " + dataObject.data.time);
 
    });
 
    conn.on("close", function(code, reason) {
 
        console.log(Date() + " " + station + " not publishing anymore");
 
    });
 
}).listen(8275);
 
 
==Protocol==
 
 
Uses '''WebSocket''' Draft_6455 and TCP port 8275.
 
Uses '''WebSocket''' Draft_6455 and TCP port 8275.
  
===Confirmation rules===
+
===Passing PDU===
 
+
Whenever server receives a PDU it is required to confirm it back to the sender (ChronoFly).
+
 
+
Response format is bumb-<data.id>
+
 
+
'''Example confirmation response'''
+
 
+
bumb-4291
+
 
+
If PDU is not confirmed back to sender (ChronoFly) it will be re-sent to server during next event cycle.
+
 
+
===Example passing PDU===
+
  
 
Sent whenever passing (transponder or manual) is registered on ChronoFly.
 
Sent whenever passing (transponder or manual) is registered on ChronoFly.
Line 43: Line 14:
 
     "station": "ChronoFly-12345",
 
     "station": "ChronoFly-12345",
 
     "competition": "166656",
 
     "competition": "166656",
     "signature": "c39cfef55ac1b4",
+
     "signature": "aW52YWxpZA==",
 +
    "sent": "2007-11-20T22:19:17.531+02:00",
 
     "data": {
 
     "data": {
 
         "event": "passing",
 
         "event": "passing",
Line 49: Line 21:
 
         "type": "Route-TC-Finish",
 
         "type": "Route-TC-Finish",
 
         "round": "ET 4 TC 9 Huhdanoja A",
 
         "round": "ET 4 TC 9 Huhdanoja A",
         "time": "20:47:57.765",
+
         "time": "2007-11-20T22:19:17.532+02:00",
 +
        "tctime": "2007-11-20T22:19:00.000+02:00",
 
         "transponder": "7",
 
         "transponder": "7",
 
         "hits": "0",
 
         "hits": "0",
Line 60: Line 33:
 
* station: ChronoFly station identifier
 
* station: ChronoFly station identifier
 
* competition: ChronoFly competition identifier
 
* competition: ChronoFly competition identifier
* signature: Data signature based on keys negotiated during ChronoFly registration
+
* signature: Data signature based on keys negotiated during ChronoFly registration (base64 encoded SHA1withRSA)
 +
* sent: time this message was sent from ChronoFly (ISO 8601)
  
 
* event: event type
 
* event: event type
* id: event id (always incremented by 1)
+
* id: event id (incremented by 1 and zeroed when round changed or tables cleared)
 
* type: station type selected currently at ChronoFly
 
* type: station type selected currently at ChronoFly
 
* round: round name selected currently at ChronoFly
 
* round: round name selected currently at ChronoFly
* time: passing time
+
* time: passing time (ISO 8601)
 +
* tctime: time check time (ISO 8601)
 
* transponder: passing transponder number
 
* transponder: passing transponder number
 
* hits: times same transponder has been seen during current passing event
 
* hits: times same transponder has been seen during current passing event
* lap: times same transponder has been seen during selected round (always incremented by 1)
+
* lap: times same transponder has been seen during selected round (incremented by 1)
  
===Example connection PDU===
+
====Confirmation rules====
 +
 
 +
Whenever server receives a PDU it is required to confirm it back to the sender (ChronoFly).
 +
 
 +
Response format is: bumb-<data.id>
 +
 
 +
'''Example confirmation response'''
 +
 
 +
bumb-4291
 +
 
 +
If PDU is not confirmed back to sender (ChronoFly) it will be re-sent to server during next event cycle.
 +
 
 +
====Supported station types====
 +
 
 +
* Route-Finish
 +
* Route-Start
 +
* Route-Pause
 +
* TC-Time
 +
* TC-Separate
 +
* TC-Pause
 +
* Route-TC-Finish
 +
* Route-TC-Start
 +
* Track-Finish
 +
* Track-Sector
 +
 
 +
===Connection PDU===
  
 
Sent whenever ChronoFly connects to the WebSocket server.
 
Sent whenever ChronoFly connects to the WebSocket server.
Line 78: Line 78:
 
     "station": "ChronoFly-12345",
 
     "station": "ChronoFly-12345",
 
     "competition": "166656",
 
     "competition": "166656",
     "signature": "642a417f03b2ea",
+
     "signature": "aW52YWxpZA==",
 +
    "sent": "2007-11-20T22:19:17.531+02:00",
 
     "data": {
 
     "data": {
 
         "event": "connected",
 
         "event": "connected",
         "id": "0"
+
         "id": "0",
 +
        "version": "1.2.30",
 +
        "unique" : "129a9a6d1ced6963",
 +
        "dn" : "O=Salkku#4"
 
     }
 
     }
 
  }
 
  }
Line 89: Line 93:
 
* station: ChronoFly station identifier
 
* station: ChronoFly station identifier
 
* competition: ChronoFly competition identifier
 
* competition: ChronoFly competition identifier
* signature: Data signature based on keys negotiated during ChronoFly registration
+
* signature: Data signature based on keys negotiated during ChronoFly registration (base64 encoded SHA1withRSA)
 +
* sent: time this message was sent from ChronoFly (ISO 8601)
  
 
* event: event type
 
* event: event type
* id: event id (always incremented by 1)
+
* id: event id (incremented by 1 and zeroed when websocket tcp stream opened, should always be 0 in this event)
 +
* version: ChronoFly version (also messaging API version)
 +
* unique: 2-part unique value containing a hash generated during install time of ChronoFly and a hash generated during start of ChronoFly
 +
* dn: domain name component of the certificate holding public key used in data signing (in field signature)
 +
 
 +
===Keepalive PDU===
 +
 
 +
Sent every 30 seconds to the WebSocket server.
 +
 
 +
{
 +
    "station": "ChronoFly-12345",
 +
    "competition": "166656",
 +
    "signature": "aW52YWxpZA==",
 +
    "sent": "2007-11-20T22:19:17.531+02:00",
 +
    "data": {
 +
        "event": "keepalive",
 +
        "latency": "262",
 +
        "id": "1"
 +
    }
 +
}
 +
 
 +
'''Where'''
 +
 
 +
* station: ChronoFly station identifier
 +
* competition: ChronoFly competition identifier
 +
* signature: Data signature based on keys negotiated during ChronoFly registration (base64 encoded SHA1withRSA)
 +
* sent: time this message was sent from ChronoFly (ISO 8601)
 +
 
 +
* event: event type
 +
* latency: round-trip time of previous keepalive (from station to server and back to station, calculated by station)
 +
* id: event id (incremented by 1 and zeroed when websocket tcp stream opened, should always be larger than 0 in this event)
 +
 
 +
====Confirmation rules====
 +
 
 +
Whenever server receives a PDU it is required to confirm it back to the sender (ChronoFly).
 +
 
 +
Response format is: pong
 +
 
 +
'''Example confirmation response'''
 +
 
 +
pong
 +
 
 +
If PDU is not confirmed back to sender (ChronoFly) it will be re-sent to server during next event cycle.
 +
 
 +
===Malfunction PDU===
 +
 
 +
Sent every 30 seconds to the WebSocket server.
 +
 
 +
{
 +
    "station": "ChronoFly-12345",
 +
    "competition": "166656",
 +
    "signature": "aW52YWxpZA==",
 +
    "sent": "2007-11-20T22:19:17.531+02:00",
 +
    "data": {
 +
        "event": "malfunction",
 +
        "message": "java.net.SocketException: Connection timeout at com.enymind.drivers.MercuryComm.connect()",
 +
        "message2": "Unknown error",
 +
        "type": "1"
 +
    }
 +
}
 +
 
 +
'''Where'''
 +
 
 +
* station: ChronoFly station identifier
 +
* competition: ChronoFly competition identifier
 +
* signature: Data signature based on keys negotiated during ChronoFly registration (base64 encoded SHA1withRSA)
 +
* sent: time this message was sent from ChronoFly (ISO 8601)
 +
 
 +
* event: event type
 +
* message: malfunction desctiption
 +
* message2: additional malfunction desctiption
 +
* type: malfunction type
 +
 
 +
==WebSocket server example==
 +
 
 +
===NodeJs===
 +
 
 +
var ws = require("nodejs-websocket");
 +
var server = ws.createServer(function(conn) {
 +
  var station = "";
 +
  conn.on("text", function(msg) {
 +
    try {
 +
      var dataObject = JSON.parse(msg);
 +
      station = dataObject.station;
 +
 +
      // Do something with dataObject here, maybe persist to database etc.
 +
 +
      if(dataObject.data.event != undefined) {
 +
        if(dataObject.data.event == "passing") {
 +
          conn.sendText("bumb-" + dataObject.data.id);
 +
        } else if(dataObject.data.event == "keepalive") {
 +
          conn.sendText("pong");
 +
        }
 +
      } else {
 +
        console.log("Received invalid json WS PDU");
 +
      }
 +
    } catch (e) {
 +
      console.log("Received non-json WS PDU of length " + msg.length);
 +
    }
 +
  });
 +
  conn.on("connect", function() {
 +
    console.log("New station started publishing");
 +
  });
 +
  conn.on("close", function(code, reason) {
 +
    console.log("Station " + station + " not publishing anymore");
 +
  });
 +
}).listen(8275);
 +
 
 +
==PDU signature verification example==
 +
 
 +
===PHP===
 +
 
 +
<?php
 +
 +
  // ######### INSERT VALUES HERE ############
 +
 +
  // Base64 encoded public key
 +
  $LOCAL_PUBLIC = 'VDKNzVNUmnL3qTDs';
 +
 +
  // Base64 encoded signature from PDU
 +
  $SIGNATURE = 'BsChw6wRCFTGziTu3p3a';
 +
 +
  // JSON PDU as-is sent by ChronoFly
 +
  // BUT replace signature value with value "aW52YWxpZA=="
 +
  $PDU = '{"station": "chronofly-2248717", "sent": "2017-12-30T21:16:36.295+02:00", "signature": "aW52YWxpZA=="}';
 +
 +
  // ######### DO NOT EDIT BELLOW ############
 +
 +
  // http://phpseclib.sourceforge.net
 +
  set_include_path( get_include_path() . PATH_SEPARATOR . 'phpseclib' );
 +
 +
  include_once( 'Crypt/RSA.php' );
 +
 +
  $rsa = new Crypt_RSA();
 +
  $rsa->loadKey( $LOCAL_PUBLIC );
 +
  $rsa->setSignatureMode( CRYPT_RSA_SIGNATURE_PKCS1 );
 +
 +
  if( $rsa->verify( $PDU, base64_decode( $SIGNATURE ) ) )
 +
    die("OK verify\n");
 +
  else
 +
    die("ERR verify\n");
 +
 +
?>
 +
 
 +
===OpenSSL/Bash===
 +
 
 +
'''Note!''' Do NOT use <code>openssl rsa</code> or <code>openssl rsautl</code> for playing with signatures as these friends does
 +
not obey PKCS#1-v1.5 standard (not encoding the hash in an ASN.1 sequence). Use <code>openssl sha1</code> instead. For playing
 +
with public keys you CAN still use these friends.
 +
 
 +
#!/bin/bash
 +
 +
############ INSERT VALUES HERE ############
 +
 +
# Base64 encoded public key
 +
LOCAL_PUBLIC='VDKNzVNUmnL3qTDs'
 +
 +
# Base64 encoded signature from PDU
 +
SIGNATURE='BsChw6wRCFTGziTu3p3a'
 +
 +
# JSON PDU as-is sent by ChronoFly
 +
# BUT replace signature value with value "aW52YWxpZA=="
 +
PDU='{"station": "chronofly-2248717", "sent": "2017-12-30T21:16:36.295+02:00", "signature": "aW52YWxpZA=="}'
 +
 +
############ DO NOT EDIT BELLOW ############
 +
 +
printf "$LOCAL_PUBLIC" | base64 -d > chrono.der
 +
openssl rsa -inform der -outform pem -pubin -in chrono.der -out chrono.pem
 +
printf "$SIGNATURE" | base64 -d > chrono.sig
 +
printf "$PDU" > chrono.json
 +
 +
printf "////// HEXDUMP chrono.der ->\n"; xxd chrono.der
 +
printf "////// HEXDUMP chrono.pem ->\n"; xxd chrono.pem
 +
printf "////// HEXDUMP chrono.json ->\n"; xxd chrono.json
 +
printf "////// HEXDUMP chrono.sig ->\n"; xxd chrono.sig
 +
 +
printf "////// LOCAL_PUBLIC ASN1 ->\n"; openssl asn1parse -in chrono.pem
 +
printf "////// LOCAL_PUBLIC ASN1 IDX 18 ->\n"; openssl asn1parse -in chrono.pem -strparse 18
 +
printf "////// LOCAL_PUBLIC KEY ->\n"; openssl rsa -pubin -inform PEM -text -noout -in chrono.pem
 +
 +
printf "////// VERIFICATION RESULT ->\n"; openssl sha1 -verify chrono.pem -signature chrono.sig chrono.json
 +
 +
rm chrono.der
 +
rm chrono.pem
 +
rm chrono.json
 +
rm chrono.sig

Latest revision as of 12:01, 19 February 2022

ChronoCloud 2 protocol enables other applications to aquire real time timing data from ChronoFly.

Application is required to establish a WebSocket server where ChronoFly can send real time timing data.

Protocol

Uses WebSocket Draft_6455 and TCP port 8275.

Passing PDU

Sent whenever passing (transponder or manual) is registered on ChronoFly.

{
    "station": "ChronoFly-12345",
    "competition": "166656",
    "signature": "aW52YWxpZA==",
    "sent": "2007-11-20T22:19:17.531+02:00",
    "data": {
        "event": "passing",
        "id": "70",
        "type": "Route-TC-Finish",
        "round": "ET 4 TC 9 Huhdanoja A",
        "time": "2007-11-20T22:19:17.532+02:00",
        "tctime": "2007-11-20T22:19:00.000+02:00",
        "transponder": "7",
        "hits": "0",
        "lap": "5"
    }
}

Where

  • station: ChronoFly station identifier
  • competition: ChronoFly competition identifier
  • signature: Data signature based on keys negotiated during ChronoFly registration (base64 encoded SHA1withRSA)
  • sent: time this message was sent from ChronoFly (ISO 8601)
  • event: event type
  • id: event id (incremented by 1 and zeroed when round changed or tables cleared)
  • type: station type selected currently at ChronoFly
  • round: round name selected currently at ChronoFly
  • time: passing time (ISO 8601)
  • tctime: time check time (ISO 8601)
  • transponder: passing transponder number
  • hits: times same transponder has been seen during current passing event
  • lap: times same transponder has been seen during selected round (incremented by 1)

Confirmation rules

Whenever server receives a PDU it is required to confirm it back to the sender (ChronoFly).

Response format is: bumb-<data.id>

Example confirmation response

bumb-4291

If PDU is not confirmed back to sender (ChronoFly) it will be re-sent to server during next event cycle.

Supported station types

  • Route-Finish
  • Route-Start
  • Route-Pause
  • TC-Time
  • TC-Separate
  • TC-Pause
  • Route-TC-Finish
  • Route-TC-Start
  • Track-Finish
  • Track-Sector

Connection PDU

Sent whenever ChronoFly connects to the WebSocket server.

{
    "station": "ChronoFly-12345",
    "competition": "166656",
    "signature": "aW52YWxpZA==",
    "sent": "2007-11-20T22:19:17.531+02:00",
    "data": {
        "event": "connected",
        "id": "0",
        "version": "1.2.30",
        "unique" : "129a9a6d1ced6963",
        "dn" : "O=Salkku#4"
    }
}

Where

  • station: ChronoFly station identifier
  • competition: ChronoFly competition identifier
  • signature: Data signature based on keys negotiated during ChronoFly registration (base64 encoded SHA1withRSA)
  • sent: time this message was sent from ChronoFly (ISO 8601)
  • event: event type
  • id: event id (incremented by 1 and zeroed when websocket tcp stream opened, should always be 0 in this event)
  • version: ChronoFly version (also messaging API version)
  • unique: 2-part unique value containing a hash generated during install time of ChronoFly and a hash generated during start of ChronoFly
  • dn: domain name component of the certificate holding public key used in data signing (in field signature)

Keepalive PDU

Sent every 30 seconds to the WebSocket server.

{
    "station": "ChronoFly-12345",
    "competition": "166656",
    "signature": "aW52YWxpZA==",
    "sent": "2007-11-20T22:19:17.531+02:00",
    "data": {
        "event": "keepalive",
        "latency": "262",
        "id": "1"
    }
}

Where

  • station: ChronoFly station identifier
  • competition: ChronoFly competition identifier
  • signature: Data signature based on keys negotiated during ChronoFly registration (base64 encoded SHA1withRSA)
  • sent: time this message was sent from ChronoFly (ISO 8601)
  • event: event type
  • latency: round-trip time of previous keepalive (from station to server and back to station, calculated by station)
  • id: event id (incremented by 1 and zeroed when websocket tcp stream opened, should always be larger than 0 in this event)

Confirmation rules

Whenever server receives a PDU it is required to confirm it back to the sender (ChronoFly).

Response format is: pong

Example confirmation response

pong

If PDU is not confirmed back to sender (ChronoFly) it will be re-sent to server during next event cycle.

Malfunction PDU

Sent every 30 seconds to the WebSocket server.

{
    "station": "ChronoFly-12345",
    "competition": "166656",
    "signature": "aW52YWxpZA==",
    "sent": "2007-11-20T22:19:17.531+02:00",
    "data": {
        "event": "malfunction",
        "message": "java.net.SocketException: Connection timeout at com.enymind.drivers.MercuryComm.connect()",
        "message2": "Unknown error",
        "type": "1"
    }
}

Where

  • station: ChronoFly station identifier
  • competition: ChronoFly competition identifier
  • signature: Data signature based on keys negotiated during ChronoFly registration (base64 encoded SHA1withRSA)
  • sent: time this message was sent from ChronoFly (ISO 8601)
  • event: event type
  • message: malfunction desctiption
  • message2: additional malfunction desctiption
  • type: malfunction type

WebSocket server example

NodeJs

var ws = require("nodejs-websocket");
var server = ws.createServer(function(conn) {
  var station = "";
  conn.on("text", function(msg) {
    try {
      var dataObject = JSON.parse(msg);
      station = dataObject.station;

      // Do something with dataObject here, maybe persist to database etc.

      if(dataObject.data.event != undefined) {
        if(dataObject.data.event == "passing") {
          conn.sendText("bumb-" + dataObject.data.id);
        } else if(dataObject.data.event == "keepalive") {
          conn.sendText("pong");
        }
      } else {
        console.log("Received invalid json WS PDU");
      }
    } catch (e) {
      console.log("Received non-json WS PDU of length " + msg.length);
    }
  });
  conn.on("connect", function() {
    console.log("New station started publishing");
  });
  conn.on("close", function(code, reason) {
    console.log("Station " + station + " not publishing anymore");
  });
}).listen(8275);

PDU signature verification example

PHP

<?php

  // ######### INSERT VALUES HERE ############

  // Base64 encoded public key
  $LOCAL_PUBLIC = 'VDKNzVNUmnL3qTDs';

  // Base64 encoded signature from PDU
  $SIGNATURE = 'BsChw6wRCFTGziTu3p3a';

  // JSON PDU as-is sent by ChronoFly
  // BUT replace signature value with value "aW52YWxpZA=="
  $PDU = '{"station": "chronofly-2248717", "sent": "2017-12-30T21:16:36.295+02:00", "signature": "aW52YWxpZA=="}';

  // ######### DO NOT EDIT BELLOW ############

  // http://phpseclib.sourceforge.net
  set_include_path( get_include_path() . PATH_SEPARATOR . 'phpseclib' );

  include_once( 'Crypt/RSA.php' );

  $rsa = new Crypt_RSA();
  $rsa->loadKey( $LOCAL_PUBLIC );
  $rsa->setSignatureMode( CRYPT_RSA_SIGNATURE_PKCS1 );

  if( $rsa->verify( $PDU, base64_decode( $SIGNATURE ) ) )
    die("OK verify\n");
  else
    die("ERR verify\n");

?>

OpenSSL/Bash

Note! Do NOT use openssl rsa or openssl rsautl for playing with signatures as these friends does not obey PKCS#1-v1.5 standard (not encoding the hash in an ASN.1 sequence). Use openssl sha1 instead. For playing with public keys you CAN still use these friends.

#!/bin/bash

############ INSERT VALUES HERE ############

# Base64 encoded public key
LOCAL_PUBLIC='VDKNzVNUmnL3qTDs'

# Base64 encoded signature from PDU
SIGNATURE='BsChw6wRCFTGziTu3p3a'

# JSON PDU as-is sent by ChronoFly
# BUT replace signature value with value "aW52YWxpZA=="
PDU='{"station": "chronofly-2248717", "sent": "2017-12-30T21:16:36.295+02:00", "signature": "aW52YWxpZA=="}'

############ DO NOT EDIT BELLOW ############

printf "$LOCAL_PUBLIC" | base64 -d > chrono.der
openssl rsa -inform der -outform pem -pubin -in chrono.der -out chrono.pem
printf "$SIGNATURE" | base64 -d > chrono.sig
printf "$PDU" > chrono.json

printf "////// HEXDUMP chrono.der ->\n"; xxd chrono.der
printf "////// HEXDUMP chrono.pem ->\n"; xxd chrono.pem
printf "////// HEXDUMP chrono.json ->\n"; xxd chrono.json
printf "////// HEXDUMP chrono.sig ->\n"; xxd chrono.sig

printf "////// LOCAL_PUBLIC ASN1 ->\n"; openssl asn1parse -in chrono.pem
printf "////// LOCAL_PUBLIC ASN1 IDX 18 ->\n"; openssl asn1parse -in chrono.pem -strparse 18
printf "////// LOCAL_PUBLIC KEY ->\n"; openssl rsa -pubin -inform PEM -text -noout -in chrono.pem

printf "////// VERIFICATION RESULT ->\n"; openssl sha1 -verify chrono.pem -signature chrono.sig chrono.json

rm chrono.der
rm chrono.pem
rm chrono.json
rm chrono.sig