blob: 4c895b97dcad5cce578f5bcf54d947506b939c81 [file] [log] [blame]
Wei-Yu Chenad55cb82022-02-15 20:07:01 +08001# SPDX-FileCopyrightText: 2020 The Magma Authors.
2# SPDX-FileCopyrightText: 2022 Open Networking Foundation <support@opennetworking.org>
3#
4# SPDX-License-Identifier: BSD-3-Clause
Wei-Yu Chen49950b92021-11-08 19:19:18 +08005
Wei-Yu Chen49950b92021-11-08 19:19:18 +08006from datetime import datetime, timedelta, timezone
7from unittest import TestCase, mock
8from unittest.mock import Mock, patch
9
10import lxml.etree as ET
11from tests.test_utils.enb_acs_builder import (
12 EnodebAcsStateMachineBuilder,
13)
14from tr069 import models
15from tr069.rpc_methods import AutoConfigServer
16from tr069.spyne_mods import Tr069Application, Tr069Soap11
17from spyne import MethodContext
18from spyne.server import ServerBase
19
20
21class Tr069Test(TestCase):
22 """ Tests for the TR-069 server """
23 acs_to_cpe_queue = None
24 cpe_to_acs_queue = None
25
26 def setUp(self):
27 # Set up the ACS
28 self.enb_acs_manager = EnodebAcsStateMachineBuilder.build_acs_manager()
29 self.handler = EnodebAcsStateMachineBuilder.build_acs_state_machine()
30 AutoConfigServer.set_state_machine_manager(self.enb_acs_manager)
31
32 def side_effect(*args, **_kwargs):
33 msg = args[1]
34 return msg
35
36 self.p = patch.object(
37 AutoConfigServer, '_handle_tr069_message',
38 Mock(side_effect=side_effect),
39 )
40 self.p.start()
41
42 self.app = Tr069Application(
43 [AutoConfigServer],
44 models.CWMP_NS,
45 in_protocol=Tr069Soap11(validator='soft'),
46 out_protocol=Tr069Soap11(),
47 )
48
49 def tearDown(self):
50 self.p.stop()
51 self.handler = None
52
53 def _get_mconfig(self):
54 return {
55 "@type": "type.googleapis.com/magma.mconfig.EnodebD",
56 "bandwidthMhz": 20,
57 "specialSubframePattern": 7,
58 "earfcndl": 44490,
59 "logLevel": "INFO",
60 "plmnidList": "00101",
61 "pci": 260,
62 "allowEnodebTransmit": False,
63 "subframeAssignment": 2,
64 "tac": 1,
65 },
66
67 def _get_service_config(self):
68 return {
69 "tr069": {
70 "interface": "eth1",
71 "port": 48080,
72 "perf_mgmt_port": 8081,
73 "public_ip": "192.88.99.142",
74 },
75 "reboot_enodeb_on_mme_disconnected": True,
76 "s1_interface": "eth1",
77 }
78
79 def test_acs_manager_exception(self):
80 """
81 Test that an unexpected exception from the ACS SM manager will result
82 in an empty response.
83 """
84 self.enb_acs_manager.handle_tr069_message = mock.MagicMock(
85 side_effect=Exception('mock exception'),
86 )
87 # stop the patcher because we want to use the above MagicMock
88 self.p.stop()
89 server = ServerBase(self.app)
90
91 ctx = MethodContext(server, MethodContext.SERVER)
92 ctx.in_string = [b'']
93 ctx, = server.generate_contexts(ctx)
94
95 server.get_in_object(ctx)
96 self.assertIsNone(ctx.in_error)
97
98 server.get_out_object(ctx)
99 self.assertIsNone(ctx.out_error)
100
101 server.get_out_string(ctx)
102 self.assertEqual(b''.join(ctx.out_string), b'')
103
104 # start the patcher otherwise the p.stop() in tearDown will complain
105 self.p.start()
106
107 def test_parse_inform(self):
108 """
109 Test that example Inform RPC call can be parsed correctly
110 """
111 # Example TR-069 CPE->ACS RPC call. Copied from:
112 # http://djuro82.blogspot.com/2011/05/tr-069-cpe-provisioning.html
113 cpe_string = b'''
114 <soapenv:Envelope soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:soap="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cwmp="urn:dslforum-org:cwmp-1-0">
115 <soapenv:Header>
116 <cwmp:ID soapenv:mustUnderstand="1">0_THOM_TR69_ID</cwmp:ID>
117 </soapenv:Header>
118 <soapenv:Body>
119 <cwmp:Inform>
120 <DeviceId>
121 <Manufacturer>THOMSON</Manufacturer>
122 <OUI>00147F</OUI>
123 <ProductClass>SpeedTouch 780</ProductClass>
124 <SerialNumber>CP0611JTLNW</SerialNumber>
125 </DeviceId>
126 <Event soap:arrayType="cwmp:EventStruct[04]">
127 <EventStruct>
128 <EventCode>0 BOOTSTRAP</EventCode>
129 <CommandKey></CommandKey>
130 </EventStruct>
131 <EventStruct>
132 <EventCode>1 BOOT</EventCode>
133 <CommandKey></CommandKey>
134 </EventStruct>
135 <EventStruct>
136 <EventCode>2 PERIODIC</EventCode>
137 <CommandKey></CommandKey>
138 </EventStruct>
139 <EventStruct>
140 <EventCode>4 VALUE CHANGE</EventCode>
141 <CommandKey></CommandKey>
142 </EventStruct>
143 </Event>
144 <MaxEnvelopes>2</MaxEnvelopes>
145 <CurrentTime>1970-01-01T00:01:09Z</CurrentTime>
146 <RetryCount>05</RetryCount>
147 <ParameterList soap:arrayType="cwmp:ParameterValueStruct[12]">
148 <ParameterValueStruct>
149 <Name>InternetGatewayDevice.DeviceSummary</Name>
150 <Value xsi:type="xsd:string">
151 InternetGatewayDevice:1.1[] (Baseline:1, EthernetLAN:1, ADSLWAN:1, Bridging:1, Time:1, WiFiLAN:1)</Value>
152 </ParameterValueStruct>
153 <ParameterValueStruct>
154 <Name>InternetGatewayDevice.DeviceInfo.SpecVersion</Name>
155 <Value xsi:type="xsd:string">1.1</Value>
156 </ParameterValueStruct>
157 <ParameterValueStruct>
158 <Name>InternetGatewayDevice.DeviceInfo.HardwareVersion</Name>
159 <Value xsi:type="xsd:string">BANT-R</Value>
160 </ParameterValueStruct>
161 <ParameterValueStruct>
162 <Name>InternetGatewayDevice.DeviceInfo.SoftwareVersion</Name>
163 <Value xsi:type="xsd:string">6.2.35.0</Value>
164 </ParameterValueStruct>
165 <ParameterValueStruct>
166 <Name>InternetGatewayDevice.DeviceInfo.ProvisioningCode</Name>
167 <Value xsi:type="xsd:string"></Value>
168 </ParameterValueStruct>
169 <ParameterValueStruct>
170 <Name>InternetGatewayDevice.DeviceInfo.VendorConfigFile.1.Name</Name>
171 <Value xsi:type="xsd:string">MyCompanyName</Value>
172 </ParameterValueStruct>
173 <ParameterValueStruct>
174 <Name>InternetGatewayDevice.DeviceInfo.VendorConfigFile.1.Version</Name>
175 <Value xsi:type="xsd:string"></Value>
176 </ParameterValueStruct>
177 <ParameterValueStruct>
178 <Name>InternetGatewayDevice.DeviceInfo.VendorConfigFile.1.Date</Name>
179 <Value xsi:type="xsd:dateTime">0001-01-01T00:00:00</Value>
180 </ParameterValueStruct>
181 <ParameterValueStruct>
182 <Name>InternetGatewayDevice.DeviceInfo.VendorConfigFile.1.Description</Name>
183 <Value xsi:type="xsd:string">MyCompanyName</Value>
184 </ParameterValueStruct>
185 <ParameterValueStruct>
186 <Name>InternetGatewayDevice.ManagementServer.ConnectionRequestURL</Name>
187 <Value xsi:type="xsd:string">http://10.127.129.205:51005/</Value>
188 </ParameterValueStruct>
189 <ParameterValueStruct>
190 <Name>InternetGatewayDevice.ManagementServer.ParameterKey</Name>
191 <Value xsi:type="xsd:string"></Value>
192 </ParameterValueStruct>
193 <ParameterValueStruct>
194 <Name>InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANIPConnection.1.ExternalIPAddress</Name>
195 <Value xsi:type="xsd:string">10.127.129.205</Value>
196 </ParameterValueStruct>
197 </ParameterList>
198 </cwmp:Inform>
199 </soapenv:Body>
200 </soapenv:Envelope>
201 '''
202
203 server = ServerBase(self.app)
204
205 ctx = MethodContext(server, MethodContext.SERVER)
206 ctx.in_string = [cpe_string]
207 ctx, = server.generate_contexts(ctx)
208
209 if ctx.in_error is not None:
210 print('In error: %s' % ctx.in_error)
211 self.assertEqual(ctx.in_error, None)
212
213 server.get_in_object(ctx)
214
215 self.assertEqual(ctx.in_object.DeviceId.OUI, '00147F')
216 self.assertEqual(
217 ctx.in_object.Event.EventStruct[0].EventCode, '0 BOOTSTRAP',
218 )
219 self.assertEqual(
220 ctx.in_object.Event.EventStruct[2].EventCode, '2 PERIODIC',
221 )
222 self.assertEqual(ctx.in_object.MaxEnvelopes, 2)
223 self.assertEqual(
224 ctx.in_object.ParameterList.ParameterValueStruct[1].Name,
225 'InternetGatewayDevice.DeviceInfo.SpecVersion',
226 )
227 self.assertEqual(
228 str(ctx.in_object.ParameterList.ParameterValueStruct[1].Value), '1.1',
229 )
230
231 def test_parse_inform_cavium(self):
232 """
233 Test that example Inform RPC call can be parsed correctly from OC-LTE
234 """
235 cpe_string = b'''<?xml version="1.0" encoding="UTF-8"?>
236 <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:cwmp="urn:dslforum-org:cwmp-1-0">
237 <SOAP-ENV:Header>
238 <cwmp:ID SOAP-ENV:mustUnderstand="1">CPE_1002</cwmp:ID>
239 </SOAP-ENV:Header>
240 <SOAP-ENV:Body SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
241 <cwmp:Inform>
242 <DeviceId>
243 <Manufacturer>Cavium, Inc.</Manufacturer>
244 <OUI>000FB7</OUI>
245 <ProductClass>Cavium eNB</ProductClass>
246 <SerialNumber>10.18.104.79</SerialNumber>
247 </DeviceId>
248 <Event xsi:type="SOAP-ENC:Array" SOAP-ENC:arrayType="cwmp:EventStruct[1]">
249 <EventStruct>
250 <EventCode>0 BOOTSTRAP</EventCode>
251 <CommandKey></CommandKey>
252 </EventStruct>
253 </Event>
254 <MaxEnvelopes>1</MaxEnvelopes>
255 <CurrentTime>1970-01-02T00:01:05.021239+00:00</CurrentTime>
256 <RetryCount>2</RetryCount>
257 <ParameterList xsi:type="SOAP-ENC:Array" SOAP-ENC:arrayType="cwmp:ParameterValueStruct[15]">
258 <ParameterValueStruct>
259 <Name>Device.DeviceInfo.HardwareVersion</Name>
260 <Value xsi:type="xsd:string">1.0</Value>
261 </ParameterValueStruct>
262 <ParameterValueStruct>
263 <Name>Device.DeviceInfo.SoftwareVersion</Name>
264 <Value xsi:type="xsd:string">1.0</Value>
265 </ParameterValueStruct>
266 <ParameterValueStruct>
267 <Name>Device.DeviceInfo.AdditionalHardwareVersion</Name>
268 <Value xsi:type="xsd:string">1.0</Value>
269 </ParameterValueStruct>
270 <ParameterValueStruct>
271 <Name>Device.DeviceInfo.AdditionalSoftwareVersion</Name>
272 <Value xsi:type="xsd:string">1.0</Value>
273 </ParameterValueStruct>
274 <ParameterValueStruct>
275 <Name>Device.DeviceInfo.ProvisioningCode</Name>
276 <Value xsi:type="xsd:string">Cavium</Value>
277 </ParameterValueStruct>
278 <ParameterValueStruct>
279 <Name>Device.ManagementServer.ParameterKey</Name>
280 <Value xsi:type="xsd:string"></Value>
281 </ParameterValueStruct>
282 <ParameterValueStruct>
283 <Name>Device.ManagementServer.ConnectionRequestURL</Name>
284 <Value xsi:type="xsd:string">http://192.88.99.253:8084/bucrhzjd</Value>
285 </ParameterValueStruct>
286 <ParameterValueStruct>
287 <Name>Device.ManagementServer.UDPConnectionRequestAddress</Name>
288 <Value xsi:type="xsd:string"></Value>
289 </ParameterValueStruct>
290 <ParameterValueStruct>
291 <Name>Device.ManagementServer.NATDetected</Name>
292 <Value xsi:type="xsd:boolean">0</Value>
293 </ParameterValueStruct>
294 <ParameterValueStruct>
295 <Name>Device.IP.Diagnostics.UDPEchoConfig.PacketsReceived</Name>
296 <Value xsi:type="xsd:unsignedInt">0</Value>
297 </ParameterValueStruct>
298 <ParameterValueStruct>
299 <Name>Device.IP.Diagnostics.UDPEchoConfig.PacketsResponded</Name>
300 <Value xsi:type="xsd:unsignedInt">0</Value>
301 </ParameterValueStruct>
302 <ParameterValueStruct>
303 <Name>Device.IP.Diagnostics.UDPEchoConfig.BytesReceived</Name>
304 <Value xsi:type="xsd:unsignedInt">0</Value>
305 </ParameterValueStruct>
306 <ParameterValueStruct>
307 <Name>Device.IP.Diagnostics.UDPEchoConfig.BytesResponded</Name>
308 <Value xsi:type="xsd:unsignedInt">0</Value>
309 </ParameterValueStruct>
310 <ParameterValueStruct>
311 <Name>Device.IP.Diagnostics.UDPEchoConfig.TimeFirstPacketReceived</Name>
312 <Value xsi:type="xsd:dateTime">1969-12-31T16:00:00.000000+00:00</Value>
313 </ParameterValueStruct>
314 <ParameterValueStruct>
315 <Name>Device.IP.Diagnostics.UDPEchoConfig.TimeLastPacketReceived</Name>
316 <Value xsi:type="xsd:dateTime">1969-12-31T16:00:00.000000+00:00</Value>
317 </ParameterValueStruct>
318 </ParameterList>
319 </cwmp:Inform>
320 </SOAP-ENV:Body>
321 </SOAP-ENV:Envelope>
322 '''
323
324 server = ServerBase(self.app)
325
326 ctx = MethodContext(server, MethodContext.SERVER)
327 ctx.in_string = [cpe_string]
328 ctx, = server.generate_contexts(ctx)
329
330 if ctx.in_error is not None:
331 print('In error: %s' % ctx.in_error)
332 self.assertEqual(ctx.in_error, None)
333
334 server.get_in_object(ctx)
335
336 self.assertEqual(ctx.in_object.DeviceId.OUI, '000FB7')
337 self.assertEqual(
338 ctx.in_object.Event.EventStruct[0].EventCode, '0 BOOTSTRAP',
339 )
340 self.assertEqual(ctx.in_object.MaxEnvelopes, 1)
341 self.assertEqual(
342 ctx.in_object.ParameterList.ParameterValueStruct[1].Name,
343 'Device.DeviceInfo.SoftwareVersion',
344 )
345 self.assertEqual(
346 str(ctx.in_object.ParameterList.ParameterValueStruct[1].Value), '1.0',
347 )
348
349 def test_handle_transfer_complete(self):
350 """
351 Test that example TransferComplete RPC call can be parsed correctly, and
352 response is correctly generated.
353 """
354 # Example TransferComplete CPE->ACS RPC request/response.
355 # Manually created.
356 cpe_string = b'''
357 <soapenv:Envelope soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:soap="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cwmp="urn:dslforum-org:cwmp-1-0">
358 <soapenv:Header>
359 <cwmp:ID soapenv:mustUnderstand="1">1234</cwmp:ID>
360 </soapenv:Header>
361 <soapenv:Body>
362 <cwmp:TransferComplete>
363 <CommandKey>Downloading stuff</CommandKey>
364 <FaultStruct>
365 <FaultCode>0</FaultCode>
366 <FaultString></FaultString>
367 </FaultStruct>
368 <StartTime>2016-11-30T10:16:29Z</StartTime>
369 <CompleteTime>2016-11-30T10:17:05Z</CompleteTime>
370 </cwmp:TransferComplete>
371 </soapenv:Body>
372 </soapenv:Envelope>
373 '''
374 expected_acs_string = b'''
375 <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:soap="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cwmp="urn:dslforum-org:cwmp-1-0">
376 <soapenv:Header>
377 <cwmp:ID soapenv:mustUnderstand="1">1234</cwmp:ID>
378 </soapenv:Header>
379 <soapenv:Body>
380 <cwmp:TransferCompleteResponse>
381 </cwmp:TransferCompleteResponse>
382 </soapenv:Body>
383 </soapenv:Envelope>
384 '''
385
386 self.p.stop()
387 self.p.start()
388
389 server = ServerBase(self.app)
390
391 ctx = MethodContext(server, MethodContext.SERVER)
392 ctx.in_string = [cpe_string]
393 ctx, = server.generate_contexts(ctx)
394
395 if ctx.in_error is not None:
396 print('In error: %s' % ctx.in_error)
397 self.assertEqual(ctx.in_error, None)
398
399 server.get_in_object(ctx)
400 self.assertEqual(ctx.in_error, None)
401
402 server.get_out_object(ctx)
403 self.assertEqual(ctx.out_error, None)
404
405 output_msg = ctx.out_object[0]
406 self.assertEqual(type(output_msg), models.TransferComplete)
407 self.assertEqual(output_msg.CommandKey, 'Downloading stuff')
408 self.assertEqual(output_msg.FaultStruct.FaultCode, 0)
409 self.assertEqual(output_msg.FaultStruct.FaultString, '')
410 self.assertEqual(
411 output_msg.StartTime,
412 datetime(
413 2016, 11, 30, 10, 16, 29,
414 tzinfo=timezone(timedelta(0)),
415 ),
416 )
417 self.assertEqual(
418 output_msg.CompleteTime,
419 datetime(
420 2016, 11, 30, 10, 17, 5,
421 tzinfo=timezone(timedelta(0)),
422 ),
423 )
424
425 server.get_out_string(ctx)
426 self.assertEqual(ctx.out_error, None)
427
428 xml_tree = XmlTree()
429 match = xml_tree.xml_compare(
430 xml_tree.convert_string_to_tree(b''.join(ctx.out_string)),
431 xml_tree.convert_string_to_tree(expected_acs_string),
432 )
433 self.assertTrue(match)
434
435 def test_parse_empty_http(self):
436 """
437 Test that empty HTTP message gets correctly mapped to 'EmptyHttp'
438 function call
439 """
440 cpe_string = b''
441
442 server = ServerBase(self.app)
443
444 ctx = MethodContext(server, MethodContext.SERVER)
445 ctx.in_string = [cpe_string]
446 ctx, = server.generate_contexts(ctx)
447
448 if ctx.in_error is not None:
449 print('In error: %s' % ctx.in_error)
450
451 self.assertEqual(ctx.in_error, None)
452 self.assertEqual(ctx.function, AutoConfigServer.empty_http)
453
454 def test_generate_empty_http(self):
455 """
456 Test that empty HTTP message is generated when setting output message
457 name to 'EmptyHttp'
458 """
459 cpe_string = b''
460
461 server = ServerBase(self.app)
462
463 ctx = MethodContext(server, MethodContext.SERVER)
464 ctx.in_string = [cpe_string]
465 ctx, = server.generate_contexts(ctx)
466
467 server.get_in_object(ctx)
468 if ctx.in_error is not None:
469 raise ctx.in_error
470
471 server.get_out_object(ctx)
472 if ctx.out_error is not None:
473 raise ctx.out_error
474
475 ctx.descriptor.out_message.Attributes.sub_name = 'EmptyHttp'
476 ctx.out_object = [models.AcsToCpeRequests()]
477
478 server.get_out_string(ctx)
479
480 self.assertEqual(b''.join(ctx.out_string), b'')
481
482 def test_generate_get_parameter_values_string(self):
483 """
484 Test that correct string is generated for SetParameterValues ACS->CPE
485 request
486 """
487 # Example ACS->CPE RPC call. Copied from:
488 # http://djuro82.blogspot.com/2011/05/tr-069-cpe-provisioning.html
489 # Following edits made:
490 # - Change header ID value from 'null0' to 'null', to match magma
491 # default ID
492 expected_acs_string = b'''
493 <soapenv:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:cwmp="urn:dslforum-org:cwmp-1-0" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
494 <soapenv:Header>
495 <cwmp:ID soapenv:mustUnderstand="1">null</cwmp:ID>
496 </soapenv:Header>
497 <soapenv:Body>
498 <cwmp:GetParameterValues>
499 <ParameterNames soap:arrayType="xsd:string[1]">
500 <string>foo</string>
501 </ParameterNames>
502 </cwmp:GetParameterValues>
503 </soapenv:Body>
504 </soapenv:Envelope>
505 '''
506
507 names = ['foo']
508 request = models.GetParameterValues()
509 request.ParameterNames = models.ParameterNames()
510 request.ParameterNames.arrayType = 'xsd:string[%d]' \
511 % len(names)
512 request.ParameterNames.string = []
513 for name in names:
514 request.ParameterNames.string.append(name)
515
516 request.ParameterKey = 'null'
517
518 def side_effect(*args, **_kwargs):
519 ctx = args[0]
520 ctx.out_header = models.ID(mustUnderstand='1')
521 ctx.out_header.Data = 'null'
522 ctx.descriptor.out_message.Attributes.sub_name = \
523 request.__class__.__name__
524 return AutoConfigServer._generate_acs_to_cpe_request_copy(request)
525
526 self.p.stop()
527 self.p = patch.object(
528 AutoConfigServer, '_handle_tr069_message',
529 side_effect=side_effect,
530 )
531 self.p.start()
532
533 server = ServerBase(self.app)
534
535 ctx = MethodContext(server, MethodContext.SERVER)
536 ctx.in_string = [b'']
537 ctx, = server.generate_contexts(ctx)
538
539 server.get_in_object(ctx)
540 if ctx.in_error is not None:
541 raise ctx.in_error
542
543 server.get_out_object(ctx)
544 if ctx.out_error is not None:
545 raise ctx.out_error
546
547 server.get_out_string(ctx)
548
549 xml_tree = XmlTree()
550 match = xml_tree.xml_compare(
551 xml_tree.convert_string_to_tree(b''.join(ctx.out_string)),
552 xml_tree.convert_string_to_tree(expected_acs_string),
553 )
554 self.assertTrue(match)
555
556 def test_generate_set_parameter_values_string(self):
557 """
558 Test that correct string is generated for SetParameterValues ACS->CPE
559 request
560 """
561 # Example ACS->CPE RPC call. Copied from:
562 # http://djuro82.blogspot.com/2011/05/tr-069-cpe-provisioning.html
563 # Following edits made:
564 # - Change header ID value from 'null0' to 'null', to match magma
565 # default ID
566 expected_acs_string = b'''
567 <soapenv:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:cwmp="urn:dslforum-org:cwmp-1-0" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
568 <soapenv:Header>
569 <cwmp:ID soapenv:mustUnderstand="1">null</cwmp:ID>
570 </soapenv:Header>
571 <soapenv:Body>
572 <cwmp:SetParameterValues>
573 <ParameterList soap:arrayType="cwmp:ParameterValueStruct[4]">
574 <ParameterValueStruct>
575 <Name>InternetGatewayDevice.ManagementServer.PeriodicInformEnable</Name>
576 <Value xsi:type="xsd:boolean">1</Value>
577 </ParameterValueStruct>
578 <ParameterValueStruct>
579 <Name>InternetGatewayDevice.ManagementServer.ConnectionRequestUsername</Name>
580 <Value xsi:type="xsd:string">00147F-SpeedTouch780-CP0611JTLNW</Value>
581 </ParameterValueStruct>
582 <ParameterValueStruct>
583 <Name>InternetGatewayDevice.ManagementServer.ConnectionRequestPassword</Name>
584 <Value xsi:type="xsd:string">98ff55fb377bf724c625f60dec448646</Value>
585 </ParameterValueStruct>
586 <ParameterValueStruct>
587 <Name>InternetGatewayDevice.ManagementServer.PeriodicInformInterval</Name>
588 <Value xsi:type="xsd:unsignedInt">60</Value>
589 </ParameterValueStruct>
590 </ParameterList>
591 <ParameterKey xsi:type="xsd:string">SetParameter1</ParameterKey>
592 </cwmp:SetParameterValues>
593 </soapenv:Body>
594 </soapenv:Envelope>
595 '''
596
597 request = models.SetParameterValues()
598
599 request.ParameterList = \
600 models.ParameterValueList(arrayType='cwmp:ParameterValueStruct[4]')
601 request.ParameterList.ParameterValueStruct = []
602
603 param = models.ParameterValueStruct()
604 param.Name = 'InternetGatewayDevice.ManagementServer.PeriodicInformEnable'
605 param.Value = models.anySimpleType(type='xsd:boolean')
606 param.Value.Data = '1'
607 request.ParameterList.ParameterValueStruct.append(param)
608
609 param = models.ParameterValueStruct()
610 param.Name = 'InternetGatewayDevice.ManagementServer.ConnectionRequestUsername'
611 param.Value = models.anySimpleType(type='xsd:string')
612 param.Value.Data = '00147F-SpeedTouch780-CP0611JTLNW'
613 request.ParameterList.ParameterValueStruct.append(param)
614
615 param = models.ParameterValueStruct()
616 param.Name = 'InternetGatewayDevice.ManagementServer.ConnectionRequestPassword'
617 param.Value = models.anySimpleType(type='xsd:string')
618 param.Value.Data = '98ff55fb377bf724c625f60dec448646'
619 request.ParameterList.ParameterValueStruct.append(param)
620
621 param = models.ParameterValueStruct()
622 param.Name = 'InternetGatewayDevice.ManagementServer.PeriodicInformInterval'
623 param.Value = models.anySimpleType(type='xsd:unsignedInt')
624 param.Value.Data = '60'
625 request.ParameterList.ParameterValueStruct.append(param)
626
627 request.ParameterKey = models.ParameterKeyType()
628 request.ParameterKey.type = 'xsd:string'
629 request.ParameterKey.Data = 'SetParameter1'
630
631 def side_effect(*args, **_kwargs):
632 ctx = args[0]
633 ctx.out_header = models.ID(mustUnderstand='1')
634 ctx.out_header.Data = 'null'
635 ctx.descriptor.out_message.Attributes.sub_name = request.__class__.__name__
636 return request
637
638 self.p.stop()
639 self.p = patch.object(
640 AutoConfigServer, '_handle_tr069_message',
641 Mock(side_effect=side_effect),
642 )
643 self.p.start()
644
645 server = ServerBase(self.app)
646
647 ctx = MethodContext(server, MethodContext.SERVER)
648 ctx.in_string = [b'']
649 ctx, = server.generate_contexts(ctx)
650
651 server.get_in_object(ctx)
652 if ctx.in_error is not None:
653 raise ctx.in_error
654
655 server.get_out_object(ctx)
656 if ctx.out_error is not None:
657 raise ctx.out_error
658
659 server.get_out_string(ctx)
660
661 xml_tree = XmlTree()
662 NS_SOAP11_ENC = 'soap11enc'
663 NS_SOAP11_ENV = 'soap11env'
664 xml_str = b''.join(ctx.out_string)
665 # Get the namespaces and validate the soap enc and env prefix are right
666 nsmap = xml_tree.get_ns(xml_str)
667 self.assertTrue(NS_SOAP11_ENC in nsmap.keys())
668 self.assertTrue(NS_SOAP11_ENV in nsmap.keys())
669
670 match = xml_tree.xml_compare(
671 xml_tree.convert_string_to_tree(xml_str),
672 xml_tree.convert_string_to_tree(expected_acs_string),
673 )
674 self.assertTrue(match)
675
676 def test_parse_fault_response(self):
677 """ Tests that a fault response from CPE is correctly parsed. """
678 # Example CPE->ACS fault response. Copied from:
679 # http://djuro82.blogspot.com/2011/05/tr-069-cpe-provisioning.html
680 cpe_string = b'''
681 <soapenv:Envelope soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:soap="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cwmp="urn:dslforum-org:cwmp-1-0">
682 <soapenv:Header>
683 <cwmp:ID soapenv:mustUnderstand="1">1031422463</cwmp:ID>
684 </soapenv:Header>
685 <soapenv:Body>
686 <soapenv:Fault>
687 <faultcode>Client</faultcode>
688 <faultstring>CWMP fault</faultstring>
689 <detail>
690 <cwmp:Fault>
691 <FaultCode>9003</FaultCode>
692 <FaultString>Invalid arguments</FaultString>
693 <SetParameterValuesFault>
694 <ParameterName>InternetGatewayDevice.WANDevice.1.WANConnectionDevice.3.WANPPPConnection.1.Password</ParameterName>
695 <FaultCode>9003</FaultCode>
696 <FaultString>Invalid arguments</FaultString>
697 </SetParameterValuesFault>
698 <SetParameterValuesFault>
699 <ParameterName>InternetGatewayDevice.WANDevice.1.WANConnectionDevice.3.WANPPPConnection.1.Username</ParameterName>
700 <FaultCode>9003</FaultCode>
701 <FaultString>Invalid arguments</FaultString>
702 </SetParameterValuesFault>
703 </cwmp:Fault>
704 </detail>
705 </soapenv:Fault>
706 </soapenv:Body>
707 </soapenv:Envelope>
708 '''
709 server = ServerBase(self.app)
710
711 ctx = MethodContext(server, MethodContext.SERVER)
712 ctx.in_string = [cpe_string]
713 ctx, = server.generate_contexts(ctx)
714 server.get_in_object(ctx)
715 if ctx.in_error is not None:
716 raise ctx.in_error
717
718 # Calls function to receive and process message
719 server.get_out_object(ctx)
720
721 output_msg = ctx.out_object[0]
722 self.assertEqual(type(output_msg), models.Fault)
723 self.assertEqual(output_msg.FaultCode, 9003)
724 self.assertEqual(output_msg.FaultString, 'Invalid arguments')
725 self.assertEqual(
726 output_msg.SetParameterValuesFault[1].ParameterName,
727 'InternetGatewayDevice.WANDevice.1.WANConnectionDevice.3.WANPPPConnection.1.Username',
728 )
729 self.assertEqual(output_msg.SetParameterValuesFault[1].FaultCode, 9003)
730 self.assertEqual(
731 output_msg.SetParameterValuesFault[1].FaultString,
732 'Invalid arguments',
733 )
734
735 def test_parse_hex_values(self):
736 """
737 Test that non-utf-8 hex values can be parsed without error
738 """
739 # Example TR-069 CPE->ACS RPC call. Copied from:
740 # http://djuro82.blogspot.com/2011/05/tr-069-cpe-provisioning.html
741 cpe_string = b'''
742 <soapenv:Envelope soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:soap="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cwmp="urn:dslforum-org:cwmp-1-0">
743 <soapenv:Header>
744 <cwmp:ID soapenv:mustUnderstand="1">0_THOM_TR69_ID</cwmp:ID>
745 </soapenv:Header>
746 <soapenv:Body>
747 <cwmp:Inform>
748 <DeviceId>
749 <Manufacturer>THOMSON</Manufacturer>
750 <OUI>00147F</OUI>
751 <ProductClass>SpeedTouch 780</ProductClass>
752 <SerialNumber>CP0611JTLNW</SerialNumber>
753 </DeviceId>
754 <Event soap:arrayType="cwmp:EventStruct[04]">
755 <EventStruct>
756 <EventCode>0 BOOTSTRAP</EventCode>
757 <CommandKey></CommandKey>
758 </EventStruct>
759 <EventStruct>
760 <EventCode>1 BOOT</EventCode>
761 <CommandKey></CommandKey>
762 </EventStruct>
763 <EventStruct>
764 <EventCode>2 PERIODIC</EventCode>
765 <CommandKey></CommandKey>
766 </EventStruct>
767 <EventStruct>
768 <EventCode>4 VALUE CHANGE</EventCode>
769 <CommandKey></CommandKey>
770 </EventStruct>
771 </Event>
772 <MaxEnvelopes>2</MaxEnvelopes>
773 <CurrentTime>1970-01-01T00:01:09Z</CurrentTime>
774 <RetryCount>05</RetryCount>
775 <ParameterList soap:arrayType="cwmp:ParameterValueStruct[12]">
776 <ParameterValueStruct>
777 <Name>InternetGatewayDevice.DeviceSummary</Name>
778 <Value xsi:type="xsd:string">
779 \xff\xff\xff\xff\xff</Value>
780 </ParameterValueStruct>
781 </ParameterList>
782 </cwmp:Inform>
783 </soapenv:Body>
784 </soapenv:Envelope>
785 '''
786
787 server = ServerBase(self.app)
788
789 ctx = MethodContext(server, MethodContext.SERVER)
790 ctx.in_string = [cpe_string]
791 ctx, = server.generate_contexts(ctx)
792
793 if ctx.in_error is not None:
794 print('In error: %s' % ctx.in_error)
795 self.assertEqual(ctx.in_error, None)
796
797 server.get_in_object(ctx)
798
799
800class XmlTree():
801
802 @staticmethod
803 def convert_string_to_tree(xmlString):
804
805 return ET.fromstring(xmlString)
806
807 @staticmethod
808 def get_ns(xmlString):
809 return ET.fromstring(xmlString).nsmap
810
811 def xml_compare(self, x1, x2, excludes=None):
812 """
813 Compares two xml etrees
814 :param x1: the first tree
815 :param x2: the second tree
816 :param excludes: list of string of attributes to exclude from comparison
817 :return:
818 True if both files match
819 """
820 excludes = [] if excludes is None else excludes
821
822 if x1.tag != x2.tag:
823 print('Tags do not match: %s and %s' % (x1.tag, x2.tag))
824 return False
825 for name, value in x1.attrib.items():
826 if name not in excludes:
827 if x2.attrib.get(name) != value:
828 print(
829 'Attributes do not match: %s=%r, %s=%r'
830 % (name, value, name, x2.attrib.get(name)),
831 )
832 return False
833 for name in x2.attrib.keys():
834 if name not in excludes:
835 if name not in x1.attrib:
836 print(
837 'x2 has an attribute x1 is missing: %s'
838 % name,
839 )
840 return False
841 if not self.text_compare(x1.text, x2.text):
842 print('text: %r != %r' % (x1.text, x2.text))
843 return False
844 if not self.text_compare(x1.tail, x2.tail):
845 print('tail: %r != %r' % (x1.tail, x2.tail))
846 return False
847 cl1 = x1.getchildren()
848 cl2 = x2.getchildren()
849 if len(cl1) != len(cl2):
850 print(
851 'children length differs, %i != %i'
852 % (len(cl1), len(cl2)),
853 )
854 return False
855 i = 0
856 for c1, c2 in zip(cl1, cl2):
857 i += 1
858 if c1.tag not in excludes:
859 if not self.xml_compare(c1, c2, excludes):
860 print(
861 'children %i do not match: %s'
862 % (i, c1.tag),
863 )
864 return False
865 return True
866
867 def text_compare(self, t1, t2):
868 """
869 Compare two text strings
870 :param t1: text one
871 :param t2: text two
872 :return:
873 True if a match
874 """
875 if not t1 and not t2:
876 return True
877 if t1 == '*' or t2 == '*':
878 return True
879 return (t1 or '').strip() == (t2 or '').strip()