Browse Source

Initial commit

Maurizio Porrato 8 years ago
commit
efe8685371
8 changed files with 470 additions and 0 deletions
  1. 165
    0
      LICENSE.txt
  2. 9
    0
      docs/stations.conf.sample
  3. 0
    0
      frn/__init__.py
  4. 0
    0
      frn/protocol/__init__.py
  5. 167
    0
      frn/protocol/client.py
  6. 46
    0
      frn/utility.py
  7. 47
    0
      parrot.py
  8. 36
    0
      tools/gsmdecode.c

+ 165
- 0
LICENSE.txt View File

@@ -0,0 +1,165 @@
1
+                   GNU LESSER GENERAL PUBLIC LICENSE
2
+                       Version 3, 29 June 2007
3
+
4
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
5
+ Everyone is permitted to copy and distribute verbatim copies
6
+ of this license document, but changing it is not allowed.
7
+
8
+
9
+  This version of the GNU Lesser General Public License incorporates
10
+the terms and conditions of version 3 of the GNU General Public
11
+License, supplemented by the additional permissions listed below.
12
+
13
+  0. Additional Definitions.
14
+
15
+  As used herein, "this License" refers to version 3 of the GNU Lesser
16
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
17
+General Public License.
18
+
19
+  "The Library" refers to a covered work governed by this License,
20
+other than an Application or a Combined Work as defined below.
21
+
22
+  An "Application" is any work that makes use of an interface provided
23
+by the Library, but which is not otherwise based on the Library.
24
+Defining a subclass of a class defined by the Library is deemed a mode
25
+of using an interface provided by the Library.
26
+
27
+  A "Combined Work" is a work produced by combining or linking an
28
+Application with the Library.  The particular version of the Library
29
+with which the Combined Work was made is also called the "Linked
30
+Version".
31
+
32
+  The "Minimal Corresponding Source" for a Combined Work means the
33
+Corresponding Source for the Combined Work, excluding any source code
34
+for portions of the Combined Work that, considered in isolation, are
35
+based on the Application, and not on the Linked Version.
36
+
37
+  The "Corresponding Application Code" for a Combined Work means the
38
+object code and/or source code for the Application, including any data
39
+and utility programs needed for reproducing the Combined Work from the
40
+Application, but excluding the System Libraries of the Combined Work.
41
+
42
+  1. Exception to Section 3 of the GNU GPL.
43
+
44
+  You may convey a covered work under sections 3 and 4 of this License
45
+without being bound by section 3 of the GNU GPL.
46
+
47
+  2. Conveying Modified Versions.
48
+
49
+  If you modify a copy of the Library, and, in your modifications, a
50
+facility refers to a function or data to be supplied by an Application
51
+that uses the facility (other than as an argument passed when the
52
+facility is invoked), then you may convey a copy of the modified
53
+version:
54
+
55
+   a) under this License, provided that you make a good faith effort to
56
+   ensure that, in the event an Application does not supply the
57
+   function or data, the facility still operates, and performs
58
+   whatever part of its purpose remains meaningful, or
59
+
60
+   b) under the GNU GPL, with none of the additional permissions of
61
+   this License applicable to that copy.
62
+
63
+  3. Object Code Incorporating Material from Library Header Files.
64
+
65
+  The object code form of an Application may incorporate material from
66
+a header file that is part of the Library.  You may convey such object
67
+code under terms of your choice, provided that, if the incorporated
68
+material is not limited to numerical parameters, data structure
69
+layouts and accessors, or small macros, inline functions and templates
70
+(ten or fewer lines in length), you do both of the following:
71
+
72
+   a) Give prominent notice with each copy of the object code that the
73
+   Library is used in it and that the Library and its use are
74
+   covered by this License.
75
+
76
+   b) Accompany the object code with a copy of the GNU GPL and this license
77
+   document.
78
+
79
+  4. Combined Works.
80
+
81
+  You may convey a Combined Work under terms of your choice that,
82
+taken together, effectively do not restrict modification of the
83
+portions of the Library contained in the Combined Work and reverse
84
+engineering for debugging such modifications, if you also do each of
85
+the following:
86
+
87
+   a) Give prominent notice with each copy of the Combined Work that
88
+   the Library is used in it and that the Library and its use are
89
+   covered by this License.
90
+
91
+   b) Accompany the Combined Work with a copy of the GNU GPL and this license
92
+   document.
93
+
94
+   c) For a Combined Work that displays copyright notices during
95
+   execution, include the copyright notice for the Library among
96
+   these notices, as well as a reference directing the user to the
97
+   copies of the GNU GPL and this license document.
98
+
99
+   d) Do one of the following:
100
+
101
+       0) Convey the Minimal Corresponding Source under the terms of this
102
+       License, and the Corresponding Application Code in a form
103
+       suitable for, and under terms that permit, the user to
104
+       recombine or relink the Application with a modified version of
105
+       the Linked Version to produce a modified Combined Work, in the
106
+       manner specified by section 6 of the GNU GPL for conveying
107
+       Corresponding Source.
108
+
109
+       1) Use a suitable shared library mechanism for linking with the
110
+       Library.  A suitable mechanism is one that (a) uses at run time
111
+       a copy of the Library already present on the user's computer
112
+       system, and (b) will operate properly with a modified version
113
+       of the Library that is interface-compatible with the Linked
114
+       Version.
115
+
116
+   e) Provide Installation Information, but only if you would otherwise
117
+   be required to provide such information under section 6 of the
118
+   GNU GPL, and only to the extent that such information is
119
+   necessary to install and execute a modified version of the
120
+   Combined Work produced by recombining or relinking the
121
+   Application with a modified version of the Linked Version. (If
122
+   you use option 4d0, the Installation Information must accompany
123
+   the Minimal Corresponding Source and Corresponding Application
124
+   Code. If you use option 4d1, you must provide the Installation
125
+   Information in the manner specified by section 6 of the GNU GPL
126
+   for conveying Corresponding Source.)
127
+
128
+  5. Combined Libraries.
129
+
130
+  You may place library facilities that are a work based on the
131
+Library side by side in a single library together with other library
132
+facilities that are not Applications and are not covered by this
133
+License, and convey such a combined library under terms of your
134
+choice, if you do both of the following:
135
+
136
+   a) Accompany the combined library with a copy of the same work based
137
+   on the Library, uncombined with any other library facilities,
138
+   conveyed under the terms of this License.
139
+
140
+   b) Give prominent notice with the combined library that part of it
141
+   is a work based on the Library, and explaining where to find the
142
+   accompanying uncombined form of the same work.
143
+
144
+  6. Revised Versions of the GNU Lesser General Public License.
145
+
146
+  The Free Software Foundation may publish revised and/or new versions
147
+of the GNU Lesser General Public License from time to time. Such new
148
+versions will be similar in spirit to the present version, but may
149
+differ in detail to address new problems or concerns.
150
+
151
+  Each version is given a distinguishing version number. If the
152
+Library as you received it specifies that a certain numbered version
153
+of the GNU Lesser General Public License "or any later version"
154
+applies to it, you have the option of following the terms and
155
+conditions either of that published version or of any later version
156
+published by the Free Software Foundation. If the Library as you
157
+received it does not specify a version number of the GNU Lesser
158
+General Public License, you may choose any version of the GNU Lesser
159
+General Public License ever published by the Free Software Foundation.
160
+
161
+  If the Library as you received it specifies that a proxy can decide
162
+whether future versions of the GNU Lesser General Public License shall
163
+apply, that proxy's public statement of acceptance of any version is
164
+permanent authorization for you to choose that version for the
165
+Library.

