(23 intermediate revisions by the same user not shown) | |||
Line 1: | Line 1: | ||
ChronoCloud 2 protocol enables other applications to aquire real time timing data from ChronoFly. | 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", | "station": "ChronoFly-12345", | ||
"competition": "166656", | "competition": "166656", | ||
+ | "signature": "aW52YWxpZA==", | ||
+ | "sent": "2007-11-20T22:19:17.531+02:00", | ||
"data": { | "data": { | ||
"event": "passing", | "event": "passing", | ||
Line 31: | 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": " | + | "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 38: | Line 29: | ||
} | } | ||
− | ==Example | + | '''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", | "station": "ChronoFly-12345", | ||
"competition": "166656", | "competition": "166656", | ||
+ | "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" | ||
} | } | ||
} | } | ||
+ | |||
+ | '''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 <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.
Contents
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