Replace peer-based hidemmsi option by a global list of mmsi in /etc/ais/hidden
[ais.git] / bin / inputs / config.py
1 # -*- coding: utf-8 -*-
2 '''
3 Peers definition
4 '''
5
6 from __future__ import division
7 import logging
8 import pprint
9 from ais.ntools import str_split_column_ipv6
10 from ais.common import strmmsi_to_mmsi
11
12 __all__ = [
13     'peers_get_config',
14     'source_get_infoin',
15     'get_hidden_mmsi',
16     ]
17
18 CONFIG_FILENAME = '/etc/ais/config'
19 HIDDENMMSI_FILENAME = '/etc/ais/hidden'
20
21 SOURCES = {}
22
23 __source_normalized__ = False
24 def peers_get_config():
25     '''
26     SOURCES normalization & checks
27     '''
28     global __source_normalized__
29     if __source_normalized__:
30         return SOURCES
31
32     config_file = file(CONFIG_FILENAME)
33     for lineno_e, line in enumerate(config_file.readlines()):
34         lineno = lineno_e + 1
35         #logging.debug('considering line #%s: %s', lineno, repr(line))
36         line = line.strip(' \r\n')
37         if len(line)==0 or line[0]=='#':
38             logging.debug('ignoring line #%s: %s', lineno, repr(line))
39             continue
40         spl = line.split(' ', 2)
41         assert len(spl) == 3, \
42             'line #%s: There should be at least 3 strings sepatated by spaces' % \
43             lineno
44         id4, io, definition = spl
45         assert len(id4)==4, 'source name (first parameter) must be 4 char long.'
46         logging.debug('%s %s %s', id4, io, definition)
47         if id4 not in SOURCES:
48             SOURCES[id4] = {}
49         if io == 'in':
50             assert len(spl)==3, \
51                 'line #%s: missing information after \'in\'' % \
52                 lineno
53             SOURCES[id4]['in'] = definition
54         elif io ==  'out':
55             assert len(spl)==3, \
56                 'line #%s: missing information after \'out\'' % \
57                 lineno
58             if not SOURCES[id4].has_key('out'):
59                 SOURCES[id4]['out'] = []
60             spl = definition.split(' ')
61             SOURCES[id4]['out'].append(spl)
62         elif io ==  'name':
63             SOURCES[id4]['name'] = definition
64         else:
65             raise AssertionError( \
66                 "line #%s: second parameter must be either 'in' or 'out'.\r%s" % \
67                 (lineno, line))
68
69     logging.debug('SOURCES \n%s', pprint.pformat(SOURCES))
70
71     logging.debug('SOURCES (raw):\n%s', pprint.pformat(SOURCES))
72     for id4, settings in SOURCES.iteritems():
73         assert len(id4) == 4, \
74             'Invalid ID %s in SOURCES. It should be 4 characters long.'
75
76         if 'name' not in settings:
77             settings['name'] =id4
78         try:
79             in_ = settings['in']
80         except KeyError:
81             continue
82     
83         assert type(in_) == str, \
84             "SOURCES['%s']['in'] should be of type str, not %s'." % \
85             (id4, type(in_))
86         in_ = settings.get('in')
87         spl = str_split_column_ipv6(in_)
88         protocol, parameters = spl[0], spl[1:]
89         assert parameters, \
90             "SOURCES['%s']['in'] should contain parameters after %s" % \
91             (id4, protocol)
92            
93         if protocol == 'udp':
94             assert len(parameters) == 4, \
95                 "SOURCES['%s']['in'] udp: protocol requires 4 parameters." % \
96                 id4
97             host_local = parameters[0]
98             if host_local == '*':
99                 host_local = ''
100             port_local = parameters[1]
101             assert port_local.isdigit(), \
102                 "SOURCES['%s']['in'] udp: illegal local port" % \
103                 id4
104             port_local = int(port_local)
105             host_remote = parameters[2]
106             if host_remote == '*':
107                 host_remote = ''
108             port_remote = parameters[3]
109             source_port_discreminate = False
110             if port_remote == '*':
111                 port_remote = ''
112             elif port_remote == '!':
113                 port_remote = ''
114                 source_port_discreminate = True
115             else:
116                 assert port_remote.isdigit(), \
117                     "SOURCES['%s']['in'] udp: illegal remote port" % \
118                     id4
119                 port_remote = int(port_remote)
120          
121             SOURCES[id4]['in'] = (protocol.title(), host_local, port_local, host_remote, port_remote, source_port_discreminate)
122
123         elif protocol == 'serial':
124             assert len(parameters) == 1, \
125                 "SOURCES['%s']['in'] serial: protocol requires 1 parameter." % \
126                 id4
127             SOURCES[id4]['in'] = tuple(['Serial'] + parameters)
128
129         elif protocol == 'tcpout':
130             assert len(parameters) == 2, \
131                 "SOURCES['%s']['in'] tcpout: protocol requires 2 parameters." % \
132                 id4
133             host = parameters[0]
134             port = int(parameters[1])
135             SOURCES[id4]['in'] = ('TcpOut', host, port)
136
137         else:
138             logging.error(
139                 "Unsupported source protocol '%s' for SOURCES['%s']: %s",
140                 protocol, id4, in_)
141     __source_normalized__ = True
142     logging.debug('SOURCES (normalised):\n%s', pprint.pformat(SOURCES))
143     return SOURCES
144
145
146
147 def source_get_infoin(*args):
148     '''
149     Gets a list of matching info in SOURCES[*]['in']:
150     example: get_source_infoin('Udp', '', 1234)
151       might return [ ['SRC1', '12.23.34.45', '', False],
152                      ['SRC2', '98.87.76.65', '', True]]
153     '''
154     result = []
155     for id4, source_info in peers_get_config().iteritems():
156         rawin = source_info.get('in', None)
157         if not rawin:
158             continue
159         if args == rawin[:len(args)]:
160             newmatch = [id4] + [rawin[i] for i in range(len(args), len(rawin))]
161             result.append(newmatch)
162     return result
163         
164 __hidden_mmsi__ = None
165 def get_hidden_mmsi():
166     global __hidden_mmsi__
167     if __hidden_mmsi__ is None:
168         __hidden_mmsi__ = []
169         try:
170             lines = file(HIDDENMMSI_FILENAME).read().split('\n')
171         except IOError, err:
172             if err.errno == 2: # No such file or directory
173                 logging.info('No hidden ship')
174                 return __hidden_mmsi__
175             else:
176                 raise
177         for line in lines:
178             line = line.strip()
179             if len(line)==0 or line[0] == '#':
180                 continue  # ignore empty lines & comments
181             mmsi = strmmsi_to_mmsi(line)
182             __hidden_mmsi__.append(mmsi)
183     return __hidden_mmsi__
184
185