+ 9
- 0
docs/stations.conf.sample View File

@@ -0,0 +1,9 @@
1
+[accountname]
2
+operator=callsign, name
3
+email=me@example.com
4
+city=City - Locator
5
+country=Your country
6
+transmission=PC Only
7
+description=
8
+password=SECRET
9
+network=Default network

+ 0
- 0
frn/__init__.py View File


+ 0
- 0
frn/protocol/__init__.py View File


+ 167
- 0
frn/protocol/client.py View File

@@ -0,0 +1,167 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+from twisted.internet.protocol import ClientFactory
4
+from twisted.protocols.basic import LineReceiver
5
+from frn.utility import *
6
+
7
+
8
+class FRNClient(LineReceiver):
9
+
10
+    client_version = 2010002
11
+
12
+    def connectionMade(self):
13
+        self.login()
14
+
15
+    def ready(self):
16
+        self.status = 'READY'
17
+        self.msgbuffer = []
18
+        self.phase = 0
19
+        self.setRawMode()
20
+
21
+    def startMultiLineMessage(self, msgtype, rest=''):
22
+        self.status = msgtype
23
+        self.phase = None
24
+        self.setLineMode(rest)
25
+
26
+    def collectMultiLineMessage(self, line):
27
+        if self.phase is None:
28
+            while line[0] not in '0123456789': # needed for client list
29
+                line = line[1:]
30
+            self.expected_lines = int(line.strip())
31
+            self.msgbuffer = []
32
+            self.phase = 0
33
+        else:
34
+            self.msgbuffer.append(line)
35
+            self.phase += 1
36
+            if self.phase >= self.expected_lines:
37
+                handler = getattr(self, 'on_'+self.status, self.on_unimplemented)
38
+                message = self.msgbuffer
39
+                self.ready()
40
+                handler(message)
41
+
42
+    def startAudioMessage(self, rest=''):
43
+        self.status = 'AUDIO'
44
+        self.phase = None
45
+        self.msgbuffer = ''
46
+        if len(rest) > 0:
47
+            self.collectAudioMessage(rest)
48
+
49
+    def collectAudioMessage(self, data):
50
+        needed = min(327, max(0, 327-len(self.msgbuffer)))
51
+        if len(data) > 0:
52
+            self.msgbuffer += data[:needed]
53
+        if len(self.msgbuffer) >= 327:
54
+            audio_data = self.msgbuffer
55
+            source = ord(audio_data[0])*256+ord(audio_data[1])
56
+            self.ready()
57
+            self.on_AUDIO(source, audio_data[2:])
58
+        if len(data) > needed:
59
+            self.dataReceived(data[needed:])
60
+
61
+    def lineReceived(self, line):
62
+        if self.status == 'AUTH':
63
+            if self.phase == 0:
64
+                self.latest_client_version = int(line.strip())
65
+                self.phase = 1
66
+            else:
67
+                self.serverdata = parseSimpleXML(line.strip())
68
+                print self.serverdata
69
+                if int(self.serverdata['sv']) > 2009004:
70
+                    self.sendLine(makeAuthKey(self.serverdata['kp']))
71
+                self.ready()
72
+        else:
73
+            self.collectMultiLineMessage(line)
74
+
75
+    def rawDataReceived(self, data):
76
+        if self.status == 'READY':
77
+            packet_type = ord(data[0])
78
+            if packet_type == 0: # Keepalive
79
+                self.sendLine('P')
80
+            elif packet_type == 1: # TX ack
81
+                self.status == 'TX'
82
+                self.phase = 0
83
+                if len(data) > 1:
84
+                    self.dataReceived(data[1:])
85
+            elif packet_type == 2: # Audio
86
+                self.startAudioMessage(data[1:])
87
+            elif packet_type == 3: # Client list
88
+                self.startMultiLineMessage('CLIENTS', data[1:])
89
+            elif packet_type == 4: # SMS
90
+                self.startMultiLineMessage('SMS', data[1:])
91
+            elif packet_type == 5: # Channel list
92
+                self.startMultiLineMessage('CHANNELS', data[1:])
93
+            else:
94
+                print "Unknown packet type %d" % packet_type
95
+        elif self.status == 'AUDIO':
96
+            self.collectAudioMessage(data)
97
+        elif self.status == 'TX':
98
+            if self.phase == 0:
99
+                self.phase = 1
100
+                self.on_TX(ord(data[0])*256+ord(data[1]))
101
+
102
+    def login(self):
103
+        d = self.factory.client_id
104
+        fields = [
105
+            ('VX', self.client_version),
106
+            ('EA', d['email']),
107
+            ('PW', d['password']),
108
+            ('ON', d['operator']),
109
+            ('BC', d['transmission']),
110
+            ('DS', d['description']),
111
+            ('NN', d['country']),
112
+            ('CT', d['city']),
113
+            ('NT', d['network'])
114
+        ]
115
+        ap = "CT:"+formatSimpleXML(fields)
116
+        self.status = 'AUTH'
117
+        self.phase = 0
118
+        self.sendLine(ap)
119
+        #self.request_rx()
120
+
121
+    def set_status(self, status):
122
+        self.sendLine('ST:%s' % str(status))
123
+
124
+    def request_rx(self):
125
+        self.sendLine('RX0')
126
+
127
+    def request_tx(self):
128
+        self.sendLine('TX0')
129
+
130
+    def send_audio(self, frame):
131
+        self.sendLine('TX1')
132
+        self.transport.write(frame)
133
+
134
+    def send_SMS(self, dest, text):
135
+        self.sendLine('TM:'+formatSimpleXML(dict(ID=dest, MS=text)))
136
+
137
+    def on_unimplemented(self, msg):
138
+        print msg
139
+
140
+    def on_AUDIO(self, from_id, frames):
141
+        pass
142
+
143
+    def on_TX(self, my_id):
144
+        pass
145
+
146
+
147
+class FRNClientFactory(ClientFactory):
148
+
149
+    protocol = FRNClient
150
+
151
+    def __init__(self, **kw):
152
+        self.client_id = kw
153
+
154
+    def startedConnecting(self, connector):
155
+        print 'Started to connect.'
156
+
157
+    def buildProtocol(self, addr):
158
+		print 'Connected.'
159
+		return ClientFactory.buildProtocol(self, addr)
160
+
161
+    def clientConnectionLost(self, connector, reason):
162
+        print 'Lost connection.  Reason:', reason
163
+
164
+    def clientConnectionFailed(self, connector, reason):
165
+        print 'Connection failed. Reason:', reason
166
+
167
+# vim: set et ai sw=4 ts=4 sts=4:

