@tak-ps/node-cot 1.0.2 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/README.md +10 -3
- package/index.js +1 -3
- package/package.json +2 -1
- package/src/util.js +93 -0
- package/src/xml.js +78 -50
- package/test/cot.test.js +5 -5
- package/assets/contact.proto +0 -14
- package/assets/cotevent.proto +0 -45
- package/assets/detail.proto +0 -78
- package/assets/group.proto +0 -13
- package/assets/precisionlocation.proto +0 -13
- package/assets/status.proto +0 -12
- package/assets/takcontrol.proto +0 -24
- package/assets/takmessage.proto +0 -18
- package/assets/takv.proto +0 -15
- package/assets/track.proto +0 -13
- package/src/proto.js +0 -110
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# CHANGELOG
|
|
2
|
+
|
|
3
|
+
## Emoji Cheatsheet
|
|
4
|
+
- :pencil2: doc updates
|
|
5
|
+
- :bug: when fixing a bug
|
|
6
|
+
- :rocket: when making general improvements
|
|
7
|
+
- :white_check_mark: when adding tests
|
|
8
|
+
- :arrow_up: when upgrading dependencies
|
|
9
|
+
- :tada: when adding new features
|
|
10
|
+
|
|
11
|
+
## Version History
|
|
12
|
+
|
|
13
|
+
### v2.1.0
|
|
14
|
+
|
|
15
|
+
- :rocket: Allow setting `type` & `how` from GeoJSON
|
|
16
|
+
|
|
17
|
+
### v2.0.0
|
|
18
|
+
|
|
19
|
+
- :rocket: Class based XML CoT approach
|
|
20
|
+
- :tada: Add GeoJSON to/from parsing (experimental)
|
|
21
|
+
- :white_check_mark: Update tests to use `tape`
|
|
22
|
+
- :pencil2: Update docs to Class approach
|
|
23
|
+
- :pencil2: Add a CHANGELOG
|
package/README.md
CHANGED
|
@@ -23,8 +23,15 @@ npm install @tak-ps/node-cot
|
|
|
23
23
|
### Basic Usage
|
|
24
24
|
|
|
25
25
|
```
|
|
26
|
-
import {
|
|
26
|
+
import { XML } from '@tak-ps/node-cot';
|
|
27
|
+
|
|
27
28
|
const message = '<event version="2.0" uid="ANDROID-deadbeef" type="a-f-G-U-C" how="m-g" time="2021-02-27T20:32:24.771Z" start="2021-02-27T20:32:24.771Z" stale="2021-02-27T20:38:39.771Z"><point lat="1.234567" lon="-3.141592" hae="-25.7" ce="9.9" le="9999999.0"/><detail><takv os="29" version="4.0.0.0 (deadbeef).1234567890-CIV" device="Some Android Device" platform="ATAK-CIV"/><contact xmppUsername="xmpp@host.com" endpoint="*:-1:stcp" callsign="JENNY"/><uid Droid="JENNY"/><precisionlocation altsrc="GPS" geopointsrc="GPS"/><__group role="Team Member" name="Cyan"/><status battery="78"/><track course="80.24833892285461" speed="0.0"/></detail></event>'
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
|
|
30
|
+
const cot = new XML(message);
|
|
31
|
+
|
|
32
|
+
// Export Formats
|
|
33
|
+
cot.to_geojson(); // Output GeoJSON Representation
|
|
34
|
+
cot.to_xml(); // Output String XML Representation
|
|
35
|
+
|
|
36
|
+
cot.raw; // JSON XML Representation
|
|
30
37
|
```
|
package/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tak-ps/node-cot",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.0
|
|
4
|
+
"version": "2.1.0",
|
|
5
5
|
"description": "Lightweight JavaScript library for parsing and manipulating TAK messages",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"scripts": {
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"protobufjs": "^7.1.2",
|
|
13
|
+
"uuid": "^9.0.0",
|
|
13
14
|
"xml-js": "^1.6.11"
|
|
14
15
|
},
|
|
15
16
|
"devDependencies": {
|
package/src/util.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Helper functions for generating CoT data
|
|
5
|
+
* @class
|
|
6
|
+
*/
|
|
7
|
+
export default class Util {
|
|
8
|
+
/**
|
|
9
|
+
* Return an event._attributes object with as many defaults as possible
|
|
10
|
+
*
|
|
11
|
+
* @param {String} type CoT Type
|
|
12
|
+
* @param {String} how CoT how
|
|
13
|
+
*
|
|
14
|
+
* @returns {Object}
|
|
15
|
+
*/
|
|
16
|
+
static cot_event_attr(type, how) {
|
|
17
|
+
if (!type) throw new Error('type param required');
|
|
18
|
+
if (!how) throw new Error('how param required');
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
...Util.cot_version(),
|
|
22
|
+
...Util.cot_uuid(),
|
|
23
|
+
type,
|
|
24
|
+
how,
|
|
25
|
+
...Util.cot_date()
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Generate a random UUID
|
|
31
|
+
*
|
|
32
|
+
* @returns {Object}
|
|
33
|
+
*/
|
|
34
|
+
static cot_uuid() {
|
|
35
|
+
return {
|
|
36
|
+
uid: uuidv4()
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Return the current version number this library supports
|
|
42
|
+
*
|
|
43
|
+
* @returns {Object}
|
|
44
|
+
*/
|
|
45
|
+
static cot_version() {
|
|
46
|
+
return {
|
|
47
|
+
version: '2.0'
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Generate Null Island CoT point object
|
|
53
|
+
*
|
|
54
|
+
* @returns {Object}
|
|
55
|
+
*/
|
|
56
|
+
static cot_point() {
|
|
57
|
+
return {
|
|
58
|
+
'_attributes': {
|
|
59
|
+
'lat': '0.000000',
|
|
60
|
+
'lon': '0.000000',
|
|
61
|
+
'hae': '0.0',
|
|
62
|
+
'ce': '9999999.0',
|
|
63
|
+
'le': '9999999.0'
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Generate CoT date objects
|
|
70
|
+
*
|
|
71
|
+
* cot_date() - Time: now, Start: now, Stale: now + 20s
|
|
72
|
+
*
|
|
73
|
+
* @param {Date|null} time Time of CoT Message - if omitted, current time is used
|
|
74
|
+
* @param {Date|null} start Start Time of CoT - if omitted, current time is used
|
|
75
|
+
* @param {Date|null|numeric} stale Expiration of CoT - if null now+20s is used. Alternative an integer representing the start + ms
|
|
76
|
+
*
|
|
77
|
+
* @returns {Object}
|
|
78
|
+
*/
|
|
79
|
+
static cot_date(time, start, stale) {
|
|
80
|
+
const now = Date.now();
|
|
81
|
+
|
|
82
|
+
if (!time) time = new Date(now).toISOString();
|
|
83
|
+
if (!start) start = new Date(now).toISOString();
|
|
84
|
+
|
|
85
|
+
if (!stale) {
|
|
86
|
+
stale = new Date(now + 20 * 1000).toISOString();
|
|
87
|
+
} else if (!isNaN(parseInt(stale))) {
|
|
88
|
+
stale = new Date(now + 20 * 1000).toISOString();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return { time, start, stale };
|
|
92
|
+
}
|
|
93
|
+
}
|
package/src/xml.js
CHANGED
|
@@ -1,73 +1,101 @@
|
|
|
1
1
|
import xmljs from 'xml-js';
|
|
2
|
+
import Util from './util.js';
|
|
2
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Convert to and from an XML CoT message
|
|
6
|
+
* @class
|
|
7
|
+
*
|
|
8
|
+
* @param {String|Object|Buffer} cot A string/buffer containing the XML representation or the xml-js object tree
|
|
9
|
+
*
|
|
10
|
+
* @prop {Object} raw Raw XML-JS representation of CoT
|
|
11
|
+
*/
|
|
3
12
|
export default class XMLCot {
|
|
4
|
-
|
|
5
|
-
if (
|
|
6
|
-
throw new Error('Attempted to parse empty Object');
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
return xmljs.js2xml(js, { compact: true });
|
|
10
|
-
}
|
|
13
|
+
constructor(cot) {
|
|
14
|
+
if (cot instanceof Buffer) String(cot);
|
|
11
15
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
result += `-${type.descriptor}`;
|
|
16
|
+
if (typeof cot === 'string') {
|
|
17
|
+
this.raw = xmljs.xml2js(cot, { compact: true });
|
|
18
|
+
} else {
|
|
19
|
+
this.raw = cot;
|
|
17
20
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
|
|
22
|
+
// Attempt to cast all point to numerics
|
|
23
|
+
for (const key of Object.keys(this.raw.event.point)) {
|
|
24
|
+
if (!isNaN(parseFloat(this.raw.event.point[key]))) {
|
|
25
|
+
this.raw.event.point[key] = parseFloat(this.raw.event.point[key]);
|
|
26
|
+
}
|
|
23
27
|
}
|
|
24
|
-
return result;
|
|
25
28
|
}
|
|
26
29
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Return an XMLCot Message
|
|
32
|
+
*
|
|
33
|
+
* @param {Object} feature GeoJSON Point Feature
|
|
34
|
+
*
|
|
35
|
+
* @return {XMLCot}
|
|
36
|
+
*/
|
|
37
|
+
static from_geojson(feature) {
|
|
38
|
+
if (feature.type !== 'Feature') throw new Error('Must be GeoJSON Feature');
|
|
39
|
+
if (!feature.geometry || feature.geometry.type !== 'Point') throw new Error('Must be GeoJSON Point Feature');
|
|
40
|
+
if (!feature.properties) throw new Error('Feature must have properties');
|
|
30
41
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
42
|
+
const cot = {
|
|
43
|
+
'event': {
|
|
44
|
+
'_attributes': Util.cot_event_attr(feature.properties.type || 'a-f-G', feature.properties.how || 'm-g'),
|
|
45
|
+
'point': Util.cot_point()
|
|
46
|
+
}
|
|
47
|
+
};
|
|
35
48
|
|
|
36
|
-
|
|
37
|
-
cot =
|
|
49
|
+
for (const key of ['time', 'start', 'stale', 'type', 'how']) {
|
|
50
|
+
if (feature.properties[key]) cot.event._attributes[key] = feature.properties[key];
|
|
38
51
|
}
|
|
39
52
|
|
|
40
|
-
return
|
|
53
|
+
return new XMLCot(cot);
|
|
41
54
|
}
|
|
42
55
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
type,
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
+
/**
|
|
57
|
+
* Return a GeoJSON Feature from an XML CoT message
|
|
58
|
+
*
|
|
59
|
+
* @returns {Object}
|
|
60
|
+
*/
|
|
61
|
+
to_geojson() {
|
|
62
|
+
const geojson = {
|
|
63
|
+
id: this.raw.event._attributes.uid,
|
|
64
|
+
type: 'Feature',
|
|
65
|
+
properties: {
|
|
66
|
+
time: this.raw.event._attributes.time,
|
|
67
|
+
start: this.raw.event._attributes.start,
|
|
68
|
+
stale: this.raw.event._attributes.stale
|
|
69
|
+
},
|
|
70
|
+
geometry: {
|
|
71
|
+
type: 'Point',
|
|
72
|
+
coordinates: [
|
|
73
|
+
this.raw.event.point._attributes.lon,
|
|
74
|
+
this.raw.event.point._attributes.lon
|
|
75
|
+
]
|
|
76
|
+
}
|
|
56
77
|
};
|
|
78
|
+
|
|
79
|
+
return geojson;
|
|
57
80
|
}
|
|
58
81
|
|
|
59
|
-
|
|
60
|
-
return
|
|
82
|
+
to_xml() {
|
|
83
|
+
return xmljs.js2xml(this.raw, {
|
|
84
|
+
compact: true
|
|
85
|
+
});
|
|
61
86
|
}
|
|
62
87
|
|
|
63
|
-
|
|
64
|
-
|
|
88
|
+
/**
|
|
89
|
+
* Return a CoT Message
|
|
90
|
+
*
|
|
91
|
+
* @returns {XMLCot}
|
|
92
|
+
*/
|
|
93
|
+
static ping() {
|
|
65
94
|
return {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
le: parseFloat(point.le)
|
|
95
|
+
event: {
|
|
96
|
+
_attributes: Util.cot_event_attr('t-x-c-t', 'h-g-i-g-o'),
|
|
97
|
+
point: Util.cot_point()
|
|
98
|
+
}
|
|
71
99
|
};
|
|
72
100
|
}
|
|
73
101
|
}
|
package/test/cot.test.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import test from 'tape';
|
|
2
|
-
import XML from '../
|
|
2
|
+
import { XML } from '../index.js';
|
|
3
3
|
|
|
4
4
|
test('Decode COT message', (t) => {
|
|
5
5
|
const packet = '<event version="2.0" uid="ANDROID-deadbeef" type="a-f-G-U-C" how="m-g" time="2021-02-27T20:32:24.771Z" start="2021-02-27T20:32:24.771Z" stale="2021-02-27T20:38:39.771Z"><point lat="1.234567" lon="-3.141592" hae="-25.7" ce="9.9" le="9999999.0"/><detail><takv os="29" version="4.0.0.0 (deadbeef).1234567890-CIV" device="Some Android Device" platform="ATAK-CIV"/><contact xmppUsername="xmpp@host.com" endpoint="*:-1:stcp" callsign="JENNY"/><uid Droid="JENNY"/><precisionlocation altsrc="GPS" geopointsrc="GPS"/><__group role="Team Member" name="Cyan"/><status battery="78"/><track course="80.24833892285461" speed="0.0"/></detail></event>';
|
|
6
6
|
|
|
7
|
-
t.deepEquals(XML
|
|
7
|
+
t.deepEquals((new XML(packet)).raw, {
|
|
8
8
|
'event': {
|
|
9
9
|
'_attributes': {
|
|
10
10
|
'version': '2.0',
|
|
@@ -55,7 +55,7 @@ test('Decode COT message', (t) => {
|
|
|
55
55
|
test('Decode COT message', (t) => {
|
|
56
56
|
const packet = '<event version="2.0" uid="TEST-deadbeef" type="a" how="m-g" time="2021-03-12T15:49:07.138Z" start="2021-03-12T15:49:07.138Z" stale="2021-03-12T15:49:07.138Z"><point lat="0.000000" lon="0.000000" hae="0.0" ce="9999999.0" le="9999999.0"/><detail><takv os="Android" version="10" device="Some Device" platform="python unittest"/><status battery="83"/><uid Droid="JENNY"/><contact callsign="JENNY" endpoint="*:-1:stcp" phone="800-867-5309"/><__group role="Team Member" name="Cyan"/><track course="90.1" speed="10.3"/></detail></event>';
|
|
57
57
|
|
|
58
|
-
t.deepEquals(XML
|
|
58
|
+
t.deepEquals((new XML(packet)).raw, {
|
|
59
59
|
'event': {
|
|
60
60
|
'_attributes': {
|
|
61
61
|
'version': '2.0',
|
|
@@ -167,7 +167,7 @@ test('Encode COT message', (t) => {
|
|
|
167
167
|
};
|
|
168
168
|
|
|
169
169
|
t.deepEquals(
|
|
170
|
-
XML
|
|
170
|
+
(new XML(packet)).to_xml(),
|
|
171
171
|
'<event version="2.0" uid="ANDROID-deadbeef" type="a-f-G-U-C" how="m-g" time="2021-02-27T20:32:24.771Z" start="2021-02-27T20:32:24.771Z" stale="2021-02-27T20:38:39.771Z"><point lat="1.234567" lon="-3.141592" hae="-25.7" ce="9.9" le="9999999.0"/><detail><takv os="29" version="4.0.0.0 (deadbeef).1234567890-CIV" device="Some Android Device" platform="ATAK-CIV"/><contact xmppUsername="xmpp@host.com" endpoint="*:-1:stcp" callsign="JENNY"/><uid Droid="JENNY"/><precisionlocation altsrc="GPS" geopointsrc="GPS"/><__group role="Team Member" name="Cyan"/><status battery="78"/><track course="80.24833892285461" speed="0.0"/></detail></event>'
|
|
172
172
|
);
|
|
173
173
|
|
|
@@ -190,7 +190,7 @@ test('Parse GeoChat message', (t) => {
|
|
|
190
190
|
' </detail>\n' +
|
|
191
191
|
'</event>';
|
|
192
192
|
|
|
193
|
-
t.deepEquals(XML
|
|
193
|
+
t.deepEquals((new XML(geochat)).raw, {
|
|
194
194
|
'event': {
|
|
195
195
|
'_attributes': {
|
|
196
196
|
'version': '2.0',
|
package/assets/contact.proto
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
syntax = "proto3";
|
|
2
|
-
option optimize_for = LITE_RUNTIME;
|
|
3
|
-
|
|
4
|
-
package atakmap.commoncommo.protobuf.v1;
|
|
5
|
-
|
|
6
|
-
// All items are required unless otherwise noted!
|
|
7
|
-
// "required" means if they are missing on send, the conversion
|
|
8
|
-
// to the message format will be rejected and fall back to opaque
|
|
9
|
-
// XML representation
|
|
10
|
-
message Contact {
|
|
11
|
-
// Endpoint is optional; if missing/empty do not populate.
|
|
12
|
-
string endpoint = 1; // endpoint=
|
|
13
|
-
string callsign = 2; // callsign=
|
|
14
|
-
}
|
package/assets/cotevent.proto
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
syntax = "proto3";
|
|
3
|
-
|
|
4
|
-
option optimize_for = LITE_RUNTIME;
|
|
5
|
-
|
|
6
|
-
package atakmap.commoncommo.protobuf.v1;
|
|
7
|
-
|
|
8
|
-
import "detail.proto";
|
|
9
|
-
|
|
10
|
-
// A note about timestamps:
|
|
11
|
-
// Uses "timeMs" units, which is number of milliseconds since
|
|
12
|
-
// 1970-01-01 00:00:00 UTC
|
|
13
|
-
//
|
|
14
|
-
// All items are required unless otherwise noted!
|
|
15
|
-
// "required" means if they are missing in the XML during outbound
|
|
16
|
-
// conversion to protobuf, the message will be
|
|
17
|
-
// rejected
|
|
18
|
-
message CotEvent {
|
|
19
|
-
// <event>
|
|
20
|
-
|
|
21
|
-
string type = 1; // <event type="x">
|
|
22
|
-
|
|
23
|
-
string access = 2; // optional
|
|
24
|
-
string qos = 3; // optional
|
|
25
|
-
string opex = 4; // optional
|
|
26
|
-
|
|
27
|
-
string uid = 5; // <event uid="x">
|
|
28
|
-
uint64 sendTime = 6; // <event time="x"> converted to timeMs
|
|
29
|
-
uint64 startTime = 7; // <event start="x"> converted to timeMs
|
|
30
|
-
uint64 staleTime = 8; // <event stale="x"> converted to timeMs
|
|
31
|
-
string how = 9; // <event how="x">
|
|
32
|
-
|
|
33
|
-
// <point>
|
|
34
|
-
double lat = 10; // <point lat="x">
|
|
35
|
-
double lon = 11; // <point lon="x">
|
|
36
|
-
double hae = 12; // <point hae="x"> use 999999 for unknown
|
|
37
|
-
double ce = 13; // <point ce="x"> use 999999 for unknown
|
|
38
|
-
double le = 14; // <point ce="x"> use 999999 for unknown
|
|
39
|
-
|
|
40
|
-
// comprises children of <detail>
|
|
41
|
-
// This is optional - if omitted, then the cot message
|
|
42
|
-
// had no data under <detail>
|
|
43
|
-
Detail detail = 15;
|
|
44
|
-
}
|
|
45
|
-
|
package/assets/detail.proto
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
syntax = "proto3";
|
|
2
|
-
option optimize_for = LITE_RUNTIME;
|
|
3
|
-
|
|
4
|
-
package atakmap.commoncommo.protobuf.v1;
|
|
5
|
-
|
|
6
|
-
import "contact.proto";
|
|
7
|
-
import "group.proto";
|
|
8
|
-
import "precisionlocation.proto";
|
|
9
|
-
import "status.proto";
|
|
10
|
-
import "takv.proto";
|
|
11
|
-
import "track.proto";
|
|
12
|
-
|
|
13
|
-
// CotEvent detail
|
|
14
|
-
// The strong typed message fields are optional. If used, they *MUST* adhere
|
|
15
|
-
// to the requirements of the message (see their proto file) and
|
|
16
|
-
// their XML source element used to populate the message MUST
|
|
17
|
-
// be omitted from the xmlDetail.
|
|
18
|
-
// WHOLE ELEMENTS MUST BE CONVERTED TO MESSAGES. Do not try to
|
|
19
|
-
// put part of the data from a given element into one of the messages
|
|
20
|
-
// and put other parts of the data in an element of xmlDetail! This applies
|
|
21
|
-
// especially if you add new things to the XML representation which do not
|
|
22
|
-
// have a place in the equivalent protobuf message. Instead, omit the
|
|
23
|
-
// message and put the entire element in xmlDetail!
|
|
24
|
-
//
|
|
25
|
-
// xmlDetail is optional. If omitted, all Detail data has been
|
|
26
|
-
// converted to the strongly typed message fields.
|
|
27
|
-
// If present, this contains any remaining detail data that has NOT been
|
|
28
|
-
// included in one of the strongly typed message fields. To process the
|
|
29
|
-
// xmlDetail, the following rules MUST be followed:
|
|
30
|
-
// Senders of this message MUST:
|
|
31
|
-
// 1. Remove child elements used to populate the other message
|
|
32
|
-
// fields. If the same child element appears more times than an
|
|
33
|
-
// associated message field(s) is intended to encompass, or if any
|
|
34
|
-
// error occurs mapping to the message equivalent, do not remove
|
|
35
|
-
// the element(s) in question and do not populate the message
|
|
36
|
-
// equivalent.
|
|
37
|
-
// 2. If no data under <detail> remains, STOP - do not populate
|
|
38
|
-
// xmlDetail
|
|
39
|
-
// 3. Serialize the remaining XML tree under <detail>....</detail>
|
|
40
|
-
// as XML in UTF-8 encoding
|
|
41
|
-
// 4. Remove the <detail> and </detail> element tags
|
|
42
|
-
// 5. Remove the XML header
|
|
43
|
-
// 6. Place the result in xmlDetail
|
|
44
|
-
// Receivers of this message MUST do the equivalent of the following:
|
|
45
|
-
// 1. If the field is not present (zero length), stop - do nothing
|
|
46
|
-
// 2. Prepend <detail> and append </detail>
|
|
47
|
-
// 3. Prepend an XML header for UTF-8 encoding, version 1.0
|
|
48
|
-
// (<?xml version="1.0" encoding="UTF-8"?> or similar)
|
|
49
|
-
// 4. Read the result, expecting a valid XML document with a document
|
|
50
|
-
// root of <detail>
|
|
51
|
-
// 5. Merge in XML equivalents of each of the strongly typed
|
|
52
|
-
// messages present in this Detail message.
|
|
53
|
-
// In the event that a sending application does not follow
|
|
54
|
-
// sending rule #1 above properly and data for the same element
|
|
55
|
-
// appears in xmlDetail, the data in xmlDetail should be left alone
|
|
56
|
-
// and the data in the equivalent message should ignored.
|
|
57
|
-
|
|
58
|
-
message Detail {
|
|
59
|
-
string xmlDetail = 1;
|
|
60
|
-
|
|
61
|
-
// <contact>
|
|
62
|
-
Contact contact = 2;
|
|
63
|
-
|
|
64
|
-
// <__group>
|
|
65
|
-
Group group = 3;
|
|
66
|
-
|
|
67
|
-
// <precisionlocation>
|
|
68
|
-
PrecisionLocation precisionLocation = 4;
|
|
69
|
-
|
|
70
|
-
// <status>
|
|
71
|
-
Status status = 5;
|
|
72
|
-
|
|
73
|
-
// <takv>
|
|
74
|
-
Takv takv = 6;
|
|
75
|
-
|
|
76
|
-
// <track>
|
|
77
|
-
Track track = 7;
|
|
78
|
-
}
|
package/assets/group.proto
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
syntax = "proto3";
|
|
2
|
-
option optimize_for = LITE_RUNTIME;
|
|
3
|
-
|
|
4
|
-
package atakmap.commoncommo.protobuf.v1;
|
|
5
|
-
|
|
6
|
-
// All items are required unless otherwise noted!
|
|
7
|
-
// "required" means if they are missing on send, the conversion
|
|
8
|
-
// to the message format will be rejected and fall back to opaque
|
|
9
|
-
// XML representation
|
|
10
|
-
message Group {
|
|
11
|
-
string name = 1; // name=
|
|
12
|
-
string role = 2; // role=
|
|
13
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
syntax = "proto3";
|
|
2
|
-
option optimize_for = LITE_RUNTIME;
|
|
3
|
-
|
|
4
|
-
package atakmap.commoncommo.protobuf.v1;
|
|
5
|
-
|
|
6
|
-
// All items are required unless otherwise noted!
|
|
7
|
-
// "required" means if they are missing on send, the conversion
|
|
8
|
-
// to the message format will be rejected and fall back to opaque
|
|
9
|
-
// XML representation
|
|
10
|
-
message PrecisionLocation {
|
|
11
|
-
string geopointsrc = 1; // geopointsrc=
|
|
12
|
-
string altsrc = 2; // altsrc=
|
|
13
|
-
}
|
package/assets/status.proto
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
syntax = "proto3";
|
|
2
|
-
option optimize_for = LITE_RUNTIME;
|
|
3
|
-
|
|
4
|
-
package atakmap.commoncommo.protobuf.v1;
|
|
5
|
-
|
|
6
|
-
// All items are required unless otherwise noted!
|
|
7
|
-
// "required" means if they are missing on send, the conversion
|
|
8
|
-
// to the message format will be rejected and fall back to opaque
|
|
9
|
-
// XML representation
|
|
10
|
-
message Status {
|
|
11
|
-
uint32 battery = 1; // battery=
|
|
12
|
-
}
|
package/assets/takcontrol.proto
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
syntax = "proto3";
|
|
2
|
-
option optimize_for = LITE_RUNTIME;
|
|
3
|
-
|
|
4
|
-
package atakmap.commoncommo.protobuf.v1;
|
|
5
|
-
|
|
6
|
-
// TAK Protocol control message
|
|
7
|
-
// This specifies to a recipient what versions
|
|
8
|
-
// of protocol elements this sender supports during
|
|
9
|
-
// decoding.
|
|
10
|
-
message TakControl {
|
|
11
|
-
// Lowest TAK protocol version supported
|
|
12
|
-
// If not filled in (reads as 0), version 1 is assumed
|
|
13
|
-
uint32 minProtoVersion = 1;
|
|
14
|
-
|
|
15
|
-
// Highest TAK protocol version supported
|
|
16
|
-
// If not filled in (reads as 0), version 1 is assumed
|
|
17
|
-
uint32 maxProtoVersion = 2;
|
|
18
|
-
|
|
19
|
-
// UID of the sending contact. May be omitted if
|
|
20
|
-
// this message is paired in a TakMessage with a CotEvent
|
|
21
|
-
// and the CotEvent contains this information
|
|
22
|
-
string contactUid = 3;
|
|
23
|
-
}
|
|
24
|
-
|
package/assets/takmessage.proto
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
syntax = "proto3";
|
|
2
|
-
option optimize_for = LITE_RUNTIME;
|
|
3
|
-
|
|
4
|
-
import "cotevent.proto";
|
|
5
|
-
import "takcontrol.proto";
|
|
6
|
-
|
|
7
|
-
package atakmap.commoncommo.protobuf.v1;
|
|
8
|
-
|
|
9
|
-
// Top level message sent for TAK Messaging Protocol Version 1.
|
|
10
|
-
message TakMessage {
|
|
11
|
-
// Optional - if omitted, continue using last reported control
|
|
12
|
-
// information
|
|
13
|
-
TakControl takControl = 1;
|
|
14
|
-
|
|
15
|
-
// Optional - if omitted, no event data in this message
|
|
16
|
-
CotEvent cotEvent = 2;
|
|
17
|
-
}
|
|
18
|
-
|
package/assets/takv.proto
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
syntax = "proto3";
|
|
2
|
-
option optimize_for = LITE_RUNTIME;
|
|
3
|
-
|
|
4
|
-
package atakmap.commoncommo.protobuf.v1;
|
|
5
|
-
|
|
6
|
-
// All items are required unless otherwise noted!
|
|
7
|
-
// "required" means if they are missing on send, the conversion
|
|
8
|
-
// to the message format will be rejected and fall back to opaque
|
|
9
|
-
// XML representation
|
|
10
|
-
message Takv {
|
|
11
|
-
string device = 1; // device=
|
|
12
|
-
string platform = 2; // platform=
|
|
13
|
-
string os = 3; // os=
|
|
14
|
-
string version = 4; // version=
|
|
15
|
-
}
|
package/assets/track.proto
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
syntax = "proto3";
|
|
2
|
-
option optimize_for = LITE_RUNTIME;
|
|
3
|
-
|
|
4
|
-
package atakmap.commoncommo.protobuf.v1;
|
|
5
|
-
|
|
6
|
-
// All items are required unless otherwise noted!
|
|
7
|
-
// "required" means if they are missing on send, the conversion
|
|
8
|
-
// to the message format will be rejected and fall back to opaque
|
|
9
|
-
// XML representation
|
|
10
|
-
message Track {
|
|
11
|
-
double speed = 1; // speed=
|
|
12
|
-
double course = 2; // course=
|
|
13
|
-
}
|
package/src/proto.js
DELETED
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
import path from 'path';
|
|
2
|
-
import protobuf from 'protobufjs';
|
|
3
|
-
import xmljs from 'xml-js';
|
|
4
|
-
|
|
5
|
-
//const root = protobuf.loadSync(new URL('../assets/takmessage.proto', import.meta.url));
|
|
6
|
-
//const TakMessage = root.lookupType('atakmap.commoncommo.protobuf.v1.TakMessage');
|
|
7
|
-
|
|
8
|
-
export default class Proto {
|
|
9
|
-
static proto2js(message) {
|
|
10
|
-
if (typeof message === 'undefined' || message === null) {
|
|
11
|
-
throw new Error('Attempted to parse empty TAK proto message');
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
if (typeof message !== Buffer) {
|
|
15
|
-
message = Buffer.from(message, 'hex');
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const result = TakMessage.decode(message);
|
|
19
|
-
return TakMessage.toObject(result, {
|
|
20
|
-
longs: String
|
|
21
|
-
}); // or decode
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
static protojs2cotjs(proto) {
|
|
25
|
-
if (!proto.cotEvent) return null;
|
|
26
|
-
|
|
27
|
-
const cot = {
|
|
28
|
-
'event': {
|
|
29
|
-
'_attributes': {
|
|
30
|
-
'version': '2.0',
|
|
31
|
-
'uid': proto.cotEvent.uid,
|
|
32
|
-
'type': proto.cotEvent.type,
|
|
33
|
-
'time': new Date(parseInt(proto.cotEvent.sendTime)).toISOString(),
|
|
34
|
-
'start': new Date(parseInt(proto.cotEvent.startTime)).toISOString(),
|
|
35
|
-
'stale': new Date(parseInt(proto.cotEvent.staleTime)).toISOString(),
|
|
36
|
-
'how': proto.cotEvent.how
|
|
37
|
-
},
|
|
38
|
-
'point': {
|
|
39
|
-
'_attributes': {
|
|
40
|
-
'lat': proto.cotEvent.lat,
|
|
41
|
-
'lon': proto.cotEvent.lon,
|
|
42
|
-
'hae': proto.cotEvent.hae,
|
|
43
|
-
'ce': proto.cotEvent.ce,
|
|
44
|
-
'le': proto.cotEvent.le
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
};
|
|
49
|
-
if (proto.cotEvent.detail) {
|
|
50
|
-
cot.event.detail = {};
|
|
51
|
-
if (proto.cotEvent.detail.takv) {
|
|
52
|
-
cot.event.detail.takv = {
|
|
53
|
-
'_attributes': {
|
|
54
|
-
'os': proto.cotEvent.detail.takv.os,
|
|
55
|
-
'version': proto.cotEvent.detail.takv.version,
|
|
56
|
-
'device': proto.cotEvent.detail.takv.device,
|
|
57
|
-
'platform': proto.cotEvent.detail.takv.platform
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
if (proto.cotEvent.detail.contact) {
|
|
62
|
-
cot.event.detail.contact = {
|
|
63
|
-
'_attributes': {
|
|
64
|
-
'endpoint': proto.cotEvent.detail.contact.endpoint,
|
|
65
|
-
'callsign': proto.cotEvent.detail.contact.callsign
|
|
66
|
-
}
|
|
67
|
-
};
|
|
68
|
-
// todo add phone
|
|
69
|
-
}
|
|
70
|
-
if (proto.cotEvent.detail.xmlDetail) {
|
|
71
|
-
const result = xmljs.xml2js(proto.cotEvent.detail.xmlDetail, { compact: true });
|
|
72
|
-
cot.event.detail = {
|
|
73
|
-
...cot.event.detail,
|
|
74
|
-
...result
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
if (proto.cotEvent.detail.group) {
|
|
78
|
-
cot.event.detail.__group = {
|
|
79
|
-
'_attributes': {
|
|
80
|
-
'role': proto.cotEvent.detail.group.role,
|
|
81
|
-
'name': proto.cotEvent.detail.group.name
|
|
82
|
-
}
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
if (proto.cotEvent.detail.status) {
|
|
86
|
-
cot.event.detail.status = {
|
|
87
|
-
'_attributes': {
|
|
88
|
-
'battery': proto.cotEvent.detail.status.battery
|
|
89
|
-
}
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
/*
|
|
93
|
-
"precisionlocation": {
|
|
94
|
-
"_attributes": {
|
|
95
|
-
"altsrc": "GPS",
|
|
96
|
-
"geopointsrc": "GPS"
|
|
97
|
-
}
|
|
98
|
-
},
|
|
99
|
-
|
|
100
|
-
"track": {
|
|
101
|
-
"_attributes": {
|
|
102
|
-
"course": "228.71039582198793",
|
|
103
|
-
"speed": "0.0"
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}*/
|
|
107
|
-
}
|
|
108
|
-
return cot;
|
|
109
|
-
}
|
|
110
|
-
}
|