+ 46
- 0
frn/utility.py View File

@@ -0,0 +1,46 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+from HTMLParser import HTMLParser
4
+
5
+
6
+def makeAuthKey(kp):
7
+	if len(kp) != 6:
8
+		return 'ERROR'
9
+	aa, bb, cc = int(kp[:2]), int(kp[2:4]), int(kp[4:6])
10
+	defgh = "%05d" % ((cc+7)*(cc+4)+(bb+1)*(aa+2), )
11
+	return defgh[3]+defgh[0]+defgh[2]+defgh[4]+defgh[1]
12
+
13
+
14
+class SimpleXMLParser(HTMLParser):
15
+
16
+    def handle_starttag(self, tag, attrs):
17
+        if not hasattr(self, 'fields'):
18
+            self.fields = {}
19
+        self.next_field = tag
20
+        self.next_data = ''
21
+
22
+    def handle_data(self, data):
23
+        self.next_data += data
24
+
25
+    def handle_endtag(self, tag):
26
+        self.fields[self.next_field] = self.next_data
27
+        self.next_field = self.next_data = None
28
+
29
+    def get_fields(self):
30
+        return self.fields
31
+
32
+
33
+def parseSimpleXML(xml):
34
+    p = SimpleXMLParser()
35
+    p.feed(xml)
36
+    return p.get_fields()
37
+
38
+
39
+def formatSimpleXML(elements):
40
+    if hasattr(elements, 'items'):
41
+        items = elements.items()
42
+    else:
43
+        items = elements
44
+    return ''.join(["<%s>%s</%s>" % (k.upper(),v,k.upper()) for k,v in items])
45
+
46
+# vim: set et ai sw=4 ts=4 sts=4:

+ 47
- 0
parrot.py View File

@@ -0,0 +1,47 @@
1
+#!/usr/bin/env python
2
+# -*- coding: utf-8 -*-
3
+
4
+from __future__ import with_statement
5
+from frn.protocol.client import FRNClient, FRNClientFactory
6
+
7
+
8
+class FRNParrot(FRNClient):
9
+
10
+    def on_SMS(self, msg):
11
+        print "Messaggio %s da %s: %s" % (msg[2], msg[0], msg[1])
12
+        if msg[2][0] == 'P': # Risponde solo ai messaggi privati
13
+            self.send_SMS(msg[0], msg[1])
14
+
15
+    def on_AUDIO(self, from_id, frames):
16
+        print "AUDIO from %d (%d bytes)" % (from_id, len(frames))
17
+        with file('rx-%d.gsm' % from_id, 'ab') as f:
18
+            f.write(frames)
19
+
20
+
21
+class FRNParrotFactory(FRNClientFactory):
22
+    protocol = FRNParrot
23
+
24
+
25
+if __name__ == '__main__':
26
+    import sys
27
+    from ConfigParser import ConfigParser
28
+    from twisted.internet import reactor
29
+
30
+    cfg = ConfigParser()
31
+    cfg.read('stations.conf')
32
+
33
+    if len(sys.argv) > 1:
34
+        station = sys.argv[1]
35
+    else:
36
+        stations = cfg.sections()
37
+        stations.sort()
38
+        station = cfg.sections()[0]
39
+        print "Profile not specified: using '%s'" % station
40
+
41
+    station_conf = dict(cfg.items(station))
42
+
43
+    reactor.connectTCP('master.freeradionetwork.it', 10024,
44
+        FRNParrotFactory(**station_conf))
45
+    reactor.run()
46
+
47
+# vim: set et ai sw=4 ts=4 sts=4:

+ 36
- 0
tools/gsmdecode.c View File

@@ -0,0 +1,36 @@
1
+#include <stdio.h>
2
+#include <errno.h>
3
+#include <string.h>
4
+#include <gsm.h>
5
+
6
+int main() {
7
+    gsm handle = gsm_create();
8
+    int one  = 1;
9
+    int zero = 0;
10
+    gsm_option(handle, GSM_OPT_WAV49, &one);
11
+    unsigned char src610[65];
12
+    gsm_frame src;
13
+    gsm_signal dst[160];
14
+    unsigned char outbuff[160];
15
+    while (!feof(stdin)) {
16
+        int rv = fread(src610, 65, 1, stdin);
17
+        if (rv < 0) {
18
+            fprintf(stderr, "%s\n", strerror(errno));
19
+            return -1;
20
+        }
21
+        else if (rv == 0) {
22
+            return 0;
23
+        }
24
+        int frameIndex = 0;
25
+        for (frameIndex = 0; frameIndex <= 1; frameIndex++) {
26
+            int iFrameIndex = !frameIndex;
27
+            gsm_decode(handle, src610 + 33 * frameIndex, dst);
28
+            rv = fwrite(dst, sizeof dst, 1, stdout);
29
+            if (rv < 0) {
30
+                fprintf(stderr, "%s\n", strerror(errno));
31
+                return -1;
32
+            }
33
+        }
34
+    }
35
+    gsm_destroy(handle);
36
+}

Loading…
Cancel
Save